Merge commit '7f16397f3c37a8e378239974b73afbfe2b6e6844' into glitch-soc/merge-upstream
Conflicts: - `lib/mastodon/version.rb`: Upstream bumped the mastodon API version, glitch-soc has a change on an adjacent line adding a glitch API version.
This commit is contained in:
2
Gemfile
2
Gemfile
@@ -5,7 +5,7 @@ ruby '>= 3.2.0', '< 3.5.0'
|
|||||||
|
|
||||||
gem 'propshaft'
|
gem 'propshaft'
|
||||||
gem 'puma', '~> 7.0'
|
gem 'puma', '~> 7.0'
|
||||||
gem 'rails', '~> 8.0'
|
gem 'rails', '~> 8.1.0'
|
||||||
gem 'thor', '~> 1.2'
|
gem 'thor', '~> 1.2'
|
||||||
|
|
||||||
gem 'dotenv'
|
gem 'dotenv'
|
||||||
|
|||||||
147
Gemfile.lock
147
Gemfile.lock
@@ -10,29 +10,31 @@ GIT
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actioncable (8.0.3)
|
action_text-trix (2.1.16)
|
||||||
actionpack (= 8.0.3)
|
railties
|
||||||
activesupport (= 8.0.3)
|
actioncable (8.1.2)
|
||||||
|
actionpack (= 8.1.2)
|
||||||
|
activesupport (= 8.1.2)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
zeitwerk (~> 2.6)
|
zeitwerk (~> 2.6)
|
||||||
actionmailbox (8.0.3)
|
actionmailbox (8.1.2)
|
||||||
actionpack (= 8.0.3)
|
actionpack (= 8.1.2)
|
||||||
activejob (= 8.0.3)
|
activejob (= 8.1.2)
|
||||||
activerecord (= 8.0.3)
|
activerecord (= 8.1.2)
|
||||||
activestorage (= 8.0.3)
|
activestorage (= 8.1.2)
|
||||||
activesupport (= 8.0.3)
|
activesupport (= 8.1.2)
|
||||||
mail (>= 2.8.0)
|
mail (>= 2.8.0)
|
||||||
actionmailer (8.0.3)
|
actionmailer (8.1.2)
|
||||||
actionpack (= 8.0.3)
|
actionpack (= 8.1.2)
|
||||||
actionview (= 8.0.3)
|
actionview (= 8.1.2)
|
||||||
activejob (= 8.0.3)
|
activejob (= 8.1.2)
|
||||||
activesupport (= 8.0.3)
|
activesupport (= 8.1.2)
|
||||||
mail (>= 2.8.0)
|
mail (>= 2.8.0)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
actionpack (8.0.3)
|
actionpack (8.1.2)
|
||||||
actionview (= 8.0.3)
|
actionview (= 8.1.2)
|
||||||
activesupport (= 8.0.3)
|
activesupport (= 8.1.2)
|
||||||
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)
|
||||||
@@ -40,15 +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.0.3)
|
actiontext (8.1.2)
|
||||||
actionpack (= 8.0.3)
|
action_text-trix (~> 2.1.15)
|
||||||
activerecord (= 8.0.3)
|
actionpack (= 8.1.2)
|
||||||
activestorage (= 8.0.3)
|
activerecord (= 8.1.2)
|
||||||
activesupport (= 8.0.3)
|
activestorage (= 8.1.2)
|
||||||
|
activesupport (= 8.1.2)
|
||||||
globalid (>= 0.6.0)
|
globalid (>= 0.6.0)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (8.0.3)
|
actionview (8.1.2)
|
||||||
activesupport (= 8.0.3)
|
activesupport (= 8.1.2)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.11)
|
erubi (~> 1.11)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
@@ -58,29 +61,29 @@ 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.0.3)
|
activejob (8.1.2)
|
||||||
activesupport (= 8.0.3)
|
activesupport (= 8.1.2)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (8.0.3)
|
activemodel (8.1.2)
|
||||||
activesupport (= 8.0.3)
|
activesupport (= 8.1.2)
|
||||||
activerecord (8.0.3)
|
activerecord (8.1.2)
|
||||||
activemodel (= 8.0.3)
|
activemodel (= 8.1.2)
|
||||||
activesupport (= 8.0.3)
|
activesupport (= 8.1.2)
|
||||||
timeout (>= 0.4.0)
|
timeout (>= 0.4.0)
|
||||||
activestorage (8.0.3)
|
activestorage (8.1.2)
|
||||||
actionpack (= 8.0.3)
|
actionpack (= 8.1.2)
|
||||||
activejob (= 8.0.3)
|
activejob (= 8.1.2)
|
||||||
activerecord (= 8.0.3)
|
activerecord (= 8.1.2)
|
||||||
activesupport (= 8.0.3)
|
activesupport (= 8.1.2)
|
||||||
marcel (~> 1.0)
|
marcel (~> 1.0)
|
||||||
activesupport (8.0.3)
|
activesupport (8.1.2)
|
||||||
base64
|
base64
|
||||||
benchmark (>= 0.3)
|
|
||||||
bigdecimal
|
bigdecimal
|
||||||
concurrent-ruby (~> 1.0, >= 1.3.1)
|
concurrent-ruby (~> 1.0, >= 1.3.1)
|
||||||
connection_pool (>= 2.2.5)
|
connection_pool (>= 2.2.5)
|
||||||
drb
|
drb
|
||||||
i18n (>= 1.6, < 2)
|
i18n (>= 1.6, < 2)
|
||||||
|
json
|
||||||
logger (>= 1.4.2)
|
logger (>= 1.4.2)
|
||||||
minitest (>= 5.1)
|
minitest (>= 5.1)
|
||||||
securerandom (>= 0.3)
|
securerandom (>= 0.3)
|
||||||
@@ -96,7 +99,7 @@ GEM
|
|||||||
ast (2.4.3)
|
ast (2.4.3)
|
||||||
attr_required (1.0.2)
|
attr_required (1.0.2)
|
||||||
aws-eventstream (1.4.0)
|
aws-eventstream (1.4.0)
|
||||||
aws-partitions (1.1213.0)
|
aws-partitions (1.1220.0)
|
||||||
aws-sdk-core (3.242.0)
|
aws-sdk-core (3.242.0)
|
||||||
aws-eventstream (~> 1, >= 1.3.0)
|
aws-eventstream (~> 1, >= 1.3.0)
|
||||||
aws-partitions (~> 1, >= 1.992.0)
|
aws-partitions (~> 1, >= 1.992.0)
|
||||||
@@ -105,7 +108,7 @@ GEM
|
|||||||
bigdecimal
|
bigdecimal
|
||||||
jmespath (~> 1, >= 1.6.1)
|
jmespath (~> 1, >= 1.6.1)
|
||||||
logger
|
logger
|
||||||
aws-sdk-kms (1.121.0)
|
aws-sdk-kms (1.122.0)
|
||||||
aws-sdk-core (~> 3, >= 3.241.4)
|
aws-sdk-core (~> 3, >= 3.241.4)
|
||||||
aws-sigv4 (~> 1.5)
|
aws-sigv4 (~> 1.5)
|
||||||
aws-sdk-s3 (1.213.0)
|
aws-sdk-s3 (1.213.0)
|
||||||
@@ -129,7 +132,7 @@ GEM
|
|||||||
binding_of_caller (1.0.1)
|
binding_of_caller (1.0.1)
|
||||||
debug_inspector (>= 1.2.0)
|
debug_inspector (>= 1.2.0)
|
||||||
blurhash (0.1.8)
|
blurhash (0.1.8)
|
||||||
bootsnap (1.22.0)
|
bootsnap (1.23.0)
|
||||||
msgpack (~> 1.2)
|
msgpack (~> 1.2)
|
||||||
brakeman (8.0.2)
|
brakeman (8.0.2)
|
||||||
racc
|
racc
|
||||||
@@ -227,7 +230,7 @@ GEM
|
|||||||
mail (~> 2.7)
|
mail (~> 2.7)
|
||||||
email_validator (2.2.4)
|
email_validator (2.2.4)
|
||||||
activemodel
|
activemodel
|
||||||
erb (6.0.1)
|
erb (6.0.2)
|
||||||
erubi (1.13.1)
|
erubi (1.13.1)
|
||||||
et-orbi (1.4.0)
|
et-orbi (1.4.0)
|
||||||
tzinfo
|
tzinfo
|
||||||
@@ -291,7 +294,7 @@ GEM
|
|||||||
activesupport (>= 5.1)
|
activesupport (>= 5.1)
|
||||||
haml (>= 4.0.6)
|
haml (>= 4.0.6)
|
||||||
railties (>= 5.1)
|
railties (>= 5.1)
|
||||||
haml_lint (0.69.0)
|
haml_lint (0.71.0)
|
||||||
haml (>= 5.0)
|
haml (>= 5.0)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
rainbow
|
rainbow
|
||||||
@@ -447,17 +450,18 @@ GEM
|
|||||||
mime-types (3.7.0)
|
mime-types (3.7.0)
|
||||||
logger
|
logger
|
||||||
mime-types-data (~> 3.2025, >= 3.2025.0507)
|
mime-types-data (~> 3.2025, >= 3.2025.0507)
|
||||||
mime-types-data (3.2026.0203)
|
mime-types-data (3.2026.0224)
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
mini_portile2 (2.8.9)
|
mini_portile2 (2.8.9)
|
||||||
minitest (6.0.1)
|
minitest (6.0.2)
|
||||||
|
drb (~> 2.0)
|
||||||
prism (~> 1.5)
|
prism (~> 1.5)
|
||||||
msgpack (1.8.0)
|
msgpack (1.8.0)
|
||||||
multi_json (1.19.1)
|
multi_json (1.19.1)
|
||||||
mutex_m (0.3.0)
|
mutex_m (0.3.0)
|
||||||
net-http (0.6.0)
|
net-http (0.6.0)
|
||||||
uri
|
uri
|
||||||
net-imap (0.6.2)
|
net-imap (0.6.3)
|
||||||
date
|
date
|
||||||
net-protocol
|
net-protocol
|
||||||
net-ldap (0.20.0)
|
net-ldap (0.20.0)
|
||||||
@@ -590,7 +594,7 @@ GEM
|
|||||||
ox (2.14.23)
|
ox (2.14.23)
|
||||||
bigdecimal (>= 3.0)
|
bigdecimal (>= 3.0)
|
||||||
parallel (1.27.0)
|
parallel (1.27.0)
|
||||||
parser (3.3.10.1)
|
parser (3.3.10.2)
|
||||||
ast (~> 2.4.1)
|
ast (~> 2.4.1)
|
||||||
racc
|
racc
|
||||||
parslet (2.0.0)
|
parslet (2.0.0)
|
||||||
@@ -657,33 +661,33 @@ GEM
|
|||||||
rack (>= 1.3)
|
rack (>= 1.3)
|
||||||
rackup (2.3.1)
|
rackup (2.3.1)
|
||||||
rack (>= 3)
|
rack (>= 3)
|
||||||
rails (8.0.3)
|
rails (8.1.2)
|
||||||
actioncable (= 8.0.3)
|
actioncable (= 8.1.2)
|
||||||
actionmailbox (= 8.0.3)
|
actionmailbox (= 8.1.2)
|
||||||
actionmailer (= 8.0.3)
|
actionmailer (= 8.1.2)
|
||||||
actionpack (= 8.0.3)
|
actionpack (= 8.1.2)
|
||||||
actiontext (= 8.0.3)
|
actiontext (= 8.1.2)
|
||||||
actionview (= 8.0.3)
|
actionview (= 8.1.2)
|
||||||
activejob (= 8.0.3)
|
activejob (= 8.1.2)
|
||||||
activemodel (= 8.0.3)
|
activemodel (= 8.1.2)
|
||||||
activerecord (= 8.0.3)
|
activerecord (= 8.1.2)
|
||||||
activestorage (= 8.0.3)
|
activestorage (= 8.1.2)
|
||||||
activesupport (= 8.0.3)
|
activesupport (= 8.1.2)
|
||||||
bundler (>= 1.15.0)
|
bundler (>= 1.15.0)
|
||||||
railties (= 8.0.3)
|
railties (= 8.1.2)
|
||||||
rails-dom-testing (2.3.0)
|
rails-dom-testing (2.3.0)
|
||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
minitest
|
minitest
|
||||||
nokogiri (>= 1.6)
|
nokogiri (>= 1.6)
|
||||||
rails-html-sanitizer (1.6.2)
|
rails-html-sanitizer (1.7.0)
|
||||||
loofah (~> 2.21)
|
loofah (~> 2.25)
|
||||||
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
||||||
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.0.3)
|
railties (8.1.2)
|
||||||
actionpack (= 8.0.3)
|
actionpack (= 8.1.2)
|
||||||
activesupport (= 8.0.3)
|
activesupport (= 8.1.2)
|
||||||
irb (~> 1.13)
|
irb (~> 1.13)
|
||||||
rackup (>= 1.0.0)
|
rackup (>= 1.0.0)
|
||||||
rake (>= 12.2)
|
rake (>= 12.2)
|
||||||
@@ -701,7 +705,7 @@ GEM
|
|||||||
readline (~> 0.0)
|
readline (~> 0.0)
|
||||||
rdf-normalize (0.7.0)
|
rdf-normalize (0.7.0)
|
||||||
rdf (~> 3.3)
|
rdf (~> 3.3)
|
||||||
rdoc (7.1.0)
|
rdoc (7.2.0)
|
||||||
erb
|
erb
|
||||||
psych (>= 4.0.0)
|
psych (>= 4.0.0)
|
||||||
tsort
|
tsort
|
||||||
@@ -792,8 +796,9 @@ GEM
|
|||||||
lint_roller (~> 1.1)
|
lint_roller (~> 1.1)
|
||||||
rubocop (~> 1.72, >= 1.72.1)
|
rubocop (~> 1.72, >= 1.72.1)
|
||||||
rubocop-rspec (~> 3.5)
|
rubocop-rspec (~> 3.5)
|
||||||
ruby-prof (1.7.2)
|
ruby-prof (2.0.2)
|
||||||
base64
|
base64
|
||||||
|
ostruct
|
||||||
ruby-progressbar (1.13.0)
|
ruby-progressbar (1.13.0)
|
||||||
ruby-saml (1.18.1)
|
ruby-saml (1.18.1)
|
||||||
nokogiri (>= 1.13.10)
|
nokogiri (>= 1.13.10)
|
||||||
@@ -903,7 +908,7 @@ GEM
|
|||||||
vite_rails (3.0.20)
|
vite_rails (3.0.20)
|
||||||
railties (>= 5.1, < 9)
|
railties (>= 5.1, < 9)
|
||||||
vite_ruby (~> 3.0, >= 3.2.2)
|
vite_ruby (~> 3.0, >= 3.2.2)
|
||||||
vite_ruby (3.9.2)
|
vite_ruby (3.9.3)
|
||||||
dry-cli (>= 0.7, < 2)
|
dry-cli (>= 0.7, < 2)
|
||||||
logger (~> 1.6)
|
logger (~> 1.6)
|
||||||
mutex_m
|
mutex_m
|
||||||
@@ -936,7 +941,7 @@ GEM
|
|||||||
xorcist (1.1.3)
|
xorcist (1.1.3)
|
||||||
xpath (3.2.0)
|
xpath (3.2.0)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
zeitwerk (2.7.4)
|
zeitwerk (2.7.5)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
ruby
|
ruby
|
||||||
@@ -1050,7 +1055,7 @@ DEPENDENCIES
|
|||||||
rack-attack (~> 6.6)
|
rack-attack (~> 6.6)
|
||||||
rack-cors
|
rack-cors
|
||||||
rack-test (~> 2.1)
|
rack-test (~> 2.1)
|
||||||
rails (~> 8.0)
|
rails (~> 8.1.0)
|
||||||
rails-i18n (~> 8.0)
|
rails-i18n (~> 8.0)
|
||||||
rdf-normalize (~> 0.5)
|
rdf-normalize (~> 0.5)
|
||||||
redcarpet (~> 3.6)
|
redcarpet (~> 3.6)
|
||||||
@@ -1100,4 +1105,4 @@ RUBY VERSION
|
|||||||
ruby 3.4.8
|
ruby 3.4.8
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
4.0.6
|
4.0.7
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class ActivityPub::FeatureAuthorizationsController < ActivityPub::BaseController
|
||||||
|
include Authorization
|
||||||
|
|
||||||
|
vary_by -> { 'Signature' if authorized_fetch_mode? }
|
||||||
|
|
||||||
|
before_action :require_account_signature!, if: :authorized_fetch_mode?
|
||||||
|
before_action :set_collection_item
|
||||||
|
|
||||||
|
def show
|
||||||
|
expires_in 30.seconds, public: true if public_fetch_mode?
|
||||||
|
render json: @collection_item, serializer: ActivityPub::FeatureAuthorizationSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def pundit_user
|
||||||
|
signed_request_account
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_collection_item
|
||||||
|
@collection_item = @account.collection_items.accepted.find(params[:id])
|
||||||
|
|
||||||
|
authorize @collection_item.collection, :show?
|
||||||
|
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||||
|
not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -54,7 +54,7 @@ module Admin
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Allow transparently upgrading a domain block
|
# Allow transparently upgrading a domain block
|
||||||
if existing_domain_block.present? && existing_domain_block.domain == TagManager.instance.normalize_domain(@domain_block.domain.strip)
|
if existing_domain_block.present? && existing_domain_block.domain == TagManager.instance.normalize_domain(@domain_block.domain)
|
||||||
@domain_block = existing_domain_block
|
@domain_block = existing_domain_block
|
||||||
@domain_block.assign_attributes(resource_params)
|
@domain_block.assign_attributes(resource_params)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -34,8 +34,11 @@ class Admin::Instances::ModerationNotesController < Admin::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def set_instance
|
def set_instance
|
||||||
domain = params[:instance_id]&.strip
|
@instance = Instance.find_or_initialize_by(domain: normalized_domain)
|
||||||
@instance = Instance.find_or_initialize_by(domain: TagManager.instance.normalize_domain(domain))
|
end
|
||||||
|
|
||||||
|
def normalized_domain
|
||||||
|
TagManager.instance.normalize_domain(params[:instance_id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_instance_note
|
def set_instance_note
|
||||||
|
|||||||
@@ -55,8 +55,11 @@ module Admin
|
|||||||
private
|
private
|
||||||
|
|
||||||
def set_instance
|
def set_instance
|
||||||
domain = params[:id]&.strip
|
@instance = Instance.find_or_initialize_by(domain: normalized_domain)
|
||||||
@instance = Instance.find_or_initialize_by(domain: TagManager.instance.normalize_domain(domain))
|
end
|
||||||
|
|
||||||
|
def normalized_domain
|
||||||
|
TagManager.instance.normalize_domain(params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_instances
|
def set_instances
|
||||||
|
|||||||
@@ -47,10 +47,6 @@ class Api::V1::Peers::SearchController < Api::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def normalized_domain
|
def normalized_domain
|
||||||
TagManager.instance.normalize_domain(query_value)
|
TagManager.instance.normalize_domain(params[:q])
|
||||||
end
|
|
||||||
|
|
||||||
def query_value
|
|
||||||
params[:q].strip
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -39,6 +39,6 @@ class Api::V1::TagsController < Api::BaseController
|
|||||||
def set_or_create_tag
|
def set_or_create_tag
|
||||||
return not_found unless Tag::HASHTAG_NAME_RE.match?(params[:id])
|
return not_found unless Tag::HASHTAG_NAME_RE.match?(params[:id])
|
||||||
|
|
||||||
@tag = Tag.find_normalized(params[:id]) || Tag.new(name: Tag.normalize(params[:id]), display_name: params[:id])
|
@tag = Tag.find_normalized(params[:id]) || Tag.new(name: params[:id], display_name: params[:id])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ exports[`<AvatarOverlay > renders a overlay avatar 1`] = `
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="alice"
|
alt="alice"
|
||||||
|
onError={[Function]}
|
||||||
src="/static/alice.jpg"
|
src="/static/alice.jpg"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -44,6 +45,7 @@ exports[`<AvatarOverlay > renders a overlay avatar 1`] = `
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="eve@blackhat.lair"
|
alt="eve@blackhat.lair"
|
||||||
|
onError={[Function]}
|
||||||
src="/static/eve.jpg"
|
src="/static/eve.jpg"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { useHovering } from 'mastodon/hooks/useHovering';
|
|||||||
import { autoPlayGif } from 'mastodon/initial_state';
|
import { autoPlayGif } from 'mastodon/initial_state';
|
||||||
import type { Account } from 'mastodon/models/account';
|
import type { Account } from 'mastodon/models/account';
|
||||||
|
|
||||||
|
import { useAccount } from '../hooks/useAccount';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
account:
|
account:
|
||||||
| Pick<Account, 'id' | 'acct' | 'avatar' | 'avatar_static'>
|
| Pick<Account, 'id' | 'acct' | 'avatar' | 'avatar_static'>
|
||||||
@@ -91,3 +93,10 @@ export const Avatar: React.FC<Props> = ({
|
|||||||
|
|
||||||
return avatar;
|
return avatar;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const AvatarById: React.FC<
|
||||||
|
{ accountId: string } & Omit<Props, 'account'>
|
||||||
|
> = ({ accountId, ...otherProps }) => {
|
||||||
|
const account = useAccount(accountId);
|
||||||
|
return <Avatar account={account} {...otherProps} />;
|
||||||
|
};
|
||||||
|
|||||||
@@ -10,6 +10,14 @@ interface Props {
|
|||||||
overlaySize?: number;
|
overlaySize?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleImgLoadError = (error: { currentTarget: HTMLElement }) => {
|
||||||
|
//
|
||||||
|
// When the img tag fails to load the image, set the img tag to display: none. This prevents the
|
||||||
|
// alt-text from overrunning the containing div.
|
||||||
|
//
|
||||||
|
error.currentTarget.style.display = 'none';
|
||||||
|
};
|
||||||
|
|
||||||
export const AvatarOverlay: React.FC<Props> = ({
|
export const AvatarOverlay: React.FC<Props> = ({
|
||||||
account,
|
account,
|
||||||
friend,
|
friend,
|
||||||
@@ -38,7 +46,13 @@ export const AvatarOverlay: React.FC<Props> = ({
|
|||||||
className='account__avatar'
|
className='account__avatar'
|
||||||
style={{ width: `${baseSize}px`, height: `${baseSize}px` }}
|
style={{ width: `${baseSize}px`, height: `${baseSize}px` }}
|
||||||
>
|
>
|
||||||
{accountSrc && <img src={accountSrc} alt={account?.get('acct')} />}
|
{accountSrc && (
|
||||||
|
<img
|
||||||
|
src={accountSrc}
|
||||||
|
alt={account?.get('acct')}
|
||||||
|
onError={handleImgLoadError}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='account__avatar-overlay-overlay'>
|
<div className='account__avatar-overlay-overlay'>
|
||||||
@@ -46,7 +60,13 @@ export const AvatarOverlay: React.FC<Props> = ({
|
|||||||
className='account__avatar'
|
className='account__avatar'
|
||||||
style={{ width: `${overlaySize}px`, height: `${overlaySize}px` }}
|
style={{ width: `${overlaySize}px`, height: `${overlaySize}px` }}
|
||||||
>
|
>
|
||||||
{friendSrc && <img src={friendSrc} alt={friend?.get('acct')} />}
|
{friendSrc && (
|
||||||
|
<img
|
||||||
|
src={friendSrc}
|
||||||
|
alt={friend?.get('acct')}
|
||||||
|
onError={handleImgLoadError}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,8 +19,9 @@ const messages = defineMessages({
|
|||||||
export const CopyIconButton: React.FC<{
|
export const CopyIconButton: React.FC<{
|
||||||
title: string;
|
title: string;
|
||||||
value: string;
|
value: string;
|
||||||
className: string;
|
className?: string;
|
||||||
}> = ({ title, value, className }) => {
|
'aria-describedby'?: string;
|
||||||
|
}> = ({ title, value, className, 'aria-describedby': ariaDescribedBy }) => {
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
@@ -38,8 +39,9 @@ export const CopyIconButton: React.FC<{
|
|||||||
className={classNames(className, copied ? 'copied' : 'copyable')}
|
className={classNames(className, copied ? 'copied' : 'copyable')}
|
||||||
title={title}
|
title={title}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
icon=''
|
icon='copy-icon'
|
||||||
iconComponent={ContentCopyIcon}
|
iconComponent={ContentCopyIcon}
|
||||||
|
aria-describedby={ariaDescribedBy}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
.wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
padding-inline-end: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyButton {
|
||||||
|
position: absolute;
|
||||||
|
inset-inline-end: 0;
|
||||||
|
top: 0;
|
||||||
|
padding: 9px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import { forwardRef, useCallback, useRef } from 'react';
|
||||||
|
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import { CopyIconButton } from 'mastodon/components/copy_icon_button';
|
||||||
|
|
||||||
|
import classes from './copy_link_field.module.scss';
|
||||||
|
import { FormFieldWrapper } from './form_field_wrapper';
|
||||||
|
import type { CommonFieldWrapperProps } from './form_field_wrapper';
|
||||||
|
import { TextInput } from './text_input_field';
|
||||||
|
import type { TextInputProps } from './text_input_field';
|
||||||
|
|
||||||
|
interface CopyLinkFieldProps extends CommonFieldWrapperProps, TextInputProps {
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A read-only text field with a button for copying the field value
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const CopyLinkField = forwardRef<HTMLInputElement, CopyLinkFieldProps>(
|
||||||
|
(
|
||||||
|
{ id, label, hint, hasError, value, required, className, ...otherProps },
|
||||||
|
ref,
|
||||||
|
) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const inputRef = useRef<HTMLInputElement | null>();
|
||||||
|
const handleFocus = useCallback(() => {
|
||||||
|
inputRef.current?.select();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const mergeRefs = useCallback(
|
||||||
|
(element: HTMLInputElement | null) => {
|
||||||
|
inputRef.current = element;
|
||||||
|
if (typeof ref === 'function') {
|
||||||
|
ref(element);
|
||||||
|
} else if (ref) {
|
||||||
|
ref.current = element;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[ref],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormFieldWrapper
|
||||||
|
label={label}
|
||||||
|
hint={hint}
|
||||||
|
required={required}
|
||||||
|
hasError={hasError}
|
||||||
|
inputId={id}
|
||||||
|
>
|
||||||
|
{(inputProps) => (
|
||||||
|
<div className={classes.wrapper}>
|
||||||
|
<TextInput
|
||||||
|
readOnly
|
||||||
|
{...otherProps}
|
||||||
|
{...inputProps}
|
||||||
|
ref={mergeRefs}
|
||||||
|
value={value}
|
||||||
|
onFocus={handleFocus}
|
||||||
|
className={classNames(className, classes.input)}
|
||||||
|
/>
|
||||||
|
<CopyIconButton
|
||||||
|
value={value}
|
||||||
|
title={intl.formatMessage({
|
||||||
|
id: 'copy_icon_button.copy_this_text',
|
||||||
|
defaultMessage: 'Copy link to clipboard',
|
||||||
|
})}
|
||||||
|
className={classes.copyButton}
|
||||||
|
aria-describedby={inputProps.id}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</FormFieldWrapper>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
CopyLinkField.displayName = 'CopyLinkField';
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
export { FormFieldWrapper } from './form_field_wrapper';
|
||||||
export { FormStack } from './form_stack';
|
export { FormStack } from './form_stack';
|
||||||
export { Fieldset } from './fieldset';
|
export { Fieldset } from './fieldset';
|
||||||
export { TextInputField, TextInput } from './text_input_field';
|
export { TextInputField, TextInput } from './text_input_field';
|
||||||
@@ -8,6 +9,7 @@ export {
|
|||||||
Combobox,
|
Combobox,
|
||||||
type ComboboxItemState,
|
type ComboboxItemState,
|
||||||
} from './combobox_field';
|
} from './combobox_field';
|
||||||
|
export { CopyLinkField } from './copy_link_field';
|
||||||
export { RadioButtonField, RadioButton } from './radio_button_field';
|
export { RadioButtonField, RadioButton } from './radio_button_field';
|
||||||
export { ToggleField, Toggle } from './toggle_field';
|
export { ToggleField, Toggle } from './toggle_field';
|
||||||
export { SelectField, Select } from './select_field';
|
export { SelectField, Select } from './select_field';
|
||||||
|
|||||||
56
app/javascript/mastodon/components/modal_shell/index.tsx
Normal file
56
app/javascript/mastodon/components/modal_shell/index.tsx
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
interface SimpleComponentProps {
|
||||||
|
className?: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ModalShellComponent extends React.FC<SimpleComponentProps> {
|
||||||
|
Body: React.FC<SimpleComponentProps>;
|
||||||
|
Actions: React.FC<SimpleComponentProps>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ModalShell: ModalShellComponent = ({ children, className }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
'modal-root__modal',
|
||||||
|
'safety-action-modal',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ModalShellBody: ModalShellComponent['Body'] = ({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className='safety-action-modal__top'>
|
||||||
|
<div
|
||||||
|
className={classNames('safety-action-modal__confirmation', className)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ModalShellActions: ModalShellComponent['Actions'] = ({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className='safety-action-modal__bottom'>
|
||||||
|
<div className={classNames('safety-action-modal__actions', className)}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ModalShell.Body = ModalShellBody;
|
||||||
|
ModalShell.Actions = ModalShellActions;
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
import type { ChangeEventHandler, FC } from 'react';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import { Callout } from '@/mastodon/components/callout';
|
||||||
|
import { ToggleField } from '@/mastodon/components/form_fields';
|
||||||
|
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
|
||||||
|
import { patchProfile } from '@/mastodon/reducers/slices/profile_edit';
|
||||||
|
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||||
|
|
||||||
|
import type { DialogModalProps } from '../../ui/components/dialog_modal';
|
||||||
|
import { DialogModal } from '../../ui/components/dialog_modal';
|
||||||
|
import { messages } from '../index';
|
||||||
|
import classes from '../styles.module.scss';
|
||||||
|
|
||||||
|
export const ProfileDisplayModal: FC<DialogModalProps> = ({ onClose }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const { profile, isPending } = useAppSelector((state) => state.profileEdit);
|
||||||
|
const serverName = useAppSelector(
|
||||||
|
(state) => state.meta.get('domain') as string,
|
||||||
|
);
|
||||||
|
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const handleToggleChange: ChangeEventHandler<HTMLInputElement> = useCallback(
|
||||||
|
(event) => {
|
||||||
|
const { name, checked } = event.target;
|
||||||
|
void dispatch(patchProfile({ [name]: checked }));
|
||||||
|
},
|
||||||
|
[dispatch],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!profile) {
|
||||||
|
return <LoadingIndicator />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogModal
|
||||||
|
onClose={onClose}
|
||||||
|
title={intl.formatMessage(messages.profileTabTitle)}
|
||||||
|
noCancelButton
|
||||||
|
>
|
||||||
|
<div className={classes.toggleInputWrapper}>
|
||||||
|
<ToggleField
|
||||||
|
checked={profile.showMedia}
|
||||||
|
onChange={handleToggleChange}
|
||||||
|
disabled={isPending}
|
||||||
|
name='show_media'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='account_edit.profile_tab.show_media.title'
|
||||||
|
defaultMessage='Show ‘Media’ tab'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
hint={
|
||||||
|
<FormattedMessage
|
||||||
|
id='account_edit.profile_tab.show_media.description'
|
||||||
|
defaultMessage='‘Media’ is an optional tab that shows your posts containing images or videos.'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ToggleField
|
||||||
|
checked={profile.showMediaReplies}
|
||||||
|
onChange={handleToggleChange}
|
||||||
|
disabled={!profile.showMedia || isPending}
|
||||||
|
name='show_media_replies'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='account_edit.profile_tab.show_media_replies.title'
|
||||||
|
defaultMessage='Include replies on ‘Media’ tab'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
hint={
|
||||||
|
<FormattedMessage
|
||||||
|
id='account_edit.profile_tab.show_media_replies.description'
|
||||||
|
defaultMessage='When enabled, Media tab shows both your posts and replies to other people’s posts.'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ToggleField
|
||||||
|
checked={profile.showFeatured}
|
||||||
|
onChange={handleToggleChange}
|
||||||
|
disabled={isPending}
|
||||||
|
name='show_featured'
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='account_edit.profile_tab.show_featured.title'
|
||||||
|
defaultMessage='Show ‘Featured’ tab'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
hint={
|
||||||
|
<FormattedMessage
|
||||||
|
id='account_edit.profile_tab.show_featured.description'
|
||||||
|
defaultMessage='‘Featured’ is an optional tab where you can showcase other accounts.'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Callout
|
||||||
|
title={
|
||||||
|
<FormattedMessage
|
||||||
|
id='account_edit.profile_tab.hint.title'
|
||||||
|
defaultMessage='Displays still vary'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
icon={false}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='account_edit.profile_tab.hint.description'
|
||||||
|
defaultMessage='These settings customize what users see on {server} in the official apps, but they may not apply to users on other servers and 3rd party apps.'
|
||||||
|
values={{
|
||||||
|
server: serverName,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Callout>
|
||||||
|
</DialogModal>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
|
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
import type { ModalType } from '@/mastodon/actions/modal';
|
import type { ModalType } from '@/mastodon/actions/modal';
|
||||||
import { openModal } from '@/mastodon/actions/modal';
|
import { openModal } from '@/mastodon/actions/modal';
|
||||||
import { Avatar } from '@/mastodon/components/avatar';
|
import { Avatar } from '@/mastodon/components/avatar';
|
||||||
|
import { Button } from '@/mastodon/components/button';
|
||||||
import { CustomEmojiProvider } from '@/mastodon/components/emoji/context';
|
import { CustomEmojiProvider } from '@/mastodon/components/emoji/context';
|
||||||
import { EmojiHTML } from '@/mastodon/components/emoji/html';
|
import { EmojiHTML } from '@/mastodon/components/emoji/html';
|
||||||
import { useElementHandledLink } from '@/mastodon/components/status/handled_link';
|
import { useElementHandledLink } from '@/mastodon/components/status/handled_link';
|
||||||
@@ -25,7 +26,7 @@ import { EditButton } from './components/edit_button';
|
|||||||
import { AccountEditSection } from './components/section';
|
import { AccountEditSection } from './components/section';
|
||||||
import classes from './styles.module.scss';
|
import classes from './styles.module.scss';
|
||||||
|
|
||||||
const messages = defineMessages({
|
export const messages = defineMessages({
|
||||||
columnTitle: {
|
columnTitle: {
|
||||||
id: 'account_edit.column_title',
|
id: 'account_edit.column_title',
|
||||||
defaultMessage: 'Edit Profile',
|
defaultMessage: 'Edit Profile',
|
||||||
@@ -104,6 +105,9 @@ export const AccountEdit: FC = () => {
|
|||||||
const handleBioEdit = useCallback(() => {
|
const handleBioEdit = useCallback(() => {
|
||||||
handleOpenModal('ACCOUNT_EDIT_BIO');
|
handleOpenModal('ACCOUNT_EDIT_BIO');
|
||||||
}, [handleOpenModal]);
|
}, [handleOpenModal]);
|
||||||
|
const handleProfileDisplayEdit = useCallback(() => {
|
||||||
|
handleOpenModal('ACCOUNT_EDIT_PROFILE_DISPLAY');
|
||||||
|
}, [handleOpenModal]);
|
||||||
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const handleFeaturedTagsEdit = useCallback(() => {
|
const handleFeaturedTagsEdit = useCallback(() => {
|
||||||
@@ -193,6 +197,17 @@ export const AccountEdit: FC = () => {
|
|||||||
title={messages.profileTabTitle}
|
title={messages.profileTabTitle}
|
||||||
description={messages.profileTabSubtitle}
|
description={messages.profileTabSubtitle}
|
||||||
showDescription
|
showDescription
|
||||||
|
buttons={
|
||||||
|
<Button
|
||||||
|
className={classes.editButton}
|
||||||
|
onClick={handleProfileDisplayEdit}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id='account_edit.profile_tab.button_label'
|
||||||
|
defaultMessage='Customize'
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</CustomEmojiProvider>
|
</CustomEmojiProvider>
|
||||||
</AccountEditColumn>
|
</AccountEditColumn>
|
||||||
|
|||||||
@@ -90,6 +90,16 @@ textarea.inputText {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.toggleInputWrapper {
|
||||||
|
> div {
|
||||||
|
padding: 12px 0;
|
||||||
|
|
||||||
|
&:not(:first-child) {
|
||||||
|
border-top: 1px solid var(--color-border-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Column component
|
// Column component
|
||||||
|
|
||||||
.column {
|
.column {
|
||||||
|
|||||||
@@ -118,14 +118,14 @@ const RedesignAccountHeaderFields: FC<{ account: Account }> = ({ account }) => {
|
|||||||
<CustomEmojiProvider emojis={emojis}>
|
<CustomEmojiProvider emojis={emojis}>
|
||||||
<dl className={classes.fieldList} ref={wrapperRef}>
|
<dl className={classes.fieldList} ref={wrapperRef}>
|
||||||
{fields.map((field, key) => (
|
{fields.map((field, key) => (
|
||||||
<FieldRow key={key} field={field} htmlHandlers={htmlHandlers} />
|
<FieldCard key={key} field={field} htmlHandlers={htmlHandlers} />
|
||||||
))}
|
))}
|
||||||
</dl>
|
</dl>
|
||||||
</CustomEmojiProvider>
|
</CustomEmojiProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const FieldRow: FC<{
|
const FieldCard: FC<{
|
||||||
htmlHandlers: ReturnType<typeof useElementHandledLink>;
|
htmlHandlers: ReturnType<typeof useElementHandledLink>;
|
||||||
field: AccountField;
|
field: AccountField;
|
||||||
}> = ({ htmlHandlers, field }) => {
|
}> = ({ htmlHandlers, field }) => {
|
||||||
@@ -183,15 +183,14 @@ const FieldRow: FC<{
|
|||||||
ref={wrapperRef}
|
ref={wrapperRef}
|
||||||
>
|
>
|
||||||
{verified_at && (
|
{verified_at && (
|
||||||
<Icon
|
<span
|
||||||
id='verified'
|
|
||||||
icon={IconVerified}
|
|
||||||
className={classes.fieldVerifiedIcon}
|
className={classes.fieldVerifiedIcon}
|
||||||
aria-label={intl.formatMessage(verifyMessage, {
|
title={intl.formatMessage(verifyMessage, {
|
||||||
date: intl.formatDate(verified_at, dateFormatOptions),
|
date: intl.formatDate(verified_at, dateFormatOptions),
|
||||||
})}
|
})}
|
||||||
noFill
|
>
|
||||||
/>
|
<Icon id='verified' icon={IconVerified} noFill />
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
</MiniCard>
|
</MiniCard>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -278,11 +278,17 @@ svg.badgeIcon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.fieldVerifiedIcon {
|
.fieldVerifiedIcon {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
position: absolute;
|
|
||||||
top: 8px;
|
top: 8px;
|
||||||
right: 8px;
|
right: 8px;
|
||||||
|
|
||||||
|
> svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.fieldOverflowButton {
|
.fieldOverflowButton {
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import type { FC } from 'react';
|
|||||||
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import { Button } from '@/mastodon/components/button';
|
||||||
import { EmojiHTML } from '@/mastodon/components/emoji/html';
|
import { EmojiHTML } from '@/mastodon/components/emoji/html';
|
||||||
|
import { ModalShell } from '@/mastodon/components/modal_shell';
|
||||||
|
|
||||||
import type { AccountField } from '../common';
|
import type { AccountField } from '../common';
|
||||||
import { useFieldHtml } from '../hooks/useFieldHtml';
|
import { useFieldHtml } from '../hooks/useFieldHtml';
|
||||||
@@ -16,29 +18,25 @@ export const AccountFieldModal: FC<{
|
|||||||
const handleLabelElement = useFieldHtml(field.nameHasEmojis);
|
const handleLabelElement = useFieldHtml(field.nameHasEmojis);
|
||||||
const handleValueElement = useFieldHtml(field.valueHasEmojis);
|
const handleValueElement = useFieldHtml(field.valueHasEmojis);
|
||||||
return (
|
return (
|
||||||
<div className='modal-root__modal safety-action-modal'>
|
<ModalShell>
|
||||||
<div className='safety-action-modal__top'>
|
<ModalShell.Body>
|
||||||
<div className='safety-action-modal__confirmation'>
|
<EmojiHTML
|
||||||
<EmojiHTML
|
as='h2'
|
||||||
as='p'
|
htmlString={field.name_emojified}
|
||||||
htmlString={field.name_emojified}
|
onElement={handleLabelElement}
|
||||||
onElement={handleLabelElement}
|
/>
|
||||||
/>
|
<EmojiHTML
|
||||||
<EmojiHTML
|
as='p'
|
||||||
as='p'
|
htmlString={field.value_emojified}
|
||||||
htmlString={field.value_emojified}
|
onElement={handleValueElement}
|
||||||
onElement={handleValueElement}
|
className={classes.fieldValue}
|
||||||
className={classes.fieldValue}
|
/>
|
||||||
/>
|
</ModalShell.Body>
|
||||||
</div>
|
<ModalShell.Actions>
|
||||||
</div>
|
<Button onClick={onClose} plain>
|
||||||
<div className='safety-action-modal__bottom'>
|
<FormattedMessage id='lightbox.close' defaultMessage='Close' />
|
||||||
<div className='safety-action-modal__actions'>
|
</Button>
|
||||||
<button onClick={onClose} className='link-button' type='button'>
|
</ModalShell.Actions>
|
||||||
<FormattedMessage id='lightbox.close' defaultMessage='Close' />
|
</ModalShell>
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
border: none;
|
border: none;
|
||||||
background: none;
|
background: none;
|
||||||
padding: 8px 0;
|
padding: 8px 0;
|
||||||
|
font-size: 15px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -41,6 +42,10 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-color-scheme='dark'] & {
|
||||||
|
border: 1px solid var(--color-border-primary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tagsWrapper {
|
.tagsWrapper {
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
--gap: 0.75ch;
|
--gap: 0.75ch;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
gap: var(--gap);
|
gap: var(--gap);
|
||||||
|
|
||||||
& > li:not(:last-child)::after {
|
& > li:not(:last-child)::after {
|
||||||
|
|||||||
@@ -3,18 +3,21 @@ import { useCallback, useEffect } from 'react';
|
|||||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
import { useParams } from 'react-router';
|
import { useLocation, useParams } from 'react-router';
|
||||||
|
|
||||||
|
import { openModal } from '@/mastodon/actions/modal';
|
||||||
import { useRelationship } from '@/mastodon/hooks/useRelationship';
|
import { useRelationship } from '@/mastodon/hooks/useRelationship';
|
||||||
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 { showAlert } from 'mastodon/actions/alerts';
|
|
||||||
import type { ApiCollectionJSON } from 'mastodon/api_types/collections';
|
import type { ApiCollectionJSON } from 'mastodon/api_types/collections';
|
||||||
import { Account } from 'mastodon/components/account';
|
import { Account } from 'mastodon/components/account';
|
||||||
import { Avatar } from 'mastodon/components/avatar';
|
import { Avatar } from 'mastodon/components/avatar';
|
||||||
import { Column } from 'mastodon/components/column';
|
import { Column } from 'mastodon/components/column';
|
||||||
import { ColumnHeader } from 'mastodon/components/column_header';
|
import { ColumnHeader } from 'mastodon/components/column_header';
|
||||||
import { LinkedDisplayName } from 'mastodon/components/display_name';
|
import {
|
||||||
|
DisplayName,
|
||||||
|
LinkedDisplayName,
|
||||||
|
} from 'mastodon/components/display_name';
|
||||||
import { IconButton } from 'mastodon/components/icon_button';
|
import { IconButton } from 'mastodon/components/icon_button';
|
||||||
import ScrollableList from 'mastodon/components/scrollable_list';
|
import ScrollableList from 'mastodon/components/scrollable_list';
|
||||||
import { Tag } from 'mastodon/components/tags/tag';
|
import { Tag } from 'mastodon/components/tags/tag';
|
||||||
@@ -46,32 +49,40 @@ const messages = defineMessages({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const AuthorNote: React.FC<{ id: string }> = ({ id }) => {
|
export const AuthorNote: React.FC<{ id: string; previewMode?: boolean }> = ({
|
||||||
|
id,
|
||||||
|
// When previewMode is enabled, your own display name
|
||||||
|
// will not be replaced with "you"
|
||||||
|
previewMode = false,
|
||||||
|
}) => {
|
||||||
const account = useAccount(id);
|
const account = useAccount(id);
|
||||||
const author = (
|
const author = (
|
||||||
<span className={classes.displayNameWithAvatar}>
|
<span className={classes.displayNameWithAvatar}>
|
||||||
<Avatar size={18} account={account} />
|
<Avatar size={18} account={account} />
|
||||||
<LinkedDisplayName displayProps={{ account, variant: 'simple' }} />
|
{previewMode ? (
|
||||||
|
<DisplayName account={account} variant='simple' />
|
||||||
|
) : (
|
||||||
|
<LinkedDisplayName displayProps={{ account, variant: 'simple' }} />
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (id === me) {
|
const displayAsYou = id === me && !previewMode;
|
||||||
return (
|
|
||||||
<p className={classes.authorNote}>
|
return (
|
||||||
|
<p className={previewMode ? classes.previewAuthorNote : classes.authorNote}>
|
||||||
|
{displayAsYou ? (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='collections.detail.curated_by_you'
|
id='collections.detail.curated_by_you'
|
||||||
defaultMessage='Curated by you'
|
defaultMessage='Curated by you'
|
||||||
/>
|
/>
|
||||||
</p>
|
) : (
|
||||||
);
|
<FormattedMessage
|
||||||
}
|
id='collections.detail.curated_by_author'
|
||||||
return (
|
defaultMessage='Curated by {author}'
|
||||||
<p className={classes.authorNote}>
|
values={{ author }}
|
||||||
<FormattedMessage
|
/>
|
||||||
id='collections.detail.curated_by_author'
|
)}
|
||||||
defaultMessage='Curated by {author}'
|
|
||||||
values={{ author }}
|
|
||||||
/>
|
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -84,8 +95,23 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const handleShare = useCallback(() => {
|
const handleShare = useCallback(() => {
|
||||||
dispatch(showAlert({ message: 'Collection sharing not yet implemented' }));
|
dispatch(
|
||||||
}, [dispatch]);
|
openModal({
|
||||||
|
modalType: 'SHARE_COLLECTION',
|
||||||
|
modalProps: {
|
||||||
|
collection,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}, [collection, dispatch]);
|
||||||
|
|
||||||
|
const location = useLocation<{ newCollection?: boolean }>();
|
||||||
|
const wasJustCreated = location.state.newCollection;
|
||||||
|
useEffect(() => {
|
||||||
|
if (wasJustCreated) {
|
||||||
|
handleShare();
|
||||||
|
}
|
||||||
|
}, [handleShare, wasJustCreated]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.header}>
|
<div className={classes.header}>
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
.heading {
|
||||||
|
font-size: 28px;
|
||||||
|
line-height: 1.3;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap-reverse;
|
||||||
|
align-items: start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--color-text-primary);
|
||||||
|
background: linear-gradient(
|
||||||
|
145deg,
|
||||||
|
var(--color-bg-brand-soft),
|
||||||
|
var(--color-bg-primary)
|
||||||
|
);
|
||||||
|
border: 1px solid var(--color-bg-brand-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.previewHeading {
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 1.3;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bottomsheet-breakpoint: 630px;
|
||||||
|
|
||||||
|
.shareButtonWrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
& > button {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 220px;
|
||||||
|
white-space: normal;
|
||||||
|
|
||||||
|
@media (width > $bottomsheet-breakpoint) {
|
||||||
|
max-width: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeButtonDesktop {
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
inset-inline-end: 4px;
|
||||||
|
padding: 8px;
|
||||||
|
|
||||||
|
@media (width <= $bottomsheet-breakpoint) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.closeButtonMobile {
|
||||||
|
margin-top: 16px;
|
||||||
|
margin-bottom: -18px;
|
||||||
|
|
||||||
|
@media (width > $bottomsheet-breakpoint) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import { useLocation } from 'react-router';
|
||||||
|
|
||||||
|
import { me } from '@/mastodon/initial_state';
|
||||||
|
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||||
|
import { changeCompose, focusCompose } from 'mastodon/actions/compose';
|
||||||
|
import type { ApiCollectionJSON } from 'mastodon/api_types/collections';
|
||||||
|
import { AvatarById } from 'mastodon/components/avatar';
|
||||||
|
import { AvatarGroup } from 'mastodon/components/avatar_group';
|
||||||
|
import { Button } from 'mastodon/components/button';
|
||||||
|
import { CopyLinkField } from 'mastodon/components/form_fields';
|
||||||
|
import { IconButton } from 'mastodon/components/icon_button';
|
||||||
|
import { ModalShell } from 'mastodon/components/modal_shell';
|
||||||
|
import { useAppDispatch } from 'mastodon/store';
|
||||||
|
|
||||||
|
import { AuthorNote } from '.';
|
||||||
|
import classes from './share_modal.module.scss';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
shareTextOwn: {
|
||||||
|
id: 'collection.share_template_own',
|
||||||
|
defaultMessage: 'Check out my new collection: {link}',
|
||||||
|
},
|
||||||
|
shareTextOther: {
|
||||||
|
id: 'collection.share_template_other',
|
||||||
|
defaultMessage: 'Check out this cool collection: {link}',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CollectionShareModal: React.FC<{
|
||||||
|
collection: ApiCollectionJSON;
|
||||||
|
onClose: () => void;
|
||||||
|
}> = ({ collection, onClose }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const location = useLocation<{ newCollection?: boolean }>();
|
||||||
|
const isNew = !!location.state.newCollection;
|
||||||
|
const isOwnCollection = collection.account_id === me;
|
||||||
|
|
||||||
|
const collectionLink = `${window.location.origin}/collections/${collection.id}`;
|
||||||
|
|
||||||
|
const handleShareOnDevice = useCallback(() => {
|
||||||
|
void navigator.share({
|
||||||
|
url: collectionLink,
|
||||||
|
});
|
||||||
|
}, [collectionLink]);
|
||||||
|
|
||||||
|
const handleShareViaPost = useCallback(() => {
|
||||||
|
const shareMessage = isOwnCollection
|
||||||
|
? intl.formatMessage(messages.shareTextOwn, {
|
||||||
|
link: collectionLink,
|
||||||
|
})
|
||||||
|
: intl.formatMessage(messages.shareTextOther, {
|
||||||
|
link: collectionLink,
|
||||||
|
});
|
||||||
|
|
||||||
|
onClose();
|
||||||
|
dispatch(changeCompose(shareMessage));
|
||||||
|
dispatch(focusCompose());
|
||||||
|
}, [collectionLink, dispatch, intl, isOwnCollection, onClose]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalShell>
|
||||||
|
<ModalShell.Body>
|
||||||
|
<h1 className={classes.heading}>
|
||||||
|
{isNew ? (
|
||||||
|
<FormattedMessage
|
||||||
|
id='collection.share_modal.title_new'
|
||||||
|
defaultMessage='Share your new collection!'
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<FormattedMessage
|
||||||
|
id='collection.share_modal.title'
|
||||||
|
defaultMessage='Share collection'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
title={intl.formatMessage({
|
||||||
|
id: 'lightbox.close',
|
||||||
|
defaultMessage: 'Close',
|
||||||
|
})}
|
||||||
|
iconComponent={CloseIcon}
|
||||||
|
icon='close'
|
||||||
|
className={classes.closeButtonDesktop}
|
||||||
|
onClick={onClose}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className={classes.preview}>
|
||||||
|
<div>
|
||||||
|
<h2 className={classes.previewHeading}>{collection.name}</h2>
|
||||||
|
<AuthorNote previewMode id={collection.account_id} />
|
||||||
|
</div>
|
||||||
|
<AvatarGroup>
|
||||||
|
{collection.items.slice(0, 5).map(({ account_id }) => {
|
||||||
|
if (!account_id) return;
|
||||||
|
return (
|
||||||
|
<AvatarById key={account_id} accountId={account_id} size={28} />
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</AvatarGroup>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CopyLinkField
|
||||||
|
label={intl.formatMessage({
|
||||||
|
id: 'collection.share_modal.share_link_label',
|
||||||
|
defaultMessage: 'Invite share link',
|
||||||
|
})}
|
||||||
|
value={collectionLink}
|
||||||
|
/>
|
||||||
|
</ModalShell.Body>
|
||||||
|
|
||||||
|
<ModalShell.Actions className={classes.actions}>
|
||||||
|
<div className={classes.shareButtonWrapper}>
|
||||||
|
<Button secondary onClick={handleShareViaPost}>
|
||||||
|
<FormattedMessage
|
||||||
|
id='collection.share_modal.share_via_post'
|
||||||
|
defaultMessage='Post on Mastodon'
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
{'share' in navigator && (
|
||||||
|
<Button secondary onClick={handleShareOnDevice}>
|
||||||
|
<FormattedMessage
|
||||||
|
id='collection.share_modal.share_via_system'
|
||||||
|
defaultMessage='Share to…'
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button plain onClick={onClose} className={classes.closeButtonMobile}>
|
||||||
|
<FormattedMessage id='lightbox.close' defaultMessage='Close' />
|
||||||
|
</Button>
|
||||||
|
</ModalShell.Actions>
|
||||||
|
</ModalShell>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -48,6 +48,10 @@
|
|||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.previewAuthorNote {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
.metaData {
|
.metaData {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
|
|||||||
@@ -127,7 +127,9 @@ export const CollectionDetails: React.FC<{
|
|||||||
history.replace(
|
history.replace(
|
||||||
`/collections/${result.payload.collection.id}/edit/details`,
|
`/collections/${result.payload.collection.id}/edit/details`,
|
||||||
);
|
);
|
||||||
history.push(`/collections/${result.payload.collection.id}`);
|
history.push(`/collections/${result.payload.collection.id}`, {
|
||||||
|
newCollection: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useCallback } from 'react';
|
|||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { Button } from 'mastodon/components/button';
|
import { Button } from 'mastodon/components/button';
|
||||||
|
import { ModalShell } from 'mastodon/components/modal_shell';
|
||||||
|
|
||||||
export interface BaseConfirmationModalProps {
|
export interface BaseConfirmationModalProps {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@@ -56,53 +57,49 @@ export const ConfirmationModal: React.FC<
|
|||||||
}, [onClose, onSecondary]);
|
}, [onClose, onSecondary]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='modal-root__modal safety-action-modal'>
|
<ModalShell>
|
||||||
<div className='safety-action-modal__top'>
|
<ModalShell.Body>
|
||||||
<div className='safety-action-modal__confirmation'>
|
<h1 id={titleId}>{title}</h1>
|
||||||
<h1 id={titleId}>{title}</h1>
|
{message && <p>{message}</p>}
|
||||||
{message && <p>{message}</p>}
|
|
||||||
|
|
||||||
{extraContent ?? children}
|
{extraContent ?? children}
|
||||||
</div>
|
</ModalShell.Body>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='safety-action-modal__bottom'>
|
<ModalShell.Actions>
|
||||||
<div className='safety-action-modal__actions'>
|
<button onClick={onClose} className='link-button' type='button'>
|
||||||
<button onClick={onClose} className='link-button' type='button'>
|
{cancel ?? (
|
||||||
{cancel ?? (
|
<FormattedMessage
|
||||||
<FormattedMessage
|
id='confirmation_modal.cancel'
|
||||||
id='confirmation_modal.cancel'
|
defaultMessage='Cancel'
|
||||||
defaultMessage='Cancel'
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{secondary && (
|
|
||||||
<>
|
|
||||||
<div className='spacer' />
|
|
||||||
<button
|
|
||||||
onClick={handleSecondary}
|
|
||||||
className='link-button'
|
|
||||||
type='button'
|
|
||||||
disabled={disabled}
|
|
||||||
>
|
|
||||||
{secondary}
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
</button>
|
||||||
|
|
||||||
{/* eslint-disable jsx-a11y/no-autofocus -- we are in a modal and thus autofocusing is justified */}
|
{secondary && (
|
||||||
<Button
|
<>
|
||||||
onClick={handleClick}
|
<div className='spacer' />
|
||||||
loading={updating}
|
<button
|
||||||
disabled={disabled}
|
onClick={handleSecondary}
|
||||||
autoFocus={!noFocusButton}
|
className='link-button'
|
||||||
>
|
type='button'
|
||||||
{confirm}
|
disabled={disabled}
|
||||||
</Button>
|
>
|
||||||
{/* eslint-enable */}
|
{secondary}
|
||||||
</div>
|
</button>
|
||||||
</div>
|
</>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
|
{/* eslint-disable jsx-a11y/no-autofocus -- we are in a modal and thus autofocusing is justified */}
|
||||||
|
<Button
|
||||||
|
onClick={handleClick}
|
||||||
|
loading={updating}
|
||||||
|
disabled={disabled}
|
||||||
|
autoFocus={!noFocusButton}
|
||||||
|
>
|
||||||
|
{confirm}
|
||||||
|
</Button>
|
||||||
|
{/* eslint-enable */}
|
||||||
|
</ModalShell.Actions>
|
||||||
|
</ModalShell>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import type { FC, ReactNode } from 'react';
|
||||||
|
|
||||||
|
import { FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import { Button } from '@/mastodon/components/button';
|
||||||
|
import { IconButton } from '@/mastodon/components/icon_button';
|
||||||
|
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||||
|
|
||||||
|
export type { BaseConfirmationModalProps as DialogModalProps } from './confirmation_modals/confirmation_modal';
|
||||||
|
|
||||||
|
interface DialogModalProps {
|
||||||
|
className?: string;
|
||||||
|
title: ReactNode;
|
||||||
|
onClose: () => void;
|
||||||
|
description?: ReactNode;
|
||||||
|
formClassName?: string;
|
||||||
|
children?: ReactNode;
|
||||||
|
noCancelButton?: boolean;
|
||||||
|
onSave?: () => void;
|
||||||
|
saveLabel?: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DialogModal: FC<DialogModalProps> = ({
|
||||||
|
className,
|
||||||
|
title,
|
||||||
|
onClose,
|
||||||
|
description,
|
||||||
|
formClassName,
|
||||||
|
children,
|
||||||
|
noCancelButton = false,
|
||||||
|
onSave,
|
||||||
|
saveLabel,
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const showButtons = !noCancelButton || onSave;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classNames('modal-root__modal dialog-modal', className)}>
|
||||||
|
<div className='dialog-modal__header'>
|
||||||
|
<IconButton
|
||||||
|
className='dialog-modal__header__close'
|
||||||
|
title={intl.formatMessage({
|
||||||
|
id: 'lightbox.close',
|
||||||
|
defaultMessage: 'Close',
|
||||||
|
})}
|
||||||
|
icon='close'
|
||||||
|
iconComponent={CloseIcon}
|
||||||
|
onClick={onClose}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<h1 className='dialog-modal__header__title'>{title}</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='dialog-modal__content'>
|
||||||
|
{description && (
|
||||||
|
<div className='dialog-modal__content__description'>
|
||||||
|
{description}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className={classNames('dialog-modal__content__form', formClassName)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showButtons && (
|
||||||
|
<div className='dialog-modal__content__actions'>
|
||||||
|
{!noCancelButton && (
|
||||||
|
<Button onClick={onClose} secondary>
|
||||||
|
<FormattedMessage
|
||||||
|
id='confirmation_modal.cancel'
|
||||||
|
defaultMessage='Cancel'
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{onSave && (
|
||||||
|
<Button onClick={onClose}>
|
||||||
|
{saveLabel ?? (
|
||||||
|
<FormattedMessage
|
||||||
|
id='confirmation_modal.cancel'
|
||||||
|
defaultMessage='Cancel'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
DomainBlockModal,
|
DomainBlockModal,
|
||||||
ReportModal,
|
ReportModal,
|
||||||
ReportCollectionModal,
|
ReportCollectionModal,
|
||||||
|
ShareCollectionModal,
|
||||||
EmbedModal,
|
EmbedModal,
|
||||||
ListAdder,
|
ListAdder,
|
||||||
CompareHistoryModal,
|
CompareHistoryModal,
|
||||||
@@ -79,6 +80,7 @@ export const MODAL_COMPONENTS = {
|
|||||||
'DOMAIN_BLOCK': DomainBlockModal,
|
'DOMAIN_BLOCK': DomainBlockModal,
|
||||||
'REPORT': ReportModal,
|
'REPORT': ReportModal,
|
||||||
'REPORT_COLLECTION': ReportCollectionModal,
|
'REPORT_COLLECTION': ReportCollectionModal,
|
||||||
|
'SHARE_COLLECTION': ShareCollectionModal,
|
||||||
'ACTIONS': () => Promise.resolve({ default: ActionsModal }),
|
'ACTIONS': () => Promise.resolve({ default: ActionsModal }),
|
||||||
'EMBED': EmbedModal,
|
'EMBED': EmbedModal,
|
||||||
'FOCAL_POINT': () => Promise.resolve({ default: AltTextModal }),
|
'FOCAL_POINT': () => Promise.resolve({ default: AltTextModal }),
|
||||||
@@ -95,6 +97,7 @@ export const MODAL_COMPONENTS = {
|
|||||||
'ACCOUNT_FIELD_OVERFLOW': () => import('@/mastodon/features/account_timeline/modals/field_modal').then(module => ({ default: module.AccountFieldModal })),
|
'ACCOUNT_FIELD_OVERFLOW': () => import('@/mastodon/features/account_timeline/modals/field_modal').then(module => ({ default: module.AccountFieldModal })),
|
||||||
'ACCOUNT_EDIT_NAME': () => import('@/mastodon/features/account_edit/components/name_modal').then(module => ({ default: module.NameModal })),
|
'ACCOUNT_EDIT_NAME': () => import('@/mastodon/features/account_edit/components/name_modal').then(module => ({ default: module.NameModal })),
|
||||||
'ACCOUNT_EDIT_BIO': () => import('@/mastodon/features/account_edit/components/bio_modal').then(module => ({ default: module.BioModal })),
|
'ACCOUNT_EDIT_BIO': () => import('@/mastodon/features/account_edit/components/bio_modal').then(module => ({ default: module.BioModal })),
|
||||||
|
'ACCOUNT_EDIT_PROFILE_DISPLAY': () => import('@/mastodon/features/account_edit/components/profile_display_modal').then(module => ({ default: module.ProfileDisplayModal })),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class ModalRoot extends PureComponent {
|
export default class ModalRoot extends PureComponent {
|
||||||
|
|||||||
@@ -62,6 +62,12 @@ export function CollectionsEditor() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ShareCollectionModal() {
|
||||||
|
return import('../../collections/detail/share_modal').then(
|
||||||
|
module => ({default: module.CollectionShareModal})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function Status () {
|
export function Status () {
|
||||||
return import('../../status');
|
return import('../../status');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -271,6 +271,13 @@
|
|||||||
"closed_registrations_modal.find_another_server": "Знайсці іншы сервер",
|
"closed_registrations_modal.find_another_server": "Знайсці іншы сервер",
|
||||||
"closed_registrations_modal.preamble": "Mastodon дэцэнтралізаваны, так што дзе б вы ні стварылі ўліковы запіс, вы зможаце падпісвацца і камунікаваць з кім хочаце на гэтым серверы. Вы нават можаце стварыць свой!",
|
"closed_registrations_modal.preamble": "Mastodon дэцэнтралізаваны, так што дзе б вы ні стварылі ўліковы запіс, вы зможаце падпісвацца і камунікаваць з кім хочаце на гэтым серверы. Вы нават можаце стварыць свой!",
|
||||||
"closed_registrations_modal.title": "Рэгістрацыя ў Mastodon",
|
"closed_registrations_modal.title": "Рэгістрацыя ў Mastodon",
|
||||||
|
"collection.share_modal.share_link_label": "Падзяліцца спасылкай",
|
||||||
|
"collection.share_modal.share_via_post": "Апублікаваць у Mastodon",
|
||||||
|
"collection.share_modal.share_via_system": "Падзяліцца ў…",
|
||||||
|
"collection.share_modal.title": "Падзяліцца калекцыяй",
|
||||||
|
"collection.share_modal.title_new": "Падзяліцеся сваёй калекцыяй!",
|
||||||
|
"collection.share_template_other": "Глядзі, якая класная калекцыя: {link}",
|
||||||
|
"collection.share_template_own": "Глядзі, у мяне новая калекцыя: {link}",
|
||||||
"collections.account_count": "{count, plural,one {# уліковы запіс} few {# уліковыя запісы} other {# уліковых запісаў}}",
|
"collections.account_count": "{count, plural,one {# уліковы запіс} few {# уліковыя запісы} other {# уліковых запісаў}}",
|
||||||
"collections.accounts.empty_description": "Дадайце да {count} уліковых запісаў, на якія Вы падпісаныя",
|
"collections.accounts.empty_description": "Дадайце да {count} уліковых запісаў, на якія Вы падпісаныя",
|
||||||
"collections.accounts.empty_title": "Гэтая калекцыя пустая",
|
"collections.accounts.empty_title": "Гэтая калекцыя пустая",
|
||||||
@@ -448,6 +455,7 @@
|
|||||||
"conversation.open": "Прагледзець размову",
|
"conversation.open": "Прагледзець размову",
|
||||||
"conversation.with": "З {names}",
|
"conversation.with": "З {names}",
|
||||||
"copy_icon_button.copied": "Скапіявана ў буфер абмену",
|
"copy_icon_button.copied": "Скапіявана ў буфер абмену",
|
||||||
|
"copy_icon_button.copy_this_text": "Скапіяваць спасылку ў буфер абмену",
|
||||||
"copypaste.copied": "Скапіравана",
|
"copypaste.copied": "Скапіравана",
|
||||||
"copypaste.copy_to_clipboard": "Скапіяваць у буфер абмену",
|
"copypaste.copy_to_clipboard": "Скапіяваць у буфер абмену",
|
||||||
"directory.federated": "З вядомага федэральнага сусвету",
|
"directory.federated": "З вядомага федэральнага сусвету",
|
||||||
|
|||||||
@@ -271,6 +271,12 @@
|
|||||||
"closed_registrations_modal.find_another_server": "Find en anden server",
|
"closed_registrations_modal.find_another_server": "Find en anden server",
|
||||||
"closed_registrations_modal.preamble": "Mastodon er decentraliseret, så uanset hvor du opretter din konto, vil du være i stand til at følge og interagere med hvem som helst på denne server. Du kan endda selv være vært for den!",
|
"closed_registrations_modal.preamble": "Mastodon er decentraliseret, så uanset hvor du opretter din konto, vil du være i stand til at følge og interagere med hvem som helst på denne server. Du kan endda selv være vært for den!",
|
||||||
"closed_registrations_modal.title": "Oprettelse på Mastodon",
|
"closed_registrations_modal.title": "Oprettelse på Mastodon",
|
||||||
|
"collection.share_modal.share_link_label": "Invitationlink til deling",
|
||||||
|
"collection.share_modal.share_via_system": "Del med…",
|
||||||
|
"collection.share_modal.title": "Del samling",
|
||||||
|
"collection.share_modal.title_new": "Del din nye samling!",
|
||||||
|
"collection.share_template_other": "Tjek denne seje samling: {link}",
|
||||||
|
"collection.share_template_own": "Tjek min nye samling: {link}",
|
||||||
"collections.account_count": "{count, plural, one {# konto} other {# konti}}",
|
"collections.account_count": "{count, plural, one {# konto} other {# konti}}",
|
||||||
"collections.accounts.empty_description": "Tilføj op til {count} konti, du følger",
|
"collections.accounts.empty_description": "Tilføj op til {count} konti, du følger",
|
||||||
"collections.accounts.empty_title": "Denne samling er tom",
|
"collections.accounts.empty_title": "Denne samling er tom",
|
||||||
@@ -448,6 +454,7 @@
|
|||||||
"conversation.open": "Vis samtale",
|
"conversation.open": "Vis samtale",
|
||||||
"conversation.with": "Med {names}",
|
"conversation.with": "Med {names}",
|
||||||
"copy_icon_button.copied": "Kopieret til udklipsholderen",
|
"copy_icon_button.copied": "Kopieret til udklipsholderen",
|
||||||
|
"copy_icon_button.copy_this_text": "Kopiér link til udklipsholderen",
|
||||||
"copypaste.copied": "Kopieret",
|
"copypaste.copied": "Kopieret",
|
||||||
"copypaste.copy_to_clipboard": "Kopiér til udklipsholder",
|
"copypaste.copy_to_clipboard": "Kopiér til udklipsholder",
|
||||||
"directory.federated": "Fra kendt fediverse",
|
"directory.federated": "Fra kendt fediverse",
|
||||||
|
|||||||
@@ -271,6 +271,13 @@
|
|||||||
"closed_registrations_modal.find_another_server": "Anderen Server suchen",
|
"closed_registrations_modal.find_another_server": "Anderen Server suchen",
|
||||||
"closed_registrations_modal.preamble": "Mastodon ist dezentralisiert, das heißt, unabhängig davon, wo du dein Konto erstellst, kannst du jedem Profil auf diesem Server folgen und mit ihm interagieren. Du kannst sogar deinen eigenen Mastodon-Server hosten!",
|
"closed_registrations_modal.preamble": "Mastodon ist dezentralisiert, das heißt, unabhängig davon, wo du dein Konto erstellst, kannst du jedem Profil auf diesem Server folgen und mit ihm interagieren. Du kannst sogar deinen eigenen Mastodon-Server hosten!",
|
||||||
"closed_registrations_modal.title": "Bei Mastodon registrieren",
|
"closed_registrations_modal.title": "Bei Mastodon registrieren",
|
||||||
|
"collection.share_modal.share_link_label": "Link zum Teilen",
|
||||||
|
"collection.share_modal.share_via_post": "Auf Mastodon veröffentlichen",
|
||||||
|
"collection.share_modal.share_via_system": "Teilen …",
|
||||||
|
"collection.share_modal.title": "Sammlung teilen",
|
||||||
|
"collection.share_modal.title_new": "Teile deine neue Sammlung!",
|
||||||
|
"collection.share_template_other": "Seht euch diese coole Sammlung an: {link}",
|
||||||
|
"collection.share_template_own": "Seht euch meine neue Sammlung an: {link}",
|
||||||
"collections.account_count": "{count, plural, one {# Konto} other {# Konten}}",
|
"collections.account_count": "{count, plural, one {# Konto} other {# Konten}}",
|
||||||
"collections.accounts.empty_description": "Füge bis zu {count} Konten, denen du folgst, hinzu",
|
"collections.accounts.empty_description": "Füge bis zu {count} Konten, denen du folgst, hinzu",
|
||||||
"collections.accounts.empty_title": "Diese Sammlung ist leer",
|
"collections.accounts.empty_title": "Diese Sammlung ist leer",
|
||||||
@@ -448,6 +455,7 @@
|
|||||||
"conversation.open": "Unterhaltung anzeigen",
|
"conversation.open": "Unterhaltung anzeigen",
|
||||||
"conversation.with": "Mit {names}",
|
"conversation.with": "Mit {names}",
|
||||||
"copy_icon_button.copied": "In die Zwischenablage kopiert",
|
"copy_icon_button.copied": "In die Zwischenablage kopiert",
|
||||||
|
"copy_icon_button.copy_this_text": "Link in die Zwischenablage kopieren",
|
||||||
"copypaste.copied": "Kopiert",
|
"copypaste.copied": "Kopiert",
|
||||||
"copypaste.copy_to_clipboard": "In die Zwischenablage kopieren",
|
"copypaste.copy_to_clipboard": "In die Zwischenablage kopieren",
|
||||||
"directory.federated": "Aus bekanntem Fediverse",
|
"directory.federated": "Aus bekanntem Fediverse",
|
||||||
|
|||||||
@@ -271,6 +271,13 @@
|
|||||||
"closed_registrations_modal.find_another_server": "Βρες άλλον διακομιστή",
|
"closed_registrations_modal.find_another_server": "Βρες άλλον διακομιστή",
|
||||||
"closed_registrations_modal.preamble": "Το Mastodon είναι αποκεντρωμένο, οπότε ανεξάρτητα από το πού θα δημιουργήσεις τον λογαριασμό σου, μπορείς να ακολουθήσεις και να αλληλεπιδράσεις με οποιονδήποτε σε αυτόν τον διακομιστή. Μπορείς ακόμη και να κάνεις τον δικό σου!",
|
"closed_registrations_modal.preamble": "Το Mastodon είναι αποκεντρωμένο, οπότε ανεξάρτητα από το πού θα δημιουργήσεις τον λογαριασμό σου, μπορείς να ακολουθήσεις και να αλληλεπιδράσεις με οποιονδήποτε σε αυτόν τον διακομιστή. Μπορείς ακόμη και να κάνεις τον δικό σου!",
|
||||||
"closed_registrations_modal.title": "Εγγραφή στο Mastodon",
|
"closed_registrations_modal.title": "Εγγραφή στο Mastodon",
|
||||||
|
"collection.share_modal.share_link_label": "Σύνδεσμος κοινοποίησης",
|
||||||
|
"collection.share_modal.share_via_post": "Ανάρτηση στο Mastodon",
|
||||||
|
"collection.share_modal.share_via_system": "Κοινοποίηση σε…",
|
||||||
|
"collection.share_modal.title": "Κοινοποίηση συλλογής",
|
||||||
|
"collection.share_modal.title_new": "Μοιραστείτε τη νέα σας συλλογή!",
|
||||||
|
"collection.share_template_other": "Δείτε αυτή την ωραία συλλογή: {link}",
|
||||||
|
"collection.share_template_own": "Δείτε τη νέα μου συλλογή: {link}",
|
||||||
"collections.account_count": "{count, plural, one {# λογαριασμός} other {# λογαριασμοί}}",
|
"collections.account_count": "{count, plural, one {# λογαριασμός} other {# λογαριασμοί}}",
|
||||||
"collections.accounts.empty_description": "Προσθέστε μέχρι και {count} λογαριασμούς που ακολουθείτε",
|
"collections.accounts.empty_description": "Προσθέστε μέχρι και {count} λογαριασμούς που ακολουθείτε",
|
||||||
"collections.accounts.empty_title": "Αυτή η συλλογή είναι κενή",
|
"collections.accounts.empty_title": "Αυτή η συλλογή είναι κενή",
|
||||||
@@ -448,6 +455,7 @@
|
|||||||
"conversation.open": "Προβολή συνομιλίας",
|
"conversation.open": "Προβολή συνομιλίας",
|
||||||
"conversation.with": "Με {names}",
|
"conversation.with": "Με {names}",
|
||||||
"copy_icon_button.copied": "Αντιγράφηκε στο πρόχειρο",
|
"copy_icon_button.copied": "Αντιγράφηκε στο πρόχειρο",
|
||||||
|
"copy_icon_button.copy_this_text": "Αντιγραφή συνδέσμου στο πρόχειρο",
|
||||||
"copypaste.copied": "Αντιγράφηκε",
|
"copypaste.copied": "Αντιγράφηκε",
|
||||||
"copypaste.copy_to_clipboard": "Αντιγραφή στο πρόχειρο",
|
"copypaste.copy_to_clipboard": "Αντιγραφή στο πρόχειρο",
|
||||||
"directory.federated": "Από το γνωστό fediverse",
|
"directory.federated": "Από το γνωστό fediverse",
|
||||||
|
|||||||
@@ -271,6 +271,13 @@
|
|||||||
"closed_registrations_modal.find_another_server": "Find another server",
|
"closed_registrations_modal.find_another_server": "Find another server",
|
||||||
"closed_registrations_modal.preamble": "Mastodon is decentralised, so no matter where you create your account, you will be able to follow and interact with anyone on this server. You can even self-host it!",
|
"closed_registrations_modal.preamble": "Mastodon is decentralised, so no matter where you create your account, you will be able to follow and interact with anyone on this server. You can even self-host it!",
|
||||||
"closed_registrations_modal.title": "Signing up on Mastodon",
|
"closed_registrations_modal.title": "Signing up on Mastodon",
|
||||||
|
"collection.share_modal.share_link_label": "Invite share link",
|
||||||
|
"collection.share_modal.share_via_post": "Post on Mastodon",
|
||||||
|
"collection.share_modal.share_via_system": "Share to…",
|
||||||
|
"collection.share_modal.title": "Share collection",
|
||||||
|
"collection.share_modal.title_new": "Share your new collection!",
|
||||||
|
"collection.share_template_other": "Check out this cool collection: {link}",
|
||||||
|
"collection.share_template_own": "Check out my new collection: {link}",
|
||||||
"collections.account_count": "{count, plural, one {# account} other {# accounts}}",
|
"collections.account_count": "{count, plural, one {# account} other {# accounts}}",
|
||||||
"collections.accounts.empty_description": "Add up to {count} accounts you follow",
|
"collections.accounts.empty_description": "Add up to {count} accounts you follow",
|
||||||
"collections.accounts.empty_title": "This collection is empty",
|
"collections.accounts.empty_title": "This collection is empty",
|
||||||
@@ -448,6 +455,7 @@
|
|||||||
"conversation.open": "View conversation",
|
"conversation.open": "View conversation",
|
||||||
"conversation.with": "With {names}",
|
"conversation.with": "With {names}",
|
||||||
"copy_icon_button.copied": "Copied to clipboard",
|
"copy_icon_button.copied": "Copied to clipboard",
|
||||||
|
"copy_icon_button.copy_this_text": "Copy link to clipboard",
|
||||||
"copypaste.copied": "Copied",
|
"copypaste.copied": "Copied",
|
||||||
"copypaste.copy_to_clipboard": "Copy to clipboard",
|
"copypaste.copy_to_clipboard": "Copy to clipboard",
|
||||||
"directory.federated": "From known fediverse",
|
"directory.federated": "From known fediverse",
|
||||||
|
|||||||
@@ -161,6 +161,15 @@
|
|||||||
"account_edit.featured_hashtags.title": "Featured hashtags",
|
"account_edit.featured_hashtags.title": "Featured hashtags",
|
||||||
"account_edit.name_modal.add_title": "Add display name",
|
"account_edit.name_modal.add_title": "Add display name",
|
||||||
"account_edit.name_modal.edit_title": "Edit display name",
|
"account_edit.name_modal.edit_title": "Edit display name",
|
||||||
|
"account_edit.profile_tab.button_label": "Customize",
|
||||||
|
"account_edit.profile_tab.hint.description": "These settings customize what users see on {server} in the official apps, but they may not apply to users on other servers and 3rd party apps.",
|
||||||
|
"account_edit.profile_tab.hint.title": "Displays still vary",
|
||||||
|
"account_edit.profile_tab.show_featured.description": "‘Featured’ is an optional tab where you can showcase other accounts.",
|
||||||
|
"account_edit.profile_tab.show_featured.title": "Show ‘Featured’ tab",
|
||||||
|
"account_edit.profile_tab.show_media.description": "‘Media’ is an optional tab that shows your posts containing images or videos.",
|
||||||
|
"account_edit.profile_tab.show_media.title": "Show ‘Media’ tab",
|
||||||
|
"account_edit.profile_tab.show_media_replies.description": "When enabled, Media tab shows both your posts and replies to other people’s posts.",
|
||||||
|
"account_edit.profile_tab.show_media_replies.title": "Include replies on ‘Media’ tab",
|
||||||
"account_edit.profile_tab.subtitle": "Customize the tabs on your profile and what they display.",
|
"account_edit.profile_tab.subtitle": "Customize the tabs on your profile and what they display.",
|
||||||
"account_edit.profile_tab.title": "Profile tab settings",
|
"account_edit.profile_tab.title": "Profile tab settings",
|
||||||
"account_edit.save": "Save",
|
"account_edit.save": "Save",
|
||||||
@@ -271,6 +280,13 @@
|
|||||||
"closed_registrations_modal.find_another_server": "Find another server",
|
"closed_registrations_modal.find_another_server": "Find another server",
|
||||||
"closed_registrations_modal.preamble": "Mastodon is decentralized, so no matter where you create your account, you will be able to follow and interact with anyone on this server. You can even self-host it!",
|
"closed_registrations_modal.preamble": "Mastodon is decentralized, so no matter where you create your account, you will be able to follow and interact with anyone on this server. You can even self-host it!",
|
||||||
"closed_registrations_modal.title": "Signing up on Mastodon",
|
"closed_registrations_modal.title": "Signing up on Mastodon",
|
||||||
|
"collection.share_modal.share_link_label": "Invite share link",
|
||||||
|
"collection.share_modal.share_via_post": "Post on Mastodon",
|
||||||
|
"collection.share_modal.share_via_system": "Share to…",
|
||||||
|
"collection.share_modal.title": "Share collection",
|
||||||
|
"collection.share_modal.title_new": "Share your new collection!",
|
||||||
|
"collection.share_template_other": "Check out this cool collection: {link}",
|
||||||
|
"collection.share_template_own": "Check out my new collection: {link}",
|
||||||
"collections.account_count": "{count, plural, one {# account} other {# accounts}}",
|
"collections.account_count": "{count, plural, one {# account} other {# accounts}}",
|
||||||
"collections.accounts.empty_description": "Add up to {count} accounts you follow",
|
"collections.accounts.empty_description": "Add up to {count} accounts you follow",
|
||||||
"collections.accounts.empty_title": "This collection is empty",
|
"collections.accounts.empty_title": "This collection is empty",
|
||||||
@@ -448,6 +464,7 @@
|
|||||||
"conversation.open": "View conversation",
|
"conversation.open": "View conversation",
|
||||||
"conversation.with": "With {names}",
|
"conversation.with": "With {names}",
|
||||||
"copy_icon_button.copied": "Copied to clipboard",
|
"copy_icon_button.copied": "Copied to clipboard",
|
||||||
|
"copy_icon_button.copy_this_text": "Copy link to clipboard",
|
||||||
"copypaste.copied": "Copied",
|
"copypaste.copied": "Copied",
|
||||||
"copypaste.copy_to_clipboard": "Copy to clipboard",
|
"copypaste.copy_to_clipboard": "Copy to clipboard",
|
||||||
"directory.federated": "From known fediverse",
|
"directory.federated": "From known fediverse",
|
||||||
|
|||||||
@@ -271,6 +271,13 @@
|
|||||||
"closed_registrations_modal.find_another_server": "Buscar otro servidor",
|
"closed_registrations_modal.find_another_server": "Buscar otro servidor",
|
||||||
"closed_registrations_modal.preamble": "Mastodon es descentralizado, por lo que no importa dónde creés tu cuenta, podrás seguir e interactuar con cualquier persona en este servidor. ¡Incluso podés montar tu propio servidor!",
|
"closed_registrations_modal.preamble": "Mastodon es descentralizado, por lo que no importa dónde creés tu cuenta, podrás seguir e interactuar con cualquier persona en este servidor. ¡Incluso podés montar tu propio servidor!",
|
||||||
"closed_registrations_modal.title": "Registrarse en Mastodon",
|
"closed_registrations_modal.title": "Registrarse en Mastodon",
|
||||||
|
"collection.share_modal.share_link_label": "Enlace para compartir",
|
||||||
|
"collection.share_modal.share_via_post": "Enviar a Mastodon",
|
||||||
|
"collection.share_modal.share_via_system": "Compartir en…",
|
||||||
|
"collection.share_modal.title": "Compartir colección",
|
||||||
|
"collection.share_modal.title_new": "¡Compartí tu nueva colección!",
|
||||||
|
"collection.share_template_other": "¡Mirá qué copada está esta colección! {link}",
|
||||||
|
"collection.share_template_own": "Mirá mi nueva colección: {link}",
|
||||||
"collections.account_count": "{count, plural, one {# hora} other {# horas}}",
|
"collections.account_count": "{count, plural, one {# hora} other {# horas}}",
|
||||||
"collections.accounts.empty_description": "Agregá hasta {count} cuentas que seguís",
|
"collections.accounts.empty_description": "Agregá hasta {count} cuentas que seguís",
|
||||||
"collections.accounts.empty_title": "Esta colección está vacía",
|
"collections.accounts.empty_title": "Esta colección está vacía",
|
||||||
@@ -448,6 +455,7 @@
|
|||||||
"conversation.open": "Ver conversación",
|
"conversation.open": "Ver conversación",
|
||||||
"conversation.with": "Con {names}",
|
"conversation.with": "Con {names}",
|
||||||
"copy_icon_button.copied": "Copiado en el portapapeles",
|
"copy_icon_button.copied": "Copiado en el portapapeles",
|
||||||
|
"copy_icon_button.copy_this_text": "Copiar enlace al portapapeles",
|
||||||
"copypaste.copied": "Copiado",
|
"copypaste.copied": "Copiado",
|
||||||
"copypaste.copy_to_clipboard": "Copiar al portapapeles",
|
"copypaste.copy_to_clipboard": "Copiar al portapapeles",
|
||||||
"directory.federated": "Desde fediverso conocido",
|
"directory.federated": "Desde fediverso conocido",
|
||||||
|
|||||||
@@ -271,6 +271,13 @@
|
|||||||
"closed_registrations_modal.find_another_server": "Buscar otro servidor",
|
"closed_registrations_modal.find_another_server": "Buscar otro servidor",
|
||||||
"closed_registrations_modal.preamble": "Mastodon es descentralizado, por lo que no importa dónde crees tu cuenta, podrás seguir e interactuar con cualquier persona en este servidor. ¡Incluso puedes alojarlo tú mismo!",
|
"closed_registrations_modal.preamble": "Mastodon es descentralizado, por lo que no importa dónde crees tu cuenta, podrás seguir e interactuar con cualquier persona en este servidor. ¡Incluso puedes alojarlo tú mismo!",
|
||||||
"closed_registrations_modal.title": "Registrarse en Mastodon",
|
"closed_registrations_modal.title": "Registrarse en Mastodon",
|
||||||
|
"collection.share_modal.share_link_label": "Enlace para compartir",
|
||||||
|
"collection.share_modal.share_via_post": "Publicar en Mastodon",
|
||||||
|
"collection.share_modal.share_via_system": "Compartir con…",
|
||||||
|
"collection.share_modal.title": "Compartir la colección",
|
||||||
|
"collection.share_modal.title_new": "¡Comparte tu nueva colección!",
|
||||||
|
"collection.share_template_other": "Echa un vistazo a esta increíble colección: {link}",
|
||||||
|
"collection.share_template_own": "Echa un vistazo a mi nueva colección: {link}",
|
||||||
"collections.account_count": "{count, plural,one {# cuenta} other {# cuentas}}",
|
"collections.account_count": "{count, plural,one {# cuenta} other {# cuentas}}",
|
||||||
"collections.accounts.empty_description": "Añade hasta {count} cuentas que sigues",
|
"collections.accounts.empty_description": "Añade hasta {count} cuentas que sigues",
|
||||||
"collections.accounts.empty_title": "Esta colección está vacía",
|
"collections.accounts.empty_title": "Esta colección está vacía",
|
||||||
@@ -448,6 +455,7 @@
|
|||||||
"conversation.open": "Ver conversación",
|
"conversation.open": "Ver conversación",
|
||||||
"conversation.with": "Con {names}",
|
"conversation.with": "Con {names}",
|
||||||
"copy_icon_button.copied": "Copiado al portapapeles",
|
"copy_icon_button.copied": "Copiado al portapapeles",
|
||||||
|
"copy_icon_button.copy_this_text": "Copiar enlace al portapapeles",
|
||||||
"copypaste.copied": "Copiado",
|
"copypaste.copied": "Copiado",
|
||||||
"copypaste.copy_to_clipboard": "Copiar al portapapeles",
|
"copypaste.copy_to_clipboard": "Copiar al portapapeles",
|
||||||
"directory.federated": "Desde el fediverso conocido",
|
"directory.federated": "Desde el fediverso conocido",
|
||||||
|
|||||||
@@ -271,6 +271,13 @@
|
|||||||
"closed_registrations_modal.find_another_server": "Buscar otro servidor",
|
"closed_registrations_modal.find_another_server": "Buscar otro servidor",
|
||||||
"closed_registrations_modal.preamble": "Mastodon es descentralizado, por lo que no importa dónde crees tu cuenta, podrás seguir e interactuar con cualquier persona en este servidor. ¡Incluso puedes alojarlo tú mismo!",
|
"closed_registrations_modal.preamble": "Mastodon es descentralizado, por lo que no importa dónde crees tu cuenta, podrás seguir e interactuar con cualquier persona en este servidor. ¡Incluso puedes alojarlo tú mismo!",
|
||||||
"closed_registrations_modal.title": "Registrarse en Mastodon",
|
"closed_registrations_modal.title": "Registrarse en Mastodon",
|
||||||
|
"collection.share_modal.share_link_label": "Enlace para compartir",
|
||||||
|
"collection.share_modal.share_via_post": "Publicar en Mastodon",
|
||||||
|
"collection.share_modal.share_via_system": "Compartir con…",
|
||||||
|
"collection.share_modal.title": "Compartir la colección",
|
||||||
|
"collection.share_modal.title_new": "¡Comparte tu nueva colección!",
|
||||||
|
"collection.share_template_other": "Echa un vistazo a esta fantástica colección: {link}",
|
||||||
|
"collection.share_template_own": "Echa un vistazo a mi nueva colección: {link}",
|
||||||
"collections.account_count": "{count, plural, one {# cuenta} other {# cuentas}}",
|
"collections.account_count": "{count, plural, one {# cuenta} other {# cuentas}}",
|
||||||
"collections.accounts.empty_description": "Añade hasta {count} cuentas que sigas",
|
"collections.accounts.empty_description": "Añade hasta {count} cuentas que sigas",
|
||||||
"collections.accounts.empty_title": "Esta colección está vacía",
|
"collections.accounts.empty_title": "Esta colección está vacía",
|
||||||
@@ -307,7 +314,7 @@
|
|||||||
"collections.no_collections_yet": "Aún no hay colecciones.",
|
"collections.no_collections_yet": "Aún no hay colecciones.",
|
||||||
"collections.old_last_post_note": "Última publicación hace más de una semana",
|
"collections.old_last_post_note": "Última publicación hace más de una semana",
|
||||||
"collections.remove_account": "Borrar esta cuenta",
|
"collections.remove_account": "Borrar esta cuenta",
|
||||||
"collections.report_collection": "Reportar esta colección",
|
"collections.report_collection": "Informar de esta colección",
|
||||||
"collections.search_accounts_label": "Buscar cuentas para añadir…",
|
"collections.search_accounts_label": "Buscar cuentas para añadir…",
|
||||||
"collections.search_accounts_max_reached": "Has añadido el número máximo de cuentas",
|
"collections.search_accounts_max_reached": "Has añadido el número máximo de cuentas",
|
||||||
"collections.sensitive": "Sensible",
|
"collections.sensitive": "Sensible",
|
||||||
@@ -448,6 +455,7 @@
|
|||||||
"conversation.open": "Ver conversación",
|
"conversation.open": "Ver conversación",
|
||||||
"conversation.with": "Con {names}",
|
"conversation.with": "Con {names}",
|
||||||
"copy_icon_button.copied": "Copiado al portapapeles",
|
"copy_icon_button.copied": "Copiado al portapapeles",
|
||||||
|
"copy_icon_button.copy_this_text": "Copiar enlace al portapapeles",
|
||||||
"copypaste.copied": "Copiado",
|
"copypaste.copied": "Copiado",
|
||||||
"copypaste.copy_to_clipboard": "Copiar al portapapeles",
|
"copypaste.copy_to_clipboard": "Copiar al portapapeles",
|
||||||
"directory.federated": "Desde el fediverso conocido",
|
"directory.federated": "Desde el fediverso conocido",
|
||||||
@@ -977,7 +985,7 @@
|
|||||||
"report.category.title_account": "perfil",
|
"report.category.title_account": "perfil",
|
||||||
"report.category.title_status": "publicación",
|
"report.category.title_status": "publicación",
|
||||||
"report.close": "Hecho",
|
"report.close": "Hecho",
|
||||||
"report.collection_comment": "¿Por qué quieres reportar esta colección?",
|
"report.collection_comment": "¿Por qué quieres informar de esta colección?",
|
||||||
"report.comment.title": "¿Hay algo más que creas que deberíamos saber?",
|
"report.comment.title": "¿Hay algo más que creas que deberíamos saber?",
|
||||||
"report.forward": "Reenviar a {target}",
|
"report.forward": "Reenviar a {target}",
|
||||||
"report.forward_hint": "Esta cuenta es de otro servidor. ¿Enviar una copia anonimizada del informe allí también?",
|
"report.forward_hint": "Esta cuenta es de otro servidor. ¿Enviar una copia anonimizada del informe allí también?",
|
||||||
@@ -999,7 +1007,7 @@
|
|||||||
"report.rules.title": "¿Qué normas se están violando?",
|
"report.rules.title": "¿Qué normas se están violando?",
|
||||||
"report.statuses.subtitle": "Selecciona todos los que correspondan",
|
"report.statuses.subtitle": "Selecciona todos los que correspondan",
|
||||||
"report.statuses.title": "¿Hay alguna publicación que respalde este informe?",
|
"report.statuses.title": "¿Hay alguna publicación que respalde este informe?",
|
||||||
"report.submission_error": "No se pudo enviar el reporte",
|
"report.submission_error": "No se pudo enviar el informe",
|
||||||
"report.submission_error_details": "Comprueba tu conexión de red e inténtalo más tarde.",
|
"report.submission_error_details": "Comprueba tu conexión de red e inténtalo más tarde.",
|
||||||
"report.submit": "Enviar",
|
"report.submit": "Enviar",
|
||||||
"report.target": "Reportando {target}",
|
"report.target": "Reportando {target}",
|
||||||
|
|||||||
@@ -271,6 +271,13 @@
|
|||||||
"closed_registrations_modal.find_another_server": "Finn ein annan ambætara",
|
"closed_registrations_modal.find_another_server": "Finn ein annan ambætara",
|
||||||
"closed_registrations_modal.preamble": "Mastodon er desentraliserað, so óansæð hvar tú stovnar tína kontu, so ber til hjá tær at fylgja og virka saman við einum og hvørjum á hesum ambætaranum. Tað ber enntá til at hýsa tí sjálvi!",
|
"closed_registrations_modal.preamble": "Mastodon er desentraliserað, so óansæð hvar tú stovnar tína kontu, so ber til hjá tær at fylgja og virka saman við einum og hvørjum á hesum ambætaranum. Tað ber enntá til at hýsa tí sjálvi!",
|
||||||
"closed_registrations_modal.title": "At stovna kontu á Mastodon",
|
"closed_registrations_modal.title": "At stovna kontu á Mastodon",
|
||||||
|
"collection.share_modal.share_link_label": "Innbjóðingarleinki at deila",
|
||||||
|
"collection.share_modal.share_via_post": "Posta á Mastodon",
|
||||||
|
"collection.share_modal.share_via_system": "Deil til…",
|
||||||
|
"collection.share_modal.title": "Deil savn",
|
||||||
|
"collection.share_modal.title_new": "Deil títt nýggja savn!",
|
||||||
|
"collection.share_template_other": "Hygg at hesum kula savninum: {link}",
|
||||||
|
"collection.share_template_own": "Hygg at mínum nýggja savni: {link}",
|
||||||
"collections.account_count": "{count, plural, one {# konta} other {# kontur}}",
|
"collections.account_count": "{count, plural, one {# konta} other {# kontur}}",
|
||||||
"collections.accounts.empty_description": "Legg afturat upp til {count} kontur, sum tú fylgir",
|
"collections.accounts.empty_description": "Legg afturat upp til {count} kontur, sum tú fylgir",
|
||||||
"collections.accounts.empty_title": "Hetta savnið er tómt",
|
"collections.accounts.empty_title": "Hetta savnið er tómt",
|
||||||
@@ -448,6 +455,7 @@
|
|||||||
"conversation.open": "Vís samrøðu",
|
"conversation.open": "Vís samrøðu",
|
||||||
"conversation.with": "Við {names}",
|
"conversation.with": "Við {names}",
|
||||||
"copy_icon_button.copied": "Avritað til setiborðið",
|
"copy_icon_button.copied": "Avritað til setiborðið",
|
||||||
|
"copy_icon_button.copy_this_text": "Avrita leinki til setiborðið",
|
||||||
"copypaste.copied": "Avritað",
|
"copypaste.copied": "Avritað",
|
||||||
"copypaste.copy_to_clipboard": "Avrita til setiborðið",
|
"copypaste.copy_to_clipboard": "Avrita til setiborðið",
|
||||||
"directory.federated": "Frá tí kenda fediversinum",
|
"directory.federated": "Frá tí kenda fediversinum",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
"account.featured.hashtags": "Hashtags",
|
"account.featured.hashtags": "Hashtags",
|
||||||
"account.featured_tags.last_status_at": "Dernière publication {date}",
|
"account.featured_tags.last_status_at": "Dernière publication {date}",
|
||||||
"account.featured_tags.last_status_never": "Aucune publication",
|
"account.featured_tags.last_status_never": "Aucune publication",
|
||||||
|
"account.field_overflow": "Voir tout",
|
||||||
"account.filters.all": "Toutes les activités",
|
"account.filters.all": "Toutes les activités",
|
||||||
"account.filters.boosts_toggle": "Afficher les partages",
|
"account.filters.boosts_toggle": "Afficher les partages",
|
||||||
"account.filters.posts_boosts": "Messages et partages",
|
"account.filters.posts_boosts": "Messages et partages",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
"account.featured.hashtags": "Hashtags",
|
"account.featured.hashtags": "Hashtags",
|
||||||
"account.featured_tags.last_status_at": "Dernier message le {date}",
|
"account.featured_tags.last_status_at": "Dernier message le {date}",
|
||||||
"account.featured_tags.last_status_never": "Aucun message",
|
"account.featured_tags.last_status_never": "Aucun message",
|
||||||
|
"account.field_overflow": "Voir tout",
|
||||||
"account.filters.all": "Toutes les activités",
|
"account.filters.all": "Toutes les activités",
|
||||||
"account.filters.boosts_toggle": "Afficher les partages",
|
"account.filters.boosts_toggle": "Afficher les partages",
|
||||||
"account.filters.posts_boosts": "Messages et partages",
|
"account.filters.posts_boosts": "Messages et partages",
|
||||||
|
|||||||
@@ -271,6 +271,13 @@
|
|||||||
"closed_registrations_modal.find_another_server": "Faigh freastalaí eile",
|
"closed_registrations_modal.find_another_server": "Faigh freastalaí eile",
|
||||||
"closed_registrations_modal.preamble": "Ós rud é go bhfuil Mastodon díláraithe, is cuma cá háit a chruthaíonn tú do chuntas, beidh tú in ann idirghníomhú le haon duine ar an bhfreastalaí seo agus iad a leanúint. Is féidir fiú é a féin-óstáil!",
|
"closed_registrations_modal.preamble": "Ós rud é go bhfuil Mastodon díláraithe, is cuma cá háit a chruthaíonn tú do chuntas, beidh tú in ann idirghníomhú le haon duine ar an bhfreastalaí seo agus iad a leanúint. Is féidir fiú é a féin-óstáil!",
|
||||||
"closed_registrations_modal.title": "Cláraigh le Mastodon",
|
"closed_registrations_modal.title": "Cláraigh le Mastodon",
|
||||||
|
"collection.share_modal.share_link_label": "Nasc roinnte cuireadh",
|
||||||
|
"collection.share_modal.share_via_post": "Postáil ar Mastodon",
|
||||||
|
"collection.share_modal.share_via_system": "Comhroinn le…",
|
||||||
|
"collection.share_modal.title": "Comhroinn bailiúchán",
|
||||||
|
"collection.share_modal.title_new": "Roinn do bhailiúchán nua!",
|
||||||
|
"collection.share_template_other": "Féach ar an mbailiúchán fionnuar seo: {link}",
|
||||||
|
"collection.share_template_own": "Féach ar mo bhailiúchán nua: {link}",
|
||||||
"collections.account_count": "{count, plural, one {# cuntas} two {# cuntais} few {# cuntais} many {# cuntais} other {# cuntais}}",
|
"collections.account_count": "{count, plural, one {# cuntas} two {# cuntais} few {# cuntais} many {# cuntais} other {# cuntais}}",
|
||||||
"collections.accounts.empty_description": "Cuir suas le {count} cuntas leis a leanann tú",
|
"collections.accounts.empty_description": "Cuir suas le {count} cuntas leis a leanann tú",
|
||||||
"collections.accounts.empty_title": "Tá an bailiúchán seo folamh",
|
"collections.accounts.empty_title": "Tá an bailiúchán seo folamh",
|
||||||
@@ -448,6 +455,7 @@
|
|||||||
"conversation.open": "Féach ar comhrá",
|
"conversation.open": "Féach ar comhrá",
|
||||||
"conversation.with": "Le {names}",
|
"conversation.with": "Le {names}",
|
||||||
"copy_icon_button.copied": "Cóipeáladh chuig an ngearrthaisce",
|
"copy_icon_button.copied": "Cóipeáladh chuig an ngearrthaisce",
|
||||||
|
"copy_icon_button.copy_this_text": "Cóipeáil nasc chuig an ghearrthaisce",
|
||||||
"copypaste.copied": "Cóipeáilte",
|
"copypaste.copied": "Cóipeáilte",
|
||||||
"copypaste.copy_to_clipboard": "Cóipeáil chuig an ngearrthaisce",
|
"copypaste.copy_to_clipboard": "Cóipeáil chuig an ngearrthaisce",
|
||||||
"directory.federated": "Ó chomhchruinne aitheanta",
|
"directory.federated": "Ó chomhchruinne aitheanta",
|
||||||
|
|||||||
@@ -271,6 +271,13 @@
|
|||||||
"closed_registrations_modal.find_another_server": "Finna annan netþjón",
|
"closed_registrations_modal.find_another_server": "Finna annan netþjón",
|
||||||
"closed_registrations_modal.preamble": "Mastodon er ekki miðstýrt, svo það skiptir ekki máli hvar þú býrð til aðgang; þú munt get fylgt eftir og haft samskipti við hvern sem er á þessum þjóni. Þú getur jafnvel hýst þinn eigin Mastodon þjón!",
|
"closed_registrations_modal.preamble": "Mastodon er ekki miðstýrt, svo það skiptir ekki máli hvar þú býrð til aðgang; þú munt get fylgt eftir og haft samskipti við hvern sem er á þessum þjóni. Þú getur jafnvel hýst þinn eigin Mastodon þjón!",
|
||||||
"closed_registrations_modal.title": "Að nýskrá sig á Mastodon",
|
"closed_registrations_modal.title": "Að nýskrá sig á Mastodon",
|
||||||
|
"collection.share_modal.share_link_label": "Tengill til að deila",
|
||||||
|
"collection.share_modal.share_via_post": "Birta á Mastodon",
|
||||||
|
"collection.share_modal.share_via_system": "Deila með…",
|
||||||
|
"collection.share_modal.title": "Deila safni",
|
||||||
|
"collection.share_modal.title_new": "Deildu nýja safninu þínu!",
|
||||||
|
"collection.share_template_other": "Kíktu á þetta áhugaverða safn: {link}",
|
||||||
|
"collection.share_template_own": "Kíktu á nýja safnið mitt: {link}",
|
||||||
"collections.account_count": "{count, plural, one {# aðgangur} other {# aðgangar}}",
|
"collections.account_count": "{count, plural, one {# aðgangur} other {# aðgangar}}",
|
||||||
"collections.accounts.empty_description": "Bættu við allt að {count} aðgöngum sem þú fylgist með",
|
"collections.accounts.empty_description": "Bættu við allt að {count} aðgöngum sem þú fylgist með",
|
||||||
"collections.accounts.empty_title": "Þetta safn er tómt",
|
"collections.accounts.empty_title": "Þetta safn er tómt",
|
||||||
@@ -448,6 +455,7 @@
|
|||||||
"conversation.open": "Skoða samtal",
|
"conversation.open": "Skoða samtal",
|
||||||
"conversation.with": "Við {names}",
|
"conversation.with": "Við {names}",
|
||||||
"copy_icon_button.copied": "Afritað á klippispjald",
|
"copy_icon_button.copied": "Afritað á klippispjald",
|
||||||
|
"copy_icon_button.copy_this_text": "Afrita tengil á klippispjald",
|
||||||
"copypaste.copied": "Afritað",
|
"copypaste.copied": "Afritað",
|
||||||
"copypaste.copy_to_clipboard": "Afrita á klippispjald",
|
"copypaste.copy_to_clipboard": "Afrita á klippispjald",
|
||||||
"directory.federated": "Frá samtengdum vefþjónum",
|
"directory.federated": "Frá samtengdum vefþjónum",
|
||||||
|
|||||||
@@ -44,9 +44,11 @@
|
|||||||
"account.familiar_followers_two": "Seguito da {name1} e {name2}",
|
"account.familiar_followers_two": "Seguito da {name1} e {name2}",
|
||||||
"account.featured": "In primo piano",
|
"account.featured": "In primo piano",
|
||||||
"account.featured.accounts": "Profili",
|
"account.featured.accounts": "Profili",
|
||||||
|
"account.featured.collections": "Collezioni",
|
||||||
"account.featured.hashtags": "Hashtag",
|
"account.featured.hashtags": "Hashtag",
|
||||||
"account.featured_tags.last_status_at": "Ultimo post il {date}",
|
"account.featured_tags.last_status_at": "Ultimo post il {date}",
|
||||||
"account.featured_tags.last_status_never": "Nessun post",
|
"account.featured_tags.last_status_never": "Nessun post",
|
||||||
|
"account.field_overflow": "Mostra il contenuto completo",
|
||||||
"account.filters.all": "Tutte le attività",
|
"account.filters.all": "Tutte le attività",
|
||||||
"account.filters.boosts_toggle": "Mostra le condivisioni",
|
"account.filters.boosts_toggle": "Mostra le condivisioni",
|
||||||
"account.filters.posts_boosts": "Post e condivisioni",
|
"account.filters.posts_boosts": "Post e condivisioni",
|
||||||
@@ -269,6 +271,13 @@
|
|||||||
"closed_registrations_modal.find_another_server": "Trova un altro server",
|
"closed_registrations_modal.find_another_server": "Trova un altro server",
|
||||||
"closed_registrations_modal.preamble": "Mastodon è decentralizzato, quindi, non importa dove crei il tuo profilo, potrai seguire e interagire con chiunque su questo server. Anche se sei tu stesso a ospitarlo!",
|
"closed_registrations_modal.preamble": "Mastodon è decentralizzato, quindi, non importa dove crei il tuo profilo, potrai seguire e interagire con chiunque su questo server. Anche se sei tu stesso a ospitarlo!",
|
||||||
"closed_registrations_modal.title": "Registrazione su Mastodon",
|
"closed_registrations_modal.title": "Registrazione su Mastodon",
|
||||||
|
"collection.share_modal.share_link_label": "Condividi il collegamento d'invito",
|
||||||
|
"collection.share_modal.share_via_post": "Pubblica su Mastodon",
|
||||||
|
"collection.share_modal.share_via_system": "Condividi con…",
|
||||||
|
"collection.share_modal.title": "Condividi la collezione",
|
||||||
|
"collection.share_modal.title_new": "Condividi la tua nuova collezione!",
|
||||||
|
"collection.share_template_other": "Dai un'occhiata a questa fantastica collezione: {link}",
|
||||||
|
"collection.share_template_own": "Dai un'occhiata alla mia collezione: {link}",
|
||||||
"collections.account_count": "{count, plural, one {# account} other {# account}}",
|
"collections.account_count": "{count, plural, one {# account} other {# account}}",
|
||||||
"collections.accounts.empty_description": "Aggiungi fino a {count} account che segui",
|
"collections.accounts.empty_description": "Aggiungi fino a {count} account che segui",
|
||||||
"collections.accounts.empty_title": "Questa collezione è vuota",
|
"collections.accounts.empty_title": "Questa collezione è vuota",
|
||||||
@@ -446,6 +455,7 @@
|
|||||||
"conversation.open": "Visualizza conversazione",
|
"conversation.open": "Visualizza conversazione",
|
||||||
"conversation.with": "Con {names}",
|
"conversation.with": "Con {names}",
|
||||||
"copy_icon_button.copied": "Copiato negli appunti",
|
"copy_icon_button.copied": "Copiato negli appunti",
|
||||||
|
"copy_icon_button.copy_this_text": "Copia il link negli appunti",
|
||||||
"copypaste.copied": "Copiato",
|
"copypaste.copied": "Copiato",
|
||||||
"copypaste.copy_to_clipboard": "Copia negli Appunti",
|
"copypaste.copy_to_clipboard": "Copia negli Appunti",
|
||||||
"directory.federated": "Da un fediverse noto",
|
"directory.federated": "Da un fediverse noto",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
"account.featured.hashtags": "Hashtags",
|
"account.featured.hashtags": "Hashtags",
|
||||||
"account.featured_tags.last_status_at": "Laatste bericht op {date}",
|
"account.featured_tags.last_status_at": "Laatste bericht op {date}",
|
||||||
"account.featured_tags.last_status_never": "Geen berichten",
|
"account.featured_tags.last_status_never": "Geen berichten",
|
||||||
|
"account.field_overflow": "Volledige inhoud tonen",
|
||||||
"account.filters.all": "Alle activiteit",
|
"account.filters.all": "Alle activiteit",
|
||||||
"account.filters.boosts_toggle": "Boosts tonen",
|
"account.filters.boosts_toggle": "Boosts tonen",
|
||||||
"account.filters.posts_boosts": "Berichten en boosts",
|
"account.filters.posts_boosts": "Berichten en boosts",
|
||||||
@@ -270,6 +271,12 @@
|
|||||||
"closed_registrations_modal.find_another_server": "Een andere server zoeken",
|
"closed_registrations_modal.find_another_server": "Een andere server zoeken",
|
||||||
"closed_registrations_modal.preamble": "Mastodon is gedecentraliseerd. Op welke server je ook een account hebt, je kunt overal vandaan mensen op deze server volgen en er mee interactie hebben. Je kunt zelfs zelf een Mastodon-server hosten!",
|
"closed_registrations_modal.preamble": "Mastodon is gedecentraliseerd. Op welke server je ook een account hebt, je kunt overal vandaan mensen op deze server volgen en er mee interactie hebben. Je kunt zelfs zelf een Mastodon-server hosten!",
|
||||||
"closed_registrations_modal.title": "Registreren op Mastodon",
|
"closed_registrations_modal.title": "Registreren op Mastodon",
|
||||||
|
"collection.share_modal.share_via_post": "Bericht op Mastodon",
|
||||||
|
"collection.share_modal.share_via_system": "Delen met…",
|
||||||
|
"collection.share_modal.title": "Verzameling delen",
|
||||||
|
"collection.share_modal.title_new": "Deel je nieuwe verzameling!",
|
||||||
|
"collection.share_template_other": "Bekijk deze coole verzameling: {link}",
|
||||||
|
"collection.share_template_own": "Bekijk mijn nieuwe verzameling: {link}",
|
||||||
"collections.account_count": "{count, plural, one {# account} other {# accounts}}",
|
"collections.account_count": "{count, plural, one {# account} other {# accounts}}",
|
||||||
"collections.accounts.empty_description": "Voeg tot {count} accounts toe die je volgt",
|
"collections.accounts.empty_description": "Voeg tot {count} accounts toe die je volgt",
|
||||||
"collections.accounts.empty_title": "Deze verzameling is leeg",
|
"collections.accounts.empty_title": "Deze verzameling is leeg",
|
||||||
@@ -447,6 +454,7 @@
|
|||||||
"conversation.open": "Gesprek tonen",
|
"conversation.open": "Gesprek tonen",
|
||||||
"conversation.with": "Met {names}",
|
"conversation.with": "Met {names}",
|
||||||
"copy_icon_button.copied": "Gekopieerd naar klembord",
|
"copy_icon_button.copied": "Gekopieerd naar klembord",
|
||||||
|
"copy_icon_button.copy_this_text": "Link kopiëren naar klembord",
|
||||||
"copypaste.copied": "Gekopieerd",
|
"copypaste.copied": "Gekopieerd",
|
||||||
"copypaste.copy_to_clipboard": "Naar klembord kopiëren",
|
"copypaste.copy_to_clipboard": "Naar klembord kopiëren",
|
||||||
"directory.federated": "Fediverse (wat bekend is)",
|
"directory.federated": "Fediverse (wat bekend is)",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
"account.featured.hashtags": "Etiquetas",
|
"account.featured.hashtags": "Etiquetas",
|
||||||
"account.featured_tags.last_status_at": "Última publicação em {date}",
|
"account.featured_tags.last_status_at": "Última publicação em {date}",
|
||||||
"account.featured_tags.last_status_never": "Sem publicações",
|
"account.featured_tags.last_status_never": "Sem publicações",
|
||||||
|
"account.field_overflow": "Mostrar todo o conteúdo",
|
||||||
"account.filters.all": "Toda a atividade",
|
"account.filters.all": "Toda a atividade",
|
||||||
"account.filters.boosts_toggle": "Mostrar partilhas",
|
"account.filters.boosts_toggle": "Mostrar partilhas",
|
||||||
"account.filters.posts_boosts": "Publicações e partilhas",
|
"account.filters.posts_boosts": "Publicações e partilhas",
|
||||||
@@ -162,8 +163,10 @@
|
|||||||
"account_edit.name_modal.edit_title": "Editar o nome a mostrar",
|
"account_edit.name_modal.edit_title": "Editar o nome a mostrar",
|
||||||
"account_edit.save": "Guardar",
|
"account_edit.save": "Guardar",
|
||||||
"account_edit_tags.column_title": "Editar etiquetas em destaque",
|
"account_edit_tags.column_title": "Editar etiquetas em destaque",
|
||||||
|
"account_edit_tags.help_text": "As etiquetas destacadas ajudam os utilizadores a descobrir e interagir com o seu perfil. Aparecem como filtros na vista de atividade da sua página de perfil.",
|
||||||
"account_edit_tags.search_placeholder": "Insira uma etiqueta…",
|
"account_edit_tags.search_placeholder": "Insira uma etiqueta…",
|
||||||
"account_edit_tags.suggestions": "Sugestões:",
|
"account_edit_tags.suggestions": "Sugestões:",
|
||||||
|
"account_edit_tags.tag_status_count": "{count, plural, one {# publicação} other {# publicações}}",
|
||||||
"account_note.placeholder": "Clicar para adicionar nota",
|
"account_note.placeholder": "Clicar para adicionar nota",
|
||||||
"admin.dashboard.daily_retention": "Taxa de retenção de utilizadores por dia após a inscrição",
|
"admin.dashboard.daily_retention": "Taxa de retenção de utilizadores por dia após a inscrição",
|
||||||
"admin.dashboard.monthly_retention": "Taxa de retenção de utilizadores por mês após a inscrição",
|
"admin.dashboard.monthly_retention": "Taxa de retenção de utilizadores por mês após a inscrição",
|
||||||
@@ -267,6 +270,7 @@
|
|||||||
"closed_registrations_modal.preamble": "O Mastodon é descentralizado, por isso não importa onde a tua conta é criada, pois continuarás a poder acompanhar e interagir com qualquer um neste servidor. Podes até alojar o teu próprio servidor!",
|
"closed_registrations_modal.preamble": "O Mastodon é descentralizado, por isso não importa onde a tua conta é criada, pois continuarás a poder acompanhar e interagir com qualquer um neste servidor. Podes até alojar o teu próprio servidor!",
|
||||||
"closed_registrations_modal.title": "Criar uma conta no Mastodon",
|
"closed_registrations_modal.title": "Criar uma conta no Mastodon",
|
||||||
"collections.account_count": "{count, plural, one {# conta} other {# contas}}",
|
"collections.account_count": "{count, plural, one {# conta} other {# contas}}",
|
||||||
|
"collections.accounts.empty_description": "Adicione até {count} contas que segue",
|
||||||
"collections.accounts.empty_title": "Esta coleção está vazia",
|
"collections.accounts.empty_title": "Esta coleção está vazia",
|
||||||
"collections.collection_description": "Descrição",
|
"collections.collection_description": "Descrição",
|
||||||
"collections.collection_name": "Nome",
|
"collections.collection_name": "Nome",
|
||||||
@@ -287,15 +291,25 @@
|
|||||||
"collections.detail.share": "Partilhar esta coleção",
|
"collections.detail.share": "Partilhar esta coleção",
|
||||||
"collections.edit_details": "Editar detalhes",
|
"collections.edit_details": "Editar detalhes",
|
||||||
"collections.error_loading_collections": "Ocorreu um erro ao tentar carregar as suas coleções.",
|
"collections.error_loading_collections": "Ocorreu um erro ao tentar carregar as suas coleções.",
|
||||||
|
"collections.hints.accounts_counter": "{count} / {max} contas",
|
||||||
"collections.hints.add_more_accounts": "Adicione pelo menos {count, plural, one {# conta} other {# contas}} para continuar",
|
"collections.hints.add_more_accounts": "Adicione pelo menos {count, plural, one {# conta} other {# contas}} para continuar",
|
||||||
|
"collections.hints.can_not_remove_more_accounts": "As coleções devem conter pelo menos {count, plural, one {# conta} other {# contas}}. Não é possível remover mais contas.",
|
||||||
"collections.last_updated_at": "Última atualização: {date}",
|
"collections.last_updated_at": "Última atualização: {date}",
|
||||||
"collections.manage_accounts": "Gerir contas",
|
"collections.manage_accounts": "Gerir contas",
|
||||||
"collections.mark_as_sensitive": "Marcar como sensível",
|
"collections.mark_as_sensitive": "Marcar como sensível",
|
||||||
"collections.mark_as_sensitive_hint": "Oculta a descrição e as contas da coleção por trás de um aviso de conteúdo. O nome da coleção ainda estará visível.",
|
"collections.mark_as_sensitive_hint": "Oculta a descrição e as contas da coleção por trás de um aviso de conteúdo. O nome da coleção ainda estará visível.",
|
||||||
|
"collections.name_length_hint": "Limite de 40 carateres",
|
||||||
"collections.new_collection": "Nova coleção",
|
"collections.new_collection": "Nova coleção",
|
||||||
"collections.no_collections_yet": "Ainda não existem coleções.",
|
"collections.no_collections_yet": "Ainda não existem coleções.",
|
||||||
|
"collections.old_last_post_note": "Última publicação há mais de uma semana",
|
||||||
|
"collections.remove_account": "Remover esta conta",
|
||||||
|
"collections.report_collection": "Denunciar esta coleção",
|
||||||
|
"collections.search_accounts_label": "Procurar contas para adicionar…",
|
||||||
|
"collections.search_accounts_max_reached": "Já adicionou o máximo de contas",
|
||||||
|
"collections.sensitive": "Sensível",
|
||||||
"collections.topic_hint": "Adicione uma etiqueta para ajudar outros a entender o tópico principal desta coleção.",
|
"collections.topic_hint": "Adicione uma etiqueta para ajudar outros a entender o tópico principal desta coleção.",
|
||||||
"collections.view_collection": "Ver coleções",
|
"collections.view_collection": "Ver coleções",
|
||||||
|
"collections.view_other_collections_by_user": "Ver outras coleções deste utilizador",
|
||||||
"collections.visibility_public": "Pública",
|
"collections.visibility_public": "Pública",
|
||||||
"collections.visibility_public_hint": "Visível nos resultados de pesquisa e outras áreas onde aparecem recomendações.",
|
"collections.visibility_public_hint": "Visível nos resultados de pesquisa e outras áreas onde aparecem recomendações.",
|
||||||
"collections.visibility_title": "Visibilidade",
|
"collections.visibility_title": "Visibilidade",
|
||||||
@@ -384,6 +398,9 @@
|
|||||||
"confirmations.discard_draft.post.title": "Descartar o rascunho da publicação?",
|
"confirmations.discard_draft.post.title": "Descartar o rascunho da publicação?",
|
||||||
"confirmations.discard_edit_media.confirm": "Descartar",
|
"confirmations.discard_edit_media.confirm": "Descartar",
|
||||||
"confirmations.discard_edit_media.message": "Tens alterações por guardar na descrição da multimédia ou pré-visualização do conteúdo. Descartar mesmo assim?",
|
"confirmations.discard_edit_media.message": "Tens alterações por guardar na descrição da multimédia ou pré-visualização do conteúdo. Descartar mesmo assim?",
|
||||||
|
"confirmations.follow_to_collection.confirm": "Seguir e adicionar à coleção",
|
||||||
|
"confirmations.follow_to_collection.message": "Precisa de seguir {name} para o/a adicionar a uma coleção.",
|
||||||
|
"confirmations.follow_to_collection.title": "Seguir conta?",
|
||||||
"confirmations.follow_to_list.confirm": "Seguir e adicionar à lista",
|
"confirmations.follow_to_list.confirm": "Seguir e adicionar à lista",
|
||||||
"confirmations.follow_to_list.message": "Tens de seguir {name} para o adicionares a uma lista.",
|
"confirmations.follow_to_list.message": "Tens de seguir {name} para o adicionares a uma lista.",
|
||||||
"confirmations.follow_to_list.title": "Seguir utilizador?",
|
"confirmations.follow_to_list.title": "Seguir utilizador?",
|
||||||
@@ -956,6 +973,7 @@
|
|||||||
"report.category.title_account": "perfil",
|
"report.category.title_account": "perfil",
|
||||||
"report.category.title_status": "publicação",
|
"report.category.title_status": "publicação",
|
||||||
"report.close": "Concluído",
|
"report.close": "Concluído",
|
||||||
|
"report.collection_comment": "Porque quer denunciar esta coleção?",
|
||||||
"report.comment.title": "Há mais alguma coisa que devamos saber?",
|
"report.comment.title": "Há mais alguma coisa que devamos saber?",
|
||||||
"report.forward": "Reencaminhar para {target}",
|
"report.forward": "Reencaminhar para {target}",
|
||||||
"report.forward_hint": "A conta pertence a outro servidor. Enviar uma cópia anónima da denúncia para esse servidor também?",
|
"report.forward_hint": "A conta pertence a outro servidor. Enviar uma cópia anónima da denúncia para esse servidor também?",
|
||||||
@@ -977,6 +995,8 @@
|
|||||||
"report.rules.title": "Que regras estão a ser violadas?",
|
"report.rules.title": "Que regras estão a ser violadas?",
|
||||||
"report.statuses.subtitle": "Seleciona tudo o que se aplicar",
|
"report.statuses.subtitle": "Seleciona tudo o que se aplicar",
|
||||||
"report.statuses.title": "Existe alguma publicação que suporte esta denúncia?",
|
"report.statuses.title": "Existe alguma publicação que suporte esta denúncia?",
|
||||||
|
"report.submission_error": "A denúncia não pôde ser enviada",
|
||||||
|
"report.submission_error_details": "Por favor, verifique a sua ligação à rede e tente novamente mais tarde.",
|
||||||
"report.submit": "Enviar",
|
"report.submit": "Enviar",
|
||||||
"report.target": "A denunciar {target}",
|
"report.target": "A denunciar {target}",
|
||||||
"report.thanks.take_action": "Aqui estão as suas opções para controlar o que vê no Mastodon:",
|
"report.thanks.take_action": "Aqui estão as suas opções para controlar o que vê no Mastodon:",
|
||||||
|
|||||||
@@ -269,6 +269,12 @@
|
|||||||
"closed_registrations_modal.find_another_server": "Gjeni shërbyes tjetër",
|
"closed_registrations_modal.find_another_server": "Gjeni shërbyes tjetër",
|
||||||
"closed_registrations_modal.preamble": "Mastodon-i është i decentralizuar, ndaj pavarësisht se ku krijoni llogarinë tuaj, do të jeni në gjendje të ndiqni dhe ndërveproni me këdo në këtë shërbyes. Mundeni madje edhe ta strehoni ju vetë!",
|
"closed_registrations_modal.preamble": "Mastodon-i është i decentralizuar, ndaj pavarësisht se ku krijoni llogarinë tuaj, do të jeni në gjendje të ndiqni dhe ndërveproni me këdo në këtë shërbyes. Mundeni madje edhe ta strehoni ju vetë!",
|
||||||
"closed_registrations_modal.title": "Po regjistroheni në Mastodon",
|
"closed_registrations_modal.title": "Po regjistroheni në Mastodon",
|
||||||
|
"collection.share_modal.share_via_post": "Postoje në Mastodon",
|
||||||
|
"collection.share_modal.share_via_system": "Ndajeni me të tjerë në…",
|
||||||
|
"collection.share_modal.title": "Ndani koleksionin me të tjerë",
|
||||||
|
"collection.share_modal.title_new": "Ndani me të tjerë koleksionin tuaj të ri!",
|
||||||
|
"collection.share_template_other": "Shihni këtë koleksion të hijshëm: {link}",
|
||||||
|
"collection.share_template_own": "Shihni koleksionin tim të ri: {link}",
|
||||||
"collections.account_count": "{count, plural, one {# llogari} other {# llogari}}",
|
"collections.account_count": "{count, plural, one {# llogari} other {# llogari}}",
|
||||||
"collections.accounts.empty_title": "Ky koleksion është i zbrazët",
|
"collections.accounts.empty_title": "Ky koleksion është i zbrazët",
|
||||||
"collections.collection_description": "Përshkrim",
|
"collections.collection_description": "Përshkrim",
|
||||||
@@ -445,6 +451,7 @@
|
|||||||
"conversation.open": "Shfaq bisedën",
|
"conversation.open": "Shfaq bisedën",
|
||||||
"conversation.with": "Me {names}",
|
"conversation.with": "Me {names}",
|
||||||
"copy_icon_button.copied": "U kopjua në të papastër",
|
"copy_icon_button.copied": "U kopjua në të papastër",
|
||||||
|
"copy_icon_button.copy_this_text": "Kopjoje lidhjen në të papastër",
|
||||||
"copypaste.copied": "U kopjua",
|
"copypaste.copied": "U kopjua",
|
||||||
"copypaste.copy_to_clipboard": "Kopjoje në të papastër",
|
"copypaste.copy_to_clipboard": "Kopjoje në të papastër",
|
||||||
"directory.federated": "Nga fedivers i njohur",
|
"directory.federated": "Nga fedivers i njohur",
|
||||||
|
|||||||
@@ -44,9 +44,11 @@
|
|||||||
"account.familiar_followers_two": "{name1} ve {name2} tarafından takip ediliyor",
|
"account.familiar_followers_two": "{name1} ve {name2} tarafından takip ediliyor",
|
||||||
"account.featured": "Öne çıkan",
|
"account.featured": "Öne çıkan",
|
||||||
"account.featured.accounts": "Profiller",
|
"account.featured.accounts": "Profiller",
|
||||||
|
"account.featured.collections": "Koleksiyonlar",
|
||||||
"account.featured.hashtags": "Etiketler",
|
"account.featured.hashtags": "Etiketler",
|
||||||
"account.featured_tags.last_status_at": "Son gönderinin tarihi {date}",
|
"account.featured_tags.last_status_at": "Son gönderinin tarihi {date}",
|
||||||
"account.featured_tags.last_status_never": "Gönderi yok",
|
"account.featured_tags.last_status_never": "Gönderi yok",
|
||||||
|
"account.field_overflow": "Tüm içeriği göster",
|
||||||
"account.filters.all": "Tüm aktiviteler",
|
"account.filters.all": "Tüm aktiviteler",
|
||||||
"account.filters.boosts_toggle": "Yeniden paylaşımları göster",
|
"account.filters.boosts_toggle": "Yeniden paylaşımları göster",
|
||||||
"account.filters.posts_boosts": "Gönderiler ve yeniden paylaşımlar",
|
"account.filters.posts_boosts": "Gönderiler ve yeniden paylaşımlar",
|
||||||
@@ -269,6 +271,13 @@
|
|||||||
"closed_registrations_modal.find_another_server": "Başka sunucu bul",
|
"closed_registrations_modal.find_another_server": "Başka sunucu bul",
|
||||||
"closed_registrations_modal.preamble": "Mastodon merkeziyetsizdir, bu yüzden hesabınızı nerede oluşturursanız oluşturun, bu sunucudaki herhangi birini takip edebilecek veya onunla etkileşebileceksiniz. Hatta kendi sunucunuzu bile barındırabilirsiniz!",
|
"closed_registrations_modal.preamble": "Mastodon merkeziyetsizdir, bu yüzden hesabınızı nerede oluşturursanız oluşturun, bu sunucudaki herhangi birini takip edebilecek veya onunla etkileşebileceksiniz. Hatta kendi sunucunuzu bile barındırabilirsiniz!",
|
||||||
"closed_registrations_modal.title": "Mastodon'a kayıt olmak",
|
"closed_registrations_modal.title": "Mastodon'a kayıt olmak",
|
||||||
|
"collection.share_modal.share_link_label": "Davet bağlantısı",
|
||||||
|
"collection.share_modal.share_via_post": "Mastodon'da paylaş",
|
||||||
|
"collection.share_modal.share_via_system": "Paylaş…",
|
||||||
|
"collection.share_modal.title": "Koleksiyonu paylaş",
|
||||||
|
"collection.share_modal.title_new": "Yeni koleksiyonunuzu paylaşın!",
|
||||||
|
"collection.share_template_other": "Bu harika koleksiyona göz atın: {link}",
|
||||||
|
"collection.share_template_own": "Yeni koleksiyonuma göz atın: {link}",
|
||||||
"collections.account_count": "{count, plural, one {# hesap} other {# hesap}}",
|
"collections.account_count": "{count, plural, one {# hesap} other {# hesap}}",
|
||||||
"collections.accounts.empty_description": "Takip ettiğiniz hesapların sayısını {count} kadar artırın",
|
"collections.accounts.empty_description": "Takip ettiğiniz hesapların sayısını {count} kadar artırın",
|
||||||
"collections.accounts.empty_title": "Bu koleksiyon boş",
|
"collections.accounts.empty_title": "Bu koleksiyon boş",
|
||||||
@@ -446,6 +455,7 @@
|
|||||||
"conversation.open": "Sohbeti görüntüle",
|
"conversation.open": "Sohbeti görüntüle",
|
||||||
"conversation.with": "{names} ile",
|
"conversation.with": "{names} ile",
|
||||||
"copy_icon_button.copied": "Panoya kopyalandı",
|
"copy_icon_button.copied": "Panoya kopyalandı",
|
||||||
|
"copy_icon_button.copy_this_text": "Bağlantıyı panoya kopyala",
|
||||||
"copypaste.copied": "Kopyalandı",
|
"copypaste.copied": "Kopyalandı",
|
||||||
"copypaste.copy_to_clipboard": "Panoya kopyala",
|
"copypaste.copy_to_clipboard": "Panoya kopyala",
|
||||||
"directory.federated": "Bilinen fediverse'lerden",
|
"directory.federated": "Bilinen fediverse'lerden",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
"account.featured.hashtags": "话题",
|
"account.featured.hashtags": "话题",
|
||||||
"account.featured_tags.last_status_at": "上次发言于 {date}",
|
"account.featured_tags.last_status_at": "上次发言于 {date}",
|
||||||
"account.featured_tags.last_status_never": "暂无嘟文",
|
"account.featured_tags.last_status_never": "暂无嘟文",
|
||||||
|
"account.field_overflow": "显示完整内容",
|
||||||
"account.filters.all": "所有活动",
|
"account.filters.all": "所有活动",
|
||||||
"account.filters.boosts_toggle": "显示转嘟",
|
"account.filters.boosts_toggle": "显示转嘟",
|
||||||
"account.filters.posts_boosts": "嘟文与转嘟",
|
"account.filters.posts_boosts": "嘟文与转嘟",
|
||||||
@@ -270,6 +271,13 @@
|
|||||||
"closed_registrations_modal.find_another_server": "查找其他服务器",
|
"closed_registrations_modal.find_another_server": "查找其他服务器",
|
||||||
"closed_registrations_modal.preamble": "Mastodon 是去中心化的,所以无论在哪个实例创建账号,都可以关注本服务器上的账号并与之交流。 或者你还可以自己搭建实例!",
|
"closed_registrations_modal.preamble": "Mastodon 是去中心化的,所以无论在哪个实例创建账号,都可以关注本服务器上的账号并与之交流。 或者你还可以自己搭建实例!",
|
||||||
"closed_registrations_modal.title": "注册 Mastodon 账号",
|
"closed_registrations_modal.title": "注册 Mastodon 账号",
|
||||||
|
"collection.share_modal.share_link_label": "分享链接",
|
||||||
|
"collection.share_modal.share_via_post": "在 Mastodon 上发布",
|
||||||
|
"collection.share_modal.share_via_system": "分享到…",
|
||||||
|
"collection.share_modal.title": "分享收藏列表",
|
||||||
|
"collection.share_modal.title_new": "分享你的新收藏列表!",
|
||||||
|
"collection.share_template_other": "发现了个收藏列表,来看看:{link}",
|
||||||
|
"collection.share_template_own": "我的新收藏列表,来看看:{link}",
|
||||||
"collections.account_count": "{count, plural, other {# 个账号}}",
|
"collections.account_count": "{count, plural, other {# 个账号}}",
|
||||||
"collections.accounts.empty_description": "添加你关注的账号,最多 {count} 个",
|
"collections.accounts.empty_description": "添加你关注的账号,最多 {count} 个",
|
||||||
"collections.accounts.empty_title": "收藏列表为空",
|
"collections.accounts.empty_title": "收藏列表为空",
|
||||||
@@ -447,6 +455,7 @@
|
|||||||
"conversation.open": "查看对话",
|
"conversation.open": "查看对话",
|
||||||
"conversation.with": "与 {names}",
|
"conversation.with": "与 {names}",
|
||||||
"copy_icon_button.copied": "已复制到剪贴板",
|
"copy_icon_button.copied": "已复制到剪贴板",
|
||||||
|
"copy_icon_button.copy_this_text": "复制链接到剪贴板",
|
||||||
"copypaste.copied": "已复制",
|
"copypaste.copied": "已复制",
|
||||||
"copypaste.copy_to_clipboard": "复制到剪贴板",
|
"copypaste.copy_to_clipboard": "复制到剪贴板",
|
||||||
"directory.federated": "来自已知联邦宇宙",
|
"directory.federated": "来自已知联邦宇宙",
|
||||||
|
|||||||
@@ -271,6 +271,13 @@
|
|||||||
"closed_registrations_modal.find_another_server": "尋找另一個伺服器",
|
"closed_registrations_modal.find_another_server": "尋找另一個伺服器",
|
||||||
"closed_registrations_modal.preamble": "Mastodon 是去中心化的,所以無論您於哪個伺服器新增帳號,都可以與此伺服器上的任何人跟隨及互動。您甚至能自行架設自己的伺服器!",
|
"closed_registrations_modal.preamble": "Mastodon 是去中心化的,所以無論您於哪個伺服器新增帳號,都可以與此伺服器上的任何人跟隨及互動。您甚至能自行架設自己的伺服器!",
|
||||||
"closed_registrations_modal.title": "註冊 Mastodon",
|
"closed_registrations_modal.title": "註冊 Mastodon",
|
||||||
|
"collection.share_modal.share_link_label": "分享連結",
|
||||||
|
"collection.share_modal.share_via_post": "於 Mastodon 上發嘟文",
|
||||||
|
"collection.share_modal.share_via_system": "分享至...",
|
||||||
|
"collection.share_modal.title": "分享收藏名單",
|
||||||
|
"collection.share_modal.title_new": "分享您的新收藏名單!",
|
||||||
|
"collection.share_template_other": "來看看這個酷酷的收藏名單:{link}",
|
||||||
|
"collection.share_template_own": "來看看我的新收藏名單:{link}",
|
||||||
"collections.account_count": "{count, plural, other {# 個帳號}}",
|
"collections.account_count": "{count, plural, other {# 個帳號}}",
|
||||||
"collections.accounts.empty_description": "加入最多 {count} 個您跟隨之帳號",
|
"collections.accounts.empty_description": "加入最多 {count} 個您跟隨之帳號",
|
||||||
"collections.accounts.empty_title": "此收藏名單是空的",
|
"collections.accounts.empty_title": "此收藏名單是空的",
|
||||||
@@ -448,6 +455,7 @@
|
|||||||
"conversation.open": "檢視對話",
|
"conversation.open": "檢視對話",
|
||||||
"conversation.with": "與 {names}",
|
"conversation.with": "與 {names}",
|
||||||
"copy_icon_button.copied": "已複製到剪貼簿",
|
"copy_icon_button.copied": "已複製到剪貼簿",
|
||||||
|
"copy_icon_button.copy_this_text": "複製連結至剪貼簿",
|
||||||
"copypaste.copied": "已複製",
|
"copypaste.copied": "已複製",
|
||||||
"copypaste.copy_to_clipboard": "複製到剪貼簿",
|
"copypaste.copy_to_clipboard": "複製到剪貼簿",
|
||||||
"directory.federated": "來自已知聯邦宇宙",
|
"directory.federated": "來自已知聯邦宇宙",
|
||||||
|
|||||||
@@ -6408,15 +6408,15 @@ a.status-card {
|
|||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
|
|
||||||
h1 {
|
:where(h1) {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
&:not(:only-child) {
|
:where(h1:not(:only-child)) {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
strong {
|
strong {
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class AccountStatusesFilter
|
|||||||
if anonymous?
|
if anonymous?
|
||||||
account.statuses.not_local_only.distributable_visibility
|
account.statuses.not_local_only.distributable_visibility
|
||||||
elsif author?
|
elsif author?
|
||||||
account.statuses.all # NOTE: #merge! does not work without the #all
|
exclude_direct? ? account.statuses.where(visibility: %i(public unlisted private)) : account.statuses.all # NOTE: #merge! does not work without the #all
|
||||||
elsif blocked?
|
elsif blocked?
|
||||||
Status.none
|
Status.none
|
||||||
else
|
else
|
||||||
@@ -46,9 +46,15 @@ class AccountStatusesFilter
|
|||||||
end
|
end
|
||||||
|
|
||||||
def filtered_scope
|
def filtered_scope
|
||||||
scope = account.statuses.left_outer_joins(:mentions)
|
scope = account.statuses
|
||||||
|
|
||||||
|
if exclude_direct?
|
||||||
|
scope = scope.where(visibility: follower? ? %i(public unlisted private) : %i(public unlisted))
|
||||||
|
else
|
||||||
|
scope = account.statuses.left_outer_joins(:mentions)
|
||||||
|
scope.merge!(scope.where(visibility: follower? ? %i(public unlisted private) : %i(public unlisted)).or(scope.where(mentions: { account_id: current_account.id })).group(Status.arel_table[:id]))
|
||||||
|
end
|
||||||
|
|
||||||
scope.merge!(scope.where(visibility: follower? ? %i(public unlisted private) : %i(public unlisted)).or(scope.where(mentions: { account_id: current_account.id })).group(Status.arel_table[:id]))
|
|
||||||
scope.merge!(filtered_reblogs_scope) if reblogs_may_occur?
|
scope.merge!(filtered_reblogs_scope) if reblogs_may_occur?
|
||||||
|
|
||||||
scope
|
scope
|
||||||
@@ -123,6 +129,10 @@ class AccountStatusesFilter
|
|||||||
truthy_param?(:only_media)
|
truthy_param?(:only_media)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def exclude_direct?
|
||||||
|
truthy_param?(:exclude_direct)
|
||||||
|
end
|
||||||
|
|
||||||
def exclude_replies?
|
def exclude_replies?
|
||||||
truthy_param?(:exclude_replies)
|
truthy_param?(:exclude_replies)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ class Admin::Metrics::Measure::BaseMeasure
|
|||||||
end
|
end
|
||||||
|
|
||||||
def length_of_period
|
def length_of_period
|
||||||
@length_of_period ||= @end_at - @start_at
|
@length_of_period ||= @end_at.to_date - @start_at.to_date
|
||||||
end
|
end
|
||||||
|
|
||||||
def params
|
def params
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ class TagManager
|
|||||||
def normalize_domain(domain)
|
def normalize_domain(domain)
|
||||||
return if domain.nil?
|
return if domain.nil?
|
||||||
|
|
||||||
uri = Addressable::URI.new
|
Addressable::URI.new.tap do |uri|
|
||||||
uri.host = domain.strip.delete_suffix('/')
|
uri.host = domain.strip.delete_suffix('/')
|
||||||
uri.normalized_host
|
end.normalized_host
|
||||||
end
|
end
|
||||||
|
|
||||||
def local_url?(url)
|
def local_url?(url)
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ class Collection < ApplicationRecord
|
|||||||
scope :with_items, -> { includes(:collection_items).merge(CollectionItem.with_accounts) }
|
scope :with_items, -> { includes(:collection_items).merge(CollectionItem.with_accounts) }
|
||||||
scope :with_tag, -> { includes(:tag) }
|
scope :with_tag, -> { includes(:tag) }
|
||||||
scope :discoverable, -> { where(discoverable: true) }
|
scope :discoverable, -> { where(discoverable: true) }
|
||||||
|
scope :local, -> { where(local: true) }
|
||||||
|
|
||||||
def remote?
|
def remote?
|
||||||
!local?
|
!local?
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ class CollectionItem < ApplicationRecord
|
|||||||
scope :ordered, -> { order(position: :asc) }
|
scope :ordered, -> { order(position: :asc) }
|
||||||
scope :with_accounts, -> { includes(account: [:account_stat, :user]) }
|
scope :with_accounts, -> { includes(account: [:account_stat, :user]) }
|
||||||
scope :not_blocked_by, ->(account) { where.not(accounts: { id: account.blocking }) }
|
scope :not_blocked_by, ->(account) { where.not(accounts: { id: account.blocking }) }
|
||||||
|
scope :local, -> { joins(:collection).merge(Collection.local) }
|
||||||
|
|
||||||
def local_item_with_remote_account?
|
def local_item_with_remote_account?
|
||||||
local? && account&.remote?
|
local? && account&.remote?
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ module DomainNormalizable
|
|||||||
private
|
private
|
||||||
|
|
||||||
def normalize_domain
|
def normalize_domain
|
||||||
self.domain = TagManager.instance.normalize_domain(domain&.strip)
|
self.domain = TagManager.instance.normalize_domain(domain)
|
||||||
rescue Addressable::URI::InvalidURIError
|
rescue Addressable::URI::InvalidURIError
|
||||||
errors.add(:domain, :invalid)
|
errors.add(:domain, :invalid)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class FeaturedTag < ApplicationRecord
|
|||||||
|
|
||||||
normalizes :name, with: ->(name) { name.strip.delete_prefix('#') }
|
normalizes :name, with: ->(name) { name.strip.delete_prefix('#') }
|
||||||
|
|
||||||
scope :by_name, ->(name) { joins(:tag).where(tag: { name: HashtagNormalizer.new.normalize(name) }) }
|
scope :by_name, ->(name) { joins(:tag).where(tag: { name: }) }
|
||||||
|
|
||||||
before_validation :set_tag
|
before_validation :set_tag
|
||||||
|
|
||||||
|
|||||||
@@ -65,8 +65,9 @@ class Tag < ApplicationRecord
|
|||||||
.where(statuses: { id: account.statuses.select(:id).limit(RECENT_STATUS_LIMIT) })
|
.where(statuses: { id: account.statuses.select(:id).limit(RECENT_STATUS_LIMIT) })
|
||||||
.group(:id).order(Arel.star.count.desc)
|
.group(:id).order(Arel.star.count.desc)
|
||||||
}
|
}
|
||||||
scope :matches_name, ->(term) { where(arel_table[:name].lower.matches(arel_table.lower("#{sanitize_sql_like(Tag.normalize(term))}%"), nil, true)) } # Search with case-sensitive to use B-tree index
|
scope :matches_name, ->(term) { where(arel_table[:name].lower.matches(arel_table.lower("#{sanitize_sql_like(normalize_value_for(:name, term))}%"), nil, true)) } # Search with case-sensitive to use B-tree index
|
||||||
|
|
||||||
|
normalizes :name, with: ->(value) { HashtagNormalizer.new.normalize(value) }
|
||||||
normalizes :display_name, with: ->(value) { value.gsub(HASHTAG_INVALID_CHARS_RE, '') }
|
normalizes :display_name, with: ->(value) { value.gsub(HASHTAG_INVALID_CHARS_RE, '') }
|
||||||
|
|
||||||
update_index('tags', :self)
|
update_index('tags', :self)
|
||||||
@@ -111,13 +112,13 @@ class Tag < ApplicationRecord
|
|||||||
|
|
||||||
class << self
|
class << self
|
||||||
def find_or_create_by_names(name_or_names)
|
def find_or_create_by_names(name_or_names)
|
||||||
names = Array(name_or_names).map { |str| [normalize(str), str] }.uniq(&:first)
|
names = Array(name_or_names).map { |str| [normalize_value_for(:name, str), str] }.uniq(&:first)
|
||||||
|
|
||||||
names.map do |(normalized_name, display_name)|
|
names.map do |name, display_name|
|
||||||
tag = begin
|
tag = begin
|
||||||
matching_name(normalized_name).first || create!(name: normalized_name, display_name:)
|
matching_name(name).first || create!(name:, display_name:)
|
||||||
rescue ActiveRecord::RecordNotUnique
|
rescue ActiveRecord::RecordNotUnique
|
||||||
find_normalized(normalized_name)
|
find_normalized(name)
|
||||||
end
|
end
|
||||||
|
|
||||||
yield tag if block_given?
|
yield tag if block_given?
|
||||||
@@ -148,7 +149,7 @@ class Tag < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def matching_name(name_or_names)
|
def matching_name(name_or_names)
|
||||||
names = Array(name_or_names).map { |name| arel_table.lower(normalize(name)) }
|
names = Array(name_or_names).map { |name| arel_table.lower(normalize_value_for(:name, name)) }
|
||||||
|
|
||||||
if names.size == 1
|
if names.size == 1
|
||||||
where(arel_table[:name].lower.eq(names.first))
|
where(arel_table[:name].lower.eq(names.first))
|
||||||
@@ -156,10 +157,6 @@ class Tag < ApplicationRecord
|
|||||||
where(arel_table[:name].lower.in(names))
|
where(arel_table[:name].lower.in(names))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def normalize(str)
|
|
||||||
HashtagNormalizer.new.normalize(str)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -173,6 +170,6 @@ class Tag < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def display_name_matches_name?
|
def display_name_matches_name?
|
||||||
HashtagNormalizer.new.normalize(display_name).casecmp(name).zero?
|
self.class.normalize_value_for(:name, display_name).casecmp(name).zero?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class ActivityPub::FeatureAuthorizationSerializer < ActivityPub::Serializer
|
||||||
|
include RoutingHelper
|
||||||
|
|
||||||
|
attributes :id, :type, :interacting_object, :interaction_target
|
||||||
|
|
||||||
|
def id
|
||||||
|
ap_account_feature_authorization_url(object.account_id, object)
|
||||||
|
end
|
||||||
|
|
||||||
|
def type
|
||||||
|
'FeatureAuthorization'
|
||||||
|
end
|
||||||
|
|
||||||
|
def interaction_target
|
||||||
|
ActivityPub::TagManager.instance.uri_for(object.account)
|
||||||
|
end
|
||||||
|
|
||||||
|
def interacting_object
|
||||||
|
ActivityPub::TagManager.instance.uri_for(object.collection)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class ActivityPub::FeaturedItemSerializer < ActivityPub::Serializer
|
class ActivityPub::FeaturedItemSerializer < ActivityPub::Serializer
|
||||||
attributes :id, :type, :featured_object, :featured_object_type
|
include RoutingHelper
|
||||||
|
|
||||||
|
attributes :id, :type, :featured_object, :featured_object_type,
|
||||||
|
:feature_authorization
|
||||||
|
|
||||||
def id
|
def id
|
||||||
ActivityPub::TagManager.instance.uri_for(object)
|
ActivityPub::TagManager.instance.uri_for(object)
|
||||||
@@ -18,4 +21,12 @@ class ActivityPub::FeaturedItemSerializer < ActivityPub::Serializer
|
|||||||
def featured_object_type
|
def featured_object_type
|
||||||
object.account.actor_type || 'Person'
|
object.account.actor_type || 'Person'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def feature_authorization
|
||||||
|
if object.account.local?
|
||||||
|
ap_account_feature_authorization_url(object.account_id, object)
|
||||||
|
else
|
||||||
|
object.approval_uri
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class ReportService < BaseService
|
|||||||
end
|
end
|
||||||
|
|
||||||
def forward_to_domains
|
def forward_to_domains
|
||||||
@forward_to_domains ||= (@options[:forward_to_domains] || [@target_account.domain]).filter_map { |domain| TagManager.instance.normalize_domain(domain&.strip) }.uniq
|
@forward_to_domains ||= (@options[:forward_to_domains] || [@target_account.domain]).filter_map { |domain| TagManager.instance.normalize_domain(domain) }.uniq
|
||||||
end
|
end
|
||||||
|
|
||||||
def reported_status_ids
|
def reported_status_ids
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class TagSearchService < BaseService
|
|||||||
def ensure_exact_match(results)
|
def ensure_exact_match(results)
|
||||||
return results unless @offset.nil? || @offset.zero?
|
return results unless @offset.nil? || @offset.zero?
|
||||||
|
|
||||||
normalized_query = Tag.normalize(@query)
|
normalized_query = Tag.normalize_value_for(:name, @query)
|
||||||
exact_match = results.find { |tag| tag.name.downcase == normalized_query }
|
exact_match = results.find { |tag| tag.name.downcase == normalized_query }
|
||||||
exact_match ||= Tag.find_normalized(normalized_query)
|
exact_match ||= Tag.find_normalized(normalized_query)
|
||||||
unless exact_match.nil?
|
unless exact_match.nil?
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ Bundler.require(:pam_authentication) if ENV['PAM_ENABLED'] == 'true'
|
|||||||
module Mastodon
|
module Mastodon
|
||||||
class Application < Rails::Application
|
class Application < Rails::Application
|
||||||
# Initialize configuration defaults for originally generated Rails version.
|
# Initialize configuration defaults for originally generated Rails version.
|
||||||
config.load_defaults 8.0
|
config.load_defaults 8.1
|
||||||
|
|
||||||
# Please, add to the `ignore` list any other `lib` subdirectories that do
|
# Please, add to the `ignore` list any other `lib` subdirectories that do
|
||||||
# not contain `.rb` files, or that should not be reloaded or eager loaded.
|
# not contain `.rb` files, or that should not be reloaded or eager loaded.
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ Rails.application.configure do
|
|||||||
|
|
||||||
config.cache_store = :redis_cache_store, REDIS_CONFIGURATION.cache
|
config.cache_store = :redis_cache_store, REDIS_CONFIGURATION.cache
|
||||||
config.public_file_server.headers = {
|
config.public_file_server.headers = {
|
||||||
'Cache-Control' => "public, max-age=#{2.days.to_i}",
|
'cache-control' => "public, max-age=#{2.days.to_i}",
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
config.action_controller.perform_caching = false
|
config.action_controller.perform_caching = false
|
||||||
@@ -67,9 +67,18 @@ Rails.application.configure do
|
|||||||
# Highlight code that triggered database queries in logs.
|
# Highlight code that triggered database queries in logs.
|
||||||
config.active_record.verbose_query_logs = true
|
config.active_record.verbose_query_logs = true
|
||||||
|
|
||||||
|
# Append comments with runtime information tags to SQL queries in logs.
|
||||||
|
config.active_record.query_log_tags_enabled = true
|
||||||
|
|
||||||
# Highlight code that enqueued background job in logs.
|
# Highlight code that enqueued background job in logs.
|
||||||
config.active_job.verbose_enqueue_logs = true
|
config.active_job.verbose_enqueue_logs = true
|
||||||
|
|
||||||
|
# Highlight code that triggered redirect in logs.
|
||||||
|
config.action_dispatch.verbose_redirect_logs = true
|
||||||
|
|
||||||
|
# Suppress logger output for asset requests.
|
||||||
|
config.assets.quiet = true
|
||||||
|
|
||||||
# Raises error for missing translations.
|
# Raises error for missing translations.
|
||||||
# config.i18n.raise_on_missing_translations = true
|
# config.i18n.raise_on_missing_translations = true
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ Rails.application.configure do
|
|||||||
# Eager load code on boot for better performance and memory savings (ignored by Rake tasks).
|
# Eager load code on boot for better performance and memory savings (ignored by Rake tasks).
|
||||||
config.eager_load = true
|
config.eager_load = true
|
||||||
|
|
||||||
# Full error reports are disabled and caching is turned on.
|
# Full error reports are disabled.
|
||||||
config.consider_all_requests_local = false
|
config.consider_all_requests_local = false
|
||||||
|
|
||||||
|
# Turn on fragment caching in view templates.
|
||||||
config.action_controller.perform_caching = true
|
config.action_controller.perform_caching = true
|
||||||
|
|
||||||
# Do not fallback to assets pipeline if a precompiled asset is missed.
|
# Do not fallback to assets pipeline if a precompiled asset is missed.
|
||||||
@@ -55,9 +57,8 @@ Rails.application.configure do
|
|||||||
# Use a different cache store in production.
|
# Use a different cache store in production.
|
||||||
config.cache_store = :redis_cache_store, REDIS_CONFIGURATION.cache
|
config.cache_store = :redis_cache_store, REDIS_CONFIGURATION.cache
|
||||||
|
|
||||||
# Disable caching for Action Mailer templates even if Action Controller
|
# Prevent health checks from clogging up the logs.
|
||||||
# caching is enabled.
|
config.silence_healthcheck_path = '/health'
|
||||||
config.action_mailer.perform_caching = false
|
|
||||||
|
|
||||||
# Don't log any deprecations.
|
# Don't log any deprecations.
|
||||||
config.active_support.report_deprecations = false
|
config.active_support.report_deprecations = false
|
||||||
@@ -105,6 +106,9 @@ Rails.application.configure do
|
|||||||
'Referrer-Policy' => 'same-origin',
|
'Referrer-Policy' => 'same-origin',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Only use :id for inspections in production.
|
||||||
|
config.active_record.attributes_for_inspect = [:id]
|
||||||
|
|
||||||
# Enable DNS rebinding protection and other `Host` header attacks.
|
# Enable DNS rebinding protection and other `Host` header attacks.
|
||||||
# config.hosts = [
|
# config.hosts = [
|
||||||
# "example.com", # Allow requests from example.com
|
# "example.com", # Allow requests from example.com
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# TODO: Starting with Rails 7.0, the framework default is true for this setting.
|
# In the Devise confirmations#show action, a redirect_to is called:
|
||||||
# This location in devise redirects and we can't hook in or override:
|
# https://github.com/heartcombo/devise/blob/v5.0.0/app/controllers/devise/confirmations_controller.rb#L28
|
||||||
# https://github.com/heartcombo/devise/blob/v4.9.3/app/controllers/devise/confirmations_controller.rb#L28
|
#
|
||||||
# When solution is found, this setting can go back to default.
|
# We override the `after_confirmation_path_for` method in a way which sometimes
|
||||||
Rails.application.config.action_controller.raise_on_open_redirects = false
|
# returns raw URLs to external hosts, as part of the auth workflow.
|
||||||
|
# Discussion: https://github.com/mastodon/mastodon/pull/36505#discussion_r2782876831
|
||||||
|
|
||||||
|
Rails.application.reloader.to_prepare do
|
||||||
|
ActionController::Base.action_on_open_redirect = :log
|
||||||
|
end
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ fr:
|
|||||||
activerecord:
|
activerecord:
|
||||||
attributes:
|
attributes:
|
||||||
poll:
|
poll:
|
||||||
expires_at: Date d'expiration
|
expires_at: Date d'échéance
|
||||||
options: Choix
|
options: Choix
|
||||||
user:
|
user:
|
||||||
agreement: Contrat de service
|
agreement: Contrat de service
|
||||||
|
|||||||
@@ -267,6 +267,7 @@ it:
|
|||||||
demote_user_html: "%{name} ha retrocesso l'utente %{target}"
|
demote_user_html: "%{name} ha retrocesso l'utente %{target}"
|
||||||
destroy_announcement_html: "%{name} ha eliminato l'annuncio %{target}"
|
destroy_announcement_html: "%{name} ha eliminato l'annuncio %{target}"
|
||||||
destroy_canonical_email_block_html: "%{name} ha sbloccato l'e-mail con l'hash %{target}"
|
destroy_canonical_email_block_html: "%{name} ha sbloccato l'e-mail con l'hash %{target}"
|
||||||
|
destroy_collection_html: "%{name} ha rimosso la collezione di %{target}"
|
||||||
destroy_custom_emoji_html: "%{name} ha eliminato l'emoji %{target}"
|
destroy_custom_emoji_html: "%{name} ha eliminato l'emoji %{target}"
|
||||||
destroy_domain_allow_html: "%{name} non ha consentito la federazione con il dominio %{target}"
|
destroy_domain_allow_html: "%{name} non ha consentito la federazione con il dominio %{target}"
|
||||||
destroy_domain_block_html: "%{name} ha sbloccato il dominio %{target}"
|
destroy_domain_block_html: "%{name} ha sbloccato il dominio %{target}"
|
||||||
@@ -306,6 +307,7 @@ it:
|
|||||||
unsilence_account_html: "%{name} ha riattivato l'account di %{target}"
|
unsilence_account_html: "%{name} ha riattivato l'account di %{target}"
|
||||||
unsuspend_account_html: "%{name} ha annullato la sospensione dell'account di %{target}"
|
unsuspend_account_html: "%{name} ha annullato la sospensione dell'account di %{target}"
|
||||||
update_announcement_html: "%{name} ha aggiornato l'annuncio %{target}"
|
update_announcement_html: "%{name} ha aggiornato l'annuncio %{target}"
|
||||||
|
update_collection_html: "%{name} ha aggiornato la collezione di %{target}"
|
||||||
update_custom_emoji_html: "%{name} ha aggiornato emoji %{target}"
|
update_custom_emoji_html: "%{name} ha aggiornato emoji %{target}"
|
||||||
update_domain_block_html: "%{name} ha aggiornato il blocco dominio per %{target}"
|
update_domain_block_html: "%{name} ha aggiornato il blocco dominio per %{target}"
|
||||||
update_ip_block_html: "%{name} ha cambiato la regola per l'IP %{target}"
|
update_ip_block_html: "%{name} ha cambiato la regola per l'IP %{target}"
|
||||||
|
|||||||
@@ -267,6 +267,7 @@ pt-PT:
|
|||||||
demote_user_html: "%{name} despromoveu o utilizador %{target}"
|
demote_user_html: "%{name} despromoveu o utilizador %{target}"
|
||||||
destroy_announcement_html: "%{name} eliminou a mensagem de manutenção %{target}"
|
destroy_announcement_html: "%{name} eliminou a mensagem de manutenção %{target}"
|
||||||
destroy_canonical_email_block_html: "%{name} desbloqueou o e-mail com a hash %{target}"
|
destroy_canonical_email_block_html: "%{name} desbloqueou o e-mail com a hash %{target}"
|
||||||
|
destroy_collection_html: "%{name} removeu a coleção de %{target}"
|
||||||
destroy_custom_emoji_html: "%{name} eliminou o emoji %{target}"
|
destroy_custom_emoji_html: "%{name} eliminou o emoji %{target}"
|
||||||
destroy_domain_allow_html: "%{name} bloqueou a federação com o domínio %{target}"
|
destroy_domain_allow_html: "%{name} bloqueou a federação com o domínio %{target}"
|
||||||
destroy_domain_block_html: "%{name} desbloqueou o domínio %{target}"
|
destroy_domain_block_html: "%{name} desbloqueou o domínio %{target}"
|
||||||
@@ -306,6 +307,7 @@ pt-PT:
|
|||||||
unsilence_account_html: "%{name} deixou de limitar a conta de %{target}"
|
unsilence_account_html: "%{name} deixou de limitar a conta de %{target}"
|
||||||
unsuspend_account_html: "%{name} desativou a suspensão de %{target}"
|
unsuspend_account_html: "%{name} desativou a suspensão de %{target}"
|
||||||
update_announcement_html: "%{name} atualizou a mensagem de manutenção %{target}"
|
update_announcement_html: "%{name} atualizou a mensagem de manutenção %{target}"
|
||||||
|
update_collection_html: "%{name} atualizou a coleção de %{target}"
|
||||||
update_custom_emoji_html: "%{name} atualizou o emoji %{target}"
|
update_custom_emoji_html: "%{name} atualizou o emoji %{target}"
|
||||||
update_domain_block_html: "%{name} atualizou o bloqueio de domínio para %{target}"
|
update_domain_block_html: "%{name} atualizou o bloqueio de domínio para %{target}"
|
||||||
update_ip_block_html: "%{name} alterou regra para o IP %{target}"
|
update_ip_block_html: "%{name} alterou regra para o IP %{target}"
|
||||||
@@ -686,6 +688,7 @@ pt-PT:
|
|||||||
cancel: Cancelar
|
cancel: Cancelar
|
||||||
category: Categoria
|
category: Categoria
|
||||||
category_description_html: A razão pela qual esta conta e/ou conteúdo foi denunciado será citada na comunicação com a conta denunciada
|
category_description_html: A razão pela qual esta conta e/ou conteúdo foi denunciado será citada na comunicação com a conta denunciada
|
||||||
|
collections: Coleções (%{count})
|
||||||
comment:
|
comment:
|
||||||
none: Nenhum
|
none: Nenhum
|
||||||
comment_description_html: 'Para fornecer mais informações, %{name} escreveu:'
|
comment_description_html: 'Para fornecer mais informações, %{name} escreveu:'
|
||||||
@@ -721,6 +724,7 @@ pt-PT:
|
|||||||
resolved_msg: Denúncia resolvida com sucesso!
|
resolved_msg: Denúncia resolvida com sucesso!
|
||||||
skip_to_actions: Passar para as ações
|
skip_to_actions: Passar para as ações
|
||||||
status: Estado
|
status: Estado
|
||||||
|
statuses: Publicações (%{count})
|
||||||
statuses_description_html: O conteúdo ofensivo será citado na comunicação com a conta denunciada
|
statuses_description_html: O conteúdo ofensivo será citado na comunicação com a conta denunciada
|
||||||
summary:
|
summary:
|
||||||
action_preambles:
|
action_preambles:
|
||||||
|
|||||||
@@ -267,6 +267,7 @@ tr:
|
|||||||
demote_user_html: "%{name}, %{target} kullanıcısını düşürdü"
|
demote_user_html: "%{name}, %{target} kullanıcısını düşürdü"
|
||||||
destroy_announcement_html: "%{name}, %{target} duyurusunu sildi"
|
destroy_announcement_html: "%{name}, %{target} duyurusunu sildi"
|
||||||
destroy_canonical_email_block_html: "%{name}, %{target} karmasıyla e-posta engelini kaldırdı"
|
destroy_canonical_email_block_html: "%{name}, %{target} karmasıyla e-posta engelini kaldırdı"
|
||||||
|
destroy_collection_html: "%{name}, %{target} kullanıcısının koleksiyonunu kaldırdı"
|
||||||
destroy_custom_emoji_html: "%{name}, %{target} ifadesini sildi"
|
destroy_custom_emoji_html: "%{name}, %{target} ifadesini sildi"
|
||||||
destroy_domain_allow_html: "%{name}, %{target} alan adıyla birlik iznini kaldırdı"
|
destroy_domain_allow_html: "%{name}, %{target} alan adıyla birlik iznini kaldırdı"
|
||||||
destroy_domain_block_html: "%{name}, %{target} alan adı engelini kaldırdı"
|
destroy_domain_block_html: "%{name}, %{target} alan adı engelini kaldırdı"
|
||||||
@@ -306,6 +307,7 @@ tr:
|
|||||||
unsilence_account_html: "%{name}, %{target} kullanıcısının hesabının sessizliğini kaldırdı"
|
unsilence_account_html: "%{name}, %{target} kullanıcısının hesabının sessizliğini kaldırdı"
|
||||||
unsuspend_account_html: "%{name}, %{target} kullanıcısının hesabının askı durumunu kaldırdı"
|
unsuspend_account_html: "%{name}, %{target} kullanıcısının hesabının askı durumunu kaldırdı"
|
||||||
update_announcement_html: "%{name}, %{target} duyurusunu güncelledi"
|
update_announcement_html: "%{name}, %{target} duyurusunu güncelledi"
|
||||||
|
update_collection_html: "%{name}, %{target} kullanıcısının koleksiyonunu güncelledi"
|
||||||
update_custom_emoji_html: "%{name}, %{target} emojisini güncelledi"
|
update_custom_emoji_html: "%{name}, %{target} emojisini güncelledi"
|
||||||
update_domain_block_html: "%{name}, %{target} alan adının engelini güncelledi"
|
update_domain_block_html: "%{name}, %{target} alan adının engelini güncelledi"
|
||||||
update_ip_block_html: "%{name}, %{target} IP adresi için kuralı güncelledi"
|
update_ip_block_html: "%{name}, %{target} IP adresi için kuralı güncelledi"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class RedirectWithVary < ActionDispatch::Routing::PathRedirect
|
|||||||
end
|
end
|
||||||
|
|
||||||
def redirect_with_vary(path)
|
def redirect_with_vary(path)
|
||||||
RedirectWithVary.new(301, path)
|
RedirectWithVary.new(301, path, caller(1..1).first)
|
||||||
end
|
end
|
||||||
|
|
||||||
Rails.application.routes.draw do
|
Rails.application.routes.draw do
|
||||||
@@ -125,6 +125,7 @@ Rails.application.routes.draw do
|
|||||||
scope path: 'ap', as: 'ap' do
|
scope path: 'ap', as: 'ap' do
|
||||||
resources :accounts, path: 'users', only: [:show], param: :id, concerns: :account_resources do
|
resources :accounts, path: 'users', only: [:show], param: :id, concerns: :account_resources do
|
||||||
resources :collection_items, only: [:show]
|
resources :collection_items, only: [:show]
|
||||||
|
resources :feature_authorizations, only: [:show], module: :activitypub
|
||||||
resources :featured_collections, only: [:index], module: :activitypub
|
resources :featured_collections, only: [:index], module: :activitypub
|
||||||
|
|
||||||
resources :statuses, only: [:show] do
|
resources :statuses, only: [:show] do
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ module Mastodon
|
|||||||
|
|
||||||
def api_versions
|
def api_versions
|
||||||
{
|
{
|
||||||
mastodon: 7,
|
mastodon: 8,
|
||||||
glitch: 1,
|
glitch: 1,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -64,6 +64,9 @@ RSpec.describe AccountStatusesFilter do
|
|||||||
|
|
||||||
expect(results_for(exclude_reblogs: true))
|
expect(results_for(exclude_reblogs: true))
|
||||||
.to all(satisfy { |status| !status.reblog? })
|
.to all(satisfy { |status| !status.reblog? })
|
||||||
|
|
||||||
|
expect(results_for(exclude_direct: true))
|
||||||
|
.to all(satisfy { |status| !status.direct_visibility? })
|
||||||
end
|
end
|
||||||
|
|
||||||
def results_for(params)
|
def results_for(params)
|
||||||
@@ -77,6 +80,18 @@ RSpec.describe AccountStatusesFilter do
|
|||||||
let(:current_account) { nil }
|
let(:current_account) { nil }
|
||||||
let(:direct_status) { nil }
|
let(:direct_status) { nil }
|
||||||
|
|
||||||
|
context 'when rejecting direct messages' do
|
||||||
|
let(:params) { { exclude_direct: true } }
|
||||||
|
|
||||||
|
it 'returns only public statuses, public replies, and public reblogs' do
|
||||||
|
expect(results_unique_visibilities).to match_array %w(unlisted public)
|
||||||
|
|
||||||
|
expect(results_in_reply_to_ids).to_not be_empty
|
||||||
|
|
||||||
|
expect(results_reblog_of_ids).to_not be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it 'returns only public statuses, public replies, and public reblogs' do
|
it 'returns only public statuses, public replies, and public reblogs' do
|
||||||
expect(results_unique_visibilities).to match_array %w(unlisted public)
|
expect(results_unique_visibilities).to match_array %w(unlisted public)
|
||||||
|
|
||||||
@@ -95,6 +110,14 @@ RSpec.describe AccountStatusesFilter do
|
|||||||
account.block!(current_account)
|
account.block!(current_account)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when rejecting direct messages' do
|
||||||
|
let(:params) { { exclude_direct: true } }
|
||||||
|
|
||||||
|
it 'returns nothing' do
|
||||||
|
expect(subject.to_a).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it 'returns nothing' do
|
it 'returns nothing' do
|
||||||
expect(subject.to_a).to be_empty
|
expect(subject.to_a).to be_empty
|
||||||
end
|
end
|
||||||
@@ -121,6 +144,18 @@ RSpec.describe AccountStatusesFilter do
|
|||||||
current_account.follow!(account)
|
current_account.follow!(account)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when rejecting direct messages' do
|
||||||
|
let(:params) { { exclude_direct: true } }
|
||||||
|
|
||||||
|
it 'returns private statuses, replies, and reblogs' do
|
||||||
|
expect(results_unique_visibilities).to match_array %w(private unlisted public)
|
||||||
|
|
||||||
|
expect(results_in_reply_to_ids).to_not be_empty
|
||||||
|
|
||||||
|
expect(results_reblog_of_ids).to_not be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it 'returns private statuses, replies, and reblogs' do
|
it 'returns private statuses, replies, and reblogs' do
|
||||||
expect(results_unique_visibilities).to match_array %w(private unlisted public)
|
expect(results_unique_visibilities).to match_array %w(private unlisted public)
|
||||||
|
|
||||||
@@ -135,6 +170,8 @@ RSpec.describe AccountStatusesFilter do
|
|||||||
it 'returns the direct status' do
|
it 'returns the direct status' do
|
||||||
expect(results_ids).to include(direct_status.id)
|
expect(results_ids).to include(direct_status.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'filter params'
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'filter params'
|
it_behaves_like 'filter params'
|
||||||
@@ -143,6 +180,18 @@ RSpec.describe AccountStatusesFilter do
|
|||||||
context 'when accessed by a non-follower' do
|
context 'when accessed by a non-follower' do
|
||||||
let(:current_account) { Fabricate(:account) }
|
let(:current_account) { Fabricate(:account) }
|
||||||
|
|
||||||
|
context 'when rejecting direct messages' do
|
||||||
|
let(:params) { { exclude_direct: true } }
|
||||||
|
|
||||||
|
it 'returns private statuses, replies, and reblogs' do
|
||||||
|
expect(results_unique_visibilities).to match_array %w(unlisted public)
|
||||||
|
|
||||||
|
expect(results_in_reply_to_ids).to_not be_empty
|
||||||
|
|
||||||
|
expect(results_reblog_of_ids).to_not be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it 'returns only public statuses, replies, and reblogs' do
|
it 'returns only public statuses, replies, and reblogs' do
|
||||||
expect(results_unique_visibilities).to match_array %w(unlisted public)
|
expect(results_unique_visibilities).to match_array %w(unlisted public)
|
||||||
|
|
||||||
@@ -157,6 +206,8 @@ RSpec.describe AccountStatusesFilter do
|
|||||||
it 'returns the private status' do
|
it 'returns the private status' do
|
||||||
expect(results_ids).to include(private_status.id)
|
expect(results_ids).to include(private_status.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'filter params'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when blocking a reblogged account' do
|
context 'when blocking a reblogged account' do
|
||||||
|
|||||||
@@ -93,24 +93,22 @@ RSpec.describe FeaturedTag do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe '#display_name' do
|
describe '#display_name' do
|
||||||
subject { Fabricate.build :featured_tag, name: name, tag: tag }
|
subject { featured_tag.display_name }
|
||||||
|
|
||||||
context 'with a name value present' do
|
let(:featured_tag) { Fabricate.build :featured_tag, name: name, tag: tag }
|
||||||
let(:name) { 'Test' }
|
|
||||||
|
context 'with a name value present on the featured tag' do
|
||||||
|
let(:name) { 'FeaturedTagName' }
|
||||||
let(:tag) { nil }
|
let(:tag) { nil }
|
||||||
|
|
||||||
it 'uses name value' do
|
it { is_expected.to eq('FeaturedTagName') }
|
||||||
expect(subject.display_name).to eq('Test')
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with a missing name value but a present tag' do
|
context 'with a missing name value but a present linked tag' do
|
||||||
let(:name) { nil }
|
let(:name) { nil }
|
||||||
let(:tag) { Fabricate.build :tag, name: 'Tester' }
|
let(:tag) { Fabricate.build :tag, display_name: 'LinkedTagDisplayName' }
|
||||||
|
|
||||||
it 'uses name value' do
|
it { is_expected.to eq('LinkedTagDisplayName') }
|
||||||
expect(subject.display_name).to eq('Tester')
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
32
spec/models/form/redirect_spec.rb
Normal file
32
spec/models/form/redirect_spec.rb
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe Form::Redirect do
|
||||||
|
describe 'Validations' do
|
||||||
|
it { is_expected.to validate_presence_of(:acct) }
|
||||||
|
|
||||||
|
describe 'target account validation' do
|
||||||
|
subject { described_class.new(account:) }
|
||||||
|
|
||||||
|
context 'when target_account is missing' do
|
||||||
|
let(:account) { Fabricate.build :account }
|
||||||
|
|
||||||
|
it { is_expected.to_not allow_value(nil).for(:target_account).against(:acct).with_message(I18n.t('migrations.errors.not_found')) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when account already moved' do
|
||||||
|
let(:account) { Fabricate.build :account, moved_to_account_id: target_account.id }
|
||||||
|
let(:target_account) { Fabricate :account }
|
||||||
|
|
||||||
|
it { is_expected.to_not allow_value(target_account).for(:target_account).against(:acct).with_message(I18n.t('migrations.errors.already_moved')) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when moving to self' do
|
||||||
|
let(:account) { Fabricate :account }
|
||||||
|
|
||||||
|
it { is_expected.to_not allow_value(account).for(:target_account).against(:acct).with_message(I18n.t('migrations.errors.move_to_self')) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -40,26 +40,40 @@ RSpec.describe Tag do
|
|||||||
I18n.t('tags.does_not_match_previous_name')
|
I18n.t('tags.does_not_match_previous_name')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'invalid with #' do
|
describe 'when skipping normalizations' do
|
||||||
expect(described_class.new(name: '#hello_world')).to_not be_valid
|
subject { described_class.new }
|
||||||
|
|
||||||
|
before { subject.attributes[:name] = name }
|
||||||
|
|
||||||
|
context 'with a # in string' do
|
||||||
|
let(:name) { '#hello_world' }
|
||||||
|
|
||||||
|
it { is_expected.to_not be_valid }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a . in string' do
|
||||||
|
let(:name) { '.abcdef123' }
|
||||||
|
|
||||||
|
it { is_expected.to_not be_valid }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a space in string' do
|
||||||
|
let(:name) { 'hello world' }
|
||||||
|
|
||||||
|
it { is_expected.to_not be_valid }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'invalid with .' do
|
it { is_expected.to allow_value('aesthetic').for(:name) }
|
||||||
expect(described_class.new(name: '.abcdef123')).to_not be_valid
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'invalid with spaces' do
|
|
||||||
expect(described_class.new(name: 'hello world')).to_not be_valid
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'valid with aesthetic' do
|
|
||||||
expect(described_class.new(name: 'aesthetic')).to be_valid
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'Normalizations' do
|
describe 'Normalizations' do
|
||||||
it { is_expected.to normalize(:display_name).from('#HelloWorld').to('HelloWorld') }
|
it { is_expected.to normalize(:display_name).from('#HelloWorld').to('HelloWorld') }
|
||||||
it { is_expected.to normalize(:display_name).from('Hello❤️World').to('HelloWorld') }
|
it { is_expected.to normalize(:display_name).from('Hello❤️World').to('HelloWorld') }
|
||||||
|
|
||||||
|
it { is_expected.to normalize(:name).from('#hello_world').to('hello_world') }
|
||||||
|
it { is_expected.to normalize(:name).from('hello world').to('helloworld') }
|
||||||
|
it { is_expected.to normalize(:name).from('.abcdef123').to('abcdef123') }
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'HASHTAG_RE' do
|
describe 'HASHTAG_RE' do
|
||||||
@@ -210,7 +224,7 @@ RSpec.describe Tag do
|
|||||||
upcase_string = 'abcABCabcABCやゆよ'
|
upcase_string = 'abcABCabcABCやゆよ'
|
||||||
downcase_string = 'abcabcabcabcやゆよ'
|
downcase_string = 'abcabcabcabcやゆよ'
|
||||||
|
|
||||||
tag = Fabricate(:tag, name: HashtagNormalizer.new.normalize(downcase_string))
|
tag = Fabricate(:tag, name: downcase_string)
|
||||||
expect(described_class.find_normalized(upcase_string)).to eq tag
|
expect(described_class.find_normalized(upcase_string)).to eq tag
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -239,7 +253,7 @@ RSpec.describe Tag do
|
|||||||
upcase_string = 'abcABCabcABCやゆよ'
|
upcase_string = 'abcABCabcABCやゆよ'
|
||||||
downcase_string = 'abcabcabcabcやゆよ'
|
downcase_string = 'abcabcabcabcやゆよ'
|
||||||
|
|
||||||
tag = Fabricate(:tag, name: HashtagNormalizer.new.normalize(downcase_string))
|
tag = Fabricate(:tag, name: downcase_string)
|
||||||
expect(described_class.matches_name(upcase_string)).to eq [tag]
|
expect(described_class.matches_name(upcase_string)).to eq [tag]
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -254,7 +268,7 @@ RSpec.describe Tag do
|
|||||||
upcase_string = 'abcABCabcABCやゆよ'
|
upcase_string = 'abcABCabcABCやゆよ'
|
||||||
downcase_string = 'abcabcabcabcやゆよ'
|
downcase_string = 'abcabcabcabcやゆよ'
|
||||||
|
|
||||||
tag = Fabricate(:tag, name: HashtagNormalizer.new.normalize(downcase_string))
|
tag = Fabricate(:tag, name: downcase_string)
|
||||||
expect(described_class.matching_name(upcase_string)).to eq [tag]
|
expect(described_class.matching_name(upcase_string)).to eq [tag]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
49
spec/requests/activitypub/feature_authorizations_spec.rb
Normal file
49
spec/requests/activitypub/feature_authorizations_spec.rb
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'ActivityPub FeatureAuthorization endpoint' do
|
||||||
|
describe 'GET /ap/accounts/:account_id/feature_authorizations/:collection_item_id' do
|
||||||
|
let(:account) { Fabricate(:account) }
|
||||||
|
let(:collection) { Fabricate(:collection) }
|
||||||
|
let(:collection_item) { Fabricate(:collection_item, collection:, account:, state:) }
|
||||||
|
|
||||||
|
context 'with an accepted collection item' do
|
||||||
|
let(:state) { :accepted }
|
||||||
|
|
||||||
|
it 'returns http success and activity json' do
|
||||||
|
get ap_account_feature_authorization_path(account.id, collection_item)
|
||||||
|
|
||||||
|
expect(response)
|
||||||
|
.to have_http_status(200)
|
||||||
|
expect(response.media_type)
|
||||||
|
.to eq 'application/activity+json'
|
||||||
|
|
||||||
|
expect(response.parsed_body)
|
||||||
|
.to include(type: 'FeatureAuthorization')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_examples 'not found' do
|
||||||
|
it 'returns http not found' do
|
||||||
|
get ap_account_feature_authorization_path(collection.account_id, collection_item)
|
||||||
|
|
||||||
|
expect(response)
|
||||||
|
.to have_http_status(404)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a revoked collection item' do
|
||||||
|
let(:state) { :revoked }
|
||||||
|
|
||||||
|
it_behaves_like 'not found'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a collection item featuring a remote account' do
|
||||||
|
let(:account) { Fabricate(:remote_account) }
|
||||||
|
let(:state) { :accepted }
|
||||||
|
|
||||||
|
it_behaves_like 'not found'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe ActivityPub::FeatureAuthorizationSerializer do
|
||||||
|
include RoutingHelper
|
||||||
|
|
||||||
|
subject { serialized_record_json(collection_item, described_class, adapter: ActivityPub::Adapter) }
|
||||||
|
|
||||||
|
describe 'serializing an object' do
|
||||||
|
let(:collection_item) { Fabricate(:collection_item) }
|
||||||
|
let(:tag_manager) { ActivityPub::TagManager.instance }
|
||||||
|
|
||||||
|
it 'returns the expected json structure' do
|
||||||
|
expect(subject)
|
||||||
|
.to include(
|
||||||
|
'type' => 'FeatureAuthorization',
|
||||||
|
'id' => ap_account_feature_authorization_url(collection_item.account_id, collection_item),
|
||||||
|
'interactionTarget' => tag_manager.uri_for(collection_item.account),
|
||||||
|
'interactingObject' => tag_manager.uri_for(collection_item.collection)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -3,6 +3,8 @@
|
|||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe ActivityPub::FeaturedCollectionSerializer do
|
RSpec.describe ActivityPub::FeaturedCollectionSerializer do
|
||||||
|
include RoutingHelper
|
||||||
|
|
||||||
subject { serialized_record_json(collection, described_class, adapter: ActivityPub::Adapter) }
|
subject { serialized_record_json(collection, described_class, adapter: ActivityPub::Adapter) }
|
||||||
|
|
||||||
let(:collection) do
|
let(:collection) do
|
||||||
@@ -35,12 +37,14 @@ RSpec.describe ActivityPub::FeaturedCollectionSerializer do
|
|||||||
'type' => 'FeaturedItem',
|
'type' => 'FeaturedItem',
|
||||||
'featuredObject' => ActivityPub::TagManager.instance.uri_for(collection_items.first.account),
|
'featuredObject' => ActivityPub::TagManager.instance.uri_for(collection_items.first.account),
|
||||||
'featuredObjectType' => 'Person',
|
'featuredObjectType' => 'Person',
|
||||||
|
'featureAuthorization' => ap_account_feature_authorization_url(collection_items.first.account_id, collection_items.first),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'id' => ActivityPub::TagManager.instance.uri_for(collection_items.last),
|
'id' => ActivityPub::TagManager.instance.uri_for(collection_items.last),
|
||||||
'type' => 'FeaturedItem',
|
'type' => 'FeaturedItem',
|
||||||
'featuredObject' => ActivityPub::TagManager.instance.uri_for(collection_items.last.account),
|
'featuredObject' => ActivityPub::TagManager.instance.uri_for(collection_items.last.account),
|
||||||
'featuredObjectType' => 'Person',
|
'featuredObjectType' => 'Person',
|
||||||
|
'featureAuthorization' => ap_account_feature_authorization_url(collection_items.last.account_id, collection_items.last),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
'published' => match_api_datetime_format,
|
'published' => match_api_datetime_format,
|
||||||
|
|||||||
@@ -3,16 +3,37 @@
|
|||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe ActivityPub::FeaturedItemSerializer do
|
RSpec.describe ActivityPub::FeaturedItemSerializer do
|
||||||
|
include RoutingHelper
|
||||||
|
|
||||||
subject { serialized_record_json(collection_item, described_class, adapter: ActivityPub::Adapter) }
|
subject { serialized_record_json(collection_item, described_class, adapter: ActivityPub::Adapter) }
|
||||||
|
|
||||||
let(:collection_item) { Fabricate(:collection_item) }
|
let(:collection_item) { Fabricate(:collection_item) }
|
||||||
|
|
||||||
it 'serializes to the expected structure' do
|
context 'when a local account is featured' do
|
||||||
expect(subject).to include({
|
it 'serializes to the expected structure' do
|
||||||
'type' => 'FeaturedItem',
|
expect(subject).to include({
|
||||||
'id' => ActivityPub::TagManager.instance.uri_for(collection_item),
|
'type' => 'FeaturedItem',
|
||||||
'featuredObject' => ActivityPub::TagManager.instance.uri_for(collection_item.account),
|
'id' => ActivityPub::TagManager.instance.uri_for(collection_item),
|
||||||
'featuredObjectType' => 'Person',
|
'featuredObject' => ActivityPub::TagManager.instance.uri_for(collection_item.account),
|
||||||
})
|
'featuredObjectType' => 'Person',
|
||||||
|
'featureAuthorization' => ap_account_feature_authorization_url(collection_item.account_id, collection_item),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when a remote account is featured' do
|
||||||
|
let(:collection) { Fabricate(:collection) }
|
||||||
|
let(:account) { Fabricate(:remote_account) }
|
||||||
|
let(:collection_item) { Fabricate(:collection_item, collection:, account:, approval_uri: 'https://example.com/auth/1') }
|
||||||
|
|
||||||
|
it 'serializes to the expected structure' do
|
||||||
|
expect(subject).to include({
|
||||||
|
'type' => 'FeaturedItem',
|
||||||
|
'id' => ActivityPub::TagManager.instance.uri_for(collection_item),
|
||||||
|
'featuredObject' => ActivityPub::TagManager.instance.uri_for(collection_item.account),
|
||||||
|
'featuredObjectType' => 'Person',
|
||||||
|
'featureAuthorization' => 'https://example.com/auth/1',
|
||||||
|
})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
14
yarn.lock
14
yarn.lock
@@ -3925,7 +3925,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@storybook/icons@npm:^2.0.0":
|
"@storybook/icons@npm:^2.0.1":
|
||||||
version: 2.0.1
|
version: 2.0.1
|
||||||
resolution: "@storybook/icons@npm:2.0.1"
|
resolution: "@storybook/icons@npm:2.0.1"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -12646,7 +12646,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.6.2, semver@npm:^7.7.1, semver@npm:^7.7.3":
|
"semver@npm:^7.3.5, semver@npm:^7.5.3, semver@npm:^7.7.1, semver@npm:^7.7.3":
|
||||||
version: 7.7.3
|
version: 7.7.3
|
||||||
resolution: "semver@npm:7.7.3"
|
resolution: "semver@npm:7.7.3"
|
||||||
bin:
|
bin:
|
||||||
@@ -13101,11 +13101,11 @@ __metadata:
|
|||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"storybook@npm:^10.0.5":
|
"storybook@npm:^10.0.5":
|
||||||
version: 10.1.10
|
version: 10.2.13
|
||||||
resolution: "storybook@npm:10.1.10"
|
resolution: "storybook@npm:10.2.13"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@storybook/global": "npm:^5.0.0"
|
"@storybook/global": "npm:^5.0.0"
|
||||||
"@storybook/icons": "npm:^2.0.0"
|
"@storybook/icons": "npm:^2.0.1"
|
||||||
"@testing-library/jest-dom": "npm:^6.6.3"
|
"@testing-library/jest-dom": "npm:^6.6.3"
|
||||||
"@testing-library/user-event": "npm:^14.6.1"
|
"@testing-library/user-event": "npm:^14.6.1"
|
||||||
"@vitest/expect": "npm:3.2.4"
|
"@vitest/expect": "npm:3.2.4"
|
||||||
@@ -13113,7 +13113,7 @@ __metadata:
|
|||||||
esbuild: "npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0"
|
esbuild: "npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0"
|
||||||
open: "npm:^10.2.0"
|
open: "npm:^10.2.0"
|
||||||
recast: "npm:^0.23.5"
|
recast: "npm:^0.23.5"
|
||||||
semver: "npm:^7.6.2"
|
semver: "npm:^7.7.3"
|
||||||
use-sync-external-store: "npm:^1.5.0"
|
use-sync-external-store: "npm:^1.5.0"
|
||||||
ws: "npm:^8.18.0"
|
ws: "npm:^8.18.0"
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -13123,7 +13123,7 @@ __metadata:
|
|||||||
optional: true
|
optional: true
|
||||||
bin:
|
bin:
|
||||||
storybook: ./dist/bin/dispatcher.js
|
storybook: ./dist/bin/dispatcher.js
|
||||||
checksum: 10c0/beff5472ee86a995cbde2789b2aabd941f823e31ca6957bb4434cb8ee3d3703cf1248e44f4b4d402416a52bfee94677e74f233cc906487901e831e8ab610defa
|
checksum: 10c0/5ca338b707c3e7e94c16ecdcb00ca3c450157dceec758c15c416649e346e628a0e034d2265656650fc4fee4680631de7cc588e1a244e42cbb41af9416281a998
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user