Merge pull request #3460 from glitch-soc/glitch-soc/merge-upstream

Merge upstream changes up to 9d5e10a70e
This commit is contained in:
Claire
2026-03-27 08:23:17 +01:00
committed by GitHub
404 changed files with 4859 additions and 2713 deletions

View File

@@ -14,7 +14,7 @@ runs:
shell: bash shell: bash
run: | run: |
sudo apt-get update 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 - name: Set up Ruby
uses: ruby/setup-ruby@4eb9f110bac952a8b68ecf92e3b5c7a987594ba6 # v1 uses: ruby/setup-ruby@4eb9f110bac952a8b68ecf92e3b5c7a987594ba6 # v1

View File

@@ -124,7 +124,6 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
ruby-version: ruby-version:
- '3.2'
- '3.3' - '3.3'
- '.ruby-version' - '.ruby-version'
steps: steps:
@@ -217,7 +216,6 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
ruby-version: ruby-version:
- '3.2'
- '3.3' - '3.3'
- '.ruby-version' - '.ruby-version'
@@ -348,7 +346,6 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
ruby-version: ruby-version:
- '3.2'
- '3.3' - '3.3'
- '.ruby-version' - '.ruby-version'
search-image: search-image:
@@ -387,10 +384,3 @@ jobs:
with: with:
name: test-search-logs-${{ matrix.ruby-version }} name: test-search-logs-${{ matrix.ruby-version }}
path: log/ path: log/
- name: Archive test screenshots
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
if: failure()
with:
name: test-search-screenshots
path: tmp/capybara/

View File

@@ -8,7 +8,7 @@ AllCops:
- lib/mastodon/migration_helpers.rb - lib/mastodon/migration_helpers.rb
ExtraDetails: true ExtraDetails: true
NewCops: enable NewCops: enable
TargetRubyVersion: 3.2 # Oldest supported ruby version TargetRubyVersion: 3.3 # Oldest supported ruby version
inherit_from: inherit_from:
- .rubocop/layout.yml - .rubocop/layout.yml

View File

@@ -137,11 +137,7 @@ const preview: Preview = {
}, [currentLocale, currentLocaleData]); }, [currentLocale, currentLocaleData]);
return ( return (
<IntlProvider <IntlProvider locale={currentLocale} messages={currentLocaleData}>
locale={currentLocale}
messages={currentLocaleData}
textComponent='span'
>
<Story /> <Story />
</IntlProvider> </IntlProvider>
); );

View File

@@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
source 'https://rubygems.org' source 'https://rubygems.org'
ruby '>= 3.2.0', '< 3.5.0' ruby '>= 3.3.0', '< 3.5.0'
gem 'propshaft' gem 'propshaft'
gem 'puma', '~> 7.0' gem 'puma', '~> 7.0'

View File

@@ -12,29 +12,29 @@ GEM
specs: specs:
action_text-trix (2.1.17) action_text-trix (2.1.17)
railties railties
actioncable (8.1.2.1) actioncable (8.1.3)
actionpack (= 8.1.2.1) actionpack (= 8.1.3)
activesupport (= 8.1.2.1) activesupport (= 8.1.3)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
actionmailbox (8.1.2.1) actionmailbox (8.1.3)
actionpack (= 8.1.2.1) actionpack (= 8.1.3)
activejob (= 8.1.2.1) activejob (= 8.1.3)
activerecord (= 8.1.2.1) activerecord (= 8.1.3)
activestorage (= 8.1.2.1) activestorage (= 8.1.3)
activesupport (= 8.1.2.1) activesupport (= 8.1.3)
mail (>= 2.8.0) mail (>= 2.8.0)
actionmailer (8.1.2.1) actionmailer (8.1.3)
actionpack (= 8.1.2.1) actionpack (= 8.1.3)
actionview (= 8.1.2.1) actionview (= 8.1.3)
activejob (= 8.1.2.1) activejob (= 8.1.3)
activesupport (= 8.1.2.1) activesupport (= 8.1.3)
mail (>= 2.8.0) mail (>= 2.8.0)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
actionpack (8.1.2.1) actionpack (8.1.3)
actionview (= 8.1.2.1) actionview (= 8.1.3)
activesupport (= 8.1.2.1) activesupport (= 8.1.3)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
rack (>= 2.2.4) rack (>= 2.2.4)
rack-session (>= 1.0.1) rack-session (>= 1.0.1)
@@ -42,16 +42,16 @@ GEM
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
useragent (~> 0.16) useragent (~> 0.16)
actiontext (8.1.2.1) actiontext (8.1.3)
action_text-trix (~> 2.1.15) action_text-trix (~> 2.1.15)
actionpack (= 8.1.2.1) actionpack (= 8.1.3)
activerecord (= 8.1.2.1) activerecord (= 8.1.3)
activestorage (= 8.1.2.1) activestorage (= 8.1.3)
activesupport (= 8.1.2.1) activesupport (= 8.1.3)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (8.1.2.1) actionview (8.1.3)
activesupport (= 8.1.2.1) activesupport (= 8.1.3)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.11) erubi (~> 1.11)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
@@ -61,22 +61,22 @@ GEM
activemodel (>= 4.1) activemodel (>= 4.1)
case_transform (>= 0.2) case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3) jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (8.1.2.1) activejob (8.1.3)
activesupport (= 8.1.2.1) activesupport (= 8.1.3)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (8.1.2.1) activemodel (8.1.3)
activesupport (= 8.1.2.1) activesupport (= 8.1.3)
activerecord (8.1.2.1) activerecord (8.1.3)
activemodel (= 8.1.2.1) activemodel (= 8.1.3)
activesupport (= 8.1.2.1) activesupport (= 8.1.3)
timeout (>= 0.4.0) timeout (>= 0.4.0)
activestorage (8.1.2.1) activestorage (8.1.3)
actionpack (= 8.1.2.1) actionpack (= 8.1.3)
activejob (= 8.1.2.1) activejob (= 8.1.3)
activerecord (= 8.1.2.1) activerecord (= 8.1.3)
activesupport (= 8.1.2.1) activesupport (= 8.1.3)
marcel (~> 1.0) marcel (~> 1.0)
activesupport (8.1.2.1) activesupport (8.1.3)
base64 base64
bigdecimal bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1) concurrent-ruby (~> 1.0, >= 1.3.1)
@@ -354,7 +354,7 @@ GEM
azure-blob (~> 0.5.2) azure-blob (~> 0.5.2)
hashie (~> 5.0) hashie (~> 5.0)
jmespath (1.6.2) jmespath (1.6.2)
json (2.19.2) json (2.19.3)
json-canonicalization (1.0.0) json-canonicalization (1.0.0)
json-jwt (1.17.0) json-jwt (1.17.0)
activesupport (>= 4.2) activesupport (>= 4.2)
@@ -657,20 +657,20 @@ GEM
rack (>= 1.3) rack (>= 1.3)
rackup (2.3.1) rackup (2.3.1)
rack (>= 3) rack (>= 3)
rails (8.1.2.1) rails (8.1.3)
actioncable (= 8.1.2.1) actioncable (= 8.1.3)
actionmailbox (= 8.1.2.1) actionmailbox (= 8.1.3)
actionmailer (= 8.1.2.1) actionmailer (= 8.1.3)
actionpack (= 8.1.2.1) actionpack (= 8.1.3)
actiontext (= 8.1.2.1) actiontext (= 8.1.3)
actionview (= 8.1.2.1) actionview (= 8.1.3)
activejob (= 8.1.2.1) activejob (= 8.1.3)
activemodel (= 8.1.2.1) activemodel (= 8.1.3)
activerecord (= 8.1.2.1) activerecord (= 8.1.3)
activestorage (= 8.1.2.1) activestorage (= 8.1.3)
activesupport (= 8.1.2.1) activesupport (= 8.1.3)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 8.1.2.1) railties (= 8.1.3)
rails-dom-testing (2.3.0) rails-dom-testing (2.3.0)
activesupport (>= 5.0.0) activesupport (>= 5.0.0)
minitest minitest
@@ -681,9 +681,9 @@ GEM
rails-i18n (8.1.0) rails-i18n (8.1.0)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
railties (>= 8.0.0, < 9) railties (>= 8.0.0, < 9)
railties (8.1.2.1) railties (8.1.3)
actionpack (= 8.1.2.1) actionpack (= 8.1.3)
activesupport (= 8.1.2.1) activesupport (= 8.1.3)
irb (~> 1.13) irb (~> 1.13)
rackup (>= 1.0.0) rackup (>= 1.0.0)
rake (>= 12.2) rake (>= 12.2)

View File

