diff --git a/.github/actions/setup-ruby/action.yml b/.github/actions/setup-ruby/action.yml index fed79add6d..2993c66f7b 100644 --- a/.github/actions/setup-ruby/action.yml +++ b/.github/actions/setup-ruby/action.yml @@ -14,7 +14,7 @@ runs: shell: bash run: | sudo apt-get update - sudo apt-get install -y libicu-dev libidn11-dev libvips42 ${{ inputs.additional-system-dependencies }} + sudo apt-get install --no-install-recommends -y libicu-dev libidn11-dev libvips42 ${{ inputs.additional-system-dependencies }} - name: Set up Ruby uses: ruby/setup-ruby@4eb9f110bac952a8b68ecf92e3b5c7a987594ba6 # v1 diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index 240261760e..d0c1d4fbcd 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -124,7 +124,6 @@ jobs: fail-fast: false matrix: ruby-version: - - '3.2' - '3.3' - '.ruby-version' steps: @@ -217,7 +216,6 @@ jobs: fail-fast: false matrix: ruby-version: - - '3.2' - '3.3' - '.ruby-version' @@ -348,7 +346,6 @@ jobs: fail-fast: false matrix: ruby-version: - - '3.2' - '3.3' - '.ruby-version' search-image: @@ -387,10 +384,3 @@ jobs: with: name: test-search-logs-${{ matrix.ruby-version }} path: log/ - - - name: Archive test screenshots - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 - if: failure() - with: - name: test-search-screenshots - path: tmp/capybara/ diff --git a/.rubocop.yml b/.rubocop.yml index 1bbba515af..425709187c 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -8,7 +8,7 @@ AllCops: - lib/mastodon/migration_helpers.rb ExtraDetails: true NewCops: enable - TargetRubyVersion: 3.2 # Oldest supported ruby version + TargetRubyVersion: 3.3 # Oldest supported ruby version inherit_from: - .rubocop/layout.yml diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index b8b9bd385e..4fb559ccf9 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -137,11 +137,7 @@ const preview: Preview = { }, [currentLocale, currentLocaleData]); return ( - + ); diff --git a/Gemfile b/Gemfile index 40f95d56e9..8f283db728 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,7 @@ # frozen_string_literal: true source 'https://rubygems.org' -ruby '>= 3.2.0', '< 3.5.0' +ruby '>= 3.3.0', '< 3.5.0' gem 'propshaft' gem 'puma', '~> 7.0' diff --git a/Gemfile.lock b/Gemfile.lock index cc3843b19b..d5d71953b4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -12,29 +12,29 @@ GEM specs: action_text-trix (2.1.17) railties - actioncable (8.1.2.1) - actionpack (= 8.1.2.1) - activesupport (= 8.1.2.1) + actioncable (8.1.3) + actionpack (= 8.1.3) + activesupport (= 8.1.3) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (8.1.2.1) - actionpack (= 8.1.2.1) - activejob (= 8.1.2.1) - activerecord (= 8.1.2.1) - activestorage (= 8.1.2.1) - activesupport (= 8.1.2.1) + actionmailbox (8.1.3) + actionpack (= 8.1.3) + activejob (= 8.1.3) + activerecord (= 8.1.3) + activestorage (= 8.1.3) + activesupport (= 8.1.3) mail (>= 2.8.0) - actionmailer (8.1.2.1) - actionpack (= 8.1.2.1) - actionview (= 8.1.2.1) - activejob (= 8.1.2.1) - activesupport (= 8.1.2.1) + actionmailer (8.1.3) + actionpack (= 8.1.3) + actionview (= 8.1.3) + activejob (= 8.1.3) + activesupport (= 8.1.3) mail (>= 2.8.0) rails-dom-testing (~> 2.2) - actionpack (8.1.2.1) - actionview (= 8.1.2.1) - activesupport (= 8.1.2.1) + actionpack (8.1.3) + actionview (= 8.1.3) + activesupport (= 8.1.3) nokogiri (>= 1.8.5) rack (>= 2.2.4) rack-session (>= 1.0.1) @@ -42,16 +42,16 @@ GEM rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) useragent (~> 0.16) - actiontext (8.1.2.1) + actiontext (8.1.3) action_text-trix (~> 2.1.15) - actionpack (= 8.1.2.1) - activerecord (= 8.1.2.1) - activestorage (= 8.1.2.1) - activesupport (= 8.1.2.1) + actionpack (= 8.1.3) + activerecord (= 8.1.3) + activestorage (= 8.1.3) + activesupport (= 8.1.3) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (8.1.2.1) - activesupport (= 8.1.2.1) + actionview (8.1.3) + activesupport (= 8.1.3) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) @@ -61,22 +61,22 @@ GEM activemodel (>= 4.1) case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) - activejob (8.1.2.1) - activesupport (= 8.1.2.1) + activejob (8.1.3) + activesupport (= 8.1.3) globalid (>= 0.3.6) - activemodel (8.1.2.1) - activesupport (= 8.1.2.1) - activerecord (8.1.2.1) - activemodel (= 8.1.2.1) - activesupport (= 8.1.2.1) + activemodel (8.1.3) + activesupport (= 8.1.3) + activerecord (8.1.3) + activemodel (= 8.1.3) + activesupport (= 8.1.3) timeout (>= 0.4.0) - activestorage (8.1.2.1) - actionpack (= 8.1.2.1) - activejob (= 8.1.2.1) - activerecord (= 8.1.2.1) - activesupport (= 8.1.2.1) + activestorage (8.1.3) + actionpack (= 8.1.3) + activejob (= 8.1.3) + activerecord (= 8.1.3) + activesupport (= 8.1.3) marcel (~> 1.0) - activesupport (8.1.2.1) + activesupport (8.1.3) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.3.1) @@ -354,7 +354,7 @@ GEM azure-blob (~> 0.5.2) hashie (~> 5.0) jmespath (1.6.2) - json (2.19.2) + json (2.19.3) json-canonicalization (1.0.0) json-jwt (1.17.0) activesupport (>= 4.2) @@ -657,20 +657,20 @@ GEM rack (>= 1.3) rackup (2.3.1) rack (>= 3) - rails (8.1.2.1) - actioncable (= 8.1.2.1) - actionmailbox (= 8.1.2.1) - actionmailer (= 8.1.2.1) - actionpack (= 8.1.2.1) - actiontext (= 8.1.2.1) - actionview (= 8.1.2.1) - activejob (= 8.1.2.1) - activemodel (= 8.1.2.1) - activerecord (= 8.1.2.1) - activestorage (= 8.1.2.1) - activesupport (= 8.1.2.1) + rails (8.1.3) + actioncable (= 8.1.3) + actionmailbox (= 8.1.3) + actionmailer (= 8.1.3) + actionpack (= 8.1.3) + actiontext (= 8.1.3) + actionview (= 8.1.3) + activejob (= 8.1.3) + activemodel (= 8.1.3) + activerecord (= 8.1.3) + activestorage (= 8.1.3) + activesupport (= 8.1.3) bundler (>= 1.15.0) - railties (= 8.1.2.1) + railties (= 8.1.3) rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest @@ -681,9 +681,9 @@ GEM rails-i18n (8.1.0) i18n (>= 0.7, < 2) railties (>= 8.0.0, < 9) - railties (8.1.2.1) - actionpack (= 8.1.2.1) - activesupport (= 8.1.2.1) + railties (8.1.3) + actionpack (= 8.1.3) + activesupport (= 8.1.3) irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) diff --git a/README.md b/README.md index 82c67b05e8..3bf5968b64 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ Mastodon is a **free, open-source social network server** based on [ActivityPub] ### Requirements -- **Ruby** 3.2+ +- **Ruby** 3.3+ - **PostgreSQL** 14+ - **Redis** 7.0+ - **Node.js** 20+ diff --git a/app/controllers/api/v1/accounts/email_subscriptions_controller.rb b/app/controllers/api/v1/accounts/email_subscriptions_controller.rb new file mode 100644 index 0000000000..dcdd41f6db --- /dev/null +++ b/app/controllers/api/v1/accounts/email_subscriptions_controller.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +class Api::V1::Accounts::EmailSubscriptionsController < Api::BaseController + before_action :set_account + before_action :require_feature_enabled! + before_action :require_account_permissions! + + def create + @account.email_subscriptions.create!(email: params[:email], locale: I18n.locale) + render_empty + end + + private + + def set_account + @account = Account.local.find(params[:account_id]) + end + + def require_feature_enabled! + head 404 unless Mastodon::Feature.email_subscriptions_enabled? + end + + def require_account_permissions! + head 404 if @account.unavailable? || !@account.user_can?(:manage_email_subscriptions) || !@account.user_email_subscriptions_enabled? + end +end diff --git a/app/controllers/api/v1/instances/terms_of_service_controller.rb b/app/controllers/api/v1/instances/terms_of_service_controller.rb new file mode 100644 index 0000000000..9968b41317 --- /dev/null +++ b/app/controllers/api/v1/instances/terms_of_service_controller.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class Api::V1::Instances::TermsOfServiceController < Api::V1::Instances::BaseController + before_action :cache_even_if_authenticated! + + def index + @terms_of_service = TermsOfService.current || raise(ActiveRecord::RecordNotFound) + render json: @terms_of_service, serializer: REST::TermsOfServiceSerializer + end + + def show + @terms_of_service = TermsOfService.published.find_by!(effective_date: params[:date]) + render json: @terms_of_service, serializer: REST::TermsOfServiceSerializer + end +end diff --git a/app/controllers/api/v1/instances/terms_of_services_controller.rb b/app/controllers/api/v1/instances/terms_of_services_controller.rb deleted file mode 100644 index a32438e31d..0000000000 --- a/app/controllers/api/v1/instances/terms_of_services_controller.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -class Api::V1::Instances::TermsOfServicesController < Api::V1::Instances::BaseController - before_action :set_terms_of_service - - def show - cache_even_if_authenticated! - render json: @terms_of_service, serializer: REST::TermsOfServiceSerializer - end - - private - - def set_terms_of_service - @terms_of_service = begin - if params[:date].present? - TermsOfService.published.find_by!(effective_date: params[:date]) - else - TermsOfService.current - end - end - not_found if @terms_of_service.nil? - end -end diff --git a/app/controllers/concerns/signature_verification.rb b/app/controllers/concerns/signature_verification.rb index 1e83ab9c69..f752e5cd93 100644 --- a/app/controllers/concerns/signature_verification.rb +++ b/app/controllers/concerns/signature_verification.rb @@ -53,19 +53,21 @@ module SignatureVerification raise Mastodon::SignatureVerificationError, 'Request not signed' unless signed_request? - actor = actor_from_key_id + keypair = keypair_from_key_id - raise Mastodon::SignatureVerificationError, "Public key not found for key #{signature_key_id}" if actor.nil? + raise Mastodon::SignatureVerificationError, "Public key not found for key #{signature_key_id}" if keypair.nil? - return (@signed_request_actor = actor) if signed_request.verified?(actor) + check_keypair_validity!(keypair) + return (@signed_request_actor = keypair.actor) if signed_request.verified?(keypair) - actor = stoplight_wrapper.run { actor_refresh_key!(actor) } + keypair = stoplight_wrapper.run { keypair_refresh_key!(keypair) } - raise Mastodon::SignatureVerificationError, "Could not refresh public key #{signature_key_id}" if actor.nil? + raise Mastodon::SignatureVerificationError, "Could not refresh public key #{signature_key_id}" if keypair.nil? - return (@signed_request_actor = actor) if signed_request.verified?(actor) + check_keypair_validity!(keypair) + return (@signed_request_actor = keypair.actor) if signed_request.verified?(keypair) - fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri}" + fail_with! "Verification failed for #{keypair.actor.to_log_human_identifier} #{keypair.actor.uri} #{keypair.uri}" rescue Mastodon::MalformedHeaderError => e @signature_verification_failure_code = 400 fail_with! e.message @@ -89,7 +91,7 @@ module SignatureVerification @signed_request_actor = nil end - def actor_from_key_id + def keypair_from_key_id key_id = signed_request.key_id domain = key_id.start_with?('acct:') ? key_id.split('@').last : key_id @@ -101,9 +103,10 @@ module SignatureVerification if key_id.start_with?('acct:') stoplight_wrapper.run { ResolveAccountService.new.call(key_id.delete_prefix('acct:'), suppress_errors: false) } elsif !ActivityPub::TagManager.instance.local_uri?(key_id) - account = ActivityPub::TagManager.instance.uri_to_actor(key_id) - account ||= stoplight_wrapper.run { ActivityPub::FetchRemoteKeyService.new.call(key_id, suppress_errors: false) } - account + keypair = Keypair.from_keyid(key_id) + return keypair if keypair.present? + + stoplight_wrapper.run { ActivityPub::FetchRemoteKeyService.new.call(key_id, suppress_errors: false) } end rescue Mastodon::PrivateNetworkAddressError => e raise Mastodon::SignatureVerificationError, "Requests to private network addresses are disallowed (tried to query #{e.host})" @@ -120,14 +123,20 @@ module SignatureVerification ) end - def actor_refresh_key!(actor) - return if actor.local? || !actor.activitypub? - return actor.refresh! if actor.respond_to?(:refresh!) && actor.possibly_stale? + def keypair_refresh_key!(keypair) + # TODO: this currently only is concerned with refreshing the actor and returning the legacy key, this needs to be reworked + return if keypair.actor.local? || !keypair.actor.activitypub? + return keypair.actor.refresh! if keypair.actor.respond_to?(:refresh!) && keypair.actor.possibly_stale? - ActivityPub::FetchRemoteActorService.new.call(actor.uri, only_key: true, suppress_errors: false) + Keypair.from_legacy_account(ActivityPub::FetchRemoteActorService.new.call(keypair.actor.uri, only_key: true, suppress_errors: false)) rescue Mastodon::PrivateNetworkAddressError => e raise Mastodon::SignatureVerificationError, "Requests to private network addresses are disallowed (tried to query #{e.host})" rescue Mastodon::HostValidationError, ActivityPub::FetchRemoteActorService::Error, Webfinger::Error => e raise Mastodon::SignatureVerificationError, e.message end + + def check_keypair_validity!(keypair) + raise Mastodon::SignatureVerification, "Key #{signature_key_id} is revoked" if keypair.revoked? + raise Mastodon::SignatureVerification, "Key #{signature_key_id} has expired" if keypair.expired? + end end diff --git a/app/controllers/email_subscriptions/confirmations_controller.rb b/app/controllers/email_subscriptions/confirmations_controller.rb new file mode 100644 index 0000000000..2750b68d4f --- /dev/null +++ b/app/controllers/email_subscriptions/confirmations_controller.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class EmailSubscriptions::ConfirmationsController < ApplicationController + layout 'auth' + + before_action :set_email_subscription + + def show + @email_subscription.confirm! unless @email_subscription.confirmed? + end + + private + + def set_email_subscription + @email_subscription = EmailSubscription.find_by!(confirmation_token: params[:confirmation_token]) + end +end diff --git a/app/controllers/mail_subscriptions_controller.rb b/app/controllers/mail_subscriptions_controller.rb deleted file mode 100644 index 34df75f63a..0000000000 --- a/app/controllers/mail_subscriptions_controller.rb +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: true - -class MailSubscriptionsController < ApplicationController - layout 'auth' - - skip_before_action :require_functional! - - before_action :set_user - before_action :set_type - - protect_from_forgery with: :null_session - - def show; end - - def create - @user.settings[email_type_from_param] = false - @user.save! - end - - private - - def set_user - @user = GlobalID::Locator.locate_signed(params[:token], for: 'unsubscribe') - not_found unless @user - end - - def set_type - @type = email_type_from_param - end - - def email_type_from_param - case params[:type] - when 'follow', 'reblog', 'favourite', 'mention', 'follow_request' - "notification_emails.#{params[:type]}" - else - not_found - end - end -end diff --git a/app/controllers/settings/privacy_controller.rb b/app/controllers/settings/privacy_controller.rb index 96efa03ccf..2716fce806 100644 --- a/app/controllers/settings/privacy_controller.rb +++ b/app/controllers/settings/privacy_controller.rb @@ -2,6 +2,7 @@ class Settings::PrivacyController < Settings::BaseController before_action :set_account + before_action :set_email_subscriptions_count def show; end @@ -24,4 +25,8 @@ class Settings::PrivacyController < Settings::BaseController def set_account @account = current_account end + + def set_email_subscriptions_count + @email_subscriptions_count = with_read_replica { @account.email_subscriptions.confirmed.count } + end end diff --git a/app/controllers/unsubscriptions_controller.rb b/app/controllers/unsubscriptions_controller.rb new file mode 100644 index 0000000000..aac58a3806 --- /dev/null +++ b/app/controllers/unsubscriptions_controller.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +class UnsubscriptionsController < ApplicationController + layout 'auth' + + skip_before_action :require_functional! + + before_action :set_recipient + before_action :set_type + before_action :set_scope + before_action :require_type_if_user! + + protect_from_forgery with: :null_session + + def show; end + + def create + case @scope + when :user + @recipient.settings[@type] = false + @recipient.save! + when :email_subscription + @recipient.destroy! + end + end + + private + + def set_recipient + @recipient = GlobalID::Locator.locate_signed(params[:token], for: 'unsubscribe') + not_found unless @recipient + end + + def set_scope + if @recipient.is_a?(User) + @scope = :user + elsif @recipient.is_a?(EmailSubscription) + @scope = :email_subscription + else + not_found + end + end + + def set_type + @type = email_type_from_param + end + + def require_type_if_user! + not_found if @recipient.is_a?(User) && @type.blank? + end + + def email_type_from_param + case params[:type] + when 'follow', 'reblog', 'favourite', 'mention', 'follow_request' + "notification_emails.#{params[:type]}" + end + end +end diff --git a/app/controllers/well_known/webfinger_controller.rb b/app/controllers/well_known/webfinger_controller.rb index 72f0ea890f..9536b948f3 100644 --- a/app/controllers/well_known/webfinger_controller.rb +++ b/app/controllers/well_known/webfinger_controller.rb @@ -18,23 +18,7 @@ module WellKnown private def set_account - username = username_from_resource - - @account = begin - if username == Rails.configuration.x.local_domain || username == Rails.configuration.x.web_domain - Account.representative - else - Account.find_local!(username) - end - end - end - - def username_from_resource - resource_user = resource_param - username, domain = resource_user.split('@') - resource_user = "#{username}@#{Rails.configuration.x.local_domain}" if Rails.configuration.x.alternate_domains.include?(domain) - - WebfingerResource.new(resource_user).username + @account = WebfingerResource.new(resource_param).account end def resource_param diff --git a/app/helpers/context_helper.rb b/app/helpers/context_helper.rb index aedadafe08..ee8090939e 100644 --- a/app/helpers/context_helper.rb +++ b/app/helpers/context_helper.rb @@ -4,6 +4,7 @@ module ContextHelper NAMED_CONTEXT_MAP = { activitystreams: 'https://www.w3.org/ns/activitystreams', security: 'https://w3id.org/security/v1', + webfinger: 'https://purl.archive.org/socialweb/webfinger', }.freeze CONTEXT_EXTENSION_MAP = { diff --git a/app/javascript/flavours/glitch/components/admin/ReportReasonSelector.jsx b/app/javascript/flavours/glitch/components/admin/ReportReasonSelector.jsx index 92669fcadc..9ca16a0f32 100644 --- a/app/javascript/flavours/glitch/components/admin/ReportReasonSelector.jsx +++ b/app/javascript/flavours/glitch/components/admin/ReportReasonSelector.jsx @@ -1,12 +1,14 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { injectIntl, defineMessages } from 'react-intl'; +import { defineMessages } from 'react-intl'; import classNames from 'classnames'; import api from 'flavours/glitch/api'; +import { injectIntl } from '../intl'; + const messages = defineMessages({ legal: { id: 'report.categories.legal', defaultMessage: 'Legal' }, other: { id: 'report.categories.other', defaultMessage: 'Other' }, diff --git a/app/javascript/flavours/glitch/components/alt_text_badge.tsx b/app/javascript/flavours/glitch/components/alt_text_badge/index.tsx similarity index 57% rename from app/javascript/flavours/glitch/components/alt_text_badge.tsx rename to app/javascript/flavours/glitch/components/alt_text_badge/index.tsx index 93d8d8b753..87e85ba2e3 100644 --- a/app/javascript/flavours/glitch/components/alt_text_badge.tsx +++ b/app/javascript/flavours/glitch/components/alt_text_badge/index.tsx @@ -1,6 +1,6 @@ import { useState, useCallback, useRef, useId } from 'react'; -import { FormattedMessage } from 'react-intl'; +import { FormattedMessage, useIntl } from 'react-intl'; import type { OffsetValue, @@ -8,24 +8,37 @@ import type { } from 'react-overlays/esm/usePopper'; import Overlay from 'react-overlays/Overlay'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { useSelectableClick } from 'flavours/glitch/hooks/useSelectableClick'; +import { IconButton } from '../icon_button'; + +import classes from './styles.module.scss'; + const offset = [0, 4] as OffsetValue; const popperConfig = { strategy: 'fixed' } as UsePopperOptions; export const AltTextBadge: React.FC<{ description: string }> = ({ description, }) => { - const accessibilityId = useId(); - const anchorRef = useRef(null); + const intl = useIntl(); + const uniqueId = useId(); + const popoverId = `${uniqueId}-popover`; + const titleId = `${uniqueId}-title`; + const buttonRef = useRef(null); + const popoverRef = useRef(null); const [open, setOpen] = useState(false); const handleClick = useCallback(() => { setOpen((v) => !v); + setTimeout(() => { + popoverRef.current?.focus(); + }, 0); }, [setOpen]); const handleClose = useCallback(() => { setOpen(false); + buttonRef.current?.focus(); }, [setOpen]); const [handleMouseDown, handleMouseUp] = useSelectableClick(handleClose); @@ -34,11 +47,12 @@ export const AltTextBadge: React.FC<{ description: string }> = ({ <> @@ -47,7 +61,7 @@ export const AltTextBadge: React.FC<{ description: string }> = ({ rootClose onHide={handleClose} show={open} - target={anchorRef} + target={buttonRef} placement='top-end' flip offset={offset} @@ -57,17 +71,33 @@ export const AltTextBadge: React.FC<{ description: string }> = ({
-

+

+ + +

{description}

diff --git a/app/javascript/flavours/glitch/components/alt_text_badge/styles.module.scss b/app/javascript/flavours/glitch/components/alt_text_badge/styles.module.scss new file mode 100644 index 0000000000..1b7d5ec788 --- /dev/null +++ b/app/javascript/flavours/glitch/components/alt_text_badge/styles.module.scss @@ -0,0 +1,17 @@ +.closeButton { + position: absolute; + top: 5px; + inset-inline-end: 2px; + padding: 10px; + + --default-icon-color: var(--color-text-on-media); + --default-bg-color: transparent; + --hover-icon-color: var(--color-text-on-media); + --hover-bg-color: rgb(from var(--color-text-on-media) r g b / 10%); + --focus-outline-color: var(--color-text-on-media); + + svg { + width: 20px; + height: 20px; + } +} diff --git a/app/javascript/flavours/glitch/components/avatar.tsx b/app/javascript/flavours/glitch/components/avatar.tsx index 0f4e597634..f16ac8a4ac 100644 --- a/app/javascript/flavours/glitch/components/avatar.tsx +++ b/app/javascript/flavours/glitch/components/avatar.tsx @@ -96,7 +96,7 @@ export const Avatar: React.FC = ({ }; export const AvatarById: React.FC< - { accountId: string } & Omit + { accountId: string | undefined } & Omit > = ({ accountId, ...otherProps }) => { const account = useAccount(accountId); return ; diff --git a/app/javascript/flavours/glitch/components/badge.tsx b/app/javascript/flavours/glitch/components/badge.tsx index 07ecdfa46c..54c28bf7b7 100644 --- a/app/javascript/flavours/glitch/components/badge.tsx +++ b/app/javascript/flavours/glitch/components/badge.tsx @@ -31,7 +31,7 @@ export const Badge: FC = ({ data-account-role-id={roleId} > {icon} - {label} + {label} {domain && {domain}} ); diff --git a/app/javascript/flavours/glitch/components/callout/styles.module.css b/app/javascript/flavours/glitch/components/callout/styles.module.css index 14003ccf5d..9df3ea40d2 100644 --- a/app/javascript/flavours/glitch/components/callout/styles.module.css +++ b/app/javascript/flavours/glitch/components/callout/styles.module.css @@ -3,7 +3,7 @@ align-items: start; padding: 12px; gap: 8px; - background-color: var(--color-bg-brand-softer); + background-color: var(--color-bg-brand-softest); color: var(--color-text-primary); border-radius: 12px; } @@ -86,11 +86,11 @@ } .variantSubtle { - border: 1px solid var(--color-bg-brand-softer); + border: 1px solid var(--color-bg-brand-softest); background-color: var(--color-bg-primary); .icon { - background-color: var(--color-bg-brand-softer); + background-color: var(--color-bg-brand-softest); } } @@ -105,11 +105,11 @@ .variantInverted { background-color: var(--color-bg-inverted); - color: var(--color-text-on-inverted); + color: var(--color-text-inverted); } .variantSuccess { - background-color: var(--color-bg-success-softer); + background-color: var(--color-bg-success-softest); .icon { background-color: var(--color-bg-success-soft); @@ -117,7 +117,7 @@ } .variantWarning { - background-color: var(--color-bg-warning-softer); + background-color: var(--color-bg-warning-softest); .icon { background-color: var(--color-bg-warning-soft); @@ -125,7 +125,7 @@ } .variantError { - background-color: var(--color-bg-error-softer); + background-color: var(--color-bg-error-softest); .icon { background-color: var(--color-bg-error-soft); diff --git a/app/javascript/flavours/glitch/components/display_name/default.tsx b/app/javascript/flavours/glitch/components/display_name/default.tsx index 57ae24ab26..ec42c9ada9 100644 --- a/app/javascript/flavours/glitch/components/display_name/default.tsx +++ b/app/javascript/flavours/glitch/components/display_name/default.tsx @@ -6,10 +6,11 @@ import { Skeleton } from '../skeleton'; import type { DisplayNameProps } from './index'; import { DisplayNameWithoutDomain } from './no-domain'; -export const DisplayNameDefault: FC< - Omit & ComponentPropsWithoutRef<'span'> -> = ({ account, localDomain, className, ...props }) => { - const username = useMemo(() => { +export function useAccountHandle( + account: DisplayNameProps['account'], + localDomain: DisplayNameProps['localDomain'], +) { + return useMemo(() => { if (!account) { return null; } @@ -20,6 +21,12 @@ export const DisplayNameDefault: FC< } return `@${acct}`; }, [account, localDomain]); +} + +export const DisplayNameDefault: FC< + Omit & ComponentPropsWithoutRef<'span'> +> = ({ account, localDomain, className, ...props }) => { + const username = useAccountHandle(account, localDomain); return ( > { + component: ComponentClass; + props: TProps; +} + +export const IntlHoc = >({ + component: Component, + props, +}: IntlHocProps) => { + const intl = useIntl(); + return ; +}; + +export const injectIntl = >( + Component: ComponentClass, +) => { + const WrappedComponent = (props: Omit) => ( + + ); + WrappedComponent.displayName = `injectIntl(${(Component.displayName ?? Component.name) || 'Component'})`; + return WrappedComponent; +}; diff --git a/app/javascript/flavours/glitch/components/more_from_author.tsx b/app/javascript/flavours/glitch/components/more_from_author.tsx index 957d97c141..6903a536d1 100644 --- a/app/javascript/flavours/glitch/components/more_from_author.tsx +++ b/app/javascript/flavours/glitch/components/more_from_author.tsx @@ -6,16 +6,12 @@ import { AuthorLink } from 'flavours/glitch/features/explore/components/author_l export const MoreFromAuthor: React.FC<{ accountId: string }> = ({ accountId, }) => ( - }} - > - {(chunks) => ( -
- - {chunks} -
- )} -
+
+ + }} + /> +
); diff --git a/app/javascript/flavours/glitch/components/not_signed_in_indicator.tsx b/app/javascript/flavours/glitch/components/not_signed_in_indicator.tsx index 015f74dcae..c2e95093b7 100644 --- a/app/javascript/flavours/glitch/components/not_signed_in_indicator.tsx +++ b/app/javascript/flavours/glitch/components/not_signed_in_indicator.tsx @@ -6,6 +6,7 @@ export const NotSignedInIndicator: React.FC = () => ( diff --git a/app/javascript/flavours/glitch/components/regeneration_indicator.tsx b/app/javascript/flavours/glitch/components/regeneration_indicator.tsx index e26b93eb4f..a79556104e 100644 --- a/app/javascript/flavours/glitch/components/regeneration_indicator.tsx +++ b/app/javascript/flavours/glitch/components/regeneration_indicator.tsx @@ -20,6 +20,7 @@ export const RegenerationIndicator: React.FC = () => ( diff --git a/app/javascript/flavours/glitch/components/scrollable_list/components.tsx b/app/javascript/flavours/glitch/components/scrollable_list/components.tsx index 79afaf837a..9e90ce807e 100644 --- a/app/javascript/flavours/glitch/components/scrollable_list/components.tsx +++ b/app/javascript/flavours/glitch/components/scrollable_list/components.tsx @@ -37,7 +37,11 @@ export const ItemList = forwardRef< } >(({ isLoading, emptyMessage, className, children, ...otherProps }, ref) => { if (!isLoading && Children.count(children) === 0 && emptyMessage) { - return
{emptyMessage}
; + return ( +
+ {emptyMessage} +
+ ); } return ( diff --git a/app/javascript/flavours/glitch/components/scrollable_list/index.jsx b/app/javascript/flavours/glitch/components/scrollable_list/index.jsx index fae61d02da..7203bad886 100644 --- a/app/javascript/flavours/glitch/components/scrollable_list/index.jsx +++ b/app/javascript/flavours/glitch/components/scrollable_list/index.jsx @@ -385,7 +385,7 @@ class ScrollableList extends PureComponent { {alwaysPrepend && prepend}
- {emptyMessage} + {emptyMessage}
{footer} diff --git a/app/javascript/flavours/glitch/components/server_banner.jsx b/app/javascript/flavours/glitch/components/server_banner.jsx index b3f4311479..520030e8be 100644 --- a/app/javascript/flavours/glitch/components/server_banner.jsx +++ b/app/javascript/flavours/glitch/components/server_banner.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; +import { FormattedMessage, defineMessages } from 'react-intl'; import { Link } from 'react-router-dom'; @@ -14,6 +14,8 @@ import { ShortNumber } from 'flavours/glitch/components/short_number'; import { Skeleton } from 'flavours/glitch/components/skeleton'; import { domain } from 'flavours/glitch/initial_state'; +import { injectIntl } from './intl'; + const messages = defineMessages({ aboutActiveUsers: { id: 'server_banner.about_active_users', defaultMessage: 'People using this server during the last 30 days (Monthly Active Users)' }, }); diff --git a/app/javascript/flavours/glitch/components/status.jsx b/app/javascript/flavours/glitch/components/status.jsx index 78bd3e9ccd..db26e5f012 100644 --- a/app/javascript/flavours/glitch/components/status.jsx +++ b/app/javascript/flavours/glitch/components/status.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { injectIntl, defineMessages, FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import classNames from 'classnames'; @@ -21,6 +21,7 @@ import { MediaGallery, Video, Audio } from '../features/ui/util/async-components import { SensitiveMediaContext } from '../features/ui/util/sensitive_media_context'; import { displayMedia } from '../initial_state'; +import { injectIntl } from './intl'; import AttachmentList from './attachment_list'; import { StatusHeader } from './status/header' import { getHashtagBarForStatus } from './hashtag_bar'; diff --git a/app/javascript/flavours/glitch/components/status_action_bar/index.jsx b/app/javascript/flavours/glitch/components/status_action_bar/index.jsx index 7f402eb645..0484f9f8b5 100644 --- a/app/javascript/flavours/glitch/components/status_action_bar/index.jsx +++ b/app/javascript/flavours/glitch/components/status_action_bar/index.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages } from 'react-intl'; import { withRouter } from 'react-router-dom'; @@ -25,11 +25,13 @@ import { Dropdown } from 'flavours/glitch/components/dropdown_menu'; import { me, quickBoosting } from '../../initial_state'; import { IconButton } from '../icon_button'; +import { injectIntl } from '../intl'; import { RelativeTimestamp } from '../relative_timestamp'; import { BoostButton } from '../status/boost_button'; import { RemoveQuoteHint } from './remove_quote_hint'; import { quoteItemState, selectStatusState } from '../status/boost_button_utils'; + const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, redraft: { id: 'status.redraft', defaultMessage: 'Delete & re-draft' }, diff --git a/app/javascript/flavours/glitch/components/status_content.jsx b/app/javascript/flavours/glitch/components/status_content.jsx index dd2d9c3f6b..40a1d26ccd 100644 --- a/app/javascript/flavours/glitch/components/status_content.jsx +++ b/app/javascript/flavours/glitch/components/status_content.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { FormattedMessage, injectIntl } from 'react-intl'; +import { FormattedMessage } from 'react-intl'; import classnames from 'classnames'; import { withRouter } from 'react-router-dom'; @@ -16,6 +16,7 @@ import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity import { languages as preloadedLanguages } from 'flavours/glitch/initial_state'; import { EmojiHTML } from './emoji/html'; +import { injectIntl } from './intl'; import { HandledLink } from './status/handled_link'; const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top) diff --git a/app/javascript/flavours/glitch/components/status_icons.jsx b/app/javascript/flavours/glitch/components/status_icons.jsx index 63f443952e..1641dedf81 100644 --- a/app/javascript/flavours/glitch/components/status_icons.jsx +++ b/app/javascript/flavours/glitch/components/status_icons.jsx @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; @@ -11,6 +11,7 @@ import HomeIcon from '@/material-icons/400-24px/home.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; import { MediaIcon } from 'flavours/glitch/components/media_icon'; import { languages } from 'flavours/glitch/initial_state'; +import { injectIntl } from '@/flavours/glitch/components/intl'; import { VisibilityIcon } from './visibility_icon'; diff --git a/app/javascript/flavours/glitch/components/status_prepend.jsx b/app/javascript/flavours/glitch/components/status_prepend.jsx index 9766b0746f..b488aee664 100644 --- a/app/javascript/flavours/glitch/components/status_prepend.jsx +++ b/app/javascript/flavours/glitch/components/status_prepend.jsx @@ -161,7 +161,9 @@ export default class StatusPrepend extends PureComponent { icon={iconComponent} /> - + + + {children} ); diff --git a/app/javascript/flavours/glitch/components/status_quoted.tsx b/app/javascript/flavours/glitch/components/status_quoted.tsx index 42287b315e..e53c99f978 100644 --- a/app/javascript/flavours/glitch/components/status_quoted.tsx +++ b/app/javascript/flavours/glitch/components/status_quoted.tsx @@ -336,7 +336,6 @@ export const QuotedStatus: React.FC = ({ return (
- {/* @ts-expect-error Status is not yet typed */}
- {label} + {label} ); }; diff --git a/app/javascript/flavours/glitch/components/tags/style.module.css b/app/javascript/flavours/glitch/components/tags/style.module.css index f3c507b644..dd14cc43df 100644 --- a/app/javascript/flavours/glitch/components/tags/style.module.css +++ b/app/javascript/flavours/glitch/components/tags/style.module.css @@ -22,8 +22,8 @@ button.tag:focus-visible { } .active { - border-color: var(--color-text-brand); - background: var(--color-bg-brand-softer); + border-color: var(--color-border-brand); + background: var(--color-bg-brand-softest); color: var(--color-text-brand); } diff --git a/app/javascript/flavours/glitch/features/about/index.jsx b/app/javascript/flavours/glitch/features/about/index.jsx index 5bab3add59..e15bcc963e 100644 --- a/app/javascript/flavours/glitch/features/about/index.jsx +++ b/app/javascript/flavours/glitch/features/about/index.jsx @@ -1,13 +1,14 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import { Helmet } from 'react-helmet'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; +import { injectIntl } from '@/flavours/glitch/components/intl'; import { fetchServer, fetchExtendedDescription, fetchDomainBlocks } from 'flavours/glitch/actions/server'; import { Account } from 'flavours/glitch/components/account'; import Column from 'flavours/glitch/components/column'; diff --git a/app/javascript/flavours/glitch/features/account/components/action_bar.tsx b/app/javascript/flavours/glitch/features/account/components/action_bar.tsx index 3f47da1339..8c9717b53e 100644 --- a/app/javascript/flavours/glitch/features/account/components/action_bar.tsx +++ b/app/javascript/flavours/glitch/features/account/components/action_bar.tsx @@ -63,7 +63,11 @@ export const ActionBar: React.FC<{ account: Account }> = ({ account }) => { className='account__action-bar__tab' to={`/@${account.get('acct')}`} > - + @@ -75,7 +79,11 @@ export const ActionBar: React.FC<{ account: Account }> = ({ account }) => { className='account__action-bar__tab' to={`/@${account.get('acct')}/following`} > - + @@ -90,6 +98,7 @@ export const ActionBar: React.FC<{ account: Account }> = ({ account }) => { {account.get('followers_count') < 0 ? ( diff --git a/app/javascript/flavours/glitch/features/account_edit/modals/image_alt.tsx b/app/javascript/flavours/glitch/features/account_edit/modals/image_alt.tsx index 8e28cb35cf..b35704db1e 100644 --- a/app/javascript/flavours/glitch/features/account_edit/modals/image_alt.tsx +++ b/app/javascript/flavours/glitch/features/account_edit/modals/image_alt.tsx @@ -3,6 +3,7 @@ import { useCallback, useState } from 'react'; import { FormattedMessage } from 'react-intl'; +import { CharacterCounter } from '@/flavours/glitch/components/character_counter'; import { Details } from '@/flavours/glitch/components/details'; import { TextAreaField } from '@/flavours/glitch/components/form_fields'; import { LoadingIndicator } from '@/flavours/glitch/components/loading_indicator'; @@ -84,7 +85,12 @@ export const ImageAltTextField: FC<{ const altLimit = useAppSelector( (state) => state.server.getIn( - ['server', 'configuration', 'media_attachments', 'description_limit'], + [ + 'server', + 'configuration', + 'accounts', + 'max_header_description_length', + ], 150, ) as number, ); @@ -100,23 +106,26 @@ export const ImageAltTextField: FC<{ <> - - } - hint={ - - } - onChange={handleChange} - value={altText} - maxLength={altLimit} - /> +
+ + } + hint={ + + } + onChange={handleChange} + value={altText} + maxLength={altLimit} + /> + +
{!hideTip && (
    {chunks}
, li: (chunks) =>
  • {chunks}
  • , diff --git a/app/javascript/flavours/glitch/features/account_edit/modals/styles.module.scss b/app/javascript/flavours/glitch/features/account_edit/modals/styles.module.scss index ebe36d412b..f4eaf04f09 100644 --- a/app/javascript/flavours/glitch/features/account_edit/modals/styles.module.scss +++ b/app/javascript/flavours/glitch/features/account_edit/modals/styles.module.scss @@ -58,7 +58,7 @@ transition: background 0.2s ease-in-out; &:hover { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); } &:focus-visible { diff --git a/app/javascript/flavours/glitch/features/account_edit/styles.module.scss b/app/javascript/flavours/glitch/features/account_edit/styles.module.scss index b2ab20ae3b..69abdb856b 100644 --- a/app/javascript/flavours/glitch/features/account_edit/styles.module.scss +++ b/app/javascript/flavours/glitch/features/account_edit/styles.module.scss @@ -160,7 +160,7 @@ &:active, &:focus, &:hover { - background-color: var(--color-bg-brand-softer); + background-color: var(--color-bg-brand-softest); } &:disabled { @@ -177,7 +177,7 @@ .deleteButton { --default-icon-color: var(--color-text-error); - --hover-bg-color: var(--color-bg-error-base-hover); + --hover-bg-color: var(--color-bg-error-base); --hover-icon-color: var(--color-text-on-error-base); } @@ -201,7 +201,7 @@ &, &:global(.active) { // Overrides the transparent background added by default with .active - --hover-bg-color: var(--color-bg-brand-softer-solid); + --hover-bg-color: var(--color-bg-brand-softest); } position: absolute; diff --git a/app/javascript/flavours/glitch/features/account_featured/components/empty_message.tsx b/app/javascript/flavours/glitch/features/account_featured/components/empty_message.tsx index ec0a551bd0..1a38022ea6 100644 --- a/app/javascript/flavours/glitch/features/account_featured/components/empty_message.tsx +++ b/app/javascript/flavours/glitch/features/account_featured/components/empty_message.tsx @@ -65,5 +65,9 @@ export const EmptyMessage: React.FC = ({ ); } - return
    {message}
    ; + return ( +
    + {message} +
    + ); }; diff --git a/app/javascript/flavours/glitch/features/account_featured/index.tsx b/app/javascript/flavours/glitch/features/account_featured/index.tsx index 0d9d92a0e0..9acb98f005 100644 --- a/app/javascript/flavours/glitch/features/account_featured/index.tsx +++ b/app/javascript/flavours/glitch/features/account_featured/index.tsx @@ -154,6 +154,7 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({ key={item.id} collection={item} withoutBorder={index === listedCollections.length - 1} + withAuthorHandle={false} positionInList={index + 1} listSize={listedCollections.length} /> diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/redesign.module.scss b/app/javascript/flavours/glitch/features/account_timeline/components/redesign.module.scss index 3b6a95d099..40fdc5cfa8 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/components/redesign.module.scss +++ b/app/javascript/flavours/glitch/features/account_timeline/components/redesign.module.scss @@ -9,6 +9,11 @@ .barWrapper { border-bottom: none; + padding-inline: 24px; + + @container (width < 500px) { + padding-inline: 16px; + } } .avatarWrapper { @@ -93,7 +98,7 @@ } svg { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); width: 28px; height: 28px; padding: 5px; @@ -125,7 +130,7 @@ $button-fallback-breakpoint: $button-breakpoint + 55px; position: sticky; bottom: var(--mobile-bottom-nav-height); padding: 12px 16px; - margin: 0 -20px; + margin: 0 -16px; @container (width >= #{$button-breakpoint}) { display: none; @@ -184,7 +189,7 @@ $button-fallback-breakpoint: $button-breakpoint + 55px; .badgeMuted { background-color: var(--color-bg-inverted); - color: var(--color-text-on-inverted); + color: var(--color-text-inverted); } .badgeBlocked { @@ -270,7 +275,7 @@ svg.badgeIcon { } .fieldVerified { - background-color: var(--color-bg-success-softer); + background-color: var(--color-bg-success-softest); dt { padding-right: 24px; @@ -292,8 +297,8 @@ svg.badgeIcon { } .fieldOverflowButton { - --default-bg-color: var(--color-bg-secondary-solid); - --hover-bg-color: var(--color-bg-brand-softer-solid); + --default-bg-color: var(--color-bg-secondary); + --hover-bg-color: var(--color-bg-brand-softest); position: absolute; right: 8px; @@ -388,7 +393,7 @@ svg.badgeIcon { padding: 0 24px; @container (width < 500px) { - padding: 0 12px; + padding: 0 16px; a { flex: 1 1 0px; @@ -413,7 +418,7 @@ svg.badgeIcon { :global(.active) { color: var(--color-text-brand); - border-bottom: 4px solid var(--color-text-brand); + border-bottom: 4px solid var(--color-border-brand); padding-bottom: 14px; } } diff --git a/app/javascript/flavours/glitch/features/account_timeline/v2/tags_suggestions.tsx b/app/javascript/flavours/glitch/features/account_timeline/v2/tags_suggestions.tsx index 138ccc4cf2..1c0c81128e 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/v2/tags_suggestions.tsx +++ b/app/javascript/flavours/glitch/features/account_timeline/v2/tags_suggestions.tsx @@ -78,6 +78,7 @@ export const TagSuggestions: FC = () => { values={{ link: (chunks) => {chunks}, }} + tagName='span' /> ); @@ -122,6 +123,7 @@ export const TagSuggestions: FC = () => { /> ), }} + tagName='span' /> ); diff --git a/app/javascript/flavours/glitch/features/alt_text_modal/components/info_button.tsx b/app/javascript/flavours/glitch/features/alt_text_modal/components/info_button.tsx index 891702d563..074a19c45a 100644 --- a/app/javascript/flavours/glitch/features/alt_text_modal/components/info_button.tsx +++ b/app/javascript/flavours/glitch/features/alt_text_modal/components/info_button.tsx @@ -59,6 +59,7 @@ export const InfoButton: React.FC = () => { > @@ -143,7 +141,6 @@ const SensitiveScreen: React.FC<{ @@ -205,7 +202,6 @@ export const CollectionAccountsList: React.FC<{ values={{ author: , }} - tagName={Fragment} />
    diff --git a/app/javascript/flavours/glitch/features/collections/detail/collection_list_item.module.scss b/app/javascript/flavours/glitch/features/collections/detail/collection_list_item.module.scss index 3c71e90f48..7cdf9b8541 100644 --- a/app/javascript/flavours/glitch/features/collections/detail/collection_list_item.module.scss +++ b/app/javascript/flavours/glitch/features/collections/detail/collection_list_item.module.scss @@ -1,8 +1,9 @@ .wrapper { display: flex; - align-items: center; + align-items: start; + margin-inline: 24px; + padding-block: 12px; gap: 16px; - padding-inline: 16px; &:not(.wrapperWithoutBorder) { border-bottom: 1px solid var(--color-border-primary); @@ -12,12 +13,42 @@ .content { position: relative; flex-grow: 1; - padding-block: 15px; + display: flex; + align-items: center; + column-gap: 12px; +} + +.avatarGrid { + position: relative; + display: grid; + grid-template-columns: repeat(2, min-content); + gap: 2px; + + &.avatarGridSensitive { + .avatar { + filter: blur(4px); + } + } +} + +.avatar { + background: var(--color-bg-brand-softest); +} + +.avatarSensitiveBadge { + position: absolute; + inset: 0; + margin: auto; + padding: 3px; + width: 18px; + height: 18px; + border-radius: 8px; + color: var(--color-text-primary); + background: var(--color-bg-warning-softest); } .link { display: block; - margin-bottom: 2px; font-size: 15px; font-weight: 500; text-decoration: none; @@ -40,15 +71,12 @@ color: var(--color-text-secondary); } -.metaList { - --gap: 0.75ch; +.menuButton { + padding: 4px; + margin-top: -2px; - display: flex; - flex-wrap: wrap; - gap: var(--gap); - - & > li:not(:last-child)::after { - content: '·'; - margin-inline-start: var(--gap); + svg { + width: 20px; + height: 20px; } } diff --git a/app/javascript/flavours/glitch/features/collections/detail/collection_list_item.tsx b/app/javascript/flavours/glitch/features/collections/detail/collection_list_item.tsx index 82d7faae1a..2e29a8a009 100644 --- a/app/javascript/flavours/glitch/features/collections/detail/collection_list_item.tsx +++ b/app/javascript/flavours/glitch/features/collections/detail/collection_list_item.tsx @@ -5,70 +5,65 @@ import { FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import { Link } from 'react-router-dom'; +import WarningIcon from '@/material-icons/400-24px/warning.svg?react'; import type { ApiCollectionJSON } from 'flavours/glitch/api_types/collections'; +import { AvatarById } from 'flavours/glitch/components/avatar'; +import { useAccountHandle } from 'flavours/glitch/components/display_name/default'; import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp'; import { Article } from 'flavours/glitch/components/scrollable_list/components'; +import { useAccount } from 'flavours/glitch/hooks/useAccount'; +import { domain } from 'flavours/glitch/initial_state'; import classes from './collection_list_item.module.scss'; import { CollectionMenu } from './collection_menu'; -export const CollectionMetaData: React.FC<{ - collection: ApiCollectionJSON; - extended?: boolean; - className?: string; -}> = ({ collection, extended, className }) => { +export const AvatarGrid: React.FC<{ + accountIds: (string | undefined)[]; + sensitive?: boolean; +}> = ({ accountIds: ids, sensitive }) => { + const avatarIds = [ids[0], ids[1], ids[2], ids[3]]; return ( -
      - - {extended && ( - <> - {collection.discoverable ? ( - - ) : ( - - )} - {collection.sensitive && ( - - )} - +
      , - }} - tagName='li' - /> -
    + > + {avatarIds.map((id) => ( + + ))} + {sensitive && } + ); }; export const CollectionListItem: React.FC<{ collection: ApiCollectionJSON; withoutBorder?: boolean; + withAuthorHandle?: boolean; + withTimestamp?: boolean; positionInList: number; listSize: number; -}> = ({ collection, withoutBorder, positionInList, listSize }) => { +}> = ({ + collection, + withoutBorder, + withAuthorHandle = true, + withTimestamp, + positionInList, + listSize, +}) => { const { id, name } = collection; - const linkId = useId(); + const uniqueId = useId(); + const linkId = `${uniqueId}-link`; + const infoId = `${uniqueId}-info`; + const authorAccount = useAccount(collection.account_id); + const authorHandle = useAccountHandle(authorAccount, domain); return (
    -

    - - {name} - -

    - + item.account_id)} + sensitive={collection.sensitive} + /> +
    +

    + + {name} + +

    +
      + {collection.sensitive && ( +
    • + +
    • + )} + {withAuthorHandle && authorAccount && ( + + )} + + {withTimestamp && ( + + ), + }} + tagName='li' + /> + )} +
    +
    - +
    ); }; diff --git a/app/javascript/flavours/glitch/features/collections/detail/index.tsx b/app/javascript/flavours/glitch/features/collections/detail/index.tsx index 85f7844cb2..f5c5e863f5 100644 --- a/app/javascript/flavours/glitch/features/collections/detail/index.tsx +++ b/app/javascript/flavours/glitch/features/collections/detail/index.tsx @@ -6,6 +6,7 @@ import { Helmet } from 'react-helmet'; import { useHistory, useLocation, useParams } from 'react-router'; import { openModal } from '@/flavours/glitch/actions/modal'; +import { RelativeTimestamp } from '@/flavours/glitch/components/relative_timestamp'; import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react'; import ShareIcon from '@/material-icons/400-24px/share.svg?react'; import type { ApiCollectionJSON } from 'flavours/glitch/api_types/collections'; @@ -25,7 +26,6 @@ import { fetchCollection } from 'flavours/glitch/reducers/slices/collections'; import { useAppDispatch, useAppSelector } from 'flavours/glitch/store'; import { CollectionAccountsList } from './accounts_list'; -import { CollectionMetaData } from './collection_list_item'; import { CollectionMenu } from './collection_menu'; import classes from './styles.module.scss'; @@ -40,6 +40,54 @@ const messages = defineMessages({ }, }); +const CollectionMetaData: React.FC<{ + collection: ApiCollectionJSON; + extended?: boolean; +}> = ({ collection, extended }) => { + return ( +
      + + {extended && ( + <> + {collection.discoverable ? ( + + ) : ( + + )} + {collection.sensitive && ( + + )} + + )} + , + }} + tagName='li' + /> +
    + ); +}; + export const AuthorNote: React.FC<{ id: string; previewMode?: boolean }> = ({ id, // When previewMode is enabled, your own display name @@ -137,7 +185,6 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({ ); diff --git a/app/javascript/flavours/glitch/features/collections/detail/styles.module.scss b/app/javascript/flavours/glitch/features/collections/detail/styles.module.scss index 786c0e7000..89bad584b6 100644 --- a/app/javascript/flavours/glitch/features/collections/detail/styles.module.scss +++ b/app/javascript/flavours/glitch/features/collections/detail/styles.module.scss @@ -52,9 +52,19 @@ font-size: 13px; } -.metaData { +.metaList { + --gap: 0.75ch; + + display: flex; + flex-wrap: wrap; margin-top: 16px; + gap: var(--gap); font-size: 15px; + + & > li:not(:last-child)::after { + content: '·'; + margin-inline-start: var(--gap); + } } .columnSubheading { diff --git a/app/javascript/flavours/glitch/features/collections/editor/details.tsx b/app/javascript/flavours/glitch/features/collections/editor/details.tsx index fd9a08f38b..8d2e07b192 100644 --- a/app/javascript/flavours/glitch/features/collections/editor/details.tsx +++ b/app/javascript/flavours/glitch/features/collections/editor/details.tsx @@ -1,4 +1,4 @@ -import { Fragment, useCallback, useMemo } from 'react'; +import { useCallback, useMemo } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; @@ -413,7 +413,6 @@ const LanguageField: React.FC = () => { {languages?.map(([code, name, localName]) => ( diff --git a/app/javascript/flavours/glitch/features/collections/index.tsx b/app/javascript/flavours/glitch/features/collections/index.tsx index a0dbaad44d..b98c2c5dad 100644 --- a/app/javascript/flavours/glitch/features/collections/index.tsx +++ b/app/javascript/flavours/glitch/features/collections/index.tsx @@ -47,6 +47,7 @@ export const Collections: React.FC<{ ) : ( <> @@ -92,6 +93,8 @@ export const Collections: React.FC<{ {collections.map((item, index) => ( = ({
    - {isProcessing ? ( - - ) : ( - - )} + + {isProcessing ? ( + + ) : ( + + )} +
    diff --git a/app/javascript/flavours/glitch/features/compose/containers/spoiler_button_container.js b/app/javascript/flavours/glitch/features/compose/containers/spoiler_button_container.js index fc87ef3725..fd53ea771e 100644 --- a/app/javascript/flavours/glitch/features/compose/containers/spoiler_button_container.js +++ b/app/javascript/flavours/glitch/features/compose/containers/spoiler_button_container.js @@ -1,8 +1,9 @@ -import { injectIntl, defineMessages } from 'react-intl'; +import { defineMessages } from 'react-intl'; import { connect } from 'react-redux'; import WarningIcon from '@/material-icons/400-20px/warning.svg?react'; +import { injectIntl } from '@/flavours/glitch/components/intl'; import { IconButton } from 'flavours/glitch/components/icon_button'; import { changeComposeSpoilerness } from '../../../actions/compose'; diff --git a/app/javascript/flavours/glitch/features/direct_timeline/components/column_settings.jsx b/app/javascript/flavours/glitch/features/direct_timeline/components/column_settings.jsx index 45de7010b6..c8f98f400b 100644 --- a/app/javascript/flavours/glitch/features/direct_timeline/components/column_settings.jsx +++ b/app/javascript/flavours/glitch/features/direct_timeline/components/column_settings.jsx @@ -1,10 +1,11 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import { injectIntl } from '@/flavours/glitch/components/intl'; import SettingToggle from 'flavours/glitch/features/notifications/components/setting_toggle'; import SettingText from '../../../components/setting_text'; diff --git a/app/javascript/flavours/glitch/features/explore/links.jsx b/app/javascript/flavours/glitch/features/explore/links.jsx index a9249430fd..603a8028e0 100644 --- a/app/javascript/flavours/glitch/features/explore/links.jsx +++ b/app/javascript/flavours/glitch/features/explore/links.jsx @@ -47,7 +47,7 @@ class Links extends PureComponent { return (
    - +
    ); diff --git a/app/javascript/flavours/glitch/features/explore/suggestions.jsx b/app/javascript/flavours/glitch/features/explore/suggestions.jsx index a5d029fc26..67c2fb4f83 100644 --- a/app/javascript/flavours/glitch/features/explore/suggestions.jsx +++ b/app/javascript/flavours/glitch/features/explore/suggestions.jsx @@ -45,7 +45,7 @@ class Suggestions extends PureComponent { return (
    - +
    ); diff --git a/app/javascript/flavours/glitch/features/explore/tags.jsx b/app/javascript/flavours/glitch/features/explore/tags.jsx index 14c221fa40..4a82ac8433 100644 --- a/app/javascript/flavours/glitch/features/explore/tags.jsx +++ b/app/javascript/flavours/glitch/features/explore/tags.jsx @@ -46,7 +46,7 @@ class Tags extends PureComponent { return (
    - +
    ); diff --git a/app/javascript/flavours/glitch/features/favourites/index.jsx b/app/javascript/flavours/glitch/features/favourites/index.jsx index 2c6289af3a..14bd9c3ac9 100644 --- a/app/javascript/flavours/glitch/features/favourites/index.jsx +++ b/app/javascript/flavours/glitch/features/favourites/index.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import { Helmet } from 'react-helmet'; @@ -16,6 +16,7 @@ import { fetchFavourites, expandFavourites } from 'flavours/glitch/actions/inter import { Account } from 'flavours/glitch/components/account'; import ColumnHeader from 'flavours/glitch/components/column_header'; import { Icon } from 'flavours/glitch/components/icon'; +import { injectIntl } from '@/flavours/glitch/components/intl'; import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; import ScrollableList from 'flavours/glitch/components/scrollable_list'; import Column from 'flavours/glitch/features/ui/components/column'; diff --git a/app/javascript/flavours/glitch/features/filters/select_filter.jsx b/app/javascript/flavours/glitch/features/filters/select_filter.jsx index 3a46b8bd48..bf7cb47b16 100644 --- a/app/javascript/flavours/glitch/features/filters/select_filter.jsx +++ b/app/javascript/flavours/glitch/features/filters/select_filter.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; @@ -9,6 +9,7 @@ import fuzzysort from 'fuzzysort'; import AddIcon from '@/material-icons/400-24px/add.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; +import { injectIntl } from '@/flavours/glitch/components/intl'; import { toServerSideType } from 'flavours/glitch/utils/filters'; import { loupeIcon, deleteIcon } from 'flavours/glitch/utils/icons'; diff --git a/app/javascript/flavours/glitch/features/follow_requests/components/account_authorize.jsx b/app/javascript/flavours/glitch/features/follow_requests/components/account_authorize.jsx index 079b742c5a..82023ba0e3 100644 --- a/app/javascript/flavours/glitch/features/follow_requests/components/account_authorize.jsx +++ b/app/javascript/flavours/glitch/features/follow_requests/components/account_authorize.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; @@ -11,6 +11,7 @@ import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { Avatar } from '@/flavours/glitch/components/avatar'; import { DisplayName } from '@/flavours/glitch/components/display_name'; import { IconButton } from '@/flavours/glitch/components/icon_button'; +import { injectIntl } from '@/flavours/glitch/components/intl'; import { EmojiHTML } from '@/flavours/glitch/components/emoji/html'; import { Permalink } from '@/flavours/glitch/components/permalink'; diff --git a/app/javascript/flavours/glitch/features/follow_requests/index.jsx b/app/javascript/flavours/glitch/features/follow_requests/index.jsx index 91648412b5..eff7aed1ab 100644 --- a/app/javascript/flavours/glitch/features/follow_requests/index.jsx +++ b/app/javascript/flavours/glitch/features/follow_requests/index.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import { Helmet } from 'react-helmet'; @@ -11,6 +11,7 @@ import { connect } from 'react-redux'; import { debounce } from 'lodash'; import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react'; +import { injectIntl } from '@/flavours/glitch/components/intl'; import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts'; import ScrollableList from '../../components/scrollable_list'; diff --git a/app/javascript/flavours/glitch/features/followers/index.tsx b/app/javascript/flavours/glitch/features/followers/index.tsx index 5719872582..0dc62b8cc9 100644 --- a/app/javascript/flavours/glitch/features/followers/index.tsx +++ b/app/javascript/flavours/glitch/features/followers/index.tsx @@ -66,6 +66,7 @@ const Followers: FC = () => {
    ); diff --git a/app/javascript/flavours/glitch/features/following/index.tsx b/app/javascript/flavours/glitch/features/following/index.tsx index 067fb4d732..7bc6be93ad 100644 --- a/app/javascript/flavours/glitch/features/following/index.tsx +++ b/app/javascript/flavours/glitch/features/following/index.tsx @@ -68,6 +68,7 @@ const Followers: FC = () => {
    ); diff --git a/app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.jsx b/app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.jsx index 94ee7bb119..87678c0429 100644 --- a/app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.jsx +++ b/app/javascript/flavours/glitch/features/hashtag_timeline/components/column_settings.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; @@ -11,6 +11,8 @@ import Toggle from 'react-toggle'; import { maxFeedHashtags } from 'flavours/glitch/initial_state'; +import { injectIntl } from '@/flavours/glitch/components/intl'; + import SettingToggle from '../../notifications/components/setting_toggle'; const messages = defineMessages({ diff --git a/app/javascript/flavours/glitch/features/home_timeline/components/inline_follow_suggestions.tsx b/app/javascript/flavours/glitch/features/home_timeline/components/inline_follow_suggestions.tsx index 14e5b5d71c..64a13ee5df 100644 --- a/app/javascript/flavours/glitch/features/home_timeline/components/inline_follow_suggestions.tsx +++ b/app/javascript/flavours/glitch/features/home_timeline/components/inline_follow_suggestions.tsx @@ -115,7 +115,7 @@ const Source: React.FC<{ id: ApiSuggestionSourceJSON }> = ({ id }) => { title={hint} > - {label} + {label} ); }; diff --git a/app/javascript/flavours/glitch/features/home_timeline/index.jsx b/app/javascript/flavours/glitch/features/home_timeline/index.jsx index b5f4a781a9..ee83e1bbc9 100644 --- a/app/javascript/flavours/glitch/features/home_timeline/index.jsx +++ b/app/javascript/flavours/glitch/features/home_timeline/index.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import { Helmet } from 'react-helmet'; @@ -10,6 +10,7 @@ import { connect } from 'react-redux'; import CampaignIcon from '@/material-icons/400-24px/campaign.svg?react'; import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react'; +import { injectIntl } from '@/flavours/glitch/components/intl'; import { SymbolLogo } from 'flavours/glitch/components/logo'; import { fetchAnnouncements, toggleShowAnnouncements } from 'flavours/glitch/actions/announcements'; import { IconWithBadge } from 'flavours/glitch/components/icon_with_badge'; diff --git a/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.jsx b/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.jsx index 6d0bd0db0b..7faf1209f8 100644 --- a/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.jsx +++ b/app/javascript/flavours/glitch/features/keyboard_shortcuts/index.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import { Helmet } from 'react-helmet'; @@ -9,6 +9,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import InfoIcon from '@/material-icons/400-24px/info.svg?react'; import Column from 'flavours/glitch/components/column'; import ColumnHeader from 'flavours/glitch/components/column_header'; +import { injectIntl } from '@/flavours/glitch/components/intl'; const messages = defineMessages({ heading: { id: 'keyboard_shortcuts.heading', defaultMessage: 'Keyboard Shortcuts' }, diff --git a/app/javascript/flavours/glitch/features/lists/members.tsx b/app/javascript/flavours/glitch/features/lists/members.tsx index 04620fbb06..79f28ac927 100644 --- a/app/javascript/flavours/glitch/features/lists/members.tsx +++ b/app/javascript/flavours/glitch/features/lists/members.tsx @@ -285,6 +285,7 @@ const ListMembers: React.FC<{ ) } diff --git a/app/javascript/flavours/glitch/features/local_settings/navigation/index.jsx b/app/javascript/flavours/glitch/features/local_settings/navigation/index.jsx index e498b18199..4923ef0620 100644 --- a/app/javascript/flavours/glitch/features/local_settings/navigation/index.jsx +++ b/app/javascript/flavours/glitch/features/local_settings/navigation/index.jsx @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { injectIntl, defineMessages } from 'react-intl'; +import { defineMessages } from 'react-intl'; import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import EditIcon from '@/material-icons/400-24px/edit.svg?react'; @@ -10,6 +10,7 @@ import ImageIcon from '@/material-icons/400-24px/image.svg?react'; import ManufacturingIcon from '@/material-icons/400-24px/manufacturing.svg?react'; import SettingsIcon from '@/material-icons/400-24px/settings-fill.svg?react'; import WarningIcon from '@/material-icons/400-24px/warning.svg?react'; +import { injectIntl } from '@/flavours/glitch/components/intl'; import { preferencesLink } from 'flavours/glitch/utils/backend_links'; import LocalSettingsNavigationItem from './item'; diff --git a/app/javascript/flavours/glitch/features/local_settings/page/index.jsx b/app/javascript/flavours/glitch/features/local_settings/page/index.jsx index 1ff9e2514d..47e0ea74b4 100644 --- a/app/javascript/flavours/glitch/features/local_settings/page/index.jsx +++ b/app/javascript/flavours/glitch/features/local_settings/page/index.jsx @@ -2,12 +2,13 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; // Our imports +import { injectIntl } from '@/flavours/glitch/components/intl'; import { expandSpoilers } from 'flavours/glitch/initial_state'; import { preferenceLink } from 'flavours/glitch/utils/backend_links'; diff --git a/app/javascript/flavours/glitch/features/mutes/index.jsx b/app/javascript/flavours/glitch/features/mutes/index.jsx index 476606abf0..d02f1b23ca 100644 --- a/app/javascript/flavours/glitch/features/mutes/index.jsx +++ b/app/javascript/flavours/glitch/features/mutes/index.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import { Helmet } from 'react-helmet'; @@ -12,6 +12,7 @@ import { debounce } from 'lodash'; import VolumeOffIcon from '@/material-icons/400-24px/volume_off.svg?react'; import { Account } from 'flavours/glitch/components/account'; +import { injectIntl } from '@/flavours/glitch/components/intl'; import { fetchMutes, expandMutes } from '../../actions/mutes'; import { LoadingIndicator } from '../../components/loading_indicator'; diff --git a/app/javascript/flavours/glitch/features/notifications/components/follow_request.jsx b/app/javascript/flavours/glitch/features/notifications/components/follow_request.jsx index bd45e1f826..153ca422cb 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/follow_request.jsx +++ b/app/javascript/flavours/glitch/features/notifications/components/follow_request.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; @@ -10,6 +10,7 @@ import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { Avatar } from 'flavours/glitch/components/avatar'; import { DisplayName } from 'flavours/glitch/components/display_name'; import { IconButton } from 'flavours/glitch/components/icon_button'; +import { injectIntl } from '@/flavours/glitch/components/intl'; import { Permalink } from 'flavours/glitch/components/permalink'; const messages = defineMessages({ diff --git a/app/javascript/flavours/glitch/features/notifications/components/notification.jsx b/app/javascript/flavours/glitch/features/notifications/components/notification.jsx index e0c6b8ba73..a471e07c43 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/notification.jsx +++ b/app/javascript/flavours/glitch/features/notifications/components/notification.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { injectIntl, FormattedMessage, defineMessages } from 'react-intl'; +import { FormattedMessage, defineMessages } from 'react-intl'; import classNames from 'classnames'; import { withRouter } from 'react-router-dom'; @@ -14,6 +14,7 @@ import PersonAddIcon from '@/material-icons/400-24px/person_add-fill.svg?react'; import { Account } from 'flavours/glitch/components/account'; import { LinkedDisplayName } from '@/flavours/glitch/components/display_name'; import { Icon } from 'flavours/glitch/components/icon'; +import { injectIntl } from '@/flavours/glitch/components/intl'; import { Hotkeys } from 'flavours/glitch/components/hotkeys'; import { StatusQuoteManager } from 'flavours/glitch/components/status_quoted'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; diff --git a/app/javascript/flavours/glitch/features/notifications/components/report.jsx b/app/javascript/flavours/glitch/features/notifications/components/report.jsx index 61b219709b..0ee737a292 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/report.jsx +++ b/app/javascript/flavours/glitch/features/notifications/components/report.jsx @@ -1,11 +1,12 @@ import PropTypes from 'prop-types'; -import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { AvatarOverlay } from 'flavours/glitch/components/avatar_overlay'; +import { injectIntl } from '@/flavours/glitch/components/intl'; import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp'; // This needs to be kept in sync with app/models/report.rb diff --git a/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js b/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js index 92ea81877c..6167a52550 100644 --- a/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js +++ b/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js @@ -1,9 +1,10 @@ -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages } from 'react-intl'; import { connect } from 'react-redux'; import { openModal } from 'flavours/glitch/actions/modal'; import { fetchNotifications , setNotificationsFilter } from 'flavours/glitch/actions/notification_groups'; +import { injectIntl } from '@/flavours/glitch/components/intl'; import { showAlert } from '../../../actions/alerts'; import { requestBrowserPermission } from '../../../actions/notifications'; diff --git a/app/javascript/flavours/glitch/features/notifications_v2/components/notification_group_with_status.tsx b/app/javascript/flavours/glitch/features/notifications_v2/components/notification_group_with_status.tsx index 25737dd9e0..4d0f2b447a 100644 --- a/app/javascript/flavours/glitch/features/notifications_v2/components/notification_group_with_status.tsx +++ b/app/javascript/flavours/glitch/features/notifications_v2/components/notification_group_with_status.tsx @@ -126,7 +126,7 @@ export const NotificationGroupWithStatus: React.FC<{
    - {label} + {label} {timestamp && ( <> diff --git a/app/javascript/flavours/glitch/features/pinned_statuses/index.jsx b/app/javascript/flavours/glitch/features/pinned_statuses/index.jsx index f50435a2f6..b32c9e7ba9 100644 --- a/app/javascript/flavours/glitch/features/pinned_statuses/index.jsx +++ b/app/javascript/flavours/glitch/features/pinned_statuses/index.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages } from 'react-intl'; import { Helmet } from 'react-helmet'; @@ -9,6 +9,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; import PushPinIcon from '@/material-icons/400-24px/push_pin.svg?react'; +import { injectIntl } from '@/flavours/glitch/components/intl'; import { getStatusList } from 'flavours/glitch/selectors'; import { fetchPinnedStatuses } from '../../actions/pin_statuses'; diff --git a/app/javascript/flavours/glitch/features/public_timeline/components/column_settings.jsx b/app/javascript/flavours/glitch/features/public_timeline/components/column_settings.jsx index 63c14b897b..e9ba6f0dd3 100644 --- a/app/javascript/flavours/glitch/features/public_timeline/components/column_settings.jsx +++ b/app/javascript/flavours/glitch/features/public_timeline/components/column_settings.jsx @@ -1,10 +1,12 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import { injectIntl } from '@/flavours/glitch/components/intl'; + import SettingText from '../../../components/setting_text'; import SettingToggle from '../../notifications/components/setting_toggle'; diff --git a/app/javascript/flavours/glitch/features/public_timeline/index.jsx b/app/javascript/flavours/glitch/features/public_timeline/index.jsx index 4a19600056..7e028b0e53 100644 --- a/app/javascript/flavours/glitch/features/public_timeline/index.jsx +++ b/app/javascript/flavours/glitch/features/public_timeline/index.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import { Helmet } from 'react-helmet'; @@ -9,6 +9,7 @@ import { connect } from 'react-redux'; import PublicIcon from '@/material-icons/400-24px/public.svg?react'; import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner'; +import { injectIntl } from '@/flavours/glitch/components/intl'; import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import { domain, localLiveFeedAccess, remoteLiveFeedAccess } from 'flavours/glitch/initial_state'; import { canViewFeed } from 'flavours/glitch/permissions'; diff --git a/app/javascript/flavours/glitch/features/reblogs/index.jsx b/app/javascript/flavours/glitch/features/reblogs/index.jsx index 6e7faa3db6..ac9b5950be 100644 --- a/app/javascript/flavours/glitch/features/reblogs/index.jsx +++ b/app/javascript/flavours/glitch/features/reblogs/index.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import { Helmet } from 'react-helmet'; @@ -14,6 +14,7 @@ import RefreshIcon from '@/material-icons/400-24px/refresh.svg?react'; import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react'; import { Account } from 'flavours/glitch/components/account'; import { Icon } from 'flavours/glitch/components/icon'; +import { injectIntl } from '@/flavours/glitch/components/intl'; import { fetchReblogs, expandReblogs } from '../../actions/interactions'; import ColumnHeader from '../../components/column_header'; diff --git a/app/javascript/flavours/glitch/features/report/category.jsx b/app/javascript/flavours/glitch/features/report/category.jsx index 9a3cc549e1..7dc6abfd29 100644 --- a/app/javascript/flavours/glitch/features/report/category.jsx +++ b/app/javascript/flavours/glitch/features/report/category.jsx @@ -1,13 +1,14 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import { List as ImmutableList } from 'immutable'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; import { Button } from 'flavours/glitch/components/button'; +import { injectIntl } from '@/flavours/glitch/components/intl'; import Option from './components/option'; diff --git a/app/javascript/flavours/glitch/features/report/comment.tsx b/app/javascript/flavours/glitch/features/report/comment.tsx index 31a7a7051a..5a05c03295 100644 --- a/app/javascript/flavours/glitch/features/report/comment.tsx +++ b/app/javascript/flavours/glitch/features/report/comment.tsx @@ -194,6 +194,7 @@ const Comment: React.FC = ({ id='report.forward' defaultMessage='Forward to {target}' values={{ target: domain }} + tagName='span' /> ))} diff --git a/app/javascript/flavours/glitch/features/status/components/action_bar.jsx b/app/javascript/flavours/glitch/features/status/components/action_bar.jsx index e2d48d9d91..a091fd0d1a 100644 --- a/app/javascript/flavours/glitch/features/status/components/action_bar.jsx +++ b/app/javascript/flavours/glitch/features/status/components/action_bar.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; @@ -13,6 +13,7 @@ import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react'; import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; import StarBorderIcon from '@/material-icons/400-24px/star.svg?react'; +import { injectIntl } from '@/flavours/glitch/components/intl'; import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions'; import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links'; diff --git a/app/javascript/flavours/glitch/features/status/index.jsx b/app/javascript/flavours/glitch/features/status/index.jsx index f60279a9b8..e7b8715a22 100644 --- a/app/javascript/flavours/glitch/features/status/index.jsx +++ b/app/javascript/flavours/glitch/features/status/index.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages } from 'react-intl'; import classNames from 'classnames'; import { Helmet } from 'react-helmet'; @@ -16,6 +16,7 @@ import VisibilityIcon from '@/material-icons/400-24px/visibility.svg?react'; import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react'; import { Hotkeys } from 'flavours/glitch/components/hotkeys'; import { Icon } from 'flavours/glitch/components/icon'; +import { injectIntl } from '@/flavours/glitch/components/intl'; import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; import { ScrollContainer } from 'flavours/glitch/containers/scroll_container'; import BundleColumnError from 'flavours/glitch/features/ui/components/bundle_column_error'; diff --git a/app/javascript/flavours/glitch/features/subscribed_languages_modal/index.jsx b/app/javascript/flavours/glitch/features/subscribed_languages_modal/index.jsx index 4594159b29..99267d7c18 100644 --- a/app/javascript/flavours/glitch/features/subscribed_languages_modal/index.jsx +++ b/app/javascript/flavours/glitch/features/subscribed_languages_modal/index.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import { createSelector } from '@reduxjs/toolkit'; import { is, List as ImmutableList, Set as ImmutableSet } from 'immutable'; @@ -12,6 +12,7 @@ import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { followAccount } from 'flavours/glitch/actions/accounts'; import { Button } from 'flavours/glitch/components/button'; import { IconButton } from 'flavours/glitch/components/icon_button'; +import { injectIntl } from '@/flavours/glitch/components/intl'; import Option from 'flavours/glitch/features/report/components/option'; import { languages as preloadedLanguages } from 'flavours/glitch/initial_state'; diff --git a/app/javascript/flavours/glitch/features/ui/components/bundle_column_error.jsx b/app/javascript/flavours/glitch/features/ui/components/bundle_column_error.jsx index b41a152792..a76d9fdfdd 100644 --- a/app/javascript/flavours/glitch/features/ui/components/bundle_column_error.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/bundle_column_error.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { injectIntl, FormattedMessage } from 'react-intl'; +import { FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import { Helmet } from 'react-helmet'; @@ -9,6 +9,7 @@ import { Link } from 'react-router-dom'; import { Button } from 'flavours/glitch/components/button'; import Column from 'flavours/glitch/components/column'; +import { injectIntl } from '@/flavours/glitch/components/intl'; import { GIF } from 'flavours/glitch/components/gif'; class CopyButton extends PureComponent { diff --git a/app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.jsx index 2f0c07e78b..f2451c9c84 100644 --- a/app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/deprecated_settings_modal.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; @@ -12,6 +12,7 @@ import { Button } from 'flavours/glitch/components/button'; import { Icon } from 'flavours/glitch/components/icon'; import illustration from 'flavours/glitch/images/logo_warn_glitch.svg'; import { preferenceLink } from 'flavours/glitch/utils/backend_links'; +import { injectIntl } from '@/flavours/glitch/components/intl'; const messages = defineMessages({ discardChanges: { id: 'confirmations.deprecated_settings.confirm', defaultMessage: 'Use Mastodon preferences' }, diff --git a/app/javascript/flavours/glitch/features/ui/components/filter_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/filter_modal.jsx index ac260f2f01..4ef0c713c0 100644 --- a/app/javascript/flavours/glitch/features/ui/components/filter_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/filter_modal.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; @@ -9,6 +9,7 @@ import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { fetchFilters, createFilter, createFilterStatus } from 'flavours/glitch/actions/filters'; import { fetchStatus } from 'flavours/glitch/actions/statuses'; import { IconButton } from 'flavours/glitch/components/icon_button'; +import { injectIntl } from '@/flavours/glitch/components/intl'; import AddedToFilter from 'flavours/glitch/features/filters/added_to_filter'; import SelectFilter from 'flavours/glitch/features/filters/select_filter'; diff --git a/app/javascript/flavours/glitch/features/ui/components/report_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/report_modal.jsx index 98ff00bcbf..1eb774e3d3 100644 --- a/app/javascript/flavours/glitch/features/ui/components/report_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/report_modal.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import { OrderedSet } from 'immutable'; import ImmutablePropTypes from 'react-immutable-proptypes'; @@ -13,6 +13,7 @@ import { submitReport } from 'flavours/glitch/actions/reports'; import { fetchServer } from 'flavours/glitch/actions/server'; import { expandAccountTimeline } from 'flavours/glitch/actions/timelines'; import { IconButton } from 'flavours/glitch/components/icon_button'; +import { injectIntl } from '@/flavours/glitch/components/intl'; import Category from 'flavours/glitch/features/report/category'; import Comment from 'flavours/glitch/features/report/comment'; import Rules from 'flavours/glitch/features/report/rules'; diff --git a/app/javascript/flavours/glitch/features/ui/components/skip_links/skip_links.module.scss b/app/javascript/flavours/glitch/features/ui/components/skip_links/skip_links.module.scss index 1d4dc1c3f5..ad206dfecc 100644 --- a/app/javascript/flavours/glitch/features/ui/components/skip_links/skip_links.module.scss +++ b/app/javascript/flavours/glitch/features/ui/components/skip_links/skip_links.module.scss @@ -33,7 +33,7 @@ &:focus-within { outline: var(--outline-focus-default); - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); } :any-link { diff --git a/app/javascript/flavours/glitch/features/ui/index.jsx b/app/javascript/flavours/glitch/features/ui/index.jsx index 431aad21c4..5b72ee482c 100644 --- a/app/javascript/flavours/glitch/features/ui/index.jsx +++ b/app/javascript/flavours/glitch/features/ui/index.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import { Redirect, Route, withRouter } from 'react-router-dom'; @@ -17,6 +17,7 @@ import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'flavour import { fetchNotifications } from 'flavours/glitch/actions/notification_groups'; import { INTRODUCTION_VERSION } from 'flavours/glitch/actions/onboarding'; import { AlertsController } from 'flavours/glitch/components/alerts_controller'; +import { injectIntl } from '@/flavours/glitch/components/intl'; import { Hotkeys } from 'flavours/glitch/components/hotkeys'; import { HoverCardController } from 'flavours/glitch/components/hover_card_controller'; import { Permalink } from 'flavours/glitch/components/permalink'; diff --git a/app/javascript/flavours/glitch/locales/intl_provider.tsx b/app/javascript/flavours/glitch/locales/intl_provider.tsx index 1e8a073249..868c758695 100644 --- a/app/javascript/flavours/glitch/locales/intl_provider.tsx +++ b/app/javascript/flavours/glitch/locales/intl_provider.tsx @@ -50,7 +50,6 @@ export const IntlProvider: React.FC< locale={locale} messages={messages} onError={onProviderError} - textComponent='span' {...props} > {children} diff --git a/app/javascript/flavours/glitch/polyfills/intl.ts b/app/javascript/flavours/glitch/polyfills/intl.ts index 80be02fdaa..bad08fad56 100644 --- a/app/javascript/flavours/glitch/polyfills/intl.ts +++ b/app/javascript/flavours/glitch/polyfills/intl.ts @@ -1,6 +1,6 @@ // import { shouldPolyfill as shouldPolyfillCanonicalLocales } from '@formatjs/intl-getcanonicallocales/should-polyfill'; // import { shouldPolyfill as shouldPolyfillLocale } from '@formatjs/intl-locale/should-polyfill'; -import { shouldPolyfill as shoudPolyfillPluralRules } from '@formatjs/intl-pluralrules/should-polyfill'; +import { shouldPolyfill as shoudPolyfillPluralRules } from '@formatjs/intl-pluralrules/should-polyfill.js'; // import { shouldPolyfill as shouldPolyfillNumberFormat } from '@formatjs/intl-numberformat/should-polyfill'; // import { shouldPolyfill as shouldPolyfillIntlDateTimeFormat } from '@formatjs/intl-datetimeformat/should-polyfill'; // import { shouldPolyfill as shouldPolyfillIntlRelativeTimeFormat } from '@formatjs/intl-relativetimeformat/should-polyfill'; @@ -54,7 +54,7 @@ async function loadIntlPluralRulesPolyfills(locale: string) { return; } // Load the polyfill 1st BEFORE loading data - await import('@formatjs/intl-pluralrules/polyfill-force'); + await import('@formatjs/intl-pluralrules/polyfill-force.js'); await import( `../../../../../node_modules/@formatjs/intl-pluralrules/locale-data/${unsupportedLocale}.js` ); diff --git a/app/javascript/flavours/glitch/selectors/user_lists.ts b/app/javascript/flavours/glitch/selectors/user_lists.ts index 9d681aa255..ed5a3271ba 100644 --- a/app/javascript/flavours/glitch/selectors/user_lists.ts +++ b/app/javascript/flavours/glitch/selectors/user_lists.ts @@ -29,7 +29,7 @@ export const selectUserListWithoutMe = createAppSelector( .filter((id) => id !== currentAccountId) .toArray(), isLoading: !!list.get('isLoading', true), - hasMore: !!list.get('hasMore', false), + hasMore: !!list.get('next'), }; }, ); diff --git a/app/javascript/flavours/glitch/styles/mastodon/_mixins.scss b/app/javascript/flavours/glitch/styles/mastodon/_mixins.scss index f0ba5e635d..8accb19ae7 100644 --- a/app/javascript/flavours/glitch/styles/mastodon/_mixins.scss +++ b/app/javascript/flavours/glitch/styles/mastodon/_mixins.scss @@ -18,7 +18,7 @@ background: var(--color-bg-secondary); color: var(--color-text-primary); border-radius: 4px; - border: 1px solid var(--color-border-on-bg-secondary); + border: 1px solid var(--color-border-primary); font-size: 17px; line-height: normal; margin: 0; diff --git a/app/javascript/flavours/glitch/styles/mastodon/accounts.scss b/app/javascript/flavours/glitch/styles/mastodon/accounts.scss index 03f15cfdff..9c589d2075 100644 --- a/app/javascript/flavours/glitch/styles/mastodon/accounts.scss +++ b/app/javascript/flavours/glitch/styles/mastodon/accounts.scss @@ -13,7 +13,7 @@ &:active, &:focus { .card__bar { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); } } } @@ -221,8 +221,8 @@ .information-badge { &.superapp { color: var(--color-text-success); - background-color: var(--color-bg-success-softer); - border-color: var(--color-border-on-bg-success-softer); + background-color: var(--color-bg-success-softest); + border-color: var(--color-border-success-soft); } } @@ -230,7 +230,7 @@ display: inline-flex; padding: 4px; padding-inline-end: 8px; - border: 1px solid var(--color-text-brand); + border: 1px solid var(--color-border-brand); color: var(--color-text-brand); font-weight: 500; font-size: 12px; @@ -256,8 +256,8 @@ .simple_form .not_recommended { color: var(--color-text-error); - background-color: var(--color-bg-error-softer); - border-color: var(--color-border-on-bg-error-softer); + background-color: var(--color-bg-error-softest); + border-color: var(--color-border-error-soft); } .simple_form .glitch_only { @@ -317,8 +317,8 @@ } .verified { - border: 1px solid var(--color-border-on-bg-success-softer); - background: var(--color-bg-success-softer); + border: 1px solid var(--color-border-success-soft); + background: var(--color-bg-success-softest); a { color: var(--color-text-success); diff --git a/app/javascript/flavours/glitch/styles/mastodon/admin.scss b/app/javascript/flavours/glitch/styles/mastodon/admin.scss index 45869a1624..46b2eeb8e4 100644 --- a/app/javascript/flavours/glitch/styles/mastodon/admin.scss +++ b/app/javascript/flavours/glitch/styles/mastodon/admin.scss @@ -68,7 +68,7 @@ $content-width: 840px; border-radius: 4px; &:focus { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); } .material-close { @@ -437,10 +437,57 @@ $content-width: 840px; } ul .simple-navigation-active-leaf a { - border-bottom-color: var(--color-text-brand); + border-bottom-color: var(--color-border-brand); } } } + + .callout { + display: flex; + align-items: start; + padding: 12px; + gap: 8px; + background-color: var(--color-bg-brand-softest); + color: var(--color-text-primary); + border-radius: 12px; + font-size: 15px; + margin-bottom: 30px; + + .icon { + padding: 4px; + border-radius: 9999px; + width: 1rem; + height: 1rem; + margin-top: -2px; + background-color: var(--color-bg-brand-soft); + } + + .content { + display: flex; + flex-direction: column; + gap: 8px; + flex-grow: 1; + padding: 0; + + @media screen and (width >= 630px) { + flex-direction: row; + } + } + + .body { + flex-grow: 1; + } + + .title { + font-weight: 600; + margin-bottom: 8px; + } + + a { + color: inherit; + font-weight: 600; + } + } } hr.spacer { @@ -498,7 +545,7 @@ body, kbd { font-family: Courier, monospace; - background-color: var(--color-bg-brand-softer); + background-color: var(--color-bg-brand-softest); padding: 4px; padding-bottom: 2px; border-radius: 5px; @@ -565,7 +612,7 @@ kbd { &.selected { color: var(--color-text-brand); - border-bottom: 2px solid var(--color-text-brand); + border-bottom: 2px solid var(--color-border-brand); } } } @@ -844,14 +891,14 @@ a.name-tag, .speech-bubble { margin-bottom: 20px; - border-inline-start: 4px solid var(--color-text-brand); + border-inline-start: 4px solid var(--color-border-brand); &.positive { border-color: var(--color-text-success); } &.negative { - border-color: var(--color-text-error); + border-color: var(--color-border-error); } &.warning { @@ -1313,7 +1360,7 @@ a.sparkline { &:hover, &:focus, &:active { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); } } @@ -1937,7 +1984,7 @@ a.sparkline { width: calc(1.375rem + 1px); height: calc(1.375rem + 1px); background: var(--color-bg-primary); - border: 1px solid var(--color-text-brand); + border: 1px solid var(--color-border-brand); color: var(--color-text-brand); border-radius: 8px; } @@ -2021,8 +2068,8 @@ a.sparkline { display: block; box-sizing: border-box; color: var(--color-text-primary); - background: var(--color-bg-brand-softer); - border: 1px solid var(--color-border-on-bg-brand-softer); + background: var(--color-bg-brand-softest); + border: 1px solid var(--color-border-brand-soft); border-radius: 8px; padding: 8px 13px; position: relative; diff --git a/app/javascript/flavours/glitch/styles/mastodon/basics.scss b/app/javascript/flavours/glitch/styles/mastodon/basics.scss index 610730df5a..a16afbe9a5 100644 --- a/app/javascript/flavours/glitch/styles/mastodon/basics.scss +++ b/app/javascript/flavours/glitch/styles/mastodon/basics.scss @@ -2,13 +2,13 @@ html { color: var(--color-text-primary); - background: var(--color-bg-ambient); + background: var(--color-bg-primary); &.custom-scrollbars { scrollbar-color: var(--color-text-secondary) var(--color-bg-secondary); } - --outline-focus-default: 2px solid var(--color-text-brand); + --outline-focus-default: 2px solid var(--color-border-brand); --avatar-border-radius: 8px; --max-media-height-small: 460px; --max-media-height-large: 566px; @@ -46,7 +46,7 @@ html.has-modal { body { font-family: $font-sans-serif, sans-serif; - background: var(--color-bg-ambient); + background: var(--color-bg-primary); font-size: 13px; line-height: 18px; font-weight: 400; diff --git a/app/javascript/flavours/glitch/styles/mastodon/components.scss b/app/javascript/flavours/glitch/styles/mastodon/components.scss index 117806878b..a876834151 100644 --- a/app/javascript/flavours/glitch/styles/mastodon/components.scss +++ b/app/javascript/flavours/glitch/styles/mastodon/components.scss @@ -114,7 +114,7 @@ } &:focus-visible { - outline: 2px solid var(--color-bg-brand-base); + outline: var(--outline-focus-default); outline-offset: 2px; } @@ -169,12 +169,12 @@ color: var(--color-text-brand); background: transparent; padding: 6px 17px; - border: 1px solid var(--color-text-brand); + border: 1px solid var(--color-border-brand); &:active, &:focus, &:hover { - border-color: var(--color-text-brand); + border-color: var(--color-border-brand); color: var(--color-text-brand); background-color: transparent; text-decoration: none; @@ -184,7 +184,7 @@ &:active, &:focus, &:hover { - border-color: var(--color-text-error); + border-color: var(--color-border-error); color: var(--color-text-error); } } @@ -284,7 +284,8 @@ --default-icon-color: var(--color-text-secondary); --default-bg-color: transparent; --hover-icon-color: var(--color-text-primary); - --hover-bg-color: var(--color-bg-brand-softer); + --hover-bg-color: var(--color-bg-brand-softest); + --focus-outline-color: var(--color-border-brand); display: inline-flex; color: var(--default-icon-color); @@ -313,7 +314,7 @@ } &:focus-visible { - outline: 2px solid var(--color-text-brand); + outline: 2px solid var(--focus-outline-color); } &.disabled { @@ -363,8 +364,8 @@ &.copied { color: var(--color-text-success); transition: none; - background-color: var(--color-bg-success-softer); - border-color: var(--color-border-on-bg-brand-softer); + background-color: var(--color-bg-success-softest); + border-color: var(--color-border-success-soft); } } @@ -536,21 +537,21 @@ body > [data-popper-placement] { flex-direction: column; flex: 0 1 auto; border-radius: 4px; - border: 1px solid var(--color-border-on-bg-secondary); + border: 1px solid var(--color-border-primary); transition: border-color 300ms linear; position: relative; background: var(--color-bg-secondary); &.active { transition: none; - border-color: var(--color-text-brand); + border-color: var(--color-border-brand); } } &__warning { color: var(--color-text-primary); - background: var(--color-bg-warning-softer); - border: 1px solid var(--color-border-on-bg-warning-softer); + background: var(--color-bg-warning-softest); + border: 1px solid var(--color-border-warning-soft); padding: 8px 10px; border-radius: 4px; font-size: 13px; @@ -618,7 +619,7 @@ body > [data-popper-placement] { .spoiler-input__input { padding: 12px 12px - 5px; - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); color: var(--color-text-brand); } @@ -937,7 +938,7 @@ body > [data-popper-placement] { line-height: 20px; letter-spacing: 0.1px; color: var(--color-text-brand); - background-color: var(--color-bg-secondary-solid); + background-color: var(--color-bg-secondary); white-space: nowrap; text-overflow: ellipsis; overflow: hidden; @@ -969,7 +970,7 @@ body > [data-popper-placement] { gap: 4px; color: var(--color-text-brand); background: transparent; - border: 1px solid var(--color-text-brand); + border: 1px solid var(--color-border-brand); border-radius: 6px; padding: 4px 8px; font-size: 13px; @@ -1522,9 +1523,9 @@ body > [data-popper-placement] { .focusable { &:focus-visible { - outline: 2px solid var(--color-text-brand); + outline: 2px solid var(--color-border-brand); outline-offset: -2px; - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); } } @@ -1646,7 +1647,7 @@ body > [data-popper-placement] { content: ''; position: absolute; inset: 0; - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); opacity: 0; animation: fade 0.7s reverse both 0.3s; pointer-events: none; @@ -1801,7 +1802,7 @@ body > [data-popper-placement] { .notification-ungrouped--direct, .notification-group--direct, .notification-group--annual-report { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); &:focus { background: var(--color-bg-brand-soft); @@ -1942,7 +1943,7 @@ body > [data-popper-placement] { .detailed-status__wrapper-direct { .detailed-status, .detailed-status__action-bar { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); } .status__prepend { @@ -1997,7 +1998,7 @@ body > [data-popper-placement] { line-height: 20px; letter-spacing: 0.25px; color: var(--color-text-secondary); - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); border-radius: 8px; cursor: default; } @@ -2125,7 +2126,7 @@ body > [data-popper-placement] { &__domain-pill { display: inline-flex; - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); border-radius: 4px; border: 0; color: var(--color-text-brand); @@ -2186,7 +2187,7 @@ body > [data-popper-placement] { &__handle { border: 2px dashed var(--color-border-on-brand-softer); - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); padding: 12px 8px; color: var(--color-text-brand); border-radius: 4px; @@ -2967,11 +2968,15 @@ a.account__display-name { &:focus, &:hover, &:active { - &:not(:disabled, [aria-disabled='true']) { - background: var(--color-bg-secondary); + &:where(:not(:disabled, [aria-disabled='true'])) { + background: var(--color-bg-brand-softest); outline: 0; } } + + &:focus-visible { + outline: var(--outline-focus-default); + } } button:disabled, @@ -3136,7 +3141,7 @@ a.account__display-name { } &:focus-visible { - border-top-color: var(--color-text-brand); + border-top-color: var(--color-border-brand); border-radius: 0; } } @@ -3151,7 +3156,7 @@ a.account__display-name { border-top: 0; @media screen and (min-width: $no-gap-breakpoint) { - border-top: 10px solid var(--color-bg-ambient); + border-top: 10px solid var(--color-bg-primary); } } @@ -3650,7 +3655,7 @@ a.account__display-name { &.focused { transition: none; outline: 0; - border-color: var(--color-text-brand); + border-color: var(--color-border-brand); } &.copied { @@ -4050,25 +4055,14 @@ a.account__display-name { height: 20px; padding: 0; border-radius: 10px; - background-color: rgb(from var(--color-bg-brand-softer) r g b / 50%); - border: 1px solid rgb(from var(--color-text-brand) r g b / 50%); + background-color: var(--color-bg-tertiary); + border: 1px solid var(--color-border-primary); box-sizing: border-box; - .react-toggle:hover:not(.react-toggle--disabled) & { - background-color: rgb( - from var(--color-bg-brand-softer) r g b / - calc(50% + var(--overlay-strength-brand)) - ); - } - .react-toggle--checked & { background-color: var(--color-bg-brand-base); border-color: var(--color-bg-brand-base); } - - .react-toggle--checked:not(.react-toggle--disabled):hover & { - background-color: var(--color-bg-brand-base-hover); - } } .react-toggle-track-check, @@ -4084,6 +4078,7 @@ a.account__display-name { height: 16px; border-radius: 50%; background-color: var(--color-text-on-brand-base); + box-shadow: 0 2px 4px 0 color-mix(var(--color-black), transparent 75%); box-sizing: border-box; transition: all 0.25s ease; transition-property: border-color, left; @@ -4145,8 +4140,8 @@ a.account__display-name { &:focus-visible { outline: none; - border-color: var(--color-text-brand); - background: var(--color-bg-brand-softer); + border-color: var(--color-border-brand); + background: var(--color-bg-brand-softest); } &--logo { @@ -4169,11 +4164,10 @@ a.account__display-name { .column-subheading { background: var(--color-bg-secondary); color: var(--color-text-secondary); - padding: 8px 20px; - font-size: 12px; + padding: 12px 24px; + font-size: 13px; font-weight: 500; text-transform: uppercase; - cursor: default; } .getting-started__wrapper { @@ -4544,7 +4538,7 @@ a.status-card { } &:focus-visible { - outline: 2px solid var(--color-text-brand); + outline: var(--outline-focus-default); outline-offset: -2px; } } @@ -4632,7 +4626,7 @@ a.status-card { z-index: 1; &.active { - box-shadow: 0 1px 0 var(--color-bg-brand-softer); + box-shadow: 0 1px 0 var(--color-bg-brand-softest); &::before { display: block; @@ -4742,7 +4736,7 @@ a.status-card { &.active { .column-header__icon { color: var(--color-text-brand); - text-shadow: 0 0 10px var(--color-bg-brand-softer); + text-shadow: 0 0 10px var(--color-bg-brand-softest); } } @@ -5160,6 +5154,13 @@ a.status-card { background-color: rgb(from var(--color-bg-media-base) r g b / 90%); } } + + &:focus-visible { + .spoiler-button__overlay__label { + outline: 2px solid var(--color-text-on-media); + outline-offset: -4px; + } + } } } @@ -5281,7 +5282,7 @@ a.status-card { &:hover, &:active, &:focus { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); color: color-mix( in oklab, var(--color-text-primary), @@ -5988,7 +5989,7 @@ a.status-card { .visibility-modal { &__quote-warning { color: var(--color-text-primary); - background: var(--color-bg-warning-softer); + background: var(--color-bg-warning-softest); padding: 16px; border-radius: 4px; @@ -6023,7 +6024,7 @@ a.status-card { display: flex; align-items: center; color: var(--color-text-primary); - background: var(--color-bg-secondary-solid); + background: var(--color-bg-secondary); border: 1px solid var(--color-border-primary); padding: 8px 12px; width: 100%; @@ -6084,7 +6085,8 @@ a.status-card { .icon-button { padding: 0; - color: var(--color-text-secondary); + + --default-icon-color: inherit; } .icon { @@ -6137,7 +6139,10 @@ a.status-card { background: var(--color-bg-brand-base); .icon-button { - color: inherit; + --default-icon-color: inherit; + --default-bg-color: transparent; + --hover-icon-color: inherit; + --hover-bg-color: var(--color-bg-brand-base-hover); } } @@ -6236,7 +6241,7 @@ a.status-card { } &:focus-visible { - box-shadow: 0 0 0 2px var(--color-text-brand); + box-shadow: 0 0 0 2px var(--color-border-brand); } &[aria-hidden='true'] { @@ -6490,9 +6495,7 @@ a.status-card { --default-icon-color: var(--color-text-on-media); --default-bg-color: transparent; --hover-icon-color: var(--color-text-on-media); - --hover-bg-color: rgb( - from var(--color-text-on-media) r g b / var(--overlay-strength-brand) - ); + --hover-bg-color: rgb(from var(--color-text-on-media) r g b / 10%); .icon { filter: var(--overlay-icon-shadow); @@ -6508,8 +6511,7 @@ a.status-card { --default-icon-color: var(--color-text-favourite-highlight); --hover-icon-color: var(--color-text-favourite-highlight); --hover-bg-color: rgb( - from var(--color-text-favourite-highlight) r g b / - var(--overlay-strength-brand) + from var(--color-text-favourite-highlight) r g b / 10% ); } @@ -7178,7 +7180,7 @@ a.status-card { } .button.button-secondary { - border-color: var(--color-text-error); + border-color: var(--color-border-error); color: var(--color-text-error); flex: 0 0 auto; @@ -7246,7 +7248,7 @@ a.status-card { &:hover, &:active, &:focus { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); } } } @@ -7380,6 +7382,13 @@ img.modal-warning { font-size: 14px; font-weight: 700; line-height: 20px; + + &:focus-visible { + outline: none; + box-shadow: + inset 0 0 0 2px var(--color-text-on-media), + 0 0 0 2px var(--color-bg-media); + } } } @@ -7409,6 +7418,13 @@ img.modal-warning { cursor: pointer; pointer-events: auto; + &:focus-visible { + outline: none; + box-shadow: + inset 0 0 0 2px var(--color-text-on-media), + 0 0 0 2px var(--color-bg-media); + } + &--non-interactive { pointer-events: none; } @@ -7433,6 +7449,16 @@ img.modal-warning { overflow-y: auto; z-index: 10; + &:focus-visible { + box-shadow: + var(--dropdown-shadow), + inset 0 0 0 2px var(--color-text-on-media); + + // Extend background color for better visibility of the + // inset box-shadow "outline" + outline: 2px solid var(--color-bg-media); + } + &--solid { color: var(--color-text-primary); background: var(--color-bg-primary); @@ -7684,6 +7710,7 @@ img.modal-warning { color: var(--color-text-primary); position: relative; z-index: -1; + border-radius: inherit; &, img { @@ -7732,6 +7759,23 @@ img.modal-warning { height: 100%; object-fit: cover; } + + &:focus { + outline: none; + border-radius: inherit; + } + + // Double focus outline for better visibility on photos + &:focus-visible::after { + content: ''; + position: absolute; + inset: 2px; + z-index: 1; + border-radius: inherit; + border: 2px solid var(--color-text-inverted); + outline: 2px solid var(--color-bg-inverted); + pointer-events: none; + } } /* End Media Gallery */ @@ -8434,7 +8478,7 @@ img.modal-warning { &.checked, &.indeterminate { - border-color: var(--color-text-brand); + border-color: var(--color-border-brand); } .icon { @@ -8976,7 +9020,7 @@ noscript { } &:focus { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); } } } @@ -9238,7 +9282,7 @@ noscript { } &__root { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); font-size: 13px; display: flex; align-items: flex-end; @@ -9322,13 +9366,13 @@ noscript { &__item { flex-shrink: 0; - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); color: color-mix( in oklab, var(--color-text-primary), var(--color-text-secondary) ); - border: 1px solid var(--color-border-on-bg-brand-softer); + border: 1px solid var(--color-border-brand-soft); border-radius: 3px; box-sizing: border-box; margin: 2px; @@ -9377,8 +9421,8 @@ noscript { &.active { color: var(--color-text-brand); - background-color: var(--color-bg-brand-softer); - border-color: var(--color-text-brand); + background-color: var(--color-bg-brand-softest); + border-color: var(--color-border-brand); transition: all 100ms ease-in; transition-property: background-color, color; } @@ -9441,7 +9485,7 @@ noscript { inset-inline-start: 0; width: 100%; height: 100%; - border-inline-start: 4px solid var(--color-text-brand); + border-inline-start: 4px solid var(--color-border-brand); pointer-events: none; } } @@ -9970,7 +10014,7 @@ noscript { } &.invalid &__input { - border-color: var(--color-text-error); + border-color: var(--color-border-error); } &.expanded .search__popout { @@ -10241,8 +10285,8 @@ noscript { margin: 10px; margin-bottom: 5px; border-radius: 8px; - border: 1px solid var(--color-border-on-bg-brand-softer); - background: var(--color-bg-brand-softer); + border: 1px solid var(--color-border-brand-soft); + background: var(--color-bg-brand-softest); overflow: hidden; flex-shrink: 0; @@ -10308,8 +10352,8 @@ noscript { } .warning-banner { - border: 1px solid var(--color-border-on-bg-error-softer); - background: var(--color-bg-error-softer); + border: 1px solid var(--color-border-error-soft); + background: var(--color-bg-error-softest); &__message { h1 { @@ -10645,7 +10689,7 @@ noscript { width: auto; padding: 15px; margin: 0; - color: var(--color-text-on-inverted); + color: var(--color-text-inverted); background: var(--color-bg-inverted); backdrop-filter: blur(8px); border: 1px solid var(--color-border-on-bg-inverted); @@ -10700,7 +10744,7 @@ noscript { &:hover, &:focus, &:active { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); } } @@ -10802,13 +10846,16 @@ noscript { color: inherit; text-decoration: none; padding: 4px 12px; - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); border-radius: 4px; font-weight: 500; &:hover, &:focus, - &:active { + &:active, + .focusable:focus-visible &, + .detailed-status__wrapper-direct .detailed-status &, + .status__wrapper-direct & { background: var(--color-bg-brand-soft); } } @@ -10828,10 +10875,10 @@ noscript { padding: 16px 0; padding-bottom: 0; border-bottom: 1px solid var(--color-border-primary); - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); &.focusable:focus-visible { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); } &__header { @@ -10973,6 +11020,7 @@ noscript { &__source { display: inline-flex; align-items: center; + max-width: 100%; color: var(--color-text-tertiary); gap: 4px; overflow: hidden; @@ -11193,12 +11241,6 @@ noscript { color: var(--color-text-secondary); } - & > span { - display: flex; - align-items: center; - gap: 8px; - } - a { display: inline-flex; align-items: center; @@ -11499,7 +11541,7 @@ noscript { inset-inline-start: 0; width: 100%; height: 100%; - border-inline-start: 4px solid var(--color-text-brand); + border-inline-start: 4px solid var(--color-border-brand); pointer-events: none; } } @@ -11696,8 +11738,8 @@ noscript { display: block; box-sizing: border-box; color: var(--color-text-primary); - background: var(--color-bg-brand-softer); - border: 1px solid var(--color-border-on-bg-brand-softer); + background: var(--color-bg-brand-softest); + border: 1px solid var(--color-border-brand-soft); border-radius: 8px; padding: 8px (5px + 8px); position: relative; diff --git a/app/javascript/flavours/glitch/styles/mastodon/dashboard.scss b/app/javascript/flavours/glitch/styles/mastodon/dashboard.scss index db3f0e8a84..014021394b 100644 --- a/app/javascript/flavours/glitch/styles/mastodon/dashboard.scss +++ b/app/javascript/flavours/glitch/styles/mastodon/dashboard.scss @@ -30,7 +30,7 @@ &:hover, &:focus, &:active { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); } } } @@ -100,12 +100,12 @@ } &.positive { - background: var(--color-bg-success-softer); + background: var(--color-bg-success-softest); color: var(--color-text-success); } &.negative { - background: var(--color-bg-error-softer); + background: var(--color-bg-error-softest); color: var(--color-text-error); } diff --git a/app/javascript/flavours/glitch/styles/mastodon/emoji_picker.scss b/app/javascript/flavours/glitch/styles/mastodon/emoji_picker.scss index ad2f2f630d..2d1e03d7b9 100644 --- a/app/javascript/flavours/glitch/styles/mastodon/emoji_picker.scss +++ b/app/javascript/flavours/glitch/styles/mastodon/emoji_picker.scss @@ -168,7 +168,7 @@ inset-inline-start: 0; width: 100%; height: 100%; - background-color: var(--color-bg-brand-softer); + background-color: var(--color-bg-brand-softest); border-radius: 100%; } } diff --git a/app/javascript/flavours/glitch/styles/mastodon/forms.scss b/app/javascript/flavours/glitch/styles/mastodon/forms.scss index ecdb660bf1..3d34effe07 100644 --- a/app/javascript/flavours/glitch/styles/mastodon/forms.scss +++ b/app/javascript/flavours/glitch/styles/mastodon/forms.scss @@ -32,7 +32,7 @@ code { display: block; background: linear-gradient( to bottom, - var(--color-bg-secondary-solid), + var(--color-bg-secondary), transparent ); position: absolute; @@ -574,7 +574,7 @@ code { input[type='datetime-local'] { &:focus:user-invalid:not(:placeholder-shown), &:required:user-invalid:not(:placeholder-shown) { - border-color: var(--color-text-error); + border-color: var(--color-border-error); } } @@ -764,7 +764,7 @@ code { input[type='datetime-local'], textarea, select { - border-color: var(--color-text-error); + border-color: var(--color-border-error); } } @@ -802,27 +802,27 @@ code { .flash-message { color: var(--color-text-brand); background: transparent; - border: 1px solid var(--color-text-brand); + border: 1px solid var(--color-border-brand); border-radius: 4px; padding: 15px 10px; margin-bottom: 30px; text-align: center; &.notice { - border: 1px solid var(--color-border-on-bg-success-softer); - background: var(--color-bg-success-softer); + border: 1px solid var(--color-border-success-soft); + background: var(--color-bg-success-softest); color: var(--color-text-success); } &.warning { - border: 1px solid var(--color-border-on-bg-warning-softer); - background: var(--color-bg-warning-softer); + border: 1px solid var(--color-border-warning-soft); + background: var(--color-bg-warning-softest); color: var(--color-text-warning); } &.alert { - border: 1px solid var(--color-border-on-bg-error-softer); - background: var(--color-bg-error-softer); + border: 1px solid var(--color-border-error-soft); + background: var(--color-bg-error-softest); color: var(--color-text-error); } @@ -888,7 +888,7 @@ code { } &:focus { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); } } @@ -1374,7 +1374,7 @@ code { cursor: pointer; &:hover { - background-color: var(--color-bg-brand-softer); + background-color: var(--color-bg-brand-softest); } img { @@ -1405,7 +1405,7 @@ code { } &.invalid img { - outline: 1px solid var(--color-text-error); + outline: 1px solid var(--color-border-error); outline-offset: -1px; } @@ -1415,7 +1415,7 @@ code { width: 100%; height: 100%; position: absolute; - background: var(--color-bg-error-softer); + background: var(--color-bg-error-softest); z-index: 2; border-radius: 8px; } diff --git a/app/javascript/flavours/glitch/styles/mastodon/modal.scss b/app/javascript/flavours/glitch/styles/mastodon/modal.scss index 6af2a182b6..4ffbd1d7bb 100644 --- a/app/javascript/flavours/glitch/styles/mastodon/modal.scss +++ b/app/javascript/flavours/glitch/styles/mastodon/modal.scss @@ -1,7 +1,7 @@ @use 'variables' as *; .modal-layout { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); display: flex; flex-direction: column; height: 100vh; @@ -42,7 +42,7 @@ position: absolute; inset: auto 0 0; height: 32px; - background-color: var(--color-bg-brand-softer); + background-color: var(--color-bg-brand-softest); /* Decorative zig-zag pattern at the bottom of the page */ mask-image: url('data:image/svg+xml;utf8,'); diff --git a/app/javascript/flavours/glitch/styles/mastodon/polls.scss b/app/javascript/flavours/glitch/styles/mastodon/polls.scss index 19fb8dd505..ce7f51f8cd 100644 --- a/app/javascript/flavours/glitch/styles/mastodon/polls.scss +++ b/app/javascript/flavours/glitch/styles/mastodon/polls.scss @@ -86,7 +86,7 @@ padding: 8px 12px; &:focus { - border-color: var(--color-text-brand); + border-color: var(--color-border-brand); } @media screen and (width <= 600px) { diff --git a/app/javascript/flavours/glitch/styles/mastodon/tables.scss b/app/javascript/flavours/glitch/styles/mastodon/tables.scss index 8e303aff68..1088781417 100644 --- a/app/javascript/flavours/glitch/styles/mastodon/tables.scss +++ b/app/javascript/flavours/glitch/styles/mastodon/tables.scss @@ -93,6 +93,26 @@ } } + &.mini-table { + border-top: 1px solid var(--color-border-primary); + width: 50%; + + & > tbody > tr > th, + & > tbody > tr > td { + padding: 12px 0; + } + + & > tbody > tr > th { + color: var(--color-text-secondary); + font-weight: 400; + } + + & > tbody > tr > td { + color: var(--color-text-primary); + font-weight: 600; + } + } + &.batch-table { & > thead > tr > th { background: var(--color-bg-primary); diff --git a/app/javascript/flavours/glitch/styles/mastodon/theme/_base.scss b/app/javascript/flavours/glitch/styles/mastodon/theme/_base.scss index 85fd0dab45..9b39f1b02e 100644 --- a/app/javascript/flavours/glitch/styles/mastodon/theme/_base.scss +++ b/app/javascript/flavours/glitch/styles/mastodon/theme/_base.scss @@ -1,40 +1,69 @@ @mixin palette { - --color-black: #000; - --color-grey-950: #181820; - --color-grey-800: #3a3a50; - --color-grey-700: #44445f; - --color-grey-600: #535374; - --color-grey-500: #67678e; - --color-grey-400: #88a; - --color-grey-300: #b2b1c8; - --color-grey-200: #d7d6e1; - --color-grey-100: #eeedf3; - --color-grey-50: #f6f6f9; --color-white: #fff; - --color-indigo-700: #5638cc; - --color-indigo-600: #6147e6; - --color-indigo-400: #8280f9; - --color-indigo-300: #a5abfd; - --color-indigo-200: #c8cdfe; - --color-indigo-100: #e0e3ff; + --color-black: #000; + + // Grey + --color-grey-50: #f6f6f9; + --color-grey-100: #eeedf3; + --color-grey-200: #d7d6e1; + --color-grey-300: #b2b1c8; + --color-grey-400: #88a; + --color-grey-500: #67678e; + --color-grey-600: #535374; + --color-grey-700: #44445f; + --color-grey-800: #3a3a50; + --color-grey-900: #21212c; + --color-grey-950: #181820; + + // Indigo --color-indigo-50: #f0f1ff; + --color-indigo-100: #e0e3ff; + --color-indigo-200: #c8cdfe; + --color-indigo-300: #a5abfd; + --color-indigo-400: #8280f9; + --color-indigo-500: #7263f2; + --color-indigo-600: #6147e6; + --color-indigo-700: #5638cc; + --color-indigo-800: #48359c; + --color-indigo-900: #3d317c; + --color-indigo-950: #261e48; + + // Red --color-red-50: #fef2f2; --color-red-100: #ffe2e2; + --color-red-200: #ffc9c9; --color-red-300: #ffa2a2; + --color-red-400: #ff6467; + --color-red-500: #fb2c36; + --color-red-600: #e7000b; + --color-red-700: #c10007; --color-red-800: #9f0712; --color-red-900: #82181a; --color-red-950: #460809; + + // Yellow --color-yellow-50: #fffbeb; --color-yellow-100: #fef3c6; + --color-yellow-200: #fee685; + --color-yellow-300: #ffd230; --color-yellow-400: #ffb900; + --color-yellow-500: #fe9a00; --color-yellow-600: #e17100; --color-yellow-700: #bb4d00; + --color-yellow-800: #973c00; --color-yellow-900: #7b3306; --color-yellow-950: #461901; + + // Green --color-green-50: #f0fdf4; --color-green-100: #dcfce7; + --color-green-200: #b9f8cf; + --color-green-300: #7bf1a8; --color-green-400: #05df72; + --color-green-500: #00c950; --color-green-600: #00a63e; + --color-green-700: #008236; + --color-green-800: #016630; --color-green-900: #0d542b; --color-green-950: #032e15; } diff --git a/app/javascript/flavours/glitch/styles/mastodon/theme/_dark.scss b/app/javascript/flavours/glitch/styles/mastodon/theme/_dark.scss index a22c7cc8f4..161524cdc4 100644 --- a/app/javascript/flavours/glitch/styles/mastodon/theme/_dark.scss +++ b/app/javascript/flavours/glitch/styles/mastodon/theme/_dark.scss @@ -5,150 +5,92 @@ --color-text-primary: var(--color-grey-100); --color-text-secondary: var(--color-grey-300); - --color-text-tertiary: var(--color-grey-400); - --color-text-on-inverted: var(--color-grey-950); + --color-text-tertiary: var(--color-grey-400); // legacy + --color-text-inverted: var(--color-grey-950); --color-text-brand: var(--color-indigo-300); --color-text-brand-soft: color-mix( in oklab, var(--color-text-primary), var(--color-text-brand) - ); + ); // legacy + --color-text-on-brand-base: var(--color-white); - --color-text-brand-on-inverted: var(--color-indigo-600); + --color-text-brand-on-inverted: var(--color-indigo-600); // legacy --color-text-error: var(--color-red-300); --color-text-on-error-base: var(--color-white); - --color-text-warning: var(--color-yellow-400); + --color-text-warning: var(--color-yellow-400); // legacy --color-text-on-warning-base: var(--color-white); - --color-text-success: var(--color-green-400); + --color-text-success: var(--color-green-400); // legacy --color-text-on-success-base: var(--color-white); - --color-text-disabled: var(--color-grey-600); - --color-text-on-disabled: var(--color-grey-400); - --color-text-bookmark-highlight: var(--color-text-error); - --color-text-favourite-highlight: var(--color-text-warning); - --color-text-on-media: var(--color-white); + --color-text-disabled: var(--color-grey-600); // legacy + --color-text-on-disabled: var(--color-grey-400); // legacy + --color-text-bookmark-highlight: var(--color-text-error); // legacy + --color-text-favourite-highlight: var(--color-text-warning); // legacy + --color-text-on-media: var(--color-white); // legacy --color-text-status-links: color-mix( in oklab, var(--color-text-primary), var(--color-text-secondary) - ); + ); // legacy /* BACKGROUND TOKENS */ // Neutrals --color-bg-primary: var(--color-grey-950); --overlay-strength-secondary: 4%; - --color-bg-secondary-base: var(--color-white); - --color-bg-secondary: #{utils.css-alpha( - var(--color-bg-secondary-base), - var(--overlay-strength-secondary) - )}; - --color-bg-secondary-solid: color-mix( - in srgb, - var(--color-bg-primary), - var(--color-bg-secondary-base) var(--overlay-strength-secondary) - ); - --color-bg-tertiary: color-mix( - in oklab, - var(--color-bg-primary), - var(--color-bg-secondary-base) calc(2 * var(--overlay-strength-secondary)) - ); + --color-bg-secondary: var(--color-grey-900); + --color-bg-tertiary: var(--color-grey-800); // legacy // Utility - --color-bg-ambient: var(--color-bg-primary); --color-bg-inverted: var(--color-grey-50); - --color-bg-media-base: var(--color-black); - --color-bg-media-strength: 65%; - --color-bg-media: #{utils.css-alpha( - var(--color-bg-media-base), - var(--color-bg-media-strength) - )}; - --color-bg-overlay: var(--color-black); - --color-bg-disabled: var(--color-grey-700); + --color-bg-overlay-base: #{utils.css-alpha(var(--color-grey-950), 60%)}; + --color-bg-overlay-highlight: #{utils.css-alpha(var(--color-white), 5%)}; + --color-bg-overlay: var(--color-black); // legacy + --color-bg-media-base: var(--color-black); // legacy + --color-bg-media: #{utils.css-alpha(var(--color-bg-media-base), 65%)}; // legacy + --color-bg-disabled: var(--color-grey-700); // legacy // Brand - --overlay-strength-brand: 22%; --color-bg-brand-base: var(--color-indigo-700); - --color-bg-brand-base-hover: color-mix( - in oklab, - var(--color-bg-brand-base), - var(--color-bg-primary) var(--overlay-strength-brand) - ); - --color-bg-brand-soft: #{utils.css-alpha( - #6f4df5, - calc(var(--overlay-strength-brand) * 2) - )}; - --color-bg-brand-softer: #{utils.css-alpha( - var(--color-bg-brand-base), - var(--overlay-strength-brand) - )}; - --color-bg-brand-softer-solid: color-mix( - in srgb, - var(--color-bg-primary), - var(--color-bg-brand-base) var(--overlay-strength-brand) - ); + --color-bg-brand-base-hover: var(--color-indigo-800); // legacy + --color-bg-brand-soft: var(--color-indigo-900); + --color-bg-brand-softest: var(--color-indigo-950); // Error - --overlay-strength-error: 10%; - --color-bg-error-base: var(--color-red-800); - --color-bg-error-base-hover: color-mix( - in oklab, - var(--color-bg-error-base), - var(--color-bg-primary) var(--overlay-strength-error) - ); + --color-bg-error-base: var(--color-red-700); + --color-bg-error-base-hover: var(--color-red-800); // legacy --color-bg-error-soft: var(--color-red-900); - --color-bg-error-softer: var(--color-red-950); + --color-bg-error-softest: var(--color-red-950); // Warning - --overlay-strength-warning: 10%; - --color-bg-warning-base: var(--color-yellow-700); - --color-bg-warning-base-hover: color-mix( - in oklab, - var(--color-bg-warning-base), - var(--color-bg-primary) var(--overlay-strength-warning) - ); + --color-bg-warning-base: var(--color-yellow-700); // legacy --color-bg-warning-soft: var(--color-yellow-900); - --color-bg-warning-softer: var(--color-yellow-950); + --color-bg-warning-softest: var(--color-yellow-950); // Success - --overlay-strength-success: 15%; - --color-bg-success-base: var(--color-green-600); - --color-bg-success-base-hover: color-mix( - in oklab, - var(--color-bg-success-base), - var(--color-bg-primary) var(--overlay-strength-success) - ); + --color-bg-success-base: var(--color-green-600); // legacy --color-bg-success-soft: var(--color-green-900); - --color-bg-success-softer: var(--color-green-950); + --color-bg-success-softest: var(--color-green-950); /* BORDER TOKENS */ - --border-strength-primary: 18%; - --color-border-primary: #{utils.css-alpha( - var(--color-indigo-200), - var(--border-strength-primary) - )}; - --color-border-media: rgb(252 248 255 / 15%); - --color-border-verified: rgb(220, 3, 240); - --color-border-on-bg-secondary: #{utils.css-alpha( - var(--color-indigo-200), - calc(var(--border-strength-primary) / 1.5) - )}; - --color-border-on-bg-brand-softer: var(--color-border-primary); - --color-border-on-bg-error-softer: #{utils.css-alpha( - var(--color-text-error), - 50% - )}; - --color-border-on-bg-warning-softer: #{utils.css-alpha( + --color-border-primary: var(--color-grey-800); + --color-border-brand: var(--color-text-brand); + --color-border-brand-soft: var(--color-indigo-800); + --color-border-error: var(--color-red-300); + --color-border-media: rgb(252 248 255 / 15%); // legacy + --color-border-error-soft: #{utils.css-alpha(var(--color-border-error), 50%)}; // legacy + --color-border-warning-soft: #{utils.css-alpha( var(--color-text-warning), 50% - )}; - --color-border-on-bg-success-softer: #{utils.css-alpha( + )}; // legacy + + --color-border-success-soft: #{utils.css-alpha( var(--color-text-success), 50% - )}; - --color-border-on-bg-inverted: var(--color-border-primary); + )}; // legacy - /* SHADOW TOKENS */ + /* SHADOW TOKENS (LEGACY) */ --shadow-strength-primary: 80%; --color-shadow-primary: #{utils.css-alpha( @@ -160,16 +102,16 @@ 0 8px 10px -6px var(--color-shadow-primary); --overlay-icon-shadow: drop-shadow(0 0 8px var(--color-shadow-primary)); - /* GRAPHS/CHARTS TOKENS */ + /* GRAPHS/CHARTS TOKENS (LEGACY) */ --color-graph-primary-stroke: var(--color-text-brand); - --color-graph-primary-fill: var(--color-bg-brand-softer); + --color-graph-primary-fill: var(--color-bg-brand-softest); --color-graph-warning-stroke: var(--color-text-warning); - --color-graph-warning-fill: var(--color-bg-warning-softer); + --color-graph-warning-fill: var(--color-bg-warning-softest); --color-graph-disabled-stroke: var(--color-text-disabled); --color-graph-disabled-fill: var(--color-bg-disabled); - /* LEGACY TOKENS */ + /* RICH TEXT TOKENS (LEGACY) */ --rich-text-container-color: rgb(87 24 60 / 100%); --rich-text-text-color: rgb(255 175 212 / 100%); @@ -182,7 +124,9 @@ --color-text-primary: var(--color-grey-50); --color-text-status-links: var(--color-text-brand); - /* BORDER TOKENS */ + /* BACKGROUND TOKENS */ + --color-bg-error-base: var(--color-red-800); - --border-strength-primary: 30%; + /* BORDER TOKENS */ + --color-border-primary: var(--color-grey-600); } diff --git a/app/javascript/flavours/glitch/styles/mastodon/theme/_light.scss b/app/javascript/flavours/glitch/styles/mastodon/theme/_light.scss index 47d32320fa..5759fffd75 100644 --- a/app/javascript/flavours/glitch/styles/mastodon/theme/_light.scss +++ b/app/javascript/flavours/glitch/styles/mastodon/theme/_light.scss @@ -5,145 +5,88 @@ --color-text-primary: var(--color-grey-950); --color-text-secondary: var(--color-grey-600); - --color-text-tertiary: var(--color-grey-500); - --color-text-on-inverted: var(--color-white); + --color-text-tertiary: var(--color-grey-500); // legacy + --color-text-inverted: var(--color-white); --color-text-brand: var(--color-indigo-700); --color-text-brand-soft: color-mix( in oklab, var(--color-text-primary), var(--color-text-brand) - ); + ); // legacy + --color-text-on-brand-base: var(--color-white); - --color-text-brand-on-inverted: var(--color-indigo-400); + --color-text-brand-on-inverted: var(--color-indigo-400); // legacy --color-text-error: var(--color-red-800); --color-text-on-error-base: var(--color-white); - --color-text-warning: var(--color-yellow-600); + --color-text-warning: var(--color-yellow-600); // legacy --color-text-on-warning-base: var(--color-white); - --color-text-success: var(--color-green-600); + --color-text-success: var(--color-green-600); // legacy --color-text-on-success-base: var(--color-white); - --color-text-disabled: var(--color-grey-300); - --color-text-on-disabled: var(--color-grey-200); - --color-text-bookmark-highlight: var(--color-text-error); - --color-text-favourite-highlight: var(--color-text-warning); - --color-text-on-media: var(--color-white); - --color-text-status-links: var(--color-text-brand); + --color-text-disabled: var(--color-grey-300); // legacy + --color-text-on-disabled: var(--color-grey-200); // legacy + --color-text-bookmark-highlight: var(--color-text-error); // legacy + --color-text-favourite-highlight: var(--color-text-warning); // legacy + --color-text-on-media: var(--color-white); // legacy + --color-text-status-links: var(--color-text-brand); // legacy /* BACKGROUND TOKENS */ // Neutrals --color-bg-primary: var(--color-white); --overlay-strength-secondary: 4%; - --color-bg-secondary-base: #000550; - --color-bg-secondary: #{color-mix( - in oklab, - var(--color-bg-primary), - var(--color-bg-secondary-base) var(--overlay-strength-secondary) - )}; - --color-bg-secondary-solid: #{color-mix( - in srgb, - var(--color-bg-primary), - var(--color-bg-secondary-base) var(--overlay-strength-secondary) - )}; - --color-bg-tertiary: #{color-mix( - in oklab, - var(--color-bg-primary), - var(--color-bg-secondary-base) calc(2 * var(--overlay-strength-secondary)) - )}; + --color-bg-secondary: var(--color-grey-50); + --color-bg-tertiary: var(--color-grey-100); // legacy // Utility - --color-bg-ambient: var(--color-bg-primary); --color-bg-inverted: var(--color-grey-950); - --color-bg-media-base: var(--color-black); - --color-bg-media-strength: 65%; - --color-bg-media: #{utils.css-alpha( - var(--color-bg-media-base), - var(--color-bg-media-strength) - )}; - --color-bg-overlay: var(--color-bg-primary); - --color-bg-disabled: var(--color-grey-400); + --color-bg-overlay-base: #{utils.css-alpha(var(--color-grey-950), 60%)}; + --color-bg-overlay-highlight: #{utils.css-alpha(var(--color-grey-950), 5%)}; + --color-bg-overlay: var(--color-bg-primary); // legacy + --color-bg-media-base: var(--color-black); // legacy + --color-bg-media: #{utils.css-alpha(var(--color-bg-media-base), 65%)}; // legacy + --color-bg-disabled: var(--color-grey-400); // legacy // Brand - --overlay-strength-brand: 6%; --color-bg-brand-base: var(--color-indigo-700); - --color-bg-brand-base-hover: color-mix( - in oklab, - var(--color-bg-brand-base), - black var(--overlay-strength-brand) - ); - --color-bg-brand-soft: #{utils.css-alpha( - #0012d8, - calc(var(--overlay-strength-brand) * 2) - )}; - --color-bg-brand-softer: #{utils.css-alpha( - #0012d8, - var(--overlay-strength-brand) - )}; - --color-bg-brand-softer-solid: color-mix( - in srgb, - var(--color-bg-primary), - var(--color-bg-brand-base) var(--overlay-strength-brand) - ); + --color-bg-brand-base-hover: var(--color-indigo-800); // legacy + --color-bg-brand-soft: var(--color-indigo-100); + --color-bg-brand-softest: var(--color-indigo-50); // Error - --overlay-strength-error: 5%; - --color-bg-error-base: var(--color-red-800); - --color-bg-error-base-hover: color-mix( - in oklab, - var(--color-bg-error-base), - black var(--overlay-strength-error) - ); + --color-bg-error-base: var(--color-red-700); + --color-bg-error-base-hover: var(--color-red-800); // legacy --color-bg-error-soft: var(--color-red-100); - --color-bg-error-softer: var(--color-red-50); + --color-bg-error-softest: var(--color-red-50); // Warning - --overlay-strength-warning: 10%; - --color-bg-warning-base: var(--color-yellow-700); - --color-bg-warning-base-hover: color-mix( - in oklab, - var(--color-bg-warning-base), - black var(--overlay-strength-warning) - ); + --color-bg-warning-base: var(--color-yellow-700); // legacy --color-bg-warning-soft: var(--color-yellow-100); - --color-bg-warning-softer: var(--color-yellow-50); + --color-bg-warning-softest: var(--color-yellow-50); // Success - --overlay-strength-success: 15%; - --color-bg-success-base: var(--color-green-600); - --color-bg-success-base-hover: color-mix( - in oklab, - var(--color-bg-success-base), - black var(--overlay-strength-success) - ); + --color-bg-success-base: var(--color-green-600); // legacy --color-bg-success-soft: var(--color-green-100); - --color-bg-success-softer: var(--color-green-50); + --color-bg-success-softest: var(--color-green-50); /* BORDER TOKENS */ - --border-strength-primary: 15%; - --color-border-primary: color-mix( - in oklab, - var(--color-bg-primary), - var(--color-grey-950) var(--border-strength-primary) - ); - --color-border-media: rgb(252 248 255 / 15%); - --color-border-verified: rgb(220, 3, 240); - --color-border-on-bg-secondary: var(--color-grey-200); - --color-border-on-bg-brand-softer: var(--color-indigo-200); - --color-border-on-bg-error-softer: #{utils.css-alpha( - var(--color-text-error), - 50% - )}; - --color-border-on-bg-warning-softer: #{utils.css-alpha( + --color-border-primary: var(--color-grey-200); + --color-border-brand: var(--color-text-brand); + --color-border-brand-soft: var(--color-indigo-200); + --color-border-error: var(--color-red-700); + --color-border-media: rgb(252 248 255 / 15%); // legacy + --color-border-error-soft: #{utils.css-alpha(var(--color-text-error), 50%)}; // legacy + --color-border-warning-soft: #{utils.css-alpha( var(--color-text-warning), 50% - )}; - --color-border-on-bg-success-softer: #{utils.css-alpha( + )}; // legacy + + --color-border-success-soft: #{utils.css-alpha( var(--color-text-success), 50% - )}; - --color-border-on-bg-inverted: var(--color-border-primary); + )}; // legacy - /* SHADOW TOKENS */ + /* SHADOW TOKENS (LEGACY) */ --shadow-strength-primary: 30%; --color-shadow-primary: #{utils.css-alpha( @@ -155,16 +98,16 @@ 0 8px 10px -6px var(--color-shadow-primary); --overlay-icon-shadow: drop-shadow(0 0 8px var(--color-shadow-primary)); - /* GRAPHS/CHARTS TOKENS */ + /* GRAPHS/CHARTS TOKENS (LEGACY) */ --color-graph-primary-stroke: var(--color-text-brand); - --color-graph-primary-fill: var(--color-bg-brand-softer); + --color-graph-primary-fill: var(--color-bg-brand-softest); --color-graph-warning-stroke: var(--color-text-warning); - --color-graph-warning-fill: var(--color-bg-warning-softer); + --color-graph-warning-fill: var(--color-bg-warning-softest); --color-graph-disabled-stroke: var(--color-text-disabled); --color-graph-disabled-fill: var(--color-bg-disabled); - /* LEGACY TOKENS */ + /* RICH TEXT TOKENS (LEGACY) */ --rich-text-container-color: rgb(255 216 231 / 100%); --rich-text-text-color: rgb(114 47 83 / 100%); @@ -179,8 +122,9 @@ --color-text-tertiary: var(--color-grey-700); --color-text-brand: var(--color-indigo-600); - /* BORDER TOKENS */ + /* BACKGROUND TOKENS */ + --color-bg-error-base: var(--color-red-800); - --border-strength-primary: 30%; - --color-border-on-bg-secondary: var(--color-grey-300); + /* BORDER TOKENS */ + --color-border-primary: var(--color-grey-300); } diff --git a/app/javascript/flavours/glitch/styles/mastodon/widgets.scss b/app/javascript/flavours/glitch/styles/mastodon/widgets.scss index 69c79cd1e6..d237a184c9 100644 --- a/app/javascript/flavours/glitch/styles/mastodon/widgets.scss +++ b/app/javascript/flavours/glitch/styles/mastodon/widgets.scss @@ -69,7 +69,7 @@ } &.active .avatar-stack .account__avatar { - border-color: var(--color-text-brand); + border-color: var(--color-border-brand); } .trends__item__current { diff --git a/app/javascript/mastodon/components/admin/ReportReasonSelector.jsx b/app/javascript/mastodon/components/admin/ReportReasonSelector.jsx index 3c33688b0c..1a61ade578 100644 --- a/app/javascript/mastodon/components/admin/ReportReasonSelector.jsx +++ b/app/javascript/mastodon/components/admin/ReportReasonSelector.jsx @@ -1,12 +1,14 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { injectIntl, defineMessages } from 'react-intl'; +import { defineMessages } from 'react-intl'; import classNames from 'classnames'; import api from 'mastodon/api'; +import { injectIntl } from '../intl'; + const messages = defineMessages({ legal: { id: 'report.categories.legal', defaultMessage: 'Legal' }, other: { id: 'report.categories.other', defaultMessage: 'Other' }, diff --git a/app/javascript/mastodon/components/alt_text_badge/index.tsx b/app/javascript/mastodon/components/alt_text_badge/index.tsx index 6bb64254c6..ecfb29fd52 100644 --- a/app/javascript/mastodon/components/alt_text_badge/index.tsx +++ b/app/javascript/mastodon/components/alt_text_badge/index.tsx @@ -1,4 +1,4 @@ -import { useState, useCallback, useRef, useId, Fragment } from 'react'; +import { useState, useCallback, useRef, useId } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; @@ -84,7 +84,6 @@ export const AltTextBadge: React.FC<{ description: string }> = ({ diff --git a/app/javascript/mastodon/components/avatar.tsx b/app/javascript/mastodon/components/avatar.tsx index b086ef4225..ced733b5d7 100644 --- a/app/javascript/mastodon/components/avatar.tsx +++ b/app/javascript/mastodon/components/avatar.tsx @@ -95,7 +95,7 @@ export const Avatar: React.FC = ({ }; export const AvatarById: React.FC< - { accountId: string } & Omit + { accountId: string | undefined } & Omit > = ({ accountId, ...otherProps }) => { const account = useAccount(accountId); return ; diff --git a/app/javascript/mastodon/components/badge.tsx b/app/javascript/mastodon/components/badge.tsx index 07ecdfa46c..54c28bf7b7 100644 --- a/app/javascript/mastodon/components/badge.tsx +++ b/app/javascript/mastodon/components/badge.tsx @@ -31,7 +31,7 @@ export const Badge: FC = ({ data-account-role-id={roleId} > {icon} - {label} + {label} {domain && {domain}}
    ); diff --git a/app/javascript/mastodon/components/callout/styles.module.css b/app/javascript/mastodon/components/callout/styles.module.css index 14003ccf5d..9df3ea40d2 100644 --- a/app/javascript/mastodon/components/callout/styles.module.css +++ b/app/javascript/mastodon/components/callout/styles.module.css @@ -3,7 +3,7 @@ align-items: start; padding: 12px; gap: 8px; - background-color: var(--color-bg-brand-softer); + background-color: var(--color-bg-brand-softest); color: var(--color-text-primary); border-radius: 12px; } @@ -86,11 +86,11 @@ } .variantSubtle { - border: 1px solid var(--color-bg-brand-softer); + border: 1px solid var(--color-bg-brand-softest); background-color: var(--color-bg-primary); .icon { - background-color: var(--color-bg-brand-softer); + background-color: var(--color-bg-brand-softest); } } @@ -105,11 +105,11 @@ .variantInverted { background-color: var(--color-bg-inverted); - color: var(--color-text-on-inverted); + color: var(--color-text-inverted); } .variantSuccess { - background-color: var(--color-bg-success-softer); + background-color: var(--color-bg-success-softest); .icon { background-color: var(--color-bg-success-soft); @@ -117,7 +117,7 @@ } .variantWarning { - background-color: var(--color-bg-warning-softer); + background-color: var(--color-bg-warning-softest); .icon { background-color: var(--color-bg-warning-soft); @@ -125,7 +125,7 @@ } .variantError { - background-color: var(--color-bg-error-softer); + background-color: var(--color-bg-error-softest); .icon { background-color: var(--color-bg-error-soft); diff --git a/app/javascript/mastodon/components/display_name/default.tsx b/app/javascript/mastodon/components/display_name/default.tsx index 57ae24ab26..ec42c9ada9 100644 --- a/app/javascript/mastodon/components/display_name/default.tsx +++ b/app/javascript/mastodon/components/display_name/default.tsx @@ -6,10 +6,11 @@ import { Skeleton } from '../skeleton'; import type { DisplayNameProps } from './index'; import { DisplayNameWithoutDomain } from './no-domain'; -export const DisplayNameDefault: FC< - Omit & ComponentPropsWithoutRef<'span'> -> = ({ account, localDomain, className, ...props }) => { - const username = useMemo(() => { +export function useAccountHandle( + account: DisplayNameProps['account'], + localDomain: DisplayNameProps['localDomain'], +) { + return useMemo(() => { if (!account) { return null; } @@ -20,6 +21,12 @@ export const DisplayNameDefault: FC< } return `@${acct}`; }, [account, localDomain]); +} + +export const DisplayNameDefault: FC< + Omit & ComponentPropsWithoutRef<'span'> +> = ({ account, localDomain, className, ...props }) => { + const username = useAccountHandle(account, localDomain); return ( > { + component: ComponentClass; + props: TProps; +} + +export const IntlHoc = >({ + component: Component, + props, +}: IntlHocProps) => { + const intl = useIntl(); + return ; +}; + +export const injectIntl = >( + Component: ComponentClass, +) => { + const WrappedComponent = (props: Omit) => ( + + ); + WrappedComponent.displayName = `injectIntl(${(Component.displayName ?? Component.name) || 'Component'})`; + return WrappedComponent; +}; diff --git a/app/javascript/mastodon/components/more_from_author.tsx b/app/javascript/mastodon/components/more_from_author.tsx index 5075a29d3d..f855a515d2 100644 --- a/app/javascript/mastodon/components/more_from_author.tsx +++ b/app/javascript/mastodon/components/more_from_author.tsx @@ -6,16 +6,12 @@ import { AuthorLink } from 'mastodon/features/explore/components/author_link'; export const MoreFromAuthor: React.FC<{ accountId: string }> = ({ accountId, }) => ( - }} - > - {(chunks) => ( -
    - - {chunks} -
    - )} -
    +
    + + }} + /> +
    ); diff --git a/app/javascript/mastodon/components/not_signed_in_indicator.tsx b/app/javascript/mastodon/components/not_signed_in_indicator.tsx index 015f74dcae..c2e95093b7 100644 --- a/app/javascript/mastodon/components/not_signed_in_indicator.tsx +++ b/app/javascript/mastodon/components/not_signed_in_indicator.tsx @@ -6,6 +6,7 @@ export const NotSignedInIndicator: React.FC = () => ( diff --git a/app/javascript/mastodon/components/regeneration_indicator.tsx b/app/javascript/mastodon/components/regeneration_indicator.tsx index e26b93eb4f..a79556104e 100644 --- a/app/javascript/mastodon/components/regeneration_indicator.tsx +++ b/app/javascript/mastodon/components/regeneration_indicator.tsx @@ -20,6 +20,7 @@ export const RegenerationIndicator: React.FC = () => ( diff --git a/app/javascript/mastodon/components/scrollable_list/components.tsx b/app/javascript/mastodon/components/scrollable_list/components.tsx index 79afaf837a..9e90ce807e 100644 --- a/app/javascript/mastodon/components/scrollable_list/components.tsx +++ b/app/javascript/mastodon/components/scrollable_list/components.tsx @@ -37,7 +37,11 @@ export const ItemList = forwardRef< } >(({ isLoading, emptyMessage, className, children, ...otherProps }, ref) => { if (!isLoading && Children.count(children) === 0 && emptyMessage) { - return
    {emptyMessage}
    ; + return ( +
    + {emptyMessage} +
    + ); } return ( diff --git a/app/javascript/mastodon/components/scrollable_list/index.jsx b/app/javascript/mastodon/components/scrollable_list/index.jsx index 02cbb056f7..a80fe5581a 100644 --- a/app/javascript/mastodon/components/scrollable_list/index.jsx +++ b/app/javascript/mastodon/components/scrollable_list/index.jsx @@ -385,7 +385,7 @@ class ScrollableList extends PureComponent { {alwaysPrepend && prepend}
    - {emptyMessage} + {emptyMessage}
    {footer} diff --git a/app/javascript/mastodon/components/server_banner.jsx b/app/javascript/mastodon/components/server_banner.jsx index 989ac7f006..7ef2296813 100644 --- a/app/javascript/mastodon/components/server_banner.jsx +++ b/app/javascript/mastodon/components/server_banner.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; +import { FormattedMessage, defineMessages } from 'react-intl'; import { Link } from 'react-router-dom'; @@ -14,6 +14,8 @@ import { ShortNumber } from 'mastodon/components/short_number'; import { Skeleton } from 'mastodon/components/skeleton'; import { domain } from 'mastodon/initial_state'; +import { injectIntl } from './intl'; + const messages = defineMessages({ aboutActiveUsers: { id: 'server_banner.about_active_users', defaultMessage: 'People using this server during the last 30 days (Monthly Active Users)' }, }); diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx index fd2054b066..1df2d0965e 100644 --- a/app/javascript/mastodon/components/status.jsx +++ b/app/javascript/mastodon/components/status.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { injectIntl, defineMessages, FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import classNames from 'classnames'; @@ -24,6 +24,7 @@ import { MediaGallery, Video, Audio } from '../features/ui/util/async-components import { SensitiveMediaContext } from '../features/ui/util/sensitive_media_context'; import { displayMedia } from '../initial_state'; +import { injectIntl } from './intl'; import { StatusHeader } from './status/header' import { LinkedDisplayName } from './display_name'; import { getHashtagBarForStatus } from './hashtag_bar'; @@ -434,7 +435,7 @@ class Status extends ImmutablePureComponent { prepend = (
    - +
    ); @@ -446,7 +447,7 @@ class Status extends ImmutablePureComponent { prepend = (
    - +
    ); } else if (showThread && status.get('in_reply_to_id')) { diff --git a/app/javascript/mastodon/components/status_action_bar/index.jsx b/app/javascript/mastodon/components/status_action_bar/index.jsx index 2e0440d012..1ad8b2002f 100644 --- a/app/javascript/mastodon/components/status_action_bar/index.jsx +++ b/app/javascript/mastodon/components/status_action_bar/index.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages } from 'react-intl'; import { withRouter } from 'react-router-dom'; @@ -23,10 +23,12 @@ import { Dropdown } from 'mastodon/components/dropdown_menu'; import { me, quickBoosting } from '../../initial_state'; import { IconButton } from '../icon_button'; +import { injectIntl } from '../intl'; import { BoostButton } from '../status/boost_button'; import { RemoveQuoteHint } from './remove_quote_hint'; import { quoteItemState, selectStatusState } from '../status/boost_button_utils'; + const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, redraft: { id: 'status.redraft', defaultMessage: 'Delete & re-draft' }, diff --git a/app/javascript/mastodon/components/status_content.jsx b/app/javascript/mastodon/components/status_content.jsx index facf7ae227..dbbac83314 100644 --- a/app/javascript/mastodon/components/status_content.jsx +++ b/app/javascript/mastodon/components/status_content.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { FormattedMessage, injectIntl } from 'react-intl'; +import { FormattedMessage } from 'react-intl'; import classnames from 'classnames'; import { withRouter } from 'react-router-dom'; @@ -16,6 +16,7 @@ import { identityContextPropShape, withIdentity } from 'mastodon/identity_contex import { languages as preloadedLanguages } from 'mastodon/initial_state'; import { EmojiHTML } from './emoji/html'; +import { injectIntl } from './intl'; import { HandledLink } from './status/handled_link'; const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top) diff --git a/app/javascript/mastodon/components/status_quoted.tsx b/app/javascript/mastodon/components/status_quoted.tsx index 1fffe26c08..5c9804fb40 100644 --- a/app/javascript/mastodon/components/status_quoted.tsx +++ b/app/javascript/mastodon/components/status_quoted.tsx @@ -335,7 +335,6 @@ export const QuotedStatus: React.FC = ({ return (
    - {/* @ts-expect-error Status is not yet typed */}
    - {label} + {label} ); }; diff --git a/app/javascript/mastodon/components/tags/style.module.css b/app/javascript/mastodon/components/tags/style.module.css index f3c507b644..dd14cc43df 100644 --- a/app/javascript/mastodon/components/tags/style.module.css +++ b/app/javascript/mastodon/components/tags/style.module.css @@ -22,8 +22,8 @@ button.tag:focus-visible { } .active { - border-color: var(--color-text-brand); - background: var(--color-bg-brand-softer); + border-color: var(--color-border-brand); + background: var(--color-bg-brand-softest); color: var(--color-text-brand); } diff --git a/app/javascript/mastodon/containers/status_container.jsx b/app/javascript/mastodon/containers/status_container.jsx index bf49bf3f55..6b0261c656 100644 --- a/app/javascript/mastodon/containers/status_container.jsx +++ b/app/javascript/mastodon/containers/status_container.jsx @@ -1,5 +1,3 @@ -import { injectIntl } from 'react-intl'; - import { connect } from 'react-redux'; import { @@ -43,6 +41,7 @@ import { undoStatusTranslation, } from '../actions/statuses'; import { setStatusQuotePolicy } from '../actions/statuses_typed'; +import { injectIntl } from '../components/intl'; import Status from '../components/status'; import { deleteModal } from '../initial_state'; import { makeGetStatus, makeGetPictureInPicture } from '../selectors'; diff --git a/app/javascript/mastodon/features/about/index.jsx b/app/javascript/mastodon/features/about/index.jsx index 27f03b17cb..a1db8889f2 100644 --- a/app/javascript/mastodon/features/about/index.jsx +++ b/app/javascript/mastodon/features/about/index.jsx @@ -1,13 +1,14 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import { Helmet } from 'react-helmet'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; +import { injectIntl } from '@/mastodon/components/intl'; import { fetchServer, fetchExtendedDescription, fetchDomainBlocks } from 'mastodon/actions/server'; import { Account } from 'mastodon/components/account'; import Column from 'mastodon/components/column'; diff --git a/app/javascript/mastodon/features/account_edit/modals/image_alt.tsx b/app/javascript/mastodon/features/account_edit/modals/image_alt.tsx index 973c74ff83..d9d014f792 100644 --- a/app/javascript/mastodon/features/account_edit/modals/image_alt.tsx +++ b/app/javascript/mastodon/features/account_edit/modals/image_alt.tsx @@ -3,6 +3,7 @@ import { useCallback, useState } from 'react'; import { FormattedMessage } from 'react-intl'; +import { CharacterCounter } from '@/mastodon/components/character_counter'; import { Details } from '@/mastodon/components/details'; import { TextAreaField } from '@/mastodon/components/form_fields'; import { LoadingIndicator } from '@/mastodon/components/loading_indicator'; @@ -84,7 +85,12 @@ export const ImageAltTextField: FC<{ const altLimit = useAppSelector( (state) => state.server.getIn( - ['server', 'configuration', 'media_attachments', 'description_limit'], + [ + 'server', + 'configuration', + 'accounts', + 'max_header_description_length', + ], 150, ) as number, ); @@ -100,23 +106,26 @@ export const ImageAltTextField: FC<{ <> - - } - hint={ - - } - onChange={handleChange} - value={altText} - maxLength={altLimit} - /> +
    + + } + hint={ + + } + onChange={handleChange} + value={altText} + maxLength={altLimit} + /> + +
    {!hideTip && (
      {chunks}
    , li: (chunks) =>
  • {chunks}
  • , diff --git a/app/javascript/mastodon/features/account_edit/modals/styles.module.scss b/app/javascript/mastodon/features/account_edit/modals/styles.module.scss index ebe36d412b..f4eaf04f09 100644 --- a/app/javascript/mastodon/features/account_edit/modals/styles.module.scss +++ b/app/javascript/mastodon/features/account_edit/modals/styles.module.scss @@ -58,7 +58,7 @@ transition: background 0.2s ease-in-out; &:hover { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); } &:focus-visible { diff --git a/app/javascript/mastodon/features/account_edit/styles.module.scss b/app/javascript/mastodon/features/account_edit/styles.module.scss index b2ab20ae3b..69abdb856b 100644 --- a/app/javascript/mastodon/features/account_edit/styles.module.scss +++ b/app/javascript/mastodon/features/account_edit/styles.module.scss @@ -160,7 +160,7 @@ &:active, &:focus, &:hover { - background-color: var(--color-bg-brand-softer); + background-color: var(--color-bg-brand-softest); } &:disabled { @@ -177,7 +177,7 @@ .deleteButton { --default-icon-color: var(--color-text-error); - --hover-bg-color: var(--color-bg-error-base-hover); + --hover-bg-color: var(--color-bg-error-base); --hover-icon-color: var(--color-text-on-error-base); } @@ -201,7 +201,7 @@ &, &:global(.active) { // Overrides the transparent background added by default with .active - --hover-bg-color: var(--color-bg-brand-softer-solid); + --hover-bg-color: var(--color-bg-brand-softest); } position: absolute; diff --git a/app/javascript/mastodon/features/account_featured/components/empty_message.tsx b/app/javascript/mastodon/features/account_featured/components/empty_message.tsx index 51450a85d8..5a60780a09 100644 --- a/app/javascript/mastodon/features/account_featured/components/empty_message.tsx +++ b/app/javascript/mastodon/features/account_featured/components/empty_message.tsx @@ -65,5 +65,9 @@ export const EmptyMessage: React.FC = ({ ); } - return
    {message}
    ; + return ( +
    + {message} +
    + ); }; diff --git a/app/javascript/mastodon/features/account_featured/index.tsx b/app/javascript/mastodon/features/account_featured/index.tsx index 5cec2250ef..0d9adfe314 100644 --- a/app/javascript/mastodon/features/account_featured/index.tsx +++ b/app/javascript/mastodon/features/account_featured/index.tsx @@ -154,6 +154,7 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({ key={item.id} collection={item} withoutBorder={index === listedCollections.length - 1} + withAuthorHandle={false} positionInList={index + 1} listSize={listedCollections.length} /> diff --git a/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss b/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss index 3b6a95d099..40fdc5cfa8 100644 --- a/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss +++ b/app/javascript/mastodon/features/account_timeline/components/redesign.module.scss @@ -9,6 +9,11 @@ .barWrapper { border-bottom: none; + padding-inline: 24px; + + @container (width < 500px) { + padding-inline: 16px; + } } .avatarWrapper { @@ -93,7 +98,7 @@ } svg { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); width: 28px; height: 28px; padding: 5px; @@ -125,7 +130,7 @@ $button-fallback-breakpoint: $button-breakpoint + 55px; position: sticky; bottom: var(--mobile-bottom-nav-height); padding: 12px 16px; - margin: 0 -20px; + margin: 0 -16px; @container (width >= #{$button-breakpoint}) { display: none; @@ -184,7 +189,7 @@ $button-fallback-breakpoint: $button-breakpoint + 55px; .badgeMuted { background-color: var(--color-bg-inverted); - color: var(--color-text-on-inverted); + color: var(--color-text-inverted); } .badgeBlocked { @@ -270,7 +275,7 @@ svg.badgeIcon { } .fieldVerified { - background-color: var(--color-bg-success-softer); + background-color: var(--color-bg-success-softest); dt { padding-right: 24px; @@ -292,8 +297,8 @@ svg.badgeIcon { } .fieldOverflowButton { - --default-bg-color: var(--color-bg-secondary-solid); - --hover-bg-color: var(--color-bg-brand-softer-solid); + --default-bg-color: var(--color-bg-secondary); + --hover-bg-color: var(--color-bg-brand-softest); position: absolute; right: 8px; @@ -388,7 +393,7 @@ svg.badgeIcon { padding: 0 24px; @container (width < 500px) { - padding: 0 12px; + padding: 0 16px; a { flex: 1 1 0px; @@ -413,7 +418,7 @@ svg.badgeIcon { :global(.active) { color: var(--color-text-brand); - border-bottom: 4px solid var(--color-text-brand); + border-bottom: 4px solid var(--color-border-brand); padding-bottom: 14px; } } diff --git a/app/javascript/mastodon/features/account_timeline/v2/tags_suggestions.tsx b/app/javascript/mastodon/features/account_timeline/v2/tags_suggestions.tsx index 93ac491f6c..e7f2d19cbe 100644 --- a/app/javascript/mastodon/features/account_timeline/v2/tags_suggestions.tsx +++ b/app/javascript/mastodon/features/account_timeline/v2/tags_suggestions.tsx @@ -78,6 +78,7 @@ export const TagSuggestions: FC = () => { values={{ link: (chunks) => {chunks}, }} + tagName='span' /> ); @@ -122,6 +123,7 @@ export const TagSuggestions: FC = () => { /> ), }} + tagName='span' /> ); diff --git a/app/javascript/mastodon/features/alt_text_modal/components/info_button.tsx b/app/javascript/mastodon/features/alt_text_modal/components/info_button.tsx index aecf9cbc2f..04c4ff8c56 100644 --- a/app/javascript/mastodon/features/alt_text_modal/components/info_button.tsx +++ b/app/javascript/mastodon/features/alt_text_modal/components/info_button.tsx @@ -59,6 +59,7 @@ export const InfoButton: React.FC = () => { > @@ -143,7 +141,6 @@ const SensitiveScreen: React.FC<{ @@ -205,7 +202,6 @@ export const CollectionAccountsList: React.FC<{ values={{ author: , }} - tagName={Fragment} />
    diff --git a/app/javascript/mastodon/features/collections/detail/collection_list_item.module.scss b/app/javascript/mastodon/features/collections/detail/collection_list_item.module.scss index 3c71e90f48..7cdf9b8541 100644 --- a/app/javascript/mastodon/features/collections/detail/collection_list_item.module.scss +++ b/app/javascript/mastodon/features/collections/detail/collection_list_item.module.scss @@ -1,8 +1,9 @@ .wrapper { display: flex; - align-items: center; + align-items: start; + margin-inline: 24px; + padding-block: 12px; gap: 16px; - padding-inline: 16px; &:not(.wrapperWithoutBorder) { border-bottom: 1px solid var(--color-border-primary); @@ -12,12 +13,42 @@ .content { position: relative; flex-grow: 1; - padding-block: 15px; + display: flex; + align-items: center; + column-gap: 12px; +} + +.avatarGrid { + position: relative; + display: grid; + grid-template-columns: repeat(2, min-content); + gap: 2px; + + &.avatarGridSensitive { + .avatar { + filter: blur(4px); + } + } +} + +.avatar { + background: var(--color-bg-brand-softest); +} + +.avatarSensitiveBadge { + position: absolute; + inset: 0; + margin: auto; + padding: 3px; + width: 18px; + height: 18px; + border-radius: 8px; + color: var(--color-text-primary); + background: var(--color-bg-warning-softest); } .link { display: block; - margin-bottom: 2px; font-size: 15px; font-weight: 500; text-decoration: none; @@ -40,15 +71,12 @@ color: var(--color-text-secondary); } -.metaList { - --gap: 0.75ch; +.menuButton { + padding: 4px; + margin-top: -2px; - display: flex; - flex-wrap: wrap; - gap: var(--gap); - - & > li:not(:last-child)::after { - content: '·'; - margin-inline-start: var(--gap); + svg { + width: 20px; + height: 20px; } } diff --git a/app/javascript/mastodon/features/collections/detail/collection_list_item.tsx b/app/javascript/mastodon/features/collections/detail/collection_list_item.tsx index 73584a9e7b..08c04f030a 100644 --- a/app/javascript/mastodon/features/collections/detail/collection_list_item.tsx +++ b/app/javascript/mastodon/features/collections/detail/collection_list_item.tsx @@ -5,70 +5,65 @@ import { FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import { Link } from 'react-router-dom'; +import WarningIcon from '@/material-icons/400-24px/warning.svg?react'; import type { ApiCollectionJSON } from 'mastodon/api_types/collections'; +import { AvatarById } from 'mastodon/components/avatar'; +import { useAccountHandle } from 'mastodon/components/display_name/default'; import { RelativeTimestamp } from 'mastodon/components/relative_timestamp'; import { Article } from 'mastodon/components/scrollable_list/components'; +import { useAccount } from 'mastodon/hooks/useAccount'; +import { domain } from 'mastodon/initial_state'; import classes from './collection_list_item.module.scss'; import { CollectionMenu } from './collection_menu'; -export const CollectionMetaData: React.FC<{ - collection: ApiCollectionJSON; - extended?: boolean; - className?: string; -}> = ({ collection, extended, className }) => { +export const AvatarGrid: React.FC<{ + accountIds: (string | undefined)[]; + sensitive?: boolean; +}> = ({ accountIds: ids, sensitive }) => { + const avatarIds = [ids[0], ids[1], ids[2], ids[3]]; return ( -
      - - {extended && ( - <> - {collection.discoverable ? ( - - ) : ( - - )} - {collection.sensitive && ( - - )} - +
      , - }} - tagName='li' - /> -
    + > + {avatarIds.map((id) => ( + + ))} + {sensitive && } + ); }; export const CollectionListItem: React.FC<{ collection: ApiCollectionJSON; withoutBorder?: boolean; + withAuthorHandle?: boolean; + withTimestamp?: boolean; positionInList: number; listSize: number; -}> = ({ collection, withoutBorder, positionInList, listSize }) => { +}> = ({ + collection, + withoutBorder, + withAuthorHandle = true, + withTimestamp, + positionInList, + listSize, +}) => { const { id, name } = collection; - const linkId = useId(); + const uniqueId = useId(); + const linkId = `${uniqueId}-link`; + const infoId = `${uniqueId}-info`; + const authorAccount = useAccount(collection.account_id); + const authorHandle = useAccountHandle(authorAccount, domain); return (
    -

    - - {name} - -

    - + item.account_id)} + sensitive={collection.sensitive} + /> +
    +

    + + {name} + +

    +
      + {collection.sensitive && ( +
    • + +
    • + )} + {withAuthorHandle && authorAccount && ( + + )} + + {withTimestamp && ( + + ), + }} + tagName='li' + /> + )} +
    +
    - +
    ); }; diff --git a/app/javascript/mastodon/features/collections/detail/index.tsx b/app/javascript/mastodon/features/collections/detail/index.tsx index 8db00e73d3..1e4248026c 100644 --- a/app/javascript/mastodon/features/collections/detail/index.tsx +++ b/app/javascript/mastodon/features/collections/detail/index.tsx @@ -6,6 +6,7 @@ import { Helmet } from 'react-helmet'; import { useHistory, useLocation, useParams } from 'react-router'; import { openModal } from '@/mastodon/actions/modal'; +import { RelativeTimestamp } from '@/mastodon/components/relative_timestamp'; import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react'; import ShareIcon from '@/material-icons/400-24px/share.svg?react'; import type { ApiCollectionJSON } from 'mastodon/api_types/collections'; @@ -25,7 +26,6 @@ import { fetchCollection } from 'mastodon/reducers/slices/collections'; import { useAppDispatch, useAppSelector } from 'mastodon/store'; import { CollectionAccountsList } from './accounts_list'; -import { CollectionMetaData } from './collection_list_item'; import { CollectionMenu } from './collection_menu'; import classes from './styles.module.scss'; @@ -40,6 +40,54 @@ const messages = defineMessages({ }, }); +const CollectionMetaData: React.FC<{ + collection: ApiCollectionJSON; + extended?: boolean; +}> = ({ collection, extended }) => { + return ( +
      + + {extended && ( + <> + {collection.discoverable ? ( + + ) : ( + + )} + {collection.sensitive && ( + + )} + + )} + , + }} + tagName='li' + /> +
    + ); +}; + export const AuthorNote: React.FC<{ id: string; previewMode?: boolean }> = ({ id, // When previewMode is enabled, your own display name @@ -137,7 +185,6 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({ ); diff --git a/app/javascript/mastodon/features/collections/detail/styles.module.scss b/app/javascript/mastodon/features/collections/detail/styles.module.scss index 786c0e7000..89bad584b6 100644 --- a/app/javascript/mastodon/features/collections/detail/styles.module.scss +++ b/app/javascript/mastodon/features/collections/detail/styles.module.scss @@ -52,9 +52,19 @@ font-size: 13px; } -.metaData { +.metaList { + --gap: 0.75ch; + + display: flex; + flex-wrap: wrap; margin-top: 16px; + gap: var(--gap); font-size: 15px; + + & > li:not(:last-child)::after { + content: '·'; + margin-inline-start: var(--gap); + } } .columnSubheading { diff --git a/app/javascript/mastodon/features/collections/editor/details.tsx b/app/javascript/mastodon/features/collections/editor/details.tsx index 9a7ea16be8..ce0019353d 100644 --- a/app/javascript/mastodon/features/collections/editor/details.tsx +++ b/app/javascript/mastodon/features/collections/editor/details.tsx @@ -1,4 +1,4 @@ -import { Fragment, useCallback, useMemo } from 'react'; +import { useCallback, useMemo } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; @@ -413,7 +413,6 @@ const LanguageField: React.FC = () => { {languages?.map(([code, name, localName]) => ( diff --git a/app/javascript/mastodon/features/collections/index.tsx b/app/javascript/mastodon/features/collections/index.tsx index 9d9b5d06d8..82653a22c1 100644 --- a/app/javascript/mastodon/features/collections/index.tsx +++ b/app/javascript/mastodon/features/collections/index.tsx @@ -47,6 +47,7 @@ export const Collections: React.FC<{ ) : ( <> @@ -92,6 +93,8 @@ export const Collections: React.FC<{ {collections.map((item, index) => ( = ({
    - {isProcessing ? ( - - ) : ( - - )} + + {isProcessing ? ( + + ) : ( + + )} +
    diff --git a/app/javascript/mastodon/features/compose/containers/spoiler_button_container.js b/app/javascript/mastodon/features/compose/containers/spoiler_button_container.js index 9acc43437b..b56f0dd8b9 100644 --- a/app/javascript/mastodon/features/compose/containers/spoiler_button_container.js +++ b/app/javascript/mastodon/features/compose/containers/spoiler_button_container.js @@ -1,8 +1,9 @@ -import { injectIntl, defineMessages } from 'react-intl'; +import { defineMessages } from 'react-intl'; import { connect } from 'react-redux'; import WarningIcon from '@/material-icons/400-20px/warning.svg?react'; +import { injectIntl } from '@/mastodon/components/intl'; import { IconButton } from 'mastodon/components/icon_button'; import { changeComposeSpoilerness } from '../../../actions/compose'; diff --git a/app/javascript/mastodon/features/explore/links.jsx b/app/javascript/mastodon/features/explore/links.jsx index 9858fc8932..8c2576f4c2 100644 --- a/app/javascript/mastodon/features/explore/links.jsx +++ b/app/javascript/mastodon/features/explore/links.jsx @@ -47,7 +47,7 @@ class Links extends PureComponent { return (
    - +
    ); diff --git a/app/javascript/mastodon/features/explore/suggestions.jsx b/app/javascript/mastodon/features/explore/suggestions.jsx index b469a15252..cca9935f9e 100644 --- a/app/javascript/mastodon/features/explore/suggestions.jsx +++ b/app/javascript/mastodon/features/explore/suggestions.jsx @@ -45,7 +45,7 @@ class Suggestions extends PureComponent { return (
    - +
    ); diff --git a/app/javascript/mastodon/features/explore/tags.jsx b/app/javascript/mastodon/features/explore/tags.jsx index 683f95bfb4..9afb5ad6fa 100644 --- a/app/javascript/mastodon/features/explore/tags.jsx +++ b/app/javascript/mastodon/features/explore/tags.jsx @@ -46,7 +46,7 @@ class Tags extends PureComponent { return (
    - +
    ); diff --git a/app/javascript/mastodon/features/favourites/index.jsx b/app/javascript/mastodon/features/favourites/index.jsx index 245954ef80..3296ec2eb9 100644 --- a/app/javascript/mastodon/features/favourites/index.jsx +++ b/app/javascript/mastodon/features/favourites/index.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import { Helmet } from 'react-helmet'; @@ -15,6 +15,7 @@ import { fetchFavourites, expandFavourites } from 'mastodon/actions/interactions import { Account } from 'mastodon/components/account'; import ColumnHeader from 'mastodon/components/column_header'; import { Icon } from 'mastodon/components/icon'; +import { injectIntl } from '@/mastodon/components/intl'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import ScrollableList from 'mastodon/components/scrollable_list'; import Column from 'mastodon/features/ui/components/column'; diff --git a/app/javascript/mastodon/features/filters/select_filter.jsx b/app/javascript/mastodon/features/filters/select_filter.jsx index 5b2eb64952..010527e932 100644 --- a/app/javascript/mastodon/features/filters/select_filter.jsx +++ b/app/javascript/mastodon/features/filters/select_filter.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; @@ -9,6 +9,7 @@ import fuzzysort from 'fuzzysort'; import AddIcon from '@/material-icons/400-24px/add.svg?react'; import { Icon } from 'mastodon/components/icon'; +import { injectIntl } from '@/mastodon/components/intl'; import { toServerSideType } from 'mastodon/utils/filters'; import { loupeIcon, deleteIcon } from 'mastodon/utils/icons'; diff --git a/app/javascript/mastodon/features/follow_requests/components/account_authorize.jsx b/app/javascript/mastodon/features/follow_requests/components/account_authorize.jsx index e865b606fe..6aff30bd47 100644 --- a/app/javascript/mastodon/features/follow_requests/components/account_authorize.jsx +++ b/app/javascript/mastodon/features/follow_requests/components/account_authorize.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages } from 'react-intl'; import { Link } from 'react-router-dom'; @@ -13,6 +13,7 @@ import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { Avatar } from '@/mastodon/components/avatar'; import { DisplayName } from '@/mastodon/components/display_name'; import { IconButton } from '@/mastodon/components/icon_button'; +import { injectIntl } from '@/mastodon/components/intl'; import { EmojiHTML } from '@/mastodon/components/emoji/html'; const messages = defineMessages({ diff --git a/app/javascript/mastodon/features/follow_requests/index.jsx b/app/javascript/mastodon/features/follow_requests/index.jsx index 91648412b5..ba632b8ea3 100644 --- a/app/javascript/mastodon/features/follow_requests/index.jsx +++ b/app/javascript/mastodon/features/follow_requests/index.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import { Helmet } from 'react-helmet'; @@ -11,6 +11,7 @@ import { connect } from 'react-redux'; import { debounce } from 'lodash'; import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react'; +import { injectIntl } from '@/mastodon/components/intl'; import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts'; import ScrollableList from '../../components/scrollable_list'; diff --git a/app/javascript/mastodon/features/followers/index.tsx b/app/javascript/mastodon/features/followers/index.tsx index bba2f4cb08..219462ef6f 100644 --- a/app/javascript/mastodon/features/followers/index.tsx +++ b/app/javascript/mastodon/features/followers/index.tsx @@ -63,6 +63,7 @@ const Followers: FC = () => {
    ); diff --git a/app/javascript/mastodon/features/following/index.tsx b/app/javascript/mastodon/features/following/index.tsx index 6bc7abda69..6f213fbe6b 100644 --- a/app/javascript/mastodon/features/following/index.tsx +++ b/app/javascript/mastodon/features/following/index.tsx @@ -65,6 +65,7 @@ const Followers: FC = () => {
    ); diff --git a/app/javascript/mastodon/features/hashtag_timeline/components/column_settings.jsx b/app/javascript/mastodon/features/hashtag_timeline/components/column_settings.jsx index 3412e5d1bd..9d1dde7f73 100644 --- a/app/javascript/mastodon/features/hashtag_timeline/components/column_settings.jsx +++ b/app/javascript/mastodon/features/hashtag_timeline/components/column_settings.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; @@ -9,6 +9,8 @@ import { NonceProvider } from 'react-select'; import AsyncSelect from 'react-select/async'; import Toggle from 'react-toggle'; +import { injectIntl } from '@/mastodon/components/intl'; + import SettingToggle from '../../notifications/components/setting_toggle'; const messages = defineMessages({ diff --git a/app/javascript/mastodon/features/home_timeline/components/inline_follow_suggestions.tsx b/app/javascript/mastodon/features/home_timeline/components/inline_follow_suggestions.tsx index 45b867ad9d..eb61183834 100644 --- a/app/javascript/mastodon/features/home_timeline/components/inline_follow_suggestions.tsx +++ b/app/javascript/mastodon/features/home_timeline/components/inline_follow_suggestions.tsx @@ -115,7 +115,7 @@ const Source: React.FC<{ id: ApiSuggestionSourceJSON }> = ({ id }) => { title={hint} > - {label} + {label} ); }; diff --git a/app/javascript/mastodon/features/home_timeline/index.jsx b/app/javascript/mastodon/features/home_timeline/index.jsx index 893e2c08ca..e135fe0cd9 100644 --- a/app/javascript/mastodon/features/home_timeline/index.jsx +++ b/app/javascript/mastodon/features/home_timeline/index.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import { Helmet } from 'react-helmet'; @@ -10,6 +10,7 @@ import { connect } from 'react-redux'; import CampaignIcon from '@/material-icons/400-24px/campaign.svg?react'; import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react'; +import { injectIntl } from '@/mastodon/components/intl'; import { SymbolLogo } from 'mastodon/components/logo'; import { fetchAnnouncements, toggleShowAnnouncements } from 'mastodon/actions/announcements'; import { IconWithBadge } from 'mastodon/components/icon_with_badge'; diff --git a/app/javascript/mastodon/features/keyboard_shortcuts/index.jsx b/app/javascript/mastodon/features/keyboard_shortcuts/index.jsx index d2b041ec3f..3322dc13a2 100644 --- a/app/javascript/mastodon/features/keyboard_shortcuts/index.jsx +++ b/app/javascript/mastodon/features/keyboard_shortcuts/index.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import { Helmet } from 'react-helmet'; @@ -9,6 +9,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import InfoIcon from '@/material-icons/400-24px/info.svg?react'; import Column from 'mastodon/components/column'; import ColumnHeader from 'mastodon/components/column_header'; +import { injectIntl } from '@/mastodon/components/intl'; const messages = defineMessages({ heading: { id: 'keyboard_shortcuts.heading', defaultMessage: 'Keyboard Shortcuts' }, diff --git a/app/javascript/mastodon/features/lists/members.tsx b/app/javascript/mastodon/features/lists/members.tsx index c8970b6d7a..5aa8279c05 100644 --- a/app/javascript/mastodon/features/lists/members.tsx +++ b/app/javascript/mastodon/features/lists/members.tsx @@ -285,6 +285,7 @@ const ListMembers: React.FC<{ ) } diff --git a/app/javascript/mastodon/features/mutes/index.jsx b/app/javascript/mastodon/features/mutes/index.jsx index 28c76a04e2..8dd230e4ad 100644 --- a/app/javascript/mastodon/features/mutes/index.jsx +++ b/app/javascript/mastodon/features/mutes/index.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import { Helmet } from 'react-helmet'; @@ -12,6 +12,7 @@ import { debounce } from 'lodash'; import VolumeOffIcon from '@/material-icons/400-24px/volume_off.svg?react'; import { Account } from 'mastodon/components/account'; +import { injectIntl } from '@/mastodon/components/intl'; import { fetchMutes, expandMutes } from '../../actions/mutes'; import { LoadingIndicator } from '../../components/loading_indicator'; diff --git a/app/javascript/mastodon/features/notifications/components/follow_request.jsx b/app/javascript/mastodon/features/notifications/components/follow_request.jsx index 4024455cbd..317768ccf8 100644 --- a/app/javascript/mastodon/features/notifications/components/follow_request.jsx +++ b/app/javascript/mastodon/features/notifications/components/follow_request.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages } from 'react-intl'; import { Link } from 'react-router-dom'; @@ -12,6 +12,7 @@ import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { Avatar } from 'mastodon/components/avatar'; import { DisplayName } from 'mastodon/components/display_name'; import { IconButton } from 'mastodon/components/icon_button'; +import { injectIntl } from '@/mastodon/components/intl'; const messages = defineMessages({ authorize: { id: 'follow_request.authorize', defaultMessage: 'Authorize' }, diff --git a/app/javascript/mastodon/features/notifications/components/notification.jsx b/app/javascript/mastodon/features/notifications/components/notification.jsx index 4fbae1e047..005a37a4d8 100644 --- a/app/javascript/mastodon/features/notifications/components/notification.jsx +++ b/app/javascript/mastodon/features/notifications/components/notification.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { injectIntl, FormattedMessage, defineMessages } from 'react-intl'; +import { FormattedMessage, defineMessages } from 'react-intl'; import classNames from 'classnames'; import { Link, withRouter } from 'react-router-dom'; @@ -20,6 +20,7 @@ import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; import { Account } from 'mastodon/components/account'; import { LinkedDisplayName } from '@/mastodon/components/display_name'; import { Icon } from 'mastodon/components/icon'; +import { injectIntl } from '@/mastodon/components/intl'; import { Hotkeys } from 'mastodon/components/hotkeys'; import { StatusQuoteManager } from 'mastodon/components/status_quoted'; import { me } from 'mastodon/initial_state'; diff --git a/app/javascript/mastodon/features/notifications/components/report.jsx b/app/javascript/mastodon/features/notifications/components/report.jsx index bc3631c86e..deb6589fdf 100644 --- a/app/javascript/mastodon/features/notifications/components/report.jsx +++ b/app/javascript/mastodon/features/notifications/components/report.jsx @@ -1,11 +1,12 @@ import PropTypes from 'prop-types'; -import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { AvatarOverlay } from 'mastodon/components/avatar_overlay'; +import { injectIntl } from '@/mastodon/components/intl'; import { RelativeTimestamp } from 'mastodon/components/relative_timestamp'; // This needs to be kept in sync with app/models/report.rb diff --git a/app/javascript/mastodon/features/notifications/containers/column_settings_container.js b/app/javascript/mastodon/features/notifications/containers/column_settings_container.js index eddd35df4a..6e9964fab1 100644 --- a/app/javascript/mastodon/features/notifications/containers/column_settings_container.js +++ b/app/javascript/mastodon/features/notifications/containers/column_settings_container.js @@ -1,9 +1,10 @@ -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages } from 'react-intl'; import { connect } from 'react-redux'; import { openModal } from 'mastodon/actions/modal'; import { fetchNotifications , setNotificationsFilter } from 'mastodon/actions/notification_groups'; +import { injectIntl } from '@/mastodon/components/intl'; import { showAlert } from '../../../actions/alerts'; import { requestBrowserPermission } from '../../../actions/notifications'; diff --git a/app/javascript/mastodon/features/notifications_v2/components/notification_group_with_status.tsx b/app/javascript/mastodon/features/notifications_v2/components/notification_group_with_status.tsx index 00746560da..8035493283 100644 --- a/app/javascript/mastodon/features/notifications_v2/components/notification_group_with_status.tsx +++ b/app/javascript/mastodon/features/notifications_v2/components/notification_group_with_status.tsx @@ -126,7 +126,7 @@ export const NotificationGroupWithStatus: React.FC<{
    - {label} + {label} {timestamp && ( <> diff --git a/app/javascript/mastodon/features/pinned_statuses/index.jsx b/app/javascript/mastodon/features/pinned_statuses/index.jsx index 786cbeee94..438921a4e3 100644 --- a/app/javascript/mastodon/features/pinned_statuses/index.jsx +++ b/app/javascript/mastodon/features/pinned_statuses/index.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages } from 'react-intl'; import { Helmet } from 'react-helmet'; @@ -9,6 +9,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; import PushPinIcon from '@/material-icons/400-24px/push_pin.svg?react'; +import { injectIntl } from '@/mastodon/components/intl'; import { getStatusList } from 'mastodon/selectors'; import { fetchPinnedStatuses } from '../../actions/pin_statuses'; diff --git a/app/javascript/mastodon/features/public_timeline/components/column_settings.jsx b/app/javascript/mastodon/features/public_timeline/components/column_settings.jsx index c865f1bb02..a9814f083a 100644 --- a/app/javascript/mastodon/features/public_timeline/components/column_settings.jsx +++ b/app/javascript/mastodon/features/public_timeline/components/column_settings.jsx @@ -1,10 +1,12 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { injectIntl, FormattedMessage } from 'react-intl'; +import { FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import { injectIntl } from '@/mastodon/components/intl'; + import SettingToggle from '../../notifications/components/setting_toggle'; class ColumnSettings extends PureComponent { diff --git a/app/javascript/mastodon/features/public_timeline/index.jsx b/app/javascript/mastodon/features/public_timeline/index.jsx index cf86a8a6df..e11b361545 100644 --- a/app/javascript/mastodon/features/public_timeline/index.jsx +++ b/app/javascript/mastodon/features/public_timeline/index.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import { Helmet } from 'react-helmet'; @@ -9,6 +9,7 @@ import { connect } from 'react-redux'; import PublicIcon from '@/material-icons/400-24px/public.svg?react'; import { DismissableBanner } from 'mastodon/components/dismissable_banner'; +import { injectIntl } from '@/mastodon/components/intl'; import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; import { domain, localLiveFeedAccess, remoteLiveFeedAccess } from 'mastodon/initial_state'; import { canViewFeed } from 'mastodon/permissions'; diff --git a/app/javascript/mastodon/features/reblogs/index.jsx b/app/javascript/mastodon/features/reblogs/index.jsx index 24786b62f0..b0ee5029f5 100644 --- a/app/javascript/mastodon/features/reblogs/index.jsx +++ b/app/javascript/mastodon/features/reblogs/index.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import { Helmet } from 'react-helmet'; @@ -13,6 +13,7 @@ import { debounce } from 'lodash'; import RefreshIcon from '@/material-icons/400-24px/refresh.svg?react'; import { Account } from 'mastodon/components/account'; import { Icon } from 'mastodon/components/icon'; +import { injectIntl } from '@/mastodon/components/intl'; import { fetchReblogs, expandReblogs } from '../../actions/interactions'; import ColumnHeader from '../../components/column_header'; diff --git a/app/javascript/mastodon/features/report/category.jsx b/app/javascript/mastodon/features/report/category.jsx index a2fc3b23b4..7a37426a65 100644 --- a/app/javascript/mastodon/features/report/category.jsx +++ b/app/javascript/mastodon/features/report/category.jsx @@ -1,13 +1,14 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import { List as ImmutableList } from 'immutable'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; import { Button } from 'mastodon/components/button'; +import { injectIntl } from '@/mastodon/components/intl'; import Option from './components/option'; diff --git a/app/javascript/mastodon/features/report/comment.tsx b/app/javascript/mastodon/features/report/comment.tsx index 8c2728a944..f65d38470e 100644 --- a/app/javascript/mastodon/features/report/comment.tsx +++ b/app/javascript/mastodon/features/report/comment.tsx @@ -194,6 +194,7 @@ const Comment: React.FC = ({ id='report.forward' defaultMessage='Forward to {target}' values={{ target: domain }} + tagName='span' /> ))} diff --git a/app/javascript/mastodon/features/status/components/action_bar.jsx b/app/javascript/mastodon/features/status/components/action_bar.jsx index b51776da19..40f5213823 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.jsx +++ b/app/javascript/mastodon/features/status/components/action_bar.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; @@ -13,6 +13,7 @@ import ReplyIcon from '@/material-icons/400-24px/reply.svg?react'; import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react'; import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; import StarBorderIcon from '@/material-icons/400-24px/star.svg?react'; +import { injectIntl } from '@/mastodon/components/intl'; import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions'; diff --git a/app/javascript/mastodon/features/status/components/detailed_status.tsx b/app/javascript/mastodon/features/status/components/detailed_status.tsx index 1dee2e5147..b85841098a 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.tsx +++ b/app/javascript/mastodon/features/status/components/detailed_status.tsx @@ -406,6 +406,7 @@ export const DetailedStatus: React.FC<{
    )} diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx index f4f137b2a1..cc9bf693ea 100644 --- a/app/javascript/mastodon/features/status/index.jsx +++ b/app/javascript/mastodon/features/status/index.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages } from 'react-intl'; import classNames from 'classnames'; import { Helmet } from 'react-helmet'; @@ -15,6 +15,7 @@ import VisibilityIcon from '@/material-icons/400-24px/visibility.svg?react'; import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react'; import { Hotkeys } from 'mastodon/components/hotkeys'; import { Icon } from 'mastodon/components/icon'; +import { injectIntl } from '@/mastodon/components/intl'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import { ScrollContainer } from 'mastodon/containers/scroll_container'; import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error'; diff --git a/app/javascript/mastodon/features/subscribed_languages_modal/index.jsx b/app/javascript/mastodon/features/subscribed_languages_modal/index.jsx index 895a2686e8..8d5665b1c4 100644 --- a/app/javascript/mastodon/features/subscribed_languages_modal/index.jsx +++ b/app/javascript/mastodon/features/subscribed_languages_modal/index.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import { createSelector } from '@reduxjs/toolkit'; import { is, List as ImmutableList, Set as ImmutableSet } from 'immutable'; @@ -12,6 +12,7 @@ import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { followAccount } from 'mastodon/actions/accounts'; import { Button } from 'mastodon/components/button'; import { IconButton } from 'mastodon/components/icon_button'; +import { injectIntl } from '@/mastodon/components/intl'; import Option from 'mastodon/features/report/components/option'; import { languages as preloadedLanguages } from 'mastodon/initial_state'; diff --git a/app/javascript/mastodon/features/ui/components/bundle_column_error.jsx b/app/javascript/mastodon/features/ui/components/bundle_column_error.jsx index 2461028d8b..da98c5632f 100644 --- a/app/javascript/mastodon/features/ui/components/bundle_column_error.jsx +++ b/app/javascript/mastodon/features/ui/components/bundle_column_error.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { injectIntl, FormattedMessage } from 'react-intl'; +import { FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import { Helmet } from 'react-helmet'; @@ -9,6 +9,7 @@ import { Link } from 'react-router-dom'; import { Button } from 'mastodon/components/button'; import Column from 'mastodon/components/column'; +import { injectIntl } from '@/mastodon/components/intl'; import { GIF } from 'mastodon/components/gif'; class CopyButton extends PureComponent { diff --git a/app/javascript/mastodon/features/ui/components/filter_modal.jsx b/app/javascript/mastodon/features/ui/components/filter_modal.jsx index a1a39ba0ab..c0a2bf6d53 100644 --- a/app/javascript/mastodon/features/ui/components/filter_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/filter_modal.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; @@ -9,6 +9,7 @@ import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import { fetchFilters, createFilter, createFilterStatus } from 'mastodon/actions/filters'; import { fetchStatus } from 'mastodon/actions/statuses'; import { IconButton } from 'mastodon/components/icon_button'; +import { injectIntl } from '@/mastodon/components/intl'; import AddedToFilter from 'mastodon/features/filters/added_to_filter'; import SelectFilter from 'mastodon/features/filters/select_filter'; diff --git a/app/javascript/mastodon/features/ui/components/report_modal.jsx b/app/javascript/mastodon/features/ui/components/report_modal.jsx index 6584364609..2ffac942be 100644 --- a/app/javascript/mastodon/features/ui/components/report_modal.jsx +++ b/app/javascript/mastodon/features/ui/components/report_modal.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; +import { defineMessages, FormattedMessage } from 'react-intl'; import { OrderedSet } from 'immutable'; import ImmutablePropTypes from 'react-immutable-proptypes'; @@ -12,6 +12,7 @@ import { submitReport } from 'mastodon/actions/reports'; import { fetchServer } from 'mastodon/actions/server'; import { expandAccountTimeline } from 'mastodon/actions/timelines'; import { IconButton } from 'mastodon/components/icon_button'; +import { injectIntl } from '@/mastodon/components/intl'; import Category from 'mastodon/features/report/category'; import Comment from 'mastodon/features/report/comment'; import Rules from 'mastodon/features/report/rules'; diff --git a/app/javascript/mastodon/features/ui/components/skip_links/skip_links.module.scss b/app/javascript/mastodon/features/ui/components/skip_links/skip_links.module.scss index 1d4dc1c3f5..ad206dfecc 100644 --- a/app/javascript/mastodon/features/ui/components/skip_links/skip_links.module.scss +++ b/app/javascript/mastodon/features/ui/components/skip_links/skip_links.module.scss @@ -33,7 +33,7 @@ &:focus-within { outline: var(--outline-focus-default); - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); } :any-link { diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx index 7540d64b4e..ffcbbccd68 100644 --- a/app/javascript/mastodon/features/ui/index.jsx +++ b/app/javascript/mastodon/features/ui/index.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -import { defineMessages, injectIntl } from 'react-intl'; +import { defineMessages } from 'react-intl'; import classNames from 'classnames'; import { Redirect, Route, withRouter } from 'react-router-dom'; @@ -16,6 +16,7 @@ import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodo import { fetchNotifications } from 'mastodon/actions/notification_groups'; import { INTRODUCTION_VERSION } from 'mastodon/actions/onboarding'; import { AlertsController } from 'mastodon/components/alerts_controller'; +import { injectIntl } from '@/mastodon/components/intl'; import { Hotkeys } from 'mastodon/components/hotkeys'; import { HoverCardController } from 'mastodon/components/hover_card_controller'; import { PictureInPicture } from 'mastodon/features/picture_in_picture'; diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json index 888834b3b7..4d00d75d8c 100644 --- a/app/javascript/mastodon/locales/be.json +++ b/app/javascript/mastodon/locales/be.json @@ -141,8 +141,8 @@ "account.unmute": "Не ігнараваць @{name}", "account.unmute_notifications_short": "Апавяшчаць", "account.unmute_short": "Не ігнараваць", + "account_edit.bio.add_label": "Апісаць сябе", "account_edit.bio.edit_label": "Змяніць апісанне", - "account_edit.bio.label": "хто я", "account_edit.bio.placeholder": "Коратка апішыце сябе, каб дапамагчы іншым пазнаць Вас.", "account_edit.bio.title": "Хто я", "account_edit.bio_modal.add_title": "Апісаць сябе", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index 512f9cbdf9..855bfa64b8 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -141,8 +141,8 @@ "account.unmute": "Vis @{name} igen", "account.unmute_notifications_short": "Vis notifikationer igen", "account.unmute_short": "Vis igen", + "account_edit.bio.add_label": "Tilføj bio", "account_edit.bio.edit_label": "Rediger bio", - "account_edit.bio.label": "bio", "account_edit.bio.placeholder": "Tilføj en kort introduktion, så andre kan få et indtryk af, hvem du er.", "account_edit.bio.title": "Bio", "account_edit.bio_modal.add_title": "Tilføj bio", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index 0346c0b017..8c83dfc1e3 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -141,8 +141,8 @@ "account.unmute": "Stummschaltung von @{name} aufheben", "account.unmute_notifications_short": "Stummschaltung der Benachrichtigungen aufheben", "account.unmute_short": "Stummschaltung aufheben", + "account_edit.bio.add_label": "Biografie hinzufügen", "account_edit.bio.edit_label": "Biografie bearbeiten", - "account_edit.bio.label": "Biografie", "account_edit.bio.placeholder": "Gib anderen einen Einblick über dich, damit sie wissen, wer du bist.", "account_edit.bio.title": "Über mich", "account_edit.bio_modal.add_title": "Biografie hinzufügen", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index a4e9077801..783cc969e8 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -141,8 +141,8 @@ "account.unmute": "Άρση σίγασης @{name}", "account.unmute_notifications_short": "Σίγαση ειδοποιήσεων", "account.unmute_short": "Κατάργηση σίγασης", + "account_edit.bio.add_label": "Προσθήκη βιογραφικού", "account_edit.bio.edit_label": "Επεξεργασία βιογραφικού", - "account_edit.bio.label": "βιογραφικό", "account_edit.bio.placeholder": "Προσθέστε μια σύντομη εισαγωγή για να βοηθήσετε άλλους να σας αναγνωρίσουν.", "account_edit.bio.title": "Βιογραφικό", "account_edit.bio_modal.add_title": "Προσθήκη βιογραφικού", diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index 47ea34cc4a..ad18733d34 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -359,6 +359,7 @@ "collections.account_count": "{count, plural, one {# account} other {# accounts}}", "collections.accounts.empty_description": "Add up to {count} accounts you follow", "collections.accounts.empty_title": "This collection is empty", + "collections.by_account": "by {account_handle}", "collections.collection_description": "Description", "collections.collection_language": "Language", "collections.collection_language_none": "None", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index 7372b4baea..c072c2d1d5 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -141,8 +141,8 @@ "account.unmute": "Dejar de silenciar a @{name}", "account.unmute_notifications_short": "Dejar de silenciar notificaciones", "account.unmute_short": "Dejar de silenciar", + "account_edit.bio.add_label": "Agregar biografía", "account_edit.bio.edit_label": "Editar biografía", - "account_edit.bio.label": "biografía", "account_edit.bio.placeholder": "Agregá una breve introducción para ayudar a otras personas a identificarte.", "account_edit.bio.title": "Biografía", "account_edit.bio_modal.add_title": "Agregar biografía", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index 9dd92e8585..91e12c1a6f 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -141,8 +141,8 @@ "account.unmute": "Dejar de silenciar a @{name}", "account.unmute_notifications_short": "Dejar de silenciar notificaciones", "account.unmute_short": "Dejar de silenciar", + "account_edit.bio.add_label": "Añadir biografía", "account_edit.bio.edit_label": "Editar biografía", - "account_edit.bio.label": "biografía", "account_edit.bio.placeholder": "Añade una breve introducción para ayudar a los demás a identificarte.", "account_edit.bio.title": "Biografía", "account_edit.bio_modal.add_title": "Añadir biografía", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 4d8f4caa28..1cbdf0bb77 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -141,27 +141,39 @@ "account.unmute": "Dejar de silenciar a @{name}", "account.unmute_notifications_short": "Dejar de silenciar notificaciones", "account.unmute_short": "Dejar de silenciar", + "account_edit.bio.add_label": "Añadir biografía", + "account_edit.bio.edit_label": "Editar biografía", "account_edit.bio.placeholder": "Añade una breve introducción para ayudar a los demás a identificarte.", "account_edit.bio.title": "Biografía", "account_edit.bio_modal.add_title": "Añadir biografía", "account_edit.bio_modal.edit_title": "Editar biografía", "account_edit.column_button": "Hecho", "account_edit.column_title": "Editar perfil", + "account_edit.custom_fields.add_label": "Añadir campo", + "account_edit.custom_fields.edit_label": "Editar campo", "account_edit.custom_fields.placeholder": "Añade tus pronombres, enlaces externos o cualquier otra cosa que quieras compartir.", "account_edit.custom_fields.reorder_button": "Reordenar campos", "account_edit.custom_fields.tip_content": "Puedes añadir credibilidad fácilmente a tu cuenta de Mastodon verificando los enlaces a tus propias webs.", "account_edit.custom_fields.tip_title": "Consejo: Añade enlaces verificados", "account_edit.custom_fields.title": "Campos personalizados", "account_edit.custom_fields.verified_hint": "¿Cómo añado un enlace verificado?", + "account_edit.display_name.add_label": "Añadir nombre para mostrar", + "account_edit.display_name.edit_label": "Editar nombre para mostrar", "account_edit.display_name.placeholder": "Tu nombre de usuario es el nombre que aparece en tu perfil y en las cronologías.", "account_edit.display_name.title": "Nombre para mostrar", + "account_edit.featured_hashtags.edit_label": "Añadir etiquetas", "account_edit.featured_hashtags.placeholder": "Ayuda a otros a identificar tus temas favoritos y a acceder rápidamente a ellos.", "account_edit.featured_hashtags.title": "Etiquetas destacadas", + "account_edit.field_actions.delete": "Eliminar campo", + "account_edit.field_actions.edit": "Editar campo", "account_edit.field_delete_modal.confirm": "¿Estás seguro de que quieres borrar este campo personalizado? La acción no se puede deshacer.", "account_edit.field_delete_modal.delete_button": "Borrar", "account_edit.field_delete_modal.title": "¿Borrar campo personalizado?", "account_edit.field_edit_modal.add_title": "Añadir campo personalizado", + "account_edit.field_edit_modal.discard_confirm": "Descartar", + "account_edit.field_edit_modal.discard_message": "Tiene cambios no guardados. ¿Seguro que quieres descartarlos?", "account_edit.field_edit_modal.edit_title": "Editar campo personalizado", + "account_edit.field_edit_modal.limit_warning": "Se ha superado el límite recomendado de caracteres. Es posible que los usuarios móviles no vean el campo completo.", "account_edit.field_edit_modal.link_emoji_warning": "Recomendamos no usar emojis personalizados combinados con enlaces. Los campos personalizados que contengan ambos solo se mostrarán como texto en vez de un enlace, para evitar confusiones.", "account_edit.field_edit_modal.name_hint": "Ej. \"Web personal\"", "account_edit.field_edit_modal.name_label": "Etiqueta", @@ -190,6 +202,8 @@ "account_edit.image_edit.alt_edit_button": "Editar texto alternativo", "account_edit.image_edit.remove_button": "Quitar imagen", "account_edit.image_edit.replace_button": "Sustituir imagen", + "account_edit.item_list.delete": "Eliminar {name}", + "account_edit.item_list.edit": "Editar {name}", "account_edit.name_modal.add_title": "Añadir nombre para mostrar", "account_edit.name_modal.edit_title": "Editar nombre para mostrar", "account_edit.profile_tab.button_label": "Personalizar", @@ -212,6 +226,10 @@ "account_edit.upload_modal.step_upload.dragging": "Suelta para subir", "account_edit.upload_modal.step_upload.header": "Elige una imagen", "account_edit.upload_modal.step_upload.hint": "Formato WEBP, PNG, GIF o JPG, hasta {limit}MB.{br}La imagen será escalada a {width}x{height}px.", + "account_edit.upload_modal.title_add.avatar": "Añadir foto de perfil", + "account_edit.upload_modal.title_add.header": "Añadir foto de portada", + "account_edit.upload_modal.title_replace.avatar": "Reemplazar foto de perfil", + "account_edit.upload_modal.title_replace.header": "Reemplazar foto de portada", "account_edit.verified_modal.details": "Añade credibilidad a tu perfil de Mastodon verificando enlaces a tus webs personales. Así es como funciona:", "account_edit.verified_modal.invisible_link.details": "Añade el enlace en el encabezado. La parte importante es rel=\"me\", que evita la suplantación de identidad en webs con contenido generado por usuarios. Incluso puedes utilizar un enlace con etiqueta en el encabezado de la página en vez de {tag}, pero el HTML debe ser accesible sin ejecutar JavaScript.", "account_edit.verified_modal.invisible_link.summary": "¿Cómo puedo hacer el enlace invisible?", @@ -220,10 +238,13 @@ "account_edit.verified_modal.step2.header": "Añade tu web como un campo personalizado", "account_edit.verified_modal.title": "Cómo añadir un enlace verificado", "account_edit_tags.add_tag": "Agregar #{tagName}", + "account_edit_tags.column_title": "Editar Etiquetas", "account_edit_tags.help_text": "Las etiquetas destacadas ayudan a los usuarios a descubrir e interactuar con tu perfil. Aparecen como filtros en la vista de actividad de tu página de perfil.", + "account_edit_tags.max_tags_reached": "Has alcanzado el número máximo de etiquetas destacadas.", "account_edit_tags.search_placeholder": "Introduce una etiqueta…", "account_edit_tags.suggestions": "Sugerencias:", "account_edit_tags.tag_status_count": "{count, plural, one {# publicación} other {# publicaciones}}", + "account_list.total": "{total, plural, one {# cuenta} other {# cuentas}}", "account_note.placeholder": "Haz clic para añadir nota", "admin.dashboard.daily_retention": "Tasa de retención de usuarios por día después del registro", "admin.dashboard.monthly_retention": "Tasa de retención de usuarios por mes después del registro", @@ -339,6 +360,8 @@ "collections.accounts.empty_description": "Añade hasta {count} cuentas que sigas", "collections.accounts.empty_title": "Esta colección está vacía", "collections.collection_description": "Descripción", + "collections.collection_language": "Idioma", + "collections.collection_language_none": "Ninguno", "collections.collection_name": "Nombre", "collections.collection_topic": "Tema", "collections.confirm_account_removal": "¿Estás seguro de que quieres eliminar esta cuenta de la colección?", @@ -662,7 +685,9 @@ "follow_suggestions.who_to_follow": "A quién seguir", "followed_tags": "Etiquetas seguidas", "followers.hide_other_followers": "Este usuario ha elegido no hacer visible sus otros seguidores", + "followers.title": "Siguiendo a {name}", "following.hide_other_following": "Este usuario ha elegido no hacer visible a quién más sigue", + "following.title": "Seguido por {name}", "footer.about": "Acerca de", "footer.about_mastodon": "Acerca de Mastodon", "footer.about_server": "Acerca de {domain}", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 0fa0d632f0..632f8f54c1 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -142,7 +142,6 @@ "account.unmute_notifications_short": "Kumoa ilmoitusten mykistys", "account.unmute_short": "Kumoa mykistys", "account_edit.bio.edit_label": "Muokkaa elämäkertaa", - "account_edit.bio.label": "elämäkerta", "account_edit.bio.placeholder": "Lisää lyhyt esittely, joka auttaa muita tunnistamaan sinut.", "account_edit.bio.title": "Elämäkerta", "account_edit.bio_modal.add_title": "Lisää elämäkerta", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index 13923aa50c..bdac463c6b 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -141,27 +141,39 @@ "account.unmute": "Ne plus masquer @{name}", "account.unmute_notifications_short": "Ne plus masquer les notifications", "account.unmute_short": "Ne plus masquer", + "account_edit.bio.add_label": "Ajouter une présentation", + "account_edit.bio.edit_label": "Modifier la présentation", "account_edit.bio.placeholder": "Ajouter une courte introduction pour aider les autres à vous connaître.", "account_edit.bio.title": "Présentation", "account_edit.bio_modal.add_title": "Ajouter une présentation", "account_edit.bio_modal.edit_title": "Modifier la présentation", "account_edit.column_button": "Terminé", "account_edit.column_title": "Modifier le profil", + "account_edit.custom_fields.add_label": "Ajouter un champ", + "account_edit.custom_fields.edit_label": "Modifier le champ", "account_edit.custom_fields.placeholder": "Ajouter vos pronoms, vos sites, ou tout ce que vous voulez partager.", "account_edit.custom_fields.reorder_button": "Réorganiser les champs", "account_edit.custom_fields.tip_content": "Vous pouvez facilement ajouter de la crédibilité à votre compte Mastodon en vérifiant les liens vers tous les sites Web que vous possédez.", "account_edit.custom_fields.tip_title": "Astuce : ajout de liens vérifiés", "account_edit.custom_fields.title": "Champs personnalisés", "account_edit.custom_fields.verified_hint": "Comment ajouter un lien vérifié ?", + "account_edit.display_name.add_label": "Ajouter un nom public", + "account_edit.display_name.edit_label": "Modifier le nom public", "account_edit.display_name.placeholder": "Votre nom public est le nom qui apparaît sur votre profil et dans les fils d'actualités.", "account_edit.display_name.title": "Nom public", + "account_edit.featured_hashtags.edit_label": "Ajouter des hashtags", "account_edit.featured_hashtags.placeholder": "Aider les autres à identifier et à accéder rapidement à vos sujets préférés.", "account_edit.featured_hashtags.title": "Hashtags mis en avant", + "account_edit.field_actions.delete": "Supprimer le champ", + "account_edit.field_actions.edit": "Modifier le champ", "account_edit.field_delete_modal.confirm": "Voulez-vous vraiment supprimer ce champ personnalisé ? Cette action ne peut pas être annulée.", "account_edit.field_delete_modal.delete_button": "Supprimer", "account_edit.field_delete_modal.title": "Supprimer le champ personnalisé ?", "account_edit.field_edit_modal.add_title": "Ajouter un champ personnalisé", + "account_edit.field_edit_modal.discard_confirm": "Abandonner", + "account_edit.field_edit_modal.discard_message": "Vos modifications n’ont pas été enregistrées. Voulez-vous vraiment les abandonner ?", "account_edit.field_edit_modal.edit_title": "Modifier un champ personnalisé", + "account_edit.field_edit_modal.limit_warning": "Le nombre de caractères dépasse la limite recommandée. Le champ peut ne pas s'afficher entièrement sur les téléphones.", "account_edit.field_edit_modal.link_emoji_warning": "Nous déconseillons l'usage d'émoji personnalisé avec les URL. Les champs personnalisés contenant les deux seront affichés comme du texte et non un lien, afin d'éviter toute confusion.", "account_edit.field_edit_modal.name_hint": "Par exemple « Site Web personnel »", "account_edit.field_edit_modal.name_label": "Libellé", @@ -190,6 +202,8 @@ "account_edit.image_edit.alt_edit_button": "Modifier le texte alternatif", "account_edit.image_edit.remove_button": "Supprimer l’image", "account_edit.image_edit.replace_button": "Remplacer l'image", + "account_edit.item_list.delete": "Supprimer {name}", + "account_edit.item_list.edit": "Modifier {name}", "account_edit.name_modal.add_title": "Ajouter un nom public", "account_edit.name_modal.edit_title": "Modifier le nom public", "account_edit.profile_tab.button_label": "Personnaliser", @@ -212,6 +226,10 @@ "account_edit.upload_modal.step_upload.dragging": "Déposer pour téléverser", "account_edit.upload_modal.step_upload.header": "Choisir une image", "account_edit.upload_modal.step_upload.hint": "Format WebP, PNG, GIF ou JPEG, jusqu'à {limit} Mo.{br}L'image sera redimensionnée à {width} × {height} px.", + "account_edit.upload_modal.title_add.avatar": "Ajouter une photo de profil", + "account_edit.upload_modal.title_add.header": "Ajouter une photo de couverture", + "account_edit.upload_modal.title_replace.avatar": "Remplacer la photo de profil", + "account_edit.upload_modal.title_replace.header": "Remplacer la photo de couverture", "account_edit.verified_modal.details": "Ajouter de la crédibilité à votre profil Mastodon en vérifiant les liens vers vos sites Web personnels. Voici comment cela fonctionne :", "account_edit.verified_modal.invisible_link.details": "Ajouter le lien dans votre en-tête. La partie importante est « rel=\"me\" » qui empêche l'usurpation d'identité sur des sites Web ayant du contenu généré par d'autres utilisateur·rice·s. Vous pouvez aussi utiliser une balise link dans l'en-tête de la page au lieu de {tag}, mais le code HTML doit être accessible sans avoir besoin d'exécuter du JavaScript.", "account_edit.verified_modal.invisible_link.summary": "Comment rendre le lien invisible ?", @@ -220,7 +238,9 @@ "account_edit.verified_modal.step2.header": "Ajouter votre site Web en tant que champ personnalisé", "account_edit.verified_modal.title": "Comment ajouter un lien vérifié ?", "account_edit_tags.add_tag": "Ajouter #{tagName}", + "account_edit_tags.column_title": "Modifier les hashtags", "account_edit_tags.help_text": "Les hashtags mis en avant aident les personnes à découvrir et interagir avec votre profil. Ils apparaissent comme des filtres dans la vue « Activité » de votre profil.", + "account_edit_tags.max_tags_reached": "Vous avez atteint le nombre maximum de hashtags mis en avant.", "account_edit_tags.search_placeholder": "Saisir un hashtag…", "account_edit_tags.suggestions": "Suggestions :", "account_edit_tags.tag_status_count": "{count, plural, one {# message} other {# messages}}", @@ -996,7 +1016,7 @@ "onboarding.profile.note_hint": "Vous pouvez @mentionner d'autres personnes ou #hashtags…", "onboarding.profile.title": "Configuration du profil", "onboarding.profile.upload_avatar": "Importer une photo de profil", - "onboarding.profile.upload_header": "Importer un entête de profil", + "onboarding.profile.upload_header": "Importer une image de couverture", "password_confirmation.exceeds_maxlength": "La confirmation du mot de passe dépasse la longueur maximale du mot de passe", "password_confirmation.mismatching": "Les deux mots de passe ne correspondent pas", "picture_in_picture.restore": "Remettre en place", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 951d25fa2f..d76f0f8d4f 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -141,27 +141,39 @@ "account.unmute": "Ne plus masquer @{name}", "account.unmute_notifications_short": "Réactiver les notifications", "account.unmute_short": "Ne plus masquer", + "account_edit.bio.add_label": "Ajouter une présentation", + "account_edit.bio.edit_label": "Modifier la présentation", "account_edit.bio.placeholder": "Ajouter une courte introduction pour aider les autres à vous connaître.", "account_edit.bio.title": "Présentation", "account_edit.bio_modal.add_title": "Ajouter une présentation", "account_edit.bio_modal.edit_title": "Modifier la présentation", "account_edit.column_button": "Terminé", "account_edit.column_title": "Modifier le profil", + "account_edit.custom_fields.add_label": "Ajouter un champ", + "account_edit.custom_fields.edit_label": "Modifier le champ", "account_edit.custom_fields.placeholder": "Ajouter vos pronoms, vos sites, ou tout ce que vous voulez partager.", "account_edit.custom_fields.reorder_button": "Réorganiser les champs", "account_edit.custom_fields.tip_content": "Vous pouvez facilement ajouter de la crédibilité à votre compte Mastodon en vérifiant les liens vers tous les sites Web que vous possédez.", "account_edit.custom_fields.tip_title": "Astuce : ajout de liens vérifiés", "account_edit.custom_fields.title": "Champs personnalisés", "account_edit.custom_fields.verified_hint": "Comment ajouter un lien vérifié ?", + "account_edit.display_name.add_label": "Ajouter un nom public", + "account_edit.display_name.edit_label": "Modifier le nom public", "account_edit.display_name.placeholder": "Votre nom public est le nom qui apparaît sur votre profil et dans les fils d'actualités.", "account_edit.display_name.title": "Nom public", + "account_edit.featured_hashtags.edit_label": "Ajouter des hashtags", "account_edit.featured_hashtags.placeholder": "Aider les autres à identifier et à accéder rapidement à vos sujets préférés.", "account_edit.featured_hashtags.title": "Hashtags mis en avant", + "account_edit.field_actions.delete": "Supprimer le champ", + "account_edit.field_actions.edit": "Modifier le champ", "account_edit.field_delete_modal.confirm": "Voulez-vous vraiment supprimer ce champ personnalisé ? Cette action ne peut pas être annulée.", "account_edit.field_delete_modal.delete_button": "Supprimer", "account_edit.field_delete_modal.title": "Supprimer le champ personnalisé ?", "account_edit.field_edit_modal.add_title": "Ajouter un champ personnalisé", + "account_edit.field_edit_modal.discard_confirm": "Abandonner", + "account_edit.field_edit_modal.discard_message": "Vos modifications n’ont pas été enregistrées. Voulez-vous vraiment les abandonner ?", "account_edit.field_edit_modal.edit_title": "Modifier un champ personnalisé", + "account_edit.field_edit_modal.limit_warning": "Le nombre de caractères dépasse la limite recommandée. Le champ peut ne pas s'afficher entièrement sur les téléphones.", "account_edit.field_edit_modal.link_emoji_warning": "Nous déconseillons l'usage d'émoji personnalisé avec les URL. Les champs personnalisés contenant les deux seront affichés comme du texte et non un lien, afin d'éviter toute confusion.", "account_edit.field_edit_modal.name_hint": "Par exemple « Site Web personnel »", "account_edit.field_edit_modal.name_label": "Libellé", @@ -190,6 +202,8 @@ "account_edit.image_edit.alt_edit_button": "Modifier le texte alternatif", "account_edit.image_edit.remove_button": "Supprimer l’image", "account_edit.image_edit.replace_button": "Remplacer l'image", + "account_edit.item_list.delete": "Supprimer {name}", + "account_edit.item_list.edit": "Modifier {name}", "account_edit.name_modal.add_title": "Ajouter un nom public", "account_edit.name_modal.edit_title": "Modifier le nom public", "account_edit.profile_tab.button_label": "Personnaliser", @@ -212,6 +226,10 @@ "account_edit.upload_modal.step_upload.dragging": "Déposer pour téléverser", "account_edit.upload_modal.step_upload.header": "Choisir une image", "account_edit.upload_modal.step_upload.hint": "Format WebP, PNG, GIF ou JPEG, jusqu'à {limit} Mo.{br}L'image sera redimensionnée à {width} × {height} px.", + "account_edit.upload_modal.title_add.avatar": "Ajouter une photo de profil", + "account_edit.upload_modal.title_add.header": "Ajouter une photo de couverture", + "account_edit.upload_modal.title_replace.avatar": "Remplacer la photo de profil", + "account_edit.upload_modal.title_replace.header": "Remplacer la photo de couverture", "account_edit.verified_modal.details": "Ajouter de la crédibilité à votre profil Mastodon en vérifiant les liens vers vos sites Web personnels. Voici comment cela fonctionne :", "account_edit.verified_modal.invisible_link.details": "Ajouter le lien dans votre en-tête. La partie importante est « rel=\"me\" » qui empêche l'usurpation d'identité sur des sites Web ayant du contenu généré par d'autres utilisateur·rice·s. Vous pouvez aussi utiliser une balise link dans l'en-tête de la page au lieu de {tag}, mais le code HTML doit être accessible sans avoir besoin d'exécuter du JavaScript.", "account_edit.verified_modal.invisible_link.summary": "Comment rendre le lien invisible ?", @@ -220,7 +238,9 @@ "account_edit.verified_modal.step2.header": "Ajouter votre site Web en tant que champ personnalisé", "account_edit.verified_modal.title": "Comment ajouter un lien vérifié ?", "account_edit_tags.add_tag": "Ajouter #{tagName}", + "account_edit_tags.column_title": "Modifier les hashtags", "account_edit_tags.help_text": "Les hashtags mis en avant aident les personnes à découvrir et interagir avec votre profil. Ils apparaissent comme des filtres dans la vue « Activité » de votre profil.", + "account_edit_tags.max_tags_reached": "Vous avez atteint le nombre maximum de hashtags mis en avant.", "account_edit_tags.search_placeholder": "Saisir un hashtag…", "account_edit_tags.suggestions": "Suggestions :", "account_edit_tags.tag_status_count": "{count, plural, one {# message} other {# messages}}", @@ -996,7 +1016,7 @@ "onboarding.profile.note_hint": "Vous pouvez @mentionner d'autres personnes ou #hashtags…", "onboarding.profile.title": "Configuration du profil", "onboarding.profile.upload_avatar": "Importer une photo de profil", - "onboarding.profile.upload_header": "Importer un entête de profil", + "onboarding.profile.upload_header": "Importer une image de couverture", "password_confirmation.exceeds_maxlength": "La confirmation du mot de passe dépasse la longueur du mot de passe", "password_confirmation.mismatching": "Les deux mots de passe ne correspondent pas", "picture_in_picture.restore": "Remettre en place", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index c6b3b1371e..5f8ebf98b8 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -141,8 +141,8 @@ "account.unmute": "Díbhalbhaigh @{name}", "account.unmute_notifications_short": "Díbhalbhaigh fógraí", "account.unmute_short": "Díbhalbhaigh", + "account_edit.bio.add_label": "Cuir beathaisnéis leis", "account_edit.bio.edit_label": "Cuir beathaisnéis in eagar", - "account_edit.bio.label": "beathaisnéis", "account_edit.bio.placeholder": "Cuir réamhrá gearr leis chun cabhrú le daoine eile tú a aithint.", "account_edit.bio.title": "Beathaisnéis", "account_edit.bio_modal.add_title": "Cuir beathaisnéis leis", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index 26b13a7d22..27ad0f4ff8 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -141,8 +141,8 @@ "account.unmute": "Deixar de silenciar a @{name}", "account.unmute_notifications_short": "Reactivar notificacións", "account.unmute_short": "Non silenciar", + "account_edit.bio.add_label": "Engadir biografía", "account_edit.bio.edit_label": "Editar biografía", - "account_edit.bio.label": "sobre ti", "account_edit.bio.placeholder": "Escribe unha breve presentación para que te coñezan mellor.", "account_edit.bio.title": "Sobre ti", "account_edit.bio_modal.add_title": "Engadir biografía", @@ -171,7 +171,9 @@ "account_edit.field_delete_modal.title": "Eliminar campo persoal?", "account_edit.field_edit_modal.add_title": "Engadir campo persoal", "account_edit.field_edit_modal.discard_confirm": "Desbotar", + "account_edit.field_edit_modal.discard_message": "Tes cambios sen gardar. Tes certeza de querer desbotalos?", "account_edit.field_edit_modal.edit_title": "Editar campo persoal", + "account_edit.field_edit_modal.limit_warning": "Superouse o límite de caracteres recomendado. Nos móbiles podería non verse a información completa.", "account_edit.field_edit_modal.link_emoji_warning": "Non recomendamos o uso de emojis persoais combinados con URLs. Os campos persoais que conteñen ambos móstranse só como texto e non como unha ligazón, para evitar a confusión de quen os lea.", "account_edit.field_edit_modal.name_hint": "Ex. \"Páxina web persoal\"", "account_edit.field_edit_modal.name_label": "Etiqueta", @@ -200,6 +202,8 @@ "account_edit.image_edit.alt_edit_button": "Editar descrición", "account_edit.image_edit.remove_button": "Retirar a imaxe", "account_edit.image_edit.replace_button": "Substituír a imaxe", + "account_edit.item_list.delete": "Eliminar {name}", + "account_edit.item_list.edit": "Editar {name}", "account_edit.name_modal.add_title": "Engadir nome público", "account_edit.name_modal.edit_title": "Editar o nome público", "account_edit.profile_tab.button_label": "Personalizar", @@ -222,6 +226,10 @@ "account_edit.upload_modal.step_upload.dragging": "Solta aquí para subir", "account_edit.upload_modal.step_upload.header": "Escoller unha imaxe", "account_edit.upload_modal.step_upload.hint": "Formato WEBP, PNG, GIF ou JPG, ata {limit}MB.{br}A imaxe será comprimida a {width}x{height}px.", + "account_edit.upload_modal.title_add.avatar": "Engadir foto do perfil", + "account_edit.upload_modal.title_add.header": "Engadir foto da cabeceira", + "account_edit.upload_modal.title_replace.avatar": "Substituír foto do perfil", + "account_edit.upload_modal.title_replace.header": "Substituír foto da cabeceira", "account_edit.verified_modal.details": "Engade maior credibilidade ao teu perfil Mastodon verificando as ligazóns ás túas páxinas web persoais. Funciona así:", "account_edit.verified_modal.invisible_link.details": "Engade a ligazón ao «header» da páxina web. A parte importante é rel=\"me\", que evita a suplantación en sitios web con contido creado polas usuarias. Tamén podes usar a etiqueta «link» na cabeceira da páxina no lugar de {tag}, pero o HTML ten que ser accesible sen usar JavaScript.", "account_edit.verified_modal.invisible_link.summary": "Como fago visible a ligazón?", @@ -230,10 +238,13 @@ "account_edit.verified_modal.step2.header": "Engade a túa páxina como campo persoal", "account_edit.verified_modal.title": "Como engadir unha ligazón verificada", "account_edit_tags.add_tag": "Engadir #{tagName}", + "account_edit_tags.column_title": "Editar etiquetas", "account_edit_tags.help_text": "Os cancelos destacados axúdanlle ás usuarias a atopar e interactuar co teu perfil. Aparecen como filtros na túa páxina de perfil na vista Actividade.", + "account_edit_tags.max_tags_reached": "Acadaches o número máximo de cancelos destacados.", "account_edit_tags.search_placeholder": "Escribe un cancelo…", "account_edit_tags.suggestions": "Suxestións:", "account_edit_tags.tag_status_count": "{count, plural, one {# publicación} other {# publicacións}}", + "account_list.total": "{total, plural, one {# conta} other {# contas}}", "account_note.placeholder": "Preme para engadir nota", "admin.dashboard.daily_retention": "Ratio de retención de usuarias diaria após rexistrarse", "admin.dashboard.monthly_retention": "Ratio de retención de usuarias mensual após o rexistro", @@ -674,7 +685,9 @@ "follow_suggestions.who_to_follow": "A quen seguir", "followed_tags": "Cancelos seguidos", "followers.hide_other_followers": "Esta usuaria escolleu non mostrar as outras persoas que a seguen", + "followers.title": "Seguindo a {name}", "following.hide_other_following": "Esta usuaria escolleu non mostrar as outras persoas que segue", + "following.title": "Seguida por {name}", "footer.about": "Sobre", "footer.about_mastodon": "Sobre Mastodon", "footer.about_server": "Sobre {domain}", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index e56abf8fd6..39f6a7f60b 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -141,27 +141,38 @@ "account.unmute": "הפסקת השתקת @{name}", "account.unmute_notifications_short": "הפעלת הודעות", "account.unmute_short": "ביטול השתקה", + "account_edit.bio.edit_label": "עריכת ביוגרפיה", "account_edit.bio.placeholder": "הוסיפו הצגה קצרה כדי לעזור לאחרים לזהות אותך.", "account_edit.bio.title": "ביוגרפיה", "account_edit.bio_modal.add_title": "הוסיפו ביוגרפיה", "account_edit.bio_modal.edit_title": "עריכת ביוגרפיה", "account_edit.column_button": "סיום", "account_edit.column_title": "עריכת הפרופיל", + "account_edit.custom_fields.add_label": "הוספת שדה", + "account_edit.custom_fields.edit_label": "עריכת שדה", "account_edit.custom_fields.placeholder": "הוסיפו צורת פניה, קישורים חיצוניים וכל דבר שתרצו לשתף.", "account_edit.custom_fields.reorder_button": "הגדרת סדר השדות", "account_edit.custom_fields.tip_content": "ניתן להוסיף אמינות לחשבון המסטודון שלך על ידי וידוא קישורים לאתרים שבבעלותך.", "account_edit.custom_fields.tip_title": "טיפ: הוספת קישורים מוודאים", "account_edit.custom_fields.title": "שדות בהתאמה אישית", "account_edit.custom_fields.verified_hint": "כיצד תוסיפו קישורים מוודאים?", + "account_edit.display_name.add_label": "הוספת שם תצוגה", + "account_edit.display_name.edit_label": "עריכת שם תצוגה", "account_edit.display_name.placeholder": "שם התצוגה שלכן הוא איך שהשם יופיע בפרופיל ובצירי הזמנים.", "account_edit.display_name.title": "שם תצוגה", + "account_edit.featured_hashtags.edit_label": "הוספת תגיות", "account_edit.featured_hashtags.placeholder": "עזרו לאחרים לזהות ולגשת בקלות לנושאים החביבים עליכם.", "account_edit.featured_hashtags.title": "תגיות נבחרות", + "account_edit.field_actions.delete": "מחיקת שדה", + "account_edit.field_actions.edit": "עריכת שדה", "account_edit.field_delete_modal.confirm": "האם אתם בטוחיםות שברצונכן למחוק את השדה המיוחד הזה? פעולה זו לא ניתנת לביטול.", "account_edit.field_delete_modal.delete_button": "מחק", "account_edit.field_delete_modal.title": "מחיקת שדה מתואם אישית?", "account_edit.field_edit_modal.add_title": "הוסף שדה מותאם אישית", + "account_edit.field_edit_modal.discard_confirm": "השלך", + "account_edit.field_edit_modal.discard_message": "יש שינויים שלא נשמרו. לסלק אותם?", "account_edit.field_edit_modal.edit_title": "עריכת שדה מותאם אישית", + "account_edit.field_edit_modal.limit_warning": "עברת את מספר התווים המירבי המומלץ. משתמשים בנייד עלולים שלא לראות את השדה המלא.", "account_edit.field_edit_modal.link_emoji_warning": "אנו ממליצים נגד שימוש באמוג'י ייחודיים ביחד עם URL. שדות מיוחדים שמכילים את שניהם יופיעו כמלל בלבד ולא כקישור, כדי למנוע בלבול משתמשים.", "account_edit.field_edit_modal.name_hint": "למשל \"אתר אישי\"", "account_edit.field_edit_modal.name_label": "תווית", @@ -190,6 +201,8 @@ "account_edit.image_edit.alt_edit_button": "עריכת מלל חלופי", "account_edit.image_edit.remove_button": "הסרת תמונה", "account_edit.image_edit.replace_button": "החלפת תמונה", + "account_edit.item_list.delete": "מחיקת {name}", + "account_edit.item_list.edit": "עריכת {name}", "account_edit.name_modal.add_title": "הוספת שם תצוגה", "account_edit.name_modal.edit_title": "עריכת שם תצוגה", "account_edit.profile_tab.button_label": "התאמה אישית", @@ -212,6 +225,10 @@ "account_edit.upload_modal.step_upload.dragging": "גרור להעלאה", "account_edit.upload_modal.step_upload.header": "בחר/י תמונה", "account_edit.upload_modal.step_upload.hint": "תכנים בתקן WEBP, PNG, GIF או JPG, עד לגודל {limit} מ\"ב.{br}התמונה תתוקן לגודל {width} על {height} פיקסלים.", + "account_edit.upload_modal.title_add.avatar": "הוספת תמונת פרופיל", + "account_edit.upload_modal.title_add.header": "הוספת תמונת מסגרת", + "account_edit.upload_modal.title_replace.avatar": "החלפת תמונת פרופיל", + "account_edit.upload_modal.title_replace.header": "החלפת תמונת מסגרת", "account_edit.verified_modal.details": "הוספת אמינות לחשבון המסטודון על ידי הוספת קישורים מוודאים לאתרים אישיים. כך זה עובד:", "account_edit.verified_modal.invisible_link.details": "הוסיפו את הקישור בכותרת. החלק החשוב הוא rel=\"me\" שמונע התחזות על אתרים עם תוכן משתמשים. ניתן גם ליצור תגית link בכותרת העמוד במקום קישור {tag} אבל קוד ה־HTML חייב להופיע שם ללא הרצה של ג'אווהסקריפט.", "account_edit.verified_modal.invisible_link.summary": "כיצד לגרום לקישור להיות בלתי נראה?", @@ -220,7 +237,9 @@ "account_edit.verified_modal.step2.header": "הוסיפו את אתרכן בשדה המיוחד", "account_edit.verified_modal.title": "כיצד תוסיפו קישורים מוודאים", "account_edit_tags.add_tag": "הוספת #{tagName}", + "account_edit_tags.column_title": "עריכת תגיות", "account_edit_tags.help_text": "תגיות נבחרות עוזרות למשתמשים לגלות ולהשתמש בפרופיל שלך. הן יופיעו כסננים במבט הפעילויות על עמוד הפרופיל שלך.", + "account_edit_tags.max_tags_reached": "הגעת למספר התגיות הנבחרות המירבי.", "account_edit_tags.search_placeholder": "הזנת תגית…", "account_edit_tags.suggestions": "הצעות:", "account_edit_tags.tag_status_count": "{count, plural, one {הודעה אחת} two {הודעותיים} other {# הודעות}}", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 48fd189c40..7735fbe1ff 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -142,7 +142,6 @@ "account.unmute_notifications_short": "Értesítések némításának feloldása", "account.unmute_short": "Némitás feloldása", "account_edit.bio.edit_label": "Bemutatkozás szerkesztése", - "account_edit.bio.label": "bemutatkozás", "account_edit.bio.placeholder": "Adj meg egy rövid bemutatkozást, hogy mások könnyebben megtaláljanak.", "account_edit.bio.title": "Bemutatkozás", "account_edit.bio_modal.add_title": "Bemutatkozás hozzáadása", diff --git a/app/javascript/mastodon/locales/intl_provider.tsx b/app/javascript/mastodon/locales/intl_provider.tsx index 94372f95b0..b59458cd2d 100644 --- a/app/javascript/mastodon/locales/intl_provider.tsx +++ b/app/javascript/mastodon/locales/intl_provider.tsx @@ -50,7 +50,6 @@ export const IntlProvider: React.FC< locale={locale} messages={messages} onError={onProviderError} - textComponent='span' {...props} > {children} diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index 512102f6fa..9476dc6f92 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -141,8 +141,8 @@ "account.unmute": "Hætta að þagga niður í @{name}", "account.unmute_notifications_short": "Hætta að þagga í tilkynningum", "account.unmute_short": "Hætta að þagga niður", + "account_edit.bio.add_label": "Bættu við æviágripi", "account_edit.bio.edit_label": "Breyta æviágripi", - "account_edit.bio.label": "æviágrip", "account_edit.bio.placeholder": "Settu inn stutta kynningu á þér svo aðrir eigi betur með að auðkenna þig.", "account_edit.bio.title": "Æviágrip", "account_edit.bio_modal.add_title": "Bættu við æviágripi", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index f8bd1f1039..1c62bdc030 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -141,8 +141,8 @@ "account.unmute": "Riattiva @{name}", "account.unmute_notifications_short": "Riattiva notifiche", "account.unmute_short": "Attiva audio", + "account_edit.bio.add_label": "Aggiungi biografia", "account_edit.bio.edit_label": "Modifica la biografia", - "account_edit.bio.label": "biografia", "account_edit.bio.placeholder": "Aggiungi una breve introduzione per aiutare gli altri a identificarti.", "account_edit.bio.title": "Biografia", "account_edit.bio_modal.add_title": "Aggiungi biografia", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index d53f17eb65..6f573b080a 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -141,27 +141,38 @@ "account.unmute": "@{name} niet langer negeren", "account.unmute_notifications_short": "Meldingen niet langer negeren", "account.unmute_short": "Niet langer negeren", + "account_edit.bio.edit_label": "Biografie bewerken", "account_edit.bio.placeholder": "Vertel iets over jezelf, zodat anderen inzicht krijgen in wat voor persoon je bent.", "account_edit.bio.title": "Biografie", "account_edit.bio_modal.add_title": "Biografie toevoegen", "account_edit.bio_modal.edit_title": "Biografie bewerken", "account_edit.column_button": "Klaar", "account_edit.column_title": "Profiel bewerken", + "account_edit.custom_fields.add_label": "Veld toevoegen", + "account_edit.custom_fields.edit_label": "Veld bewerken", "account_edit.custom_fields.placeholder": "Voeg je voornaamwoorden, externe links of iets anders toe dat je wilt delen.", "account_edit.custom_fields.reorder_button": "Velden opnieuw ordenen", "account_edit.custom_fields.tip_content": "Je kunt gemakkelijk je Mastodon-account geloofwaardig maken door links naar websites die van jou zijn te laten verifiëren.", "account_edit.custom_fields.tip_title": "Tip: Geverifieerde links toevoegen", "account_edit.custom_fields.title": "Extra velden", "account_edit.custom_fields.verified_hint": "Hoe voeg ik een geverifieerde link toe?", + "account_edit.display_name.add_label": "Weergavenaam toevoegen", + "account_edit.display_name.edit_label": "Weergavenaam bewerken", "account_edit.display_name.placeholder": "Je weergavenaam wordt op jouw profiel en op tijdlijnen weergegeven.", "account_edit.display_name.title": "Weergavenaam", + "account_edit.featured_hashtags.edit_label": "Hashtags toevoegen", "account_edit.featured_hashtags.placeholder": "Geef anderen een overzicht van en snel toegang tot je favoriete onderwerpen.", "account_edit.featured_hashtags.title": "Uitgelichte hashtags", + "account_edit.field_actions.delete": "Veld verwijderen", + "account_edit.field_actions.edit": "Veld bewerken", "account_edit.field_delete_modal.confirm": "Weet je zeker dat je dit aangepaste veld wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt.", "account_edit.field_delete_modal.delete_button": "Verwijderen", "account_edit.field_delete_modal.title": "Aangepast veld verwijderen?", "account_edit.field_edit_modal.add_title": "Aangepast veld toevoegen", + "account_edit.field_edit_modal.discard_confirm": "Weggooien", + "account_edit.field_edit_modal.discard_message": "U heeft niet-opgeslagen wijzigingen. Weet u zeker dat u ze wilt weggooien?", "account_edit.field_edit_modal.edit_title": "Aangepast veld bewerken", + "account_edit.field_edit_modal.limit_warning": "Aanbevolen tekenlimiet overschreden. Mobiele gebruikers zien mogelijk uw veld niet volledig.", "account_edit.field_edit_modal.link_emoji_warning": "We raden aan om geen lokale emoji in combinatie met URL's te gebruiken. Aangepaste velden die beide bevatten worden alleen als tekst weergegeven, in plaats van als een link. Dit om verwarring voor de gebruiker te voorkomen.", "account_edit.field_edit_modal.name_hint": "Bijv. \"Persoonlijke website\"", "account_edit.field_edit_modal.name_label": "Label", @@ -190,6 +201,8 @@ "account_edit.image_edit.alt_edit_button": "Alt-tekst bewerken", "account_edit.image_edit.remove_button": "Afbeelding verwijderen", "account_edit.image_edit.replace_button": "Afbeelding vervangen", + "account_edit.item_list.delete": "{name} verwijderen", + "account_edit.item_list.edit": "{name} bewerken", "account_edit.name_modal.add_title": "Weergavenaam toevoegen", "account_edit.name_modal.edit_title": "Weergavenaam bewerken", "account_edit.profile_tab.button_label": "Aanpassen", @@ -212,6 +225,10 @@ "account_edit.upload_modal.step_upload.dragging": "Hierheen slepen om te uploaden", "account_edit.upload_modal.step_upload.header": "Kies een afbeelding", "account_edit.upload_modal.step_upload.hint": "WEBP-, PNG-, GIF- of JPG-formaat, tot max. {limit}MB.{br}Afbeelding wordt geschaald naar {width}x{height}px.", + "account_edit.upload_modal.title_add.avatar": "Profielfoto toevoegen", + "account_edit.upload_modal.title_add.header": "Omslagfoto toevoegen", + "account_edit.upload_modal.title_replace.avatar": "Profielfoto vervangen", + "account_edit.upload_modal.title_replace.header": "Omslagfoto vervangen", "account_edit.verified_modal.details": "Maak je Mastodonprofiel geloofwaardig door links naar persoonlijke websites te verifiëren. Zo werkt het:", "account_edit.verified_modal.invisible_link.details": "Voeg de link aan de HTML van je website toe. Het belangrijkste onderdeel is rel=\"me\", waarmee wordt voorkomen dat websites met user-generated content geïmpersoneerd kunnen worden. Je kunt zelfs een -tag gebruiken binnen de -tag van je website in plaats van {tag}, maar de HTML moet zonder JavaScript toegankelijk zijn.", "account_edit.verified_modal.invisible_link.summary": "Hoe maak ik de link onzichtbaar?", @@ -220,10 +237,13 @@ "account_edit.verified_modal.step2.header": "Voeg je website toe als een aangepast veld", "account_edit.verified_modal.title": "Hoe voeg je een geverifieerde link toe", "account_edit_tags.add_tag": "#{tagName} toevoegen", + "account_edit_tags.column_title": "Labels bewerken", "account_edit_tags.help_text": "Uitgelichte hashtags helpen gebruikers je profiel te ontdekken en om er interactie mee te communiceren. Ze verschijnen als filters op je Profielpagina onder het tabblad Activiteit.", + "account_edit_tags.max_tags_reached": "Maximum aantal uitgelichte hashtags bereikt.", "account_edit_tags.search_placeholder": "Voer een hashtag in…", "account_edit_tags.suggestions": "Suggesties:", "account_edit_tags.tag_status_count": "{count, plural, one {# bericht} other {# berichten}}", + "account_list.total": "{total, plural, one {# account} other {# accounts}}", "account_note.placeholder": "Klik om een opmerking toe te voegen", "admin.dashboard.daily_retention": "Retentiegraad van gebruikers per dag, vanaf registratie", "admin.dashboard.monthly_retention": "Retentiegraad van gebruikers per maand, vanaf registratie", @@ -618,6 +638,10 @@ "featured_carousel.header": "{count, plural, one {Vastgezet bericht} other {Vastgezette berichten}}", "featured_carousel.slide": "Bericht {current, number} van {max, number}", "featured_tags.more_items": "+{count}", + "featured_tags.suggestions": "De laatste tijd heb over {items} gepost. Deze toevoegen als uitgelichte hashtags?", + "featured_tags.suggestions.add": "Toevoegen", + "featured_tags.suggestions.added": "Je kunt je uitgelichte hashtags beheren onder Profiel bewerken > Uitgelichte hashtags.", + "featured_tags.suggestions.dismiss": "Nee, bedankt", "filter_modal.added.context_mismatch_explanation": "Deze filtercategorie is niet van toepassing op de context waarin je dit bericht hebt benaderd. Als je wilt dat het bericht ook in deze context wordt gefilterd, moet je het filter bewerken.", "filter_modal.added.context_mismatch_title": "Context komt niet overeen!", "filter_modal.added.expired_explanation": "Deze filtercategorie is verlopen. Je moet de vervaldatum wijzigen om de categorie toe te kunnen passen.", @@ -660,7 +684,9 @@ "follow_suggestions.who_to_follow": "Wie te volgen", "followed_tags": "Gevolgde hashtags", "followers.hide_other_followers": "Deze gebruiker heeft ervoor gekozen diens andere volgers niet zichtbaar te maken", + "followers.title": "{name} volgen", "following.hide_other_following": "Deze gebruiker heeft ervoor gekozen de rest van diens gevolgde accounts niet zichtbaar te maken", + "following.title": "Gevolgd door {name}", "footer.about": "Over", "footer.about_mastodon": "Over Mastodon", "footer.about_server": "Over {domain}", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index 88b21f47ca..c3642f25c0 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -14,9 +14,16 @@ "about.powered_by": "Zdecentralizowane media społecznościowe napędzane przez {mastodon}", "about.rules": "Regulamin serwera", "account.account_note_header": "Notatka", + "account.activity": "Aktywność", + "account.add_note": "Dodaj osobistą notkę", "account.add_or_remove_from_list": "Dodaj lub usuń z list", + "account.badges.admin": "Admin", + "account.badges.blocked": "Zablokowane", "account.badges.bot": "Bot", + "account.badges.domain_blocked": "Zablokowana domena", "account.badges.group": "Grupa", + "account.badges.muted": "Wyciszone", + "account.badges.muted_until": "Wyciszone do {until}", "account.block": "Blokuj @{name}", "account.block_domain": "Blokuj wszystko z {domain}", "account.block_short": "Zablokuj", @@ -27,6 +34,7 @@ "account.direct": "Napisz bezpośrednio do @{name}", "account.disable_notifications": "Przestań powiadamiać mnie o wpisach @{name}", "account.domain_blocking": "Blokowanie domeny", + "account.edit_note": "Edytuj osobistą notkę", "account.edit_profile": "Edytuj profil", "account.edit_profile_short": "Edytuj", "account.enable_notifications": "Powiadamiaj mnie o wpisach @{name}", @@ -36,9 +44,17 @@ "account.familiar_followers_two": "To konto jest obserwowane przez {name1} i {name2}", "account.featured": "Wyróżnione", "account.featured.accounts": "Profile", + "account.featured.collections": "Kolekcje", "account.featured.hashtags": "Tagi", "account.featured_tags.last_status_at": "Ostatni post {date}", "account.featured_tags.last_status_never": "Brak postów", + "account.field_overflow": "Pokaż całą zawartość", + "account.filters.all": "Wszystkie aktywności", + "account.filters.boosts_toggle": "Pokaż ulepszenia", + "account.filters.posts_boosts": "Posty i ulepszenia", + "account.filters.posts_only": "Posty", + "account.filters.posts_replies": "Posty i odpowiedzi", + "account.filters.replies_toggle": "Pokaż odpowiedzi", "account.follow": "Obserwuj", "account.follow_back": "Również obserwuj", "account.follow_back_short": "Również obserwuj", @@ -63,6 +79,24 @@ "account.locked_info": "To konto jest prywatne. Właściciel ręcznie wybiera kto może go obserwować.", "account.media": "Multimedia", "account.mention": "Wspomnij o @{name}", + "account.menu.add_to_list": "Dodaj do listy…", + "account.menu.block": "Zablokuj konto", + "account.menu.block_domain": "Zablokuj {domain}", + "account.menu.copied": "Skopiowano link do konta do schowka", + "account.menu.copy": "Skopiuj link", + "account.menu.direct": "Prywatna wzmianka", + "account.menu.hide_reblogs": "Ukryj ulepszenia na osi czasu", + "account.menu.mention": "Wzmianka", + "account.menu.mute": "Wycisz konta", + "account.menu.note.description": "Widoczne tylko dla Ciebie", + "account.menu.open_original_page": "Zobacz na {domain}", + "account.menu.remove_follower": "Usuń obserwującego", + "account.menu.report": "Zgłoś konto", + "account.menu.share": "Udostępnij...", + "account.menu.show_reblogs": "Pokaż ulepszenia na osi czasu", + "account.menu.unblock": "Odblokuj konto", + "account.menu.unblock_domain": "Odblokuj {domain}", + "account.menu.unmute": "Odcisz konto", "account.moved_to": "{name} jako swoje nowe konto wskazał/a:", "account.mute": "Wycisz @{name}", "account.mute_notifications_short": "Wycisz powiadomienia", @@ -90,6 +124,35 @@ "account.unmute": "Nie wyciszaj @{name}", "account.unmute_notifications_short": "Nie wyciszaj powiadomień", "account.unmute_short": "Nie wyciszaj", + "account_edit.bio_modal.add_title": "Szczegóły profilu", + "account_edit.bio_modal.edit_title": "Edytuj szczegóły profilu", + "account_edit.column_button": "Gotowe", + "account_edit.column_title": "Edytuj profil", + "account_edit.custom_fields.add_label": "Dodaj pole", + "account_edit.custom_fields.edit_label": "Edytuj pole", + "account_edit.custom_fields.placeholder": "Dodaj swoje zaimki, linki zewnętrzne lub cokolwiek innego, które chcesz udostępnić.", + "account_edit.custom_fields.reorder_button": "Zmień kolejność pól", + "account_edit.custom_fields.tip_content": "Możesz z łatwością zwiększyć wiarygodność swojego konta Mastodon poprzez weryfikację linków do wszelkich stron internetowych, które posiadasz.", + "account_edit.custom_fields.tip_title": "Wskazówka: Dodawanie zweryfikowanych linków", + "account_edit.custom_fields.title": "Pola niestandardowe", + "account_edit.custom_fields.verified_hint": "Jak dodać zweryfikowany link?", + "account_edit.display_name.add_label": "Dodaj nazwę wyświetlaną", + "account_edit.display_name.edit_label": "Edytuj nazwę wyświetlaną", + "account_edit.display_name.placeholder": "Twoja nazwa wyświetlana jest jak Twoja nazwa pojawia się na Twoim profilu i na osi czasu.", + "account_edit.display_name.title": "Wyświetlana nazwa", + "account_edit.featured_hashtags.edit_label": "Dodaj hashtagi", + "account_edit.featured_hashtags.placeholder": "Pomóż innym zidentyfikować i mieć szybki dostęp do Twoich ulubionych tematów.", + "account_edit.featured_hashtags.title": "Wyróżnione hashtagi", + "account_edit.field_actions.delete": "Usuń pole", + "account_edit.field_actions.edit": "Edytuj pole", + "account_edit.field_delete_modal.confirm": "Czy na pewno chcesz usunąć to pole niestandardowe? Tej czynności nie można cofnąć.", + "account_edit.field_delete_modal.delete_button": "Usuń", + "account_edit.field_delete_modal.title": "Usuń pole niestandardowe ", + "account_edit.field_edit_modal.add_title": "Dodaj pole niestandardowe", + "account_edit.field_edit_modal.discard_confirm": "Odrzuć", + "account_edit.field_edit_modal.discard_message": "Masz niezapisane zmiany. Czy na pewno chcesz je odrzucić?", + "account_edit.field_edit_modal.edit_title": "Edytuj dodatkowe pole", + "account_edit.field_edit_modal.limit_warning": "Przekroczono limit zalecanych znaków. Użytkownicy mobilni mogą nie widzieć Twojego pola w całości.", "account_note.placeholder": "Kliknij, aby dodać notatkę", "admin.dashboard.daily_retention": "Wskaźnik utrzymania użytkowników według dni od rejestracji", "admin.dashboard.monthly_retention": "Wskaźnik utrzymania użytkowników według miesięcy od rejestracji", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index b0b49bc117..0d067c6d8c 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -142,7 +142,6 @@ "account.unmute_notifications_short": "Ativar som de notificações", "account.unmute_short": "Desativar silêncio", "account_edit.bio.edit_label": "Editar Biografia", - "account_edit.bio.label": "biografia", "account_edit.bio.placeholder": "Insira uma breve introdução para ajudar os outros a lhe identificar.", "account_edit.bio.title": "Bio", "account_edit.bio_modal.add_title": "Adicionar biografia", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index 62339bf622..b31772b7af 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -141,12 +141,16 @@ "account.unmute": "Desocultar @{name}", "account.unmute_notifications_short": "Desocultar notificações", "account.unmute_short": "Desocultar", + "account_edit.bio.add_label": "Adicionar biografia", + "account_edit.bio.edit_label": "Editar biografia", "account_edit.bio.placeholder": "Adicione uma breve apresentação para ajudar os outros a identificá-lo.", "account_edit.bio.title": "Bio", "account_edit.bio_modal.add_title": "Adicionar biografia", "account_edit.bio_modal.edit_title": "Editar biografia", "account_edit.column_button": "Concluído", "account_edit.column_title": "Editar Perfil", + "account_edit.custom_fields.add_label": "Adicionar campo", + "account_edit.custom_fields.edit_label": "Editar campo", "account_edit.custom_fields.placeholder": "Adicione os seus pronomes, hiperligações externas ou qualquer outra coisa que queira partilhar.", "account_edit.custom_fields.reorder_button": "Reordenar campos", "account_edit.custom_fields.tip_content": "Pode adicionar facilmente credibilidade à sua conta Mastodon, verificando ligações para qualquer website que possua.", @@ -157,10 +161,13 @@ "account_edit.display_name.title": "Nome a mostrar", "account_edit.featured_hashtags.placeholder": "Ajude à sua identificação por outros e tenha acesso rápido aos seus tópicos favoritos.", "account_edit.featured_hashtags.title": "Etiquetas em destaque", + "account_edit.field_actions.delete": "Eliminar campo", + "account_edit.field_actions.edit": "Editar campo", "account_edit.field_delete_modal.confirm": "Tem certeza de que deseja excluir este campo personalizado? Esta ação não pode ser desfeita.", "account_edit.field_delete_modal.delete_button": "Excluir", "account_edit.field_delete_modal.title": "Excluir campo personalizado?", "account_edit.field_edit_modal.add_title": "Adicionar campo personalizado", + "account_edit.field_edit_modal.discard_confirm": "Descartar", "account_edit.field_edit_modal.edit_title": "Editar campo personalizado", "account_edit.field_edit_modal.link_emoji_warning": "Não recomendamos o uso de emojis personalizados em combinação com URLs. Campos personalizados que contenham ambos serão exibidos apenas como texto, em vez de como hiperligação, para evitar confusão aos utilizadores.", "account_edit.field_edit_modal.name_hint": "Ex.: \"Site pessoal\"", diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json index b65762ddf6..4965eaac16 100644 --- a/app/javascript/mastodon/locales/sq.json +++ b/app/javascript/mastodon/locales/sq.json @@ -141,8 +141,8 @@ "account.unmute": "Ktheji zërin @{name}", "account.unmute_notifications_short": "Shfaqi njoftimet", "account.unmute_short": "Çheshtoje", + "account_edit.bio.add_label": "Shtoni jetëshkrim", "account_edit.bio.edit_label": "Përpunoni jetëshkrim", - "account_edit.bio.label": "jetëshkrim", "account_edit.bio.placeholder": "Shtoni një hyrje të shkurtër për të ndihmuar të tjerët t’ju identifikojnë.", "account_edit.bio.title": "Jetëshkrim", "account_edit.bio_modal.add_title": "Shtoni jetëshkrim", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 9b0dae15ec..e8af5b7078 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -134,6 +134,7 @@ "account.unmute": "Sluta tysta @{name}", "account.unmute_notifications_short": "Aktivera aviseringsljud", "account.unmute_short": "Avtysta", + "account_edit.bio.add_label": "Lägg till biografi", "account_edit.bio.placeholder": "Lägg till en kort introduktion för att hjälpa andra att identifiera dig.", "account_edit.bio.title": "Biografi", "account_edit.bio_modal.add_title": "Lägg till biografi", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index c30e20c867..4f13dec3c3 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -141,27 +141,39 @@ "account.unmute": "@{name} adlı kişinin sesini aç", "account.unmute_notifications_short": "Bildirimlerin sesini aç", "account.unmute_short": "Susturmayı kaldır", + "account_edit.bio.add_label": "Kişisel bilgi ekle", + "account_edit.bio.edit_label": "Kişisel bilgiyi düzenle", "account_edit.bio.placeholder": "Diğerlerinin sizi tanımasına yardımcı olmak için kısa bir tanıtım ekleyin.", "account_edit.bio.title": "Kişisel bilgiler", "account_edit.bio_modal.add_title": "Kişisel bilgi ekle", "account_edit.bio_modal.edit_title": "Kişisel bilgiyi düzenle", "account_edit.column_button": "Tamamlandı", "account_edit.column_title": "Profili Düzenle", + "account_edit.custom_fields.add_label": "Alan ekle", + "account_edit.custom_fields.edit_label": "Alanı düzenle", "account_edit.custom_fields.placeholder": "Zamirlerinizi, harici bağlantılarınızı veya paylaşmak istediğiniz diğer bilgileri ekleyin.", "account_edit.custom_fields.reorder_button": "Alanları yeniden sırala", "account_edit.custom_fields.tip_content": "Sahip olduğunuz web sitelerine bağlantıları doğrulayarak Mastodon hesabınıza kolayca güvenilirlik katabilirsiniz.", "account_edit.custom_fields.tip_title": "İpucu: Doğrulanmış bağlantılar ekleme", "account_edit.custom_fields.title": "Özel alanlar", "account_edit.custom_fields.verified_hint": "Doğrulanmış bir bağlantı nasıl eklerim?", + "account_edit.display_name.add_label": "Görüntülenecek ad ekle", + "account_edit.display_name.edit_label": "Görüntülenecek adı düzenle", "account_edit.display_name.placeholder": "Görünen adınız profilinizde ve zaman akışlarında adınızın nasıl göründüğüdür.", "account_edit.display_name.title": "Görünen ad", + "account_edit.featured_hashtags.edit_label": "Etiket ekle", "account_edit.featured_hashtags.placeholder": "Başkalarının favori konularınızı tanımlamasına ve bunlara hızlı bir şekilde erişmesine yardımcı olun.", "account_edit.featured_hashtags.title": "Öne çıkan etiketler", + "account_edit.field_actions.delete": "Alanı sil", + "account_edit.field_actions.edit": "Alanı düzenle", "account_edit.field_delete_modal.confirm": "Bu özel alanı silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.", "account_edit.field_delete_modal.delete_button": "Sil", "account_edit.field_delete_modal.title": "Özel alanı sil?", "account_edit.field_edit_modal.add_title": "Özel alan ekle", + "account_edit.field_edit_modal.discard_confirm": "Yoksay", + "account_edit.field_edit_modal.discard_message": "Kaydedilmemiş değişiklikleriniz var. Bunları silmek istediğinizden emin misiniz?", "account_edit.field_edit_modal.edit_title": "Özel alanı düzenle", + "account_edit.field_edit_modal.limit_warning": "Önerilen karakter sınırı aşıldı. Mobil kullanıcılar sahayı tam olarak görmeyebilirler.", "account_edit.field_edit_modal.link_emoji_warning": "Url'lerle birlikte özel emoji kullanmamanızı öneririz. Her ikisini de içeren özel alanlar, kullanıcıların kafasını karıştırmamak için bağlantı yerine yalnızca metin olarak görüntülenir.", "account_edit.field_edit_modal.name_hint": "Örn. \"Kişisel web sitesi\"", "account_edit.field_edit_modal.name_label": "Etiket", @@ -190,6 +202,8 @@ "account_edit.image_edit.alt_edit_button": "Alternatif metni düzenle", "account_edit.image_edit.remove_button": "Görseli kaldır", "account_edit.image_edit.replace_button": "Görseli değiştir", + "account_edit.item_list.delete": "{name} sil", + "account_edit.item_list.edit": "{name} düzenle", "account_edit.name_modal.add_title": "Görünen ad ekle", "account_edit.name_modal.edit_title": "Görünen adı düzenle", "account_edit.profile_tab.button_label": "Özelleştir", @@ -212,6 +226,10 @@ "account_edit.upload_modal.step_upload.dragging": "Yüklemek için bırakın", "account_edit.upload_modal.step_upload.header": "Bir resim seç", "account_edit.upload_modal.step_upload.hint": "WEBP, PNG, GIF veya JPG formatında, en fazla {limit} MB.{br}Görsel {width}x{height} piksel boyutuna getirilir.", + "account_edit.upload_modal.title_add.avatar": "Profil fotoğrafı ekle", + "account_edit.upload_modal.title_add.header": "Kapak fotoğrafı ekle", + "account_edit.upload_modal.title_replace.avatar": "Profil fotoğrafını değiştir", + "account_edit.upload_modal.title_replace.header": "Kapak fotoğrafını değiştirin", "account_edit.verified_modal.details": "Kişisel web sitelerine bağlantıları doğrulayarak Mastodon profilinize güvenilirlik katın. İşte böyle çalışıyor:", "account_edit.verified_modal.invisible_link.details": "Bağlantıyı başlığınıza ekleyin. Önemli olan kısım, kullanıcı tarafından oluşturulan içeriğe sahip web sitelerinde kimlik sahtekarlığını önleyen rel=\"me\" özniteliğidir. {tag} yerine sayfanın başlığında bir bağlantı etiketi bile kullanabilirsiniz, ancak HTML, JavaScript çalıştırılmadan erişilebilir olmalıdır.", "account_edit.verified_modal.invisible_link.summary": "Bağlantıyı nasıl görünmez hale getirebilirim?", @@ -220,7 +238,9 @@ "account_edit.verified_modal.step2.header": "Web sitenizi özel bir alan olarak ekleyin", "account_edit.verified_modal.title": "Doğrulanmış bir bağlantı nasıl eklenir", "account_edit_tags.add_tag": "#{tagName} ekle", + "account_edit_tags.column_title": "Etiketleri Düzenle", "account_edit_tags.help_text": "Öne çıkan etiketler kullanıcıların profilinizi keşfetmesine ve etkileşim kurmasına yardımcı olur. Profil sayfanızın Etkinlik görünümünde filtreler olarak görünürler.", + "account_edit_tags.max_tags_reached": "Azami öne çıkan etiket sayısına ulaştınız.", "account_edit_tags.search_placeholder": "Bir etiket girin…", "account_edit_tags.suggestions": "Öneriler:", "account_edit_tags.tag_status_count": "{count, plural, one {# gönderi} other {# gönderi}}", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index af8f7d186e..c68db5734c 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -141,8 +141,8 @@ "account.unmute": "Bỏ phớt lờ @{name}", "account.unmute_notifications_short": "Bỏ phớt lờ thông báo", "account.unmute_short": "Bỏ phớt lờ", + "account_edit.bio.add_label": "Thêm giới thiệu", "account_edit.bio.edit_label": "Sửa giới thiệu", - "account_edit.bio.label": "giới thiệu", "account_edit.bio.placeholder": "Thêm một dòng giới thiệu để giúp mọi người nhận ra bạn.", "account_edit.bio.title": "Giới thiệu", "account_edit.bio_modal.add_title": "Thêm giới thiệu", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 9cf0de1a1a..482455e55b 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -141,8 +141,8 @@ "account.unmute": "不再隐藏 @{name}", "account.unmute_notifications_short": "恢复通知", "account.unmute_short": "取消隐藏", + "account_edit.bio.add_label": "添加个人简介", "account_edit.bio.edit_label": "编辑个人简介", - "account_edit.bio.label": "简介", "account_edit.bio.placeholder": "添加一段简短介绍,帮助其他人认识你。", "account_edit.bio.title": "简介", "account_edit.bio_modal.add_title": "添加个人简介", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 15256077de..8552ab635f 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -141,8 +141,8 @@ "account.unmute": "解除靜音 @{name}", "account.unmute_notifications_short": "解除靜音推播通知", "account.unmute_short": "解除靜音", + "account_edit.bio.add_label": "新增個人簡介", "account_edit.bio.edit_label": "編輯個人簡介", - "account_edit.bio.label": "個人簡介", "account_edit.bio.placeholder": "加入一段簡短介紹以幫助其他人識別您。", "account_edit.bio.title": "個人簡介", "account_edit.bio_modal.add_title": "新增個人簡介", @@ -639,7 +639,7 @@ "featured_carousel.header": "{count, plural, other {# 則釘選嘟文}}", "featured_carousel.slide": "{max, number} 則嘟文中之第 {current, number} 則", "featured_tags.more_items": "+{count}", - "featured_tags.suggestions": "最近您曾發有關 {items} 之嘟文。是否新增其為推薦主題標籤?", + "featured_tags.suggestions": "最近您曾發過有關 {items} 之嘟文。是否新增其為推薦主題標籤?", "featured_tags.suggestions.add": "新增", "featured_tags.suggestions.added": "於 編輯個人檔案 > 推薦主題標籤 隨時管理您的推薦主題標籤。", "featured_tags.suggestions.dismiss": "不需要,謝謝", diff --git a/app/javascript/mastodon/polyfills/intl.ts b/app/javascript/mastodon/polyfills/intl.ts index b1157557e5..ea09ee6bf6 100644 --- a/app/javascript/mastodon/polyfills/intl.ts +++ b/app/javascript/mastodon/polyfills/intl.ts @@ -1,6 +1,6 @@ // import { shouldPolyfill as shouldPolyfillCanonicalLocales } from '@formatjs/intl-getcanonicallocales/should-polyfill'; // import { shouldPolyfill as shouldPolyfillLocale } from '@formatjs/intl-locale/should-polyfill'; -import { shouldPolyfill as shoudPolyfillPluralRules } from '@formatjs/intl-pluralrules/should-polyfill'; +import { shouldPolyfill as shoudPolyfillPluralRules } from '@formatjs/intl-pluralrules/should-polyfill.js'; // import { shouldPolyfill as shouldPolyfillNumberFormat } from '@formatjs/intl-numberformat/should-polyfill'; // import { shouldPolyfill as shouldPolyfillIntlDateTimeFormat } from '@formatjs/intl-datetimeformat/should-polyfill'; // import { shouldPolyfill as shouldPolyfillIntlRelativeTimeFormat } from '@formatjs/intl-relativetimeformat/should-polyfill'; @@ -54,7 +54,7 @@ async function loadIntlPluralRulesPolyfills(locale: string) { return; } // Load the polyfill 1st BEFORE loading data - await import('@formatjs/intl-pluralrules/polyfill-force'); + await import('@formatjs/intl-pluralrules/polyfill-force.js'); await import( `../../../../node_modules/@formatjs/intl-pluralrules/locale-data/${unsupportedLocale}.js` ); diff --git a/app/javascript/mastodon/selectors/user_lists.ts b/app/javascript/mastodon/selectors/user_lists.ts index 9d681aa255..ed5a3271ba 100644 --- a/app/javascript/mastodon/selectors/user_lists.ts +++ b/app/javascript/mastodon/selectors/user_lists.ts @@ -29,7 +29,7 @@ export const selectUserListWithoutMe = createAppSelector( .filter((id) => id !== currentAccountId) .toArray(), isLoading: !!list.get('isLoading', true), - hasMore: !!list.get('hasMore', false), + hasMore: !!list.get('next'), }; }, ); diff --git a/app/javascript/styles/entrypoints/mailer.scss b/app/javascript/styles/entrypoints/mailer.scss index fcbbd66f4c..725f4e4de4 100644 --- a/app/javascript/styles/entrypoints/mailer.scss +++ b/app/javascript/styles/entrypoints/mailer.scss @@ -721,6 +721,52 @@ table + p { line-height: 24px; } +// Banner item +.email-banner-table { + border-radius: 12px; + background-color: #1b001f; + background-image: url('../../images/mailer-new/common/header-bg-start.png'); + background-position: left top; + background-repeat: repeat; +} + +.email-banner-td { + padding: 24px 24px 14px; +} + +.email-banner-text-td { + p { + margin: 0 0 12px; + color: #fff; + font-size: 14px; + font-weight: 600; + line-height: 16.8px; + } + + .email-desktop-flex { + align-items: center; + } + + .email-btn-table { + background-color: #fff; + } + + .email-btn-td { + mso-padding-alt: 10px; + } + + .email-btn-a { + color: #181820; + padding-left: 10px; + padding-right: 10px; + } + + div + div { + margin-inline-start: auto; + margin-bottom: 12px; + } +} + // Checklist item .email-checklist-wrapper-td { padding: 4px 0; diff --git a/app/javascript/styles/mastodon/_mixins.scss b/app/javascript/styles/mastodon/_mixins.scss index a8c3604dc5..c92c8d8cee 100644 --- a/app/javascript/styles/mastodon/_mixins.scss +++ b/app/javascript/styles/mastodon/_mixins.scss @@ -7,7 +7,7 @@ background: var(--color-bg-secondary); color: var(--color-text-primary); border-radius: 4px; - border: 1px solid var(--color-border-on-bg-secondary); + border: 1px solid var(--color-border-primary); font-size: 17px; line-height: normal; margin: 0; diff --git a/app/javascript/styles/mastodon/accounts.scss b/app/javascript/styles/mastodon/accounts.scss index d1c35e3f9e..fad41b5eb8 100644 --- a/app/javascript/styles/mastodon/accounts.scss +++ b/app/javascript/styles/mastodon/accounts.scss @@ -13,7 +13,7 @@ &:active, &:focus { .card__bar { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); } } } @@ -220,8 +220,8 @@ .information-badge { &.superapp { color: var(--color-text-success); - background-color: var(--color-bg-success-softer); - border-color: var(--color-border-on-bg-success-softer); + background-color: var(--color-bg-success-softest); + border-color: var(--color-border-success-soft); } } @@ -229,7 +229,7 @@ display: inline-flex; padding: 4px; padding-inline-end: 8px; - border: 1px solid var(--color-text-brand); + border: 1px solid var(--color-border-brand); color: var(--color-text-brand); font-weight: 500; font-size: 12px; @@ -255,8 +255,8 @@ .simple_form .not_recommended { color: var(--color-text-error); - background-color: var(--color-bg-error-softer); - border-color: var(--color-border-on-bg-error-softer); + background-color: var(--color-bg-error-softest); + border-color: var(--color-border-error-soft); } .account__header__fields { @@ -310,8 +310,8 @@ } .verified { - border: 1px solid var(--color-border-on-bg-success-softer); - background: var(--color-bg-success-softer); + border: 1px solid var(--color-border-success-soft); + background: var(--color-bg-success-softest); a { color: var(--color-text-success); diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index 7d5a3be314..89f2493291 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -68,7 +68,7 @@ $content-width: 840px; border-radius: 4px; &:focus { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); } .material-close { @@ -438,10 +438,57 @@ $content-width: 840px; } ul .simple-navigation-active-leaf a { - border-bottom-color: var(--color-text-brand); + border-bottom-color: var(--color-border-brand); } } } + + .callout { + display: flex; + align-items: start; + padding: 12px; + gap: 8px; + background-color: var(--color-bg-brand-softest); + color: var(--color-text-primary); + border-radius: 12px; + font-size: 15px; + margin-bottom: 30px; + + .icon { + padding: 4px; + border-radius: 9999px; + width: 1rem; + height: 1rem; + margin-top: -2px; + background-color: var(--color-bg-brand-soft); + } + + .content { + display: flex; + flex-direction: column; + gap: 8px; + flex-grow: 1; + padding: 0; + + @media screen and (width >= 630px) { + flex-direction: row; + } + } + + .body { + flex-grow: 1; + } + + .title { + font-weight: 600; + margin-bottom: 8px; + } + + a { + color: inherit; + font-weight: 600; + } + } } hr.spacer { @@ -499,7 +546,7 @@ body, kbd { font-family: Courier, monospace; - background-color: var(--color-bg-brand-softer); + background-color: var(--color-bg-brand-softest); padding: 4px; padding-bottom: 2px; border-radius: 5px; @@ -566,7 +613,7 @@ kbd { &.selected { color: var(--color-text-brand); - border-bottom: 2px solid var(--color-text-brand); + border-bottom: 2px solid var(--color-border-brand); } } } @@ -845,14 +892,14 @@ a.name-tag, .speech-bubble { margin-bottom: 20px; - border-inline-start: 4px solid var(--color-text-brand); + border-inline-start: 4px solid var(--color-border-brand); &.positive { border-color: var(--color-text-success); } &.negative { - border-color: var(--color-text-error); + border-color: var(--color-border-error); } &.warning { @@ -1314,7 +1361,7 @@ a.sparkline { &:hover, &:focus, &:active { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); } } @@ -1938,7 +1985,7 @@ a.sparkline { width: calc(1.375rem + 1px); height: calc(1.375rem + 1px); background: var(--color-bg-primary); - border: 1px solid var(--color-text-brand); + border: 1px solid var(--color-border-brand); color: var(--color-text-brand); border-radius: 8px; } @@ -2022,8 +2069,8 @@ a.sparkline { display: block; box-sizing: border-box; color: var(--color-text-primary); - background: var(--color-bg-brand-softer); - border: 1px solid var(--color-border-on-bg-brand-softer); + background: var(--color-bg-brand-softest); + border: 1px solid var(--color-border-brand-soft); border-radius: 8px; padding: 8px 13px; position: relative; diff --git a/app/javascript/styles/mastodon/basics.scss b/app/javascript/styles/mastodon/basics.scss index 610730df5a..a16afbe9a5 100644 --- a/app/javascript/styles/mastodon/basics.scss +++ b/app/javascript/styles/mastodon/basics.scss @@ -2,13 +2,13 @@ html { color: var(--color-text-primary); - background: var(--color-bg-ambient); + background: var(--color-bg-primary); &.custom-scrollbars { scrollbar-color: var(--color-text-secondary) var(--color-bg-secondary); } - --outline-focus-default: 2px solid var(--color-text-brand); + --outline-focus-default: 2px solid var(--color-border-brand); --avatar-border-radius: 8px; --max-media-height-small: 460px; --max-media-height-large: 566px; @@ -46,7 +46,7 @@ html.has-modal { body { font-family: $font-sans-serif, sans-serif; - background: var(--color-bg-ambient); + background: var(--color-bg-primary); font-size: 13px; line-height: 18px; font-weight: 400; diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index e38193c30b..c6870f1fb8 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -114,7 +114,7 @@ } &:focus-visible { - outline: 2px solid var(--color-bg-brand-base); + outline: var(--outline-focus-default); outline-offset: 2px; } @@ -169,12 +169,12 @@ color: var(--color-text-brand); background: transparent; padding: 6px 17px; - border: 1px solid var(--color-text-brand); + border: 1px solid var(--color-border-brand); &:active, &:focus, &:hover { - border-color: var(--color-text-brand); + border-color: var(--color-border-brand); color: var(--color-text-brand); background-color: transparent; text-decoration: none; @@ -184,7 +184,7 @@ &:active, &:focus, &:hover { - border-color: var(--color-text-error); + border-color: var(--color-border-error); color: var(--color-text-error); } } @@ -284,8 +284,8 @@ --default-icon-color: var(--color-text-secondary); --default-bg-color: transparent; --hover-icon-color: var(--color-text-primary); - --hover-bg-color: var(--color-bg-brand-softer); - --focus-outline-color: var(--color-text-brand); + --hover-bg-color: var(--color-bg-brand-softest); + --focus-outline-color: var(--color-border-brand); display: inline-flex; color: var(--default-icon-color); @@ -364,8 +364,8 @@ &.copied { color: var(--color-text-success); transition: none; - background-color: var(--color-bg-success-softer); - border-color: var(--color-border-on-bg-brand-softer); + background-color: var(--color-bg-success-softest); + border-color: var(--color-border-success-soft); } } @@ -537,21 +537,21 @@ body > [data-popper-placement] { flex-direction: column; flex: 0 1 auto; border-radius: 4px; - border: 1px solid var(--color-border-on-bg-secondary); + border: 1px solid var(--color-border-primary); transition: border-color 300ms linear; position: relative; background: var(--color-bg-secondary); &.active { transition: none; - border-color: var(--color-text-brand); + border-color: var(--color-border-brand); } } &__warning { color: var(--color-text-primary); - background: var(--color-bg-warning-softer); - border: 1px solid var(--color-border-on-bg-warning-softer); + background: var(--color-bg-warning-softest); + border: 1px solid var(--color-border-warning-soft); padding: 8px 10px; border-radius: 4px; font-size: 13px; @@ -619,7 +619,7 @@ body > [data-popper-placement] { .spoiler-input__input { padding: 12px 12px - 5px; - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); color: var(--color-text-brand); } @@ -885,7 +885,7 @@ body > [data-popper-placement] { line-height: 20px; letter-spacing: 0.1px; color: var(--color-text-brand); - background-color: var(--color-bg-secondary-solid); + background-color: var(--color-bg-secondary); white-space: nowrap; text-overflow: ellipsis; overflow: hidden; @@ -917,7 +917,7 @@ body > [data-popper-placement] { gap: 4px; color: var(--color-text-brand); background: transparent; - border: 1px solid var(--color-text-brand); + border: 1px solid var(--color-border-brand); border-radius: 6px; padding: 4px 8px; font-size: 13px; @@ -1464,9 +1464,9 @@ body > [data-popper-placement] { .focusable { &:focus-visible { - outline: 2px solid var(--color-text-brand); + outline: 2px solid var(--color-border-brand); outline-offset: -2px; - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); } } @@ -1587,7 +1587,7 @@ body > [data-popper-placement] { content: ''; position: absolute; inset: 0; - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); opacity: 0; animation: fade 0.7s reverse both 0.3s; pointer-events: none; @@ -1739,7 +1739,7 @@ body > [data-popper-placement] { .notification-ungrouped--direct, .notification-group--direct, .notification-group--annual-report { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); &:focus { background: var(--color-bg-brand-soft); @@ -1877,7 +1877,7 @@ body > [data-popper-placement] { .detailed-status__wrapper-direct { .detailed-status, .detailed-status__action-bar { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); } .status__prepend { @@ -1932,7 +1932,7 @@ body > [data-popper-placement] { line-height: 20px; letter-spacing: 0.25px; color: var(--color-text-secondary); - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); border-radius: 8px; cursor: default; } @@ -2060,7 +2060,7 @@ body > [data-popper-placement] { &__domain-pill { display: inline-flex; - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); border-radius: 4px; border: 0; color: var(--color-text-brand); @@ -2121,7 +2121,7 @@ body > [data-popper-placement] { &__handle { border: 2px dashed var(--color-border-on-brand-softer); - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); padding: 12px 8px; color: var(--color-text-brand); border-radius: 4px; @@ -2903,11 +2903,15 @@ a.account__display-name { &:focus, &:hover, &:active { - &:not(:disabled, [aria-disabled='true']) { - background: var(--color-bg-secondary); + &:where(:not(:disabled, [aria-disabled='true'])) { + background: var(--color-bg-brand-softest); outline: 0; } } + + &:focus-visible { + outline: var(--outline-focus-default); + } } button:disabled, @@ -3072,7 +3076,7 @@ a.account__display-name { } &:focus-visible { - border-top-color: var(--color-text-brand); + border-top-color: var(--color-border-brand); border-radius: 0; } } @@ -3087,7 +3091,7 @@ a.account__display-name { border-top: 0; @media screen and (min-width: $no-gap-breakpoint) { - border-top: 10px solid var(--color-bg-ambient); + border-top: 10px solid var(--color-bg-primary); } } @@ -3586,7 +3590,7 @@ a.account__display-name { &.focused { transition: none; outline: 0; - border-color: var(--color-text-brand); + border-color: var(--color-border-brand); } &.copied { @@ -3986,25 +3990,14 @@ a.account__display-name { height: 20px; padding: 0; border-radius: 10px; - background-color: rgb(from var(--color-bg-brand-softer) r g b / 50%); - border: 1px solid rgb(from var(--color-text-brand) r g b / 50%); + background-color: var(--color-bg-tertiary); + border: 1px solid var(--color-border-primary); box-sizing: border-box; - .react-toggle:hover:not(.react-toggle--disabled) & { - background-color: rgb( - from var(--color-bg-brand-softer) r g b / - calc(50% + var(--overlay-strength-brand)) - ); - } - .react-toggle--checked & { background-color: var(--color-bg-brand-base); border-color: var(--color-bg-brand-base); } - - .react-toggle--checked:not(.react-toggle--disabled):hover & { - background-color: var(--color-bg-brand-base-hover); - } } .react-toggle-track-check, @@ -4020,6 +4013,7 @@ a.account__display-name { height: 16px; border-radius: 50%; background-color: var(--color-text-on-brand-base); + box-shadow: 0 2px 4px 0 color-mix(var(--color-black), transparent 75%); box-sizing: border-box; transition: all 0.25s ease; transition-property: border-color, left; @@ -4081,8 +4075,8 @@ a.account__display-name { &:focus-visible { outline: none; - border-color: var(--color-text-brand); - background: var(--color-bg-brand-softer); + border-color: var(--color-border-brand); + background: var(--color-bg-brand-softest); } &--logo { @@ -4105,11 +4099,10 @@ a.account__display-name { .column-subheading { background: var(--color-bg-secondary); color: var(--color-text-secondary); - padding: 8px 20px; - font-size: 12px; + padding: 12px 24px; + font-size: 13px; font-weight: 500; text-transform: uppercase; - cursor: default; } .getting-started__wrapper { @@ -4457,7 +4450,7 @@ a.status-card { } &:focus-visible { - outline: 2px solid var(--color-text-brand); + outline: var(--outline-focus-default); outline-offset: -2px; } } @@ -4545,7 +4538,7 @@ a.status-card { z-index: 1; &.active { - box-shadow: 0 1px 0 var(--color-bg-brand-softer); + box-shadow: 0 1px 0 var(--color-bg-brand-softest); &::before { display: block; @@ -4655,7 +4648,7 @@ a.status-card { &.active { .column-header__icon { color: var(--color-text-brand); - text-shadow: 0 0 10px var(--color-bg-brand-softer); + text-shadow: 0 0 10px var(--color-bg-brand-softest); } } @@ -5150,7 +5143,7 @@ a.status-card { &:hover, &:active, &:focus { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); color: color-mix( in oklab, var(--color-text-primary), @@ -5752,7 +5745,7 @@ a.status-card { .visibility-modal { &__quote-warning { color: var(--color-text-primary); - background: var(--color-bg-warning-softer); + background: var(--color-bg-warning-softest); padding: 16px; border-radius: 4px; @@ -5787,7 +5780,7 @@ a.status-card { display: flex; align-items: center; color: var(--color-text-primary); - background: var(--color-bg-secondary-solid); + background: var(--color-bg-secondary); border: 1px solid var(--color-border-primary); padding: 8px 12px; width: 100%; @@ -5848,7 +5841,8 @@ a.status-card { .icon-button { padding: 0; - color: var(--color-text-secondary); + + --default-icon-color: inherit; } .icon { @@ -5901,7 +5895,10 @@ a.status-card { background: var(--color-bg-brand-base); .icon-button { - color: inherit; + --default-icon-color: inherit; + --default-bg-color: transparent; + --hover-icon-color: inherit; + --hover-bg-color: var(--color-bg-brand-base-hover); } } @@ -6000,7 +5997,7 @@ a.status-card { } &:focus-visible { - box-shadow: 0 0 0 2px var(--color-text-brand); + box-shadow: 0 0 0 2px var(--color-border-brand); } &[aria-hidden='true'] { @@ -6254,9 +6251,7 @@ a.status-card { --default-icon-color: var(--color-text-on-media); --default-bg-color: transparent; --hover-icon-color: var(--color-text-on-media); - --hover-bg-color: rgb( - from var(--color-text-on-media) r g b / var(--overlay-strength-brand) - ); + --hover-bg-color: rgb(from var(--color-text-on-media) r g b / 10%); .icon { filter: var(--overlay-icon-shadow); @@ -6272,8 +6267,7 @@ a.status-card { --default-icon-color: var(--color-text-favourite-highlight); --hover-icon-color: var(--color-text-favourite-highlight); --hover-bg-color: rgb( - from var(--color-text-favourite-highlight) r g b / - var(--overlay-strength-brand) + from var(--color-text-favourite-highlight) r g b / 10% ); } @@ -6918,7 +6912,7 @@ a.status-card { } .button.button-secondary { - border-color: var(--color-text-error); + border-color: var(--color-border-error); color: var(--color-text-error); flex: 0 0 auto; @@ -6986,7 +6980,7 @@ a.status-card { &:hover, &:active, &:focus { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); } } } @@ -7431,7 +7425,7 @@ a.status-card { inset: 2px; z-index: 1; border-radius: inherit; - border: 2px solid var(--color-text-on-inverted); + border: 2px solid var(--color-text-inverted); outline: 2px solid var(--color-bg-inverted); pointer-events: none; } @@ -8150,7 +8144,7 @@ a.status-card { &.checked, &.indeterminate { - border-color: var(--color-text-brand); + border-color: var(--color-border-brand); } .icon { @@ -8691,7 +8685,7 @@ noscript { } &:focus { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); } } } @@ -8953,7 +8947,7 @@ noscript { } &__root { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); font-size: 13px; display: flex; align-items: flex-end; @@ -9037,13 +9031,13 @@ noscript { &__item { flex-shrink: 0; - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); color: color-mix( in oklab, var(--color-text-primary), var(--color-text-secondary) ); - border: 1px solid var(--color-border-on-bg-brand-softer); + border: 1px solid var(--color-border-brand-soft); border-radius: 3px; box-sizing: border-box; margin: 2px; @@ -9092,8 +9086,8 @@ noscript { &.active { color: var(--color-text-brand); - background-color: var(--color-bg-brand-softer); - border-color: var(--color-text-brand); + background-color: var(--color-bg-brand-softest); + border-color: var(--color-border-brand); transition: all 100ms ease-in; transition-property: background-color, color; } @@ -9156,7 +9150,7 @@ noscript { inset-inline-start: 0; width: 100%; height: 100%; - border-inline-start: 4px solid var(--color-text-brand); + border-inline-start: 4px solid var(--color-border-brand); pointer-events: none; } } @@ -9679,7 +9673,7 @@ noscript { } &.invalid &__input { - border-color: var(--color-text-error); + border-color: var(--color-border-error); } &.expanded .search__popout { @@ -9950,8 +9944,8 @@ noscript { margin: 10px; margin-bottom: 5px; border-radius: 8px; - border: 1px solid var(--color-border-on-bg-brand-softer); - background: var(--color-bg-brand-softer); + border: 1px solid var(--color-border-brand-soft); + background: var(--color-bg-brand-softest); overflow: hidden; flex-shrink: 0; @@ -10017,8 +10011,8 @@ noscript { } .warning-banner { - border: 1px solid var(--color-border-on-bg-error-softer); - background: var(--color-bg-error-softer); + border: 1px solid var(--color-border-error-soft); + background: var(--color-bg-error-softest); &__message { h1 { @@ -10354,7 +10348,7 @@ noscript { width: auto; padding: 15px; margin: 0; - color: var(--color-text-on-inverted); + color: var(--color-text-inverted); background: var(--color-bg-inverted); backdrop-filter: blur(8px); border-radius: 8px; @@ -10408,7 +10402,7 @@ noscript { &:hover, &:focus, &:active { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); } } @@ -10510,13 +10504,16 @@ noscript { color: inherit; text-decoration: none; padding: 4px 12px; - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); border-radius: 4px; font-weight: 500; &:hover, &:focus, - &:active { + &:active, + .focusable:focus-visible &, + .detailed-status__wrapper-direct .detailed-status &, + .status__wrapper-direct & { background: var(--color-bg-brand-soft); } } @@ -10536,10 +10533,10 @@ noscript { padding: 16px 0; padding-bottom: 0; border-bottom: 1px solid var(--color-border-primary); - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); &.focusable:focus-visible { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); } &__header { @@ -10681,6 +10678,7 @@ noscript { &__source { display: inline-flex; align-items: center; + max-width: 100%; color: var(--color-text-tertiary); gap: 4px; overflow: hidden; @@ -10901,12 +10899,6 @@ noscript { color: var(--color-text-secondary); } - & > span { - display: flex; - align-items: center; - gap: 8px; - } - a { display: inline-flex; align-items: center; @@ -11201,7 +11193,7 @@ noscript { inset-inline-start: 0; width: 100%; height: 100%; - border-inline-start: 4px solid var(--color-text-brand); + border-inline-start: 4px solid var(--color-border-brand); pointer-events: none; } } @@ -11398,8 +11390,8 @@ noscript { display: block; box-sizing: border-box; color: var(--color-text-primary); - background: var(--color-bg-brand-softer); - border: 1px solid var(--color-border-on-bg-brand-softer); + background: var(--color-bg-brand-softest); + border: 1px solid var(--color-border-brand-soft); border-radius: 8px; padding: 8px (5px + 8px); position: relative; diff --git a/app/javascript/styles/mastodon/dashboard.scss b/app/javascript/styles/mastodon/dashboard.scss index db3f0e8a84..014021394b 100644 --- a/app/javascript/styles/mastodon/dashboard.scss +++ b/app/javascript/styles/mastodon/dashboard.scss @@ -30,7 +30,7 @@ &:hover, &:focus, &:active { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); } } } @@ -100,12 +100,12 @@ } &.positive { - background: var(--color-bg-success-softer); + background: var(--color-bg-success-softest); color: var(--color-text-success); } &.negative { - background: var(--color-bg-error-softer); + background: var(--color-bg-error-softest); color: var(--color-text-error); } diff --git a/app/javascript/styles/mastodon/emoji_picker.scss b/app/javascript/styles/mastodon/emoji_picker.scss index ad2f2f630d..2d1e03d7b9 100644 --- a/app/javascript/styles/mastodon/emoji_picker.scss +++ b/app/javascript/styles/mastodon/emoji_picker.scss @@ -168,7 +168,7 @@ inset-inline-start: 0; width: 100%; height: 100%; - background-color: var(--color-bg-brand-softer); + background-color: var(--color-bg-brand-softest); border-radius: 100%; } } diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index cc6827db4c..7d2af8ddc1 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -32,7 +32,7 @@ code { display: block; background: linear-gradient( to bottom, - var(--color-bg-secondary-solid), + var(--color-bg-secondary), transparent ); position: absolute; @@ -573,7 +573,7 @@ code { input[type='datetime-local'] { &:focus:user-invalid:not(:placeholder-shown), &:required:user-invalid:not(:placeholder-shown) { - border-color: var(--color-text-error); + border-color: var(--color-border-error); } } @@ -763,7 +763,7 @@ code { input[type='datetime-local'], textarea, select { - border-color: var(--color-text-error); + border-color: var(--color-border-error); } } @@ -801,27 +801,27 @@ code { .flash-message { color: var(--color-text-brand); background: transparent; - border: 1px solid var(--color-text-brand); + border: 1px solid var(--color-border-brand); border-radius: 4px; padding: 15px 10px; margin-bottom: 30px; text-align: center; &.notice { - border: 1px solid var(--color-border-on-bg-success-softer); - background: var(--color-bg-success-softer); + border: 1px solid var(--color-border-success-soft); + background: var(--color-bg-success-softest); color: var(--color-text-success); } &.warning { - border: 1px solid var(--color-border-on-bg-warning-softer); - background: var(--color-bg-warning-softer); + border: 1px solid var(--color-border-warning-soft); + background: var(--color-bg-warning-softest); color: var(--color-text-warning); } &.alert { - border: 1px solid var(--color-border-on-bg-error-softer); - background: var(--color-bg-error-softer); + border: 1px solid var(--color-border-error-soft); + background: var(--color-bg-error-softest); color: var(--color-text-error); } @@ -887,7 +887,7 @@ code { } &:focus { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); } } @@ -1373,7 +1373,7 @@ code { cursor: pointer; &:hover { - background-color: var(--color-bg-brand-softer); + background-color: var(--color-bg-brand-softest); } img { @@ -1404,7 +1404,7 @@ code { } &.invalid img { - outline: 1px solid var(--color-text-error); + outline: 1px solid var(--color-border-error); outline-offset: -1px; } @@ -1414,7 +1414,7 @@ code { width: 100%; height: 100%; position: absolute; - background: var(--color-bg-error-softer); + background: var(--color-bg-error-softest); z-index: 2; border-radius: 8px; } diff --git a/app/javascript/styles/mastodon/modal.scss b/app/javascript/styles/mastodon/modal.scss index 6af2a182b6..4ffbd1d7bb 100644 --- a/app/javascript/styles/mastodon/modal.scss +++ b/app/javascript/styles/mastodon/modal.scss @@ -1,7 +1,7 @@ @use 'variables' as *; .modal-layout { - background: var(--color-bg-brand-softer); + background: var(--color-bg-brand-softest); display: flex; flex-direction: column; height: 100vh; @@ -42,7 +42,7 @@ position: absolute; inset: auto 0 0; height: 32px; - background-color: var(--color-bg-brand-softer); + background-color: var(--color-bg-brand-softest); /* Decorative zig-zag pattern at the bottom of the page */ mask-image: url('data:image/svg+xml;utf8,'); diff --git a/app/javascript/styles/mastodon/polls.scss b/app/javascript/styles/mastodon/polls.scss index 19fb8dd505..ce7f51f8cd 100644 --- a/app/javascript/styles/mastodon/polls.scss +++ b/app/javascript/styles/mastodon/polls.scss @@ -86,7 +86,7 @@ padding: 8px 12px; &:focus { - border-color: var(--color-text-brand); + border-color: var(--color-border-brand); } @media screen and (width <= 600px) { diff --git a/app/javascript/styles/mastodon/tables.scss b/app/javascript/styles/mastodon/tables.scss index 8e303aff68..1088781417 100644 --- a/app/javascript/styles/mastodon/tables.scss +++ b/app/javascript/styles/mastodon/tables.scss @@ -93,6 +93,26 @@ } } + &.mini-table { + border-top: 1px solid var(--color-border-primary); + width: 50%; + + & > tbody > tr > th, + & > tbody > tr > td { + padding: 12px 0; + } + + & > tbody > tr > th { + color: var(--color-text-secondary); + font-weight: 400; + } + + & > tbody > tr > td { + color: var(--color-text-primary); + font-weight: 600; + } + } + &.batch-table { & > thead > tr > th { background: var(--color-bg-primary); diff --git a/app/javascript/styles/mastodon/theme/_base.scss b/app/javascript/styles/mastodon/theme/_base.scss index 85fd0dab45..9b39f1b02e 100644 --- a/app/javascript/styles/mastodon/theme/_base.scss +++ b/app/javascript/styles/mastodon/theme/_base.scss @@ -1,40 +1,69 @@ @mixin palette { - --color-black: #000; - --color-grey-950: #181820; - --color-grey-800: #3a3a50; - --color-grey-700: #44445f; - --color-grey-600: #535374; - --color-grey-500: #67678e; - --color-grey-400: #88a; - --color-grey-300: #b2b1c8; - --color-grey-200: #d7d6e1; - --color-grey-100: #eeedf3; - --color-grey-50: #f6f6f9; --color-white: #fff; - --color-indigo-700: #5638cc; - --color-indigo-600: #6147e6; - --color-indigo-400: #8280f9; - --color-indigo-300: #a5abfd; - --color-indigo-200: #c8cdfe; - --color-indigo-100: #e0e3ff; + --color-black: #000; + + // Grey + --color-grey-50: #f6f6f9; + --color-grey-100: #eeedf3; + --color-grey-200: #d7d6e1; + --color-grey-300: #b2b1c8; + --color-grey-400: #88a; + --color-grey-500: #67678e; + --color-grey-600: #535374; + --color-grey-700: #44445f; + --color-grey-800: #3a3a50; + --color-grey-900: #21212c; + --color-grey-950: #181820; + + // Indigo --color-indigo-50: #f0f1ff; + --color-indigo-100: #e0e3ff; + --color-indigo-200: #c8cdfe; + --color-indigo-300: #a5abfd; + --color-indigo-400: #8280f9; + --color-indigo-500: #7263f2; + --color-indigo-600: #6147e6; + --color-indigo-700: #5638cc; + --color-indigo-800: #48359c; + --color-indigo-900: #3d317c; + --color-indigo-950: #261e48; + + // Red --color-red-50: #fef2f2; --color-red-100: #ffe2e2; + --color-red-200: #ffc9c9; --color-red-300: #ffa2a2; + --color-red-400: #ff6467; + --color-red-500: #fb2c36; + --color-red-600: #e7000b; + --color-red-700: #c10007; --color-red-800: #9f0712; --color-red-900: #82181a; --color-red-950: #460809; + + // Yellow --color-yellow-50: #fffbeb; --color-yellow-100: #fef3c6; + --color-yellow-200: #fee685; + --color-yellow-300: #ffd230; --color-yellow-400: #ffb900; + --color-yellow-500: #fe9a00; --color-yellow-600: #e17100; --color-yellow-700: #bb4d00; + --color-yellow-800: #973c00; --color-yellow-900: #7b3306; --color-yellow-950: #461901; + + // Green --color-green-50: #f0fdf4; --color-green-100: #dcfce7; + --color-green-200: #b9f8cf; + --color-green-300: #7bf1a8; --color-green-400: #05df72; + --color-green-500: #00c950; --color-green-600: #00a63e; + --color-green-700: #008236; + --color-green-800: #016630; --color-green-900: #0d542b; --color-green-950: #032e15; } diff --git a/app/javascript/styles/mastodon/theme/_dark.scss b/app/javascript/styles/mastodon/theme/_dark.scss index a22c7cc8f4..161524cdc4 100644 --- a/app/javascript/styles/mastodon/theme/_dark.scss +++ b/app/javascript/styles/mastodon/theme/_dark.scss @@ -5,150 +5,92 @@ --color-text-primary: var(--color-grey-100); --color-text-secondary: var(--color-grey-300); - --color-text-tertiary: var(--color-grey-400); - --color-text-on-inverted: var(--color-grey-950); + --color-text-tertiary: var(--color-grey-400); // legacy + --color-text-inverted: var(--color-grey-950); --color-text-brand: var(--color-indigo-300); --color-text-brand-soft: color-mix( in oklab, var(--color-text-primary), var(--color-text-brand) - ); + ); // legacy + --color-text-on-brand-base: var(--color-white); - --color-text-brand-on-inverted: var(--color-indigo-600); + --color-text-brand-on-inverted: var(--color-indigo-600); // legacy --color-text-error: var(--color-red-300); --color-text-on-error-base: var(--color-white); - --color-text-warning: var(--color-yellow-400); + --color-text-warning: var(--color-yellow-400); // legacy --color-text-on-warning-base: var(--color-white); - --color-text-success: var(--color-green-400); + --color-text-success: var(--color-green-400); // legacy --color-text-on-success-base: var(--color-white); - --color-text-disabled: var(--color-grey-600); - --color-text-on-disabled: var(--color-grey-400); - --color-text-bookmark-highlight: var(--color-text-error); - --color-text-favourite-highlight: var(--color-text-warning); - --color-text-on-media: var(--color-white); + --color-text-disabled: var(--color-grey-600); // legacy + --color-text-on-disabled: var(--color-grey-400); // legacy + --color-text-bookmark-highlight: var(--color-text-error); // legacy + --color-text-favourite-highlight: var(--color-text-warning); // legacy + --color-text-on-media: var(--color-white); // legacy --color-text-status-links: color-mix( in oklab, var(--color-text-primary), var(--color-text-secondary) - ); + ); // legacy /* BACKGROUND TOKENS */ // Neutrals --color-bg-primary: var(--color-grey-950); --overlay-strength-secondary: 4%; - --color-bg-secondary-base: var(--color-white); - --color-bg-secondary: #{utils.css-alpha( - var(--color-bg-secondary-base), - var(--overlay-strength-secondary) - )}; - --color-bg-secondary-solid: color-mix( - in srgb, - var(--color-bg-primary), - var(--color-bg-secondary-base) var(--overlay-strength-secondary) - ); - --color-bg-tertiary: color-mix( - in oklab, - var(--color-bg-primary), - var(--color-bg-secondary-base) calc(2 * var(--overlay-strength-secondary)) - ); + --color-bg-secondary: var(--color-grey-900); + --color-bg-tertiary: var(--color-grey-800); // legacy // Utility - --color-bg-ambient: var(--color-bg-primary); --color-bg-inverted: var(--color-grey-50); - --color-bg-media-base: var(--color-black); - --color-bg-media-strength: 65%; - --color-bg-media: #{utils.css-alpha( - var(--color-bg-media-base), - var(--color-bg-media-strength) - )}; - --color-bg-overlay: var(--color-black); - --color-bg-disabled: var(--color-grey-700); + --color-bg-overlay-base: #{utils.css-alpha(var(--color-grey-950), 60%)}; + --color-bg-overlay-highlight: #{utils.css-alpha(var(--color-white), 5%)}; + --color-bg-overlay: var(--color-black); // legacy + --color-bg-media-base: var(--color-black); // legacy + --color-bg-media: #{utils.css-alpha(var(--color-bg-media-base), 65%)}; // legacy + --color-bg-disabled: var(--color-grey-700); // legacy // Brand - --overlay-strength-brand: 22%; --color-bg-brand-base: var(--color-indigo-700); - --color-bg-brand-base-hover: color-mix( - in oklab, - var(--color-bg-brand-base), - var(--color-bg-primary) var(--overlay-strength-brand) - ); - --color-bg-brand-soft: #{utils.css-alpha( - #6f4df5, - calc(var(--overlay-strength-brand) * 2) - )}; - --color-bg-brand-softer: #{utils.css-alpha( - var(--color-bg-brand-base), - var(--overlay-strength-brand) - )}; - --color-bg-brand-softer-solid: color-mix( - in srgb, - var(--color-bg-primary), - var(--color-bg-brand-base) var(--overlay-strength-brand) - ); + --color-bg-brand-base-hover: var(--color-indigo-800); // legacy + --color-bg-brand-soft: var(--color-indigo-900); + --color-bg-brand-softest: var(--color-indigo-950); // Error - --overlay-strength-error: 10%; - --color-bg-error-base: var(--color-red-800); - --color-bg-error-base-hover: color-mix( - in oklab, - var(--color-bg-error-base), - var(--color-bg-primary) var(--overlay-strength-error) - ); + --color-bg-error-base: var(--color-red-700); + --color-bg-error-base-hover: var(--color-red-800); // legacy --color-bg-error-soft: var(--color-red-900); - --color-bg-error-softer: var(--color-red-950); + --color-bg-error-softest: var(--color-red-950); // Warning - --overlay-strength-warning: 10%; - --color-bg-warning-base: var(--color-yellow-700); - --color-bg-warning-base-hover: color-mix( - in oklab, - var(--color-bg-warning-base), - var(--color-bg-primary) var(--overlay-strength-warning) - ); + --color-bg-warning-base: var(--color-yellow-700); // legacy --color-bg-warning-soft: var(--color-yellow-900); - --color-bg-warning-softer: var(--color-yellow-950); + --color-bg-warning-softest: var(--color-yellow-950); // Success - --overlay-strength-success: 15%; - --color-bg-success-base: var(--color-green-600); - --color-bg-success-base-hover: color-mix( - in oklab, - var(--color-bg-success-base), - var(--color-bg-primary) var(--overlay-strength-success) - ); + --color-bg-success-base: var(--color-green-600); // legacy --color-bg-success-soft: var(--color-green-900); - --color-bg-success-softer: var(--color-green-950); + --color-bg-success-softest: var(--color-green-950); /* BORDER TOKENS */ - --border-strength-primary: 18%; - --color-border-primary: #{utils.css-alpha( - var(--color-indigo-200), - var(--border-strength-primary) - )}; - --color-border-media: rgb(252 248 255 / 15%); - --color-border-verified: rgb(220, 3, 240); - --color-border-on-bg-secondary: #{utils.css-alpha( - var(--color-indigo-200), - calc(var(--border-strength-primary) / 1.5) - )}; - --color-border-on-bg-brand-softer: var(--color-border-primary); - --color-border-on-bg-error-softer: #{utils.css-alpha( - var(--color-text-error), - 50% - )}; - --color-border-on-bg-warning-softer: #{utils.css-alpha( + --color-border-primary: var(--color-grey-800); + --color-border-brand: var(--color-text-brand); + --color-border-brand-soft: var(--color-indigo-800); + --color-border-error: var(--color-red-300); + --color-border-media: rgb(252 248 255 / 15%); // legacy + --color-border-error-soft: #{utils.css-alpha(var(--color-border-error), 50%)}; // legacy + --color-border-warning-soft: #{utils.css-alpha( var(--color-text-warning), 50% - )}; - --color-border-on-bg-success-softer: #{utils.css-alpha( + )}; // legacy + + --color-border-success-soft: #{utils.css-alpha( var(--color-text-success), 50% - )}; - --color-border-on-bg-inverted: var(--color-border-primary); + )}; // legacy - /* SHADOW TOKENS */ + /* SHADOW TOKENS (LEGACY) */ --shadow-strength-primary: 80%; --color-shadow-primary: #{utils.css-alpha( @@ -160,16 +102,16 @@ 0 8px 10px -6px var(--color-shadow-primary); --overlay-icon-shadow: drop-shadow(0 0 8px var(--color-shadow-primary)); - /* GRAPHS/CHARTS TOKENS */ + /* GRAPHS/CHARTS TOKENS (LEGACY) */ --color-graph-primary-stroke: var(--color-text-brand); - --color-graph-primary-fill: var(--color-bg-brand-softer); + --color-graph-primary-fill: var(--color-bg-brand-softest); --color-graph-warning-stroke: var(--color-text-warning); - --color-graph-warning-fill: var(--color-bg-warning-softer); + --color-graph-warning-fill: var(--color-bg-warning-softest); --color-graph-disabled-stroke: var(--color-text-disabled); --color-graph-disabled-fill: var(--color-bg-disabled); - /* LEGACY TOKENS */ + /* RICH TEXT TOKENS (LEGACY) */ --rich-text-container-color: rgb(87 24 60 / 100%); --rich-text-text-color: rgb(255 175 212 / 100%); @@ -182,7 +124,9 @@ --color-text-primary: var(--color-grey-50); --color-text-status-links: var(--color-text-brand); - /* BORDER TOKENS */ + /* BACKGROUND TOKENS */ + --color-bg-error-base: var(--color-red-800); - --border-strength-primary: 30%; + /* BORDER TOKENS */ + --color-border-primary: var(--color-grey-600); } diff --git a/app/javascript/styles/mastodon/theme/_light.scss b/app/javascript/styles/mastodon/theme/_light.scss index 47d32320fa..5759fffd75 100644 --- a/app/javascript/styles/mastodon/theme/_light.scss +++ b/app/javascript/styles/mastodon/theme/_light.scss @@ -5,145 +5,88 @@ --color-text-primary: var(--color-grey-950); --color-text-secondary: var(--color-grey-600); - --color-text-tertiary: var(--color-grey-500); - --color-text-on-inverted: var(--color-white); + --color-text-tertiary: var(--color-grey-500); // legacy + --color-text-inverted: var(--color-white); --color-text-brand: var(--color-indigo-700); --color-text-brand-soft: color-mix( in oklab, var(--color-text-primary), var(--color-text-brand) - ); + ); // legacy + --color-text-on-brand-base: var(--color-white); - --color-text-brand-on-inverted: var(--color-indigo-400); + --color-text-brand-on-inverted: var(--color-indigo-400); // legacy --color-text-error: var(--color-red-800); --color-text-on-error-base: var(--color-white); - --color-text-warning: var(--color-yellow-600); + --color-text-warning: var(--color-yellow-600); // legacy --color-text-on-warning-base: var(--color-white); - --color-text-success: var(--color-green-600); + --color-text-success: var(--color-green-600); // legacy --color-text-on-success-base: var(--color-white); - --color-text-disabled: var(--color-grey-300); - --color-text-on-disabled: var(--color-grey-200); - --color-text-bookmark-highlight: var(--color-text-error); - --color-text-favourite-highlight: var(--color-text-warning); - --color-text-on-media: var(--color-white); - --color-text-status-links: var(--color-text-brand); + --color-text-disabled: var(--color-grey-300); // legacy + --color-text-on-disabled: var(--color-grey-200); // legacy + --color-text-bookmark-highlight: var(--color-text-error); // legacy + --color-text-favourite-highlight: var(--color-text-warning); // legacy + --color-text-on-media: var(--color-white); // legacy + --color-text-status-links: var(--color-text-brand); // legacy /* BACKGROUND TOKENS */ // Neutrals --color-bg-primary: var(--color-white); --overlay-strength-secondary: 4%; - --color-bg-secondary-base: #000550; - --color-bg-secondary: #{color-mix( - in oklab, - var(--color-bg-primary), - var(--color-bg-secondary-base) var(--overlay-strength-secondary) - )}; - --color-bg-secondary-solid: #{color-mix( - in srgb, - var(--color-bg-primary), - var(--color-bg-secondary-base) var(--overlay-strength-secondary) - )}; - --color-bg-tertiary: #{color-mix( - in oklab, - var(--color-bg-primary), - var(--color-bg-secondary-base) calc(2 * var(--overlay-strength-secondary)) - )}; + --color-bg-secondary: var(--color-grey-50); + --color-bg-tertiary: var(--color-grey-100); // legacy // Utility - --color-bg-ambient: var(--color-bg-primary); --color-bg-inverted: var(--color-grey-950); - --color-bg-media-base: var(--color-black); - --color-bg-media-strength: 65%; - --color-bg-media: #{utils.css-alpha( - var(--color-bg-media-base), - var(--color-bg-media-strength) - )}; - --color-bg-overlay: var(--color-bg-primary); - --color-bg-disabled: var(--color-grey-400); + --color-bg-overlay-base: #{utils.css-alpha(var(--color-grey-950), 60%)}; + --color-bg-overlay-highlight: #{utils.css-alpha(var(--color-grey-950), 5%)}; + --color-bg-overlay: var(--color-bg-primary); // legacy + --color-bg-media-base: var(--color-black); // legacy + --color-bg-media: #{utils.css-alpha(var(--color-bg-media-base), 65%)}; // legacy + --color-bg-disabled: var(--color-grey-400); // legacy // Brand - --overlay-strength-brand: 6%; --color-bg-brand-base: var(--color-indigo-700); - --color-bg-brand-base-hover: color-mix( - in oklab, - var(--color-bg-brand-base), - black var(--overlay-strength-brand) - ); - --color-bg-brand-soft: #{utils.css-alpha( - #0012d8, - calc(var(--overlay-strength-brand) * 2) - )}; - --color-bg-brand-softer: #{utils.css-alpha( - #0012d8, - var(--overlay-strength-brand) - )}; - --color-bg-brand-softer-solid: color-mix( - in srgb, - var(--color-bg-primary), - var(--color-bg-brand-base) var(--overlay-strength-brand) - ); + --color-bg-brand-base-hover: var(--color-indigo-800); // legacy + --color-bg-brand-soft: var(--color-indigo-100); + --color-bg-brand-softest: var(--color-indigo-50); // Error - --overlay-strength-error: 5%; - --color-bg-error-base: var(--color-red-800); - --color-bg-error-base-hover: color-mix( - in oklab, - var(--color-bg-error-base), - black var(--overlay-strength-error) - ); + --color-bg-error-base: var(--color-red-700); + --color-bg-error-base-hover: var(--color-red-800); // legacy --color-bg-error-soft: var(--color-red-100); - --color-bg-error-softer: var(--color-red-50); + --color-bg-error-softest: var(--color-red-50); // Warning - --overlay-strength-warning: 10%; - --color-bg-warning-base: var(--color-yellow-700); - --color-bg-warning-base-hover: color-mix( - in oklab, - var(--color-bg-warning-base), - black var(--overlay-strength-warning) - ); + --color-bg-warning-base: var(--color-yellow-700); // legacy --color-bg-warning-soft: var(--color-yellow-100); - --color-bg-warning-softer: var(--color-yellow-50); + --color-bg-warning-softest: var(--color-yellow-50); // Success - --overlay-strength-success: 15%; - --color-bg-success-base: var(--color-green-600); - --color-bg-success-base-hover: color-mix( - in oklab, - var(--color-bg-success-base), - black var(--overlay-strength-success) - ); + --color-bg-success-base: var(--color-green-600); // legacy --color-bg-success-soft: var(--color-green-100); - --color-bg-success-softer: var(--color-green-50); + --color-bg-success-softest: var(--color-green-50); /* BORDER TOKENS */ - --border-strength-primary: 15%; - --color-border-primary: color-mix( - in oklab, - var(--color-bg-primary), - var(--color-grey-950) var(--border-strength-primary) - ); - --color-border-media: rgb(252 248 255 / 15%); - --color-border-verified: rgb(220, 3, 240); - --color-border-on-bg-secondary: var(--color-grey-200); - --color-border-on-bg-brand-softer: var(--color-indigo-200); - --color-border-on-bg-error-softer: #{utils.css-alpha( - var(--color-text-error), - 50% - )}; - --color-border-on-bg-warning-softer: #{utils.css-alpha( + --color-border-primary: var(--color-grey-200); + --color-border-brand: var(--color-text-brand); + --color-border-brand-soft: var(--color-indigo-200); + --color-border-error: var(--color-red-700); + --color-border-media: rgb(252 248 255 / 15%); // legacy + --color-border-error-soft: #{utils.css-alpha(var(--color-text-error), 50%)}; // legacy + --color-border-warning-soft: #{utils.css-alpha( var(--color-text-warning), 50% - )}; - --color-border-on-bg-success-softer: #{utils.css-alpha( + )}; // legacy + + --color-border-success-soft: #{utils.css-alpha( var(--color-text-success), 50% - )}; - --color-border-on-bg-inverted: var(--color-border-primary); + )}; // legacy - /* SHADOW TOKENS */ + /* SHADOW TOKENS (LEGACY) */ --shadow-strength-primary: 30%; --color-shadow-primary: #{utils.css-alpha( @@ -155,16 +98,16 @@ 0 8px 10px -6px var(--color-shadow-primary); --overlay-icon-shadow: drop-shadow(0 0 8px var(--color-shadow-primary)); - /* GRAPHS/CHARTS TOKENS */ + /* GRAPHS/CHARTS TOKENS (LEGACY) */ --color-graph-primary-stroke: var(--color-text-brand); - --color-graph-primary-fill: var(--color-bg-brand-softer); + --color-graph-primary-fill: var(--color-bg-brand-softest); --color-graph-warning-stroke: var(--color-text-warning); - --color-graph-warning-fill: var(--color-bg-warning-softer); + --color-graph-warning-fill: var(--color-bg-warning-softest); --color-graph-disabled-stroke: var(--color-text-disabled); --color-graph-disabled-fill: var(--color-bg-disabled); - /* LEGACY TOKENS */ + /* RICH TEXT TOKENS (LEGACY) */ --rich-text-container-color: rgb(255 216 231 / 100%); --rich-text-text-color: rgb(114 47 83 / 100%); @@ -179,8 +122,9 @@ --color-text-tertiary: var(--color-grey-700); --color-text-brand: var(--color-indigo-600); - /* BORDER TOKENS */ + /* BACKGROUND TOKENS */ + --color-bg-error-base: var(--color-red-800); - --border-strength-primary: 30%; - --color-border-on-bg-secondary: var(--color-grey-300); + /* BORDER TOKENS */ + --color-border-primary: var(--color-grey-300); } diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss index 69c79cd1e6..d237a184c9 100644 --- a/app/javascript/styles/mastodon/widgets.scss +++ b/app/javascript/styles/mastodon/widgets.scss @@ -69,7 +69,7 @@ } &.active .avatar-stack .account__avatar { - border-color: var(--color-text-brand); + border-color: var(--color-border-brand); } .trends__item__current { diff --git a/app/lib/activitypub/activity/feature_request.rb b/app/lib/activitypub/activity/feature_request.rb index 180eeb492c..67355f6bda 100644 --- a/app/lib/activitypub/activity/feature_request.rb +++ b/app/lib/activitypub/activity/feature_request.rb @@ -7,7 +7,7 @@ class ActivityPub::Activity::FeatureRequest < ActivityPub::Activity return unless Mastodon::Feature.collections_federation_enabled? return if non_matching_uri_hosts?(@account.uri, @json['id']) - @collection = @account.collections.find_by(uri: value_or_id(@json['instrument'])) + @collection = find_or_fetch_collection @featured_account = ActivityPub::TagManager.instance.uris_to_local_accounts([value_or_id(@json['object'])]).first return if @collection.nil? || @featured_account.nil? @@ -23,8 +23,7 @@ class ActivityPub::Activity::FeatureRequest < ActivityPub::Activity def accept_request! collection_item = @collection.collection_items.create!( - account: @featured_account, - state: :accepted + collection_item_attributes(:accepted) ) queue_delivery!(collection_item, ActivityPub::AcceptFeatureRequestSerializer) @@ -32,13 +31,27 @@ class ActivityPub::Activity::FeatureRequest < ActivityPub::Activity def reject_request! collection_item = @collection.collection_items.build( - account: @featured_account, - state: :rejected + collection_item_attributes(:rejected) ) queue_delivery!(collection_item, ActivityPub::RejectFeatureRequestSerializer) end + def find_or_fetch_collection + uri = value_or_id(@json['instrument']) + collection = @account.collections.find_by(uri:) + return collection if collection.present? + + collection = ActivityPub::FetchRemoteFeaturedCollectionService.new.call(uri) + return collection if collection.present? && collection.account == @account + + nil + end + + def collection_item_attributes(state = :accepted) + { account: @featured_account, activity_uri: @json['id'], state: } + end + def queue_delivery!(collection_item, serializer) json = JSON.generate(serialize_payload(collection_item, serializer)) ActivityPub::DeliveryWorker.perform_async(json, @featured_account.id, @account.inbox_url) diff --git a/app/lib/activitypub/linked_data_signature.rb b/app/lib/activitypub/linked_data_signature.rb index c42313b05e..f6c4eeb90e 100644 --- a/app/lib/activitypub/linked_data_signature.rb +++ b/app/lib/activitypub/linked_data_signature.rb @@ -19,16 +19,15 @@ class ActivityPub::LinkedDataSignature return unless type == 'RsaSignature2017' - creator = ActivityPub::TagManager.instance.uri_to_actor(creator_uri) - creator = ActivityPub::FetchRemoteKeyService.new.call(creator_uri) if creator&.public_key.blank? - - return if creator.nil? + keypair = Keypair.from_keyid(creator_uri) + keypair = ActivityPub::FetchRemoteKeyService.new.call(creator_uri) if keypair&.public_key.blank? + return if keypair.nil? || !keypair.usable? options_hash = hash(@json['signature'].without('type', 'id', 'signatureValue').merge('@context' => CONTEXT)) document_hash = hash(@json.without('signature')) to_be_verified = options_hash + document_hash - creator if creator.keypair.public_key.verify(OpenSSL::Digest.new('SHA256'), Base64.decode64(signature), to_be_verified) + keypair.actor if keypair.keypair.public_key.verify(OpenSSL::Digest.new('SHA256'), Base64.decode64(signature), to_be_verified) rescue OpenSSL::PKey::RSAError false end diff --git a/app/lib/signed_request.rb b/app/lib/signed_request.rb index 1cea2955f5..6fd0772de3 100644 --- a/app/lib/signed_request.rb +++ b/app/lib/signed_request.rb @@ -23,14 +23,14 @@ class SignedRequest %w(rsa-sha256 hs2019).include?(signature_algorithm) end - def verified?(actor) + def verified?(keypair) signature = Base64.decode64(signature_params['signature']) compare_signed_string = build_signed_string(include_query_string: true) - return true unless verify_signature(actor, signature, compare_signed_string).nil? + return true unless verify_signature(keypair, signature, compare_signed_string).nil? compare_signed_string = build_signed_string(include_query_string: false) - return true unless verify_signature(actor, signature, compare_signed_string).nil? + return true unless verify_signature(keypair, signature, compare_signed_string).nil? false end @@ -99,8 +99,8 @@ class SignedRequest signature_params.fetch('headers', signature_algorithm == 'hs2019' ? '(created)' : 'date').downcase.split end - def verify_signature(actor, signature, compare_signed_string) - true if actor.keypair.public_key.verify(OpenSSL::Digest.new('SHA256'), signature, compare_signed_string) + def verify_signature(keypair, signature, compare_signed_string) + true if keypair.keypair.public_key.verify(OpenSSL::Digest.new('SHA256'), signature, compare_signed_string) rescue OpenSSL::PKey::RSAError nil end @@ -170,8 +170,8 @@ class SignedRequest true end - def verified?(actor) - key = Linzer.new_rsa_v1_5_sha256_public_key(actor.public_key) + def verified?(keypair) + key = Linzer.new_rsa_v1_5_sha256_public_key(keypair.public_key) Linzer.verify(key, @message, @signature) rescue Linzer::VerifyError @@ -243,7 +243,7 @@ class SignedRequest end end - def verified?(actor) + def verified?(keypair) missing_signature_parameters = @signature.missing_signature_parameters raise Mastodon::SignatureVerificationError, "Incompatible request signature. #{missing_signature_parameters.to_sentence} are required" if missing_signature_parameters raise Mastodon::SignatureVerificationError, 'Unsupported signature algorithm (only rsa-sha256 and hs2019 are supported)' unless @signature.algorithm_supported? @@ -251,7 +251,7 @@ class SignedRequest @signature.verify_signature_strength! @signature.verify_body_digest! - @signature.verified?(actor) + @signature.verified?(keypair) end private diff --git a/app/lib/webfinger_resource.rb b/app/lib/webfinger_resource.rb index 95de496a6d..2d4c6ab2be 100644 --- a/app/lib/webfinger_resource.rb +++ b/app/lib/webfinger_resource.rb @@ -9,14 +9,14 @@ class WebfingerResource @resource = resource end - def username + def account case resource when %r{\A(https?://)?#{instance_actor_regexp}/?\Z} - Rails.configuration.x.local_domain + Account.representative when /\Ahttps?/i - username_from_url + account_from_url when /@/ - username_from_acct + account_from_acct else raise InvalidRequest end @@ -31,11 +31,11 @@ class WebfingerResource Regexp.union(hosts) end - def username_from_url + def account_from_url if account_show_page? - path_params[:username] + path_params.key?(:username) ? Account.find_local!(path_params[:username]) : Account.local.find(path_params[:id]) elsif instance_actor_page? - Rails.configuration.x.local_domain + Account.representative else raise ActiveRecord::RecordNotFound end @@ -53,10 +53,13 @@ class WebfingerResource Rails.application.routes.recognize_path(resource) end - def username_from_acct + def account_from_acct raise ActiveRecord::RecordNotFound unless domain_matches_local? - local_username + username = local_username + return Account.representative if username == Rails.configuration.x.local_domain || username == Rails.configuration.x.web_domain + + Account.find_local!(username) end def split_acct @@ -76,6 +79,6 @@ class WebfingerResource end def domain_matches_local? - TagManager.instance.local_domain?(local_domain) || TagManager.instance.web_domain?(local_domain) + TagManager.instance.local_domain?(local_domain) || TagManager.instance.web_domain?(local_domain) || Rails.configuration.x.alternate_domains.include?(local_domain) end end diff --git a/app/mailers/email_subscription_mailer.rb b/app/mailers/email_subscription_mailer.rb new file mode 100644 index 0000000000..35bd6da2f9 --- /dev/null +++ b/app/mailers/email_subscription_mailer.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +class EmailSubscriptionMailer < ApplicationMailer + include BulkMailSettingsConcern + include Redisable + + layout 'mailer' + + helper :accounts + helper :routing + helper :statuses + + before_action :set_subscription + before_action :set_unsubscribe_url + before_action :set_instance + before_action :set_skip_preferences_link + + after_action :use_bulk_mail_delivery_settings, except: [:confirmation] + after_action :set_list_headers + + default to: -> { @subscription.email } + + def confirmation + I18n.with_locale(locale) do + mail subject: default_i18n_subject + end + end + + def notification(statuses) + @statuses = statuses + + I18n.with_locale(locale) do + mail subject: default_i18n_subject(count: @statuses.size, name: @subscription.account.display_name, excerpt: @statuses.first.text.truncate(17)) + end + end + + private + + def set_list_headers + headers( + 'List-ID' => "<#{@subscription.account.username}.#{Rails.configuration.x.local_domain}>", + 'List-Unsubscribe-Post' => 'List-Unsubscribe=One-Click', + 'List-Unsubscribe' => "<#{@unsubscribe_url}>" + ) + end + + def set_subscription + @subscription = params[:subscription] + end + + def set_unsubscribe_url + @unsubscribe_url = unsubscribe_url(token: @subscription.to_sgid(for: 'unsubscribe').to_s) + end + + def set_instance + @instance = Rails.configuration.x.local_domain + end + + def set_skip_preferences_link + @skip_preferences_link = true + end + + def locale + @subscription.locale.presence || I18n.default_locale + end +end diff --git a/app/models/account.rb b/app/models/account.rb index 60868b44dc..9a8d3cedfa 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -193,8 +193,10 @@ class Account < ApplicationRecord :role, :locale, :shows_application?, + :email_subscriptions_enabled?, :prefers_noindex?, :time_zone, + :can?, to: :user, prefix: true, allow_nil: true diff --git a/app/models/admin/tag_filter.rb b/app/models/admin/tag_filter.rb index 5e75757b23..a35c77bcad 100644 --- a/app/models/admin/tag_filter.rb +++ b/app/models/admin/tag_filter.rb @@ -32,7 +32,7 @@ class Admin::TagFilter when :status status_scope(value) when :name - Tag.search_for(value.to_s.strip, params[:limit], params[:offset], exclude_unlistable: false) + Tag.search_for(value, params[:limit], params[:offset], exclude_unlistable: false) when :order order_scope(value) else diff --git a/app/models/concerns/account/associations.rb b/app/models/concerns/account/associations.rb index 4b4dfc8879..db2e996d0f 100644 --- a/app/models/concerns/account/associations.rb +++ b/app/models/concerns/account/associations.rb @@ -37,6 +37,8 @@ module Account::Associations has_many :scheduled_statuses has_many :status_pins has_many :statuses + has_many :keypairs + has_many :email_subscriptions has_one :deletion_request, class_name: 'AccountDeletionRequest' has_one :follow_recommendation_suppression diff --git a/app/models/concerns/user/has_settings.rb b/app/models/concerns/user/has_settings.rb index b2f6db5faa..a613e58f85 100644 --- a/app/models/concerns/user/has_settings.rb +++ b/app/models/concerns/user/has_settings.rb @@ -15,6 +15,10 @@ module User::HasSettings settings['noindex'] end + def email_subscriptions_enabled? + settings['email_subscriptions'] + end + def preferred_posting_language valid_locale_cascade(settings['default_language'], locale, I18n.locale) end diff --git a/app/models/email_subscription.rb b/app/models/email_subscription.rb new file mode 100644 index 0000000000..a965f06e31 --- /dev/null +++ b/app/models/email_subscription.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: email_subscriptions +# +# id :bigint(8) not null, primary key +# confirmation_token :string +# confirmed_at :datetime +# email :string not null +# locale :string not null +# created_at :datetime not null +# updated_at :datetime not null +# account_id :bigint(8) not null +# + +class EmailSubscription < ApplicationRecord + belongs_to :account + + normalizes :email, with: ->(str) { str.squish.downcase } + + validates :email, presence: true, email_address: true, uniqueness: { scope: :account_id } + + scope :confirmed, -> { where.not(confirmed_at: nil) } + scope :unconfirmed, -> { where(confirmed_at: nil) } + + before_create :set_confirmation_token + + after_create_commit :send_confirmation_email + + def confirmed? + confirmed_at.present? + end + + def confirm! + touch(:confirmed_at) + end + + private + + def set_confirmation_token + self.confirmation_token = Devise.friendly_token unless confirmed? + end + + def send_confirmation_email + EmailSubscriptionMailer.with(subscription: self).confirmation.deliver_later + end +end diff --git a/app/models/keypair.rb b/app/models/keypair.rb new file mode 100644 index 0000000000..80c313f4df --- /dev/null +++ b/app/models/keypair.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +# == Schema Information +# +# Table name: keypairs +# +# id :bigint(8) not null, primary key +# expires_at :datetime +# private_key :string +# public_key :string not null +# revoked :boolean default(FALSE), not null +# type :integer not null +# uri :string not null +# created_at :datetime not null +# updated_at :datetime not null +# account_id :bigint(8) not null +# + +class Keypair < ApplicationRecord + include Expireable + + self.inheritance_column = nil + + encrypts :private_key + + belongs_to :account + + enum :type, { rsa: 0 } + + attr_accessor :require_private_key + + validates :uri, presence: true, uniqueness: true + validates :public_key, presence: true + validates :private_key, presence: true, if: -> { account.local? } + + # NOTE: this should be true in production, but tests heavily rely on remote accounts having a keypair + validates :private_key, absence: true, if: -> { account.remote? && !require_private_key } + + scope :unexpired, -> { where(expires_at: nil).or(where.not(expires_at: ..Time.now.utc)) } + scope :usable, -> { unexpired.where(revoked: false) } + + alias actor account + + def keypair + @keypair ||= begin + case type + when 'rsa' + OpenSSL::PKey::RSA.new(private_key || public_key) + end + end + end + + def usable? + !revoked? && !expired? + end + + def self.from_keyid(uri) + keypair = find_by(uri: uri) + return keypair unless keypair.nil? + + # No keypair found, try the old way we used to store RSA keypairs + account = ActivityPub::TagManager.instance.uri_to_actor(uri) + return if account&.public_key.blank? + + from_legacy_account(account, uri: uri) + end + + def self.from_legacy_account(account, uri: nil) + Keypair.new( + account:, + uri: uri.presence || ActivityPub::TagManager.instance.key_uri_for(account), + public_key: account.public_key, + private_key: account.private_key, + type: :rsa + ) + end +end diff --git a/app/models/tag.rb b/app/models/tag.rb index b14dfce763..a5fbf2f683 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -128,7 +128,7 @@ class Tag < ApplicationRecord end def search_for(term, limit = 5, offset = 0, options = {}) - stripped_term = term.strip + stripped_term = term.to_s.strip options.reverse_merge!({ exclude_unlistable: true, exclude_unreviewed: false }) query = Tag.matches_name(stripped_term) diff --git a/app/models/user_role.rb b/app/models/user_role.rb index f2597e1c43..e98d9bc479 100644 --- a/app/models/user_role.rb +++ b/app/models/user_role.rb @@ -39,6 +39,7 @@ class UserRole < ApplicationRecord delete_user_data: (1 << 19), view_feeds: (1 << 20), invite_bypass_approval: (1 << 21), + manage_email_subscriptions: (1 << 22), }.freeze EVERYONE_ROLE_ID = -99 @@ -60,6 +61,10 @@ class UserRole < ApplicationRecord invite_bypass_approval ).freeze, + email: %i( + manage_email_subscriptions + ).freeze, + moderation: %i( view_dashboard view_audit_log diff --git a/app/models/user_settings.rb b/app/models/user_settings.rb index c129888195..8d1a3c8b46 100644 --- a/app/models/user_settings.rb +++ b/app/models/user_settings.rb @@ -19,6 +19,7 @@ class UserSettings setting :default_content_type, default: 'text/plain' setting :hide_followers_count, default: false setting :default_quote_policy, default: 'public', in: %w(public followers nobody) + setting :email_subscriptions, default: false setting_inverse_alias :indexable, :noindex setting_inverse_alias :show_followers_count, :hide_followers_count diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb index 9b78412887..664d8f88d7 100644 --- a/app/serializers/activitypub/actor_serializer.rb +++ b/app/serializers/activitypub/actor_serializer.rb @@ -4,7 +4,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer include RoutingHelper include FormattingHelper - context :security + context :security, :webfinger context_extensions :manually_approves_followers, :featured, :also_known_as, :moved_to, :property_value, :discoverable, :suspended, @@ -55,6 +55,10 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer ActivityPub::TagManager.instance.uri_for(object) end + def webfinger + object.local_username_and_domain + end + def type if object.instance_actor? 'Application' diff --git a/app/serializers/activitypub/featured_collection_serializer.rb b/app/serializers/activitypub/featured_collection_serializer.rb index e37085d422..00fdb368e6 100644 --- a/app/serializers/activitypub/featured_collection_serializer.rb +++ b/app/serializers/activitypub/featured_collection_serializer.rb @@ -32,7 +32,7 @@ class ActivityPub::FeaturedCollectionSerializer < ActivityPub::Serializer end def total_items - object.collection_items.size + object.accepted_collection_items.size end def published diff --git a/app/serializers/rest/account_serializer.rb b/app/serializers/rest/account_serializer.rb index 2b2c74d879..1477ab3319 100644 --- a/app/serializers/rest/account_serializer.rb +++ b/app/serializers/rest/account_serializer.rb @@ -22,6 +22,7 @@ class REST::AccountSerializer < ActiveModel::Serializer attribute :memorial, if: :memorial? attribute :feature_approval, if: -> { Mastodon::Feature.collections_enabled? } + attribute :email_subscriptions, if: -> { Mastodon::Feature.email_subscriptions_enabled? } class AccountDecorator < SimpleDelegator def self.model_name @@ -180,4 +181,8 @@ class REST::AccountSerializer < ActiveModel::Serializer current_user: object.feature_policy_for_account(current_user&.account), } end + + def email_subscriptions + object.user_can?(:manage_email_subscriptions) && object.user_email_subscriptions_enabled? + end end diff --git a/app/serializers/rest/profile_serializer.rb b/app/serializers/rest/profile_serializer.rb index b96daf87d4..79b620f0ac 100644 --- a/app/serializers/rest/profile_serializer.rb +++ b/app/serializers/rest/profile_serializer.rb @@ -2,9 +2,11 @@ class REST::ProfileSerializer < ActiveModel::Serializer include RoutingHelper + include FormattingHelper # Please update app/javascript/api_types/profile.ts when making changes to the attributes attributes :id, :display_name, :note, :fields, + :formatted_note, :formatted_fields, :avatar, :avatar_static, :avatar_description, :header, :header_static, :header_description, :locked, :bot, :hide_collections, :discoverable, :indexable, @@ -17,10 +19,18 @@ class REST::ProfileSerializer < ActiveModel::Serializer object.id.to_s end + def formatted_note + account_bio_format(object) + end + def fields object.fields.map(&:to_h) end + def formatted_fields + object.fields.map { |field| { name: field.name, value: account_field_value_format(field), verified_at: field.verified_at } } + end + def avatar object.avatar_file_name.present? ? full_asset_url(object.avatar_original_url) : nil end diff --git a/app/services/activitypub/fetch_remote_actor_service.rb b/app/services/activitypub/fetch_remote_actor_service.rb index 1fb3f45ce5..9daa7e445e 100644 --- a/app/services/activitypub/fetch_remote_actor_service.rb +++ b/app/services/activitypub/fetch_remote_actor_service.rb @@ -27,11 +27,23 @@ class ActivityPub::FetchRemoteActorService < BaseService raise Error, "Unsupported JSON-LD context for document #{uri}" unless supported_context? raise Error, "Unexpected object type for actor #{uri} (expected any of: #{SUPPORTED_TYPES})" unless expected_type? raise Error, "Actor #{uri} has moved to #{@json['movedTo']}" if break_on_redirect && @json['movedTo'].present? - raise Error, "Actor #{uri} has no 'preferredUsername', which is a requirement for Mastodon compatibility" if @json['preferredUsername'].blank? + raise Error, "Actor #{uri} has neither 'preferredUsername' nor `webfinger`, which is a requirement for Mastodon compatibility" if @json['preferredUsername'].blank? && @json['webfinger'].blank? - @uri = @json['id'] - @username = @json['preferredUsername'] - @domain = Addressable::URI.parse(@uri).normalized_host + @uri = @json['id'] + + # FEP-2c59 defines a `webfinger` attribute that makes things more explicit and spares an extra request in some cases. + # It supersedes `preferredUsername`. + if @json['webfinger'].present? && @json['webfinger'].is_a?(String) + @username, @domain = split_acct(@json['webfinger']) + Rails.logger.debug { "Actor #{uri} has an invalid `webfinger` value, falling back to `preferredUsername`" } + end + + if @username.blank? || @domain.blank? + raise "Actor #{uri} has no `preferredUsername`, and either a bogus or missing `webfinger`, which is a requirement for Mastodon compatibility" if @json['preferredUsername'].blank? + + @username = @json['preferredUsername'] + @domain = Addressable::URI.parse(@uri).normalized_host + end check_webfinger! unless only_key diff --git a/app/services/activitypub/fetch_remote_key_service.rb b/app/services/activitypub/fetch_remote_key_service.rb index b6d9cfa733..7226f76737 100644 --- a/app/services/activitypub/fetch_remote_key_service.rb +++ b/app/services/activitypub/fetch_remote_key_service.rb @@ -14,7 +14,7 @@ class ActivityPub::FetchRemoteKeyService < BaseService raise Error, "Unable to fetch key JSON at #{uri}" if @json.nil? raise Error, "Unsupported JSON-LD context for document #{uri}" unless supported_context?(@json) || (supported_security_context?(@json) && @json['owner'].present? && !actor_type?) raise Error, "Unexpected object type for key #{uri}" unless expected_type? - return find_actor(@json['id'], @json, suppress_errors) if actor_type? + return Keypair.from_legacy_account(find_actor(@json['id'], @json, suppress_errors), uri: uri) if actor_type? @owner = fetch_resource(owner_uri, true) @@ -23,7 +23,8 @@ class ActivityPub::FetchRemoteKeyService < BaseService raise Error, "Unexpected object type for actor #{owner_uri} (expected any of: #{SUPPORTED_TYPES})" unless expected_owner_type? raise Error, "publicKey id for #{owner_uri} does not correspond to #{@json['id']}" unless confirmed_owner? - find_actor(owner_uri, @owner, suppress_errors) + # TODO: change to fetch and persist key + Keypair.from_legacy_account(find_actor(owner_uri, @owner, suppress_errors), uri: uri) rescue Error => e Rails.logger.debug { "Fetching key #{uri} failed: #{e.message}" } raise unless suppress_errors diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index bececf4bac..4a5597f2fe 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -6,6 +6,7 @@ class ActivityPub::ProcessAccountService < BaseService include Redisable include Lockable + MAX_PUBLIC_KEYS = 10 MAX_PROFILE_FIELDS = 50 SUBDOMAINS_RATELIMIT = 10 DISCOVERIES_PER_REQUEST = 400 @@ -33,8 +34,8 @@ class ActivityPub::ProcessAccountService < BaseService with_redis_lock("process_account:#{@uri}") do @account = Account.remote.find_by(uri: @uri) if @options[:only_key] @account ||= Account.find_remote(@username, @domain) - @old_public_key = @account&.public_key - @old_protocol = @account&.protocol + @old_public_keys = @account.present? ? (@account.keypairs.pluck(:public_key) + [@account.public_key.presence].compact) : [] + @old_protocol = @account&.protocol @suspension_changed = false if @account.nil? @@ -56,8 +57,9 @@ class ActivityPub::ProcessAccountService < BaseService end after_protocol_change! if protocol_changed? - after_key_change! if key_changed? && !@options[:signed_with_known_key] - clear_tombstones! if key_changed? + after_key_change! if all_public_keys_changed? && !@options[:signed_with_known_key] + # TODO: maybe tie tombstones to specific keys? i.e. we don't need to keep tombstones if all keys changed + clear_tombstones! if all_public_keys_changed? after_suspension_change! if suspension_changed? unless @options[:only_key] || @account.suspended? @@ -145,7 +147,11 @@ class ActivityPub::ProcessAccountService < BaseService end def set_fetchable_key! - @account.public_key = public_key || '' + @account.keypairs.upsert_all(public_keys, unique_by: :uri) + @account.keypairs.where.not(uri: public_keys.pluck(:uri)).delete_all + + # Unset legacy public key attribute + @account.public_key = '' end def set_fetchable_attributes! @@ -257,14 +263,35 @@ class ActivityPub::ProcessAccountService < BaseService [url, description] end - def public_key - value = first_of_value(@json['publicKey']) + def public_keys + # TODO: handle FEP-521a - return if value.nil? - return value['publicKeyPem'] if value.is_a?(Hash) + @public_keys ||= as_array(@json['publicKey']).take(MAX_PUBLIC_KEYS).filter_map do |value| + next if value.nil? - key = fetch_resource_without_id_validation(value) - key['publicKeyPem'] if key + if value.is_a?(Hash) + next unless value['owner'] == @account.uri + + key = value['publicKeyPem'] + value = value['id'] + + # Key is contained within the actor document, no need to fetch anything else + next { type: :rsa, public_key: key, uri: value } if value.split('#').first == @account.uri + end + + key_id = value + + # Key is fetched without ID validation because of a GoToSocial bug + value = fetch_resource_without_id_validation(key_id) + + # Special handling for GoToSocial which returns the whole actor for the key ID + value = first_of_value(value['publicKey']) if value.is_a?(Hash) && value.key?('publicKey') + + next unless value['owner'] == @account.uri + + key = value['publicKeyPem'] + { type: :rsa, public_key: key, uri: key_id } + end end def url @@ -353,8 +380,8 @@ class ActivityPub::ProcessAccountService < BaseService @domain_block = DomainBlock.rule_for(@domain) end - def key_changed? - !@old_public_key.nil? && @old_public_key != @account.public_key + def all_public_keys_changed? + !@old_public_keys.empty? && @account.keypairs.none? { |keypair| keypair.usable? && @old_public_keys.include?(keypair.public_key) } end def suspension_changed? diff --git a/app/services/activitypub/process_featured_collection_service.rb b/app/services/activitypub/process_featured_collection_service.rb index 2ef555e6bc..9e7c82af42 100644 --- a/app/services/activitypub/process_featured_collection_service.rb +++ b/app/services/activitypub/process_featured_collection_service.rb @@ -17,20 +17,15 @@ class ActivityPub::ProcessFeaturedCollectionService Collection.transaction do @collection = @account.collections.find_or_initialize_by(uri: @json['id']) - @collection.update!( - local: false, - name: (@json['name'] || '')[0, Collection::NAME_LENGTH_HARD_LIMIT], - description_html: truncated_summary, - language:, - sensitive: @json['sensitive'], - discoverable: @json['discoverable'], - original_number_of_items: @json['totalItems'] || 0, - tag_name: @json.dig('topic', 'name') - ) + @collection.update!(collection_attributes) - process_items! + @items = (@json['orderedItems'] || [])[0, ITEMS_LIMIT] + item_uris = @items.filter_map { |i| value_or_id(i) } + @collection.collection_items.where.not(uri: item_uris).delete_all end + process_items! + @collection end end @@ -46,14 +41,22 @@ class ActivityPub::ProcessFeaturedCollectionService @json['summaryMap']&.keys&.first end + def collection_attributes + { + local: false, + name: (@json['name'] || '')[0, Collection::NAME_LENGTH_HARD_LIMIT], + description_html: truncated_summary, + language:, + sensitive: @json['sensitive'], + discoverable: @json['discoverable'], + original_number_of_items: @json['totalItems'] || 0, + tag_name: @json.dig('topic', 'name'), + } + end + def process_items! - uris = [] - items = @json['orderedItems'] || [] - items.take(ITEMS_LIMIT).each_with_index do |item_json, index| - uris << value_or_id(item_json) - ActivityPub::ProcessFeaturedItemWorker.perform_async(@collection.id, item_json, index, @request_id) + @items.each_with_index do |item_json, index| + ActivityPub::ProcessFeaturedItemWorker.perform_async(@collection.id, item_json, index + 1, @request_id) end - uris.compact! - @collection.collection_items.where.not(uri: uris).delete_all end end diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 2018544b28..132bed45bb 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -5,6 +5,12 @@ class PostStatusService < BaseService include Lockable include LanguagesHelper + # How much to delay sending an e-mail about a new post, to allow grouping multiple posts + EMAIL_DISTRIBUTION_DELAY = 5.minutes.freeze + + # If the job is not executed within this timeframe, it will lose its arguments + EMAIL_DISTRIBUTION_TTL = 1.hour.to_i + class UnexpectedMentionsError < StandardError attr_reader :accounts @@ -174,11 +180,26 @@ class PostStatusService < BaseService Trends.tags.register(@status) LinkCrawlWorker.perform_async(@status.id) DistributionWorker.perform_async(@status.id) + process_email_subscriptions! ActivityPub::DistributionWorker.perform_async(@status.id) unless @status.local_only? PollExpirationNotifyWorker.perform_at(@status.poll.expires_at, @status.poll.id) if @status.poll ActivityPub::QuoteRequestWorker.perform_async(@status.quote.id) if @status.quote&.quoted_status.present? && !@status.quote&.quoted_status&.local? end + def process_email_subscriptions! + return unless Mastodon::Feature.email_subscriptions_enabled? && + @status.public_visibility? && (!@status.reply? || @status.in_reply_to_account_id == @status.account_id) && + @status.account.user_can?(:manage_email_subscriptions) && + @status.account.user_email_subscriptions_enabled? + + # To allow e-mail grouping, pass the arguments via a redis set and schedule + # a unique worker a few minutes in the future, in case the user makes subsequent + # posts within that time window + redis.sadd("email_subscriptions:#{@status.account_id}:next_batch", @status.id) + redis.expire("email_subscriptions:#{@status.account_id}:next_batch", EMAIL_DISTRIBUTION_TTL) + EmailDistributionWorker.perform_in(EMAIL_DISTRIBUTION_DELAY, @status.account_id) + end + def validate_media! if @options[:media_ids].blank? || !@options[:media_ids].is_a?(Enumerable) @media = [] diff --git a/app/services/revoke_collection_item_service.rb b/app/services/revoke_collection_item_service.rb index c0dc70e952..9b5c53f70c 100644 --- a/app/services/revoke_collection_item_service.rb +++ b/app/services/revoke_collection_item_service.rb @@ -6,6 +6,7 @@ class RevokeCollectionItemService < BaseService def call(collection_item) @collection_item = collection_item @account = collection_item.account + @collection = @collection_item.collection @collection_item.revoke! @@ -15,7 +16,8 @@ class RevokeCollectionItemService < BaseService private def distribute_stamp_deletion! - ActivityPub::AccountRawDistributionWorker.perform_async(signed_activity_json, @collection_item.collection.account_id) + ActivityPub::DeliveryWorker.perform_async(signed_activity_json, @account.id, @collection.account.inbox_url) + ActivityPub::AccountRawDistributionWorker.perform_async(signed_activity_json, @collection.account_id) end def signed_activity_json diff --git a/app/views/email_subscription_mailer/confirmation.html.haml b/app/views/email_subscription_mailer/confirmation.html.haml new file mode 100644 index 0000000000..7d14d9ff59 --- /dev/null +++ b/app/views/email_subscription_mailer/confirmation.html.haml @@ -0,0 +1,20 @@ += content_for :heading do + = render 'application/mailer/heading', + image_url: full_asset_url(@subscription.account.avatar.url), + title: t('.title', name: display_name(@subscription.account)) + +%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } + %tr + %td.email-body-padding-td + %table.email-inner-card-table{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } + %tr + %td.email-inner-card-td.email-prose + %p= t '.instructions_to_confirm', name: display_name(@subscription.account), acct: "#{@subscription.account.username}@#{@instance}" + + = render 'application/mailer/button', text: t('.action'), url: email_subscriptions_confirmation_url(confirmation_token: @subscription.confirmation_token) + + %p= t '.instructions_to_ignore' + +- content_for :footer do + %p.email-footer-p= t('email_subscription_mailer.notification.footer.reason_for_email_html', name: display_name(@subscription.account), unsubscribe_path: @unsubscribe_url) + %p.email-footer-p= t('email_subscription_mailer.notification.footer.privacy_html', domain: @instance, privacy_policy_path: privacy_policy_path) diff --git a/app/views/email_subscription_mailer/confirmation.text.erb b/app/views/email_subscription_mailer/confirmation.text.erb new file mode 100644 index 0000000000..4d80bebc0d --- /dev/null +++ b/app/views/email_subscription_mailer/confirmation.text.erb @@ -0,0 +1,9 @@ +<%= t '.title', name: display_name(@subscription.account) %> + +=== + +<%= t '.instructions_to_confirm', name: display_name(@subscription.account), acct: "#{@subscription.account.username}@#{@instance}" %> + +=> <%= root_url(confirmation_token: @subscription.confirmation_token) %> + +<%= t '.instructions_to_ignore' %> diff --git a/app/views/email_subscription_mailer/notification.html.haml b/app/views/email_subscription_mailer/notification.html.haml new file mode 100644 index 0000000000..ad816ba460 --- /dev/null +++ b/app/views/email_subscription_mailer/notification.html.haml @@ -0,0 +1,36 @@ +%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } + - @statuses.each do |status| + %tr + %td.email-body-padding-td + %table.email-inner-card-table{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } + %tr + %td.email-inner-card-td + = render 'notification_mailer/status', status: status, time_zone: nil + +%table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } + %tr + %td.email-body-padding-td + %table.email-w-full.email-checklist-wrapper-table{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } + %tr + %td.email-checklist-wrapper-td + %table.email-w-full.email-banner-table{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } + %tr + %td.email-banner-td + %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } + %tr + %td.email-banner-text-td + .email-desktop-flex + /[if mso] +
    + %div + %p= t('.interact_with_this_post', count: @statuses.size) + /[if mso] + + %div + = render 'application/mailer/button', text: t('.create_account'), url: available_sign_up_path, has_arrow: false + /[if mso] +
    + +- content_for :footer do + %p.email-footer-p= t('.footer.reason_for_email_html', name: display_name(@subscription.account), unsubscribe_path: @unsubscribe_url) + %p.email-footer-p= t('.footer.privacy_html', domain: @instance, privacy_policy_path: privacy_policy_path) diff --git a/app/views/email_subscription_mailer/notification.text.erb b/app/views/email_subscription_mailer/notification.text.erb new file mode 100644 index 0000000000..7da5261b64 --- /dev/null +++ b/app/views/email_subscription_mailer/notification.text.erb @@ -0,0 +1,7 @@ +<%= t '.title', count: @statuses.size, name: display_name(@subscription.account), excerpt: truncate(@statuses.first.text, length: 17) %> + +=== + +<%- @statuses.each do |status| %> +<%= render 'notification_mailer/status', status: status %> +<%- end %> diff --git a/app/views/email_subscriptions/confirmations/show.html.haml b/app/views/email_subscriptions/confirmations/show.html.haml new file mode 100644 index 0000000000..a13504bb48 --- /dev/null +++ b/app/views/email_subscriptions/confirmations/show.html.haml @@ -0,0 +1,11 @@ +- content_for :page_title do + = t('.title') + +.simple_form + %h1.title + = t('.title') + %p.lead + = t('.success_html', name: content_tag(:strong, display_name(@email_subscription.account)), sender: content_tag(:strong, EmailSubscriptionMailer.default[:from])) + %p.lead + = t('.changed_your_mind') + = link_to t('.unsubscribe'), unsubscribe_url(token: @email_subscription.to_sgid(for: 'unsubscribe')) diff --git a/app/views/layouts/mailer.html.haml b/app/views/layouts/mailer.html.haml index a018c8b7db..a85142235a 100644 --- a/app/views/layouts/mailer.html.haml +++ b/app/views/layouts/mailer.html.haml @@ -12,6 +12,7 @@ %style{ 'data-premailer': 'ignore' } \.email a { color: inherit; text-decoration: none; } \.email-btn-hover:hover { background-color: #563acc !important; } + \.email-banner-text-td .email-btn-hover:hover { background-color: #fff !important; } /[if mso] @@ -73,15 +74,18 @@ %table.email-w-full{ cellspacing: 0, cellpadding: 0, border: 0, role: 'presentation' } %tr %td.email-footer-td - %p.email-footer-p - = link_to root_url, class: 'email-footer-logo-a' do - = image_tag frontend_asset_url('images/mailer-new/common/logo-footer.png'), alt: 'Mastodon', width: 44, height: 44 - %p.email-footer-p - = t 'about.hosted_on', domain: site_hostname - %p.email-footer-p - = link_to t('application_mailer.notification_preferences'), settings_preferences_notifications_url - - if defined?(@unsubscribe_url) - · - = link_to t('application_mailer.unsubscribe'), @unsubscribe_url + - if content_for?(:footer) + = yield :footer + - else + %p.email-footer-p + = link_to root_url, class: 'email-footer-logo-a' do + = image_tag frontend_asset_url('images/mailer-new/common/logo-footer.png'), alt: 'Mastodon', width: 44, height: 44 + %p.email-footer-p + = t 'about.hosted_on', domain: site_hostname + %p.email-footer-p + = link_to t('application_mailer.notification_preferences'), settings_preferences_notifications_url + - if defined?(@unsubscribe_url) + · + = link_to t('application_mailer.unsubscribe'), @unsubscribe_url /[if mso] diff --git a/app/views/layouts/mailer.text.erb b/app/views/layouts/mailer.text.erb index 87b0b2929c..1a78f33110 100644 --- a/app/views/layouts/mailer.text.erb +++ b/app/views/layouts/mailer.text.erb @@ -1,5 +1,9 @@ <%= yield %> --- -<%= t 'about.hosted_on', domain: site_hostname %> -<%= t('application_mailer.settings', link: settings_preferences_url) %> +<%- unless defined?(@skip_preferences_link) %> +<%= t('application_mailer.notification_preferences') %>: <%= settings_preferences_url %> +<%- end %> +<%- if defined?(@unsubscribe_url) %> +<%= t('application_mailer.unsubscribe') %>: <%= @unsubscribe_url %> +<%- end %> diff --git a/app/views/mail_subscriptions/create.html.haml b/app/views/mail_subscriptions/create.html.haml deleted file mode 100644 index 16ee486b00..0000000000 --- a/app/views/mail_subscriptions/create.html.haml +++ /dev/null @@ -1,9 +0,0 @@ -- content_for :page_title do - = t('mail_subscriptions.unsubscribe.title') - -.simple_form - %h1.title= t('mail_subscriptions.unsubscribe.complete') - %p.lead - = t('mail_subscriptions.unsubscribe.success_html', domain: content_tag(:strong, site_hostname), type: content_tag(:strong, I18n.t(@type, scope: 'mail_subscriptions.unsubscribe.emails')), email: content_tag(:strong, @user.email)) - %p.lead - = t('mail_subscriptions.unsubscribe.resubscribe_html', settings_path: settings_preferences_notifications_path) diff --git a/app/views/mail_subscriptions/show.html.haml b/app/views/mail_subscriptions/show.html.haml deleted file mode 100644 index 78de486457..0000000000 --- a/app/views/mail_subscriptions/show.html.haml +++ /dev/null @@ -1,20 +0,0 @@ -- content_for :page_title do - = t('mail_subscriptions.unsubscribe.title') - -.simple_form - %h1.title= t('mail_subscriptions.unsubscribe.title') - %p.lead - = t 'mail_subscriptions.unsubscribe.confirmation_html', - domain: content_tag(:strong, site_hostname), - type: content_tag(:strong, I18n.t(@type, scope: 'mail_subscriptions.unsubscribe.emails')), - email: content_tag(:strong, @user.email), - settings_path: settings_preferences_notifications_path - - = form_with url: unsubscribe_path do |form| - = form.hidden_field :token, - value: params[:token] - = form.hidden_field :type, - value: params[:type] - = form.button t('mail_subscriptions.unsubscribe.action'), - type: :submit, - class: 'btn' diff --git a/app/views/settings/privacy/show.html.haml b/app/views/settings/privacy/show.html.haml index a6a1511dee..130fe73d4f 100644 --- a/app/views/settings/privacy/show.html.haml +++ b/app/views/settings/privacy/show.html.haml @@ -45,5 +45,25 @@ .fields-group = ff.input :show_application, wrapper: :with_label + - if Mastodon::Feature.email_subscriptions_enabled? && current_user.can?(:manage_email_subscriptions) + %h2= t('privacy.email_subscriptions') + + %p.lead= t('privacy.email_subscriptions_hint_html') + + - if @email_subscriptions_count.positive? || @account.user_email_subscriptions_enabled? + .table-wrapper + %table.table.mini-table + %tbody + %tr + %th= t('email_subscriptions.status') + %td= @account.user_email_subscriptions_enabled? ? t('email_subscriptions.active') : t('email_subscriptions.inactive') + %tr + %th= t('email_subscriptions.subscribers') + %td= number_with_delimiter @email_subscriptions_count + + = f.simple_fields_for :settings, current_user.settings do |ff| + .fields-group + = ff.input :email_subscriptions, wrapper: :with_label + .actions = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/settings/profiles/show.html.haml b/app/views/settings/profiles/show.html.haml index 4f59c04747..7d7eab3545 100644 --- a/app/views/settings/profiles/show.html.haml +++ b/app/views/settings/profiles/show.html.haml @@ -5,6 +5,15 @@ %h1= t('settings.profile') = render partial: 'settings/shared/profile_navigation' +- if Mastodon::Feature.profile_redesign_enabled? + %aside.callout + = material_symbol 'info' + .content + .body + %p.title= t('edit_profile.redesign_title') + %p= t('edit_profile.redesign_body') + = link_to t('edit_profile.redesign_button'), '/profile/edit' + = simple_form_for @account, url: settings_profile_path, html: { id: :edit_profile } do |f| = render 'shared/error_messages', object: @account diff --git a/app/views/unsubscriptions/create.html.haml b/app/views/unsubscriptions/create.html.haml new file mode 100644 index 0000000000..157cf26226 --- /dev/null +++ b/app/views/unsubscriptions/create.html.haml @@ -0,0 +1,12 @@ +- content_for :page_title do + = t('.title') + +.simple_form + %h1.title= t('.title') + %p.lead + - if @scope == :email_subscription + = t('.email_subscription.confirmation_html', name: display_name(@recipient.account)) + - elsif @scope == :user + = t('.user.confirmation_html', type: I18n.t(@type, scope: 'unsubscriptions.notification_emails'), domain: site_hostname) + + = link_to t('.action'), root_path, class: 'btn' diff --git a/app/views/unsubscriptions/show.html.haml b/app/views/unsubscriptions/show.html.haml new file mode 100644 index 0000000000..6f5f007612 --- /dev/null +++ b/app/views/unsubscriptions/show.html.haml @@ -0,0 +1,26 @@ +- content_for :page_title do + - if @scope == :user + = t('.user.title', type: I18n.t(@type, scope: 'unsubscriptions.notification_emails')) + - elsif @scope == :email_subscription + = t('.email_subscription.title', name: display_name(@recipient.account)) + +.simple_form + %h1.title + - if @scope == :user + = t('.user.title', type: I18n.t(@type, scope: 'unsubscriptions.notification_emails')) + - elsif @scope == :email_subscription + = t('.email_subscription.title', name: display_name(@recipient.account)) + %p.lead + - if @scope == :user + = t('.user.confirmation_html') + - elsif @scope == :email_subscription + = t('.email_subscription.confirmation_html') + + = form_with url: unsubscribe_path do |form| + = form.hidden_field :token, + value: params[:token] + = form.hidden_field :type, + value: params[:type] + = form.button t('.action'), + type: :submit, + class: 'btn' diff --git a/app/workers/email_distribution_worker.rb b/app/workers/email_distribution_worker.rb new file mode 100644 index 0000000000..41edcb932c --- /dev/null +++ b/app/workers/email_distribution_worker.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +class EmailDistributionWorker + include Sidekiq::Worker + include Redisable + + sidekiq_options lock: :until_executed, lock_ttl: 1.day.to_i + + def perform(account_id) + return unless Mastodon::Feature.email_subscriptions_enabled? + + @account = Account.find(account_id) + + return unless @account.user_can?(:manage_email_subscriptions) && @account.user_email_subscriptions_enabled? + + with_redis do |redis| + @status_ids = redis.smembers("email_subscriptions:#{account_id}:next_batch") + redis.srem("email_subscriptions:#{account_id}:next_batch", @status_ids) + end + + return if @account.email_subscriptions.confirmed.empty? || @status_ids.empty? + + statuses = Status.without_replies + .without_reblogs + .public_visibility + .where(id: @status_ids) + .to_a + + return if statuses.empty? + + @account.email_subscriptions.confirmed.find_each do |email_subscription| + EmailSubscriptionMailer.with(subscription: email_subscription).notification(statuses).deliver_later + end + rescue ActiveRecord::RecordNotFound + nil + end +end diff --git a/app/workers/scheduler/user_cleanup_scheduler.rb b/app/workers/scheduler/user_cleanup_scheduler.rb index 03544e2e98..20d895551a 100644 --- a/app/workers/scheduler/user_cleanup_scheduler.rb +++ b/app/workers/scheduler/user_cleanup_scheduler.rb @@ -11,6 +11,7 @@ class Scheduler::UserCleanupScheduler def perform clean_unconfirmed_accounts! clean_discarded_statuses! + clean_unconfirmed_email_subscriptions! end private @@ -32,4 +33,10 @@ class Scheduler::UserCleanupScheduler end end end + + def clean_unconfirmed_email_subscriptions! + EmailSubscription.unconfirmed.where(created_at: ..UNCONFIRMED_ACCOUNTS_MAX_AGE_DAYS.days.ago).find_in_batches do |batch| + EmailSubscription.where(id: batch.map(&:id)).delete_all + end + end end diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index 0ae47a235d..ada9f7168d 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -73,6 +73,7 @@ ignore_unused: - 'move_handler.carry_{mutes,blocks}_over_text' - 'admin_mailer.*.subject' - 'user_mailer.*.subject' + - 'email_subscription_mailer.*' - 'notification_mailer.*' - 'imports.overwrite_preambles.{following,blocking,muting,domain_blocking,bookmarks,lists}_html.*' - 'imports.preambles.{following,blocking,muting,domain_blocking,bookmarks,lists}_html.*' diff --git a/config/initializers/json_ld_webfinger.rb b/config/initializers/json_ld_webfinger.rb new file mode 100644 index 0000000000..da2437260c --- /dev/null +++ b/config/initializers/json_ld_webfinger.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require 'json/ld' + +class JSON::LD::Context + add_preloaded("http://purl.archive.org/socialweb/webfinger") do + new(processingMode: "json-ld-1.0", term_definitions: { + "webfinger" => TermDefinition.new("webfinger", id: "https://purl.archive.org/socialweb/webfinger#webfinger", type_mapping: "http://www.w3.org/2001/XMLSchema#string"), + "wf" => TermDefinition.new("wf", id: "https://purl.archive.org/socialweb/webfinger#", simple: true, prefix: true), + "xsd" => TermDefinition.new("xsd", id: "http://www.w3.org/2001/XMLSchema#", simple: true, prefix: true) + }) + end + alias_preloaded("https://purl.archive.org/socialweb/webfinger", "http://purl.archive.org/socialweb/webfinger") +end diff --git a/config/locales/ar.yml b/config/locales/ar.yml index f689f79007..91a16c83f6 100644 --- a/config/locales/ar.yml +++ b/config/locales/ar.yml @@ -1267,7 +1267,6 @@ ar: application_mailer: notification_preferences: تغيير تفضيلات البريد الإلكتروني salutation: "%{name}،" - settings: 'تغيير تفضيلات البريد الإلكتروني: %{link}' unsubscribe: إلغاء الاشتراك view: 'اعرض:' view_profile: اعرض الصفحة التعريفية @@ -1762,21 +1761,6 @@ ar: failed_sign_in_html: فشل محاولة تسجيل الدخول مع %{method} من %{ip} (%{browser}) successful_sign_in_html: تم تسجيل الدخول بنجاح مع %{method} من %{ip} (%{browser}) title: تاريخ المصادقة - mail_subscriptions: - unsubscribe: - action: نعم، ألغِ الاشتراك - complete: غير مشترك - confirmation_html: هل أنت متأكد أنك تريد إلغاء الاشتراك عن تلقي %{type} لماستدون على %{domain} إلى بريدك الإلكتروني %{email}؟ يمكنك دائمًا إعادة الاشتراك من إعدادات إشعارات البريد الإلكتروني. - emails: - notification_emails: - favourite: إرسال إشعارات التفضيلات بالبريد الإلكتروني - follow: إرسال إشعارات المتابعة بالبريد الإلكتروني - follow_request: إرسال إشعارات الطلبات بالبريد الإلكتروني - mention: إشعارات رسائل البريد عندما يَذكُرك أحدهم - reblog: رسائل البريد الخاصة بالمنشورات المعاد نشرها - resubscribe_html: إذا قمت بإلغاء الاشتراك عن طريق الخطأ، يمكنك إعادة الاشتراك من إعدادات إشعارات البريد الإلكتروني. - success_html: لن تتلقّ بعد الآن %{type} لماستدون مِن %{domain} على بريدك الإلكتروني %{email}. - title: إلغاء الاشتراك media_attachments: validations: images_and_video: ليس بالإمكان إرفاق فيديو في منشور يحتوي مسبقا على صور diff --git a/config/locales/az.yml b/config/locales/az.yml index fb1282ab94..ea5304929b 100644 --- a/config/locales/az.yml +++ b/config/locales/az.yml @@ -211,11 +211,6 @@ az: otp: iki faktorlu kimlik doğrulama tətbiqi password: parol description_html: Əgər tanımadığınız bir fəaliyyəti görsəniz, parolunuzu dəyişdirməyi və iki faktorlu kimlik doğrulamanı fəallaşdırmağı düşünə bilərsiniz - mail_subscriptions: - unsubscribe: - emails: - notification_emails: - reblog: təkrar paylaşma bildirişi e-poçtları migrations: incoming_migrations: Fərqli bir hesabdan daşı incoming_migrations_html: Başqa bir hesabdan bu hesaba daşımaq üçün əvvəlcə bir hesab alias-ı yaratmalısınız. diff --git a/config/locales/be.yml b/config/locales/be.yml index 9d06a26030..70f032c2fc 100644 --- a/config/locales/be.yml +++ b/config/locales/be.yml @@ -1273,7 +1273,6 @@ be: application_mailer: notification_preferences: Змяніць налады эл. пошты salutation: "%{name}," - settings: 'Змяніць налады эл. пошты: %{link}' unsubscribe: Адпісацца view: 'Паглядзець:' view_profile: Паглядзець профіль @@ -1735,21 +1734,6 @@ be: failed_sign_in_html: Няўдалая спроба ўваходу праз %{method} з %{ip} (%{browser}) successful_sign_in_html: Паспяховы ўваход праз %{method} з %{ip} (%{browser}) title: Гісторыя ўваходаў - mail_subscriptions: - unsubscribe: - action: Так, адпісацца - complete: Адпісаны - confirmation_html: Вы ўпэўнены, што жадаеце адмовіцца ад атрымання %{type} з Mastodon на дамене %{domain} на сваю электронную пошту %{email}? Вы заўсёды можаце паўторна падпісацца ў наладах апавяшчэнняў па электроннай пошце. - emails: - notification_emails: - favourite: апавяшчэнні на пошту пра упадабанае - follow: апавяшчэнні на пошту пра падпіскі - follow_request: апавяшчэнні на пошту пра запыты на падпіску - mention: апавяшчэнні на пошту пра згадванні - reblog: апавяшчэнні на пошту пра пашырэнні - resubscribe_html: Калі вы адмовіліся ад падпіскі памылкова, вы можаце зноў падпісацца ў наладах апавяшчэнняў па электроннай пошце. - success_html: Вы больш не будзеце атрымліваць %{type} на сваю электронную пошту %{email} ад Mastodon на дамене %{domain}. - title: Адпісацца media_attachments: validations: images_and_video: Немагчыма далучыць відэа да допісу, які ўжо змяшчае выявы diff --git a/config/locales/bg.yml b/config/locales/bg.yml index 514cb2f6e9..84e88b3cad 100644 --- a/config/locales/bg.yml +++ b/config/locales/bg.yml @@ -1186,7 +1186,6 @@ bg: application_mailer: notification_preferences: Промяна на предпочитанията за е-поща salutation: "%{name}," - settings: 'Промяна на предпочитанията за имейл: %{link}' unsubscribe: Стоп на абонамента view: 'Преглед:' view_profile: Преглед на профила @@ -1599,21 +1598,6 @@ bg: failed_sign_in_html: Неуспешен опит за влизане с %{method} от %{ip} (%{browser}) successful_sign_in_html: Успешно влизане с %{method} от %{ip} (%{browser}) title: Историята на удостоверяване - mail_subscriptions: - unsubscribe: - action: Да, да се спре абонамента - complete: Спрян абонамент - confirmation_html: Наистина ли искате да спрете абонамента от получаването на %{type} за Mastodon в %{domain} към имейла си при %{email}? Може винаги пак да се абонирате от своите настройки за известяване по е-поща. - emails: - notification_emails: - favourite: е-писма за известия с любими - follow: е-писма с известия за последване - follow_request: е-писма със заявки за следване - mention: е-писма с известия за споменаване - reblog: е-писма с известия за подсилване - resubscribe_html: Ако погрешка сте спрели абонамента, то може пак да се абонирате от своите настройки за известия по е-поща. - success_html: Повече няма да получавате %{type} за Mastodon на %{domain} към имейла си при %{email}. - title: Спиране на абонамента media_attachments: validations: images_and_video: Не мога да прикача видеоклип към публикация, която вече съдържа изображения diff --git a/config/locales/br.yml b/config/locales/br.yml index c1b2906548..850d7dc093 100644 --- a/config/locales/br.yml +++ b/config/locales/br.yml @@ -683,9 +683,6 @@ br: authentication_methods: password: ger-tremen webauthn: alc’hwezioù surentez - mail_subscriptions: - unsubscribe: - action: Ya, digoumanantiñ media_attachments: validations: images_and_video: N'haller stagañ ur video ouzh un embannadur a zo fotoioù gantañ dija diff --git a/config/locales/ca.yml b/config/locales/ca.yml index a7ef2e7c38..e0bac6c6c3 100644 --- a/config/locales/ca.yml +++ b/config/locales/ca.yml @@ -1194,7 +1194,6 @@ ca: application_mailer: notification_preferences: Canviar les preferències de correu-e salutation: "%{name}," - settings: 'Canviar les preferències de correu-e: %{link}' unsubscribe: Cancel·la la subscripció view: 'Visualització:' view_profile: Mostra el perfil @@ -1614,21 +1613,6 @@ ca: failed_sign_in_html: Intent d'inici de sessió errat amb %{method} des de %{ip} (%{browser}) successful_sign_in_html: Inici de sessió exitós amb %{method} des de %{ip} (%{browser}) title: Historial d'autenticació - mail_subscriptions: - unsubscribe: - action: Sí, canceŀla la subscripció - complete: Subscripció cancel·lada - confirmation_html: Segur que vols donar-te de baixa de rebre %{type} de Mastodon a %{domain} a %{email}? Sempre pots subscriure't de nou des de la configuració de les notificacions per correu electrònic. - emails: - notification_emails: - favourite: notificacions dels favorits per correu electrònic - follow: notificacions dels seguiments per correu electrònic - follow_request: correus electrònics de peticions de seguiment - mention: correus electrònics de notificacions de mencions - reblog: correus electrònics de notificacions d'impulsos - resubscribe_html: Si ets dones de baixa per error pots donar-te d'alta des de la configuració de les notificacions per correu electrònic. - success_html: Ja no rebràs %{type} de Mastodon a %{domain} a %{email}. - title: Cancel·la la subscripció media_attachments: validations: images_and_video: No es pot adjuntar un vídeo a una publicació que ja contingui imatges diff --git a/config/locales/cs.yml b/config/locales/cs.yml index 0d324d1453..a090b663a3 100644 --- a/config/locales/cs.yml +++ b/config/locales/cs.yml @@ -1267,7 +1267,6 @@ cs: application_mailer: notification_preferences: Změnit předvolby e-mailu salutation: "%{name}," - settings: 'Změnit předvolby e-mailu: %{link}' unsubscribe: Přestat odebírat view: 'Zobrazit:' view_profile: Zobrazit profil @@ -1727,21 +1726,6 @@ cs: failed_sign_in_html: Neúspěšný pokus o přihlášení %{method} z %{ip} (%{browser}) successful_sign_in_html: Úspěšné přihlášení %{method} z %{ip} (%{browser}) title: Historie přihlášení - mail_subscriptions: - unsubscribe: - action: Ano, odeberte odběr - complete: Odběr byl odhlášen - confirmation_html: Jste si jisti, že chcete odhlásit odběr %{type} pro Mastodon na %{domain} na váš e-mail %{email}? Vždy se můžete znovu přihlásit ve svém nastavení e-mailových oznámení. - emails: - notification_emails: - favourite: e-mailové oznámení při oblíbení - follow: e-mailové oznámení při sledování - follow_request: e-mail při žádost o sledování - mention: e-mailové oznámení při zmínění - reblog: e-mailové oznámení při boostu - resubscribe_html: Pokud jste se odhlásili omylem, můžete se znovu přihlásit ve svých nastavení e-mailových oznámení. - success_html: Již nebudete dostávat %{type} pro Mastodon na %{domain} na vaši e-mailovou adresu %{email}. - title: Odhlásit odběr media_attachments: validations: images_and_video: K příspěvku, který již obsahuje obrázky, nelze připojit video diff --git a/config/locales/cy.yml b/config/locales/cy.yml index 560bc3e6f3..2bf693f5ea 100644 --- a/config/locales/cy.yml +++ b/config/locales/cy.yml @@ -1313,7 +1313,6 @@ cy: application_mailer: notification_preferences: Newid dewisiadau e-bost salutation: "%{name}," - settings: 'Newid dewisiadau e-bost: %{link}' unsubscribe: Dad-danysgrifio view: 'Gweld:' view_profile: Gweld proffil @@ -1815,21 +1814,6 @@ cy: failed_sign_in_html: Ymgais mewngofnodi wedi methu gyda %{method} gan %{ip} (%{browser}) successful_sign_in_html: Mewngofnodi llwyddiannus gyda %{method} o %{ip} (%{browser}) title: Hanes dilysu - mail_subscriptions: - unsubscribe: - action: Iawn, dad-danysgrifio - complete: Dad-danysgrifiwyd - confirmation_html: Ydych chi'n siŵr eich bod am ddad-danysgrifio rhag derbyn %{type} Mastodon ar %{domain} i'ch e-bost yn %{email}? Gallwch ail-danysgrifio o'ch gosodiadau hysbysu e-bost rhywbryd eto. - emails: - notification_emails: - favourite: e-bost hysbysu hoffi - follow: e-byst hysbysu dilyn - follow_request: e-byst ceisiadau dilyn - mention: e-byst hysbysu crybwylliadau - reblog: e-byst hysbysiadau hybu - resubscribe_html: Os ydych wedi dad-danysgrifio trwy gamgymeriad, gallwch ail-danysgrifio drwy'ch gosodiadau hysbysu e-bost. - success_html: Ni fyddwch bellach yn derbyn %{type} ar gyfer Mastodon ar %{domain} i'ch e-bost am %{email}. - title: Dad-danysgrifio media_attachments: validations: images_and_video: Methu atodi fideo i bostiad sydd eisoes yn cynnwys delweddau diff --git a/config/locales/da.yml b/config/locales/da.yml index c83750d58c..9b2ef173b7 100644 --- a/config/locales/da.yml +++ b/config/locales/da.yml @@ -762,6 +762,7 @@ da: categories: administration: Administration devops: DevOps + email: E-mail invites: Invitationer moderation: Moderering special: Speciel @@ -790,6 +791,8 @@ da: manage_blocks_description: Tillader brugere at blokere e-mailudbydere og IP-adresser manage_custom_emojis: Administrere tilpassede emojier manage_custom_emojis_description: Tillader brugere at administrere tilpassede emojier på serveren + manage_email_subscriptions: Administrer e-mail-abonnementer + manage_email_subscriptions_description: Giv brugere mulighed for at abonnere på andre brugere med denne tilladelse via e-mail manage_federation: Administrere federation manage_federation_description: Tillader brugere at blokere eller tillade federation med andre domæner og styre leverbarhed manage_invites: Administrere invitationer @@ -1231,7 +1234,6 @@ da: application_mailer: notification_preferences: Skift e-mailpræferencer salutation: "%{name}" - settings: 'Skift e-mailpræferencer: %{link}' unsubscribe: Afmeld notifikationer view: 'Vis:' view_profile: Vis profil @@ -1419,6 +1421,38 @@ da: basic_information: Oplysninger hint_html: "Tilpas, hvad folk ser på din offentlige profil og ved siden af dine indlæg. Andre personer er mere tilbøjelige til at følge dig tilbage og interagere med dig, når du har en udfyldt profil og et profilbillede." other: Andre + email_subscription_mailer: + confirmation: + action: Bekræft e-mailadresse + instructions_to_confirm: Bekræft, at du gerne vil modtage e-mails fra %{name} (@%{acct}), når vedkommende offentliggør nye indlæg. + instructions_to_ignore: Hvis du ikke ved, hvorfor du har modtaget denne e-mail, kan du slette den. Du bliver ikke tilmeldt, hvis du ikke klikker på linket ovenfor. + subject: Bekræft din e-mailadresse + title: Modtag e-mail opdateringer fra %{name}? + notification: + create_account: Opret en Mastodon-konto + footer: + privacy_html: E-mails sendes fra %{domain}, en server drevet af Mastodon. For at få mere at vide om, hvordan denne server behandler dine personoplysninger, kan du læse privatlivspolitikken. + reason_for_email_html: Du modtager denne e-mail, fordi du har valgt at modtage e-mailopdateringer fra %{name}. Ønsker du ikke at modtage disse e-mails? Afmeld + interact_with_this_post: + one: Interagér med dette indlæg, og find flere lignende indlæg. + other: Interagér med disse indlæg, og find flere. + subject: + one: 'Nyt indlæg: "%{excerpt}"' + other: Nye indlæg fra %{name} + title: + one: 'Nyt indlæg: "%{excerpt}"' + other: Nye indlæg fra %{name} + email_subscriptions: + active: Aktive + confirmations: + show: + changed_your_mind: Har du skiftet mening? + success_html: Du vil nu begynde at modtage e-mails, når %{name} offentliggør nye indlæg. Tilføj %{sender} til dine kontakter, så disse indlæg ikke ender i din spam-mappe. + title: Du er tilmeldt + unsubscribe: Afmeld + inactive: Inaktive + status: Status + subscribers: Abonnenter emoji_styles: auto: Auto native: Indbygget @@ -1653,21 +1687,6 @@ da: failed_sign_in_html: Mislykket indlogning med %{method} fra %{ip} (%{browser}) successful_sign_in_html: Gennemført indlogning med %{method} fra %{ip} (%{browser}) title: Godkendelseshistorik - mail_subscriptions: - unsubscribe: - action: Ja, afmeld - complete: Afmeldt - confirmation_html: Er du sikker på, at du vil afmelde modtagelse af %{type} for Mastodon på %{domain} til din e-mail på %{email}? Du kan altid tilmelde dig igen fra dine indstillinger for e-mail-notifikationer. - emails: - notification_emails: - favourite: e-mailnotifikationer om favoritmarkeringer - follow: e-mailnotifikationer om nye følgere - follow_request: e-mailnotifikationer om følgeanmodninger - mention: e-mailnotifikationer om omtaler - reblog: e-mailnotifikationer om fremhævelser - resubscribe_html: Har du afmeldt dig ved en fejl, kan du gentilmelde dig via indstillingerne for e-mail-notifikationer. - success_html: Du vil ikke længere modtage %{type} for Mastodon på %{domain} til din e-mail %{email}. - title: Opsig abonnement media_attachments: validations: images_and_video: En video kan ikke vedhæftes et indlæg med billedindhold @@ -1805,6 +1824,8 @@ da: posting_defaults: Standarder for indlæg public_timelines: Offentlige tidslinjer privacy: + email_subscriptions: Send indlæg via e-mail + email_subscriptions_hint_html: Tilføj en tilmeldingsformular til din profil, som vises for brugere, der ikke er logget ind. Når besøgende indtaster deres e-mailadresse og tilmelder sig, sender Mastodon e-mailopdateringer om dine offentlige indlæg. hint_html: "Tilpas hvordan din profil og dine indlæg kan findes. En række funktioner i Mastodon kan hjælpe dig med at nå ud til et bredere publikum, hvis du aktiverer dem. Tjek indstillingerne herunder for at sikre, at de passer til dit brugsscenarie." privacy: Privatliv privacy_hint_html: Styr, hvor meget du vil afsløre til gavn for andre. Folk opdager interessante profiler og apps ved at gennemse andres følgere og se, hvilke apps de sender fra, men du foretrækker måske at holde det skjult. @@ -2068,6 +2089,28 @@ da: resume_app_authorization: Genoptag godkendelse af applikation role_requirement: "%{domain} kræver, at du konfigurerer tofaktorgodkendelse, før du kan bruge Mastodon." webauthn: Sikkerhedsnøgler + unsubscriptions: + create: + action: Gå til serverens hjemmeside + email_subscription: + confirmation_html: Du vil ikke længere modtage e-mails fra %{name}. + title: Du er afmeldt + user: + confirmation_html: Du vil ikke længere modtage %{type} fra Mastodon på %{domain}. + notification_emails: + favourite: e-mail-notifikationer om favoritmarkeringer + follow: e-mail-notifikationer om nye følgere + follow_request: e-mail-notifikationer om følgeanmodninger + mention: e-mail-notifikationer om omtaler + reblog: e-mail-notifikationer om fremhævelser + show: + action: Afmeld + email_subscription: + confirmation_html: Du vil ikke længere modtage e-mails, når denne konto offentliggør nye indlæg. + title: Afmeld fra %{name}? + user: + confirmation_html: Du vil ikke længere modtage %{type} fra Mastodon på %{domain}. + title: Afmeld fra %{type}? user_mailer: announcement_published: description: 'Administratorerne på %{domain} udsender en annoncering:' diff --git a/config/locales/de.yml b/config/locales/de.yml index 4981257442..f0203268a2 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -762,6 +762,7 @@ de: categories: administration: Administration devops: DevOps + email: E-Mail invites: Einladungen moderation: Moderation special: Besonderheit @@ -790,6 +791,7 @@ de: manage_blocks_description: E-Mail-Provider und IP-Adressen sperren manage_custom_emojis: Emojis manage_custom_emojis_description: Spezielle Emojis dieses Servers verwalten + manage_email_subscriptions: E-Mail-Benachrichtigungen verwalten manage_federation: Föderation manage_federation_description: Domains anderer Mastodon-Server sperren/zulassen – und Zustellbarkeit kontrollieren manage_invites: Einladungen @@ -1231,7 +1233,6 @@ de: application_mailer: notification_preferences: E-Mail-Einstellungen ändern salutation: "%{name}," - settings: 'E-Mail-Einstellungen ändern: %{link}' unsubscribe: Abbestellen view: 'Siehe:' view_profile: Profil anzeigen @@ -1419,6 +1420,28 @@ de: basic_information: Allgemeine Informationen hint_html: "Bestimme, was andere auf deinem öffentlichen Profil und neben deinen Beiträgen sehen können. Wenn du ein Profilbild festlegst und dein Profil vervollständigst, werden andere eher mit dir interagieren und dir folgen." other: Andere + email_subscription_mailer: + confirmation: + action: E-Mail-Adresse bestätigen + subject: Bestätige deine E-Mail-Adresse + title: E-Mail-Benachrichtigungen von %{name} erhalten? + notification: + create_account: Mastodon-Konto erstellen + subject: + one: 'Neuer Beitrag: „%{excerpt}“' + other: Neue Beiträge von %{name} + title: + one: 'Neuer Beitrag: „%{excerpt}“' + other: Neue Beiträge von %{name} + email_subscriptions: + active: Aktiviert + confirmations: + show: + changed_your_mind: Meinung geändert? + unsubscribe: Abbestellen + inactive: Deaktiviert + status: Status + subscribers: Abonnent*innen emoji_styles: auto: Automatisch native: Nativ @@ -1653,21 +1676,6 @@ de: failed_sign_in_html: Fehlgeschlagener Anmeldeversuch mit %{method} von %{ip} (%{browser}) successful_sign_in_html: Erfolgreiches Anmelden mit %{method} von %{ip} (%{browser}) title: Anmeldeverlauf - mail_subscriptions: - unsubscribe: - action: Ja, abbestellen - complete: Abbestellt - confirmation_html: Möchtest du %{type} für Mastodon auf %{domain} an deine E-Mail-Adresse %{email} wirklich abbestellen? Du kannst dies später in den Einstellungen Benachrichtigungen per E-Mail rückgängig machen. - emails: - notification_emails: - favourite: E-Mail-Benachrichtigungen bei Favoriten - follow: E-Mail-Benachrichtigungen bei Followern - follow_request: E-Mail-Benachrichtigungen bei Follower-Anfragen - mention: E-Mail-Benachrichtigungen bei Erwähnungen - reblog: E-Mail-Benachrichtigungen bei geteilten Beiträgen - resubscribe_html: Falls du etwas irrtümlich abbestellt hast, kannst du das in den Einstellungen Benachrichtigungen per E-Mail rückgängig machen. - success_html: Du wirst nicht länger %{type} für Mastodon auf %{domain} an deine E-Mail-Adresse %{email} erhalten. - title: Abbestellen media_attachments: validations: images_and_video: Es kann kein Video an einen Beitrag angehängt werden, der bereits Bilder enthält @@ -1805,6 +1813,7 @@ de: posting_defaults: Standardeinstellungen für Beiträge public_timelines: Öffentliche Timelines privacy: + email_subscriptions: Sende Beiträge per E-Mail hint_html: "Bestimme selbst, wie dein Profil und deine Beiträge gefunden werden sollen. Zahlreiche Mastodon-Funktionen können dir für eine größere Reichweite behilflich sein. Nimm dir einen Moment Zeit, um diese Einstellungen zu überprüfen." privacy: Datenschutz privacy_hint_html: Bestimme, wie viele Informationen du für andere preisgeben möchtest. Viele Menschen entdecken interessante Profile und coole Apps, indem sie die Follower anderer Profile durchstöbern und die Apps sehen, über die Beiträge veröffentlicht wurden – möglicherweise möchtest du diese Informationen ausblenden. @@ -2068,6 +2077,9 @@ de: resume_app_authorization: Autorisierung der App fortsetzen role_requirement: "%{domain} verlangt das Einrichten einer Zwei-Faktor-Authentisierung, bevor du Mastodon verwenden kannst." webauthn: Sicherheitsschlüssel + unsubscriptions: + show: + action: Abbestellen user_mailer: announcement_published: description: 'Ankündigung der Administrator*innen von %{domain}:' diff --git a/config/locales/el.yml b/config/locales/el.yml index de95a5b725..9239f1d93a 100644 --- a/config/locales/el.yml +++ b/config/locales/el.yml @@ -762,6 +762,7 @@ el: categories: administration: Διαχείριση devops: DevOps + email: Email invites: Προσκλήσεις moderation: Συντονισμός special: Ειδικός @@ -790,6 +791,8 @@ el: manage_blocks_description: Επιτρέπει στους χρήστες να αποκλείουν παρόχους email και διευθύνσεις IP manage_custom_emojis: Διαχείριση Προσαρμοσμένων Emojis manage_custom_emojis_description: Επιτρέπει στους χρήστες να διαχειρίζονται προσαρμοσμένα emojis στον διακομιστή + manage_email_subscriptions: Διαχείριση Συνδρομών Email + manage_email_subscriptions_description: Να επιτρέπεται στους χρήστες να εγγράφονται σε χρήστες με αυτήν την άδεια μέσω ηλεκτρονικού ταχυδρομείου manage_federation: Διαχείριση Ομοσπονδίας manage_federation_description: Επιτρέπει στους χρήστες να αποκλείουν ή να επιτρέπουν τις συναλλαγές με άλλους τομείς και να ελέγχουν την παράδοση manage_invites: Διαχείριση Προσκλήσεων @@ -1231,7 +1234,6 @@ el: application_mailer: notification_preferences: Αλλαγή προτιμήσεων email salutation: "%{name}," - settings: 'Αλλαγή προτιμήσεων email: %{link}' unsubscribe: Κατάργηση εγγραφής view: 'Προβολή:' view_profile: Προβολή προφίλ @@ -1419,6 +1421,38 @@ el: basic_information: Βασικές πληροφορίες hint_html: "Προσάρμοσε τί βλέπουν άτομα στο δημόσιο προφίλ σου και δίπλα στις αναρτήσεις σου. Είναι πιο πιθανό άλλα άτομα να σε ακολουθήσουν πίσω και να αλληλεπιδράσουν μαζί σου αν έχεις ολοκληρωμένο προφίλ και εικόνα προφίλ." other: Άλλο + email_subscription_mailer: + confirmation: + action: Επιβεβαιώστε τη διεύθυνση email + instructions_to_confirm: Επιβεβαιώστε ότι θα θέλατε να λαμβάνετε email από %{name} (@%{acct}) όταν δημοσιεύει νέες αναρτήσεις. + instructions_to_ignore: Αν δεν είστε σίγουροι γιατί λάβατε αυτό το email, μπορείτε να το διαγράψετε. Δεν θα εγγραφείτε αν δεν κάνετε κλικ στον παραπάνω σύνδεσμο. + subject: Επιβεβαιώστε τη διεύθυνση email σας + title: Να λαμβάνετε ενημερώσεις μέσω email από %{name}; + notification: + create_account: Δημιουργήστε έναν λογαριασμό Mastodon + footer: + privacy_html: Τα email στέλνονται από το %{domain}, έναν διακομιστή που βασίζεται στο Mastodon. Για να καταλάβετε πώς ο διακομιστής αυτός επεξεργάζεται τα προσωπικά σας δεδομένα, ανατρέξτε στην Πολιτική Απορρήτου. + reason_for_email_html: Λαμβάνετε αυτό το email επειδή έχετε κάνει εγγραφή για ενημερώσεις από %{name}. Δεν θέλετε να λαμβάνετε τέτοια email; Κατάργηση συνδρομής + interact_with_this_post: + one: Αλληλεπιδράστε με αυτήν την ανάρτηση και ανακαλύψτε περισσότερες σαν αυτήν. + other: Αλληλεπιδράστε με αυτές τις αναρτήσεις και ανακαλύψτε περισσότερες. + subject: + one: 'Νέα ανάρτηση: "%{excerpt}"' + other: Νέες αναρτήσεις από %{name} + title: + one: 'Νέα ανάρτηση: "%{excerpt}"' + other: Νέες αναρτήσεις από %{name} + email_subscriptions: + active: Ενεργή + confirmations: + show: + changed_your_mind: Αλλάξατε γνώμη; + success_html: Τώρα θα αρχίσετε να λαμβάνετε email όταν ο χρήστης %{name} δημοσιεύει νέες αναρτήσεις. Προσθέστε το %{sender} στις επαφές σας, έτσι ώστε αυτές οι αναρτήσεις να μην καταλήγουν στο φάκελο Ανεπιθύμητα. + title: Έχετε εγγραφεί + unsubscribe: Κατάργηση συνδρομής + inactive: Ανενεργή + status: Κατάσταση + subscribers: Συνδρομητές emoji_styles: auto: Αυτόματο native: Εγγενές @@ -1653,21 +1687,6 @@ el: failed_sign_in_html: Αποτυχημένη προσπάθεια σύνδεσης με %{method} από %{ip} (%{browser}) successful_sign_in_html: Επιτυχής σύνδεση με %{method} από %{ip} (%{browser}) title: Ιστορικό ελέγχου ταυτότητας - mail_subscriptions: - unsubscribe: - action: Ναι, κατάργηση συνδρομής - complete: Η συνδρομή καταργήθηκε - confirmation_html: Σίγουρα θες να καταργήσεις την εγγραφή σου για %{type} για το Mastodon στο %{domain} στο email σου %{email}; Μπορείς πάντα να εγγραφείς ξανά από τις ρυθμίσεις ειδοποιήσεων email. - emails: - notification_emails: - favourite: ειδοποιήσεις email για αγαπημένα - follow: ειδοποιήσεις email για ακολουθήσεις - follow_request: email για αιτήματα ακολούθησης - mention: ειδοποιήσεις email για επισημάνσεις - reblog: ειδοποιήσεις email για ενίσχυση - resubscribe_html: Αν έχεις καταργήσει την εγγραφή σου κατά λάθος, μπορείς να εγγραφείς εκ νέου από τις ρυθμίσεις ειδοποίησης email. - success_html: Δεν θα λαμβάνεις πλέον %{type} για το Mastodon στο %{domain} στο email σου στο %{email}. - title: Κατάργηση συνδρομής media_attachments: validations: images_and_video: Δεν γίνεται να προσθέσεις βίντεο σε ανάρτηση που ήδη περιέχει εικόνες @@ -1805,6 +1824,8 @@ el: posting_defaults: Προεπιλογές ανάρτησης public_timelines: Δημόσιες ροές privacy: + email_subscriptions: Αποστολή αναρτήσεων μέσω email + email_subscriptions_hint_html: Προσθέστε μια φόρμα εγγραφής μέσω email στο προφίλ σας που εμφανίζεται για αποσυνδεδεμένους χρήστες. Όταν οι επισκέπτες εισαγάγουν τη διεύθυνση email τους και επιλέξουν εγγραφή, το Mastodon θα στέλνει ενημερώσεις email για τις δημόσιες αναρτήσεις σας. hint_html: "Προσάρμοσε πώς θες το προφίλ και οι αναρτήσεις σου να ανακαλύπτονται.. Μια ποικιλία δυνατοτήτων στο Mastodon μπορούν να σε βοηθήσουν να απευθυνθείς σε μεγαλύτερο κοινό όταν ενεργοποιηθούν. Αφιέρωσε μερικά λεπτά για να εξετάσεις τις ρυθμίσεις και να σιγουρευτείς ότι σου ταιριάζουν." privacy: Απόρρητο privacy_hint_html: "'Έλεγξε πόσο θες να αποκαλύπτεις προς όφελος των άλλων. Οι άνθρωποι ανακαλύπτουν ενδιαφέροντα προφίλ και εφαρμογές με την περιήγηση των ακολούθων άλλων ατόμων και βλέποντας από ποιες εφαρμογές δημοσιεύουν, αλλά μπορεί να προτιμάς να το κρατάς κρυφό." @@ -2068,6 +2089,28 @@ el: resume_app_authorization: Συνέχιση εξουσιοδότησης εφαρμογής role_requirement: Το %{domain} απαιτεί να ρυθμίσετε τον έλεγχο ταυτότητας δύο παραγόντων πριν χρησιμοποιήσετε το Mastodon. webauthn: Κλειδιά ασφαλείας + unsubscriptions: + create: + action: Μετάβαση στην αρχική σελίδα του διακομιστή + email_subscription: + confirmation_html: Δεν θα λαμβάνετε πλέον email από %{name}. + title: Έχετε καταργήσει τη συνδρομή + user: + confirmation_html: Δεν θα λαμβάνετε πλέον %{type} από το Mastodon στο %{domain}. + notification_emails: + favourite: ειδοποιήσεις email για αγαπημένα + follow: ειδοποιήσεις email για ακολουθήσεις + follow_request: email για αιτήματα ακολούθησης + mention: ειδοποιήσεις email για επισημάνσεις + reblog: ειδοποιήσεις email για ενισχύσεις + show: + action: Κατάργηση συνδρομής + email_subscription: + confirmation_html: Θα σταματήσετε να λαμβάνετε email όταν αυτός ο λογαριασμός δημοσιεύει νέες αναρτήσεις. + title: Κατάργηση συνδρομής από %{name}; + user: + confirmation_html: Θα σταματήσετε να λαμβάνετε %{type} από το Mastodon στο %{domain}. + title: Κατάργηση συνδρομής από %{type}; user_mailer: announcement_published: description: 'Οι διαχειριστές του %{domain} κάνουν μια ανακοίνωση:' diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml index 815378fbdd..1e970f3bc6 100644 --- a/config/locales/en-GB.yml +++ b/config/locales/en-GB.yml @@ -1229,7 +1229,6 @@ en-GB: application_mailer: notification_preferences: Change email preferences salutation: "%{name}," - settings: 'Change email preferences: %{link}' unsubscribe: Unsubscribe view: 'View:' view_profile: View profile @@ -1649,21 +1648,6 @@ en-GB: failed_sign_in_html: Failed login attempt with %{method} from %{ip} (%{browser}) successful_sign_in_html: Successful login with %{method} from %{ip} (%{browser}) title: Authentication history - mail_subscriptions: - unsubscribe: - action: Yes, unsubscribe - complete: Unsubscribed - confirmation_html: Are you sure you want to unsubscribe from receiving %{type} for Mastodon on %{domain} to your email at %{email}? You can always re-subscribe from your email notification settings. - emails: - notification_emails: - favourite: favourite notification emails - follow: follow notification emails - follow_request: follow request emails - mention: mention notification emails - reblog: boost notification emails - resubscribe_html: If you've unsubscribed by mistake, you can re-subscribe from your email notification settings. - success_html: You'll no longer receive %{type} for Mastodon on %{domain} to your email at %{email}. - title: Unsubscribe media_attachments: validations: images_and_video: Cannot attach a video to a post that already contains images diff --git a/config/locales/en.yml b/config/locales/en.yml index a745021dc1..76b25667df 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -762,6 +762,7 @@ en: categories: administration: Administration devops: DevOps + email: Email invites: Invites moderation: Moderation special: Special @@ -790,6 +791,8 @@ en: manage_blocks_description: Allows users to block email providers and IP addresses manage_custom_emojis: Manage Custom Emojis manage_custom_emojis_description: Allows users to manage custom emojis on the server + manage_email_subscriptions: Manage Email Subscriptions + manage_email_subscriptions_description: Allow users to subscribe to users with this permission by email manage_federation: Manage Federation manage_federation_description: Allows users to block or allow federation with other domains, and control deliverability manage_invites: Manage Invites @@ -1231,7 +1234,6 @@ en: application_mailer: notification_preferences: Change email preferences salutation: "%{name}," - settings: 'Change email preferences: %{link}' unsubscribe: Unsubscribe view: 'View:' view_profile: View profile @@ -1419,6 +1421,41 @@ en: basic_information: Basic information hint_html: "Customize what people see on your public profile and next to your posts. Other people are more likely to follow you back and interact with you when you have a filled out profile and a profile picture." other: Other + redesign_body: Profile editing can now be accessed directly from the profile page. + redesign_button: Go there + redesign_title: There’s a new profile editing experience + email_subscription_mailer: + confirmation: + action: Confirm email address + instructions_to_confirm: Confirm you'd like to receive emails from %{name} (@%{acct}) when they publish new posts. + instructions_to_ignore: If you're not sure why you received this email, you can delete it. You will not be subscribed if you don't click on the link above. + subject: Confirm your email address + title: Get email updates from %{name}? + notification: + create_account: Create a Mastodon account + footer: + privacy_html: Emails are sent from %{domain}, a server powered by Mastodon. To understand how this server processes your personal data, refer to the Privacy Policy. + reason_for_email_html: You're receiving this email because you opted into email updates from %{name}. Don't want to receive these emails? Unsubscribe + interact_with_this_post: + one: Interact with this post and discover more like it. + other: Interact with these posts and discover more. + subject: + one: 'New post: "%{excerpt}"' + other: New posts from %{name} + title: + one: 'New post: "%{excerpt}"' + other: New posts from %{name} + email_subscriptions: + active: Active + confirmations: + show: + changed_your_mind: Changed your mind? + success_html: You'll now start receiving emails when %{name} publishes new posts. Add %{sender} to your contacts so these posts don't end up in your Spam folder. + title: You're signed up + unsubscribe: Unsubscribe + inactive: Inactive + status: Status + subscribers: Subscribers emoji_styles: auto: Auto native: Native @@ -1653,21 +1690,6 @@ en: failed_sign_in_html: Failed sign-in attempt with %{method} from %{ip} (%{browser}) successful_sign_in_html: Successful sign-in with %{method} from %{ip} (%{browser}) title: Authentication history - mail_subscriptions: - unsubscribe: - action: Yes, unsubscribe - complete: Unsubscribed - confirmation_html: Are you sure you want to unsubscribe from receiving %{type} for Mastodon on %{domain} to your email at %{email}? You can always re-subscribe from your email notification settings. - emails: - notification_emails: - favourite: favorite notification emails - follow: follow notification emails - follow_request: follow request emails - mention: mention notification emails - reblog: boost notification emails - resubscribe_html: If you've unsubscribed by mistake, you can re-subscribe from your email notification settings. - success_html: You'll no longer receive %{type} for Mastodon on %{domain} to your email at %{email}. - title: Unsubscribe media_attachments: validations: images_and_video: Cannot attach a video to a post that already contains images @@ -1806,6 +1828,8 @@ en: posting_defaults: Posting defaults public_timelines: Public timelines privacy: + email_subscriptions: Send posts via email + email_subscriptions_hint_html: Add an email sign-up form to your profile that appears for logged-out users. When visitors enter their email address and opt in, Mastodon will send email updates for your public posts. hint_html: "Customize how you want your profile and your posts to be found. A variety of features in Mastodon can help you reach a wider audience when enabled. Take a moment to review these settings to make sure they fit your use case." privacy: Privacy privacy_hint_html: Control how much you want to disclose for the benefit of others. People discover interesting profiles and cool apps by browsing other people's follows and seeing which apps they post from, but you may prefer to keep it hidden. @@ -2069,6 +2093,28 @@ en: resume_app_authorization: Resume application authorization role_requirement: "%{domain} requires you to set up Two-Factor Authentication before you can use Mastodon." webauthn: Security keys + unsubscriptions: + create: + action: Go to server homepage + email_subscription: + confirmation_html: You'll no longer receive emails from %{name}. + title: You are unsubscribed + user: + confirmation_html: You'll no longer receive %{type} from Mastodon on %{domain}. + notification_emails: + favourite: favorite notification emails + follow: follow notification emails + follow_request: follow request emails + mention: mention notification emails + reblog: boost notification emails + show: + action: Unsubscribe + email_subscription: + confirmation_html: You'll stop receiving emails when this account publishes new posts. + title: Unsubscribe from %{name}? + user: + confirmation_html: You'll stop receiving %{type} from Mastodon on %{domain}. + title: Unsubscribe from %{type}? user_mailer: announcement_published: description: 'The administrators of %{domain} are making an announcement:' diff --git a/config/locales/eo.yml b/config/locales/eo.yml index de46b0a7be..195b95f07e 100644 --- a/config/locales/eo.yml +++ b/config/locales/eo.yml @@ -1182,7 +1182,6 @@ eo: application_mailer: notification_preferences: Ŝanĝi retpoŝtajn preferojn salutation: "%{name}," - settings: 'Ŝanĝi retpoŝtajn preferojn: %{link}' unsubscribe: Malabonu view: 'Vidi:' view_profile: Vidi profilon @@ -1590,21 +1589,6 @@ eo: failed_sign_in_html: Malsukcese ensalutprovo per %{method} de %{ip} (%{browser}) successful_sign_in_html: Sukcese ensaluto per %{method} de %{ip} (%{browser}) title: Aŭtentiga historio - mail_subscriptions: - unsubscribe: - action: Jes, malabonu - complete: Malabonita - confirmation_html: Ĉu vi certas, ke vi volas malaboni je ricevi %{type} por Mastodon ĉe %{domain} al via retpoŝto ĉe %{email}? Vi ĉiam povas reaboni de viaj retpoŝtaj sciigaj agordoj. - emails: - notification_emails: - favourite: sciigoj retpoŝtaj de ŝatataj - follow: sciigoj retpoŝtaj de sekvoj - follow_request: retpoŝtajn petoj de sekvado - mention: sciigoj retpoŝtaj de mencioj - reblog: sciigoj retpoŝtaj de diskonigoj - resubscribe_html: Se vi malabonis erare, vi povas reaboni de viaj retpoŝtaj sciigaj agordoj. - success_html: Vi ne plu ricevos %{type} por Mastodon ĉe %{domain} al via retpoŝto ĉe %{email}. - title: Malaboni media_attachments: validations: images_and_video: Aldoni videon al mesaĝo, kiu jam havas bildojn ne eblas diff --git a/config/locales/es-AR.yml b/config/locales/es-AR.yml index e4c8e85622..dc3535a4f0 100644 --- a/config/locales/es-AR.yml +++ b/config/locales/es-AR.yml @@ -762,6 +762,7 @@ es-AR: categories: administration: Administración devops: Operadores de desarrollo + email: Correo electrónico invites: Invitaciones moderation: Moderación special: Especial @@ -790,6 +791,8 @@ es-AR: manage_blocks_description: Permite a los usuarios bloquear proveedores de correo electrónico y direcciones IP manage_custom_emojis: Administrar emojis personalizados manage_custom_emojis_description: Permite a los usuarios administrar emojis personalizados en el servidor + manage_email_subscriptions: Administrar suscripciones de correo electrónico + manage_email_subscriptions_description: Permitir a los usuarios suscribirse a usuarios con este permiso por correo electrónico manage_federation: Administrar Federación manage_federation_description: Permite a los usuarios bloquear o permitir la federación con otros dominios y controlar las entregas manage_invites: Administrar invitaciones @@ -1231,7 +1234,6 @@ es-AR: application_mailer: notification_preferences: Cambiar configuración de correo electrónico salutation: "%{name}:" - settings: 'Cambiar configuración de correo electrónico: %{link}' unsubscribe: Desuscribirse view: 'Visitá:' view_profile: Ver perfil @@ -1419,6 +1421,38 @@ es-AR: basic_information: Información básica hint_html: "Personalizá lo que la gente ve en tu perfil público y junto a tus publicaciones. Es más probable que otras personas te sigan e interactúen con vos cuando tengas un perfil completo y una foto de perfil." other: Otros + email_subscription_mailer: + confirmation: + action: Confirmar dirección de correo electrónico + instructions_to_confirm: Confirmá que querés recibir correos electrónicos de %{name} (@%{acct}) cuando publique algo nuevo. + instructions_to_ignore: Si no estás seguro de por qué recibiste este correo electrónico, podés eliminarlo. Si no hacés clic en el enlace de arriba, no te vas a suscribir. + subject: Confirmá tu dirección de correo electrónico + title: "¿Obtener actualizaciones por correo electrónico de %{name}?" + notification: + create_account: Crear una cuenta de Mastodon + footer: + privacy_html: Los correos electrónicos son enviados desde %{domain}, un servidor de Mastodon. Para conocer cómo este servidor procesa tus datos personales, consultá la Política de Privacidad. + reason_for_email_html: Estás recibiendo este correo porque optaste recibir actualizaciones por correo electrónico de %{name}. ¿No querés recibir estos correos? Desuscribite + interact_with_this_post: + one: Interactuá con esta publicación y descubrí otras similares. + other: Interactuá con estas publicaciones y descubrí otras similares. + subject: + one: 'Nueva publicación: «%{excerpt}»' + other: Nuevos publicaciones de %{name} + title: + one: 'Nueva publicación: «%{excerpt}»' + other: Nuevos mensajes de %{name} + email_subscriptions: + active: Activa + confirmations: + show: + changed_your_mind: "¿Cambiaste de opinión?" + success_html: Ahora vas a empezar a recibir correos electrónicos cuando %{name} publique algo nuevo. Agregá a %{sender} a tus contactos para que estas publicaciones no terminen en tu carpeta de spam o correo no deseado. + title: Te suscribiste + unsubscribe: Desuscribirse + inactive: Inactiva + status: Estado + subscribers: Suscriptores emoji_styles: auto: Automático native: Nativo @@ -1653,21 +1687,6 @@ es-AR: failed_sign_in_html: Intento de inicio de sesión fallido con %{method} desde %{ip} (%{browser}) successful_sign_in_html: Inicio de sesión exitoso con %{method} desde %{ip} (%{browser}) title: Historial de autenticación - mail_subscriptions: - unsubscribe: - action: Sí, desuscribir - complete: Desuscripto - confirmation_html: ¿Estás seguro que querés dejar de recibir %{type} de Mastodon en %{domain} a tu correo electrónico %{email}? Siempre podrás volver a suscribirte desde la configuración de notificaciones por correo electrónico.. - emails: - notification_emails: - favourite: notificaciones de favoritos por correo electrónico - follow: notificaciones de seguidores por correo electrónico - follow_request: notificaciones de solicitudes de seguimiento por correo electrónico - mention: notificaciones de menciones por correo electrónico - reblog: notificaciones de adhesiones por correo electrónico - resubscribe_html: Si te desuscribiste por error, podés resuscribirte desde la configuración de notificaciones por correo electrónico. - success_html: Ya no recibirás %{type} de mastodon en %{domain} a tu correo electrónico %{email}. - title: Desuscribirse media_attachments: validations: images_and_video: No se puede adjuntar un video a un mensaje que ya contenga imágenes @@ -1805,6 +1824,8 @@ es-AR: posting_defaults: Configuración predeterminada de mensajes public_timelines: Líneas temporales públicas privacy: + email_subscriptions: Enviar mensajes por correo electrónico + email_subscriptions_hint_html: Agregá un formulario de registro por correo electrónico a tu perfil, que aparece para los usuarios que no iniciaron sesión. Cuando los visitantes ingresen su dirección de correo electrónico y opten por ello, Mastodon enviará actualizaciones por correo electrónico para tus mensajes públicos. hint_html: "Personalizá cómo querés que sean encontrados tu perfil y tus mensajes. Una variedad de funciones en Mastodon pueden ayudarte a alcanzar una mayor audiencia al estar activada. Tomate un momento para revisar esta configuración para asegurarte de que se ajusta a tu caso." privacy: Privacidad privacy_hint_html: Controlá cuánto querés revelar a los demás. La gente descubre perfiles interesantes y aplicaciones copadas explorando los seguimientos de otras personas y viendo qué aplicaciones usan, pero puede que prefieras mantener esto oculto. @@ -2068,6 +2089,28 @@ es-AR: resume_app_authorization: Reanudar autorización de aplicación role_requirement: "%{domain} requiere que configurés la autenticación de dos factores antes de poder usar Mastodon." webauthn: Llaves de seguridad + unsubscriptions: + create: + action: Ir a la página de inicio del servidor + email_subscription: + confirmation_html: Ya no recibirás correos electrónicos de %{name}. + title: Te desuscribiste + user: + confirmation_html: Ya no recibirás %{type} de Mastodon en %{domain}. + notification_emails: + favourite: correos de notificación de favoritos + follow: correos de notificación de nuevos seguidores + follow_request: correos de notificación de solicitudes de seguimiento + mention: correos de notificación de menciones + reblog: correos de notificación de adhesiones + show: + action: Desuscribirse + email_subscription: + confirmation_html: Vas a dejar de recibir correos cuando esta cuenta publique algo nuevo. + title: "¿Desuscribirse de %{name}?" + user: + confirmation_html: Vas a dejar de recibir %{type} de Mastodon en %{domain}. + title: "¿Desuscribirse de %{type}?" user_mailer: announcement_published: description: 'Los administradores de %{domain} están haciendo un anuncio:' diff --git a/config/locales/es-MX.yml b/config/locales/es-MX.yml index 44677285a7..3b1237bdf8 100644 --- a/config/locales/es-MX.yml +++ b/config/locales/es-MX.yml @@ -762,6 +762,7 @@ es-MX: categories: administration: Administración devops: DevOps + email: Correo electrónico invites: Invitaciones moderation: Moderación special: Especial @@ -790,6 +791,8 @@ es-MX: manage_blocks_description: Permite a los usuarios bloquear proveedores de correo electrónico y direcciones IP manage_custom_emojis: Administrar Emojis Personalizados manage_custom_emojis_description: Permite a los usuarios gestionar emojis personalizados en el servidor + manage_email_subscriptions: Gestionar suscripciones por correo electrónico + manage_email_subscriptions_description: Permitir a los usuarios suscribirse a otros usuarios con este permiso por correo electrónico manage_federation: Administrar Federación manage_federation_description: Permite a los usuarios bloquear o permitir la federación con otros dominios, y controlar la entregabilidad manage_invites: Administrar Invitaciones @@ -1231,7 +1234,6 @@ es-MX: application_mailer: notification_preferences: Cambiar preferencias de correo electrónico salutation: "%{name}:" - settings: 'Cambiar preferencias de correo: %{link}' unsubscribe: Cancelar suscripción view: 'Vista:' view_profile: Ver perfil @@ -1419,6 +1421,38 @@ es-MX: basic_information: Información básica hint_html: "Personaliza lo que la gente ve en tu perfil público junto a tus publicaciones. Es más probable que otras personas te sigan e interactúen contigo cuando completes tu perfil y agregues una foto." other: Otro + email_subscription_mailer: + confirmation: + action: Confirmar dirección de correo electrónico + instructions_to_confirm: Confirma que deseas recibir correos electrónicos de %{name} (@%{acct}) cuando haga una nueva publicación. + instructions_to_ignore: Si no estás seguro de por qué has recibido este correo electrónico, puedes borrarlo. No te suscribirás si no haces clic en el enlace de arriba. + subject: Confirma tu dirección de correo electrónico + title: "¿Deseas recibir actualizaciones por correo electrónico de %{name}?" + notification: + create_account: Crear una cuenta de Mastodon + footer: + privacy_html: Los correos electrónicos se envían desde %{domain} un servidor que utiliza Mastodon. Para saber cómo este servidor procesa tus datos personales, consulta la Política de privacidad. + reason_for_email_html: Recibes este correo electrónico porque te has suscrito a las actualizaciones por correo electrónico de %{name}. ¿No quieres recibir estos correos electrónicos? Cancelar suscripción + interact_with_this_post: + one: Interactúa con esta publicación y descubre otras similares. + other: Interactúa con estas publicaciones y descubre más. + subject: + one: 'Nueva publicación: "%{excerpt}"' + other: Nuevas publicaciones de %{name} + title: + one: 'Nueva publicación: "%{excerpt}"' + other: Nuevas publicaciones de %{name} + email_subscriptions: + active: Activa + confirmations: + show: + changed_your_mind: "¿Has cambiado de opinión?" + success_html: A partir de ahora, recibirás correos electrónicos cada vez que %{name} haga nuevas publicaciones. Añade a %{sender} a tus contactos para que estas publicaciones no terminen en tu carpeta de spam. + title: Ya estás registrado + unsubscribe: Cancelar suscripción + inactive: Inactiva + status: Estado + subscribers: Suscriptores emoji_styles: auto: Automático native: Nativo @@ -1653,21 +1687,6 @@ es-MX: failed_sign_in_html: Intento de inicio de sesión fallido con %{method} de %{ip} (%{browser}) successful_sign_in_html: Inicio de sesión exitoso con %{method} desde %{ip} (%{browser}) title: Historial de autenticación - mail_subscriptions: - unsubscribe: - action: Sí, darse de baja - complete: Has cancelado tu suscripción - confirmation_html: ¿Estás seguro de que quieres dejar de recibir %{type} de Mastodon en %{domain} a tu correo %{email}? Siempre podrás volver a suscribirte de nuevo desde los ajustes de notificación por correo. - emails: - notification_emails: - favourite: correos de notificación de favoritos - follow: correos electrónicos de notificación de seguimiento - follow_request: correos electrónicos de solicitud de seguimiento - mention: correos de notificación de menciones - reblog: correos de notificación de impulsos - resubscribe_html: Si te has dado de baja por error, puedes volver a darte de alta desde tus ajustes de notificaciones por correo. - success_html: Ya no recibirás %{type} de Mastodon en %{domain} a tu correo %{email}. - title: Cancelar suscripción media_attachments: validations: images_and_video: No se puede adjuntar un video a una publicación que ya contenga imágenes @@ -1805,6 +1824,8 @@ es-MX: posting_defaults: Configuración por defecto de publicaciones public_timelines: Líneas de tiempo públicas privacy: + email_subscriptions: Enviar publicaciones por correo electrónico + email_subscriptions_hint_html: Añade un formulario de suscripción por correo electrónico a tu perfil que se muestre a los usuarios que no hayan iniciado sesión. Cuando los visitantes introduzcan su dirección de correo electrónico y se suscriban, Mastodon les enviará actualizaciones por correo electrónico de tus publicaciones públicas. hint_html: "Personaliza cómo te gustaría que tu perfil y tus publicaciones sean encontradas. En Mastodon tienes a tu disposición distintas características que pueden ayudarte a llegar a una audiencia más amplia cuando se encuentran activadas. Toma un momento para revisar estos ajustes para asegurarte si cumplen tus necesidades." privacy: Privacidad privacy_hint_html: Controla cuánto quieres compartir para el beneficio de otros. Las personas descubren perfiles interesantes y aplicaciones geniales al navegar por los seguidores de otras personas y viendo desde cuáles aplicaciones publican, pero también puedes preferir mantenerlo oculto. @@ -2068,6 +2089,28 @@ es-MX: resume_app_authorization: Reanudar autorización de aplicación role_requirement: "%{domain} requiere que configures la autenticación de dos pasos antes de poder utilizar Mastodon." webauthn: Claves de seguridad + unsubscriptions: + create: + action: Ir a la página de inicio del servidor + email_subscription: + confirmation_html: Ya no recibirás correos electrónicos de %{name}. + title: Ya no estás suscrito + user: + confirmation_html: Ya no recibirás %{type} de Mastodon en %{domain}. + notification_emails: + favourite: correos electrónicos de notificación de favoritos + follow: correos electrónicos de notificación de seguimiento + follow_request: correos electrónicos de solicitud de seguimiento + mention: correos de notificación de menciones + reblog: correos de notificación de impulsos + show: + action: Cancelar suscripción + email_subscription: + confirmation_html: Dejarás de recibir correos electrónicos cuando esta cuenta haga nuevas publicaciones. + title: "¿Deseas cancelar suscripción a %{name}?" + user: + confirmation_html: Dejarás de recibir %{type} de Mastodon en %{domain}. + title: "¿Deseas cancelar suscripción a %{type}?" user_mailer: announcement_published: description: 'Los administradores de %{domain} están haciendo un anuncio:' diff --git a/config/locales/es.yml b/config/locales/es.yml index c7de434673..90f631b49a 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -762,6 +762,7 @@ es: categories: administration: Administración devops: DevOps + email: Correo electrónico invites: Invitaciones moderation: Moderación special: Especial @@ -790,6 +791,8 @@ es: manage_blocks_description: Permite a los usuarios bloquear proveedores de correo electrónico y direcciones IP manage_custom_emojis: Administrar Emojis Personalizados manage_custom_emojis_description: Permite a los usuarios gestionar emojis personalizados en el servidor + manage_email_subscriptions: Administrar suscripciones de correo electrónico + manage_email_subscriptions_description: Permitir a los usuarios suscribirse a usuarios con este permiso por correo electrónico manage_federation: Administrar Federación manage_federation_description: Permite a los usuarios bloquear o permitir la federación con otros dominios, y controlar la entregabilidad manage_invites: Administrar Invitaciones @@ -1231,7 +1234,6 @@ es: application_mailer: notification_preferences: Cambiar preferencias de correo electrónico salutation: "%{name}:" - settings: 'Cambiar preferencias de correo: %{link}' unsubscribe: Cancelar suscripción view: 'Vista:' view_profile: Ver perfil @@ -1419,6 +1421,38 @@ es: basic_information: Información básica hint_html: "Personaliza lo que la gente ve en tu perfil público junto a tus publicaciones. Es más probable que otras personas te sigan e interactúen contigo cuando completas tu perfil y foto." other: Otros + email_subscription_mailer: + confirmation: + action: Confirmar dirección de correo electrónico + instructions_to_confirm: Confirma que quieres recibir correos electrónicos de %{name} (@%{acct}) cuando publique algo nuevo. + instructions_to_ignore: Si no estás seguro de por qué recibiste este correo electrónico, puedes eliminarlo. No te suscribirás si no haces clic en el enlace de arriba. + subject: Confirme tu dirección de correo electrónico + title: "¿Obtener actualizaciones por correo electrónico de %{name}?" + notification: + create_account: Crear una cuenta de Mastodon + footer: + privacy_html: Los correos electrónicos son enviados desde %{domain}, un servidor de Mastodon. Para conocer cómo este servidor procesa tus datos personales, consulta la Política de Privacidad. + reason_for_email_html: Estás recibiendo este correo porque has optado por recibir actualizaciones por correo electrónico de %{name}. ¿No quieres recibir estos correos? Cancela la suscripción + interact_with_this_post: + one: Interactúa con esta publicación y descubre otras similares. + other: Interactúa con estas publicaciones y descubre otras similares. + subject: + one: 'Nueva publicación: "%{excerpt}"' + other: Nuevas publicaciones de %{name} + title: + one: 'Nueva publicación: "%{excerpt}"' + other: Nuevas publicaciones de %{name} + email_subscriptions: + active: Activa + confirmations: + show: + changed_your_mind: "¿Cambiaste de opinión?" + success_html: Ahora empezarás a recibir correos electrónicos cuando %{name} publique algo nuevo. Añade %{sender} a tus contactos para que estas publicaciones no terminen en tu carpeta de Spam. + title: Estás suscrito + unsubscribe: Cancelar suscripición + inactive: Inactiva + status: Estado + subscribers: Suscriptores emoji_styles: auto: Automático native: Nativo @@ -1653,21 +1687,6 @@ es: failed_sign_in_html: Intento de inicio de sesión fallido con %{method} de %{ip} (%{browser}) successful_sign_in_html: Inicio de sesión exitoso con %{method} desde %{ip} (%{browser}) title: Historial de autenticación - mail_subscriptions: - unsubscribe: - action: Sí, cancelar suscripción - complete: Has cancelado tu suscripción - confirmation_html: ¿Estás seguro de que quieres dejar de recibir %{type} de Mastodon en %{domain} a tu correo %{email}? Siempre podrás volver a suscribirte de nuevo desde los ajustes de notificación por correo. - emails: - notification_emails: - favourite: correos de notificación de favoritos - follow: correos de notificación de nuevos seguidores - follow_request: correos de notificación de solicitud de seguidor - mention: correos de notificación de menciones - reblog: correos de notificación de impulsos - resubscribe_html: Si te has dado de baja por error, puedes volver a darte de alta desde tus ajustes de notificaciones por correo. - success_html: Ya no recibirás %{type} de Mastodon en %{domain} a tu correo %{email}. - title: Cancelar suscripición media_attachments: validations: images_and_video: No se puede adjuntar un video a una publicación que ya contenga imágenes @@ -1805,6 +1824,8 @@ es: posting_defaults: Configuración por defecto de publicaciones public_timelines: Líneas de tiempo públicas privacy: + email_subscriptions: Enviar mensajes por correo electrónico + email_subscriptions_hint_html: Añade un formulario de registro por correo electrónico a tu perfil, que aparece para los usuarios que no han iniciado sesión. Cuando los visitantes ingresen su dirección de correo electrónico y opten por ello, Mastodon enviará actualizaciones por correo electrónico para tus publicaciones públicas. hint_html: "Personaliza el descubrimiento de tu perfil y tus publicaciones. En Mastodon tienes distintas características que te ayudarán a alcanzar una mayor audiencia si las activas. Tómate un momento para revisar estas configuraciones y asegurarte de que cumplen tus necesidades." privacy: Privacidad privacy_hint_html: Controla cuánto deseas revelar a los demás. Las personas descubren perfiles y aplicaciones interesantes navegando por los seguidores de otras personas y viendo desde qué aplicaciones publican, pero puede que prefieras mantenerlo oculto. @@ -2068,6 +2089,28 @@ es: resume_app_authorization: Reanudar autorización de aplicación role_requirement: "%{domain} requiere que establezcas la autenticación en dos pasos para poder usar Mastodon." webauthn: Claves de seguridad + unsubscriptions: + create: + action: Ir a la página de inicio del servidor + email_subscription: + confirmation_html: Ya no recibirás correos electrónicos de %{name}. + title: Ya no estás suscrito + user: + confirmation_html: Ya no recibirás %{type} de Mastodon en %{domain}. + notification_emails: + favourite: correos de notificación de favoritos + follow: correos de notificación de nuevos seguidores + follow_request: correos de notificación de solicitudes de seguimiento + mention: correos de notificación de menciones + reblog: correos de notificación de impulsos + show: + action: Cancelar suscripción + email_subscription: + confirmation_html: Dejarás de recibir correos cuando esta cuenta publique algo nuevo. + title: "¿Darse de baja de %{name}?" + user: + confirmation_html: Dejarás de recibir %{type} de Mastodon en %{domain}. + title: "¿Darse de baja de %{type}?" user_mailer: announcement_published: description: 'Los administradores de %{domain} están haciendo un anuncio:' diff --git a/config/locales/et.yml b/config/locales/et.yml index ddd0572cfc..8a6e8b39bc 100644 --- a/config/locales/et.yml +++ b/config/locales/et.yml @@ -1214,7 +1214,6 @@ et: application_mailer: notification_preferences: Muuda e-posti eelistusi salutation: "%{name}!" - settings: 'Muuda e-posti eelistusi: %{link}' unsubscribe: Loobu tellimisest view: 'Vaade:' view_profile: Vaata profiili @@ -1634,21 +1633,6 @@ et: failed_sign_in_html: Nurjunud sisenemine meetodiga %{method} aadressilt %{ip} (%{browser}) successful_sign_in_html: Edukas sisenemine meetodiga %{method} aadressilt %{ip} (%{browser}) title: Autentimise ajalugu - mail_subscriptions: - unsubscribe: - action: Jah, lõpeta tellimine - complete: Tellimine lõpetatud - confirmation_html: Kas oled kindel, et soovid loobuda %{type} tellimisest oma e-postiaadressile %{email} Mastodonist kohas %{domain}? Saad alati uuesti tellida oma e-posti teavituste seadetest. - emails: - notification_emails: - favourite: lemmikuks märkimise teavituskirjade - follow: jälgimiste teavituskirjade - follow_request: jälgimistaotluste teavituskirjade - mention: mainimiste teavituskirjade - reblog: jagamiste teavituskirjade - resubscribe_html: Kui loobusid tellimisest ekslikult, saad uuesti tellida oma e-posti teavituste seadetest. - success_html: Sa ei saa enam %{type} teateid oma e-postile %{email} Mastodonist kohas %{domain}. - title: Loobu tellimisest media_attachments: validations: images_and_video: Ei saa lisada video postitusele, milles on juba pildid diff --git a/config/locales/eu.yml b/config/locales/eu.yml index 0102508797..85b6eb08f5 100644 --- a/config/locales/eu.yml +++ b/config/locales/eu.yml @@ -1171,7 +1171,6 @@ eu: application_mailer: notification_preferences: Posta elektronikoaren lehentasunak aldatu salutation: "%{name}," - settings: 'Posta elektronikoaren lehentasunak aldatu: %{link}' unsubscribe: Kendu harpidetza view: 'Ikusi:' view_profile: Ikusi profila @@ -1536,23 +1535,6 @@ eu: failed_sign_in_html: Huts egindako saioa hasteko saiakera %{method} erabiliz %{ip} IPtik (%{browser}) successful_sign_in_html: Saioa hasiera arrakastatsua %{method} erabiliz %{ip} IPtik (%{browser}) title: Autentifikazioen historia - mail_subscriptions: - unsubscribe: - action: Bai, kendu harpidetza - complete: Harpidetza kenduta - confirmation_html: |- - Ziur Mastodonen %{domain} zerbitzariko %{type} %{email} helbide elektronikoan jasotzeari utzi nahi diozula? - Beti harpidetu zaitezke berriro eposta jakinarazpenen hobespenetan. - emails: - notification_emails: - favourite: zure argitalpena gogoko egin dutenaren jakinarazpen e-mailak - follow: jarraitu jakinarazpen-mezu elektronikoak - follow_request: jarraipen-eskaeren jakinarazpen e-mailak - mention: aipamenen jakinarazpen e-mailak - reblog: bultzaden jakinarazpen e-mailak - resubscribe_html: Nahi gabe utzi badiozu jakinarazpenak jasotzeari, berriro harpidetu zaitezke e-mail jakinarazpenen hobespenetan. - success_html: Ez duzu Mastodonen %{domain} zerbitzariko %{type} jasoko %{email} helbide elektronikoan. - title: Kendu harpidetza media_attachments: validations: images_and_video: Ezin da irudiak dituen bidalketa batean bideo bat erantsi diff --git a/config/locales/fa.yml b/config/locales/fa.yml index b166d1f304..50e5a8576f 100644 --- a/config/locales/fa.yml +++ b/config/locales/fa.yml @@ -1210,7 +1210,6 @@ fa: application_mailer: notification_preferences: تغییر ترجیحات رایانامه salutation: "%{name}،" - settings: 'تغییر ترجیحات رایانامه: %{link}' unsubscribe: لغو اشتراک view: 'نمایش:' view_profile: دیدن نمایه @@ -1630,21 +1629,6 @@ fa: failed_sign_in_html: تلاش‌های شکست‌خوردهٔ ورود با %{method} از %{ip} (%{browser}) successful_sign_in_html: ورودهای موفق با %{method} از %{ip} (%{browser}) title: تاریخچهٔ تأیید هویت - mail_subscriptions: - unsubscribe: - action: بله. لغو اشتراک - complete: لغو اشتراک شد - confirmation_html: مطمئنید که می‌خواهید اشتراک %{type} را از ماستودون روی %{domain} برای رایانامهٔ %{email} لغو کنید؟ همواره می‌توانید از تنظیمات آگاهی رایانامه‌ای دوباره مشترک شوید. - emails: - notification_emails: - favourite: رایانامه‌های آگاهی برگزیدن - follow: رایانامه‌های آگاهی پی‌گیری - follow_request: رایانامه‌های درخواست پی‌گیری - mention: رایانامه‌های آگاهی اشاره - reblog: رایانامه‌های آگاهی تقویت - resubscribe_html: اگر اشتراک را اشتباهی لغو کردید می‌توانید از تنظیمات آگاهی رایانامه‌ای دوباره مشترک شوید. - success_html: دیگر %{type} را از ماستودون روی %{domain} برای رایانامهٔ %{email} نخواهید گرفت. - title: لغو اشتراک media_attachments: validations: images_and_video: نمی‌توان برای نوشته‌ای که تصویر دارد ویدیو بارگذاری کرد diff --git a/config/locales/fi.yml b/config/locales/fi.yml index 77d938b6b7..d0828dfbce 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -1231,7 +1231,6 @@ fi: application_mailer: notification_preferences: Muuta sähköpostiasetuksia salutation: "%{name}" - settings: 'Muuta sähköpostiasetuksia: %{link}' unsubscribe: Lopeta tilaus view: 'Näytä:' view_profile: Näytä profiili @@ -1653,21 +1652,6 @@ fi: failed_sign_in_html: Epäonnistunut kirjautumisyritys %{method} IP-osoitteesta %{ip} (%{browser}) successful_sign_in_html: Onnistunut kirjautuminen %{method} IP-osoitteesta %{ip} (%{browser}) title: Todennushistoria - mail_subscriptions: - unsubscribe: - action: Kyllä, peru tilaus - complete: Tilaus lopetettiin - confirmation_html: Haluatko varmasti lopettaa Mastodonin sähköposti-ilmoitusten vastaanottamisen aiheesta %{type} palvelimelta %{domain} osoitteeseesi %{email}? Voit tilata ilmoitusviestejä milloin tahansa uudelleen sähköposti-ilmoitusten asetuksista. - emails: - notification_emails: - favourite: sähköposti-ilmoituksia suosikkeihin lisäämisistä - follow: sähköposti-ilmoituksia seuraamisista - follow_request: sähköposti-ilmoituksia seurantapyynnöistä - mention: sähköposti-ilmoituksia maininnoista - reblog: sähköposti-ilmoituksia tehostuksista - resubscribe_html: Jos olet perunut tilauksen erehdyksessä, voit tilata ilmoitusviestejä uudelleen sähköposti-ilmoitusten asetuksista. - success_html: Sinulle ei enää lähetetä Mastodonin %{type} palvelimelta %{domain} osoitteeseen %{email}. - title: Lopeta tilaus media_attachments: validations: images_and_video: Videota ei voi liittää tilapäivitykseen, jossa on jo kuvia diff --git a/config/locales/fo.yml b/config/locales/fo.yml index 36b2dc5c68..057afbec78 100644 --- a/config/locales/fo.yml +++ b/config/locales/fo.yml @@ -1229,7 +1229,6 @@ fo: application_mailer: notification_preferences: Broyt teldupostastillingar salutation: "%{name}" - settings: 'Broyt teldupostastillingar: %{link}' unsubscribe: Strika hald view: 'Vís:' view_profile: Vís vanga @@ -1649,21 +1648,6 @@ fo: failed_sign_in_html: Miseydnað innritanarroynd við %{method} frá %{ip} (%{browser}) successful_sign_in_html: Eydnað innritan við %{method} frá %{ip} (%{browser}) title: Samgildissøga - mail_subscriptions: - unsubscribe: - action: Ja, strika hald - complete: Hald strikað - confirmation_html: Ert tú vís/ur í, at tú vil gevast at móttaka %{type} fyri Mastodon á %{domain} til tína teldupostadressu á %{email}? Tú kanst altíð gera haldið virkið aftur frá tínum teldupostfráboðanarstillingum. - emails: - notification_emails: - favourite: yndisfráboðanarteldupostar - follow: fylg fráboðanarteldupostar - follow_request: fylg umbønir um teldupost - mention: nevn fráboðanarteldupostar - reblog: framhevja fráboðanarpostar - resubscribe_html: Um tú hevur strikað haldið av misgávum, so kanst tú tekna haldið av nýggjum í tínum teldupostfráboðarstillingum. - success_html: Tú fer ikki longur at móttaka %{type} fyri Mastodon á %{domain} til tín teldupost á %{email}. - title: Strika hald media_attachments: validations: images_and_video: Kann ikki viðfesta sjónfílu til ein post, sum longu inniheldur myndir diff --git a/config/locales/fr-CA.yml b/config/locales/fr-CA.yml index 14e7e5a3d8..c255371b9b 100644 --- a/config/locales/fr-CA.yml +++ b/config/locales/fr-CA.yml @@ -1234,7 +1234,6 @@ fr-CA: application_mailer: notification_preferences: Modification des préférences de la messagerie salutation: "%{name}," - settings: 'Modifier les préférences de la messagerie : %{link}' unsubscribe: Se désabonner view: 'Voir :' view_profile: Voir le profil @@ -1656,21 +1655,6 @@ fr-CA: failed_sign_in_html: Tentative de connexion échouée avec %{method} de %{ip} (%{browser}) successful_sign_in_html: Connexion réussie avec %{method} de %{ip} (%{browser}) title: Historique d'authentification - mail_subscriptions: - unsubscribe: - action: Oui, me désabonner - complete: Désabonné·e - confirmation_html: Êtes-vous sûr de vouloir vous désabonner de la réception de %{type} pour Mastodon sur %{domain} à votre adresse e-mail %{email} ? Vous pouvez toujours vous réabonner à partir de vos paramètres de notification par messagerie. - emails: - notification_emails: - favourite: e-mails de notifications de favoris - follow: e-mails de notifications d’abonnements - follow_request: e-mails de demandes d’abonnements - mention: e-mails de notifications de mentions - reblog: e-mails de notifications de partages - resubscribe_html: Si vous vous êtes désinscrit par erreur, vous pouvez vous réinscrire à partir de vos paramètres de notification par e-mail. - success_html: Vous ne recevrez plus de %{type} pour Mastodon sur %{domain} à votre adresse e-mail à %{email}. - title: Se désabonner media_attachments: validations: images_and_video: Impossible de joindre une vidéo à un message contenant déjà des images diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 3407c4515b..b8fe93c5e0 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -80,7 +80,7 @@ fr: enabled_msg: Le compte de %{username} a été dégelé avec succès followers: Abonné·e·s follows: Abonnements - header: Entête + header: Image de couverture inbox_url: URL d’entrée invite_request_text: Raisons de l’adhésion invited_by: Invité par @@ -128,7 +128,7 @@ fr: remote_suspension_irreversible: Les données de ce compte ont été supprimées définitivement. remote_suspension_reversible_hint_html: Ce compte a été suspendu par son serveur d'accueil, et les données rattachées seront supprimées le %{date}. Jusqu'à cette date, il peut être restauré sans aucune perte par le serveur distant. Si vous souhaitez supprimer immédiatement toutes les données de ce compte, vous pouvez le faire ci-dessous. remove_avatar: Supprimer l’avatar - remove_header: Supprimer l’entête + remove_header: Supprimer l'image de couverture removed_avatar_msg: L’avatar de %{username} a été supprimé avec succès removed_header_msg: L’image d’en-tête de %{username} a été supprimée avec succès resend_confirmation: @@ -1234,7 +1234,6 @@ fr: application_mailer: notification_preferences: Modification des préférences de la messagerie salutation: "%{name}," - settings: 'Modifier les préférences de la messagerie : %{link}' unsubscribe: Se désabonner view: 'Voir :' view_profile: Voir le profil @@ -1656,21 +1655,6 @@ fr: failed_sign_in_html: Tentative de connexion échouée avec %{method} de %{ip} (%{browser}) successful_sign_in_html: Connexion réussie avec %{method} de %{ip} (%{browser}) title: Historique d'authentification - mail_subscriptions: - unsubscribe: - action: Oui, se désinscrire - complete: Désinscrit - confirmation_html: Êtes-vous sûr de vouloir vous désabonner de la réception de %{type} pour Mastodon sur %{domain} à votre adresse e-mail %{email} ? Vous pouvez toujours vous réabonner à partir de vos paramètres de notification par messagerie. - emails: - notification_emails: - favourite: e-mails de notifications de favoris - follow: e-mails de notifications d’abonnements - follow_request: e-mails de demandes d’abonnements - mention: e-mails de notifications de mentions - reblog: e-mails de notifications de partages - resubscribe_html: Si vous vous êtes désinscrit par erreur, vous pouvez vous réinscrire à partir de vos paramètres de notification par e-mail. - success_html: Vous ne recevrez plus de %{type} pour Mastodon sur %{domain} à votre adresse e-mail à %{email}. - title: Se désinscrire media_attachments: validations: images_and_video: Impossible de joindre une vidéo à un message contenant déjà des images diff --git a/config/locales/fy.yml b/config/locales/fy.yml index 4959b1adaa..55381b4ba6 100644 --- a/config/locales/fy.yml +++ b/config/locales/fy.yml @@ -1189,7 +1189,6 @@ fy: application_mailer: notification_preferences: E-mailynstellingen wizigje salutation: "%{name}," - settings: 'E-mailfoarkarren wizigje: %{link}' unsubscribe: Ofmelde view: 'Besjoch:' view_profile: Profyl besjen @@ -1595,21 +1594,6 @@ fy: failed_sign_in_html: Mislearre oanmeldbesykjen mei %{method} fan %{ip} (%{browser}) successful_sign_in_html: Mei sukses oanmeld mei %{method} fan %{ip} (%{browser}) title: Oanmeldskiednis - mail_subscriptions: - unsubscribe: - action: Ja, ôfmelde - complete: Ofmelden - confirmation_html: Binne jo wis dat jo jo ôfmelde wolle foar it ûntfangen fan %{type} fan Mastodon op %{domain} op jo e-mailadres %{email}? Jo kinne jo altyd opnij abonnearje yn jo ynstellingen foar e-mailmeldingen. - emails: - notification_emails: - favourite: e-mailmeldingen foar favoriten - follow: e-mailmeldingen foar nije folgers - follow_request: e-mailmeldingen foar folchfersiken - mention: e-mailmeldingen foar fermeldingen - reblog: e-mailmeldingen foar boosts - resubscribe_html: As jo jo mei fersin ôfmeld hawwe, kinne jo jo opnij abonnearje yn jo ynstellingen foar e-mailmeldingen. - success_html: Jo ûntfange net langer %{type} fan Mastodon op %{domain} op jo e-mailadres %{email}. - title: Ofmelde media_attachments: validations: images_and_video: In fideo kin net oan in berjocht mei ôfbyldingen keppele wurde diff --git a/config/locales/ga.yml b/config/locales/ga.yml index 1295a4efc5..00909fdcd2 100644 --- a/config/locales/ga.yml +++ b/config/locales/ga.yml @@ -1296,7 +1296,6 @@ ga: application_mailer: notification_preferences: Athraigh roghanna ríomhphoist salutation: "%{name}," - settings: 'Athraigh sainroghanna ríomhphoist: %{link}' unsubscribe: Díliostáil view: 'Amharc:' view_profile: Féach ar phróifíl @@ -1778,21 +1777,6 @@ ga: failed_sign_in_html: Theip ar iarracht síniú isteach le %{method} ó %{ip} (%{browser}) successful_sign_in_html: D'éirigh le síniú isteach le %{method} ó %{ip} (%{browser}) title: Stair fíordheimhnithe - mail_subscriptions: - unsubscribe: - action: Sea, díliostáil - complete: Gan liostáil - confirmation_html: An bhfuil tú cinnte gur mhaith leat díliostáil ó %{type} a fháil do Mastodon ar %{domain} chuig do ríomhphost ag %{email}? Is féidir leat liostáil arís i gcónaí ó do socruithe fógra ríomhphoist. - emails: - notification_emails: - favourite: ríomhphoist fógra is fearr leat - follow: leanúint ríomhphoist fógra - follow_request: lean ríomhphoist iarratais - mention: trácht ar ríomhphoist fógra - reblog: ríomhphoist fógraí a threisiú - resubscribe_html: Má dhíliostáil tú de dhearmad, is féidir leat liostáil arís ó do socruithe fógra ríomhphoist. - success_html: Ní bhfaighidh tú %{type} le haghaidh Mastodon ar %{domain} chuig do ríomhphost ag %{email} a thuilleadh. - title: Díliostáil media_attachments: validations: images_and_video: Ní féidir físeán a cheangal le postáil a bhfuil íomhánna ann cheana féin diff --git a/config/locales/gd.yml b/config/locales/gd.yml index c619e7818b..7eaf6a9852 100644 --- a/config/locales/gd.yml +++ b/config/locales/gd.yml @@ -1248,7 +1248,6 @@ gd: application_mailer: notification_preferences: Atharraich roghainnean a’ phuist-d salutation: "%{name}," - settings: 'Atharraich roghainnean a’ phuist-d: %{link}' unsubscribe: Cuir crìoch air an fho-sgrìobhadh view: 'Faic:' view_profile: Seall a’ phròifil @@ -1708,21 +1707,6 @@ gd: failed_sign_in_html: Oidhirp clàraidh a-steach nach deach leis le %{method} o %{ip} (%{browser}) successful_sign_in_html: Oidhirp clàraidh a-steach a shoirbhich leis le %{method} o %{ip} (%{browser}) title: Eachdraidh an dearbhaidh - mail_subscriptions: - unsubscribe: - action: Tha, cuir crìoch air an fho-sgrìobhadh - complete: Chaidh crìoch a chur air an fho-sgrìobhadh - confirmation_html: A bheil thu cinnteach nach eil thu airson %{type} fhaighinn tuilleadh o Mhastodon air %{domain} dhan post-d agad aig %{email}? ’S urrainn dhut fo-sgrìobhadh a-rithist uair sam bith o roghainnean a’ puist-d agad. - emails: - notification_emails: - favourite: puist-d le brathan mu annsachdan - follow: puist-d le brathan mu leantainn - follow_request: puist-d le brathan mu iarrtasan leantainn - mention: puist-d le brathan mu iomraidhean - reblog: puist-d le brathan mu bhrosnachaidhean - resubscribe_html: Ma chuir thu crìoch air an fho-sgrìobhadh le mearachd, ’s urrainn dhut fo-sgrìobhadh a-rithist o roghainnean a’ puist-d agad. - success_html: Chan fhaigh thu %{type} o Mhastodon air %{domain} dhan phost-d agad aig %{email} tuilleadh. - title: Cuir crìoch air an fho-sgrìobhadh media_attachments: validations: images_and_video: Chan urrainn dhut video a cheangal ri post sa bheil dealbh mu thràth diff --git a/config/locales/gl.yml b/config/locales/gl.yml index 469f7badd7..57e6a5fc9c 100644 --- a/config/locales/gl.yml +++ b/config/locales/gl.yml @@ -762,6 +762,7 @@ gl: categories: administration: Administración devops: DevOps + email: Correo electrónico invites: Convites moderation: Moderación special: Especial @@ -1231,7 +1232,6 @@ gl: application_mailer: notification_preferences: Cambiar preferencias de correo salutation: "%{name}," - settings: 'Cambiar preferencias de correo: %{link}' unsubscribe: Anular subscrición view: 'Vista:' view_profile: Ver perfil @@ -1653,21 +1653,6 @@ gl: failed_sign_in_html: Intento de acceso errado con %{method} desde %{ip} (%{browser}) successful_sign_in_html: Acceso correcto con %{method} desde %{ip} (%{browser}) title: Historial de autenticación - mail_subscriptions: - unsubscribe: - action: Si, retirar subscrición - complete: Subscrición anulada - confirmation_html: Tes a certeza de querer retirar a subscrición a Mastodon en %{domain} para recibir %{type} no teu correo electrónico en %{email}? Poderás volver a subscribirte desde os axustes de notificacións por correo. - emails: - notification_emails: - favourite: notificacións de favorecidas - follow: notificacións de seguimentos - follow_request: notificacións de solicitudes de seguimento - mention: notificacións de mencións - reblog: notificacións de promocións - resubscribe_html: Se por un erro eliminaches a subscrición, podes volver a subscribirte desde os axustes de notificacións por correo electrónico. - success_html: Non vas recibir %{type} de Mastodon en %{domain} no enderezo %{email}. - title: Anular subscrición media_attachments: validations: images_and_video: Non podes anexar un vídeo a unha publicación que xa contén imaxes diff --git a/config/locales/he.yml b/config/locales/he.yml index d18d44689d..3732077701 100644 --- a/config/locales/he.yml +++ b/config/locales/he.yml @@ -1273,7 +1273,6 @@ he: application_mailer: notification_preferences: שינוי העדפות דוא"ל salutation: "%{name}," - settings: 'שינוי הגדרות דוא"ל: %{link}' unsubscribe: בטל מנוי view: 'תצוגה:' view_profile: צפיה בפרופיל @@ -1735,21 +1734,6 @@ he: failed_sign_in_html: נסיון כניסה כושל בשיטת %{method} מכתובת %{ip} (%{browser}) successful_sign_in_html: נסיון כניסה מוצלח בשיטה %{method} מכתובת %{ip} (%{browser}) title: הסטוריית אימותים - mail_subscriptions: - unsubscribe: - action: כן, לבטל הרשמה - complete: הפסקת הרשמה - confirmation_html: יש לאשר את ביטול ההרשמה להודעות %{type} ממסטודון בשרת %{domain} לכתובת הדואל %{email}. תמיד אפשר להרשם מחדש בכיוונוני הודעות דואל. - emails: - notification_emails: - favourite: הודעות דואל לגבי חיבובים - follow: הודעות דואל לגבי עוקבים חדשים - follow_request: הודעות דואל לגבי בקשות מעקב - mention: הודעות דואל לגבי איזכורים - reblog: הודעות דואל לגבי הידהודים - resubscribe_html: אם ביטול ההרשמה היה בטעות, ניתן להרשם מחדש מתוך מסך הגדרות ההרשמה שלך. - success_html: לא יגיעו אליך יותר הודעות %{type} משרת מסטודון %{domain} לכתובת הדואל %{email}. - title: הפסקת הרשמה media_attachments: validations: images_and_video: לא ניתן להוסיף וידאו להודעה שכבר מכילה תמונות diff --git a/config/locales/hu.yml b/config/locales/hu.yml index f8cbce5cc3..d633054b17 100644 --- a/config/locales/hu.yml +++ b/config/locales/hu.yml @@ -1231,7 +1231,6 @@ hu: application_mailer: notification_preferences: E-mail-beállítások módosítása salutation: "%{name}!" - settings: 'E-mail-beállítások módosítása: %{link}' unsubscribe: Leiratkozás view: 'Megtekintés:' view_profile: Profil megtekintése @@ -1653,21 +1652,6 @@ hu: failed_sign_in_html: Bejelentkezés meghiúsult ezzel %{method} innen %{ip} (%{browser}) successful_sign_in_html: Sikeres bejelentkezés ezzel %{method} innen %{ip} (%{browser}) title: Hitelesítési történet - mail_subscriptions: - unsubscribe: - action: Igen, leiratkozás - complete: Leiratkozva - confirmation_html: 'Biztos, hogy leiratkozol arról, hogy %{type} típusú üzeneteket kapj a(z) %{domain} Mastodon-kiszolgálótól erre a címedre: %{email}? Bármikor újra feliratkozhatsz az e-mail-értesítési beállításokban.' - emails: - notification_emails: - favourite: kedvencnek jelölés értesítő e-mailjei - follow: követés értesítő e-mailjei - follow_request: követési kérések e-mailjei - mention: megemlítés értesítő e-mailjei - reblog: megtolás értesítő e-mailjei - resubscribe_html: Ha tévedésből iratkoztál le, újra feliratkozhatsz az e-mail-értesítési beállításoknál. - success_html: 'Mostantól nem kapsz %{type} típusú üzeneket a(z) %{domain} Mastodon-kiszolgálón erre a címedre: %{email}.' - title: Leiratkozás media_attachments: validations: images_and_video: Nem csatolhatsz videót olyan bejegyzéshez, amelyhez már csatoltál képet diff --git a/config/locales/ia.yml b/config/locales/ia.yml index 3fea1ed91c..babee4f9aa 100644 --- a/config/locales/ia.yml +++ b/config/locales/ia.yml @@ -1207,7 +1207,6 @@ ia: application_mailer: notification_preferences: Cambiar preferentias de e-mail salutation: "%{name}," - settings: 'Cambiar preferentias de e-mail: %{link}' unsubscribe: Cancellar subscription view: 'Visita:' view_profile: Vider profilo @@ -1620,21 +1619,6 @@ ia: failed_sign_in_html: Tentativa de authentication fallite con %{method} ab %{ip} (%{browser}) successful_sign_in_html: Apertura de session succedite con %{method} desde %{ip} (%{browser}) title: Historia de authentication - mail_subscriptions: - unsubscribe: - action: Si, cancellar subscription - complete: Desubscribite - confirmation_html: Es tu secur de voler cancellar le subscription al %{type} de Mastodon sur %{domain} pro tu adresse de e-mail %{email}? Tu pote sempre resubscriber te a partir del parametros de notification in e-mail. - emails: - notification_emails: - favourite: notificationes de favorites in e-mail - follow: notificationes de sequimento in e-mail - follow_request: requestas de sequimento in e-mail - mention: notificationes de mentiones in e-mail - reblog: notificationes de impulsos in e-mail - resubscribe_html: Si tu ha cancellate le subscription in error, tu pote resubscriber te a partir del parametros de notification in e-mail. - success_html: Tu non recipera plus %{type} pro Mastodon sur %{domain} a tu adresse de e-mail %{email}. - title: Desubcriber media_attachments: validations: images_and_video: Impossibile annexar un video a un message que jam contine imagines diff --git a/config/locales/ie.yml b/config/locales/ie.yml index 1576504444..b28cbbb70b 100644 --- a/config/locales/ie.yml +++ b/config/locales/ie.yml @@ -1328,11 +1328,6 @@ ie: failed_sign_in_html: Fallit prova de apertion de session per %{method} de %{ip} (%{browser}) successful_sign_in_html: Successosi apertion de session per %{method} de %{ip} (%{browser}) title: Historie de autentication - mail_subscriptions: - unsubscribe: - action: Yes, desabonnar - complete: Desabonnat - title: Desabonnar media_attachments: validations: images_and_video: On ne posse atachar un video a un posta quel ja contene images diff --git a/config/locales/io.yml b/config/locales/io.yml index 58ae0f015d..85aa06234a 100644 --- a/config/locales/io.yml +++ b/config/locales/io.yml @@ -1104,7 +1104,6 @@ io: application_mailer: notification_preferences: Chanjar retpostopreferaji salutation: "%{name}," - settings: 'Chanjar retpostopreferaji: %{link}' unsubscribe: Desabonez view: 'Vidar:' view_profile: Videz profilo @@ -1461,11 +1460,6 @@ io: failed_sign_in_html: Falita enirprob per %{method} de %{ip} (%{browser}) successful_sign_in_html: Sucesoza eniro per %{method} de %{ip} (%{browser}) title: Yurizeshistorio - mail_subscriptions: - unsubscribe: - action: Yes, desabonez - complete: Desabonita - title: Desabonez media_attachments: validations: images_and_video: Ne povas addonar video ad afisho qua ja enhavas imaji diff --git a/config/locales/is.yml b/config/locales/is.yml index 1ea18a39d4..ccaac522ca 100644 --- a/config/locales/is.yml +++ b/config/locales/is.yml @@ -762,6 +762,7 @@ is: categories: administration: Stjórnun devops: DevOps + email: Tölvupóstur invites: Boðsgestir moderation: Umsjón special: Sérstakt @@ -790,6 +791,8 @@ is: manage_blocks_description: Leyfir notendum að loka á tölvupóstþjónustur og IP-vistföng manage_custom_emojis: Sýsla með sérsniðin lyndistákn manage_custom_emojis_description: Leyfir notendum að sýsla með sérsniðin lyndistákn á netþjóninum + manage_email_subscriptions: Sýsla með tölvupóstáskriftir + manage_email_subscriptions_description: Leyfa notendum að gerast áskrifendur að tölvupósti frá notendum með þessa heimild manage_federation: Sýsla með netþjónasambönd manage_federation_description: Leyfir notendum að loka á eða leyfa samþættingu við önnur lén (federation) og stýra afhendingu skilaboða manage_invites: Sýsla með boðsgesti @@ -1233,7 +1236,6 @@ is: application_mailer: notification_preferences: Breyta kjörstillingum tölvupósts salutation: "%{name}," - settings: 'Breyta kjörstillingum tölvupósts: %{link}' unsubscribe: Taka úr áskrift view: 'Skoða:' view_profile: Skoða notandasnið @@ -1423,6 +1425,33 @@ is: basic_information: Grunnupplýsingar hint_html: "Sérsníddu hvað fólk sér á opinbera notandasniðinu þínu og næst færslunum þínum. Annað fólk er líklegra til að fylgjast með þér og eiga í samskiptum við þig ef þú fyllir út notandasniðið og setur auðkennismynd." other: Annað + email_subscription_mailer: + confirmation: + action: Staðfestu tölvupóstfangið + instructions_to_confirm: Staðfestu að þú viljir fá tölvupósta frá %{name} (@%{acct}) þegar viðkomandi birtir nýjar færslur. + subject: Staðfestu tölvupóstfangið þitt + title: Fá tilkynningar í tölvupósti frá %{name}? + notification: + create_account: Búa til nýjan Mastodon-aðgang + interact_with_this_post: + one: Eigðu í samskiptum við þessa færslu og finndu fleiri í sama dúr. + other: Eigðu í samskiptum við þessar færslur og finndu fleiri í sama dúr. + subject: + one: 'Ný færsla: "%{excerpt}"' + other: Nýjar færslur frá %{name} + title: + one: 'Ný færsla: "%{excerpt}"' + other: Nýjar færslur frá %{name} + email_subscriptions: + active: Virkur + confirmations: + show: + changed_your_mind: Skiptirðu um skoðun? + title: Þú hefur skráð þig + unsubscribe: Hætta í áskrift + inactive: Óvirkur + status: Staða + subscribers: Áskrifendur emoji_styles: auto: Sjálfvirkt native: Innbyggt @@ -1657,21 +1686,6 @@ is: failed_sign_in_html: Misheppnuð tilraun til innskráningar með %{method} frá %{ip} (%{browser}) successful_sign_in_html: Vel heppnuð tilraun til innskráningar með %{method} frá %{ip} (%{browser}) title: Auðkenningarferill - mail_subscriptions: - unsubscribe: - action: Já, hætta í áskrift - complete: Hætta í áskrift - confirmation_html: Ertu viss um að þú viljir hætta áskrift sendinga á %{type} fyrir Mastodon á %{domain} til póstfangsins þíns %{email}? Þú getur alltaf aftur gerst áskrifandi í stillingunum fyrir tilkynningar í tölvupósti. - emails: - notification_emails: - favourite: tilkynningum í tölvupósti um eftirlæti - follow: tilkynningum í tölvupósti um fylgjendur - follow_request: tilkynningum í tölvupósti um beiðnir um að fylgjast með - mention: tilkynningum í tölvupósti um tilvísanir - reblog: tilkynningum í tölvupósti um endurbirtingar - resubscribe_html: Ef þú hættir áskrift fyrir mistök, geturðu alltaf aftur gerst áskrifandi í stillingunum fyrir tilkynningar í tölvupósti. - success_html: Þú munt ekki lengur fá sendingar með %{type} fyrir Mastodon á %{domain} á póstfangið þitt %{email}. - title: Taka úr áskrift media_attachments: validations: images_and_video: Ekki er hægt að hengja myndskeið við færslu sem þegar inniheldur myndir @@ -1809,6 +1823,7 @@ is: posting_defaults: Sjálfgefin gildi við gerð færslna public_timelines: Opinberar tímalínur privacy: + email_subscriptions: Senda færslur með tölvupósti hint_html: "Sérsníddu hvernig þú vilt að finna megi notandasnið þitt og færslur. Ýmsir eiginleikar í Mastodon geta hjálpað þér að ná til breiðari áheyrendahóps, séu þeir virkjaðir. Taktu þér tíma til að yfirfara þessar stillingar svo að þær henti þér." privacy: Gagnaleynd privacy_hint_html: Stýrðu því hve miklar upplýsingar þú birtir sem gætu gagnast öðrum. Fólk uppgötvar áhugaverða notendur og sniðug forrit með því að skoða hvað annað fólk fylgist með og hvaða forrit það notar til að birta færslur, en hinsvegar er þér frjálst að halda þessu leyndu. @@ -2072,6 +2087,28 @@ is: resume_app_authorization: Halda áfram með auðkenningu forrits role_requirement: "%{domain} krefst þess að þú setjir upp tveggja-þátta auðkenningu áður en þú getur notað Mastodon." webauthn: Öryggislyklar + unsubscriptions: + create: + action: Fara á heimasíðu netþjónsins + email_subscription: + confirmation_html: Þú munt ekki lengur fá tölvupósta frá %{name}. + title: Þú hefur sagt upp áskrift + user: + confirmation_html: Þú munt ekki lengur fá %{type} frá Mastodon á %{domain}. + notification_emails: + favourite: tölvupóst um eftirlæti + follow: tölvupóst um fylgjendur + follow_request: tölvupóst um fylgjendabeiðnir + mention: tölvupóst um tilvísanir í þig + reblog: tölvupóst um endurbirtingar + show: + action: Hætta í áskrift + email_subscription: + confirmation_html: Þú munt ekki lengur fá tölvupóst þegar þessi aðili birtir nýjar færslur. + title: Afpanta áskrift að %{name}? + user: + confirmation_html: Þú munt ekki lengur fá %{type} frá Mastodon á %{domain}. + title: Afpanta áskrift að %{type}? user_mailer: announcement_published: description: 'Stjórnendur %{domain} eru að senda frá sér yfirlýsingu:' diff --git a/config/locales/it.yml b/config/locales/it.yml index e01bed8b97..1083640095 100644 --- a/config/locales/it.yml +++ b/config/locales/it.yml @@ -762,6 +762,7 @@ it: categories: administration: Amministrazione devops: DevOps + email: Email invites: Inviti moderation: Moderazione special: Speciale @@ -1231,7 +1232,6 @@ it: application_mailer: notification_preferences: Modifica le preferenze e-mail salutation: "%{name}," - settings: 'Modifica le preferenze e-mail: %{link}' unsubscribe: Disiscriviti view: 'Guarda:' view_profile: Mostra profilo @@ -1653,21 +1653,6 @@ it: failed_sign_in_html: Tentativo di accesso fallito con %{method} da %{ip} (%{browser}) successful_sign_in_html: Accesso riuscito con %{method} da %{ip} (%{browser}) title: Cronologia delle autenticazioni - mail_subscriptions: - unsubscribe: - action: Sì, annulla l'iscrizione - complete: Iscrizione annullata - confirmation_html: Si è sicuri di voler annullare l'iscrizione per non ricevere %{type} per Mastodon su %{domain} sulla tua e-mail %{email}? Puoi sempre reiscriverti dalle tue impostazioni di notifica e-mail. - emails: - notification_emails: - favourite: e-mail di notifica preferite - follow: segui le e-mail di notifica - follow_request: segui le e-mail di richiesta - mention: menziona le e-mail di notifica - reblog: e-mail di notifica per le condivisioni - resubscribe_html: Se hai annullato l'iscrizione per errore, puoi reiscriverti tramite le impostazioni di notifica e-mail. - success_html: Non riceverai più %{type} per Mastodon su %{domain} al tuo indirizzo e-mail %{email}. - title: Disiscriviti media_attachments: validations: images_and_video: Impossibile allegare video a un post che contiene già immagini diff --git a/config/locales/ja.yml b/config/locales/ja.yml index b1e2fdbef7..b058bfdacf 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -1169,7 +1169,6 @@ ja: application_mailer: notification_preferences: メール設定の変更 salutation: "%{name}さん" - settings: 'メール設定の変更: %{link}' unsubscribe: 購読解除 view: 'リンク:' view_profile: プロフィールを表示 @@ -1558,21 +1557,6 @@ ja: failed_sign_in_html: "%{ip} (%{browser}) から%{method}を利用したサインインに失敗しました。" successful_sign_in_html: "%{ip} (%{browser}) から%{method}を利用したサインインに成功しました" title: 認証履歴 - mail_subscriptions: - unsubscribe: - action: 購読を解除する - complete: 購読を解除しました - confirmation_html: Mastodon (%{domain}) による %{email} 宛の「%{type}」の配信を停止します。再度必要になった場合はメール通知の設定からいつでも再開できます。 - emails: - notification_emails: - favourite: お気に入りの通知メール - follow: フォローの通知メール - follow_request: フォローリクエストの通知メール - mention: 返信の通知メール - reblog: ブーストの通知メール - resubscribe_html: 誤って解除した場合はメール通知の設定から再購読できます。 - success_html: Mastodon (%{domain}) から %{email} への「%{type}」の配信が停止されました。 - title: 購読の解除 media_attachments: validations: images_and_video: 既に画像が追加されているため、動画を追加することはできません diff --git a/config/locales/kab.yml b/config/locales/kab.yml index 35bffc2810..93dc72865c 100644 --- a/config/locales/kab.yml +++ b/config/locales/kab.yml @@ -567,7 +567,6 @@ kab: application_mailer: notification_preferences: Snifel imenyafen n imayl salutation: "%{name}," - settings: 'Snifel imenyafen n imayl: %{link}' view: 'Ẓaṛ:' view_profile: Ssken-d amaɣnu view_status: Ssken-d tasuffiɣt diff --git a/config/locales/ko.yml b/config/locales/ko.yml index a5e90f9894..179956c433 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -1194,7 +1194,6 @@ ko: application_mailer: notification_preferences: 이메일 설정 변경 salutation: "%{name} 님," - settings: '이메일 설정 변경: %{link}' unsubscribe: 구독 해제 view: '보기:' view_profile: 프로필 보기 @@ -1594,21 +1593,6 @@ ko: failed_sign_in_html: 실패한 로그인 시도 %{method} %{ip} (%{browser}) successful_sign_in_html: 성공한 로그인 시도 %{method} %{ip} (%{browser}) title: 인증 이력 - mail_subscriptions: - unsubscribe: - action: 네, 구독 취소합니다 - complete: 구독 취소됨 - confirmation_html: 정말로 %{domain}에서 %{email}로 보내는 마스토돈의 %{type}에 대한 구독을 취소하시겠습니까? 언제든지 이메일 알림 설정에서 다시 구독할 수 있습니다. - emails: - notification_emails: - favourite: 좋아요 알림 이메일 - follow: 팔로우 알림 이메일 - follow_request: 팔로우 요청 이메일 - mention: 멘션 알림 이메일 - reblog: 부스트 알림 이메일 - resubscribe_html: 만약 실수로 구독 취소를 했다면 이메일 알림 설정에서 다시 구독할 수 있습니다. - success_html: 이제 더이상 %{domain}의 마스토돈에서 %{email}로 %{type} 알림을 보내지 않습니다. - title: 구독 취소 media_attachments: validations: images_and_video: 이미 사진이 첨부된 게시물엔 동영상을 첨부할 수 없습니다. diff --git a/config/locales/lad.yml b/config/locales/lad.yml index aeffb477be..04a09327c0 100644 --- a/config/locales/lad.yml +++ b/config/locales/lad.yml @@ -1135,7 +1135,6 @@ lad: application_mailer: notification_preferences: Troka preferensyas de posta salutation: "%{name}," - settings: 'Troka preferensyas de posta: %{link}' unsubscribe: Dezabona view: 'Mira:' view_profile: Ve profil @@ -1503,21 +1502,6 @@ lad: failed_sign_in_html: Prova de inisiasyon de sesion no reushida kon %{method} de %{ip} (%{browser}) successful_sign_in_html: Prova de sesion reushida kon %{method} dizde %{ip} (%{browser}) title: Estoria de autentifikasyon - mail_subscriptions: - unsubscribe: - action: Si, dezabona - complete: Dezabonado - confirmation_html: Estas siguro de ke ya no keres risivir %{type} de Mastodon en %{domain} a tu posta elektronika %{email}? Syempre podras reabonarte dizde las opsyones de avizos por posta.. - emails: - notification_emails: - favourite: avizos de favoritos por posta - follow: avizos de segidores por posta - follow_request: avizos de solisitasyones de segimyento por posta - mention: avizos de enmentaduras por posta - reblog: avizos de repartajasyones por posta - resubscribe_html: Si tyenes deabonado por yerro, puedes reabonar en tus opsyones de avizos por posta elektronika. - success_html: Ya no risiviras %{type} de Mastodon en %{domain} a tu posta en %{email}. - title: Dezabona media_attachments: validations: images_and_video: No se puede adjuntar un video a un estado ke ya kontenga imajes diff --git a/config/locales/lt.yml b/config/locales/lt.yml index b807451ece..006668fbe8 100644 --- a/config/locales/lt.yml +++ b/config/locales/lt.yml @@ -1073,12 +1073,6 @@ lt: description_html: Jei pastebėjei neatpažįstamą veiklą, apsvarstyk galimybę pakeisti slaptažodį ir įjungti dvigubą tapatybės nustatymą. empty: Tapatybės nustatymas istorijos nėra title: Tapatybės nustatymo istorija - mail_subscriptions: - unsubscribe: - emails: - notification_emails: - reblog: dalintis pranešimų el. pašto laiškais - success_html: Daugiau negausi %{type} „Mastodon“ domene %{domain} į savo el. paštą %{email}. media_attachments: validations: images_and_video: Negalima pridėti video prie statuso, kuris jau turi nuotrauką diff --git a/config/locales/lv.yml b/config/locales/lv.yml index 1a31034a59..89f5c43a21 100644 --- a/config/locales/lv.yml +++ b/config/locales/lv.yml @@ -1174,7 +1174,6 @@ lv: application_mailer: notification_preferences: Mainīt e-pasta uztādījumus salutation: "%{name}," - settings: 'Mainīt e-pasta uztādījumus: %{link}' unsubscribe: Atcelt abonēšanu view: 'Skatīt:' view_profile: Skatīt profilu @@ -1596,21 +1595,6 @@ lv: failed_sign_in_html: Neizdevies pieteikšanās mēģinājums ar %{method} no %{ip} (%{browser}) successful_sign_in_html: Sekmīga pieteikšanās ar %{method} no %{ip} (%{browser}) title: Autentifikācijas vēsture - mail_subscriptions: - 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 - follow: sekošanas paziņojumu e-pasta ziņojumi - 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 diff --git a/config/locales/ms.yml b/config/locales/ms.yml index 6edddd4323..afd74144a0 100644 --- a/config/locales/ms.yml +++ b/config/locales/ms.yml @@ -1278,14 +1278,6 @@ ms: failed_sign_in_html: Percubaan log masuk gagal dengan %{method} daripada %{ip} (%{browser}) successful_sign_in_html: Log masuk yang berjaya dengan %{method} daripada %{ip} (%{browser}) title: Sejarah pengesahan - mail_subscriptions: - unsubscribe: - action: Ya, nyahlanggan - complete: Menyahlanggan - emails: - notification_emails: - favourite: emel pemberitahuan sukaan - title: Hentikan langganan media_attachments: validations: images_and_video: Tidak boleh melampirkan video pada pos yang sudah mengandungi imej diff --git a/config/locales/my.yml b/config/locales/my.yml index f8f69586a9..fd0bbcb005 100644 --- a/config/locales/my.yml +++ b/config/locales/my.yml @@ -1275,11 +1275,6 @@ my: failed_sign_in_html: "%{ip} (%{browser}) မှ %{method} ဖြင့် အကောင့်ဝင်ရောက်ခြင်း မအောင်မြင်ပါ" successful_sign_in_html: "%{ip} (%{browser}) မှ %{method} ဖြင့် အကောင့်ဝင်၍ရပါပြီ" title: အထောက်အထားမှတ်တမ်း - mail_subscriptions: - unsubscribe: - action: ဟုတ်ကဲ့၊ စာရင်းမှ ဖြုတ်လိုက်ပါပြီ - complete: စာရင်းမှထွက်ရန် - title: စာရင်းမှထွက်ရန် media_attachments: validations: images_and_video: ရုပ်ပုံပါရှိပြီးသားပို့စ်တွင် ဗီဒီယို ပူးတွဲ၍မရပါ diff --git a/config/locales/nan-TW.yml b/config/locales/nan-TW.yml index dc471a9c2c..a69d14d3b8 100644 --- a/config/locales/nan-TW.yml +++ b/config/locales/nan-TW.yml @@ -1210,7 +1210,6 @@ nan-TW: application_mailer: notification_preferences: 改電子phue ê偏好 salutation: "%{name}、" - settings: 改電子phue ê偏好:%{link} unsubscribe: 取消訂 view: 檢視: view_profile: 看個人資料 @@ -1612,21 +1611,6 @@ nan-TW: failed_sign_in_html: Uì %{ip} (%{browser}) 用 %{method} 試登入失敗 successful_sign_in_html: Uì %{ip} (%{browser}) 用 %{method} 登入成功 title: 認證歷史 - mail_subscriptions: - unsubscribe: - action: Hennh,mài訂 - complete: 無訂ah - confirmation_html: Lí kám確定beh取消訂 %{domain} ê Mastodon 內底 ê %{type} kàu lí ê電子批 %{email}?Lí ē當隨時對lí ê電子批通知設定重訂。 - emails: - notification_emails: - favourite: 收藏通知電子批 - follow: 跟tuè通知電子批 - follow_request: 跟tuè請求電子批 - mention: 提起通知電子批 - reblog: 轉送通知電子批 - resubscribe_html: Nā出tshê取消訂,lí通重訂tuì lí ê電子批通知設定。 - success_html: Lí bē koh收著佇 %{domain} ê Mastodon內底ê %{type} kàu lí ê電子批 %{email}。 - title: 取消訂 media_attachments: validations: images_and_video: Bē當佇有影像ê PO文內底加影片 diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 69ff56febd..1fcbd0206c 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -1231,7 +1231,6 @@ nl: application_mailer: notification_preferences: E-mailvoorkeuren wijzigen salutation: "%{name}," - settings: 'E-mailvoorkeuren wijzigen: %{link}' unsubscribe: Afmelden view: 'Bekijk:' view_profile: Profiel bekijken @@ -1653,21 +1652,6 @@ nl: failed_sign_in_html: Mislukte inlogpoging met %{method} van %{ip} (%{browser}) successful_sign_in_html: Succesvol ingelogd met %{method} van %{ip} (%{browser}) title: Inloggeschiedenis - mail_subscriptions: - unsubscribe: - action: Ja, afmelden - complete: Afgemeld - confirmation_html: Weet je zeker dat je je wilt afmelden voor het ontvangen van %{type} van Mastodon op %{domain} op je e-mailadres %{email}? Je kunt je altijd opnieuw abonneren in jouw instellingen voor e-mailmeldingen. - emails: - notification_emails: - favourite: e-mailmeldingen voor favorieten - follow: e-mailmeldingen voor nieuwe volgers - follow_request: e-mailmeldingen voor volgverzoeken - mention: e-mailmeldingen voor vermeldingen - reblog: e-mailmeldingen voor boosts - resubscribe_html: Als je je per ongeluk hebt afgemeld, kun je je opnieuw abonneren in jouw instellingen voor e-mailmeldingen. - success_html: Je ontvangt niet langer %{type} van Mastodon op %{domain} op je e-mailadres %{email}. - title: Afmelden media_attachments: validations: images_and_video: Een video kan niet aan een bericht met afbeeldingen worden gekoppeld diff --git a/config/locales/nn.yml b/config/locales/nn.yml index f59ba33334..b2564b118a 100644 --- a/config/locales/nn.yml +++ b/config/locales/nn.yml @@ -1229,7 +1229,6 @@ nn: application_mailer: notification_preferences: Endre e-post-innstillingane salutation: Hei %{name}, - settings: 'Endre e-post-innstillingar: %{link}' unsubscribe: Meld av view: 'Sjå:' view_profile: Sjå profil @@ -1651,21 +1650,6 @@ nn: failed_sign_in_html: Mislykket innloggingsforsøk med %{method} fra %{ip} (%{browser}) successful_sign_in_html: Vellykket innlogging med %{method} fra %{ip} (%{browser}) title: Autentiseringshistorikk - mail_subscriptions: - unsubscribe: - action: Ja, meld av - complete: Meldt av - confirmation_html: Er du sikker på at du ikkje lenger ynskjer å motta %{type} frå Mastodon på %{domain} til e-posten din %{email}? Du kan alltids gjera om på dette i innstillingar for e-postvarsling. - emails: - notification_emails: - favourite: e-postar om favorittmarkeringar - follow: e-postar om nye fylgjarar - follow_request: e-postar om fylgjeførespurnadar - mention: e-postar om omtaler - reblog: e-postar om framhevingar - resubscribe_html: Om du har avslutta abonnementet ved ein feil, kan du abonnera på nytt i innstillingar for e-postvarsling. - success_html: Du vil ikkje lenger få %{type} frå Mastodon på %{domain} til e-posten på %{email}. - title: Meld av media_attachments: validations: images_and_video: Kan ikkje leggja ved video til status som allereie inneheld bilete diff --git a/config/locales/no.yml b/config/locales/no.yml index f6dd1e9c8d..e2bf55a229 100644 --- a/config/locales/no.yml +++ b/config/locales/no.yml @@ -1352,11 +1352,6 @@ failed_sign_in_html: Mislykket innloggingsforsøk med %{method} fra %{ip} (%{browser}) successful_sign_in_html: Vellykket innlogging med %{method} fra %{ip} (%{browser}) title: Autentiseringshistorikk - mail_subscriptions: - unsubscribe: - action: Ja, avslutt abonnement - complete: Abonnement avsluttet - title: Avslutt abonnement media_attachments: validations: images_and_video: Kan ikke legge ved video på en status som allerede inneholder bilder diff --git a/config/locales/pl.yml b/config/locales/pl.yml index e0ae1f7106..2b258ab495 100644 --- a/config/locales/pl.yml +++ b/config/locales/pl.yml @@ -1230,7 +1230,6 @@ pl: application_mailer: notification_preferences: Zmień ustawienia e-maili salutation: "%{name}," - settings: 'Zmień ustawienia e-maili: %{link}' unsubscribe: Anuluj subskrypcję view: 'Zobacz:' view_profile: Wyświetl profil @@ -1687,21 +1686,6 @@ pl: failed_sign_in_html: Próba logowania zakończona niepowodzeniem przy pomocy %{method} z %{ip} (%{browser}) successful_sign_in_html: Pomyślne logowanie przy pomocy %{method} z %{ip} (%{browser}) title: Historia uwierzytelniania - mail_subscriptions: - unsubscribe: - action: Tak, wypisuję się - complete: Anulowano subskrypcję - confirmation_html: Czy na pewno chcesz wypisać się z otrzymywania %{type} z Mastodona na %{domain} na adres %{email}? Zawsze możesz zapisać się ponownie ze strony ustawień powiadomień mejlowych. - emails: - notification_emails: - favourite: powiadomień mejlowych o polubieniach - follow: powiadomień mejlowych o obserwujących - follow_request: mejli o prośbach o możliwość obserwowania - mention: powiadomień mejlowych o wspomnieniach - reblog: powiadomień mejlowych o podbiciach - resubscribe_html: W przypadku przypadkowego wypisania możesz zapisać się ponownie z ustawień powiadomień mejlowych. - success_html: Już nie będziesz otrzymywać %{type} z Mastodona na %{domain} na adres %{email}. - title: Anuluj subskrypcję media_attachments: validations: images_and_video: Nie możesz załączyć pliku wideo do wpisu, który zawiera już zdjęcia diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml index e8789d4236..b0f42b31e3 100644 --- a/config/locales/pt-BR.yml +++ b/config/locales/pt-BR.yml @@ -762,6 +762,7 @@ pt-BR: categories: administration: Administração devops: DevOps + email: E-mail invites: Convites moderation: Moderação special: Especial @@ -778,6 +779,8 @@ pt-BR: administrator_description: Usuários com essa permissão irão ignorar todas as permissões delete_user_data: Apagar Dados de Usuário delete_user_data_description: Permitir aos usuários apagar os dados de outros usuários instantaneamente + invite_bypass_approval: Convidar usuário sem revisão + invite_bypass_approval_description: Permitir pessoas convidadas para esse servidor por esses usuários para dispensar aprovação por moderação invite_users: Convidar Usuários invite_users_description: Permite que os usuários convidem novas pessoas para o servidor manage_announcements: Gerenciar Avisos @@ -788,6 +791,8 @@ pt-BR: manage_blocks_description: Permite aos usuários bloquear provedores de e-mail e endereços IP manage_custom_emojis: Gerenciar Emojis Personalizados manage_custom_emojis_description: Permite aos usuários gerenciar emojis personalizados no servidor + manage_email_subscriptions: Gerenciar assinaturas do correio eletrônico + manage_email_subscriptions_description: Permitir que usuários se inscrevam com essa permissão por e-mail manage_federation: Gerenciar Federação manage_federation_description: Permite aos usuários bloquear ou permitir federação com outros domínios e controlar a entregabilidade manage_invites: Gerenciar convites @@ -1229,7 +1234,6 @@ pt-BR: application_mailer: notification_preferences: Alterar preferências de e-mail salutation: "%{name}," - settings: 'Alterar preferências de e-mail: %{link}' unsubscribe: Desinscrever view: 'Ver:' view_profile: Ver perfil @@ -1279,6 +1283,7 @@ pt-BR: progress: confirm: Confirmar e-mail details: Suas informações + list: Progresso do cadastro review: Nossa avaliação rules: Aceitar regras providers: @@ -1294,6 +1299,7 @@ pt-BR: invited_by: 'Você pode juntar-se a %{domain} graças ao convite que recebeu de:' preamble: Estes são definidos e aplicados pelos moderadores de %{domain}. preamble_invited: Antes de prosseguir, considere as regras de base definidas pelos moderadores de %{domain}. + read_more: Ler mais title: Algumas regras básicas. title_invited: Você recebeu convite. security: Segurança @@ -1415,6 +1421,38 @@ pt-BR: basic_information: Informações básicas hint_html: "Personalize o que as pessoas veem no seu perfil público e ao lado de suas publicações. É mais provável que outras pessoas o sigam de volta e interajam com você quando você tiver um perfil preenchido e uma foto de perfil." other: Outro + email_subscription_mailer: + confirmation: + action: Confirmar endereço de e-mail + instructions_to_confirm: Confirme que você gostaria de receber e-mails do %{name} (@%{acct}) quando postarem novas publicações. + instructions_to_ignore: Se você não tem certeza por que recebeu esse e-mail, você pode excluir. Você não será inscrito se não clicar no link acima. + subject: Confirme seu endereço de e-mail + title: Obter atualizações de e-mail do %{name}? + notification: + create_account: Criar uma conta Mastodon + footer: + privacy_html: E-mails são enviados do %{domain}, Um servidor alimentado pelo Mastodon. Para entender como esse servidor processa seus dados pessoais, consulte a Política de privacidade. + reason_for_email_html: Você está recebendo esse e-mail porque você optou por receber atualizações do %{name}. Não quer receber esses e-mails? Cancelar + interact_with_this_post: + one: Interaja com essa publicação e descubra mais como essa. + other: Interaja com essas publicações e descubra mais. + subject: + one: Nova publicação "%{excerpt}" + other: Novas publicações do %{name} + title: + one: 'Nova publicação: "%{excerpt}"' + other: Novas publicações do %{name} + email_subscriptions: + active: Ativo + confirmations: + show: + changed_your_mind: Mudou de ideia? + success_html: Agora você começará a receber e-mails quando %{name} publicar novas postagens. Adicione %{sender} para seus contatos para que essas publicações não apareçam em sua caixa de Spam. + title: Você está registrado + unsubscribe: Cancelar inscrição + inactive: Inativo + status: Situação + subscribers: Inscritos emoji_styles: auto: Automático native: Nativo @@ -1649,21 +1687,6 @@ pt-BR: failed_sign_in_html: Falha na tentativa de login com %{method} de %{ip} (%{browser}) successful_sign_in_html: Login bem-sucedido com %{method} de %{ip} (%{browser}) title: Histórico de autenticação - mail_subscriptions: - unsubscribe: - action: Sim, cancelar subscrição - complete: Desinscrito - confirmation_html: Tem certeza que deseja cancelar a assinatura de %{type} para Mastodon no %{domain} para o seu endereço de e-mail %{email}? Você sempre pode se inscrever novamente nas configurações de notificação de email. - emails: - notification_emails: - favourite: emails de notificação favoritos - follow: seguir emails de notificação - follow_request: emails de seguidores pendentes - mention: emails de notificação de menções - reblog: emails de notificação de impulsos - resubscribe_html: Se você cancelou sua inscrição por engano, você pode se inscrever novamente em suas configurações de notificações por e-mail. - success_html: Você não mais receberá %{type} no Mastodon em %{domain} ao seu endereço de e-mail %{email}. - title: Cancelar inscrição media_attachments: validations: images_and_video: Não foi possível anexar um vídeo a uma publicação que já contém imagens @@ -1802,6 +1825,8 @@ pt-BR: posting_defaults: Padrões de publicação public_timelines: Linhas públicas privacy: + email_subscriptions: Enviar publicações por e-mail + email_subscriptions_hint_html: Adicione um formulário de inscrição por e-mail ao seu perfil, que será exibido para usuários que não estiverem conectados. Quando os visitantes inserirem seu endereço de e-mail e se inscreverem, o Mastodon enviará atualizações por e-mail sobre suas publicações públicas. hint_html: "Personalize como você quer que seu perfil e suas publicações sejam encontrados. Uma variedade de funcionalidades no Mastodon pode ajudar a alcançar um público mais amplo quando habilitado. Reserve um momento para revisar estas configurações para garantir que atendem ao seu caso de uso." privacy: Privacidade privacy_hint_html: Controle o quanto você deseja revelar para o benefício de outros. As pessoas descobrem perfis interessantes e aplicativos legais navegando pelos seguidores de outras pessoas e vendo de quais aplicativos eles publicam, mas você pode preferir manter isso oculto. @@ -2065,6 +2090,17 @@ pt-BR: resume_app_authorization: Retomar autorização de aplicativo role_requirement: "%{domain} exige que você configure a autenticação de dois fatores antes de poder utilizar o Mastodon." webauthn: Chaves de segurança + unsubscriptions: + create: + action: Acesse a página inicial do servidor + email_subscription: + confirmation_html: Você não receberá mais e-mails do %{name}. + title: Você não está registrado + user: + confirmation_html: Você não receberá mais %{type} do Mastodon no %{domain}. + notification_emails: + favourite: e-mails de notificações de favoritos + follow: receber e-mails de notificação user_mailer: announcement_published: description: 'Os administradores do %{domain} estão fazendo um anúncio:' diff --git a/config/locales/pt-PT.yml b/config/locales/pt-PT.yml index c388a0a48e..36ba06e400 100644 --- a/config/locales/pt-PT.yml +++ b/config/locales/pt-PT.yml @@ -1229,7 +1229,6 @@ pt-PT: application_mailer: notification_preferences: Alterar preferências de e-mail salutation: "%{name}," - settings: 'Alterar preferências de e-mail: %{link}' unsubscribe: Cancelar subscrição view: 'Ver:' view_profile: Ver perfil @@ -1649,21 +1648,6 @@ pt-PT: failed_sign_in_html: Tentativa falhada de início de sessão com %{method} de %{ip} (%{browser}) successful_sign_in_html: Sessão corretamente iniciada com %{method} de %{ip} (%{browser}) title: Histórico de autenticação - mail_subscriptions: - unsubscribe: - action: Sim, cancelar subscrição - complete: Subscrição cancelada - confirmation_html: Tens a certeza que desejas cancelar a subscrição para receber %{type} pelo Mastodon em %{domain} no teu e-mail em %{email}? Podes sempre subscrever novamente nas tuas definições de notificação por e-mail. - emails: - notification_emails: - favourite: e-mails de notificação de favoritos - follow: e-mails de notificação de seguidor - follow_request: e-mails de pedido de seguidor - mention: e-mails de notificação de menção - reblog: e-mails de notificação de partilhas - resubscribe_html: Se tiveres anulado a subscrição por engano, podes voltar a subscrevê-la nas definições de notificação por e-mail. - success_html: Não receberás novamente %{type} do Mastodon em %{domain} para o teu e-mail em %{email}. - title: Cancelar subscrição media_attachments: validations: images_and_video: Não é possível anexar um vídeo a uma publicação que já contém imagens diff --git a/config/locales/ru.yml b/config/locales/ru.yml index 626870709d..78dcda366f 100644 --- a/config/locales/ru.yml +++ b/config/locales/ru.yml @@ -1227,7 +1227,6 @@ ru: application_mailer: notification_preferences: Настроить оповещения по электронной почте salutation: Привет, %{name}! - settings: 'Настроить оповещения по электронной почте можно здесь: %{link}' unsubscribe: Отписаться view: 'Открыть в браузере:' view_profile: Перейти к профилю @@ -1673,21 +1672,6 @@ ru: failed_sign_in_html: Неудачная попытка входа при помощи %{method} с IP-адреса %{ip} (%{browser}) successful_sign_in_html: Вход при помощи %{method} с IP-адреса %{ip} (%{browser}) title: История входов - mail_subscriptions: - unsubscribe: - action: Да, я хочу отписаться - complete: Подписка отменена - confirmation_html: Вы уверены в том, что хотите отписаться от всех %{type}, которые вы получаете на адрес %{email} для учётной записи на сервере Mastodon %{domain}? Вы всегда сможете подписаться снова в настройках уведомлений по электронной почте. - emails: - notification_emails: - favourite: уведомлений о добавлении ваших постов в избранное - follow: уведомлений о новых подписчиках - follow_request: уведомлений о новых запросах на подписку - mention: уведомлений о новых упоминаниях - reblog: уведомлений о продвижении ваших постов - resubscribe_html: Если вы отписались по ошибке и хотите подписаться снова, перейдите на страницу настройки уведомлений по электронной почте. - success_html: Вы отказались от %{type}, которые вы получали на адрес %{email} для вашей учётной записи на сервере Mastodon %{domain}. - title: Отписаться media_attachments: validations: images_and_video: Нельзя добавить видео к посту с изображениями diff --git a/config/locales/sc.yml b/config/locales/sc.yml index 6924ba67c4..2c94bc3a10 100644 --- a/config/locales/sc.yml +++ b/config/locales/sc.yml @@ -913,9 +913,6 @@ sc: authentication_methods: password: crae webauthn: craes de seguresa - mail_subscriptions: - unsubscribe: - title: Annulla sa sutiscritzione media_attachments: validations: images_and_video: Non si podet allegare unu vìdeu in una publicatzione chi cuntenet giai immàgines diff --git a/config/locales/simple_form.da.yml b/config/locales/simple_form.da.yml index 4a236d374a..32a3a54f8d 100644 --- a/config/locales/simple_form.da.yml +++ b/config/locales/simple_form.da.yml @@ -134,6 +134,7 @@ da: otp: 'Angiv tofaktorkoden generet af din mobil-app eller brug en af dine gendannelseskoder:' webauthn: Er det en USB-nøgle, så sørg for at isætte den og, om nødvendigt, åbne den manuelt. settings: + email_subscriptions: Deaktivering bevarer eksisterende abonnenter, men stopper udsendelsen af e-mails. indexable: Din profilside kan fremgå i søgeresultater på Google, Bing mv. show_application: Du vil dog altid kunne se, hvilken app, der offentliggjorde dit indlæg. tag: @@ -356,6 +357,7 @@ da: hint: Yderligere oplysninger text: Regel settings: + email_subscriptions: Aktivér e-mail-tilmeldinger indexable: Inkludér profilside i søgemaskiner show_application: Vis, fra hvilken app et indlæg er sendt tag: diff --git a/config/locales/simple_form.el.yml b/config/locales/simple_form.el.yml index 1477f1e2b1..7a7e929ae1 100644 --- a/config/locales/simple_form.el.yml +++ b/config/locales/simple_form.el.yml @@ -134,6 +134,7 @@ el: otp: 'Βάλε τον κωδικό δυο παραγόντων (2FA) από την εφαρμογή του τηλεφώνου σου ή χρησιμοποίησε κάποιον από τους κωδικούς ανάκτησης σου:' webauthn: Αν πρόκειται για ένα κλειδί USB βεβαιωθείτε ότι είναι συνδεδεμένο και αν απαιτείται πατήστε το ελαφρά. settings: + email_subscriptions: Η απενεργοποίηση διατηρεί τους υπάρχοντες συνδρομητές αλλά σταματά την αποστολή email. indexable: Η σελίδα του προφίλ σου μπορεί να εμφανιστεί στα αποτελέσματα αναζήτησης στο Google, Bing και άλλες. show_application: Θα είσαι πάντα σε θέση να δεις ποια εφαρμογή δημοσίευσε την ανάρτησή σου όπως και να 'χει. tag: @@ -356,6 +357,7 @@ el: hint: Επιπρόσθετες πληροφορίες text: Κανόνας settings: + email_subscriptions: Ενεργοποίηση εγγραφών μέσω email indexable: Συμπερίληψη σελίδας προφίλ στις μηχανές αναζήτησης show_application: Εμφάνιση από ποια εφαρμογή έστειλες μία ανάρτηση tag: diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 87334b14e7..be92d56a8a 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -134,6 +134,7 @@ en: otp: 'Enter the two-factor code generated by your phone app or use one of your recovery codes:' webauthn: If it's an USB key be sure to insert it and, if necessary, tap it. settings: + email_subscriptions: Disabling retains existing subscribers but stops sending emails. indexable: Your profile page may appear in search results on Google, Bing, and others. show_application: You will always be able to see which app published your post regardless. tag: @@ -356,6 +357,7 @@ en: hint: Additional information text: Rule settings: + email_subscriptions: Enable email sign-ups indexable: Include profile page in search engines show_application: Display from which app you sent a post tag: diff --git a/config/locales/simple_form.es-AR.yml b/config/locales/simple_form.es-AR.yml index 62961d7817..d9eeec211b 100644 --- a/config/locales/simple_form.es-AR.yml +++ b/config/locales/simple_form.es-AR.yml @@ -134,6 +134,7 @@ es-AR: otp: 'Ingresá el código de autenticación de dos factores generado por la aplicación en tu dispositivo, o usá uno de tus códigos de recuperación:' webauthn: Si es una llave USB, asegurate de insertarla y, de ser necesario, tocarla. settings: + email_subscriptions: Deshabilitar retiene a los suscriptores existentes, pero detiene el envío de correos electrónicos. indexable: Tu página de perfil podría aparecer en los resultados de búsqueda en Google, Bing y otros motores de búsqueda. show_application: Sin embargo, siempre podrás ver desde qué aplicación se envió tu mensaje. tag: @@ -356,6 +357,7 @@ es-AR: hint: Información adicional text: Regla settings: + email_subscriptions: Habilitar suscripciones por correo electrónico indexable: Incluir la página de perfil en los motores de búsqueda show_application: Mostrar desde qué aplicación enviaste un mensaje tag: diff --git a/config/locales/simple_form.es-MX.yml b/config/locales/simple_form.es-MX.yml index c110d76ee6..b90f40d68c 100644 --- a/config/locales/simple_form.es-MX.yml +++ b/config/locales/simple_form.es-MX.yml @@ -37,11 +37,11 @@ es-MX: starts_at: Opcional. En caso de que su anuncio esté vinculado a un intervalo de tiempo específico text: Puedes usar la sintaxis de publicaciones. Por favor ten en cuenta el espacio que ocupará el anuncio en la pantalla del usuario appeal: - text: Sólo puede apelar una amonestación a la vez + text: Solo se puede apelar una amonestación una vez defaults: autofollow: Los usuarios que se registren mediante la invitación te seguirán automáticamente avatar: WEBP, PNG, GIF o JPG. Máximo %{size}. Será escalado a %{dimensions}px - bot: Esta cuenta ejecuta principalmente acciones automatizadas y podría no ser monitorizada + bot: Indica a los demás que la cuenta realiza principalmente acciones automatizadas y que es posible que no esté supervisada context: Uno o múltiples contextos en los que debe aplicarse el filtro current_password: Por razones de seguridad por favor ingrese la contraseña de la cuenta actual current_username: Para confirmar, por favor ingrese el nombre de usuario de la cuenta actual @@ -53,9 +53,9 @@ es-MX: locale: El idioma de la interfaz de usuario, correos y notificaciones push 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. + scopes: A qué API tendrá acceso la aplicación. Si seleccionas un ámbito de nivel superior, no es necesario que selecciones ámbitos individuales. setting_advanced_layout: Mostrar Mastodon en un diseño de varias columnas, lo que te permite ver la cronología, las notificaciones y una tercera columna de tu elección. No se recomienda para pantallas pequeñas. - setting_aggregate_reblogs: No mostrar nuevos impulsos para las publicaciones que han sido recientemente impulsadas (sólo afecta a las publicaciones recibidas recientemente) + setting_aggregate_reblogs: No mostrar nuevos impulsos para las publicaciones que se hayan impulsado recientemente (solo afecta a los impulsos recibidos recientemente) setting_always_send_emails: Normalmente las notificaciones por correo electrónico no se enviarán cuando estés usando Mastodon activamente setting_boost_modal: Cuando está habilitado, impulsar abrirá primero un cuadro de confirmación en el que podrás cambiar la visibilidad de tu impulso. setting_default_quote_policy_private: Las publicaciones solo para seguidores hechas en Mastodon no pueden ser citadas por otros usuarios. @@ -134,6 +134,7 @@ es-MX: otp: 'Introduce el código de autenticación de dos factores generado por tu aplicación de teléfono o usa uno de tus códigos de recuperación:' webauthn: Si es una tecla USB, asegúrese de insertarla y, si es necesario, púlsela. settings: + email_subscriptions: Al desactivar la función, se conservan los suscriptores actuales, pero se deja de enviar correos electrónicos. indexable: Tu página de perfil puede aparecer en los resultados de búsqueda en Google, Bing, entre otros. show_application: Siempre podrás ver desde cuál aplicación realizaste una publicación. tag: @@ -356,6 +357,7 @@ es-MX: hint: Información adicional text: Norma settings: + email_subscriptions: Habilitar suscripciones por correo electrónico indexable: Incluir la página de perfil en los motores de búsqueda show_application: Mostrar desde cuál aplicación enviaste una publicación tag: diff --git a/config/locales/simple_form.es.yml b/config/locales/simple_form.es.yml index 30da06b1d7..056e85b1a1 100644 --- a/config/locales/simple_form.es.yml +++ b/config/locales/simple_form.es.yml @@ -134,6 +134,7 @@ es: otp: 'Introduce el código de autenticación de dos factores generado por tu aplicación de teléfono o usa uno de tus códigos de recuperación:' webauthn: Si es una tecla USB, asegúrese de insertarla y, si es necesario, púlsela. settings: + email_subscriptions: Deshabilitar retiene a los suscriptores existentes pero detiene el envío de correos electrónicos. indexable: Puede que tu página de perfil aparezca en los resultados de búsqueda en Google, Bing y otros. show_application: Tú siempre podrás ver desde qué aplicación se ha publicado tu publicación. tag: @@ -356,6 +357,7 @@ es: hint: Información adicional text: Norma settings: + email_subscriptions: Habilitar registros de correo electrñonico indexable: Incluye la página de perfil en los buscadores show_application: Mostrar desde qué aplicación enviaste una publicación tag: diff --git a/config/locales/simple_form.fr.yml b/config/locales/simple_form.fr.yml index 4375b37d3d..846533bddb 100644 --- a/config/locales/simple_form.fr.yml +++ b/config/locales/simple_form.fr.yml @@ -225,7 +225,7 @@ fr: expires_in: Expire après fields: Métadonnées du profil filter_action: Action du filtre - header: Image d’en-tête + header: Photo de couverture honeypot: "%{label} (ne pas remplir)" inbox_url: URL de la boîte de relais irreversible: Supprimer plutôt que masquer diff --git a/config/locales/simple_form.is.yml b/config/locales/simple_form.is.yml index 34121f6e8c..a1540d0712 100644 --- a/config/locales/simple_form.is.yml +++ b/config/locales/simple_form.is.yml @@ -134,6 +134,7 @@ is: otp: 'Settu inn tveggja-þátta kóðann sem farsímaforritið útbjó eða notaðu einn af endurheimtukóðunum þínum:' webauthn: Ef þetta er USB-lykill, gakktu úr skugga um að honum sé stungið í samband og ef þörf þykir að ýta á hann. settings: + email_subscriptions: Sé þetta gert óvirkt haldast fyrirliggjandi áskrifendur en sending tölvupósta stöðvast. indexable: Síðan með notandasniðinu þínu gæti birst í leitarniðurstöðum Google, Bing og fleiri. show_application: Þú munt alltaf geta séð hvaða forrit birti færsluna þína. tag: @@ -356,6 +357,7 @@ is: hint: Viðbótarupplýsingar text: Regla settings: + email_subscriptions: Virkja áskriftir í tölvupósti indexable: Hafa notandasnið með í leitarvélum show_application: Birta úr hvaða forriti þú sendir færslu tag: diff --git a/config/locales/simple_form.sq.yml b/config/locales/simple_form.sq.yml index b36bdb59ec..5d7b7b6757 100644 --- a/config/locales/simple_form.sq.yml +++ b/config/locales/simple_form.sq.yml @@ -133,6 +133,7 @@ sq: otp: 'Jepni kodin dyfaktorësh të prodhuar nga aplikacioni i telefonit tuaj ose përdorni një nga kodet tuaj të rikthimeve:' webauthn: Nëse është një diskth USB, sigurohuni se e keni futur dhe, në qoftë e nevojshme, prekeni. settings: + email_subscriptions: Çaktivzimi i mban pajtimtarët ekzistues, por resht dërgim email-esh. indexable: Faqja e profilit tuaj mund të shfaqet në përfundime kërkimi në Google, Bing dhe të tjerë. show_application: Pavarësisht nga kjo, do të jeni përherë në gjendje të shihni cili aplikacion botoi postimin tuaj. tag: @@ -355,6 +356,7 @@ sq: hint: Informacion shtesë text: Rregull settings: + email_subscriptions: Aktivizo regjistrime me email indexable: Përfshi faqe profili në motorë kërkimesh show_application: Shfaq prej cilit aplikacion dërguat një postim tag: diff --git a/config/locales/simple_form.vi.yml b/config/locales/simple_form.vi.yml index bb43d41c1e..04c1a977ea 100644 --- a/config/locales/simple_form.vi.yml +++ b/config/locales/simple_form.vi.yml @@ -134,6 +134,7 @@ vi: otp: 'Nhập mã xác thực 2 bước được tạo bởi ứng dụng điện thoại của bạn hoặc dùng một trong các mã khôi phục của bạn:' webauthn: Nếu đây là USB key, hãy cắm vào và thử xoay chiều. settings: + email_subscriptions: Việc vô hiệu hóa sẽ giữ lại những người đăng ký hiện có nhưng ngừng gửi email. indexable: Trang của bạn có thể xuất hiện trong kết quả tìm kiếm trên Google, Bing và các nơi khác. show_application: Bạn luôn có thể xem ứng dụng đã đăng tút của mình. tag: @@ -355,6 +356,7 @@ vi: hint: Thông tin thêm text: Nội quy settings: + email_subscriptions: Bật đăng ký đọc email indexable: Hiện hồ sơ trong công cụ tìm kiếm show_application: Hiện ứng dụng dùng để đăng tút tag: diff --git a/config/locales/simple_form.zh-CN.yml b/config/locales/simple_form.zh-CN.yml index c2e16f2459..03f8cf63f9 100644 --- a/config/locales/simple_form.zh-CN.yml +++ b/config/locales/simple_form.zh-CN.yml @@ -134,6 +134,7 @@ zh-CN: otp: 输入你手机应用上生成的双因素认证代码,或者任意一个恢复代码: webauthn: 如果是 USB 密钥,请确保将其插入,如有必要,请点击它。 settings: + email_subscriptions: 禁用会保留现有的订阅者,而停止发送电子邮件。 indexable: 你的个人资料可能会出现在Google、Bing等的搜索结果中。 show_application: 无论如何,你始终可以看到是哪个应用发布了你的嘟文。 tag: @@ -355,6 +356,7 @@ zh-CN: hint: 补充信息 text: 规则 settings: + email_subscriptions: 启用邮件订阅 indexable: 允许搜索引擎索引个人资料 show_application: 显示你发嘟使用的应用 tag: diff --git a/config/locales/simple_form.zh-TW.yml b/config/locales/simple_form.zh-TW.yml index fcc6694b89..9cea1c15a7 100644 --- a/config/locales/simple_form.zh-TW.yml +++ b/config/locales/simple_form.zh-TW.yml @@ -134,6 +134,7 @@ zh-TW: otp: 請輸入產生自您手機 App 的兩階段驗證碼,或輸入其中一個備用驗證碼: webauthn: 若它是 USB 安全金鑰,請確認已正確插入,如有需要請觸擊。 settings: + email_subscriptions: 停用將保留既有訂閱者,但會停止寄送電子郵件。 indexable: 個人檔案可能出現於 Google、Bing、或其他搜尋引擎。 show_application: 將總是顯示您發嘟文之應用程式 tag: @@ -355,6 +356,7 @@ zh-TW: hint: 其他資訊 text: 規則 settings: + email_subscriptions: 啟用電子郵件訂閱註冊 indexable: 於搜尋引擎中包含個人檔案頁面 show_application: 顯示您發嘟文之應用程式 tag: diff --git a/config/locales/sk.yml b/config/locales/sk.yml index 0a684eeb4c..a3f9b5cd8a 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -1080,12 +1080,6 @@ sk: password: heslom webauthn: bezpečnostnými kľúčmi title: História overení - mail_subscriptions: - unsubscribe: - emails: - notification_emails: - reblog: e-mailové upozornenia na zdieľania - title: Ukonči odber media_attachments: validations: images_and_video: K príspevku ktorý už obsahuje obrázky nemôžeš priložiť video diff --git a/config/locales/sl.yml b/config/locales/sl.yml index 6061b6fc8e..ed5f5f4ca8 100644 --- a/config/locales/sl.yml +++ b/config/locales/sl.yml @@ -1227,7 +1227,6 @@ sl: application_mailer: notification_preferences: Spremenite e-poštne nastavitve salutation: "%{name}," - settings: 'Spremenite e-poštne nastavitve: %{link}' unsubscribe: Odjavi od naročnine view: 'Pogled:' view_profile: Ogled profila @@ -1685,21 +1684,6 @@ sl: failed_sign_in_html: Spodletela prijava z metodo %{method} iz %{ip} (%{browser}) successful_sign_in_html: Uspešna prijava z metodo %{method} iz %{ip} (%{browser}) title: Zgodovina overjanja - mail_subscriptions: - unsubscribe: - action: Da, odjavi me - complete: Odjavljeni - confirmation_html: Ali se res želite odjaviti od prejemanja %{type} za Mastodon na %{domain} na svojo e-pošto %{email}? Kadarkoli se lahko znova prijavite iz svojih nastavitev e-poštnih obvestil. - emails: - notification_emails: - favourite: e-sporočil z obvestili o priljubljenosti - follow: e-sporočil z obvestili o sledenju - follow_request: e-sporočil o zahtevah za sledenje - mention: e-sporočil z obvestili o omembah - reblog: e-sporočil z obvestili o izpostavljanju - resubscribe_html: Če ste se odjavili po pomoti, se lahko znova prijavite iz svojih nastavitev e-poštnih obvestil. - success_html: Nič več ne boste prejemali %{type} za Mastodon na %{domain} na svoj e-naslov %{email}. - title: Odjavi od naročnine media_attachments: validations: images_and_video: Videoposnetka ni mogoče priložiti objavi, ki že vsebuje slike diff --git a/config/locales/sq.yml b/config/locales/sq.yml index caf915df78..910daa74db 100644 --- a/config/locales/sq.yml +++ b/config/locales/sq.yml @@ -757,6 +757,7 @@ sq: other: "%{count} përdorues" categories: administration: Administrim + email: Email invites: Ftesa moderation: Moderim special: Special @@ -785,6 +786,8 @@ sq: manage_blocks_description: U lejon përdoruesve të bllkojnë shërbime email dhe adresa IP manage_custom_emojis: Të Administrojë Emoxhi Vetjake manage_custom_emojis_description: U lejon përdoruesve të administrojnë te shërbyesi emoxhi vetjake + manage_email_subscriptions: Administroni Pajtime Me Email + manage_email_subscriptions_description: Lejojini përdoruesit të pajtohen me email te përdorues me këtë leje manage_federation: Të Administrojë Federim manage_federation_description: U lejon përdoruesve të bllokojnë ose lejojnë federim me përkatësi të tjera dhe të kontrollojnë shpërndarjen manage_invites: Të Administrojë Ftesa @@ -1220,7 +1223,6 @@ sq: application_mailer: notification_preferences: Ndryshoni parapëlqime rreth email-esh salutation: "%{name}," - settings: 'Ndryshoni parapëlqime rreth email-esh: %{link}' unsubscribe: Shpajtohuni view: 'Parje:' view_profile: Shihni profilin @@ -1408,6 +1410,38 @@ sq: basic_information: Hollësi elementare hint_html: "Përshtatni ç’shohin njerëzit në profilin tuaj publik dhe në krah të postimeve tuaja. Personat e tjerë ka më shumë gjasa t’ju ndjekin dhe ndërveprojnë me ju, kur keni të plotësuar profilin dhe një foto profili." other: Tjetër + email_subscription_mailer: + confirmation: + action: Ripohoni adresë email + instructions_to_confirm: Ripohoni se do të donit të merrni email -e nga %{name} (@%{acct}), kur boton postime të reja. + instructions_to_ignore: Nëse s’jeni i sigurt pse e morët këtë email, mund ta fshini. S’do të pajtoheni, po qe se nuk klikoni mbi lidhjen më sipër. + subject: Ripohoni adresën tuaj email + title: Të merren përditësime me email nga %{name}? + notification: + create_account: Krijoni një llogari Mastodon + footer: + privacy_html: Email-et dërgohen nga %{domain}, një shërbyes i bazuar në Mastodon. Që të kuptoni se si ky shërbyes përpunon të dhënat tuaja personale, referojuni Rregullave të Privatësisë. + reason_for_email_html: Po e merrni këtë email ngaqë keni zgjedhur përditësime me email nga %{name}. S’doni t’i merrni këta email-e? Shpajtohuni + interact_with_this_post: + one: Ndërveproni me këtë postim dhe zbuloni më tepër si ai. + other: Ndërveproni me këto postime dhe zbuloni më tepër. + subject: + one: 'Postim i ri: “%{excerpt}”' + other: Postime të reja nga %{name} + title: + one: 'Postim i ri: “%{excerpt}”' + other: Postime të reja nga %{name} + email_subscriptions: + active: Aktiv + confirmations: + show: + changed_your_mind: Ndërruat mendje? + success_html: Tani do të filloni të merrni email-e, kur %{name} boton postime të reja. Shtojeni %{sender} te kontaktet tuaj, që këto postime të mos përfundojnë te dosja juaj e Të padëshiruarve. + title: U regjistruat + unsubscribe: Shpajtohuni + inactive: Joaktiv + status: Gjendje + subscribers: Pajtimtarë errors: '400': Kërkesa që parashtruar qe e pavlefshme ose e keqformuar. '403': S’keni leje të shihni këtë faqe. @@ -1638,21 +1672,6 @@ sq: failed_sign_in_html: Dështoi përpjekje hyrjeje me %{method} nga %{ip} (%{browser}) successful_sign_in_html: Hyrje e suksesshme me %{method} nga %{ip} (%{browser}) title: Historik mirëfilltësimesh - mail_subscriptions: - unsubscribe: - action: Po, shpajtomëni - complete: U shpajtuat - confirmation_html: Jeni i sigurt se doni të shpajtoheni nga marrje %{type} për Mastodon te %{domain} në email-in tuaj %{email}? Mundeni përherë të ripajtoheni që nga rregullimet tuaja për njoftime me email. - emails: - notification_emails: - favourite: email-e njoftimesh parapëlqimesh - follow: email-e njoftimesh ndjekjeje - follow_request: email-e kërkesash ndjekjeje - mention: email-e njoftimesh përmendjesh - reblog: email-e njoftimesh përforcimesh - resubscribe_html: Nëse u shpajtuat gabimisht, mund të ripajtoheni që nga rregullimet tuaja për njoftime me email. - success_html: S’do të merrni më %{type} për Mastodon te %{domain} në email-in tuaj te %{email}. - title: Shpajtohuni media_attachments: validations: images_and_video: S’mund të bashkëngjitet video te një gjendje që përmban figura tashmë @@ -1790,6 +1809,8 @@ sq: posting_defaults: Parazgjedhje postimesh public_timelines: Rrjedha kohore publike privacy: + email_subscriptions: Dërgo postime me email + email_subscriptions_hint_html: Shtoni te profili juaj një fotmular regjistrimi me email, që u shfaqet përdoruesve jo të futur në llogari. Kur vizitorët japin adresën e tyre email dhe zgjedhin, Mastodon-i do të dërgojë përditësime me email për postimet tuaja publike. hint_html: "Përshtatni mënyrën se si dëshironi të gjenden prej të tjerëve profili dhe postimet tuaja. Një larmi veçorish të Mastodon-it mund t’ju ndihmojnë të shtriheni në një publik më të gjerë, kur aktivizohen. Ndaluni një çast t’i shqyrtoni këto rregullime, për t’u siguruar se i përshtaten rastit tuaj." privacy: Privatësi privacy_hint_html: Kontrolloni sa doni të tregoni prej vetes për të mirën e të tjerëve. Njerëzit zbulojnë profile interensante dhe aplikacione të lezetshme duke shfletuar ndjekjet e personave të tjerë dhe duke parë se prej cilëve aplikacione postuan, por mund të parapëlqeni ta mbani të fshehur këtë. @@ -2052,6 +2073,28 @@ sq: resume_app_authorization: Rinis autorizim aplikacioni role_requirement: "%{domain} lyp që të ujdisni Mirëfilltësim Dyfaktorësh, para se të përdorni Mastodon-in." webauthn: Kyçe sigurie + unsubscriptions: + create: + action: Kalo te faqja hyrëse e shërbyesit + email_subscription: + confirmation_html: S’do të merrni më email-e nga %{name}. + title: U shpajtuat + user: + confirmation_html: S’do të merrni më %{type} nga Mastodon-i në %{domain}. + notification_emails: + favourite: email-e njoftimesh parapëlqimesh + follow: email-e njoftimesh ndjekjeje + follow_request: email-e kërkesash ndjekjeje + mention: email-e njoftimesh përmendjesh + reblog: email-e njoftimesh përforcimesh + show: + action: Shpajtohuni + email_subscription: + confirmation_html: Do të reshtni së marri email-e, kur kjo llogari të botojë postime të reja. + title: Të bëhet shpajtim nga %{name}? + user: + confirmation_html: Do të reshtni së marri %{type} nga Mastodon-i në %{domain}. + title: Të bëhet shpajtim nga %{type}? user_mailer: announcement_published: description: 'Përgjegjësit e %{domain} po bëjnë një njoftim:' diff --git a/config/locales/sr-Latn.yml b/config/locales/sr-Latn.yml index 3f9eee3cae..d75f5f76d3 100644 --- a/config/locales/sr-Latn.yml +++ b/config/locales/sr-Latn.yml @@ -1357,11 +1357,6 @@ sr-Latn: failed_sign_in_html: Neuspešan pokušaj prijavljivanja putem %{method} sa %{ip} (%{browser}) successful_sign_in_html: Uspešan pokušaj prijavljivanja putem %{method} sa %{ip} (%{browser}) title: Istorija autentifikacije - mail_subscriptions: - unsubscribe: - action: Da, odjavi me - complete: Odjavljen - title: Odjavi se media_attachments: validations: images_and_video: Ne može da se prikači video na status koji već ima slike diff --git a/config/locales/sr.yml b/config/locales/sr.yml index 35d845bb1e..8bc89180d4 100644 --- a/config/locales/sr.yml +++ b/config/locales/sr.yml @@ -1387,11 +1387,6 @@ sr: failed_sign_in_html: Неуспешан покушај пријављивања путем %{method} са %{ip} (%{browser}) successful_sign_in_html: Успешан покушај пријављивања путем %{method} са %{ip} (%{browser}) title: Историја аутентификације - mail_subscriptions: - unsubscribe: - action: Да, одјави ме - complete: Одјављен - title: Одјави се media_attachments: validations: images_and_video: Не може да се прикачи видео на статус који већ има слике diff --git a/config/locales/sv.yml b/config/locales/sv.yml index a514556ba9..0a26249b7d 100644 --- a/config/locales/sv.yml +++ b/config/locales/sv.yml @@ -1228,7 +1228,6 @@ sv: application_mailer: notification_preferences: Ändra e-postpreferenser salutation: "%{name}," - settings: 'Ändra e-postinställningar: %{link}' unsubscribe: Avprenumerera view: 'Granska:' view_profile: Visa profil @@ -1650,21 +1649,6 @@ sv: failed_sign_in_html: Misslyckat inloggningsförsök med %{method} från %{ip} (%{browser}) successful_sign_in_html: Lyckad inloggning med %{method} från %{ip} (%{browser}) title: Autentiseringshistorik - mail_subscriptions: - unsubscribe: - action: Ja, avsluta prenumerationen - complete: Prenumeration avslutad - confirmation_html: Är du säker på att du vill avregistrera dig från att ta emot %{type} för Mastodon på %{domain} med din e-post på %{email}? Du kan alltid återprenumerera bland dina e-postmeddelandeinställningar. - emails: - notification_emails: - favourite: aviseringsmejl för favoriserade inlägg - follow: aviseringsmejl för följda inlägg - follow_request: aviseringsmejl för följdförfrågningar - mention: aviseringsmejl för inlägg där du nämns - reblog: aviseringsmejl för förhöjda inlägg - resubscribe_html: Om du slutat prenumerera av misstag kan du återprenumerera i dina e-postaviseringsinställningar. - success_html: Du får inte längre %{type} för Mastodon på %{domain} till din e-post på %{email}. - title: Avsluta prenumeration media_attachments: validations: images_and_video: Det går inte att bifoga en video till ett inlägg som redan innehåller bilder diff --git a/config/locales/th.yml b/config/locales/th.yml index b3e2413235..905ecad4ab 100644 --- a/config/locales/th.yml +++ b/config/locales/th.yml @@ -1124,7 +1124,6 @@ th: application_mailer: notification_preferences: เปลี่ยนการกำหนดลักษณะอีเมล salutation: "%{name}," - settings: 'เปลี่ยนการกำหนดลักษณะอีเมล: %{link}' unsubscribe: เลิกบอกรับ view: 'ดู:' view_profile: ดูโปรไฟล์ @@ -1508,21 +1507,6 @@ th: failed_sign_in_html: ความพยายามในการลงชื่อเข้าด้วย %{method} จาก %{ip} (%{browser}) ล้มเหลว successful_sign_in_html: ลงชื่อเข้าด้วย %{method} จาก %{ip} (%{browser}) สำเร็จ title: ประวัติการรับรองความถูกต้อง - mail_subscriptions: - unsubscribe: - action: ใช่ เลิกบอกรับ - complete: เลิกบอกรับแล้ว - confirmation_html: คุณแน่ใจหรือไม่ว่าต้องการเลิกบอกรับจากการรับ %{type} สำหรับ Mastodon ใน %{domain} ไปยังอีเมลของคุณที่ %{email}? คุณสามารถบอกรับใหม่ได้เสมอจาก การตั้งค่าการแจ้งเตือนอีเมล ของคุณ - emails: - notification_emails: - favourite: อีเมลการแจ้งเตือนการชื่นชอบ - follow: อีเมลการแจ้งเตือนการติดตาม - follow_request: อีเมลคำขอติดตาม - mention: อีเมลการแจ้งเตือนการกล่าวถึง - reblog: อีเมลการแจ้งเตือนการดัน - resubscribe_html: หากคุณได้เลิกบอกรับโดยไม่ได้ตั้งใจ คุณสามารถบอกรับใหม่ได้จาก การตั้งค่าการแจ้งเตือนอีเมล ของคุณ - success_html: คุณจะไม่ได้รับ %{type} สำหรับ Mastodon ใน %{domain} ไปยังอีเมลของคุณที่ %{email} อีกต่อไป - title: เลิกบอกรับ media_attachments: validations: images_and_video: ไม่สามารถแนบวิดีโอกับโพสต์ที่มีภาพอยู่แล้ว diff --git a/config/locales/tr.yml b/config/locales/tr.yml index d58aab2664..c930f08e45 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -1231,7 +1231,6 @@ tr: application_mailer: notification_preferences: E-posta tercihlerini değiştir salutation: "%{name}," - settings: 'E-posta tercihlerini değiştir: %{link}' unsubscribe: Abonelikten çık view: 'Görüntüle:' view_profile: Profili görüntüle @@ -1653,21 +1652,6 @@ tr: failed_sign_in_html: "%{method} yöntemiyle %{ip} (%{browser}) adresinden başarısız oturum açma girişimi" successful_sign_in_html: "%{method} yöntemiyle %{ip} (%{browser}) adresinden başarılı oturum açma" title: Kimlik doğrulama geçmişi - mail_subscriptions: - unsubscribe: - action: Evet, abonelikten çık - complete: Abonelikten çık - confirmation_html: '%{domain} üzerindeki Mastodon için %{type} almayı durdurarak %{email} adresinize aboneliğinizi iptal etmek istediğinizden emin misiniz? e-posta bildirim ayarlarınızdan her zaman yeniden abone olabilirsiniz.' - emails: - notification_emails: - favourite: favori bildirim e-postaları - follow: takip bildirim e-postaları - follow_request: takip isteği bildirim e-postaları - mention: bahsetme bildirim e-postaları - reblog: öne çıkanlar bildirim e-postaları - resubscribe_html: Abonelikten yanlışlıkla çıktıysanız, e-posta bildirim ayarlarınızdan yeniden abone olabilirsiniz. - success_html: Artık %{email} adresindeki e-postanıza %{domain} üzerindeki Mastodon için %{type} almayacaksınız. - title: Abonelikten çık media_attachments: validations: images_and_video: Zaten resim içeren bir duruma video eklenemez diff --git a/config/locales/uk.yml b/config/locales/uk.yml index 11dcf51c6e..431b6e18a5 100644 --- a/config/locales/uk.yml +++ b/config/locales/uk.yml @@ -1180,7 +1180,6 @@ uk: application_mailer: notification_preferences: Змінити налаштування електронної пошти salutation: "%{name}," - settings: 'Змінити налаштування електронної пошти: %{link}' unsubscribe: Відписатися view: 'Перегляд:' view_profile: Показати профіль @@ -1570,21 +1569,6 @@ uk: failed_sign_in_html: Не вдалося увійти з %{method} з %{ip} (%{browser}) successful_sign_in_html: Успішний вхід з %{method} з %{ip} (%{browser}) title: Історія входів - mail_subscriptions: - unsubscribe: - action: Так, відписатися - complete: Відписалися - confirmation_html: Ви впевнені, що хочете відписатися від отримання %{type} для Mastodon на %{domain} до своєї скриньки %{email}? Ви можете повторно підписатися у налаштуваннях сповіщень електронною поштою. - emails: - notification_emails: - favourite: отримувати сповіщення про вподобання електронною поштою - follow: отримувати сповіщення про підписки електронною поштою - follow_request: отримувати сповіщення про запити на стеження електронною поштою - mention: отримувати сповіщення про згадки електронною поштою - reblog: отримувати сповіщення про поширення електронною поштою - resubscribe_html: Якщо ви відписалися помилково, ви можете повторно підписатися в налаштуваннях сповіщень електронною поштою. - success_html: Ви більше не отримуватимете %{type} для Mastodon %{domain} на адресу %{email}. - title: Відписатися media_attachments: validations: images_and_video: Не можна додати відео до допису з зображеннями diff --git a/config/locales/vi.yml b/config/locales/vi.yml index f773dda729..976fb782ce 100644 --- a/config/locales/vi.yml +++ b/config/locales/vi.yml @@ -747,6 +747,7 @@ vi: categories: administration: Quản trị viên devops: Nhà phát triển + email: Email invites: Lời mời moderation: Kiểm duyệt special: Đặc biệt @@ -774,6 +775,8 @@ vi: manage_blocks_description: Cho phép người dùng tự chặn các nhà cung cấp email và địa chỉ IP manage_custom_emojis: Quản lý emoji manage_custom_emojis_description: Cho phép quản lý các emoji tùy chỉnh trên máy chủ + manage_email_subscriptions: Quản lý đăng ký đọc qua email + manage_email_subscriptions_description: Cho phép người dùng đăng ký nhận thông báo qua email từ những người dùng có quyền này manage_federation: Quản lý liên hợp manage_federation_description: Cho phép chặn hoặc liên hợp với các máy chủ khác và kiểm soát khả năng phân phối manage_invites: Quản lý lời mời @@ -1210,7 +1213,6 @@ vi: application_mailer: notification_preferences: Thay đổi thiết lập email salutation: "%{name}," - settings: 'Thay đổi thiết lập email: %{link}' unsubscribe: Hủy đăng ký view: 'Chi tiết:' view_profile: Xem trang hồ sơ @@ -1398,6 +1400,35 @@ vi: basic_information: Thông tin cơ bản hint_html: Mọi người sẽ muốn theo dõi và tương tác với bạn hơn nếu bạn có ảnh đại diện và hồ sơ hoàn chỉnh. other: Khác + email_subscription_mailer: + confirmation: + action: Xác nhận địa chỉ email + instructions_to_confirm: Xác nhận bạn muốn nhận email từ %{name} (@%{acct}) khi họ đăng tút mới. + instructions_to_ignore: Nếu bạn không chắc tại sao mình nhận được email này, bạn có thể xóa nó. Bạn sẽ không được đăng ký nếu không nhấn vào liên kết ở trên. + subject: Xác nhận địa chỉ email của bạn + title: Nhận cập nhật qua email từ %{name}? + notification: + create_account: Tạo một tài khoản Mastodon + footer: + privacy_html: Các email sẽ được gửi từ %{domain}, một máy chủ vận hành nhờ Mastodon. Để hiểu cách máy chủ này xử lý dữ liệu cá nhân của bạn, vui lòng tham khảo Chính sách bảo mật. + reason_for_email_html: Bạn nhận được email này vì bạn đã đăng ký nhận tút mới qua email từ %{name}. Không muốn nhận email này? Hủy đăng ký đọc + interact_with_this_post: + other: Hãy tương tác với những tút này và khám phá thêm nhiều điều thú vị. + subject: + other: Tút mới từ %{name} + title: + other: Những tút mới từ %{name} + email_subscriptions: + active: Hoạt động + confirmations: + show: + changed_your_mind: Thay đổi ý định? + success_html: Bạn sẽ bắt đầu nhận được email khi %{name} đăng tút mới. Thêm %{sender} vào danh bạ của bạn để những tút này không bị chuyển vào thư mục Spam. + title: Bạn đã đăng ký đọc + unsubscribe: Hủy đăng ký đọc + inactive: Không hoạt động + status: Trạng thái + subscribers: Người đăng ký đọc emoji_styles: auto: Tự động native: Nguyên bản @@ -1612,21 +1643,6 @@ vi: failed_sign_in_html: Đăng nhập thất bại bằng %{method} từ %{ip} (%{browser}) successful_sign_in_html: Đăng nhập bằng %{method} từ %{ip} (%{browser}) title: Lịch sử đăng nhập - mail_subscriptions: - unsubscribe: - action: Đúng, hủy đăng ký - complete: Đã hủy đăng ký - confirmation_html: Bạn có chắc muốn hủy đăng ký %{type} Mastodon trên %{domain} tới %{email}? Bạn có thể đăng ký lại từ cài đặt thông báo email. - emails: - notification_emails: - favourite: email thông báo lượt thích - follow: email thông báo người theo dõi mới - follow_request: email thông báo yêu cầu theo dõi - mention: email thông báo lượt nhắc đến - reblog: email thông báo lượt đăng lại - resubscribe_html: Nếu đổi ý, bạn có thể đăng ký lại từ cài đặt thông báo email. - success_html: Bạn sẽ không còn nhận %{type} Mastodon trên %{domain} tới %{email}. - title: Hủy đăng ký media_attachments: validations: images_and_video: Không thể đính kèm video vào tút đã chứa hình ảnh @@ -1764,6 +1780,8 @@ vi: posting_defaults: Mặc định cho tút public_timelines: Bảng tin privacy: + email_subscriptions: Gửi tút qua email + email_subscriptions_hint_html: Thêm biểu mẫu đăng ký email vào hồ sơ của bạn, biểu mẫu này sẽ hiển thị cho cả người dùng chưa đăng nhập. Khi khách truy cập nhập địa chỉ email của họ và chọn đăng ký, Mastodon sẽ gửi thông báo qua email về các tút công khai của bạn. hint_html: Tùy chỉnh cách mọi người tìm thấy hồ sơ và tút của bạn. privacy: Riêng tư privacy_hint_html: Kiểm soát mức độ tiết lộ chi tiết. Mọi người khám phá các hồ sơ thú vị và các ứng dụng thú vị bằng cách xem bạn theo dõi ai và bạn đăng bằng ứng dụng nào, nhưng có thể bạn muốn ẩn nó đi. @@ -2023,6 +2041,28 @@ vi: resume_app_authorization: Tiếp tục xác thực bằng ứng dụng role_requirement: "%{domain} yêu cầu bạn thiết lập Xác thụec 2 bước trước khi sử dụng Mastodon." webauthn: Khóa bảo mật + unsubscriptions: + create: + action: Đến trang chủ máy chủ + email_subscription: + confirmation_html: Bạn sẽ không còn nhận được email từ %{name}. + title: Bạn đã hủy đăng ký đọc + user: + confirmation_html: Bạn sẽ không còn nhận %{type} từ Mastodon trên %{domain}. + notification_emails: + favourite: email thông báo lượt thích + follow: email thông báo người theo dõi mới + follow_request: email thông báo yêu cầu theo dõi + mention: email thông báo lượt nhắc đến + reblog: email thông báo lượt đăng lại + show: + action: Hủy đăng ký đọc + email_subscription: + confirmation_html: Bạn sẽ ngừng nhận email khi tài khoản này đăng tút mới. + title: Hủy đăng ký đọc %{name}? + user: + confirmation_html: Bạn sẽ không còn nhận %{type} từ Mastodon trên %{domain}. + title: Hủy đăng ký đọc %{type}? user_mailer: announcement_published: description: 'Quản trị viên %{domain} gửi một thông báo:' diff --git a/config/locales/zh-CN.yml b/config/locales/zh-CN.yml index c161b99681..f0e5517565 100644 --- a/config/locales/zh-CN.yml +++ b/config/locales/zh-CN.yml @@ -747,6 +747,7 @@ zh-CN: categories: administration: 管理 devops: 开发运维 + email: 电子邮件 invites: 邀请 moderation: 审核 special: 特殊 @@ -774,6 +775,8 @@ zh-CN: manage_blocks_description: 允许用户屏蔽邮箱域名与IP地址 manage_custom_emojis: 管理自定义表情 manage_custom_emojis_description: 允许用户管理站点上的自定义表情 + manage_email_subscriptions: 管理邮件订阅 + manage_email_subscriptions_description: 允许其他用户通过电子邮件订阅具备此权限的用户 manage_federation: 管理联合 manage_federation_description: 允许用户禁止或允许本站同其他站点的联合,并控制消息投递能力 manage_invites: 管理邀请 @@ -1210,7 +1213,6 @@ zh-CN: application_mailer: notification_preferences: 更改邮件偏好 salutation: "%{name}:" - settings: 更改邮件偏好: %{link} unsubscribe: 取消订阅 view: 点此链接查看详情: view_profile: 查看个人资料 @@ -1398,6 +1400,35 @@ zh-CN: basic_information: 基本信息 hint_html: "自定义公开资料和嘟文旁边显示的内容。当你填写完整的个人资料并设置了头像时,其他人更有可能关注你并与你互动。" other: 其他 + email_subscription_mailer: + confirmation: + action: 确认电子邮件地址 + instructions_to_confirm: 确认你确实想要在 %{name}(@%{acct})发布新嘟文时收到电子邮件通知。 + instructions_to_ignore: 如果你不知道为什么收到这封电子邮件,可以直接删除。如果你不点击上方链接,则不会为你订阅。 + subject: 确认你的电子邮件地址 + title: 要接收来自 %{name} 的电子邮件推送吗? + notification: + create_account: 创建 Mastodon 账号 + footer: + privacy_html: 电子邮件发送自 %{domain},由 Mastodon 驱动的社区实例。要了解本站如何处理你的个人信息,请参见隐私政策。 + reason_for_email_html: 你收到此邮件是因为你先前订阅了来自 %{name} 的嘟文邮件通知。不想收到这些电子邮件?可以取消订阅 + interact_with_this_post: + other: 和嘟文互动,探索更多相关内容。 + subject: + other: 来自 %{name} 的新嘟文 + title: + other: 来自 %{name} 的新嘟文 + email_subscriptions: + active: 已生效 + confirmations: + show: + changed_your_mind: 改变主意了吗? + success_html: 现在开始当 %{name} 发布新嘟文时你会收到邮件提醒。记得将 %{sender} 添加到邮箱联系人中,以免嘟文推送被丢入垃圾邮件文件夹。 + title: 你已成功订阅 + unsubscribe: 取消订阅 + inactive: 未生效 + status: 状态 + subscribers: 订阅者 emoji_styles: auto: 自动 native: 原生 @@ -1612,21 +1643,6 @@ zh-CN: failed_sign_in_html: 失败的 %{method} 登录尝试,来自 %{ip} (%{browser}) successful_sign_in_html: 通过 %{method} 成功登录,来自 %{ip} (%{browser}) title: 认证历史 - mail_subscriptions: - unsubscribe: - action: 是,取消订阅 - complete: 已取消订阅 - confirmation_html: 你确定要退订来自 %{domain} 上的 Mastodon 的 %{type} 到你的邮箱 %{email} 吗?你可以随时在邮件通知设置中重新订阅。 - emails: - notification_emails: - favourite: 嘟文被喜欢邮件通知 - follow: 账号被关注邮件通知 - follow_request: 关注请求邮件通知 - mention: 账号被提及邮件通知 - reblog: 嘟文被转嘟邮件通知 - resubscribe_html: 如果你不小心取消了订阅,可以在你的邮件通知设置中重新订阅。 - success_html: 你将不会在你的邮箱 %{email} 中收到 %{domain} 上的 Mastodon的 %{type} - title: 取消订阅 media_attachments: validations: images_and_video: 无法在嘟文中同时插入视频和图片 @@ -1764,6 +1780,8 @@ zh-CN: posting_defaults: 发布默认值 public_timelines: 公共时间线 privacy: + email_subscriptions: 通过电子邮件发送嘟文 + email_subscriptions_hint_html: 在你的个人资料中添加电子邮件订阅表单,此表单会显示给未登录的用户。当访客输入电子邮件地址并主动加入时,Mastodon 将在你更新公开嘟文时为这些访客发送电子邮件通知。 hint_html: "自定义你希望如何找到你的个人资料和嘟文。启用Mastodon中的各种功能可以帮助你扩大受众范围。请花点时间查看这些设置,确保它们适合你的使用情况。" privacy: 隐私 privacy_hint_html: 控制你愿意向他人透露多少信息。通过浏览他人的关注列表和查看他们发嘟所用的应用,人们可以发现有趣的用户和酷炫的应用,但你可能更喜欢将其隐藏起来。 @@ -2023,6 +2041,28 @@ zh-CN: resume_app_authorization: 恢复应用程序授权 role_requirement: "%{domain} 要求你设置双因素认证以使用 Mastodon。" webauthn: 安全密钥 + unsubscriptions: + create: + action: 转到服务器主页 + email_subscription: + confirmation_html: 你将不会再收到来自 %{name} 的电子邮件。 + title: 你已取消订阅 + user: + confirmation_html: 你将不会再收到 %{domain} 上的 Mastodon 的%{type}。 + notification_emails: + favourite: 嘟文被喜欢邮件通知 + follow: 账号被关注邮件通知 + follow_request: 关注请求邮件通知 + mention: 账号被提及邮件通知 + reblog: 嘟文被转嘟邮件通知 + show: + action: 取消订阅 + email_subscription: + confirmation_html: 你将不再收到此账号发布新嘟文时的通知邮件。 + title: 取消订阅%{name}? + user: + confirmation_html: 你将停止接收 %{domain} 上的 Mastodon 的%{type}。 + title: 取消订阅%{type}? user_mailer: announcement_published: description: "%{domain}管理员发布了一则公告:" diff --git a/config/locales/zh-HK.yml b/config/locales/zh-HK.yml index ac26ce9af4..6caf362fa7 100644 --- a/config/locales/zh-HK.yml +++ b/config/locales/zh-HK.yml @@ -1341,11 +1341,6 @@ zh-HK: failed_sign_in_html: 以 %{method} 從 %{ip} (%{browser}) 登入失敗 successful_sign_in_html: 以 %{method} 從 %{ip} (%{browser}) 成功登入 title: 驗證操作歷史 - mail_subscriptions: - unsubscribe: - action: 沒錯,取消訂閱 - complete: 已取消訂閱 - title: 取消訂閱 media_attachments: validations: images_and_video: 不能在已有圖片的文章上加入影片 diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 4585b729bc..ecffe7aefe 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -747,6 +747,7 @@ zh-TW: categories: administration: 管理員 devops: DevOps + email: 電子郵件 invites: 邀請 moderation: 站務 special: 特殊 @@ -774,6 +775,8 @@ zh-TW: manage_blocks_description: 允許使用者封鎖電子郵件提供商與 IP 位址 manage_custom_emojis: 管理自訂 emoji 表情符號 manage_custom_emojis_description: 允許使用者管理伺服器上之自訂 emoji 表情符號 + manage_email_subscriptions: 管理電子郵件訂閱 + manage_email_subscriptions_description: 此權限允許使用者透過電子郵件訂閱其他使用者動態 manage_federation: 管理聯邦宇宙 manage_federation_description: 允許使用者封鎖或允許與其他網域的聯邦宇宙,並控制傳遞能力 manage_invites: 管理邀請 @@ -1212,7 +1215,6 @@ zh-TW: application_mailer: notification_preferences: 變更電子郵件設定 salutation: "%{name}、" - settings: 變更電子郵件設定︰%{link} unsubscribe: 取消訂閱 view: 進入瀏覽: view_profile: 檢視個人檔案 @@ -1400,6 +1402,35 @@ zh-TW: basic_information: 基本資訊 hint_html: "自訂人們能於您個人檔案及嘟文旁所見之內容。當您完成填寫個人檔案及設定大頭貼後,其他人們比較願意跟隨您並與您互動。" other: 其他 + email_subscription_mailer: + confirmation: + action: 確認電子郵件地址 + instructions_to_confirm: 確認您想要收到來自 %{name} (@%{acct}) 發表新嘟文時的電子郵件。 + instructions_to_ignore: 您若不確定為何您收到此電子郵件,您可以刪除它。您若不點擊上方連結,將不會訂閱。 + subject: 確認您的電子郵件地址 + title: 是否想要收到來自 %{name} 之電子郵件通訊? + notification: + create_account: 建立 Mastodon 帳號 + footer: + privacy_html: 這些電子郵件由 Mastodon 伺服器 %{domain} 寄出。欲了解這台伺服器如何處理您的個人資料,請參考 隱私權政策。 + reason_for_email_html: 您正收到這封電子郵件,因為您選擇收到來自 %{name} 之電子郵件通訊。不想再收到這些電子郵件?取消訂閱 + interact_with_this_post: + other: 與此嘟文互動並且發現更多類似嘟文。 + subject: + other: 來自 %{name} 之新嘟文 + title: + other: 來自 %{name} 之新嘟文 + email_subscriptions: + active: 生效中 + confirmations: + show: + changed_your_mind: 改變主意了嗎? + success_html: 您將開始收到當 %{name} 發表新嘟文之電子郵件。請新增 %{sender} 至您的通訊錄使這些嘟文不被分類至垃圾信件夾。 + title: 已完成註冊 + unsubscribe: 取消訂閱 + inactive: 已停用 + status: 狀態 + subscribers: 訂閱者 emoji_styles: auto: 自動 native: 原生風格 @@ -1614,21 +1645,6 @@ zh-TW: failed_sign_in_html: 使用來自 %{ip} (%{browser}) 的 %{method} 登入嘗試失敗 successful_sign_in_html: 使用來自 %{ip} (%{browser}) 的 %{method} 登入成功 title: 認證歷史紀錄 - mail_subscriptions: - unsubscribe: - action: 是的,取消訂閱 - complete: 取消訂閱 - confirmation_html: 您確定要取要取消訂閱自 Mastodon 上 %{domain} 之 %{type} 至您電子郵件 %{email} 嗎?您隨時可以自電子郵件通知設定重新訂閱。 - emails: - notification_emails: - favourite: 最愛通知電子郵件 - follow: 跟隨通知電子郵件 - follow_request: 跟隨請求通知電子郵件 - mention: 提及通知電子郵件 - reblog: 轉嘟通知電子郵件 - resubscribe_html: 若您不慎錯誤地取消訂閱,您可以自電子郵件通知設定重新訂閱。 - success_html: 您將不再收到來自 Mastodon 上 %{domain} 之 %{type} 至您電子郵件 %{email}。 - title: 取消訂閱 media_attachments: validations: images_and_video: 無法於已有圖片之嘟文中加入影片 @@ -1766,6 +1782,8 @@ zh-TW: posting_defaults: 嘟文預設值 public_timelines: 公開時間軸 privacy: + email_subscriptions: 以電子郵件寄送嘟文 + email_subscriptions_hint_html: 於您的個人檔案中新增電子郵件訂閱表單,該表單將對未登入使用者顯示。當訪客輸入他們的電子郵件地址並選擇訂閱時,Mastodon 將會寄送您公開嘟文之電子郵件通訊。 hint_html: "自訂您希望如何使您的個人檔案及嘟文被發現。藉由啟用一系列 Mastodon 功能以幫助您觸及更廣的受眾。煩請花些時間確認您是否欲啟用這些設定。" privacy: 隱私權 privacy_hint_html: 控制您希望向其他人揭露之內容。人們透過瀏覽其他人的跟隨者與其發嘟之應用程式發現有趣的個人檔案與酷炫的 Mastodon 應用程式,但您能選擇將其隱藏。 @@ -2025,6 +2043,28 @@ zh-TW: resume_app_authorization: 恢復應用程式授權 role_requirement: "%{domain} 要求您設定兩階段驗證以使用 Mastodon。" webauthn: 安全金鑰 + unsubscriptions: + create: + action: 前往伺服器首頁 + email_subscription: + confirmation_html: 您將不再收到來自 %{name} 之電子郵件。 + title: 您已成功取消訂閱 + user: + confirmation_html: 您將不再收到來自 %{domain} Mastodon 之 %{type}。 + notification_emails: + favourite: 最愛通知電子郵件 + follow: 跟隨通知電子郵件 + follow_request: 跟隨請求通知電子郵件 + mention: 提及通知電子郵件 + reblog: 轉嘟通知電子郵件 + show: + action: 取消訂閱 + email_subscription: + confirmation_html: 您將不再收到來自此帳號新嘟文之電子郵件。 + title: 取消訂閱 %{name}? + user: + confirmation_html: 您將不再收到來自 %{domain} Mastodon 之 %{type}。 + title: 取消訂閱 %{type}? user_mailer: announcement_published: description: "%{domain} 之管理員正在進行公告:" diff --git a/config/routes.rb b/config/routes.rb index 1bc32a2861..d31331a6c1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -71,7 +71,7 @@ Rails.application.routes.draw do devise_scope :user do get '/invite/:invite_code', to: 'auth/registrations#new', as: :public_invite - resource :unsubscribe, only: [:show, :create], controller: :mail_subscriptions + resource :unsubscribe, only: [:show, :create], controller: :unsubscriptions namespace :auth do resource :setup, only: [:show, :update], controller: :setup @@ -188,6 +188,10 @@ Rails.application.routes.draw do resources :statuses, only: :show end + namespace :email_subscriptions do + resource :confirmation, only: :show + end + resources :media, only: [:show] do get :player end diff --git a/config/routes/api.rb b/config/routes/api.rb index 300baa1bc5..87a1738483 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -146,14 +146,13 @@ namespace :api, format: false do resources :peers, only: [:index] resources :rules, only: [:index] resources :domain_blocks, only: [:index] + resources :terms_of_service, only: [:index, :show], param: :date + resource :privacy_policy, only: [:show] - resource :terms_of_service, only: [:show] resource :extended_description, only: [:show] resource :translation_languages, only: [:show] resource :languages, only: [:show] resource :activity, only: [:show], controller: :activity - - get '/terms_of_service/:date', to: 'terms_of_services#show' end end @@ -223,6 +222,7 @@ namespace :api, format: false do resources :identity_proofs, only: :index resources :featured_tags, only: :index resources :endorsements, only: :index + resources :email_subscriptions, only: :create end member do diff --git a/db/migrate/20260311212130_create_email_subscriptions.rb b/db/migrate/20260311212130_create_email_subscriptions.rb new file mode 100644 index 0000000000..b750ccc7fc --- /dev/null +++ b/db/migrate/20260311212130_create_email_subscriptions.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class CreateEmailSubscriptions < ActiveRecord::Migration[8.1] + def change + create_table :email_subscriptions do |t| + t.references :account, null: false, foreign_key: { on_delete: :cascade } + t.string :email, null: false + t.string :locale, null: false + t.string :confirmation_token, index: { unique: true, where: 'confirmation_token is not null' } + t.datetime :confirmed_at + + t.timestamps + end + + add_index :email_subscriptions, [:account_id, :email], unique: true + end +end diff --git a/db/migrate/20260323105645_create_keypairs.rb b/db/migrate/20260323105645_create_keypairs.rb new file mode 100644 index 0000000000..e3ab970a7c --- /dev/null +++ b/db/migrate/20260323105645_create_keypairs.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class CreateKeypairs < ActiveRecord::Migration[8.0] + def change + create_table :keypairs do |t| + t.references :account, null: false, foreign_key: { on_delete: :cascade } + + t.string :uri, null: false + t.integer :type, null: false + t.string :public_key, null: false + t.string :private_key + t.datetime :expires_at + t.boolean :revoked, default: false, null: false + + t.timestamps + end + + add_index :keypairs, :uri, unique: true + end +end diff --git a/db/migrate/20260325151755_add_unique_indexes_to_collections_and_items.rb b/db/migrate/20260325151755_add_unique_indexes_to_collections_and_items.rb new file mode 100644 index 0000000000..df8604ebf1 --- /dev/null +++ b/db/migrate/20260325151755_add_unique_indexes_to_collections_and_items.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class AddUniqueIndexesToCollectionsAndItems < ActiveRecord::Migration[8.1] + disable_ddl_transaction! + + def change + add_index :collections, :uri, unique: true, where: 'uri IS NOT NULL', algorithm: :concurrently + add_index :collection_items, :uri, unique: true, where: 'uri IS NOT NULL', algorithm: :concurrently + end +end diff --git a/db/migrate/20260326112324_remove_unique_index_on_collection_item_object_uris.rb b/db/migrate/20260326112324_remove_unique_index_on_collection_item_object_uris.rb new file mode 100644 index 0000000000..0b4ba869bc --- /dev/null +++ b/db/migrate/20260326112324_remove_unique_index_on_collection_item_object_uris.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class RemoveUniqueIndexOnCollectionItemObjectUris < ActiveRecord::Migration[8.1] + def change + remove_index :collection_items, :object_uri, unique: true, where: '(activity_uri IS NOT NULL)' + end +end diff --git a/db/schema.rb b/db/schema.rb index 2ac78171aa..03b72a7fa1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2026_03_19_142348) do +ActiveRecord::Schema[8.1].define(version: 2026_03_26_112324) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -373,7 +373,7 @@ ActiveRecord::Schema[8.1].define(version: 2026_03_19_142348) do t.index ["account_id"], name: "index_collection_items_on_account_id" t.index ["approval_uri"], name: "index_collection_items_on_approval_uri", unique: true, where: "(approval_uri IS NOT NULL)" t.index ["collection_id"], name: "index_collection_items_on_collection_id" - t.index ["object_uri"], name: "index_collection_items_on_object_uri", unique: true, where: "(activity_uri IS NOT NULL)" + t.index ["uri"], name: "index_collection_items_on_uri", unique: true, where: "(uri IS NOT NULL)" end create_table "collection_reports", force: :cascade do |t| @@ -402,6 +402,7 @@ ActiveRecord::Schema[8.1].define(version: 2026_03_19_142348) do t.string "uri" t.index ["account_id"], name: "index_collections_on_account_id" t.index ["tag_id"], name: "index_collections_on_tag_id" + t.index ["uri"], name: "index_collections_on_uri", unique: true, where: "(uri IS NOT NULL)" end create_table "conversation_mutes", force: :cascade do |t| @@ -504,6 +505,19 @@ ActiveRecord::Schema[8.1].define(version: 2026_03_19_142348) do t.index ["domain"], name: "index_email_domain_blocks_on_domain", unique: true end + create_table "email_subscriptions", force: :cascade do |t| + t.bigint "account_id", null: false + t.string "confirmation_token" + t.datetime "confirmed_at" + t.datetime "created_at", null: false + t.string "email", null: false + t.string "locale", null: false + t.datetime "updated_at", null: false + t.index ["account_id", "email"], name: "index_email_subscriptions_on_account_id_and_email", unique: true + t.index ["account_id"], name: "index_email_subscriptions_on_account_id" + t.index ["confirmation_token"], name: "index_email_subscriptions_on_confirmation_token", unique: true, where: "(confirmation_token IS NOT NULL)" + end + create_table "fasp_backfill_requests", force: :cascade do |t| t.string "category", null: false t.datetime "created_at", null: false @@ -683,6 +697,20 @@ ActiveRecord::Schema[8.1].define(version: 2026_03_19_142348) do t.index ["ip"], name: "index_ip_blocks_on_ip", unique: true end + create_table "keypairs", force: :cascade do |t| + t.bigint "account_id", null: false + t.datetime "created_at", null: false + t.datetime "expires_at" + t.string "private_key" + t.string "public_key", null: false + t.boolean "revoked", default: false, null: false + t.integer "type", null: false + t.datetime "updated_at", null: false + t.string "uri", null: false + t.index ["account_id"], name: "index_keypairs_on_account_id" + t.index ["uri"], name: "index_keypairs_on_uri", unique: true + end + create_table "list_accounts", force: :cascade do |t| t.bigint "account_id", null: false t.bigint "follow_id" @@ -1475,6 +1503,7 @@ ActiveRecord::Schema[8.1].define(version: 2026_03_19_142348) do add_foreign_key "custom_filter_statuses", "statuses", on_delete: :cascade add_foreign_key "custom_filters", "accounts", on_delete: :cascade add_foreign_key "email_domain_blocks", "email_domain_blocks", column: "parent_id", on_delete: :cascade + add_foreign_key "email_subscriptions", "accounts", on_delete: :cascade add_foreign_key "fasp_backfill_requests", "fasp_providers" add_foreign_key "fasp_debug_callbacks", "fasp_providers" add_foreign_key "fasp_follow_recommendations", "accounts", column: "recommended_account_id" @@ -1495,6 +1524,7 @@ ActiveRecord::Schema[8.1].define(version: 2026_03_19_142348) do add_foreign_key "identities", "users", name: "fk_bea040f377", on_delete: :cascade add_foreign_key "instance_moderation_notes", "accounts", on_delete: :cascade add_foreign_key "invites", "users", on_delete: :cascade + add_foreign_key "keypairs", "accounts", on_delete: :cascade add_foreign_key "list_accounts", "accounts", on_delete: :cascade add_foreign_key "list_accounts", "follow_requests", on_delete: :cascade add_foreign_key "list_accounts", "follows", on_delete: :cascade diff --git a/package.json b/package.json index 304e10b778..67611ce968 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,8 @@ "@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", - "@formatjs/intl-pluralrules": "^5.4.4", + "@formatjs/intl-pluralrules": "^6.0.0", + "@formatjs/unplugin": "^1.1.5", "@gamestdio/websocket": "^0.3.2", "@github/webauthn-json": "^2.1.1", "@optimize-lodash/rollup-plugin": "^6.0.0", @@ -59,7 +60,6 @@ "async-mutex": "^0.5.0", "atrament": "0.2.4", "axios": "^1.4.0", - "babel-plugin-formatjs": "^10.5.37", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", "blurhash": "^2.0.5", "classnames": "^2.3.2", @@ -83,7 +83,7 @@ "http-link-header": "^1.1.1", "idb": "^8.0.3", "immutable": "^4.3.0", - "intl-messageformat": "^10.7.16", + "intl-messageformat": "^11.0.0", "js-yaml": "^4.1.0", "lande": "^1.0.10", "lodash": "^4.17.21", @@ -98,7 +98,7 @@ "react-helmet": "^6.1.0", "react-immutable-proptypes": "^2.2.0", "react-immutable-pure-component": "^2.2.2", - "react-intl": "^7.1.10", + "react-intl": "^10.0.0", "react-overlays": "^5.2.1", "react-redux": "^9.0.4", "react-redux-loading-bar": "^5.0.8", @@ -167,10 +167,10 @@ "@vitest/browser-playwright": "^4.1.0", "@vitest/coverage-v8": "^4.1.0", "@vitest/ui": "^4.1.0", - "chromatic": "^13.3.3", + "chromatic": "^16.0.0", "eslint": "^9.39.2", "eslint-import-resolver-typescript": "^4.2.5", - "eslint-plugin-formatjs": "^5.3.1", + "eslint-plugin-formatjs": "^6.0.0", "eslint-plugin-import": "~2.32.0", "eslint-plugin-jsdoc": "^62.0.0", "eslint-plugin-jsx-a11y": "~6.10.2", @@ -190,7 +190,7 @@ "storybook": "^10.3.0", "stylelint": "^17.0.0", "stylelint-config-standard-scss": "^17.0.0", - "typescript": "~5.9.0", + "typescript": "~6.0.0", "typescript-eslint": "^8.55.0", "typescript-plugin-css-modules": "^5.2.0", "vitest": "^4.1.0" diff --git a/spec/fabricators/email_subscription_fabricator.rb b/spec/fabricators/email_subscription_fabricator.rb new file mode 100644 index 0000000000..8d61945564 --- /dev/null +++ b/spec/fabricators/email_subscription_fabricator.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +Fabricator(:email_subscription) do + account + email { sequence(:email) { |i| "#{i}#{Faker::Internet.email}" } } + locale 'en' +end diff --git a/spec/fabricators/keypair_fabricator.rb b/spec/fabricators/keypair_fabricator.rb new file mode 100644 index 0000000000..5eae3872d2 --- /dev/null +++ b/spec/fabricators/keypair_fabricator.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +keypair = OpenSSL::PKey::RSA.new(2048) +public_key = keypair.public_key.to_pem +private_key = keypair.to_pem + +Fabricator(:keypair) do + account + type :rsa + public_key public_key + expires_at nil + revoked false + + after_build do |keypair| + keypair.uri ||= ActivityPub::TagManager.instance.key_uri_for(keypair.account) + keypair.private_key ||= private_key if keypair.account.local? + end +end diff --git a/spec/lib/activitypub/activity/feature_request_spec.rb b/spec/lib/activitypub/activity/feature_request_spec.rb index ac3e42b272..cd199a806a 100644 --- a/spec/lib/activitypub/activity/feature_request_spec.rb +++ b/spec/lib/activitypub/activity/feature_request_spec.rb @@ -32,6 +32,7 @@ RSpec.describe ActivityPub::Activity::FeatureRequest do .with(satisfying do |body| response_json = JSON.parse(body) response_json['type'] == 'Accept' && + response_json['object'] == 'https://example.com/feature_requests/1' && response_json['to'] == sender.uri end, recipient.id, sender.inbox_url) end @@ -46,9 +47,33 @@ RSpec.describe ActivityPub::Activity::FeatureRequest do .with(satisfying do |body| response_json = JSON.parse(body) response_json['type'] == 'Reject' && + response_json['object'] == 'https://example.com/feature_requests/1' && response_json['to'] == sender.uri end, recipient.id, sender.inbox_url) end end + + context 'when the collection is not yet known' do + let(:discoverable) { true } + let(:collection) { instance_double(Collection, uri: 'https://example.com/collections/1') } + let(:stubbed_service) do + service = instance_double(ActivityPub::FetchRemoteFeaturedCollectionService) + allow(service).to receive(:call) do + Fabricate(:remote_collection, account: sender, uri: collection.uri) + end + service + end + + before do + allow(ActivityPub::FetchRemoteFeaturedCollectionService).to receive(:new).and_return(stubbed_service) + end + + it 'fetches the collection before handling the request' do + subject.perform + + expect(ActivityPub::DeliveryWorker).to have_enqueued_sidekiq_job + expect(stubbed_service).to have_received(:call) + end + end end end diff --git a/spec/lib/activitypub/linked_data_signature_spec.rb b/spec/lib/activitypub/linked_data_signature_spec.rb index 8128fdd070..7aaff9680e 100644 --- a/spec/lib/activitypub/linked_data_signature_spec.rb +++ b/spec/lib/activitypub/linked_data_signature_spec.rb @@ -7,6 +7,7 @@ RSpec.describe ActivityPub::LinkedDataSignature do subject { described_class.new(json) } + let(:keyid) { 'http://example.com/alice#rsa-key' } let!(:sender) { Fabricate(:account, uri: 'http://example.com/alice', domain: 'example.com') } let(:raw_json) do @@ -25,7 +26,7 @@ RSpec.describe ActivityPub::LinkedDataSignature do context 'when signature matches' do let(:raw_signature) do { - 'creator' => 'http://example.com/alice', + 'creator' => keyid, 'created' => '2017-09-23T20:21:34Z', } end @@ -40,7 +41,7 @@ RSpec.describe ActivityPub::LinkedDataSignature do context 'when local account record is missing a public key' do let(:raw_signature) do { - 'creator' => 'http://example.com/alice', + 'creator' => keyid, 'created' => '2017-09-23T20:21:34Z', } end @@ -59,15 +60,14 @@ RSpec.describe ActivityPub::LinkedDataSignature do allow(ActivityPub::FetchRemoteKeyService).to receive(:new).and_return(service_stub) - allow(service_stub).to receive(:call).with('http://example.com/alice') do - sender.update!(public_key: old_key) - sender + allow(service_stub).to receive(:call).with(keyid) do + Keypair.new(account: sender, type: :rsa, public_key: old_key, uri: keyid) end end it 'fetches key and returns creator' do expect(subject.verify_actor!).to eq sender - expect(service_stub).to have_received(:call).with('http://example.com/alice').once + expect(service_stub).to have_received(:call).with(keyid).once end end @@ -82,7 +82,7 @@ RSpec.describe ActivityPub::LinkedDataSignature do context 'when signature is tampered' do let(:raw_signature) do { - 'creator' => 'http://example.com/alice', + 'creator' => keyid, 'created' => '2017-09-23T20:21:34Z', } end @@ -100,7 +100,7 @@ RSpec.describe ActivityPub::LinkedDataSignature do let(:raw_signature) do { - 'creator' => 'http://example.com/alice', + 'creator' => keyid, 'created' => '2017-09-23T20:21:34Z', } end @@ -116,7 +116,7 @@ RSpec.describe ActivityPub::LinkedDataSignature do let(:raw_signature) do { - 'creator' => 'http://example.com/alice', + 'creator' => keyid, 'created' => '2017-09-23T20:21:34Z', } end @@ -132,7 +132,7 @@ RSpec.describe ActivityPub::LinkedDataSignature do let(:raw_signature) do { - 'creator' => 'http://example.com/alice', + 'creator' => keyid, 'created' => '2017-09-23T20:21:34Z', } end diff --git a/spec/lib/private_address_check_spec.rb b/spec/lib/private_address_check_spec.rb index ee9f9295d5..20a94983d9 100644 --- a/spec/lib/private_address_check_spec.rb +++ b/spec/lib/private_address_check_spec.rb @@ -4,17 +4,11 @@ require 'rails_helper' RSpec.describe PrivateAddressCheck do describe 'private_address?' do + let(:private_ips) { %w(192.168.1.7 0.0.0.0 127.0.0.1 ::ffff:0.0.0.1) } + it 'returns true for private addresses' do - # rubocop:disable RSpec/ExpectActual - expect( - [ - '192.168.1.7', - '0.0.0.0', - '127.0.0.1', - '::ffff:0.0.0.1', - ] - ).to all satisfy('return true') { |addr| described_class.private_address?(IPAddr.new(addr)) } - # rubocop:enable RSpec/ExpectActual + expect(private_ips) + .to all(satisfy { |addr| described_class.private_address?(IPAddr.new(addr)) }) end end end diff --git a/spec/lib/webfinger_resource_spec.rb b/spec/lib/webfinger_resource_spec.rb index 0b86b41c48..581fa7264b 100644 --- a/spec/lib/webfinger_resource_spec.rb +++ b/spec/lib/webfinger_resource_spec.rb @@ -11,133 +11,127 @@ RSpec.describe WebfingerResource do Rails.configuration.x.web_domain = before_web end - describe '#username' do + describe '#account' do + subject { described_class.new(resource).account } + describe 'with a URL value' do - it 'raises with a route whose controller is not AccountsController' do - resource = 'https://example.com/users/alice/other' + context 'with a route whose controller is not AccountsController' do + let(:resource) { 'https://example.com/users/alice/other' } - expect do - described_class.new(resource).username - end.to raise_error(ActiveRecord::RecordNotFound) + it 'raises an error' do + expect { subject }.to raise_error(ActiveRecord::RecordNotFound) + end end - it 'raises with a route whose action is not show' do - resource = 'https://example.com/users/alice' + context 'with a string that does not start with an URL' do + let(:resource) { 'website for http://example.com/users/alice.other' } - recognized = Rails.application.routes.recognize_path(resource) - allow(recognized).to receive(:[]).with(:controller).and_return('accounts') - allow(recognized).to receive(:[]).with(:username).and_return('alice') - allow(recognized).to receive(:[]).with(:action).and_return('create') - - allow(Rails.application.routes).to receive(:recognize_path).with(resource).and_return(recognized) - - expect do - described_class.new(resource).username - end.to raise_error(ActiveRecord::RecordNotFound) - expect(recognized).to have_received(:[]).exactly(3).times - - expect(Rails.application.routes).to have_received(:recognize_path) - .with(resource) - .at_least(:once) + it 'raises an error' do + expect { subject }.to raise_error(described_class::InvalidRequest) + end end - it 'raises with a string that doesnt start with URL' do - resource = 'website for http://example.com/users/alice/other' + context 'with a valid HTTPS route to an existing user' do + let(:account) { Fabricate(:account) } + let(:resource) { "https://example.com/users/#{account.username}" } - expect do - described_class.new(resource).username - end.to raise_error(described_class::InvalidRequest) + it { is_expected.to eq(account) } end - it 'finds the username in a valid https route' do - resource = 'https://example.com/users/alice' + context 'with a valid HTTPS route to an existing user using the new API scheme' do + let(:account) { Fabricate(:account) } + let(:resource) { "https://example.com/ap/users/#{account.id}" } - result = described_class.new(resource).username - expect(result).to eq 'alice' + it { is_expected.to eq(account) } end - it 'finds the username in a mixed case http route' do - resource = 'HTTp://exAMPLe.com/users/alice' + context 'with a valid HTTPS route to a non-existing user' do + let(:account) { Fabricate(:account) } + let(:resource) { 'https://example.com/users/alice' } - result = described_class.new(resource).username - expect(result).to eq 'alice' + it 'raises an error' do + expect { subject }.to raise_error(ActiveRecord::RecordNotFound) + end end - it 'finds the username in a valid http route' do - resource = 'http://example.com/users/alice' + context 'with a mixed case HTTP but valid route to an existing user' do + let(:account) { Fabricate(:account) } + let(:resource) { "HTTp://example.com/users/#{account.username}" } - result = described_class.new(resource).username - expect(result).to eq 'alice' + it { is_expected.to eq(account) } + end + + context 'with a valid HTTP route to an existing user' do + let(:account) { Fabricate(:account) } + let(:resource) { "http://example.com/users/#{account.username}" } + + it { is_expected.to eq(account) } end end describe 'with a username and hostname value' do - it 'raises on a non-local domain' do - resource = 'user@remote-host.com' + context 'with a non-local domain' do + let(:account) { Fabricate(:account) } + let(:resource) { "#{account.username}@remote-host.com" } - expect do - described_class.new(resource).username - end.to raise_error(ActiveRecord::RecordNotFound) + it 'raises an error' do + expect { subject }.to raise_error(ActiveRecord::RecordNotFound) + end end - it 'finds username for a local domain' do - Rails.configuration.x.local_domain = 'example.com' - resource = 'alice@example.com' + context 'with a valid handle for a local user with local domain' do + let(:account) { Fabricate(:account) } + let(:resource) { "#{account.username}@example.com" } - result = described_class.new(resource).username - expect(result).to eq 'alice' + before { Rails.configuration.x.local_domain = 'example.com' } + + it { is_expected.to eq(account) } end - it 'finds username for a web domain' do - Rails.configuration.x.web_domain = 'example.com' - resource = 'alice@example.com' + context 'with a valid handle for a local user with web domain' do + let(:account) { Fabricate(:account) } + let(:resource) { "#{account.username}@example.com" } - result = described_class.new(resource).username - expect(result).to eq 'alice' + before { Rails.configuration.x.web_domain = 'example.com' } + + it { is_expected.to eq(account) } end end describe 'with an acct value' do - it 'raises on a non-local domain' do - resource = 'acct:user@remote-host.com' + context 'with a non-local domain' do + let(:account) { Fabricate(:account) } + let(:resource) { "acct:#{account.username}@remote-host.com" } - expect do - described_class.new(resource).username - end.to raise_error(ActiveRecord::RecordNotFound) + it 'raises an error' do + expect { subject }.to raise_error(ActiveRecord::RecordNotFound) + end end - it 'raises on a nonsense domain' do - resource = 'acct:user@remote-host@remote-hostess.remote.local@remote' + context 'with a valid handle for a local user with local domain' do + let(:account) { Fabricate(:account) } + let(:resource) { "acct:#{account.username}@example.com" } - expect do - described_class.new(resource).username - end.to raise_error(ActiveRecord::RecordNotFound) + before { Rails.configuration.x.local_domain = 'example.com' } + + it { is_expected.to eq(account) } end - it 'finds the username for a local account if the domain is the local one' do - Rails.configuration.x.local_domain = 'example.com' - resource = 'acct:alice@example.com' + context 'with a valid handle for a local user with web domain' do + let(:account) { Fabricate(:account) } + let(:resource) { "acct:#{account.username}@example.com" } - result = described_class.new(resource).username - expect(result).to eq 'alice' - end + before { Rails.configuration.x.web_domain = 'example.com' } - it 'finds the username for a local account if the domain is the Web one' do - Rails.configuration.x.web_domain = 'example.com' - resource = 'acct:alice@example.com' - - result = described_class.new(resource).username - expect(result).to eq 'alice' + it { is_expected.to eq(account) } end end describe 'with a nonsense resource' do - it 'raises InvalidRequest' do - resource = 'df/:dfkj' + let(:resource) { 'df/:dfkj' } - expect do - described_class.new(resource).username - end.to raise_error(described_class::InvalidRequest) + it 'raises an error' do + expect { subject }.to raise_error(described_class::InvalidRequest) end end end diff --git a/spec/mailers/email_subscription_mailer_spec.rb b/spec/mailers/email_subscription_mailer_spec.rb new file mode 100644 index 0000000000..0d8ec6e66b --- /dev/null +++ b/spec/mailers/email_subscription_mailer_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe EmailSubscriptionMailer do + describe '.confirmation' do + let(:email_subscription) { Fabricate(:email_subscription) } + let(:mail) { described_class.with(subscription: email_subscription).confirmation } + + it 'renders the email' do + expect { mail.deliver } + .to send_email( + to: email_subscription.email, + from: 'notifications@localhost', + subject: I18n.t('email_subscription_mailer.confirmation.subject') + ) + end + end + + describe '.notification' do + let(:email_subscription) { Fabricate(:email_subscription, confirmed_at: Time.now.utc) } + let(:statuses) { Fabricate.times(num_of_statuses, :status) } + let(:mail) { described_class.with(subscription: email_subscription).notification(statuses) } + + context 'with a single status' do + let(:num_of_statuses) { 1 } + + it 'renders the email' do + expect { mail.deliver } + .to send_email( + to: email_subscription.email, + from: 'notifications@localhost', + subject: I18n.t('email_subscription_mailer.notification.subject', count: statuses.size, name: email_subscription.account.display_name, excerpt: statuses.first.text.truncate(17)) + ) + end + end + + context 'with multiple statuses' do + let(:num_of_statuses) { 2 } + + it 'renders the email' do + expect { mail.deliver } + .to send_email( + to: email_subscription.email, + from: 'notifications@localhost', + subject: I18n.t('email_subscription_mailer.notification.subject', count: statuses.size, name: email_subscription.account.display_name, excerpt: ActionController::Base.helpers.truncate(statuses.first.text, length: 17)) + ) + end + end + end +end diff --git a/spec/mailers/previews/email_subscription_mailer_preview.rb b/spec/mailers/previews/email_subscription_mailer_preview.rb new file mode 100644 index 0000000000..436e24c390 --- /dev/null +++ b/spec/mailers/previews/email_subscription_mailer_preview.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +# Preview all emails at http://localhost:3000/rails/mailers/admin_mailer + +class EmailSubscriptionMailerPreview < ActionMailer::Preview + # Preview this email at http://localhost:3000/rails/mailers/email_subscription_mailer/confirmation + def confirmation + EmailSubscriptionMailer.with(subscription: EmailSubscription.last!).confirmation + end + + # Preview this email at http://localhost:3000/rails/mailers/email_subscription_mailer/notification + def notification + EmailSubscriptionMailer.with(subscription: EmailSubscription.last!).notification(Status.where(visibility: :public).without_replies.without_reblogs.limit(5)) + end +end diff --git a/spec/models/email_subscription_spec.rb b/spec/models/email_subscription_spec.rb new file mode 100644 index 0000000000..6e1a0483f8 --- /dev/null +++ b/spec/models/email_subscription_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe EmailSubscription do + describe '#confirmed?' do + it 'returns true when confirmed' do + subject.confirmed_at = Time.now.utc + expect(subject.confirmed?).to be true + end + + it 'returns false when not confirmed' do + subject.confirmed_at = nil + expect(subject.confirmed?).to be false + end + end + + describe '#confirm!' do + subject { Fabricate(:email_subscription) } + + it 'records confirmation time' do + subject.confirm! + expect(subject.confirmed_at).to_not be_nil + end + end + + describe 'Callbacks' do + subject { Fabricate(:email_subscription) } + + it 'generates token and delivers confirmation email', :inline_jobs do + emails = capture_emails { subject } + + expect(subject.confirmed_at).to be_nil + expect(subject.confirmation_token).to_not be_nil + expect(emails.size).to eq(1) + expect(emails.first) + .to have_attributes( + to: contain_exactly(subject.email), + subject: eq(I18n.t('email_subscription_mailer.confirmation.subject', name: subject.account.username, domain: Rails.configuration.x.local_domain)) + ) + end + end +end diff --git a/spec/models/keypair_spec.rb b/spec/models/keypair_spec.rb new file mode 100644 index 0000000000..e7b18d8e68 --- /dev/null +++ b/spec/models/keypair_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Keypair do + describe '#keypair' do + let(:keypair) { Fabricate(:keypair) } + + it 'returns an RSA key pair' do + expect(keypair.keypair).to be_instance_of OpenSSL::PKey::RSA + end + end + + describe 'from_keyid' do + context 'when a key with the given key ID exists' do + let(:account) { Fabricate(:account, domain: 'example.com') } + let(:keypair) { Fabricate(:keypair, account: account) } + + it 'returns the expected Keypair' do + expect(described_class.from_keyid(keypair.uri)) + .to eq keypair + end + end + + context 'when no key with the expected key ID exists but there is an account with the same ID and a key' do + let(:account) { Fabricate(:account, domain: 'example.com') } + let(:keyid) { "#{ActivityPub::TagManager.instance.uri_for(account)}#main-rsa-key" } + + it 'returns the expected Keypair' do + expect(described_class.from_keyid(keyid)) + .to have_attributes( + account: account, + type: 'rsa', + uri: keyid + ) + end + end + + context 'when no key with the expected key ID exists but there is an account with the same ID and no key' do + let(:account) { Fabricate(:account, domain: 'example.com', public_key: '', private_key: nil) } + let(:keyid) { "#{ActivityPub::TagManager.instance.uri_for(account)}#main-rsa-key" } + + it 'returns nil' do + expect(described_class.from_keyid(keyid)) + .to be_nil + end + end + + context 'when no key with the expected key ID exists and no matching account exists' do + let(:keyid) { 'https://example.com/alice#main-key' } + + it 'returns nil' do + expect(described_class.from_keyid(keyid)) + .to be_nil + end + end + end +end diff --git a/spec/models/tag_spec.rb b/spec/models/tag_spec.rb index 61ef531fe1..d0a06013d2 100644 --- a/spec/models/tag_spec.rb +++ b/spec/models/tag_spec.rb @@ -338,6 +338,23 @@ RSpec.describe Tag do expect(results).to eq [tag] end + it 'finds tag records from padded term queries' do + tag = Fabricate(:tag, name: 'MATCH') + _miss_tag = Fabricate(:tag, name: 'miss') + + results = described_class.search_for(' match ') + + expect(results) + .to contain_exactly(tag) + end + + it 'handles nil query' do + results = described_class.search_for(nil) + + expect(results) + .to be_empty + end + it 'finds the exact matching tag as the first item' do similar_tag = Fabricate(:tag, name: 'matchlater', reviewed_at: Time.now.utc) tag = Fabricate(:tag, name: 'match', reviewed_at: Time.now.utc) @@ -364,5 +381,16 @@ RSpec.describe Tag do expect(results).to eq [tag, unlisted_tag] end + + it 'excludes non reviewed tags via option' do + tag = Fabricate(:tag, name: 'match', reviewed_at: 5.days.ago) + unreviewed_tag = Fabricate(:tag, name: 'matchreviewed', reviewed_at: nil) + + results = described_class.search_for('match', 5, 0, exclude_unreviewed: true) + + expect(results) + .to include(tag) + .and not_include(unreviewed_tag) + end end end diff --git a/spec/requests/api/v1/accounts/email_subscriptions_spec.rb b/spec/requests/api/v1/accounts/email_subscriptions_spec.rb new file mode 100644 index 0000000000..ef7a31476a --- /dev/null +++ b/spec/requests/api/v1/accounts/email_subscriptions_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Accounts Email Subscriptions API', feature: :email_subscriptions do + let(:account) { Fabricate(:user).account } + + describe 'POST /api/v1/accounts/:id/email_subscriptions' do + context 'when the account has the permission' do + let(:role) { Fabricate(:user_role, permissions: UserRole::FLAGS[:manage_email_subscriptions]) } + + before do + account.user.update!(role: role) + end + + context 'when user has enabled the setting' do + before do + account.user.settings['email_subscriptions'] = true + account.user.save! + end + + it 'returns http success' do + post "/api/v1/accounts/#{account.id}/email_subscriptions", params: { email: 'test@example.com' } + + expect(response).to have_http_status(200) + expect(response.content_type) + .to start_with('application/json') + end + end + + context 'when user has not enabled the setting' do + it 'returns http not found' do + post "/api/v1/accounts/#{account.id}/email_subscriptions", params: { email: 'test@example.com' } + + expect(response).to have_http_status(404) + end + end + end + + context 'when the account does not have the permission' do + it 'returns http not found' do + post "/api/v1/accounts/#{account.id}/email_subscriptions", params: { email: 'test@example.com' } + + expect(response).to have_http_status(404) + end + end + end +end diff --git a/spec/requests/api/v1/instances/terms_of_services_spec.rb b/spec/requests/api/v1/instances/terms_of_services_spec.rb index 5feb49f48d..3a352a6d0c 100644 --- a/spec/requests/api/v1/instances/terms_of_services_spec.rb +++ b/spec/requests/api/v1/instances/terms_of_services_spec.rb @@ -4,21 +4,76 @@ require 'rails_helper' RSpec.describe 'Terms of Service' do describe 'GET /api/v1/instance/terms_of_service' do - before do - Fabricate(:terms_of_service) + context 'with a current TOS record' do + before do + Fabricate(:terms_of_service) + end + + it 'returns http success' do + get api_v1_instance_terms_of_service_index_path + + expect(response) + .to have_http_status(200) + expect(response.media_type) + .to eq('application/json') + + expect(response.parsed_body) + .to be_present + .and include(:content) + end end - it 'returns http success' do - get api_v1_instance_terms_of_service_path + context 'without a current TOS record' do + it 'returns http success' do + get api_v1_instance_terms_of_service_index_path - expect(response) - .to have_http_status(200) - expect(response.content_type) - .to start_with('application/json') + expect(response) + .to have_http_status(404) + expect(response.media_type) + .to eq('application/json') - expect(response.parsed_body) - .to be_present - .and include(:content) + expect(response.parsed_body) + .to be_present + .and include(error: /not found/i) + end + end + end + + describe 'GET /api/v1/instance/terms_of_service/:date' do + context 'with an effective TOS record' do + before do + travel_to 2.days.ago do + Fabricate(:terms_of_service, effective_date: 2.days.from_now, published_at: Date.current) + end + end + + it 'returns http success' do + get api_v1_instance_terms_of_service_path(date: Date.current.to_s) + + expect(response) + .to have_http_status(200) + expect(response.media_type) + .to eq('application/json') + + expect(response.parsed_body) + .to be_present + .and include(:content) + end + end + + context 'without an effective TOS record' do + it 'returns http not found' do + get api_v1_instance_terms_of_service_path(date: Date.current.to_s) + + expect(response) + .to have_http_status(404) + expect(response.media_type) + .to eq('application/json') + + expect(response.parsed_body) + .to be_present + .and include(error: /not found/i) + end end end end diff --git a/spec/requests/api/v1/profiles_spec.rb b/spec/requests/api/v1/profiles_spec.rb index 7d0d1a3622..0107b9c404 100644 --- a/spec/requests/api/v1/profiles_spec.rb +++ b/spec/requests/api/v1/profiles_spec.rb @@ -44,8 +44,10 @@ RSpec.describe 'Profile API' do 'indexable' => account.indexable, 'display_name' => account.display_name, 'fields' => [], + 'formatted_fields' => [], 'attribution_domains' => [], 'note' => account.note, + 'formatted_note' => account.note, 'show_featured' => account.show_featured, 'show_media' => account.show_media, 'show_media_replies' => account.show_media_replies, diff --git a/spec/requests/email_subscriptions/confirmations_spec.rb b/spec/requests/email_subscriptions/confirmations_spec.rb new file mode 100644 index 0000000000..909aab1b77 --- /dev/null +++ b/spec/requests/email_subscriptions/confirmations_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Email Subscriptions Confirmation' do + describe 'GET /email_subscriptions/confirmation' do + context 'when email subscription is unconfirmed' do + let!(:email_subscription) { Fabricate(:email_subscription, confirmed_at: nil) } + + it 'renders success page and updates subscription as confirmed' do + get email_subscriptions_confirmation_path(confirmation_token: email_subscription.confirmation_token) + + expect(response) + .to have_http_status(200) + expect(email_subscription.reload.confirmed?) + .to be true + end + end + + context 'when email subscription is already confirmed' do + let!(:email_subscription) { Fabricate(:email_subscription, confirmed_at: Time.now.utc) } + + it 'renders success page' do + get email_subscriptions_confirmation_path(confirmation_token: email_subscription.confirmation_token) + + expect(response) + .to have_http_status(200) + expect(email_subscription.reload.confirmed?) + .to be true + end + end + end +end diff --git a/spec/requests/mail_subscriptions_spec.rb b/spec/requests/unsubscriptions_spec.rb similarity index 90% rename from spec/requests/mail_subscriptions_spec.rb rename to spec/requests/unsubscriptions_spec.rb index cc6557cab0..95a1499223 100644 --- a/spec/requests/mail_subscriptions_spec.rb +++ b/spec/requests/unsubscriptions_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -RSpec.describe 'MailSubscriptionsController' do +RSpec.describe 'UnsubscriptionsController' do let(:user) { Fabricate(:user) } let(:token) { user.to_sgid(for: 'unsubscribe').to_s } let(:type) { 'follow' } @@ -39,9 +39,8 @@ RSpec.describe 'MailSubscriptionsController' do expect(response).to have_http_status(200) expect(response.body).to include( - I18n.t('mail_subscriptions.unsubscribe.action') + I18n.t('unsubscriptions.show.action') ) - expect(response.body).to include(user.email) end end @@ -60,9 +59,8 @@ RSpec.describe 'MailSubscriptionsController' do expect(response).to have_http_status(200) expect(response.body).to include( - I18n.t('mail_subscriptions.unsubscribe.complete') + I18n.t('unsubscriptions.create.title') ) - expect(response.body).to include(user.email) end it 'updates notification settings' do diff --git a/spec/serializers/activitypub/featured_collection_serializer_spec.rb b/spec/serializers/activitypub/featured_collection_serializer_spec.rb index b25ec13a52..78b9daf613 100644 --- a/spec/serializers/activitypub/featured_collection_serializer_spec.rb +++ b/spec/serializers/activitypub/featured_collection_serializer_spec.rb @@ -76,6 +76,7 @@ RSpec.describe ActivityPub::FeaturedCollectionSerializer do it 'only includes accepted items' do items = subject['orderedItems'] + expect(subject['totalItems']).to eq 1 expect(items.size).to eq 1 expect(items.first['id']).to eq ActivityPub::TagManager.instance.uri_for(collection_items.last) end diff --git a/spec/services/activitypub/fetch_remote_actor_service_spec.rb b/spec/services/activitypub/fetch_remote_actor_service_spec.rb index 61b1e15c95..a014e234ad 100644 --- a/spec/services/activitypub/fetch_remote_actor_service_spec.rb +++ b/spec/services/activitypub/fetch_remote_actor_service_spec.rb @@ -133,5 +133,97 @@ RSpec.describe ActivityPub::FetchRemoteActorService do expect(subject.call('https://fake.address/@foo', prefetched_body: actor.to_json)).to be_nil end end + + context 'when the actor uses the webfinger propery from FEP-2c59' do + before do + actor[:webfinger] = acct + end + + context 'when URI and WebFinger share the same host' do + let(:acct) { 'alice@example.com' } + let!(:webfinger) { { subject: "acct:#{acct}", links: [{ rel: 'self', href: 'https://example.com/alice', type: 'application/activity+json' }] } } + + before do + stub_request(:get, 'https://example.com/alice').to_return(body: actor.to_json, headers: { 'Content-Type': 'application/activity+json' }) + stub_request(:get, "https://example.com/.well-known/webfinger?resource=acct:#{acct}").to_return(body: webfinger.to_json, headers: { 'Content-Type': 'application/jrd+json' }) + end + + it 'fetches resource and looks up webfinger and sets values' do + account + + expect(a_request(:get, 'https://example.com/alice')).to have_been_made.once + expect(a_request(:get, "https://example.com/.well-known/webfinger?resource=acct:#{acct}")).to have_been_made.once + + expect(account.username).to eq 'alice' + expect(account.domain).to eq 'example.com' + end + + it_behaves_like 'sets profile data' + end + + context 'when WebFinger returns a different URI' do + let(:acct) { 'alice@example.com' } + let!(:webfinger) { { subject: "acct:#{acct}", links: [{ rel: 'self', href: 'https://example.com/bob', type: 'application/activity+json' }] } } + + before do + stub_request(:get, 'https://example.com/alice').to_return(body: actor.to_json, headers: { 'Content-Type': 'application/activity+json' }) + stub_request(:get, "https://example.com/.well-known/webfinger?resource=acct:#{acct}").to_return(body: webfinger.to_json, headers: { 'Content-Type': 'application/jrd+json' }) + end + + it 'fetches resource and looks up webfinger and does not create account' do + expect(account).to be_nil + + expect(a_request(:get, 'https://example.com/alice')).to have_been_made.once + expect(a_request(:get, "https://example.com/.well-known/webfinger?resource=acct:#{acct}")).to have_been_made.once + end + end + + context 'when WebFinger is at another domain' do + let(:acct) { 'alice@iscool.af' } + let!(:webfinger) { { subject: "acct:#{acct}", links: [{ rel: 'self', href: 'https://example.com/alice', type: 'application/activity+json' }] } } + + before do + stub_request(:get, 'https://example.com/alice').to_return(body: actor.to_json, headers: { 'Content-Type': 'application/activity+json' }) + stub_request(:get, "https://iscool.af/.well-known/webfinger?resource=acct:#{acct}").to_return(body: webfinger.to_json, headers: { 'Content-Type': 'application/jrd+json' }) + end + + it 'fetches resource and looks up webfinger and follows redirect and sets values' do + account + + expect(a_request(:get, 'https://example.com/alice')).to have_been_made.once + expect(a_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com')).to_not have_been_made + expect(a_request(:get, 'https://iscool.af/.well-known/webfinger?resource=acct:alice@iscool.af')).to have_been_made.once + + expect(account.username).to eq 'alice' + expect(account.domain).to eq 'iscool.af' + end + + it_behaves_like 'sets profile data' + end + + context 'when WebFinger is at another domain and redirects back' do + let(:acct) { 'alice@iscool.af' } + let!(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice', type: 'application/activity+json' }] } } + + before do + stub_request(:get, 'https://example.com/alice').to_return(body: actor.to_json, headers: { 'Content-Type': 'application/activity+json' }) + stub_request(:get, "https://iscool.af/.well-known/webfinger?resource=acct:#{acct}").to_return(body: webfinger.to_json, headers: { 'Content-Type': 'application/jrd+json' }) + stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com').to_return(body: webfinger.to_json, headers: { 'Content-Type': 'application/jrd+json' }) + end + + it 'fetches resource and looks up webfinger and follows redirect and sets values' do + account + + expect(a_request(:get, 'https://example.com/alice')).to have_been_made.once + expect(a_request(:get, 'https://iscool.af/.well-known/webfinger?resource=acct:alice@iscool.af')).to have_been_made.once + expect(a_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:alice@example.com')).to have_been_made + + expect(account.username).to eq 'alice' + expect(account.domain).to eq 'example.com' + end + + it_behaves_like 'sets profile data' + end + end end end diff --git a/spec/services/activitypub/fetch_remote_key_service_spec.rb b/spec/services/activitypub/fetch_remote_key_service_spec.rb index f5df52d5ba..cd61ebee22 100644 --- a/spec/services/activitypub/fetch_remote_key_service_spec.rb +++ b/spec/services/activitypub/fetch_remote_key_service_spec.rb @@ -55,7 +55,7 @@ RSpec.describe ActivityPub::FetchRemoteKeyService do end describe '#call' do - let(:account) { subject.call(public_key_id) } + let(:keypair) { subject.call(public_key_id) } context 'when the key is a sub-object from the actor' do before do @@ -63,7 +63,7 @@ RSpec.describe ActivityPub::FetchRemoteKeyService do end it 'returns the expected account' do - expect(account.uri).to eq 'https://example.com/alice' + expect(keypair.account.uri).to eq 'https://example.com/alice' end end @@ -75,7 +75,7 @@ RSpec.describe ActivityPub::FetchRemoteKeyService do end it 'returns the expected account' do - expect(account.uri).to eq 'https://example.com/alice' + expect(keypair.account.uri).to eq 'https://example.com/alice' end end @@ -88,7 +88,7 @@ RSpec.describe ActivityPub::FetchRemoteKeyService do end it 'returns the nil' do - expect(account).to be_nil + expect(keypair).to be_nil end end end diff --git a/spec/services/activitypub/process_account_service_spec.rb b/spec/services/activitypub/process_account_service_spec.rb index 949cc45618..99bcbba9fe 100644 --- a/spec/services/activitypub/process_account_service_spec.rb +++ b/spec/services/activitypub/process_account_service_spec.rb @@ -94,6 +94,216 @@ RSpec.describe ActivityPub::ProcessAccountService do end end + context 'with a single keypair' do + let(:public_key) { 'foo' } + + let(:payload) do + { + id: 'https://foo.test/actor', + type: 'Actor', + inbox: 'https://foo.test/inbox', + preferredUsername: 'alice', + publicKey: { + id: 'https://foo.test/actor#key1', + owner: 'https://foo.test/actor', + publicKeyPem: public_key, + }, + }.with_indifferent_access + end + + it 'stores the key' do + account = subject.call('alice', 'example.com', payload) + + expect(account.public_key).to eq '' + expect(account.keypairs).to contain_exactly( + have_attributes( + uri: 'https://foo.test/actor#key1', + type: 'rsa', + public_key: + ) + ) + end + + context 'when the account was known with a legacy key' do + let!(:alice) { Fabricate(:account, uri: 'https://foo.test/actor', domain: 'example.com', username: 'alice') } + + it 'invalidates the legacy key and stores the new key' do + expect { subject.call('alice', 'example.com', payload) } + .to change { alice.reload.public_key }.to('') + .and change { alice.reload.keypairs.to_a }.from([]).to(contain_exactly(have_attributes({ uri: 'https://foo.test/actor#key1', type: 'rsa', public_key: }))) + end + end + + context 'when the account was known with an old key' do + let!(:alice) { Fabricate(:account, uri: 'https://foo.test/actor', domain: 'example.com', username: 'alice', public_key: '') } + + before do + Fabricate(:keypair, account: alice, uri: 'https://foo.test/actor#old-key', type: :rsa) + end + + it 'invalidates the legacy key and stores the new key' do + expect { subject.call('alice', 'example.com', payload) } + .to change { alice.reload.keypairs.to_a }.from(contain_exactly(have_attributes({ uri: 'https://foo.test/actor#old-key' }))).to(contain_exactly(have_attributes({ uri: 'https://foo.test/actor#key1', type: 'rsa', public_key: }))) + + expect(alice.reload.public_key) + .to eq '' + end + end + end + + context 'when the key is in a separate document' do + let(:key_id) { 'https://foo.test/actor/main-key' } + let(:public_key) { 'foo' } + + let(:payload) do + { + id: 'https://foo.test/actor', + type: 'Actor', + inbox: 'https://foo.test/inbox', + preferredUsername: 'alice', + publicKey: key_id, + }.deep_stringify_keys + end + + let(:key_document) do + { + id: key_id, + owner: 'https://foo.test/actor', + publicKeyPem: public_key, + }.deep_stringify_keys + end + + before do + stub_request(:get, key_id).to_return(status: 200, body: key_document.to_json, headers: { 'Content-Type': 'application/activity+json' }) + end + + it 'stores the key' do + account = subject.call('alice', 'example.com', payload) + + expect(account.public_key).to eq '' + expect(account.keypairs).to contain_exactly( + have_attributes( + uri: key_id, + public_key:, + type: 'rsa' + ) + ) + end + + context 'when the key document is a bogus copy of the author (GoToSocial quirk)' do + let(:payload) do + { + id: 'https://foo.test/actor', + type: 'Actor', + inbox: 'https://foo.test/inbox', + preferredUsername: 'alice', + publicKey: { + id: key_id, + owner: 'https://foo.test/actor', + publicKeyPem: public_key, + }, + }.deep_stringify_keys + end + + let(:key_document) { payload } + + it 'stores the key' do + account = subject.call('alice', 'example.com', payload) + + expect(account.public_key).to eq '' + expect(account.keypairs).to contain_exactly( + have_attributes( + uri: key_id, + public_key:, + type: 'rsa' + ) + ) + end + end + + context 'when the account was known with a legacy key' do + let!(:alice) { Fabricate(:account, uri: 'https://foo.test/actor', domain: 'example.com', username: 'alice') } + + it 'invalidates the legacy key and stores the new key' do + expect { subject.call('alice', 'example.com', payload) } + .to change { alice.reload.public_key }.to('') + .and change { alice.reload.keypairs.to_a }.from([]).to(contain_exactly(have_attributes({ uri: key_id, type: 'rsa', public_key: }))) + end + end + + context 'when the account was known with an old key' do + let!(:alice) { Fabricate(:account, uri: 'https://foo.test/actor', domain: 'example.com', username: 'alice', public_key: '') } + + before do + Fabricate(:keypair, account: alice, uri: 'https://foo.test/actor#old-key', type: :rsa) + end + + it 'invalidates the legacy key and stores the new key' do + expect { subject.call('alice', 'example.com', payload) } + .to change { alice.reload.keypairs.to_a }.from(contain_exactly(have_attributes({ uri: 'https://foo.test/actor#old-key' }))).to(contain_exactly(have_attributes({ uri: key_id, type: 'rsa', public_key: }))) + + expect(alice.reload.public_key) + .to eq '' + end + end + end + + context 'with multiple keypairs' do + let(:payload) do + { + id: 'https://foo.test/actor', + type: 'Actor', + inbox: 'https://foo.test/inbox', + preferredUsername: 'alice', + publicKey: [ + { + id: 'https://foo.test/actor#key1', + owner: 'https://foo.test/actor', + publicKeyPem: 'foo', + }, + { + id: 'https://foo.test/actor#key2', + owner: 'https://foo.test/actor', + publicKeyPem: 'bar', + }, + ], + }.with_indifferent_access + end + + it 'stores the keys' do + account = subject.call('alice', 'example.com', payload) + + expect(account.public_key).to eq '' + expect(account.keypairs).to contain_exactly( + have_attributes( + uri: 'https://foo.test/actor#key1', + type: 'rsa', + public_key: 'foo' + ), + have_attributes( + uri: 'https://foo.test/actor#key2', + type: 'rsa', + public_key: 'bar' + ) + ) + end + + context 'when the account was known with a legacy key' do + let!(:alice) { Fabricate(:account, uri: 'https://foo.test/actor', domain: 'example.com', username: 'alice') } + + it 'invalidates the legacy key and stores the new keys' do + expect { subject.call('alice', 'example.com', payload) } + .to change { alice.reload.public_key }.to('') + .and change { alice.keypairs.to_a }.from([]).to( + contain_exactly( + have_attributes({ uri: 'https://foo.test/actor#key1', type: 'rsa', public_key: 'foo' }), + have_attributes({ uri: 'https://foo.test/actor#key2', type: 'rsa', public_key: 'bar' }) + ) + ) + end + end + end + context 'with attribution domains' do let(:payload) do { diff --git a/spec/services/activitypub/process_featured_collection_service_spec.rb b/spec/services/activitypub/process_featured_collection_service_spec.rb index c68120bf0d..343d3ac3b2 100644 --- a/spec/services/activitypub/process_featured_collection_service_spec.rb +++ b/spec/services/activitypub/process_featured_collection_service_spec.rb @@ -52,7 +52,8 @@ RSpec.describe ActivityPub::ProcessFeaturedCollectionService do expect(new_collection.discoverable).to be true expect(new_collection.tag.formatted_name).to eq '#people' - expect(ActivityPub::ProcessFeaturedItemWorker).to have_enqueued_sidekiq_job.exactly(2).times + expect(ActivityPub::ProcessFeaturedItemWorker).to have_enqueued_sidekiq_job.with(new_collection.id, 'https://example.com/featured_items/1', 1, nil) + expect(ActivityPub::ProcessFeaturedItemWorker).to have_enqueued_sidekiq_job.with(new_collection.id, 'https://example.com/featured_items/2', 2, nil) end end diff --git a/spec/services/bootstrap_timeline_service_spec.rb b/spec/services/bootstrap_timeline_service_spec.rb index c99813bceb..4c2d129e9e 100644 --- a/spec/services/bootstrap_timeline_service_spec.rb +++ b/spec/services/bootstrap_timeline_service_spec.rb @@ -3,35 +3,31 @@ require 'rails_helper' RSpec.describe BootstrapTimelineService do - subject { described_class.new } + subject { described_class.new.call(new_user.account) } + + let(:invite) { nil } + let(:new_user) { Fabricate(:user, invite_code: invite&.code) } context 'when the new user has registered from an invite' do - let(:service) { instance_double(FollowService) } let(:autofollow) { false } let(:inviter) { Fabricate(:user, confirmed_at: 2.days.ago) } let(:invite) { Fabricate(:invite, user: inviter, max_uses: nil, expires_at: 1.hour.from_now, autofollow: autofollow) } - let(:new_user) { Fabricate(:user, invite_code: invite.code) } - - before do - allow(FollowService).to receive(:new).and_return(service) - allow(service).to receive(:call) - end context 'when the invite has auto-follow enabled' do let(:autofollow) { true } - it 'calls FollowService to follow the inviter' do - subject.call(new_user.account) - expect(service).to have_received(:call).with(new_user.account, inviter.account) + it 'follows the inviter' do + subject + expect(new_user.account.following?(inviter.account)).to be true end end context 'when the invite does not have auto-follow enable' do let(:autofollow) { false } - it 'calls FollowService to follow the inviter' do - subject.call(new_user.account) - expect(service).to_not have_received(:call) + it 'does not follow the inviter' do + subject + expect(new_user.account.following?(inviter.account)).to be false end end end diff --git a/spec/services/revoke_collection_item_service_spec.rb b/spec/services/revoke_collection_item_service_spec.rb index 8ea753dcc5..b4cba82056 100644 --- a/spec/services/revoke_collection_item_service_spec.rb +++ b/spec/services/revoke_collection_item_service_spec.rb @@ -13,12 +13,14 @@ RSpec.describe RevokeCollectionItemService do end context 'when the collection is remote', feature: :collections_federation do - let(:collection) { Fabricate(:remote_collection) } + let(:account) { Fabricate(:remote_account, inbox_url: 'https://example.com/actor/1/inbox') } + let(:collection) { Fabricate(:remote_collection, account:) } let(:collection_item) { Fabricate(:collection_item, collection:, uri: 'https://example.com') } it 'federates a `Delete` activity' do subject.call(collection_item) + expect(ActivityPub::DeliveryWorker).to have_enqueued_sidekiq_job.with(instance_of(String), collection_item.account_id, 'https://example.com/actor/1/inbox') expect(ActivityPub::AccountRawDistributionWorker).to have_enqueued_sidekiq_job end end diff --git a/streaming/package.json b/streaming/package.json index 3989917b08..04ab576f89 100644 --- a/streaming/package.json +++ b/streaming/package.json @@ -38,7 +38,7 @@ "@types/ws": "^8.5.9", "globals": "^17.3.0", "pino-pretty": "^13.0.0", - "typescript": "~5.9.0", + "typescript": "~6.0.0", "typescript-eslint": "^8.55.0" }, "optionalDependencies": { diff --git a/tsconfig.json b/tsconfig.json index 5663aeada4..bc9198248b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,6 @@ { "compilerOptions": { "jsx": "react-jsx", - "target": "esnext", - "module": "ES2022", - "moduleResolution": "bundler", "allowJs": true, "resolveJsonModule": true, "noEmit": true, @@ -14,18 +11,17 @@ "esModuleInterop": true, "skipLibCheck": true, "types": ["vite/client", "vitest/globals"], - "baseUrl": "./", "incremental": true, "tsBuildInfoFile": "tmp/cache/tsconfig.tsbuildinfo", "paths": { - "@/*": ["app/javascript/*"], - "mastodon": ["app/javascript/mastodon"], - "mastodon/*": ["app/javascript/mastodon/*"], - "locales": ["app/javascript/locales"], - "flavours/glitch": ["app/javascript/flavours/glitch"], - "flavours/glitch/*": ["app/javascript/flavours/glitch/*"], - "images/*": ["app/javascript/images/*"], - "styles/*": ["app/javascript/styles/*"] + "@/*": ["./app/javascript/*"], + "mastodon": ["./app/javascript/mastodon"], + "mastodon/*": ["./app/javascript/mastodon/*"], + "locales": ["./app/javascript/locales"], + "flavours/glitch": ["./app/javascript/flavours/glitch"], + "flavours/glitch/*": ["./app/javascript/flavours/glitch/*"], + "images/*": ["./app/javascript/images/*"], + "styles/*": ["./app/javascript/styles/*"] }, "plugins": [{ "name": "typescript-plugin-css-modules" }] }, diff --git a/vite.config.mts b/vite.config.mts index aae1ce89b8..cca1d23520 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -1,6 +1,7 @@ import { readdir } from 'node:fs/promises'; import path from 'node:path'; +import formatjs from '@formatjs/unplugin/vite'; import { optimizeLodashImports } from '@optimize-lodash/rollup-plugin'; import babel from '@rolldown/plugin-babel'; import legacy from '@vitejs/plugin-legacy'; @@ -171,8 +172,9 @@ export const config: UserConfigFnPromise = async ({ mode, command }) => { plugins: [ react(), babel({ - plugins: ['formatjs', 'transform-react-remove-prop-types'], + plugins: ['transform-react-remove-prop-types'], }), + formatjs(), MastodonThemes(), MastodonAssetsManifest(), MastodonServiceWorkerLocales(), diff --git a/yarn.lock b/yarn.lock index 1638b3fa34..815c288e9d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -90,7 +90,7 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.21.3, @babel/core@npm:^7.24.4, @babel/core@npm:^7.26.10, @babel/core@npm:^7.28.0, @babel/core@npm:^7.29.0": +"@babel/core@npm:^7.21.3, @babel/core@npm:^7.24.4, @babel/core@npm:^7.28.0, @babel/core@npm:^7.29.0": version: 7.29.0 resolution: "@babel/core@npm:7.29.0" dependencies: @@ -242,7 +242,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.26.5, @babel/helper-plugin-utils@npm:^7.27.1, @babel/helper-plugin-utils@npm:^7.28.6": +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.27.1, @babel/helper-plugin-utils@npm:^7.28.6": version: 7.28.6 resolution: "@babel/helper-plugin-utils@npm:7.28.6" checksum: 10c0/3f5f8acc152fdbb69a84b8624145ff4f9b9f6e776cb989f9f968f8606eb7185c5c3cfcf3ba08534e37e1e0e1c118ac67080610333f56baa4f7376c99b5f1143d @@ -428,17 +428,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-jsx@npm:^7.25.9": - version: 7.27.1 - resolution: "@babel/plugin-syntax-jsx@npm:7.27.1" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.27.1" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10c0/bc5afe6a458d5f0492c02a54ad98c5756a0c13bd6d20609aae65acd560a9e141b0876da5f358dce34ea136f271c1016df58b461184d7ae9c4321e0f98588bc84 - languageName: node - linkType: hard - "@babel/plugin-syntax-unicode-sets-regex@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-syntax-unicode-sets-regex@npm:7.18.6" @@ -1187,7 +1176,7 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.26.10, @babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.5, @babel/traverse@npm:^7.28.6, @babel/traverse@npm:^7.29.0": +"@babel/traverse@npm:^7.27.1, @babel/traverse@npm:^7.28.0, @babel/traverse@npm:^7.28.5, @babel/traverse@npm:^7.28.6, @babel/traverse@npm:^7.29.0": version: 7.29.0 resolution: "@babel/traverse@npm:7.29.0" dependencies: @@ -1202,7 +1191,7 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.26.10, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.5, @babel/types@npm:^7.28.6, @babel/types@npm:^7.29.0, @babel/types@npm:^7.4.4": +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.27.1, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.28.5, @babel/types@npm:^7.28.6, @babel/types@npm:^7.29.0, @babel/types@npm:^7.4.4": version: 7.29.0 resolution: "@babel/types@npm:7.29.0" dependencies: @@ -2439,6 +2428,13 @@ __metadata: languageName: node linkType: hard +"@formatjs/bigdecimal@npm:0.2.0": + version: 0.2.0 + resolution: "@formatjs/bigdecimal@npm:0.2.0" + checksum: 10c0/dec607e3d9d4b8c5d0474862e867726cbf322a24d543d5b2cbc3cab6fea187ac787a8e1a0e3df5ceef85a1ab9d58112a08bb7af40b1b3a3b00670431b0603510 + languageName: node + linkType: hard + "@formatjs/cli@npm:^6.1.1": version: 6.7.4 resolution: "@formatjs/cli@npm:6.7.4" @@ -2471,103 +2467,124 @@ __metadata: languageName: node linkType: hard -"@formatjs/ecma402-abstract@npm:2.3.6": - version: 2.3.6 - resolution: "@formatjs/ecma402-abstract@npm:2.3.6" +"@formatjs/ecma402-abstract@npm:3.2.0": + version: 3.2.0 + resolution: "@formatjs/ecma402-abstract@npm:3.2.0" dependencies: - "@formatjs/fast-memoize": "npm:2.2.7" - "@formatjs/intl-localematcher": "npm:0.6.2" - decimal.js: "npm:^10.4.3" - tslib: "npm:^2.8.0" - checksum: 10c0/63be2a73d3168bf45ab5d50db58376e852db5652d89511ae6e44f1fa03ad96ebbfe9b06a1dfaa743db06e40eb7f33bd77530b9388289855cca79a0e3fc29eacf + "@formatjs/bigdecimal": "npm:0.2.0" + "@formatjs/fast-memoize": "npm:3.1.1" + "@formatjs/intl-localematcher": "npm:0.8.2" + checksum: 10c0/b3c8ac881c3d7533fb4127ca3d771d2a32cb89e6efbbcc72d80b1dcc6a798494ace9ca5ee822b25eb08ebdc7ee2885a9e33496a436b40271ffc915ece605a3ce languageName: node linkType: hard -"@formatjs/fast-memoize@npm:2.2.7": - version: 2.2.7 - resolution: "@formatjs/fast-memoize@npm:2.2.7" - dependencies: - tslib: "npm:^2.8.0" - checksum: 10c0/f5eabb0e4ab7162297df8252b4cfde194b23248120d9df267592eae2be2d2f7c4f670b5a70523d91b4ecdc35d40e65823bb8eeba8dd79fbf8601a972bf3b8866 +"@formatjs/fast-memoize@npm:3.1.1": + version: 3.1.1 + resolution: "@formatjs/fast-memoize@npm:3.1.1" + checksum: 10c0/79b24dc1389a49b2b2fb9e90a2ba922a4057d4b74e7bc33a3811f0dc94a5a868d28e8e37917b68c2f831070d11dfd0889de686f269bf5214085a44efc1c25a8c languageName: node linkType: hard -"@formatjs/icu-messageformat-parser@npm:2.11.4": - version: 2.11.4 - resolution: "@formatjs/icu-messageformat-parser@npm:2.11.4" +"@formatjs/icu-messageformat-parser@npm:3.5.3": + version: 3.5.3 + resolution: "@formatjs/icu-messageformat-parser@npm:3.5.3" dependencies: - "@formatjs/ecma402-abstract": "npm:2.3.6" - "@formatjs/icu-skeleton-parser": "npm:1.8.16" - tslib: "npm:^2.8.0" - checksum: 10c0/3ea9e9dae18282881d19a5f88107b6013f514ec8675684ed2c04bee2a174032377858937243e3bd9c9263a470988a3773a53bf8d208a34a78e7843ce66f87f3b + "@formatjs/ecma402-abstract": "npm:3.2.0" + "@formatjs/icu-skeleton-parser": "npm:2.1.3" + checksum: 10c0/9a9632348df058e0da339234381b11f71b5ace1c93eaf1950b3eb45f4e146a73f8923af82818543e90c1135523b254d2c04fb47cab3624eb1f601d2a4edd35c6 languageName: node linkType: hard -"@formatjs/icu-skeleton-parser@npm:1.8.16": - version: 1.8.16 - resolution: "@formatjs/icu-skeleton-parser@npm:1.8.16" +"@formatjs/icu-skeleton-parser@npm:2.1.3": + version: 2.1.3 + resolution: "@formatjs/icu-skeleton-parser@npm:2.1.3" dependencies: - "@formatjs/ecma402-abstract": "npm:2.3.6" - tslib: "npm:^2.8.0" - checksum: 10c0/6fa1586dc11c925cd8d17e927cc635d238c969a6b7e97282a924376f78622fc25336c407589d19796fb6f8124a0e7765f99ecdb1aac014edcfbe852e7c3d87f3 + "@formatjs/ecma402-abstract": "npm:3.2.0" + checksum: 10c0/6a8ed06c722bce1d73d54b2d72462bfe46b752f43d09e3d8c14649ef775b06f3c7f8d36274e67e6cfb95800bf43230a2595a7e1790922ebb683711201fcbccc8 languageName: node linkType: hard -"@formatjs/intl-localematcher@npm:0.6.2": - version: 0.6.2 - resolution: "@formatjs/intl-localematcher@npm:0.6.2" +"@formatjs/intl-localematcher@npm:0.8.2": + version: 0.8.2 + resolution: "@formatjs/intl-localematcher@npm:0.8.2" dependencies: - tslib: "npm:^2.8.0" - checksum: 10c0/22a17a4c67160b6c9f52667914acfb7b79cd6d80630d4ac6d4599ce447cb89d2a64f7d58fa35c3145ddb37fef893f0a45b9a55e663a4eb1f2ae8b10a89fac235 + "@formatjs/fast-memoize": "npm:3.1.1" + checksum: 10c0/3bf838a018184837b167964849dafdcdeac95531a24f4df7d868638d4ad716854a250e9bccac9ab4568264c0db7470e70b99363da1db308fdc882b87f3eca651 languageName: node linkType: hard -"@formatjs/intl-pluralrules@npm:^5.4.4": - version: 5.4.6 - resolution: "@formatjs/intl-pluralrules@npm:5.4.6" +"@formatjs/intl-pluralrules@npm:^6.0.0": + version: 6.3.1 + resolution: "@formatjs/intl-pluralrules@npm:6.3.1" dependencies: - "@formatjs/ecma402-abstract": "npm:2.3.6" - "@formatjs/intl-localematcher": "npm:0.6.2" - decimal.js: "npm:^10.4.3" - tslib: "npm:^2.8.0" - checksum: 10c0/95dd6fb3e9bd84ce44cc194f6f815d690703bd60b75bf2ae895535d2d9a1a675765879de9b54f854882fc1335cbfac6a535873d5b2d75cc5ca93c6ca172aa272 + "@formatjs/bigdecimal": "npm:0.2.0" + "@formatjs/ecma402-abstract": "npm:3.2.0" + "@formatjs/intl-localematcher": "npm:0.8.2" + checksum: 10c0/8c0847d21d06276557316a01998528b79310b1c5411b0932b51dc88640a8ed6181c2221694d1d206a217f5a2be29227edfa52d1eca0873bc7c8c04b9a7dc2cc7 languageName: node linkType: hard -"@formatjs/intl@npm:3.1.8": - version: 3.1.8 - resolution: "@formatjs/intl@npm:3.1.8" +"@formatjs/intl@npm:4.1.4": + version: 4.1.4 + resolution: "@formatjs/intl@npm:4.1.4" dependencies: - "@formatjs/ecma402-abstract": "npm:2.3.6" - "@formatjs/fast-memoize": "npm:2.2.7" - "@formatjs/icu-messageformat-parser": "npm:2.11.4" - intl-messageformat: "npm:10.7.18" - tslib: "npm:^2.8.0" + "@formatjs/ecma402-abstract": "npm:3.2.0" + "@formatjs/fast-memoize": "npm:3.1.1" + "@formatjs/icu-messageformat-parser": "npm:3.5.3" + intl-messageformat: "npm:11.2.0" peerDependencies: typescript: ^5.6.0 peerDependenciesMeta: typescript: optional: true - checksum: 10c0/b291e867bcde491737f70254ec30898e120f36784b5ee2911dcc271fbd744e90382f03232ac7f5a55d46071f4ffccfc84b63445734117b75ca1ced659f6b7827 + checksum: 10c0/2591b86cdec44b91761757edd3433e751b28ed7ed4a6e1a7f240356db2c32a5732565374c9d9b645f83d3558bc9a19231af7ef0ea555524abe4001b7c43f1754 languageName: node linkType: hard -"@formatjs/ts-transformer@npm:3.14.2": - version: 3.14.2 - resolution: "@formatjs/ts-transformer@npm:3.14.2" +"@formatjs/ts-transformer@npm:4.4.2": + version: 4.4.2 + resolution: "@formatjs/ts-transformer@npm:4.4.2" dependencies: - "@formatjs/icu-messageformat-parser": "npm:2.11.4" - "@types/node": "npm:^22.0.0" - chalk: "npm:^4.1.2" + "@formatjs/icu-messageformat-parser": "npm:3.5.3" + "@types/node": "npm:22 || 24" json-stable-stringify: "npm:^1.3.0" - tslib: "npm:^2.8.0" typescript: "npm:^5.6.0" peerDependencies: ts-jest: ^29 peerDependenciesMeta: ts-jest: optional: true - checksum: 10c0/990cf49cdc318e37825ec26b1b24d7368e89c5d03184867a4accd8b35d6d6d99a20a8abe6366c9870e56da9e04f4672990ca428686306c9ad8204b401c7d19f8 + checksum: 10c0/3385706cb4c72c4a7fed49d659b83dc98db22591c8904cc807b730e1fcc824584aa3edee7fdb9085720a3506c6daa271b34778ee7bef16eeacc39a625026c9e9 + languageName: node + linkType: hard + +"@formatjs/unplugin@npm:^1.1.5": + version: 1.1.5 + resolution: "@formatjs/unplugin@npm:1.1.5" + dependencies: + "@formatjs/icu-messageformat-parser": "npm:3.5.3" + "@formatjs/ts-transformer": "npm:4.4.2" + magic-string: "npm:^0.30.0" + oxc-parser: "npm:^0.120.0" + unplugin: "npm:^3.0.0" + peerDependencies: + "@rspack/core": ">=1" + esbuild: ">=0.17" + rollup: ">=3" + vite: ">=5" + webpack: ^5.104.1 + peerDependenciesMeta: + "@rspack/core": + optional: true + esbuild: + optional: true + rollup: + optional: true + vite: + optional: true + webpack: + optional: true + checksum: 10c0/39ca0c669906a699e4a7bfc650cfdeba5d0c5c3a6cd2e5ebe5ed04e432b6d0091aa47d85d69ad3a67f65688f7421611f2d1ae33058e8a611c0075620abd2bd93 languageName: node linkType: hard @@ -2810,7 +2827,8 @@ __metadata: "@dnd-kit/utilities": "npm:^3.2.2" "@eslint/js": "npm:^9.39.2" "@formatjs/cli": "npm:^6.1.1" - "@formatjs/intl-pluralrules": "npm:^5.4.4" + "@formatjs/intl-pluralrules": "npm:^6.0.0" + "@formatjs/unplugin": "npm:^1.1.5" "@gamestdio/websocket": "npm:^0.3.2" "@github/webauthn-json": "npm:^2.1.1" "@optimize-lodash/rollup-plugin": "npm:^6.0.0" @@ -2856,10 +2874,9 @@ __metadata: async-mutex: "npm:^0.5.0" atrament: "npm:0.2.4" axios: "npm:^1.4.0" - babel-plugin-formatjs: "npm:^10.5.37" babel-plugin-transform-react-remove-prop-types: "npm:^0.4.24" blurhash: "npm:^2.0.5" - chromatic: "npm:^13.3.3" + chromatic: "npm:^16.0.0" classnames: "npm:^2.3.2" cocoon-js-vanilla: "npm:^1.5.1" color-blend: "npm:^4.0.0" @@ -2875,7 +2892,7 @@ __metadata: escape-html: "npm:^1.0.3" eslint: "npm:^9.39.2" eslint-import-resolver-typescript: "npm:^4.2.5" - eslint-plugin-formatjs: "npm:^5.3.1" + eslint-plugin-formatjs: "npm:^6.0.0" eslint-plugin-import: "npm:~2.32.0" eslint-plugin-jsdoc: "npm:^62.0.0" eslint-plugin-jsx-a11y: "npm:~6.10.2" @@ -2894,7 +2911,7 @@ __metadata: husky: "npm:^9.0.11" idb: "npm:^8.0.3" immutable: "npm:^4.3.0" - intl-messageformat: "npm:^10.7.16" + intl-messageformat: "npm:^11.0.0" js-yaml: "npm:^4.1.0" lande: "npm:^1.0.10" lint-staged: "npm:^16.2.6" @@ -2914,7 +2931,7 @@ __metadata: react-helmet: "npm:^6.1.0" react-immutable-proptypes: "npm:^2.2.0" react-immutable-pure-component: "npm:^2.2.2" - react-intl: "npm:^7.1.10" + react-intl: "npm:^10.0.0" react-overlays: "npm:^5.2.1" react-redux: "npm:^9.0.4" react-redux-loading-bar: "npm:^5.0.8" @@ -2941,7 +2958,7 @@ __metadata: tesseract.js: "npm:^7.0.0" tiny-queue: "npm:^0.2.1" twitter-text: "npm:3.1.0" - typescript: "npm:~5.9.0" + typescript: "npm:~6.0.0" typescript-eslint: "npm:^8.55.0" typescript-plugin-css-modules: "npm:^5.2.0" use-debounce: "npm:^10.0.0" @@ -2987,7 +3004,7 @@ __metadata: pino-http: "npm:^11.0.0" pino-pretty: "npm:^13.0.0" prom-client: "npm:^15.0.0" - typescript: "npm:~5.9.0" + typescript: "npm:~6.0.0" typescript-eslint: "npm:^8.55.0" utf-8-validate: "npm:^6.0.3" uuid: "npm:^13.0.0" @@ -3150,6 +3167,148 @@ __metadata: languageName: node linkType: hard +"@oxc-parser/binding-android-arm-eabi@npm:0.120.0": + version: 0.120.0 + resolution: "@oxc-parser/binding-android-arm-eabi@npm:0.120.0" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + +"@oxc-parser/binding-android-arm64@npm:0.120.0": + version: 0.120.0 + resolution: "@oxc-parser/binding-android-arm64@npm:0.120.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@oxc-parser/binding-darwin-arm64@npm:0.120.0": + version: 0.120.0 + resolution: "@oxc-parser/binding-darwin-arm64@npm:0.120.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@oxc-parser/binding-darwin-x64@npm:0.120.0": + version: 0.120.0 + resolution: "@oxc-parser/binding-darwin-x64@npm:0.120.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@oxc-parser/binding-freebsd-x64@npm:0.120.0": + version: 0.120.0 + resolution: "@oxc-parser/binding-freebsd-x64@npm:0.120.0" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@oxc-parser/binding-linux-arm-gnueabihf@npm:0.120.0": + version: 0.120.0 + resolution: "@oxc-parser/binding-linux-arm-gnueabihf@npm:0.120.0" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@oxc-parser/binding-linux-arm-musleabihf@npm:0.120.0": + version: 0.120.0 + resolution: "@oxc-parser/binding-linux-arm-musleabihf@npm:0.120.0" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@oxc-parser/binding-linux-arm64-gnu@npm:0.120.0": + version: 0.120.0 + resolution: "@oxc-parser/binding-linux-arm64-gnu@npm:0.120.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@oxc-parser/binding-linux-arm64-musl@npm:0.120.0": + version: 0.120.0 + resolution: "@oxc-parser/binding-linux-arm64-musl@npm:0.120.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@oxc-parser/binding-linux-ppc64-gnu@npm:0.120.0": + version: 0.120.0 + resolution: "@oxc-parser/binding-linux-ppc64-gnu@npm:0.120.0" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@oxc-parser/binding-linux-riscv64-gnu@npm:0.120.0": + version: 0.120.0 + resolution: "@oxc-parser/binding-linux-riscv64-gnu@npm:0.120.0" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + +"@oxc-parser/binding-linux-riscv64-musl@npm:0.120.0": + version: 0.120.0 + resolution: "@oxc-parser/binding-linux-riscv64-musl@npm:0.120.0" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + +"@oxc-parser/binding-linux-s390x-gnu@npm:0.120.0": + version: 0.120.0 + resolution: "@oxc-parser/binding-linux-s390x-gnu@npm:0.120.0" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + +"@oxc-parser/binding-linux-x64-gnu@npm:0.120.0": + version: 0.120.0 + resolution: "@oxc-parser/binding-linux-x64-gnu@npm:0.120.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@oxc-parser/binding-linux-x64-musl@npm:0.120.0": + version: 0.120.0 + resolution: "@oxc-parser/binding-linux-x64-musl@npm:0.120.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@oxc-parser/binding-openharmony-arm64@npm:0.120.0": + version: 0.120.0 + resolution: "@oxc-parser/binding-openharmony-arm64@npm:0.120.0" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + +"@oxc-parser/binding-wasm32-wasi@npm:0.120.0": + version: 0.120.0 + resolution: "@oxc-parser/binding-wasm32-wasi@npm:0.120.0" + dependencies: + "@napi-rs/wasm-runtime": "npm:^1.1.1" + conditions: cpu=wasm32 + languageName: node + linkType: hard + +"@oxc-parser/binding-win32-arm64-msvc@npm:0.120.0": + version: 0.120.0 + resolution: "@oxc-parser/binding-win32-arm64-msvc@npm:0.120.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@oxc-parser/binding-win32-ia32-msvc@npm:0.120.0": + version: 0.120.0 + resolution: "@oxc-parser/binding-win32-ia32-msvc@npm:0.120.0" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@oxc-parser/binding-win32-x64-msvc@npm:0.120.0": + version: 0.120.0 + resolution: "@oxc-parser/binding-win32-x64-msvc@npm:0.120.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@oxc-project/types@npm:=0.122.0": version: 0.122.0 resolution: "@oxc-project/types@npm:0.122.0" @@ -3157,6 +3316,13 @@ __metadata: languageName: node linkType: hard +"@oxc-project/types@npm:^0.120.0": + version: 0.120.0 + resolution: "@oxc-project/types@npm:0.120.0" + checksum: 10c0/3090ca95ed1467ae790a79cf7aa49d1ea4ac390dbfccb7afb914c138034d01e72115e2e137a3cc76f409ba424e4d2b160a599fe137c88033ad68ba2df1e40b29 + languageName: node + linkType: hard + "@oxfmt/binding-android-arm-eabi@npm:0.33.0": version: 0.33.0 resolution: "@oxfmt/binding-android-arm-eabi@npm:0.33.0" @@ -4206,7 +4372,7 @@ __metadata: languageName: node linkType: hard -"@types/babel__core@npm:*, @types/babel__core@npm:^7.20.5": +"@types/babel__core@npm:^7.20.5": version: 7.20.5 resolution: "@types/babel__core@npm:7.20.5" dependencies: @@ -4228,15 +4394,6 @@ __metadata: languageName: node linkType: hard -"@types/babel__helper-plugin-utils@npm:^7.10.3": - version: 7.10.3 - resolution: "@types/babel__helper-plugin-utils@npm:7.10.3" - dependencies: - "@types/babel__core": "npm:*" - checksum: 10c0/c22f68e8019c1e75e42fccc6eaca94a269fa177c4544599aa084b216b879b626f63f89755a4ac2dc9054b6e9ed4e0fab1e3460d36ce20767c99aef4a3c81fce3 - languageName: node - linkType: hard - "@types/babel__template@npm:*": version: 7.4.3 resolution: "@types/babel__template@npm:7.4.3" @@ -4247,7 +4404,7 @@ __metadata: languageName: node linkType: hard -"@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.20.6, @types/babel__traverse@npm:^7.20.7": +"@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.20.7": version: 7.28.0 resolution: "@types/babel__traverse@npm:7.28.0" dependencies: @@ -4332,23 +4489,6 @@ __metadata: languageName: node linkType: hard -"@types/eslint@npm:^9.6.1": - version: 9.6.1 - resolution: "@types/eslint@npm:9.6.1" - dependencies: - "@types/estree": "npm:*" - "@types/json-schema": "npm:*" - checksum: 10c0/69ba24fee600d1e4c5abe0df086c1a4d798abf13792d8cfab912d76817fe1a894359a1518557d21237fbaf6eda93c5ab9309143dee4c59ef54336d1b3570420e - languageName: node - linkType: hard - -"@types/estree@npm:*, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.6, @types/estree@npm:^1.0.8": - version: 1.0.8 - resolution: "@types/estree@npm:1.0.8" - checksum: 10c0/39d34d1afaa338ab9763f37ad6066e3f349444f9052b9676a7cc0252ef9485a41c6d81c9c4e0d26e9077993354edf25efc853f3224dd4b447175ef62bdcc86a5 - languageName: node - linkType: hard - "@types/estree@npm:0.0.39": version: 0.0.39 resolution: "@types/estree@npm:0.0.39" @@ -4356,6 +4496,13 @@ __metadata: languageName: node linkType: hard +"@types/estree@npm:^1.0.0, @types/estree@npm:^1.0.6, @types/estree@npm:^1.0.8": + version: 1.0.8 + resolution: "@types/estree@npm:1.0.8" + checksum: 10c0/39d34d1afaa338ab9763f37ad6066e3f349444f9052b9676a7cc0252ef9485a41c6d81c9c4e0d26e9077993354edf25efc853f3224dd4b447175ef62bdcc86a5 + languageName: node + linkType: hard + "@types/express-serve-static-core@npm:^5.0.0": version: 5.1.0 resolution: "@types/express-serve-static-core@npm:5.1.0" @@ -4427,7 +4574,7 @@ __metadata: languageName: node linkType: hard -"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.15": +"@types/json-schema@npm:^7.0.15": version: 7.0.15 resolution: "@types/json-schema@npm:7.0.15" checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db @@ -4469,12 +4616,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:*, @types/node@npm:^22.0.0": - version: 22.13.14 - resolution: "@types/node@npm:22.13.14" +"@types/node@npm:*, @types/node@npm:22 || 24": + version: 24.12.0 + resolution: "@types/node@npm:24.12.0" dependencies: - undici-types: "npm:~6.20.0" - checksum: 10c0/fa2ab5b8277bfbcc86c42e46a3ea9871b0d559894cc9d955685d17178c9499f0b1bf03d1d1ea8d92ef2dda818988f4035acb8abf9dc15423a998fa56173ab804 + undici-types: "npm:~7.16.0" + checksum: 10c0/8b31c0af5b5474f13048a4e77c57f22cd4f8fe6e58c4b6fde9456b0c13f46a5bfaf5744ff88fd089581de9f0d6e99c584e022681de7acb26a58d258c654c4843 languageName: node linkType: hard @@ -4503,10 +4650,10 @@ __metadata: languageName: node linkType: hard -"@types/picomatch@npm:^3": - version: 3.0.2 - resolution: "@types/picomatch@npm:3.0.2" - checksum: 10c0/f35d16fe10a6e13ead6499dd7d7d317e4fd78e48260398104e837e5ca83d393024bdc6f432cb644c0a69b0726a071fcc6eb09befbbcfafb3c3c5f71dbbfde487 +"@types/picomatch@npm:^4.0.0": + version: 4.0.2 + resolution: "@types/picomatch@npm:4.0.2" + checksum: 10c0/0f46198c2d1beb5061816745355888e94a80a449a49af1ef69723f50e850c678b50cff299bd461f71e8009d46705e7cdeda8c8ffa23815b2e942c83877f855b9 languageName: node linkType: hard @@ -4856,7 +5003,7 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@npm:8.55.0, @typescript-eslint/utils@npm:^8.27.0, @typescript-eslint/utils@npm:^8.48.0": +"@typescript-eslint/utils@npm:8.55.0, @typescript-eslint/utils@npm:^8.48.0": version: 8.55.0 resolution: "@typescript-eslint/utils@npm:8.55.0" dependencies: @@ -4881,6 +5028,13 @@ __metadata: languageName: node linkType: hard +"@unicode/unicode-17.0.0@npm:^1.6.16": + version: 1.6.16 + resolution: "@unicode/unicode-17.0.0@npm:1.6.16" + checksum: 10c0/0d45cedb8349663e7d98509b0c78c10630adc86121003635e731654f152e84711eed1e9002d3ac588c77eadc360a1e1b51e4d153c7ff26443ee58f2bc77184e2 + languageName: node + linkType: hard + "@unrs/resolver-binding-android-arm-eabi@npm:1.11.1": version: 1.11.1 resolution: "@unrs/resolver-binding-android-arm-eabi@npm:1.11.1" @@ -5058,8 +5212,8 @@ __metadata: linkType: hard "@vitejs/plugin-react@npm:^5.0.0": - version: 5.1.4 - resolution: "@vitejs/plugin-react@npm:5.1.4" + version: 5.2.0 + resolution: "@vitejs/plugin-react@npm:5.2.0" dependencies: "@babel/core": "npm:^7.29.0" "@babel/plugin-transform-react-jsx-self": "npm:^7.27.1" @@ -5068,8 +5222,8 @@ __metadata: "@types/babel__core": "npm:^7.20.5" react-refresh: "npm:^0.18.0" peerDependencies: - vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - checksum: 10c0/dd7b8f40717ecd4a5ab18f467134ea8135f9a443359333d71e4114aeacfc8b679be9fd36dc12290d076c78883a02e708bfe1f0d93411c06c9659da0879b952e3 + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 10c0/bac0a409e71eee954a05bc41580411c369bd5f9ef0586a1f9743fba76ad6603c437d93d407d230780015361f93d1592c55e53314813cded6369c36d3c1e8edbf languageName: node linkType: hard @@ -5674,25 +5828,6 @@ __metadata: languageName: node linkType: hard -"babel-plugin-formatjs@npm:^10.5.37": - version: 10.5.41 - resolution: "babel-plugin-formatjs@npm:10.5.41" - dependencies: - "@babel/core": "npm:^7.26.10" - "@babel/helper-plugin-utils": "npm:^7.26.5" - "@babel/plugin-syntax-jsx": "npm:^7.25.9" - "@babel/traverse": "npm:^7.26.10" - "@babel/types": "npm:^7.26.10" - "@formatjs/icu-messageformat-parser": "npm:2.11.4" - "@formatjs/ts-transformer": "npm:3.14.2" - "@types/babel__core": "npm:^7.20.5" - "@types/babel__helper-plugin-utils": "npm:^7.10.3" - "@types/babel__traverse": "npm:^7.20.6" - tslib: "npm:^2.8.0" - checksum: 10c0/bbe0e182185c72e4136a4cf37b2366952ad5b1d090de00a132757d2a65a5a6aef95ada93dffdc4ed0cf4338a0ff29c5a0d025d77e8b774b088c69bd68ac07ca6 - languageName: node - linkType: hard - "babel-plugin-macros@npm:^3.1.0": version: 3.1.0 resolution: "babel-plugin-macros@npm:3.1.0" @@ -6028,7 +6163,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.2": +"chalk@npm:^4.0.0, chalk@npm:^4.0.2": version: 4.1.2 resolution: "chalk@npm:4.1.2" dependencies: @@ -6068,9 +6203,9 @@ __metadata: languageName: node linkType: hard -"chromatic@npm:^13.3.3": - version: 13.3.5 - resolution: "chromatic@npm:13.3.5" +"chromatic@npm:^16.0.0": + version: 16.0.0 + resolution: "chromatic@npm:16.0.0" peerDependencies: "@chromatic-com/cypress": ^0.*.* || ^1.0.0 "@chromatic-com/playwright": ^0.*.* || ^1.0.0 @@ -6083,7 +6218,7 @@ __metadata: chroma: dist/bin.js chromatic: dist/bin.js chromatic-cli: dist/bin.js - checksum: 10c0/58b3d7984db000f8c7b605788569a24c3f3cd41bb6b2d3a94f18acc9ff11ce6c6881f795c8390a94ff721ccfcf8a2d7942e78a54a1f70294a7b3d35ccc382154 + checksum: 10c0/ebebbf1c7d57e1ee9863997416c5125aab0a1886dce60fcb0358d34a51e0e1a45edc4635c8f8fb56d9facbcf21cd48014320c550f723b4791da51dde8552ee2b languageName: node linkType: hard @@ -6566,7 +6701,7 @@ __metadata: languageName: node linkType: hard -"decimal.js@npm:^10.4.3, decimal.js@npm:^10.6.0": +"decimal.js@npm:^10.6.0": version: 10.6.0 resolution: "decimal.js@npm:10.6.0" checksum: 10c0/07d69fbcc54167a340d2d97de95f546f9ff1f69d2b45a02fd7a5292412df3cd9eb7e23065e532a318f5474a2e1bccf8392fdf0443ef467f97f3bf8cb0477e5aa @@ -6831,7 +6966,7 @@ __metadata: languageName: node linkType: hard -"emoji-regex@npm:10.3.0, emoji-regex@npm:^10.3.0": +"emoji-regex@npm:^10.3.0": version: 10.3.0 resolution: "emoji-regex@npm:10.3.0" checksum: 10c0/b4838e8dcdceb44cf47f59abe352c25ff4fe7857acaf5fb51097c427f6f75b44d052eb907a7a3b86f86bc4eae3a93f5c2b7460abe79c407307e6212d65c91163 @@ -7291,22 +7426,19 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-formatjs@npm:^5.3.1": - version: 5.4.2 - resolution: "eslint-plugin-formatjs@npm:5.4.2" +"eslint-plugin-formatjs@npm:^6.0.0": + version: 6.4.3 + resolution: "eslint-plugin-formatjs@npm:6.4.3" dependencies: - "@formatjs/icu-messageformat-parser": "npm:2.11.4" - "@formatjs/ts-transformer": "npm:3.14.2" - "@types/eslint": "npm:^9.6.1" - "@types/picomatch": "npm:^3" - "@typescript-eslint/utils": "npm:^8.27.0" + "@formatjs/icu-messageformat-parser": "npm:3.5.3" + "@formatjs/ts-transformer": "npm:4.4.2" + "@types/picomatch": "npm:^4.0.0" + "@unicode/unicode-17.0.0": "npm:^1.6.16" magic-string: "npm:^0.30.0" picomatch: "npm:2 || 3 || 4" - tslib: "npm:^2.8.0" - unicode-emoji-utils: "npm:^1.2.0" peerDependencies: - eslint: ^9.23.0 - checksum: 10c0/46bee038c54a7da58647eaccee6c956857ad81499b60f814914921b26852e6bf9b0efabb9556d7aab0e151ba349dcf949dc5f14a356b80bc5e63f338e7f7cc7f + eslint: 9 || 10 + checksum: 10c0/348f72c8668ebeb10c7273103b6b91230684b97bf683eddaeaeb6a298e6fa483028504274ed53f3971d5acb68aaa790b89e184328a1520b41158c1b9ef770b39 languageName: node linkType: hard @@ -8676,15 +8808,14 @@ __metadata: languageName: node linkType: hard -"intl-messageformat@npm:10.7.18, intl-messageformat@npm:^10.7.16": - version: 10.7.18 - resolution: "intl-messageformat@npm:10.7.18" +"intl-messageformat@npm:11.2.0, intl-messageformat@npm:^11.0.0": + version: 11.2.0 + resolution: "intl-messageformat@npm:11.2.0" dependencies: - "@formatjs/ecma402-abstract": "npm:2.3.6" - "@formatjs/fast-memoize": "npm:2.2.7" - "@formatjs/icu-messageformat-parser": "npm:2.11.4" - tslib: "npm:^2.8.0" - checksum: 10c0/d54da9987335cb2bca26246304cea2ca6b1cb44ca416d6b28f3aa62b11477c72f7ce0bf3f11f5d236ceb1842bdc3378a926e606496d146fde18783ec92c314e1 + "@formatjs/ecma402-abstract": "npm:3.2.0" + "@formatjs/fast-memoize": "npm:3.1.1" + "@formatjs/icu-messageformat-parser": "npm:3.5.3" + checksum: 10c0/0f0a92324ef61c885902ff41c99754a65f0cc5cbe25d690c1771605117df4f97a786bb06dec50f12a7047e4cb2fab1f2516002df32e93be6ac02e1967e928e44 languageName: node linkType: hard @@ -10550,6 +10681,76 @@ __metadata: languageName: node linkType: hard +"oxc-parser@npm:^0.120.0": + version: 0.120.0 + resolution: "oxc-parser@npm:0.120.0" + dependencies: + "@oxc-parser/binding-android-arm-eabi": "npm:0.120.0" + "@oxc-parser/binding-android-arm64": "npm:0.120.0" + "@oxc-parser/binding-darwin-arm64": "npm:0.120.0" + "@oxc-parser/binding-darwin-x64": "npm:0.120.0" + "@oxc-parser/binding-freebsd-x64": "npm:0.120.0" + "@oxc-parser/binding-linux-arm-gnueabihf": "npm:0.120.0" + "@oxc-parser/binding-linux-arm-musleabihf": "npm:0.120.0" + "@oxc-parser/binding-linux-arm64-gnu": "npm:0.120.0" + "@oxc-parser/binding-linux-arm64-musl": "npm:0.120.0" + "@oxc-parser/binding-linux-ppc64-gnu": "npm:0.120.0" + "@oxc-parser/binding-linux-riscv64-gnu": "npm:0.120.0" + "@oxc-parser/binding-linux-riscv64-musl": "npm:0.120.0" + "@oxc-parser/binding-linux-s390x-gnu": "npm:0.120.0" + "@oxc-parser/binding-linux-x64-gnu": "npm:0.120.0" + "@oxc-parser/binding-linux-x64-musl": "npm:0.120.0" + "@oxc-parser/binding-openharmony-arm64": "npm:0.120.0" + "@oxc-parser/binding-wasm32-wasi": "npm:0.120.0" + "@oxc-parser/binding-win32-arm64-msvc": "npm:0.120.0" + "@oxc-parser/binding-win32-ia32-msvc": "npm:0.120.0" + "@oxc-parser/binding-win32-x64-msvc": "npm:0.120.0" + "@oxc-project/types": "npm:^0.120.0" + dependenciesMeta: + "@oxc-parser/binding-android-arm-eabi": + optional: true + "@oxc-parser/binding-android-arm64": + optional: true + "@oxc-parser/binding-darwin-arm64": + optional: true + "@oxc-parser/binding-darwin-x64": + optional: true + "@oxc-parser/binding-freebsd-x64": + optional: true + "@oxc-parser/binding-linux-arm-gnueabihf": + optional: true + "@oxc-parser/binding-linux-arm-musleabihf": + optional: true + "@oxc-parser/binding-linux-arm64-gnu": + optional: true + "@oxc-parser/binding-linux-arm64-musl": + optional: true + "@oxc-parser/binding-linux-ppc64-gnu": + optional: true + "@oxc-parser/binding-linux-riscv64-gnu": + optional: true + "@oxc-parser/binding-linux-riscv64-musl": + optional: true + "@oxc-parser/binding-linux-s390x-gnu": + optional: true + "@oxc-parser/binding-linux-x64-gnu": + optional: true + "@oxc-parser/binding-linux-x64-musl": + optional: true + "@oxc-parser/binding-openharmony-arm64": + optional: true + "@oxc-parser/binding-wasm32-wasi": + optional: true + "@oxc-parser/binding-win32-arm64-msvc": + optional: true + "@oxc-parser/binding-win32-ia32-msvc": + optional: true + "@oxc-parser/binding-win32-x64-msvc": + optional: true + checksum: 10c0/12b717560645480f12954b2eaaabcf8043dfc7a8bc0105627ff11d7958d52a90eaf9f5cfb6af82f148d5e69ef7c57ac5a84f8834fc1edda695b2b09d6bb4e73d + languageName: node + linkType: hard + "oxfmt@npm:^0.33.0": version: 0.33.0 resolution: "oxfmt@npm:0.33.0" @@ -11857,25 +12058,22 @@ __metadata: languageName: node linkType: hard -"react-intl@npm:^7.1.10": - version: 7.1.14 - resolution: "react-intl@npm:7.1.14" +"react-intl@npm:^10.0.0": + version: 10.1.0 + resolution: "react-intl@npm:10.1.0" dependencies: - "@formatjs/ecma402-abstract": "npm:2.3.6" - "@formatjs/icu-messageformat-parser": "npm:2.11.4" - "@formatjs/intl": "npm:3.1.8" - "@types/hoist-non-react-statics": "npm:^3.3.1" - "@types/react": "npm:16 || 17 || 18 || 19" - hoist-non-react-statics: "npm:^3.3.2" - intl-messageformat: "npm:10.7.18" - tslib: "npm:^2.8.0" + "@formatjs/ecma402-abstract": "npm:3.2.0" + "@formatjs/icu-messageformat-parser": "npm:3.5.3" + "@formatjs/intl": "npm:4.1.4" + intl-messageformat: "npm:11.2.0" peerDependencies: - react: 16 || 17 || 18 || 19 + "@types/react": 19 + react: 19 typescript: ^5.6.0 peerDependenciesMeta: typescript: optional: true - checksum: 10c0/b4361427ea05b4c9e7d87635a323854ca871710e01cd2a46b5da70b34b78a50661c04b2065258f3f49be134ca414c429c804bc34edc277784a9ffa0c04a30b04 + checksum: 10c0/94002b767b9d8b28f368f203a7debdea617c320b83154fa1edf9d1b6586ddffa4a763a1063a9dd1a148e393f2ffc8d5954ba983a4e86b26750d9312dfe3cd3fd languageName: node linkType: hard @@ -13964,7 +14162,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.3.0, tslib@npm:^2.4.0, tslib@npm:^2.8.0": +"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.3.0, tslib@npm:^2.4.0": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: 10c0/9c4759110a19c53f992d9aae23aac5ced636e99887b51b9e61def52611732872ff7668757d4e4c61f19691e36f4da981cd9485e869b4a7408d689f6bf1f14e62 @@ -14130,7 +14328,7 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5.6.0, typescript@npm:~5.9.0": +"typescript@npm:^5.6.0": version: 5.9.3 resolution: "typescript@npm:5.9.3" bin: @@ -14140,7 +14338,17 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^5.6.0#optional!builtin, typescript@patch:typescript@npm%3A~5.9.0#optional!builtin": +"typescript@npm:~6.0.0": + version: 6.0.2 + resolution: "typescript@npm:6.0.2" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/4b860b0bf87cc0fee0f66d8ef2640b5a8a8a8c74d1129adb82e389e5f97124383823c47946bef8a73ede371461143a3aa8544399d2133c7b2e4f07e81860af7f + languageName: node + linkType: hard + +"typescript@patch:typescript@npm%3A^5.6.0#optional!builtin": version: 5.9.3 resolution: "typescript@patch:typescript@npm%3A5.9.3#optional!builtin::version=5.9.3&hash=5786d5" bin: @@ -14150,6 +14358,16 @@ __metadata: languageName: node linkType: hard +"typescript@patch:typescript@npm%3A~6.0.0#optional!builtin": + version: 6.0.2 + resolution: "typescript@patch:typescript@npm%3A6.0.2#optional!builtin::version=6.0.2&hash=5786d5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 10c0/49f0b84fc6ca55653e77752b8a61beabc09ee3dae5d965c31596225aa6ef213c5727b1d2e895b900416dc603854ba0872ac4a812c2a4ed6793a601f9c675de02 + languageName: node + linkType: hard + "unbox-primitive@npm:^1.1.0": version: 1.1.0 resolution: "unbox-primitive@npm:1.1.0" @@ -14176,10 +14394,10 @@ __metadata: languageName: node linkType: hard -"undici-types@npm:~6.20.0": - version: 6.20.0 - resolution: "undici-types@npm:6.20.0" - checksum: 10c0/68e659a98898d6a836a9a59e6adf14a5d799707f5ea629433e025ac90d239f75e408e2e5ff086afc3cace26f8b26ee52155293564593fbb4a2f666af57fc59bf +"undici-types@npm:~7.16.0": + version: 7.16.0 + resolution: "undici-types@npm:7.16.0" + checksum: 10c0/3033e2f2b5c9f1504bdc5934646cb54e37ecaca0f9249c983f7b1fc2e87c6d18399ebb05dc7fd5419e02b2e915f734d872a65da2e3eeed1813951c427d33cc9a languageName: node linkType: hard @@ -14197,15 +14415,6 @@ __metadata: languageName: node linkType: hard -"unicode-emoji-utils@npm:^1.2.0": - version: 1.2.0 - resolution: "unicode-emoji-utils@npm:1.2.0" - dependencies: - emoji-regex: "npm:10.3.0" - checksum: 10c0/224413cab5f915abbbbf3e6061878f3c1b67acf7c6ab1d4bf283f13d290677633d614a7fd58b7af8cec54dc3a4e4f51c01f4797caa23c7c83cdaa759fe6de9ce - languageName: node - linkType: hard - "unicode-match-property-ecmascript@npm:^2.0.0": version: 2.0.0 resolution: "unicode-match-property-ecmascript@npm:2.0.0" @@ -14290,6 +14499,17 @@ __metadata: languageName: node linkType: hard +"unplugin@npm:^3.0.0": + version: 3.0.0 + resolution: "unplugin@npm:3.0.0" + dependencies: + "@jridgewell/remapping": "npm:^2.3.5" + picomatch: "npm:^4.0.3" + webpack-virtual-modules: "npm:^0.6.2" + checksum: 10c0/9b3a9eb7c1cfaab677160b9659b008b4562e08360b6c715f31bdd7692738a75de91f217931032ec247979f71e83d4c9b908245cf47d984b26fb318b60b1d2d36 + languageName: node + linkType: hard + "unrs-resolver@npm:^1.7.11": version: 1.11.1 resolution: "unrs-resolver@npm:1.11.1"