@@ -72,7 +72,7 @@ Mastodon is a **free, open-source social network server** based on [ActivityPub]
### Requirements ### Requirements
- **Ruby** 3.2+ - **Ruby** 3.3+
- **PostgreSQL** 14+ - **PostgreSQL** 14+
- **Redis** 7.0+ - **Redis** 7.0+
- **Node.js** 20+ - **Node.js** 20+

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -53,19 +53,21 @@ module SignatureVerification
raise Mastodon::SignatureVerificationError, 'Request not signed' unless signed_request? 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 rescue Mastodon::MalformedHeaderError => e
@signature_verification_failure_code = 400 @signature_verification_failure_code = 400
fail_with! e.message fail_with! e.message
@@ -89,7 +91,7 @@ module SignatureVerification
@signed_request_actor = nil @signed_request_actor = nil
end end
def actor_from_key_id def keypair_from_key_id
key_id = signed_request.key_id key_id = signed_request.key_id
domain = key_id.start_with?('acct:') ? key_id.split('@').last : 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:') if key_id.start_with?('acct:')
stoplight_wrapper.run { ResolveAccountService.new.call(key_id.delete_prefix('acct:'), suppress_errors: false) } stoplight_wrapper.run { ResolveAccountService.new.call(key_id.delete_prefix('acct:'), suppress_errors: false) }
elsif !ActivityPub::TagManager.instance.local_uri?(key_id) elsif !ActivityPub::TagManager.instance.local_uri?(key_id)
account = ActivityPub::TagManager.instance.uri_to_actor(key_id) keypair = Keypair.from_keyid(key_id)
account ||= stoplight_wrapper.run { ActivityPub::FetchRemoteKeyService.new.call(key_id, suppress_errors: false) } return keypair if keypair.present?
account
stoplight_wrapper.run { ActivityPub::FetchRemoteKeyService.new.call(key_id, suppress_errors: false) }
end end
rescue Mastodon::PrivateNetworkAddressError => e rescue Mastodon::PrivateNetworkAddressError => e
raise Mastodon::SignatureVerificationError, "Requests to private network addresses are disallowed (tried to query #{e.host})" raise Mastodon::SignatureVerificationError, "Requests to private network addresses are disallowed (tried to query #{e.host})"
@@ -120,14 +123,20 @@ module SignatureVerification
) )
end end
def actor_refresh_key!(actor) def keypair_refresh_key!(keypair)
return if actor.local? || !actor.activitypub? # TODO: this currently only is concerned with refreshing the actor and returning the legacy key, this needs to be reworked
return actor.refresh! if actor.respond_to?(:refresh!) && actor.possibly_stale? 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 rescue Mastodon::PrivateNetworkAddressError => e
raise Mastodon::SignatureVerificationError, "Requests to private network addresses are disallowed (tried to query #{e.host})" raise Mastodon::SignatureVerificationError, "Requests to private network addresses are disallowed (tried to query #{e.host})"
rescue Mastodon::HostValidationError, ActivityPub::FetchRemoteActorService::Error, Webfinger::Error => e rescue Mastodon::HostValidationError, ActivityPub::FetchRemoteActorService::Error, Webfinger::Error => e
raise Mastodon::SignatureVerificationError, e.message raise Mastodon::SignatureVerificationError, e.message
end 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 end

View File

@@ -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

View File

@@ -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

View File

@@ -2,6 +2,7 @@
class Settings::PrivacyController < Settings::BaseController class Settings::PrivacyController < Settings::BaseController
before_action :set_account before_action :set_account
before_action :set_email_subscriptions_count
def show; end def show; end
@@ -24,4 +25,8 @@ class Settings::PrivacyController < Settings::BaseController
def set_account def set_account
@account = current_account @account = current_account
end end
def set_email_subscriptions_count
@email_subscriptions_count = with_read_replica { @account.email_subscriptions.confirmed.count }
end
end end

View File

@@ -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

View File

@@ -18,23 +18,7 @@ module WellKnown
private private
def set_account def set_account
username = username_from_resource @account = WebfingerResource.new(resource_param).account
@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
end end
def resource_param def resource_param

View File

@@ -4,6 +4,7 @@ module ContextHelper
NAMED_CONTEXT_MAP = { NAMED_CONTEXT_MAP = {
activitystreams: 'https://www.w3.org/ns/activitystreams', activitystreams: 'https://www.w3.org/ns/activitystreams',
security: 'https://w3id.org/security/v1', security: 'https://w3id.org/security/v1',
webfinger: 'https://purl.archive.org/socialweb/webfinger',
}.freeze }.freeze
CONTEXT_EXTENSION_MAP = { CONTEXT_EXTENSION_MAP = {

View File

@@ -1,12 +1,14 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { injectIntl, defineMessages } from 'react-intl'; import { defineMessages } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import api from 'flavours/glitch/api'; import api from 'flavours/glitch/api';
import { injectIntl } from '../intl';
const messages = defineMessages({ const messages = defineMessages({
legal: { id: 'report.categories.legal', defaultMessage: 'Legal' }, legal: { id: 'report.categories.legal', defaultMessage: 'Legal' },
other: { id: 'report.categories.other', defaultMessage: 'Other' }, other: { id: 'report.categories.other', defaultMessage: 'Other' },

View File

@@ -1,6 +1,6 @@
import { useState, useCallback, useRef, useId } from 'react'; import { useState, useCallback, useRef, useId } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage, useIntl } from 'react-intl';
import type { import type {
OffsetValue, OffsetValue,
@@ -8,24 +8,37 @@ import type {
} from 'react-overlays/esm/usePopper'; } from 'react-overlays/esm/usePopper';
import Overlay from 'react-overlays/Overlay'; import Overlay from 'react-overlays/Overlay';
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import { useSelectableClick } from 'flavours/glitch/hooks/useSelectableClick'; 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 offset = [0, 4] as OffsetValue;
const popperConfig = { strategy: 'fixed' } as UsePopperOptions; const popperConfig = { strategy: 'fixed' } as UsePopperOptions;
export const AltTextBadge: React.FC<{ description: string }> = ({ export const AltTextBadge: React.FC<{ description: string }> = ({
description, description,
}) => { }) => {
const accessibilityId = useId(); const intl = useIntl();
const anchorRef = useRef<HTMLButtonElement>(null); const uniqueId = useId();
const popoverId = `${uniqueId}-popover`;
const titleId = `${uniqueId}-title`;
const buttonRef = useRef<HTMLButtonElement>(null);
const popoverRef = useRef<HTMLDivElement>(null);
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const handleClick = useCallback(() => { const handleClick = useCallback(() => {
setOpen((v) => !v); setOpen((v) => !v);
setTimeout(() => {
popoverRef.current?.focus();
}, 0);
}, [setOpen]); }, [setOpen]);
const handleClose = useCallback(() => { const handleClose = useCallback(() => {
setOpen(false); setOpen(false);
buttonRef.current?.focus();
}, [setOpen]); }, [setOpen]);
const [handleMouseDown, handleMouseUp] = useSelectableClick(handleClose); const [handleMouseDown, handleMouseUp] = useSelectableClick(handleClose);
@@ -34,11 +47,12 @@ export const AltTextBadge: React.FC<{ description: string }> = ({
<> <>
<button <button
type='button' type='button'
ref={anchorRef} ref={buttonRef}
className='media-gallery__alt__label' className='media-gallery__alt__label'
onClick={handleClick} onClick={handleClick}
aria-expanded={open} aria-expanded={open}
aria-controls={accessibilityId} aria-controls={popoverId}
aria-haspopup='dialog'
> >
ALT ALT
</button> </button>
@@ -47,7 +61,7 @@ export const AltTextBadge: React.FC<{ description: string }> = ({
rootClose rootClose
onHide={handleClose} onHide={handleClose}
show={open} show={open}
target={anchorRef} target={buttonRef}
placement='top-end' placement='top-end'
flip flip
offset={offset} offset={offset}
@@ -57,17 +71,33 @@ export const AltTextBadge: React.FC<{ description: string }> = ({
<div {...props} className='hover-card-controller'> <div {...props} className='hover-card-controller'>
<div // eslint-disable-line jsx-a11y/no-noninteractive-element-interactions <div // eslint-disable-line jsx-a11y/no-noninteractive-element-interactions
className='info-tooltip dropdown-animation' className='info-tooltip dropdown-animation'
role='region' role='dialog'
id={accessibilityId} aria-labelledby={titleId}
ref={popoverRef}
id={popoverId}
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp} onMouseUp={handleMouseUp}
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
tabIndex={0}
> >
<h4> <h4 id={titleId}>
<FormattedMessage <FormattedMessage
id='alt_text_badge.title' id='alt_text_badge.title'
defaultMessage='Alt text' defaultMessage='Alt text'
/> />
</h4> </h4>
<IconButton
title={intl.formatMessage({
id: 'lightbox.close',
defaultMessage: 'Close',
})}
icon='close'
iconComponent={CloseIcon}
onClick={handleClose}
className={classes.closeButton}
/>
<p>{description}</p> <p>{description}</p>
</div> </div>
</div> </div>

View File

@@ -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;
}
}

View File

@@ -96,7 +96,7 @@ export const Avatar: React.FC<Props> = ({
}; };
export const AvatarById: React.FC< export const AvatarById: React.FC<
{ accountId: string } & Omit<Props, 'account'> { accountId: string | undefined } & Omit<Props, 'account'>
> = ({ accountId, ...otherProps }) => { > = ({ accountId, ...otherProps }) => {
const account = useAccount(accountId); const account = useAccount(accountId);
return <Avatar account={account} {...otherProps} />; return <Avatar account={account} {...otherProps} />;

View File

@@ -31,7 +31,7 @@ export const Badge: FC<BadgeProps> = ({
data-account-role-id={roleId} data-account-role-id={roleId}
> >
{icon} {icon}
{label} <span>{label}</span>
{domain && <span className='account-role__domain'>{domain}</span>} {domain && <span className='account-role__domain'>{domain}</span>}
</div> </div>
); );

View File

@@ -3,7 +3,7 @@
align-items: start; align-items: start;
padding: 12px; padding: 12px;
gap: 8px; gap: 8px;
background-color: var(--color-bg-brand-softer); background-color: var(--color-bg-brand-softest);
color: var(--color-text-primary); color: var(--color-text-primary);
border-radius: 12px; border-radius: 12px;
} }
@@ -86,11 +86,11 @@
} }
.variantSubtle { .variantSubtle {
border: 1px solid var(--color-bg-brand-softer); border: 1px solid var(--color-bg-brand-softest);
background-color: var(--color-bg-primary); background-color: var(--color-bg-primary);
.icon { .icon {
background-color: var(--color-bg-brand-softer); background-color: var(--color-bg-brand-softest);
} }
} }
@@ -105,11 +105,11 @@
.variantInverted { .variantInverted {
background-color: var(--color-bg-inverted); background-color: var(--color-bg-inverted);
color: var(--color-text-on-inverted); color: var(--color-text-inverted);
} }
.variantSuccess { .variantSuccess {
background-color: var(--color-bg-success-softer); background-color: var(--color-bg-success-softest);
.icon { .icon {
background-color: var(--color-bg-success-soft); background-color: var(--color-bg-success-soft);
@@ -117,7 +117,7 @@
} }
.variantWarning { .variantWarning {
background-color: var(--color-bg-warning-softer); background-color: var(--color-bg-warning-softest);
.icon { .icon {
background-color: var(--color-bg-warning-soft); background-color: var(--color-bg-warning-soft);
@@ -125,7 +125,7 @@
} }
.variantError { .variantError {
background-color: var(--color-bg-error-softer); background-color: var(--color-bg-error-softest);
.icon { .icon {
background-color: var(--color-bg-error-soft); background-color: var(--color-bg-error-soft);

View File

@@ -6,10 +6,11 @@ import { Skeleton } from '../skeleton';
import type { DisplayNameProps } from './index'; import type { DisplayNameProps } from './index';
import { DisplayNameWithoutDomain } from './no-domain'; import { DisplayNameWithoutDomain } from './no-domain';
export const DisplayNameDefault: FC< export function useAccountHandle(
Omit<DisplayNameProps, 'variant'> & ComponentPropsWithoutRef<'span'> account: DisplayNameProps['account'],
> = ({ account, localDomain, className, ...props }) => { localDomain: DisplayNameProps['localDomain'],
const username = useMemo(() => { ) {
return useMemo(() => {
if (!account) { if (!account) {
return null; return null;
} }
@@ -20,6 +21,12 @@ export const DisplayNameDefault: FC<
} }
return `@${acct}`; return `@${acct}`;
}, [account, localDomain]); }, [account, localDomain]);
}
export const DisplayNameDefault: FC<
Omit<DisplayNameProps, 'variant'> & ComponentPropsWithoutRef<'span'>
> = ({ account, localDomain, className, ...props }) => {
const username = useAccountHandle(account, localDomain);
return ( return (
<DisplayNameWithoutDomain <DisplayNameWithoutDomain

View File

@@ -61,6 +61,6 @@
} }
[data-has-error='true'] & { [data-has-error='true'] & {
border-color: var(--color-text-error); border-color: var(--color-border-error);
} }
} }

View File

@@ -32,11 +32,11 @@
&:focus:user-invalid, &:focus:user-invalid,
&:required:user-invalid, &:required:user-invalid,
[data-has-error='true'] & { [data-has-error='true'] & {
outline-color: var(--color-text-error); outline-color: var(--color-border-error);
} }
&:focus { &:focus {
outline-color: var(--color-text-brand); outline-color: var(--color-border-brand);
} }
&:required:user-valid { &:required:user-valid {

View File

@@ -0,0 +1,26 @@
import type { ComponentClass } from 'react';
import { useIntl } from 'react-intl';
interface IntlHocProps<TProps extends Record<string, unknown>> {
component: ComponentClass<TProps>;
props: TProps;
}
export const IntlHoc = <TProps extends Record<string, unknown>>({
component: Component,
props,
}: IntlHocProps<TProps>) => {
const intl = useIntl();
return <Component {...props} intl={intl} />;
};
export const injectIntl = <TProps extends Record<string, unknown>>(
Component: ComponentClass<TProps>,
) => {
const WrappedComponent = (props: Omit<TProps, 'intl'>) => (
<IntlHoc component={Component} props={props as TProps} />
);
WrappedComponent.displayName = `injectIntl(${(Component.displayName ?? Component.name) || 'Component'})`;
return WrappedComponent;
};

View File

@@ -6,16 +6,12 @@ import { AuthorLink } from 'flavours/glitch/features/explore/components/author_l
export const MoreFromAuthor: React.FC<{ accountId: string }> = ({ export const MoreFromAuthor: React.FC<{ accountId: string }> = ({
accountId, accountId,
}) => ( }) => (
<FormattedMessage <div className='more-from-author'>
id='link_preview.more_from_author' <IconLogo />
defaultMessage='More from {name}' <FormattedMessage
values={{ name: <AuthorLink accountId={accountId} /> }} id='link_preview.more_from_author'
> defaultMessage='More from {name}'
{(chunks) => ( values={{ name: <AuthorLink accountId={accountId} /> }}
<div className='more-from-author'> />
<IconLogo /> </div>
{chunks}
</div>
)}
</FormattedMessage>
); );

View File

@@ -6,6 +6,7 @@ export const NotSignedInIndicator: React.FC = () => (
<FormattedMessage <FormattedMessage
id='not_signed_in_indicator.not_signed_in' id='not_signed_in_indicator.not_signed_in'
defaultMessage='You need to login to access this resource.' defaultMessage='You need to login to access this resource.'
tagName='span'
/> />
</div> </div>
</div> </div>

View File

@@ -20,6 +20,7 @@ export const RegenerationIndicator: React.FC = () => (
<FormattedMessage <FormattedMessage
id='regeneration_indicator.please_stand_by' id='regeneration_indicator.please_stand_by'
defaultMessage='Please stand by.' defaultMessage='Please stand by.'
tagName='span'
/> />
</div> </div>
</div> </div>

View File

@@ -37,7 +37,11 @@ export const ItemList = forwardRef<
} }
>(({ isLoading, emptyMessage, className, children, ...otherProps }, ref) => { >(({ isLoading, emptyMessage, className, children, ...otherProps }, ref) => {
if (!isLoading && Children.count(children) === 0 && emptyMessage) { if (!isLoading && Children.count(children) === 0 && emptyMessage) {
return <div className='empty-column-indicator'>{emptyMessage}</div>; return (
<div className='empty-column-indicator'>
<span>{emptyMessage}</span>
</div>
);
} }
return ( return (

View File

@@ -385,7 +385,7 @@ class ScrollableList extends PureComponent {
{alwaysPrepend && prepend} {alwaysPrepend && prepend}
<div className='empty-column-indicator'> <div className='empty-column-indicator'>
{emptyMessage} <span>{emptyMessage}</span>
</div> </div>
{footer} {footer}

View File

@@ -1,7 +1,7 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; import { FormattedMessage, defineMessages } from 'react-intl';
import { Link } from 'react-router-dom'; 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 { Skeleton } from 'flavours/glitch/components/skeleton';
import { domain } from 'flavours/glitch/initial_state'; import { domain } from 'flavours/glitch/initial_state';
import { injectIntl } from './intl';
const messages = defineMessages({ const messages = defineMessages({
aboutActiveUsers: { id: 'server_banner.about_active_users', defaultMessage: 'People using this server during the last 30 days (Monthly Active Users)' }, aboutActiveUsers: { id: 'server_banner.about_active_users', defaultMessage: 'People using this server during the last 30 days (Monthly Active Users)' },
}); });

View File

@@ -1,6 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl'; import { defineMessages, FormattedMessage } from 'react-intl';
import classNames from 'classnames'; 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 { SensitiveMediaContext } from '../features/ui/util/sensitive_media_context';
import { displayMedia } from '../initial_state'; import { displayMedia } from '../initial_state';
import { injectIntl } from './intl';
import AttachmentList from './attachment_list'; import AttachmentList from './attachment_list';
import { StatusHeader } from './status/header' import { StatusHeader } from './status/header'
import { getHashtagBarForStatus } from './hashtag_bar'; import { getHashtagBarForStatus } from './hashtag_bar';

View File

@@ -1,6 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages } from 'react-intl';
import { withRouter } from 'react-router-dom'; 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 { me, quickBoosting } from '../../initial_state';
import { IconButton } from '../icon_button'; import { IconButton } from '../icon_button';
import { injectIntl } from '../intl';
import { RelativeTimestamp } from '../relative_timestamp'; import { RelativeTimestamp } from '../relative_timestamp';
import { BoostButton } from '../status/boost_button'; import { BoostButton } from '../status/boost_button';
import { RemoveQuoteHint } from './remove_quote_hint'; import { RemoveQuoteHint } from './remove_quote_hint';
import { quoteItemState, selectStatusState } from '../status/boost_button_utils'; import { quoteItemState, selectStatusState } from '../status/boost_button_utils';
const messages = defineMessages({ const messages = defineMessages({
delete: { id: 'status.delete', defaultMessage: 'Delete' }, delete: { id: 'status.delete', defaultMessage: 'Delete' },
redraft: { id: 'status.redraft', defaultMessage: 'Delete & re-draft' }, redraft: { id: 'status.redraft', defaultMessage: 'Delete & re-draft' },

View File

@@ -1,7 +1,7 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { FormattedMessage, injectIntl } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import classnames from 'classnames'; import classnames from 'classnames';
import { withRouter } from 'react-router-dom'; 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 { languages as preloadedLanguages } from 'flavours/glitch/initial_state';
import { EmojiHTML } from './emoji/html'; import { EmojiHTML } from './emoji/html';
import { injectIntl } from './intl';
import { HandledLink } from './status/handled_link'; import { HandledLink } from './status/handled_link';
const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top) const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top)

View File

@@ -2,7 +2,7 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; 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 { Icon } from 'flavours/glitch/components/icon';
import { MediaIcon } from 'flavours/glitch/components/media_icon'; import { MediaIcon } from 'flavours/glitch/components/media_icon';
import { languages } from 'flavours/glitch/initial_state'; import { languages } from 'flavours/glitch/initial_state';
import { injectIntl } from '@/flavours/glitch/components/intl';
import { VisibilityIcon } from './visibility_icon'; import { VisibilityIcon } from './visibility_icon';

View File

@@ -161,7 +161,9 @@ export default class StatusPrepend extends PureComponent {
icon={iconComponent} icon={iconComponent}
/> />
</div> </div>
<Message /> <span>
<Message />
</span>
{children} {children}
</aside> </aside>
); );

View File

@@ -336,7 +336,6 @@ export const QuotedStatus: React.FC<QuotedStatusProps> = ({
return ( return (
<div className='status__quote'> <div className='status__quote'>
{/* @ts-expect-error Status is not yet typed */}
<StatusContainer <StatusContainer
isQuotedPost isQuotedPost
id={quotedStatusId} id={quotedStatusId}

View File

@@ -51,7 +51,7 @@ export const StatusThreadLabel: React.FC<{
<div className='status__prepend__icon'> <div className='status__prepend__icon'>
<Icon id='reply' icon={ReplyIcon} /> <Icon id='reply' icon={ReplyIcon} />
</div> </div>
{label} <span>{label}</span>
</div> </div>
); );
}; };

View File

@@ -22,8 +22,8 @@ button.tag:focus-visible {
} }
.active { .active {
border-color: var(--color-text-brand); border-color: var(--color-border-brand);
background: var(--color-bg-brand-softer); background: var(--color-bg-brand-softest);
color: var(--color-text-brand); color: var(--color-text-brand);
} }

View File

@@ -1,13 +1,14 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, FormattedMessage } from 'react-intl';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { injectIntl } from '@/flavours/glitch/components/intl';
import { fetchServer, fetchExtendedDescription, fetchDomainBlocks } from 'flavours/glitch/actions/server'; import { fetchServer, fetchExtendedDescription, fetchDomainBlocks } from 'flavours/glitch/actions/server';
import { Account } from 'flavours/glitch/components/account'; import { Account } from 'flavours/glitch/components/account';
import Column from 'flavours/glitch/components/column'; import Column from 'flavours/glitch/components/column';

View File

@@ -63,7 +63,11 @@ export const ActionBar: React.FC<{ account: Account }> = ({ account }) => {
className='account__action-bar__tab' className='account__action-bar__tab'
to={`/@${account.get('acct')}`} to={`/@${account.get('acct')}`}
> >
<FormattedMessage id='account.posts' defaultMessage='Posts' /> <FormattedMessage
id='account.posts'
defaultMessage='Posts'
tagName='span'
/>
<strong> <strong>
<FormattedNumber value={account.get('statuses_count')} /> <FormattedNumber value={account.get('statuses_count')} />
</strong> </strong>
@@ -75,7 +79,11 @@ export const ActionBar: React.FC<{ account: Account }> = ({ account }) => {
className='account__action-bar__tab' className='account__action-bar__tab'
to={`/@${account.get('acct')}/following`} to={`/@${account.get('acct')}/following`}
> >
<FormattedMessage id='account.follows' defaultMessage='Follows' /> <FormattedMessage
id='account.follows'
defaultMessage='Follows'
tagName='span'
/>
<strong> <strong>
<FormattedNumber value={account.get('following_count')} /> <FormattedNumber value={account.get('following_count')} />
</strong> </strong>
@@ -90,6 +98,7 @@ export const ActionBar: React.FC<{ account: Account }> = ({ account }) => {
<FormattedMessage <FormattedMessage
id='account.followers' id='account.followers'
defaultMessage='Followers' defaultMessage='Followers'
tagName='span'
/> />
<strong> <strong>
{account.get('followers_count') < 0 ? ( {account.get('followers_count') < 0 ? (

View File

@@ -3,6 +3,7 @@ import { useCallback, useState } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { CharacterCounter } from '@/flavours/glitch/components/character_counter';
import { Details } from '@/flavours/glitch/components/details'; import { Details } from '@/flavours/glitch/components/details';
import { TextAreaField } from '@/flavours/glitch/components/form_fields'; import { TextAreaField } from '@/flavours/glitch/components/form_fields';
import { LoadingIndicator } from '@/flavours/glitch/components/loading_indicator'; import { LoadingIndicator } from '@/flavours/glitch/components/loading_indicator';
@@ -84,7 +85,12 @@ export const ImageAltTextField: FC<{
const altLimit = useAppSelector( const altLimit = useAppSelector(
(state) => (state) =>
state.server.getIn( state.server.getIn(
['server', 'configuration', 'media_attachments', 'description_limit'], [
'server',
'configuration',
'accounts',
'max_header_description_length',
],
150, 150,
) as number, ) as number,
); );
@@ -100,23 +106,26 @@ export const ImageAltTextField: FC<{
<> <>
<img src={imageSrc} alt='' className={classes.altImage} /> <img src={imageSrc} alt='' className={classes.altImage} />
<TextAreaField <div>
label={ <TextAreaField
<FormattedMessage label={
id='account_edit.image_alt_modal.text_label' <FormattedMessage
defaultMessage='Alt text' id='account_edit.image_alt_modal.text_label'
/> defaultMessage='Alt text'
} />
hint={ }
<FormattedMessage hint={
id='account_edit.image_alt_modal.text_hint' <FormattedMessage
defaultMessage='Alt text helps screen reader users to understand your content.' id='account_edit.image_alt_modal.text_hint'
/> defaultMessage='Alt text helps screen reader users to understand your content.'
} />
onChange={handleChange} }
value={altText} onChange={handleChange}
maxLength={altLimit} value={altText}
/> maxLength={altLimit}
/>
<CharacterCounter currentString={altText} maxLength={altLimit} />
</div>
{!hideTip && ( {!hideTip && (
<Details <Details
@@ -130,7 +139,7 @@ export const ImageAltTextField: FC<{
> >
<FormattedMessage <FormattedMessage
id='account_edit.image_alt_modal.details_content' id='account_edit.image_alt_modal.details_content'
defaultMessage='DO: <ul> <li>Describe yourself as pictured</li> <li>Use third person language (e.g. “Alex” instead of “me”)</li> <li>Be succinct a few words is often enough</li> </ul> DONT: <ul> <li>Start with “Photo of” its redundant for screen readers</li> </ul> EXAMPLE: <ul> <li>“Alex wearing a green shirt and glasses”</li> </ul>' defaultMessage='DO: <ul> <li>Describe yourself as pictured</li> <li>Use third person language (e.g. “Alex” instead of “me”)</li> <li>Be succinct a few words is often enough</li> </ul> DONT: <ul> <li>Start with “Photo of” its redundant for screen readers</li> </ul> EXAMPLE: <ul> <li>“Alex wearing a green shirt and glasses”</li></ul>'
values={{ values={{
ul: (chunks) => <ul>{chunks}</ul>, ul: (chunks) => <ul>{chunks}</ul>,
li: (chunks) => <li>{chunks}</li>, li: (chunks) => <li>{chunks}</li>,

View File

@@ -58,7 +58,7 @@
transition: background 0.2s ease-in-out; transition: background 0.2s ease-in-out;
&:hover { &:hover {
background: var(--color-bg-brand-softer); background: var(--color-bg-brand-softest);
} }
&:focus-visible { &:focus-visible {

View File

@@ -160,7 +160,7 @@
&:active, &:active,
&:focus, &:focus,
&:hover { &:hover {
background-color: var(--color-bg-brand-softer); background-color: var(--color-bg-brand-softest);
} }
&:disabled { &:disabled {
@@ -177,7 +177,7 @@
.deleteButton { .deleteButton {
--default-icon-color: var(--color-text-error); --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); --hover-icon-color: var(--color-text-on-error-base);
} }
@@ -201,7 +201,7 @@
&, &,
&:global(.active) { &:global(.active) {
// Overrides the transparent background added by default with .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; position: absolute;

View File

@@ -65,5 +65,9 @@ export const EmptyMessage: React.FC<EmptyMessageProps> = ({
); );
} }
return <div className='empty-column-indicator'>{message}</div>; return (
<div className='empty-column-indicator'>
<span>{message}</span>
</div>
);
}; };

View File

@@ -154,6 +154,7 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
key={item.id} key={item.id}
collection={item} collection={item}
withoutBorder={index === listedCollections.length - 1} withoutBorder={index === listedCollections.length - 1}
withAuthorHandle={false}
positionInList={index + 1} positionInList={index + 1}
listSize={listedCollections.length} listSize={listedCollections.length}
/> />

View File

@@ -9,6 +9,11 @@
.barWrapper { .barWrapper {
border-bottom: none; border-bottom: none;
padding-inline: 24px;
@container (width < 500px) {
padding-inline: 16px;
}
} }
.avatarWrapper { .avatarWrapper {
@@ -93,7 +98,7 @@
} }
svg { svg {
background: var(--color-bg-brand-softer); background: var(--color-bg-brand-softest);
width: 28px; width: 28px;
height: 28px; height: 28px;
padding: 5px; padding: 5px;
@@ -125,7 +130,7 @@ $button-fallback-breakpoint: $button-breakpoint + 55px;
position: sticky; position: sticky;
bottom: var(--mobile-bottom-nav-height); bottom: var(--mobile-bottom-nav-height);
padding: 12px 16px; padding: 12px 16px;
margin: 0 -20px; margin: 0 -16px;
@container (width >= #{$button-breakpoint}) { @container (width >= #{$button-breakpoint}) {
display: none; display: none;
@@ -184,7 +189,7 @@ $button-fallback-breakpoint: $button-breakpoint + 55px;
.badgeMuted { .badgeMuted {
background-color: var(--color-bg-inverted); background-color: var(--color-bg-inverted);
color: var(--color-text-on-inverted); color: var(--color-text-inverted);
} }
.badgeBlocked { .badgeBlocked {
@@ -270,7 +275,7 @@ svg.badgeIcon {
} }
.fieldVerified { .fieldVerified {
background-color: var(--color-bg-success-softer); background-color: var(--color-bg-success-softest);
dt { dt {
padding-right: 24px; padding-right: 24px;
@@ -292,8 +297,8 @@ svg.badgeIcon {
} }
.fieldOverflowButton { .fieldOverflowButton {
--default-bg-color: var(--color-bg-secondary-solid); --default-bg-color: var(--color-bg-secondary);
--hover-bg-color: var(--color-bg-brand-softer-solid); --hover-bg-color: var(--color-bg-brand-softest);
position: absolute; position: absolute;
right: 8px; right: 8px;
@@ -388,7 +393,7 @@ svg.badgeIcon {
padding: 0 24px; padding: 0 24px;
@container (width < 500px) { @container (width < 500px) {
padding: 0 12px; padding: 0 16px;
a { a {
flex: 1 1 0px; flex: 1 1 0px;
@@ -413,7 +418,7 @@ svg.badgeIcon {
:global(.active) { :global(.active) {
color: var(--color-text-brand); color: var(--color-text-brand);
border-bottom: 4px solid var(--color-text-brand); border-bottom: 4px solid var(--color-border-brand);
padding-bottom: 14px; padding-bottom: 14px;
} }
} }

View File

@@ -78,6 +78,7 @@ export const TagSuggestions: FC = () => {
values={{ values={{
link: (chunks) => <Link to='/profile/featured_tags'>{chunks}</Link>, link: (chunks) => <Link to='/profile/featured_tags'>{chunks}</Link>,
}} }}
tagName='span'
/> />
</Callout> </Callout>
); );
@@ -122,6 +123,7 @@ export const TagSuggestions: FC = () => {
/> />
), ),
}} }}
tagName='span'
/> />
</Callout> </Callout>
); );

View File

@@ -59,6 +59,7 @@ export const InfoButton: React.FC = () => {
> >
<FormattedMessage <FormattedMessage
id='info_button.what_is_alt_text' id='info_button.what_is_alt_text'
// eslint-disable-next-line formatjs/prefer-full-sentence
defaultMessage='<h1>What is alt text?</h1> defaultMessage='<h1>What is alt text?</h1>
<p>Alt text provides image descriptions for people with vision impairments, low-bandwidth connections, or those seeking extra context.</p> <p>Alt text provides image descriptions for people with vision impairments, low-bandwidth connections, or those seeking extra context.</p>

View File

@@ -1,6 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
@@ -8,6 +8,7 @@ import { connect } from 'react-redux';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { injectIntl } from '@/flavours/glitch/components/intl';
import BlockIcon from '@/material-icons/400-24px/block-fill.svg?react'; import BlockIcon from '@/material-icons/400-24px/block-fill.svg?react';
import { Account } from 'flavours/glitch/components/account'; import { Account } from 'flavours/glitch/components/account';

View File

@@ -1,4 +1,4 @@
import { Fragment, useCallback, useRef, useState } from 'react'; import { useCallback, useRef, useState } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
@@ -100,14 +100,12 @@ const RevokeControls: React.FC<{
<FormattedMessage <FormattedMessage
id='collections.detail.accept_inclusion' id='collections.detail.accept_inclusion'
defaultMessage='Okay' defaultMessage='Okay'
tagName={Fragment}
/> />
</Button> </Button>
<Button secondary onClick={confirmRevoke}> <Button secondary onClick={confirmRevoke}>
<FormattedMessage <FormattedMessage
id='collections.detail.revoke_inclusion' id='collections.detail.revoke_inclusion'
defaultMessage='Remove me' defaultMessage='Remove me'
tagName={Fragment}
/> />
</Button> </Button>
</div> </div>
@@ -143,7 +141,6 @@ const SensitiveScreen: React.FC<{
<FormattedMessage <FormattedMessage
id='content_warning.show' id='content_warning.show'
defaultMessage='Show anyway' defaultMessage='Show anyway'
tagName={Fragment}
/> />
</Button> </Button>
</div> </div>
@@ -205,7 +202,6 @@ export const CollectionAccountsList: React.FC<{
values={{ values={{
author: <SimpleAuthorName id={collection.account_id} />, author: <SimpleAuthorName id={collection.account_id} />,
}} }}
tagName={Fragment}
/> />
</h3> </h3>
<Article <Article
@@ -231,7 +227,6 @@ export const CollectionAccountsList: React.FC<{
<FormattedMessage <FormattedMessage
id='collections.detail.other_accounts_in_collection' id='collections.detail.other_accounts_in_collection'
defaultMessage='Others in this collection:' defaultMessage='Others in this collection:'
tagName={Fragment}
/> />
</h3> </h3>
</> </>

View File

@@ -1,8 +1,9 @@
.wrapper { .wrapper {
display: flex; display: flex;
align-items: center; align-items: start;
margin-inline: 24px;
padding-block: 12px;
gap: 16px; gap: 16px;
padding-inline: 16px;
&:not(.wrapperWithoutBorder) { &:not(.wrapperWithoutBorder) {
border-bottom: 1px solid var(--color-border-primary); border-bottom: 1px solid var(--color-border-primary);
@@ -12,12 +13,42 @@
.content { .content {
position: relative; position: relative;
flex-grow: 1; 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 { .link {
display: block; display: block;
margin-bottom: 2px;
font-size: 15px; font-size: 15px;
font-weight: 500; font-weight: 500;
text-decoration: none; text-decoration: none;
@@ -40,15 +71,12 @@
color: var(--color-text-secondary); color: var(--color-text-secondary);
} }
.metaList { .menuButton {
--gap: 0.75ch; padding: 4px;
margin-top: -2px;
display: flex; svg {
flex-wrap: wrap; width: 20px;
gap: var(--gap); height: 20px;
& > li:not(:last-child)::after {
content: '·';
margin-inline-start: var(--gap);
} }
} }

View File

@@ -5,70 +5,65 @@ import { FormattedMessage } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { Link } from 'react-router-dom'; 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 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 { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp';
import { Article } from 'flavours/glitch/components/scrollable_list/components'; 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 classes from './collection_list_item.module.scss';
import { CollectionMenu } from './collection_menu'; import { CollectionMenu } from './collection_menu';
export const CollectionMetaData: React.FC<{ export const AvatarGrid: React.FC<{
collection: ApiCollectionJSON; accountIds: (string | undefined)[];
extended?: boolean; sensitive?: boolean;
className?: string; }> = ({ accountIds: ids, sensitive }) => {
}> = ({ collection, extended, className }) => { const avatarIds = [ids[0], ids[1], ids[2], ids[3]];
return ( return (
<ul className={classNames(classes.metaList, className)}> <div
<FormattedMessage className={classNames(
id='collections.account_count' classes.avatarGrid,
defaultMessage='{count, plural, one {# account} other {# accounts}}' sensitive ? classes.avatarGridSensitive : null,
values={{ count: collection.item_count }}
tagName='li'
/>
{extended && (
<>
{collection.discoverable ? (
<FormattedMessage
id='collections.visibility_public'
defaultMessage='Public'
tagName='li'
/>
) : (
<FormattedMessage
id='collections.visibility_unlisted'
defaultMessage='Unlisted'
tagName='li'
/>
)}
{collection.sensitive && (
<FormattedMessage
id='collections.sensitive'
defaultMessage='Sensitive'
tagName='li'
/>
)}
</>
)} )}
<FormattedMessage >
id='collections.last_updated_at' {avatarIds.map((id) => (
defaultMessage='Last updated: {date}' <AvatarById
values={{ animate={false}
date: <RelativeTimestamp timestamp={collection.updated_at} long />, key={id}
}} accountId={id}
tagName='li' className={classes.avatar}
/> size={25}
</ul> />
))}
{sensitive && <WarningIcon className={classes.avatarSensitiveBadge} />}
</div>
); );
}; };
export const CollectionListItem: React.FC<{ export const CollectionListItem: React.FC<{
collection: ApiCollectionJSON; collection: ApiCollectionJSON;
withoutBorder?: boolean; withoutBorder?: boolean;
withAuthorHandle?: boolean;
withTimestamp?: boolean;
positionInList: number; positionInList: number;
listSize: number; listSize: number;
}> = ({ collection, withoutBorder, positionInList, listSize }) => { }> = ({
collection,
withoutBorder,
withAuthorHandle = true,
withTimestamp,
positionInList,
listSize,
}) => {
const { id, name } = collection; 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 ( return (
<Article <Article
@@ -78,19 +73,67 @@ export const CollectionListItem: React.FC<{
withoutBorder && classes.wrapperWithoutBorder, withoutBorder && classes.wrapperWithoutBorder,
)} )}
aria-labelledby={linkId} aria-labelledby={linkId}
aria-describedby={infoId}
aria-posinset={positionInList} aria-posinset={positionInList}
aria-setsize={listSize} aria-setsize={listSize}
> >
<div className={classes.content}> <div className={classes.content}>
<h2 id={linkId}> <AvatarGrid
<Link to={`/collections/${id}`} className={classes.link}> accountIds={collection.items.map((item) => item.account_id)}
{name} sensitive={collection.sensitive}
</Link> />
</h2> <div>
<CollectionMetaData collection={collection} className={classes.info} /> <h2 id={linkId}>
<Link to={`/collections/${id}`} className={classes.link}>
{name}
</Link>
</h2>
<ul className={classes.info} id={infoId}>
{collection.sensitive && (
<li className='sr-only'>
<FormattedMessage
id='collections.sensitive'
defaultMessage='Sensitive'
/>
</li>
)}
{withAuthorHandle && authorAccount && (
<FormattedMessage
id='collections.by_account'
defaultMessage='by {account_handle}'
values={{
account_handle: authorHandle,
}}
tagName='li'
/>
)}
<FormattedMessage
id='collections.account_count'
defaultMessage='{count, plural, one {# account} other {# accounts}}'
values={{ count: collection.item_count }}
tagName='li'
/>
{withTimestamp && (
<FormattedMessage
id='collections.last_updated_at'
defaultMessage='Last updated: {date}'
values={{
date: (
<RelativeTimestamp timestamp={collection.updated_at} long />
),
}}
tagName='li'
/>
)}
</ul>
</div>
</div> </div>
<CollectionMenu context='list' collection={collection} /> <CollectionMenu
context='list'
collection={collection}
className={classes.menuButton}
/>
</Article> </Article>
); );
}; };

View File

@@ -6,6 +6,7 @@ import { Helmet } from 'react-helmet';
import { useHistory, useLocation, useParams } from 'react-router'; import { useHistory, useLocation, useParams } from 'react-router';
import { openModal } from '@/flavours/glitch/actions/modal'; 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 ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
import ShareIcon from '@/material-icons/400-24px/share.svg?react'; import ShareIcon from '@/material-icons/400-24px/share.svg?react';
import type { ApiCollectionJSON } from 'flavours/glitch/api_types/collections'; 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 { useAppDispatch, useAppSelector } from 'flavours/glitch/store';
import { CollectionAccountsList } from './accounts_list'; import { CollectionAccountsList } from './accounts_list';
import { CollectionMetaData } from './collection_list_item';
import { CollectionMenu } from './collection_menu'; import { CollectionMenu } from './collection_menu';
import classes from './styles.module.scss'; import classes from './styles.module.scss';
@@ -40,6 +40,54 @@ const messages = defineMessages({
}, },
}); });
const CollectionMetaData: React.FC<{
collection: ApiCollectionJSON;
extended?: boolean;
}> = ({ collection, extended }) => {
return (
<ul className={classes.metaList}>
<FormattedMessage
id='collections.account_count'
defaultMessage='{count, plural, one {# account} other {# accounts}}'
values={{ count: collection.item_count }}
tagName='li'
/>
{extended && (
<>
{collection.discoverable ? (
<FormattedMessage
id='collections.visibility_public'
defaultMessage='Public'
tagName='li'
/>
) : (
<FormattedMessage
id='collections.visibility_unlisted'
defaultMessage='Unlisted'
tagName='li'
/>
)}
{collection.sensitive && (
<FormattedMessage
id='collections.sensitive'
defaultMessage='Sensitive'
tagName='li'
/>
)}
</>
)}
<FormattedMessage
id='collections.last_updated_at'
defaultMessage='Last updated: {date}'
values={{
date: <RelativeTimestamp timestamp={collection.updated_at} long />,
}}
tagName='li'
/>
</ul>
);
};
export const AuthorNote: React.FC<{ id: string; previewMode?: boolean }> = ({ export const AuthorNote: React.FC<{ id: string; previewMode?: boolean }> = ({
id, id,
// When previewMode is enabled, your own display name // When previewMode is enabled, your own display name
@@ -137,7 +185,6 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({
<CollectionMetaData <CollectionMetaData
extended={account_id === me} extended={account_id === me}
collection={collection} collection={collection}
className={classes.metaData}
/> />
</div> </div>
); );

View File

@@ -52,9 +52,19 @@
font-size: 13px; font-size: 13px;
} }
.metaData { .metaList {
--gap: 0.75ch;
display: flex;
flex-wrap: wrap;
margin-top: 16px; margin-top: 16px;
gap: var(--gap);
font-size: 15px; font-size: 15px;
& > li:not(:last-child)::after {
content: '·';
margin-inline-start: var(--gap);
}
} }
.columnSubheading { .columnSubheading {

View File

@@ -1,4 +1,4 @@
import { Fragment, useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { FormattedMessage, useIntl } from 'react-intl'; import { FormattedMessage, useIntl } from 'react-intl';
@@ -413,7 +413,6 @@ const LanguageField: React.FC = () => {
<FormattedMessage <FormattedMessage
id='collections.collection_language_none' id='collections.collection_language_none'
defaultMessage='None' defaultMessage='None'
tagName={Fragment}
/> />
</option> </option>
{languages?.map(([code, name, localName]) => ( {languages?.map(([code, name, localName]) => (

View File

@@ -47,6 +47,7 @@ export const Collections: React.FC<{
<FormattedMessage <FormattedMessage
id='collections.error_loading_collections' id='collections.error_loading_collections'
defaultMessage='There was an error when trying to load your collections.' defaultMessage='There was an error when trying to load your collections.'
tagName='span'
/> />
) : ( ) : (
<> <>
@@ -92,6 +93,8 @@ export const Collections: React.FC<{
<ItemList emptyMessage={emptyMessage} isLoading={status === 'loading'}> <ItemList emptyMessage={emptyMessage} isLoading={status === 'loading'}>
{collections.map((item, index) => ( {collections.map((item, index) => (
<CollectionListItem <CollectionListItem
withTimestamp
withAuthorHandle={false}
key={item.id} key={item.id}
collection={item} collection={item}
positionInList={index + 1} positionInList={index + 1}

View File

@@ -1,10 +1,11 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { injectIntl } from '@/flavours/glitch/components/intl';
import SettingText from 'flavours/glitch/components/setting_text'; import SettingText from 'flavours/glitch/components/setting_text';
import SettingToggle from 'flavours/glitch/features/notifications/components/setting_toggle'; import SettingToggle from 'flavours/glitch/features/notifications/components/setting_toggle';

View File

@@ -1,13 +1,14 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, FormattedMessage } from 'react-intl';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import PeopleIcon from '@/material-icons/400-24px/group.svg?react'; import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
import { injectIntl } from '@/flavours/glitch/components/intl';
import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner'; import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { domain, localLiveFeedAccess } from 'flavours/glitch/initial_state'; import { domain, localLiveFeedAccess } from 'flavours/glitch/initial_state';

View File

@@ -1,7 +1,7 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { createRef } from 'react'; import { createRef } from 'react';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
@@ -15,6 +15,7 @@ import { missingAltTextModal } from 'flavours/glitch/initial_state';
import AutosuggestInput from 'flavours/glitch/components/autosuggest_input'; import AutosuggestInput from 'flavours/glitch/components/autosuggest_input';
import AutosuggestTextarea from 'flavours/glitch/components/autosuggest_textarea'; import AutosuggestTextarea from 'flavours/glitch/components/autosuggest_textarea';
import { Button } from 'flavours/glitch/components/button'; import { Button } from 'flavours/glitch/components/button';
import { injectIntl } from '@/flavours/glitch/components/intl';
import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container'; import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container';
import PollButtonContainer from '../containers/poll_button_container'; import PollButtonContainer from '../containers/poll_button_container';
import SpoilerButtonContainer from '../containers/spoiler_button_container'; import SpoilerButtonContainer from '../containers/spoiler_button_container';

View File

@@ -1,7 +1,7 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, FormattedMessage } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
@@ -12,6 +12,7 @@ import Overlay from 'react-overlays/Overlay';
import MoodIcon from '@/material-icons/400-20px/mood.svg?react'; import MoodIcon from '@/material-icons/400-20px/mood.svg?react';
import { IconButton } from 'flavours/glitch/components/icon_button'; import { IconButton } from 'flavours/glitch/components/icon_button';
import { injectIntl } from '@/flavours/glitch/components/intl';
import { buildCustomEmojis, categoriesFromEmojis } from '../../emoji/emoji'; import { buildCustomEmojis, categoriesFromEmojis } from '../../emoji/emoji';
import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components'; import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components';

View File

@@ -1,9 +1,10 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages } from 'react-intl';
import BarChart4BarsIcon from '@/material-icons/400-20px/bar_chart_4_bars.svg?react'; import BarChart4BarsIcon from '@/material-icons/400-20px/bar_chart_4_bars.svg?react';
import { injectIntl } from '@/flavours/glitch/components/intl';
import { IconButton } from '../../../components/icon_button'; import { IconButton } from '../../../components/icon_button';

View File

@@ -1,6 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
@@ -10,6 +10,8 @@ import PhotoLibraryIcon from '@/material-icons/400-20px/photo_library.svg?react'
import BrushIcon from '@/material-icons/400-24px/brush.svg?react'; import BrushIcon from '@/material-icons/400-24px/brush.svg?react';
import UploadFileIcon from '@/material-icons/400-24px/upload_file.svg?react'; import UploadFileIcon from '@/material-icons/400-24px/upload_file.svg?react';
import { injectIntl } from '@/flavours/glitch/components/intl';
import { DropdownIconButton } from './dropdown_icon_button'; import { DropdownIconButton } from './dropdown_icon_button';
const messages = defineMessages({ const messages = defineMessages({

View File

@@ -30,17 +30,19 @@ export const UploadProgress: React.FC<UploadProgressProps> = ({
<Icon id='upload' icon={UploadFileIcon} /> <Icon id='upload' icon={UploadFileIcon} />
<div className='upload-progress__message'> <div className='upload-progress__message'>
{isProcessing ? ( <span>
<FormattedMessage {isProcessing ? (
id='upload_progress.processing' <FormattedMessage
defaultMessage='Processing' id='upload_progress.processing'
/> defaultMessage='Processing…'
) : ( />
<FormattedMessage ) : (
id='upload_progress.label' <FormattedMessage
defaultMessage='Uploading…' id='upload_progress.label'
/> defaultMessage='Uploading…'
)} />
)}
</span>
<div className='upload-progress__backdrop'> <div className='upload-progress__backdrop'>
<animated.div className='upload-progress__tracker' style={styles} /> <animated.div className='upload-progress__tracker' style={styles} />

View File

@@ -1,8 +1,9 @@
import { injectIntl, defineMessages } from 'react-intl'; import { defineMessages } from 'react-intl';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import WarningIcon from '@/material-icons/400-20px/warning.svg?react'; 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 { IconButton } from 'flavours/glitch/components/icon_button';
import { changeComposeSpoilerness } from '../../../actions/compose'; import { changeComposeSpoilerness } from '../../../actions/compose';

View File

@@ -1,10 +1,11 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { injectIntl } from '@/flavours/glitch/components/intl';
import SettingToggle from 'flavours/glitch/features/notifications/components/setting_toggle'; import SettingToggle from 'flavours/glitch/features/notifications/components/setting_toggle';
import SettingText from '../../../components/setting_text'; import SettingText from '../../../components/setting_text';

View File

@@ -47,7 +47,7 @@ class Links extends PureComponent {
return ( return (
<div className='explore__links scrollable scrollable--flex'> <div className='explore__links scrollable scrollable--flex'>
<div className='empty-column-indicator'> <div className='empty-column-indicator'>
<FormattedMessage id='empty_column.explore_statuses' defaultMessage='Nothing is trending right now. Check back later!' /> <FormattedMessage id='empty_column.explore_statuses' defaultMessage='Nothing is trending right now. Check back later!' tagName='span' />
</div> </div>
</div> </div>
); );

View File

@@ -45,7 +45,7 @@ class Suggestions extends PureComponent {
return ( return (
<div className='explore__suggestions scrollable scrollable--flex'> <div className='explore__suggestions scrollable scrollable--flex'>
<div className='empty-column-indicator'> <div className='empty-column-indicator'>
<FormattedMessage id='empty_column.explore_statuses' defaultMessage='Nothing is trending right now. Check back later!' /> <FormattedMessage id='empty_column.explore_statuses' defaultMessage='Nothing is trending right now. Check back later!' tagName='span' />
</div> </div>
</div> </div>
); );

View File

@@ -46,7 +46,7 @@ class Tags extends PureComponent {
return ( return (
<div className='explore__links scrollable scrollable--flex'> <div className='explore__links scrollable scrollable--flex'>
<div className='empty-column-indicator'> <div className='empty-column-indicator'>
<FormattedMessage id='empty_column.explore_statuses' defaultMessage='Nothing is trending right now. Check back later!' /> <FormattedMessage id='empty_column.explore_statuses' defaultMessage='Nothing is trending right now. Check back later!' tagName='span' />
</div> </div>
</div> </div>
); );

View File

@@ -1,6 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, FormattedMessage } from 'react-intl';
import { Helmet } from 'react-helmet'; 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 { Account } from 'flavours/glitch/components/account';
import ColumnHeader from 'flavours/glitch/components/column_header'; import ColumnHeader from 'flavours/glitch/components/column_header';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { injectIntl } from '@/flavours/glitch/components/intl';
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
import ScrollableList from 'flavours/glitch/components/scrollable_list'; import ScrollableList from 'flavours/glitch/components/scrollable_list';
import Column from 'flavours/glitch/features/ui/components/column'; import Column from 'flavours/glitch/features/ui/components/column';

View File

@@ -1,7 +1,7 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, FormattedMessage } from 'react-intl';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@@ -9,6 +9,7 @@ import fuzzysort from 'fuzzysort';
import AddIcon from '@/material-icons/400-24px/add.svg?react'; import AddIcon from '@/material-icons/400-24px/add.svg?react';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { injectIntl } from '@/flavours/glitch/components/intl';
import { toServerSideType } from 'flavours/glitch/utils/filters'; import { toServerSideType } from 'flavours/glitch/utils/filters';
import { loupeIcon, deleteIcon } from 'flavours/glitch/utils/icons'; import { loupeIcon, deleteIcon } from 'flavours/glitch/utils/icons';

View File

@@ -1,6 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; 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 { Avatar } from '@/flavours/glitch/components/avatar';
import { DisplayName } from '@/flavours/glitch/components/display_name'; import { DisplayName } from '@/flavours/glitch/components/display_name';
import { IconButton } from '@/flavours/glitch/components/icon_button'; import { IconButton } from '@/flavours/glitch/components/icon_button';
import { injectIntl } from '@/flavours/glitch/components/intl';
import { EmojiHTML } from '@/flavours/glitch/components/emoji/html'; import { EmojiHTML } from '@/flavours/glitch/components/emoji/html';
import { Permalink } from '@/flavours/glitch/components/permalink'; import { Permalink } from '@/flavours/glitch/components/permalink';

View File

@@ -1,6 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, FormattedMessage } from 'react-intl';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
@@ -11,6 +11,7 @@ import { connect } from 'react-redux';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react'; 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 { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts';
import ScrollableList from '../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list';

View File

@@ -66,6 +66,7 @@ const Followers: FC = () => {
<FormattedMessage <FormattedMessage
id='followers.hide_other_followers' id='followers.hide_other_followers'
defaultMessage='This user has chosen to not make their other followers visible' defaultMessage='This user has chosen to not make their other followers visible'
tagName='span'
/> />
</div> </div>
); );

View File

@@ -68,6 +68,7 @@ const Followers: FC = () => {
<FormattedMessage <FormattedMessage
id='following.hide_other_following' id='following.hide_other_following'
defaultMessage='This user has chosen to not make the rest of who they follow visible' defaultMessage='This user has chosen to not make the rest of who they follow visible'
tagName='span'
/> />
</div> </div>
); );

View File

@@ -1,7 +1,7 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
@@ -11,6 +11,8 @@ import Toggle from 'react-toggle';
import { maxFeedHashtags } from 'flavours/glitch/initial_state'; import { maxFeedHashtags } from 'flavours/glitch/initial_state';
import { injectIntl } from '@/flavours/glitch/components/intl';
import SettingToggle from '../../notifications/components/setting_toggle'; import SettingToggle from '../../notifications/components/setting_toggle';
const messages = defineMessages({ const messages = defineMessages({

View File

@@ -115,7 +115,7 @@ const Source: React.FC<{ id: ApiSuggestionSourceJSON }> = ({ id }) => {
title={hint} title={hint}
> >
<Icon id='' icon={InfoIcon} /> <Icon id='' icon={InfoIcon} />
{label} <span>{label}</span>
</div> </div>
); );
}; };

View File

@@ -1,7 +1,7 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, FormattedMessage } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { Helmet } from 'react-helmet'; 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 CampaignIcon from '@/material-icons/400-24px/campaign.svg?react';
import HomeIcon from '@/material-icons/400-24px/home-fill.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 { SymbolLogo } from 'flavours/glitch/components/logo';
import { fetchAnnouncements, toggleShowAnnouncements } from 'flavours/glitch/actions/announcements'; import { fetchAnnouncements, toggleShowAnnouncements } from 'flavours/glitch/actions/announcements';
import { IconWithBadge } from 'flavours/glitch/components/icon_with_badge'; import { IconWithBadge } from 'flavours/glitch/components/icon_with_badge';

View File

@@ -1,6 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, FormattedMessage } from 'react-intl';
import { Helmet } from 'react-helmet'; 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 InfoIcon from '@/material-icons/400-24px/info.svg?react';
import Column from 'flavours/glitch/components/column'; import Column from 'flavours/glitch/components/column';
import ColumnHeader from 'flavours/glitch/components/column_header'; import ColumnHeader from 'flavours/glitch/components/column_header';
import { injectIntl } from '@/flavours/glitch/components/intl';
const messages = defineMessages({ const messages = defineMessages({
heading: { id: 'keyboard_shortcuts.heading', defaultMessage: 'Keyboard Shortcuts' }, heading: { id: 'keyboard_shortcuts.heading', defaultMessage: 'Keyboard Shortcuts' },

View File

@@ -285,6 +285,7 @@ const ListMembers: React.FC<{
<FormattedMessage <FormattedMessage
id='lists.no_results_found' id='lists.no_results_found'
defaultMessage='No results found.' defaultMessage='No results found.'
tagName='span'
/> />
) )
} }

View File

@@ -2,7 +2,7 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { PureComponent } from 'react'; 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 CloseIcon from '@/material-icons/400-24px/close.svg?react';
import EditIcon from '@/material-icons/400-24px/edit.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 ManufacturingIcon from '@/material-icons/400-24px/manufacturing.svg?react';
import SettingsIcon from '@/material-icons/400-24px/settings-fill.svg?react'; import SettingsIcon from '@/material-icons/400-24px/settings-fill.svg?react';
import WarningIcon from '@/material-icons/400-24px/warning.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 { preferencesLink } from 'flavours/glitch/utils/backend_links';
import LocalSettingsNavigationItem from './item'; import LocalSettingsNavigationItem from './item';

View File

@@ -2,12 +2,13 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; import { defineMessages, FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
// Our imports // Our imports
import { injectIntl } from '@/flavours/glitch/components/intl';
import { expandSpoilers } from 'flavours/glitch/initial_state'; import { expandSpoilers } from 'flavours/glitch/initial_state';
import { preferenceLink } from 'flavours/glitch/utils/backend_links'; import { preferenceLink } from 'flavours/glitch/utils/backend_links';

View File

@@ -1,6 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, FormattedMessage } from 'react-intl';
import { Helmet } from 'react-helmet'; 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 VolumeOffIcon from '@/material-icons/400-24px/volume_off.svg?react';
import { Account } from 'flavours/glitch/components/account'; import { Account } from 'flavours/glitch/components/account';
import { injectIntl } from '@/flavours/glitch/components/intl';
import { fetchMutes, expandMutes } from '../../actions/mutes'; import { fetchMutes, expandMutes } from '../../actions/mutes';
import { LoadingIndicator } from '../../components/loading_indicator'; import { LoadingIndicator } from '../../components/loading_indicator';

View File

@@ -1,6 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; 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 { Avatar } from 'flavours/glitch/components/avatar';
import { DisplayName } from 'flavours/glitch/components/display_name'; import { DisplayName } from 'flavours/glitch/components/display_name';
import { IconButton } from 'flavours/glitch/components/icon_button'; import { IconButton } from 'flavours/glitch/components/icon_button';
import { injectIntl } from '@/flavours/glitch/components/intl';
import { Permalink } from 'flavours/glitch/components/permalink'; import { Permalink } from 'flavours/glitch/components/permalink';
const messages = defineMessages({ const messages = defineMessages({

View File

@@ -1,6 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { injectIntl, FormattedMessage, defineMessages } from 'react-intl'; import { FormattedMessage, defineMessages } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { withRouter } from 'react-router-dom'; 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 { Account } from 'flavours/glitch/components/account';
import { LinkedDisplayName } from '@/flavours/glitch/components/display_name'; import { LinkedDisplayName } from '@/flavours/glitch/components/display_name';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { injectIntl } from '@/flavours/glitch/components/intl';
import { Hotkeys } from 'flavours/glitch/components/hotkeys'; import { Hotkeys } from 'flavours/glitch/components/hotkeys';
import { StatusQuoteManager } from 'flavours/glitch/components/status_quoted'; import { StatusQuoteManager } from 'flavours/glitch/components/status_quoted';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';

View File

@@ -1,11 +1,12 @@
import PropTypes from 'prop-types'; 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 ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { AvatarOverlay } from 'flavours/glitch/components/avatar_overlay'; import { AvatarOverlay } from 'flavours/glitch/components/avatar_overlay';
import { injectIntl } from '@/flavours/glitch/components/intl';
import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp'; import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp';
// This needs to be kept in sync with app/models/report.rb // This needs to be kept in sync with app/models/report.rb

View File

@@ -1,9 +1,10 @@
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages } from 'react-intl';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { openModal } from 'flavours/glitch/actions/modal'; import { openModal } from 'flavours/glitch/actions/modal';
import { fetchNotifications , setNotificationsFilter } from 'flavours/glitch/actions/notification_groups'; import { fetchNotifications , setNotificationsFilter } from 'flavours/glitch/actions/notification_groups';
import { injectIntl } from '@/flavours/glitch/components/intl';
import { showAlert } from '../../../actions/alerts'; import { showAlert } from '../../../actions/alerts';
import { requestBrowserPermission } from '../../../actions/notifications'; import { requestBrowserPermission } from '../../../actions/notifications';

View File

@@ -126,7 +126,7 @@ export const NotificationGroupWithStatus: React.FC<{
</div> </div>
<div className='notification-group__main__header__label'> <div className='notification-group__main__header__label'>
{label} <span>{label}</span>
{timestamp && ( {timestamp && (
<> <>
<span className='notification-group__main__header__label-separator'> <span className='notification-group__main__header__label-separator'>

View File

@@ -1,6 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages } from 'react-intl';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
@@ -9,6 +9,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import PushPinIcon from '@/material-icons/400-24px/push_pin.svg?react'; 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 { getStatusList } from 'flavours/glitch/selectors';
import { fetchPinnedStatuses } from '../../actions/pin_statuses'; import { fetchPinnedStatuses } from '../../actions/pin_statuses';

View File

@@ -1,10 +1,12 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { injectIntl } from '@/flavours/glitch/components/intl';
import SettingText from '../../../components/setting_text'; import SettingText from '../../../components/setting_text';
import SettingToggle from '../../notifications/components/setting_toggle'; import SettingToggle from '../../notifications/components/setting_toggle';

View File

@@ -1,7 +1,7 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, FormattedMessage } from 'react-intl';
import { Helmet } from 'react-helmet'; 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 PublicIcon from '@/material-icons/400-24px/public.svg?react';
import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner'; import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner';
import { injectIntl } from '@/flavours/glitch/components/intl';
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { domain, localLiveFeedAccess, remoteLiveFeedAccess } from 'flavours/glitch/initial_state'; import { domain, localLiveFeedAccess, remoteLiveFeedAccess } from 'flavours/glitch/initial_state';
import { canViewFeed } from 'flavours/glitch/permissions'; import { canViewFeed } from 'flavours/glitch/permissions';

View File

@@ -1,6 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, FormattedMessage } from 'react-intl';
import { Helmet } from 'react-helmet'; 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 RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
import { Account } from 'flavours/glitch/components/account'; import { Account } from 'flavours/glitch/components/account';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { injectIntl } from '@/flavours/glitch/components/intl';
import { fetchReblogs, expandReblogs } from '../../actions/interactions'; import { fetchReblogs, expandReblogs } from '../../actions/interactions';
import ColumnHeader from '../../components/column_header'; import ColumnHeader from '../../components/column_header';

View File

@@ -1,13 +1,14 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, FormattedMessage } from 'react-intl';
import { List as ImmutableList } from 'immutable'; import { List as ImmutableList } from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Button } from 'flavours/glitch/components/button'; import { Button } from 'flavours/glitch/components/button';
import { injectIntl } from '@/flavours/glitch/components/intl';
import Option from './components/option'; import Option from './components/option';

View File

@@ -194,6 +194,7 @@ const Comment: React.FC<Props> = ({
id='report.forward' id='report.forward'
defaultMessage='Forward to {target}' defaultMessage='Forward to {target}'
values={{ target: domain }} values={{ target: domain }}
tagName='span'
/> />
</label> </label>
))} ))}

View File

@@ -1,7 +1,7 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux'; 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 ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react';
import StarIcon from '@/material-icons/400-24px/star-fill.svg?react'; import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
import StarBorderIcon from '@/material-icons/400-24px/star.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 { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions';
import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links'; import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links';

View File

@@ -1,6 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { Helmet } from 'react-helmet'; 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 VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react';
import { Hotkeys } from 'flavours/glitch/components/hotkeys'; import { Hotkeys } from 'flavours/glitch/components/hotkeys';
import { Icon } from 'flavours/glitch/components/icon'; import { Icon } from 'flavours/glitch/components/icon';
import { injectIntl } from '@/flavours/glitch/components/intl';
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
import { ScrollContainer } from 'flavours/glitch/containers/scroll_container'; import { ScrollContainer } from 'flavours/glitch/containers/scroll_container';
import BundleColumnError from 'flavours/glitch/features/ui/components/bundle_column_error'; import BundleColumnError from 'flavours/glitch/features/ui/components/bundle_column_error';

View File

@@ -1,6 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; import { defineMessages, FormattedMessage } from 'react-intl';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { is, List as ImmutableList, Set as ImmutableSet } from 'immutable'; 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 { followAccount } from 'flavours/glitch/actions/accounts';
import { Button } from 'flavours/glitch/components/button'; import { Button } from 'flavours/glitch/components/button';
import { IconButton } from 'flavours/glitch/components/icon_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 Option from 'flavours/glitch/features/report/components/option';
import { languages as preloadedLanguages } from 'flavours/glitch/initial_state'; import { languages as preloadedLanguages } from 'flavours/glitch/initial_state';

View File

@@ -1,7 +1,7 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { PureComponent } from 'react';
import { injectIntl, FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
@@ -9,6 +9,7 @@ import { Link } from 'react-router-dom';
import { Button } from 'flavours/glitch/components/button'; import { Button } from 'flavours/glitch/components/button';
import Column from 'flavours/glitch/components/column'; import Column from 'flavours/glitch/components/column';
import { injectIntl } from '@/flavours/glitch/components/intl';
import { GIF } from 'flavours/glitch/components/gif'; import { GIF } from 'flavours/glitch/components/gif';
class CopyButton extends PureComponent { class CopyButton extends PureComponent {

Some files were not shown because too many files have changed in this diff Show More