Merge branch 'kb_development' into kb_patch
This commit is contained in:
commit
a045c5eff1
244 changed files with 5451 additions and 1468 deletions
|
@ -1,4 +0,0 @@
|
|||
---
|
||||
ignore:
|
||||
# Sidekiq security issue, fixes in the latest Sidekiq 7 but we can not upgrade. Will be fixed in Sidekiq 6.5.10
|
||||
- CVE-2023-26141
|
|
@ -70,7 +70,7 @@ services:
|
|||
hard: -1
|
||||
|
||||
libretranslate:
|
||||
image: libretranslate/libretranslate:v1.3.11
|
||||
image: libretranslate/libretranslate:v1.3.12
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- lt-data:/home/libretranslate/.local
|
||||
|
|
9
.github/workflows/test-ruby.yml
vendored
9
.github/workflows/test-ruby.yml
vendored
|
@ -284,8 +284,8 @@ jobs:
|
|||
ports:
|
||||
- 6379:6379
|
||||
|
||||
elasticsearch:
|
||||
image: docker.elastic.co/elasticsearch/elasticsearch:7.17.13
|
||||
search:
|
||||
image: ${{ matrix.search-image }}
|
||||
env:
|
||||
discovery.type: single-node
|
||||
xpack.security.enabled: false
|
||||
|
@ -315,6 +315,11 @@ jobs:
|
|||
- '3.0'
|
||||
- '3.1'
|
||||
- '.ruby-version'
|
||||
search-image:
|
||||
- docker.elastic.co/elasticsearch/elasticsearch:7.17.13
|
||||
include:
|
||||
- ruby-version: '.ruby-version'
|
||||
search-image: docker.elastic.co/elasticsearch/elasticsearch:8.10.2
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -31,9 +31,6 @@
|
|||
# Ignore Vagrant files
|
||||
.vagrant/
|
||||
|
||||
# Ignore Capistrano customizations
|
||||
/config/deploy/*
|
||||
|
||||
# Ignore IDE files
|
||||
.vscode/
|
||||
.idea/
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
# This configuration was generated by
|
||||
# `haml-lint --auto-gen-config`
|
||||
# on 2023-07-20 09:47:50 -0400 using Haml-Lint version 0.48.0.
|
||||
# on 2023-10-03 08:32:28 -0400 using Haml-Lint version 0.51.0.
|
||||
# The point is for the user to remove these configuration records
|
||||
# one by one as the lints are removed from the code base.
|
||||
# Note that changes in the inspected code, or installation of new
|
||||
# versions of Haml-Lint, may require this file to be generated again.
|
||||
|
||||
linters:
|
||||
# Offense count: 951
|
||||
# Offense count: 944
|
||||
LineLength:
|
||||
enabled: false
|
||||
|
||||
|
@ -15,7 +15,7 @@ linters:
|
|||
UnnecessaryStringOutput:
|
||||
enabled: false
|
||||
|
||||
# Offense count: 57
|
||||
# Offense count: 44
|
||||
RuboCop:
|
||||
enabled: false
|
||||
|
||||
|
@ -27,23 +27,17 @@ linters:
|
|||
- 'app/views/admin/reports/show.html.haml'
|
||||
- 'app/views/disputes/strikes/show.html.haml'
|
||||
|
||||
# Offense count: 32
|
||||
# Offense count: 15
|
||||
InstanceVariables:
|
||||
exclude:
|
||||
- 'app/views/admin/reports/_actions.html.haml'
|
||||
- 'app/views/admin/roles/_form.html.haml'
|
||||
- 'app/views/admin/webhooks/_form.html.haml'
|
||||
- 'app/views/auth/registrations/_status.html.haml'
|
||||
- 'app/views/auth/sessions/two_factor/_otp_authentication_form.html.haml'
|
||||
- 'app/views/authorize_interactions/_post_follow_actions.html.haml'
|
||||
- 'app/views/invites/_form.html.haml'
|
||||
- 'app/views/relationships/_account.html.haml'
|
||||
- 'app/views/shared/_og.html.haml'
|
||||
- 'app/views/application/_sidebar.html.haml'
|
||||
|
||||
# Offense count: 3
|
||||
# Offense count: 2
|
||||
IdNames:
|
||||
exclude:
|
||||
- 'app/views/authorize_interactions/error.html.haml'
|
||||
- 'app/views/oauth/authorizations/error.html.haml'
|
||||
- 'app/views/shared/_error_messages.html.haml'
|
||||
|
|
2
.nvmrc
2
.nvmrc
|
@ -1 +1 @@
|
|||
20.7
|
||||
20.8
|
||||
|
|
|
@ -31,9 +31,6 @@
|
|||
# Ignore Vagrant files
|
||||
.vagrant/
|
||||
|
||||
# Ignore Capistrano customizations
|
||||
/config/deploy/*
|
||||
|
||||
# Ignore IDE files
|
||||
.vscode/
|
||||
.idea/
|
||||
|
|
|
@ -28,6 +28,7 @@ AllCops:
|
|||
- 'Vagrantfile'
|
||||
- 'vendor/**/*'
|
||||
- 'lib/json_ld/*' # Generated files
|
||||
- 'lib/mastodon/migration_helpers.rb' # Vendored from GitLab
|
||||
- 'lib/templates/**/*'
|
||||
|
||||
# Reason: Prefer Hashes without extreme indentation
|
||||
|
@ -76,12 +77,6 @@ Metrics/AbcSize:
|
|||
- 'lib/mastodon/cli/*.rb'
|
||||
- db/*migrate/**/*
|
||||
|
||||
# Reason:
|
||||
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsblocknesting
|
||||
Metrics/BlockNesting:
|
||||
Exclude:
|
||||
- 'lib/mastodon/cli/*.rb'
|
||||
|
||||
# Reason: Currently disabled in .rubocop_todo.yml
|
||||
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricscyclomaticcomplexity
|
||||
Metrics/CyclomaticComplexity:
|
||||
|
|
|
@ -13,32 +13,6 @@ Bundler/OrderedGems:
|
|||
Exclude:
|
||||
- 'Gemfile'
|
||||
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: EnforcedStyle, IndentationWidth.
|
||||
# SupportedStyles: with_first_argument, with_fixed_indentation
|
||||
Layout/ArgumentAlignment:
|
||||
Exclude:
|
||||
- 'config/initializers/cors.rb'
|
||||
- 'config/initializers/session_store.rb'
|
||||
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle.
|
||||
# SupportedHashRocketStyles: key, separator, table
|
||||
# SupportedColonStyles: key, separator, table
|
||||
# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit
|
||||
Layout/HashAlignment:
|
||||
Exclude:
|
||||
- 'config/environments/production.rb'
|
||||
- 'config/initializers/rack_attack.rb'
|
||||
- 'config/routes.rb'
|
||||
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: AllowDoxygenCommentStyle, AllowGemfileRubyComment.
|
||||
Layout/LeadingCommentSpace:
|
||||
Exclude:
|
||||
- 'config/application.rb'
|
||||
- 'config/initializers/3_omniauth.rb'
|
||||
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: Max, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns.
|
||||
# URISchemes: http, https
|
||||
|
@ -46,14 +20,6 @@ Layout/LineLength:
|
|||
Exclude:
|
||||
- 'app/models/account.rb'
|
||||
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: require_no_space, require_space
|
||||
Layout/SpaceInLambdaLiteral:
|
||||
Exclude:
|
||||
- 'config/environments/production.rb'
|
||||
- 'config/initializers/content_security_policy.rb'
|
||||
|
||||
# Configuration parameters: AllowComments, AllowEmptyLambdas.
|
||||
Lint/EmptyBlock:
|
||||
Exclude:
|
||||
|
@ -289,10 +255,6 @@ RSpec/MultipleMemoizedHelpers:
|
|||
RSpec/NestedGroups:
|
||||
Max: 6
|
||||
|
||||
RSpec/PendingWithoutReason:
|
||||
Exclude:
|
||||
- 'spec/models/account_spec.rb'
|
||||
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
Rails/ApplicationController:
|
||||
Exclude:
|
||||
|
@ -848,6 +810,5 @@ Style/TrailingCommaInHashLiteral:
|
|||
Style/WordArray:
|
||||
Exclude:
|
||||
- 'app/helpers/languages_helper.rb'
|
||||
- 'config/initializers/cors.rb'
|
||||
- 'spec/controllers/settings/imports_controller_spec.rb'
|
||||
- 'spec/models/form/import_spec.rb'
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [4.2.1] - UNRELEASED
|
||||
## [4.2.1] - 2023-10-10
|
||||
|
||||
### Added
|
||||
|
||||
|
@ -16,6 +16,11 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
### Fixed
|
||||
|
||||
- Fix clicking on already-opened thread post scrolling to the top of the thread ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27331), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27338), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27350))
|
||||
- Fix some remote posts getting truncated ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27307))
|
||||
- Fix some cases of infinite scroll code trying to fetch inaccessible posts in a loop ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27286))
|
||||
- Fix `Vary` headers not being set on some redirects ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27272))
|
||||
- Fix mentions being matched in some URL query strings ([mjankowski](https://github.com/mastodon/mastodon/pull/25656))
|
||||
- Fix unexpected linebreak in version string in the Web UI ([vmstan](https://github.com/mastodon/mastodon/pull/26986))
|
||||
- Fix double scroll bars in some columns in advanced interface ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27187))
|
||||
- Fix boosts of local users being filtered in account timelines ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27204))
|
||||
|
@ -32,7 +37,7 @@ All notable changes to this project will be documented in this file.
|
|||
- Fix retention dashboard not displaying correct month ([vmstan](https://github.com/mastodon/mastodon/pull/27180))
|
||||
- Fix tIME chunk not being properly removed from PNG uploads ([TheEssem](https://github.com/mastodon/mastodon/pull/27111))
|
||||
- Fix division by zero in video in bitrate computation code ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27129))
|
||||
- Fix inefficient queries in “Follows and followers” as well as several admin pages ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27116))
|
||||
- Fix inefficient queries in “Follows and followers” as well as several admin pages ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27116), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27306))
|
||||
- Fix ActiveRecord using two connection pools when no replica is defined ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27061))
|
||||
- Fix the search documentation URL in system checks ([renchap](https://github.com/mastodon/mastodon/pull/27036))
|
||||
|
||||
|
|
15
Capfile
15
Capfile
|
@ -1,15 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'capistrano/setup'
|
||||
require 'capistrano/deploy'
|
||||
require 'capistrano/scm/git'
|
||||
|
||||
install_plugin Capistrano::SCM::Git
|
||||
|
||||
require 'capistrano/rbenv'
|
||||
require 'capistrano/bundler'
|
||||
require 'capistrano/yarn'
|
||||
require 'capistrano/rails/assets'
|
||||
require 'capistrano/rails/migrations'
|
||||
|
||||
Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }
|
|
@ -1,6 +1,6 @@
|
|||
# syntax=docker/dockerfile:1.4
|
||||
# This needs to be bookworm-slim because the Ruby image is built on bookworm-slim
|
||||
ARG NODE_VERSION="20.7-bookworm-slim"
|
||||
ARG NODE_VERSION="20.8-bookworm-slim"
|
||||
|
||||
FROM ghcr.io/moritzheiber/ruby-jemalloc:3.2.2-slim as ruby
|
||||
FROM node:${NODE_VERSION} as build
|
||||
|
|
6
Gemfile
6
Gemfile
|
@ -172,12 +172,6 @@ group :development do
|
|||
# Linter CLI for HAML files
|
||||
gem 'haml_lint', require: false
|
||||
|
||||
# Deployment automation
|
||||
gem 'capistrano', '~> 3.17'
|
||||
gem 'capistrano-rails', '~> 1.6'
|
||||
gem 'capistrano-rbenv', '~> 2.2'
|
||||
gem 'capistrano-yarn', '~> 2.0'
|
||||
|
||||
# Validate missing i18n keys
|
||||
gem 'i18n-tasks', '~> 1.0', require: false
|
||||
end
|
||||
|
|
47
Gemfile.lock
47
Gemfile.lock
|
@ -84,9 +84,9 @@ GEM
|
|||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||
active_model_serializers (0.10.13)
|
||||
actionpack (>= 4.1, < 7.1)
|
||||
activemodel (>= 4.1, < 7.1)
|
||||
active_model_serializers (0.10.14)
|
||||
actionpack (>= 4.1)
|
||||
activemodel (>= 4.1)
|
||||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||
activejob (7.0.8)
|
||||
|
@ -112,8 +112,6 @@ GEM
|
|||
addressable (2.8.5)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
aes_key_wrap (1.1.0)
|
||||
airbrussh (1.4.1)
|
||||
sshkit (>= 1.6.1, != 1.7.0)
|
||||
android_key_attestation (0.3.0)
|
||||
annotate (3.2.0)
|
||||
activerecord (>= 3.2, < 8.0)
|
||||
|
@ -175,21 +173,6 @@ GEM
|
|||
bundler-audit (0.9.1)
|
||||
bundler (>= 1.2.0, < 3)
|
||||
thor (~> 1.0)
|
||||
capistrano (3.17.3)
|
||||
airbrussh (>= 1.0.0)
|
||||
i18n
|
||||
rake (>= 10.0.0)
|
||||
sshkit (>= 1.9.0)
|
||||
capistrano-bundler (2.1.0)
|
||||
capistrano (~> 3.1)
|
||||
capistrano-rails (1.6.3)
|
||||
capistrano (~> 3.1)
|
||||
capistrano-bundler (>= 1.1, < 3)
|
||||
capistrano-rbenv (2.2.0)
|
||||
capistrano (~> 3.1)
|
||||
sshkit (~> 1.3)
|
||||
capistrano-yarn (2.0.2)
|
||||
capistrano (~> 3.0)
|
||||
capybara (3.39.2)
|
||||
addressable
|
||||
matrix
|
||||
|
@ -324,7 +307,7 @@ GEM
|
|||
ruby-progressbar (~> 1.4)
|
||||
globalid (1.1.0)
|
||||
activesupport (>= 5.0)
|
||||
haml (6.1.2)
|
||||
haml (6.2.0)
|
||||
temple (>= 0.8.2)
|
||||
thor
|
||||
tilt
|
||||
|
@ -333,8 +316,8 @@ GEM
|
|||
activesupport (>= 5.1)
|
||||
haml (>= 4.0.6)
|
||||
railties (>= 5.1)
|
||||
haml_lint (0.50.0)
|
||||
haml (>= 4.0, < 6.2)
|
||||
haml_lint (0.51.0)
|
||||
haml (>= 4.0)
|
||||
parallel (~> 1.10)
|
||||
rainbow
|
||||
rubocop (>= 1.0)
|
||||
|
@ -473,11 +456,8 @@ GEM
|
|||
net-protocol
|
||||
net-protocol (0.2.1)
|
||||
timeout
|
||||
net-scp (4.0.0)
|
||||
net-ssh (>= 2.6.5, < 8.0.0)
|
||||
net-smtp (0.3.3)
|
||||
net-protocol
|
||||
net-ssh (7.1.0)
|
||||
nio4r (2.5.9)
|
||||
nokogiri (1.15.4)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
|
@ -642,7 +622,7 @@ GEM
|
|||
sidekiq (>= 5, < 8)
|
||||
rspec-support (3.12.1)
|
||||
rspec_chunked (0.6)
|
||||
rubocop (1.56.3)
|
||||
rubocop (1.56.4)
|
||||
base64 (~> 0.1.1)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (>= 3.17.0)
|
||||
|
@ -693,7 +673,7 @@ GEM
|
|||
rubyzip (>= 1.2.2, < 3.0)
|
||||
websocket (~> 1.0)
|
||||
semantic_range (3.0.0)
|
||||
sidekiq (6.5.9)
|
||||
sidekiq (6.5.10)
|
||||
connection_pool (>= 2.2.5, < 3)
|
||||
rack (~> 2.0)
|
||||
redis (>= 4.5.0, < 5)
|
||||
|
@ -728,9 +708,6 @@ GEM
|
|||
actionpack (>= 5.2)
|
||||
activesupport (>= 5.2)
|
||||
sprockets (>= 3.0.0)
|
||||
sshkit (1.21.5)
|
||||
net-scp (>= 1.1.2)
|
||||
net-ssh (>= 2.8.0)
|
||||
stackprof (0.2.25)
|
||||
statsd-ruby (1.5.0)
|
||||
stoplight (3.0.2)
|
||||
|
@ -749,7 +726,7 @@ GEM
|
|||
climate_control (>= 0.0.3, < 1.0)
|
||||
test-prof (1.2.3)
|
||||
thor (1.2.2)
|
||||
tilt (2.2.0)
|
||||
tilt (2.3.0)
|
||||
timeout (0.4.0)
|
||||
tpm-key_attestation (0.12.0)
|
||||
bindata (~> 2.4)
|
||||
|
@ -775,7 +752,7 @@ GEM
|
|||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.8.2)
|
||||
unicode-display_width (2.4.2)
|
||||
unicode-display_width (2.5.0)
|
||||
uri (0.12.2)
|
||||
validate_email (0.1.6)
|
||||
activemodel (>= 3.0)
|
||||
|
@ -831,10 +808,6 @@ DEPENDENCIES
|
|||
brakeman (~> 6.0)
|
||||
browser
|
||||
bundler-audit (~> 0.9)
|
||||
capistrano (~> 3.17)
|
||||
capistrano-rails (~> 1.6)
|
||||
capistrano-rbenv (~> 2.2)
|
||||
capistrano-yarn (~> 2.0)
|
||||
capybara (~> 3.39)
|
||||
charlock_holmes (~> 0.7.7)
|
||||
chewy (~> 7.3)
|
||||
|
|
|
@ -15,6 +15,7 @@ A "vulnerability in Mastodon" is a vulnerability in the code distributed through
|
|||
|
||||
| Version | Supported |
|
||||
| ------- | ---------------- |
|
||||
| 4.2.x | Yes |
|
||||
| 4.1.x | Yes |
|
||||
| 4.0.x | Until 2023-10-31 |
|
||||
| 3.5.x | Until 2023-12-31 |
|
||||
|
|
|
@ -5,15 +5,7 @@ class AboutController < ApplicationController
|
|||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
before_action :set_instance_presenter
|
||||
|
||||
def show
|
||||
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless user_signed_in?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_instance_presenter
|
||||
@instance_presenter = InstancePresenter.new
|
||||
end
|
||||
end
|
||||
|
|
|
@ -89,17 +89,17 @@ module Admin
|
|||
|
||||
def update_params
|
||||
params.require(:domain_block).permit(:severity, :reject_media, :reject_favourite, :reject_reply, :reject_reply_exclude_followers, :reject_send_not_public_searchability, :reject_send_public_unlisted, :reject_send_dissubscribable, :reject_send_media, :reject_send_sensitive, :reject_hashtag,
|
||||
:reject_straight_follow, :reject_new_follow, :detect_invalid_subscription, :reject_reports, :private_comment, :public_comment, :obfuscate, :hidden, :hidden_anonymous)
|
||||
:reject_straight_follow, :reject_new_follow, :reject_friend, :detect_invalid_subscription, :reject_reports, :private_comment, :public_comment, :obfuscate, :hidden, :hidden_anonymous)
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_favourite, :reject_reply, :reject_reply_exclude_followers, :reject_send_not_public_searchability, :reject_send_public_unlisted, :reject_send_dissubscribable, :reject_send_media, :reject_send_sensitive, :reject_hashtag,
|
||||
:reject_straight_follow, :reject_new_follow, :detect_invalid_subscription, :reject_reports, :private_comment, :public_comment, :obfuscate, :hidden, :hidden_anonymous)
|
||||
:reject_straight_follow, :reject_new_follow, :reject_friend, :detect_invalid_subscription, :reject_reports, :private_comment, :public_comment, :obfuscate, :hidden, :hidden_anonymous)
|
||||
end
|
||||
|
||||
def form_domain_block_batch_params
|
||||
params.require(:form_domain_block_batch).permit(domain_blocks_attributes: [:enabled, :domain, :severity, :reject_media, :reject_favourite, :reject_reply, :reject_reply_exclude_followers, :reject_send_not_public_searchability, :reject_send_public_unlisted, :reject_send_dissubscribable, :reject_send_media,
|
||||
:reject_send_sensitive, :reject_hashtag, :reject_straight_follow, :reject_new_follow, :detect_invalid_subscription, :reject_reports, :private_comment, :public_comment, :obfuscate, :hidden, :hidden_anonymous])
|
||||
:reject_send_sensitive, :reject_hashtag, :reject_straight_follow, :reject_new_follow, :reject_friend, :detect_invalid_subscription, :reject_reports, :private_comment, :public_comment, :obfuscate, :hidden, :hidden_anonymous])
|
||||
end
|
||||
|
||||
def action_from_button
|
||||
|
|
93
app/controllers/admin/friend_servers_controller.rb
Normal file
93
app/controllers/admin/friend_servers_controller.rb
Normal file
|
@ -0,0 +1,93 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class FriendServersController < BaseController
|
||||
before_action :set_friend, except: [:index, :new, :create]
|
||||
before_action :warn_signatures_not_enabled!, only: [:new, :edit, :create, :follow, :unfollow, :accept, :reject]
|
||||
|
||||
def index
|
||||
authorize :friend_server, :update?
|
||||
@friends = FriendDomain.all
|
||||
end
|
||||
|
||||
def new
|
||||
authorize :friend_server, :update?
|
||||
@friend = FriendDomain.new
|
||||
end
|
||||
|
||||
def edit
|
||||
authorize :friend_server, :update?
|
||||
end
|
||||
|
||||
def create
|
||||
authorize :friend_server, :update?
|
||||
|
||||
@friend = FriendDomain.new(resource_params)
|
||||
|
||||
if @friend.save
|
||||
@friend.follow!
|
||||
redirect_to admin_friend_servers_path
|
||||
else
|
||||
render action: :new
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
authorize :friend_server, :update?
|
||||
|
||||
if @friend.update(update_resource_params)
|
||||
redirect_to admin_friend_servers_path
|
||||
else
|
||||
render action: :edit
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize :friend_server, :update?
|
||||
@friend.destroy
|
||||
redirect_to admin_friend_servers_path
|
||||
end
|
||||
|
||||
def follow
|
||||
authorize :friend_server, :update?
|
||||
@friend.follow!
|
||||
render action: :edit
|
||||
end
|
||||
|
||||
def unfollow
|
||||
authorize :friend_server, :update?
|
||||
@friend.unfollow!
|
||||
render action: :edit
|
||||
end
|
||||
|
||||
def accept
|
||||
authorize :friend_server, :update?
|
||||
@friend.accept!
|
||||
render action: :edit
|
||||
end
|
||||
|
||||
def reject
|
||||
authorize :friend_server, :update?
|
||||
@friend.reject!
|
||||
render action: :edit
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_friend
|
||||
@friend = FriendDomain.find(params[:id])
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:friend_domain).permit(:domain, :inbox_url, :available, :pseudo_relay, :delivery_local, :unlocked, :allow_all_posts)
|
||||
end
|
||||
|
||||
def update_resource_params
|
||||
params.require(:friend_domain).permit(:inbox_url, :available, :pseudo_relay, :delivery_local, :unlocked, :allow_all_posts)
|
||||
end
|
||||
|
||||
def warn_signatures_not_enabled!
|
||||
flash.now[:error] = I18n.t('admin.relays.signatures_not_enabled') if authorized_fetch_mode?
|
||||
end
|
||||
end
|
||||
end
|
|
@ -70,7 +70,7 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController
|
|||
|
||||
def domain_block_params
|
||||
params.permit(:severity, :reject_media, :reject_favourite, :reject_reply, :reject_reply_exclude_followers, :reject_reports, :reject_send_not_public_searchability, :reject_send_public_unlisted, :reject_send_dissubscribable, :reject_send_media, :reject_send_sensitive, :reject_hashtag, :reject_straight_follow,
|
||||
:reject_new_follow, :detect_invalid_subscription, :private_comment, :public_comment, :obfuscate, :hidden, :hidden_anonymous)
|
||||
:reject_new_follow, :reject_friend, :detect_invalid_subscription, :private_comment, :public_comment, :obfuscate, :hidden, :hidden_anonymous)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
|
@ -103,6 +103,6 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController
|
|||
|
||||
def resource_params
|
||||
params.permit(:domain, :severity, :reject_media, :reject_favourite, :reject_reply, :reject_reply_exclude_followers, :reject_send_not_public_searchability, :reject_send_public_unlisted, :reject_send_dissubscribable, :reject_send_media, :reject_send_sensitive, :reject_hashtag, :reject_straight_follow,
|
||||
:reject_new_follow, :detect_invalid_subscription, :reject_reports, :private_comment, :public_comment, :obfuscate, :hidden, :hidden_anonymous)
|
||||
:reject_new_follow, :reject_friend, :detect_invalid_subscription, :reject_reports, :private_comment, :public_comment, :obfuscate, :hidden, :hidden_anonymous)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -52,11 +52,11 @@ class Api::V1::FiltersController < Api::BaseController
|
|||
end
|
||||
|
||||
def resource_params
|
||||
params.permit(:phrase, :expires_in, :irreversible, :exclude_follows, :exclude_localusers, :whole_word, context: [])
|
||||
params.permit(:phrase, :expires_in, :irreversible, :exclude_follows, :exclude_localusers, :with_quote, :whole_word, context: [])
|
||||
end
|
||||
|
||||
def filter_params
|
||||
resource_params.slice(:phrase, :expires_in, :irreversible, :exclude_follows, :exclude_localusers, :context)
|
||||
resource_params.slice(:phrase, :expires_in, :irreversible, :exclude_follows, :exclude_localusers, :with_quote, :context)
|
||||
end
|
||||
|
||||
def keyword_params
|
||||
|
|
|
@ -31,7 +31,7 @@ class Api::V1::Statuses::EmojiReactionsController < Api::BaseController
|
|||
end
|
||||
|
||||
render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(
|
||||
[@status], current_account.id, emoji_reactions_map: { @status.id => false }
|
||||
[@status], current_account.id
|
||||
)
|
||||
rescue Mastodon::NotPermittedError
|
||||
not_found
|
||||
|
|
|
@ -43,6 +43,6 @@ class Api::V2::FiltersController < Api::BaseController
|
|||
end
|
||||
|
||||
def resource_params
|
||||
params.permit(:title, :expires_in, :filter_action, :exclude_follows, :exclude_localusers, context: [], keywords_attributes: [:id, :keyword, :whole_word, :_destroy])
|
||||
params.permit(:title, :expires_in, :filter_action, :exclude_follows, :exclude_localusers, :with_quote, context: [], keywords_attributes: [:id, :keyword, :whole_word, :_destroy])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,7 +10,6 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||
before_action :configure_sign_up_params, only: [:create]
|
||||
before_action :set_sessions, only: [:edit, :update]
|
||||
before_action :set_strikes, only: [:edit, :update]
|
||||
before_action :set_instance_presenter, only: [:new, :create, :update]
|
||||
before_action :set_body_classes, only: [:new, :create, :edit, :update]
|
||||
before_action :require_not_suspended!, only: [:update]
|
||||
before_action :set_cache_headers, only: [:edit, :update]
|
||||
|
@ -107,10 +106,6 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||
|
||||
private
|
||||
|
||||
def set_instance_presenter
|
||||
@instance_presenter = InstancePresenter.new
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = %w(edit update).include?(action_name) ? 'admin' : 'lighter'
|
||||
end
|
||||
|
|
|
@ -11,7 +11,6 @@ class Auth::SessionsController < Devise::SessionsController
|
|||
|
||||
include TwoFactorAuthenticationConcern
|
||||
|
||||
before_action :set_instance_presenter, only: [:new]
|
||||
before_action :set_body_classes
|
||||
|
||||
content_security_policy only: :new do |p|
|
||||
|
@ -99,10 +98,6 @@ class Auth::SessionsController < Devise::SessionsController
|
|||
|
||||
private
|
||||
|
||||
def set_instance_presenter
|
||||
@instance_presenter = InstancePresenter.new
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'lighter'
|
||||
end
|
||||
|
|
|
@ -9,17 +9,11 @@ module AccountControllerConcern
|
|||
FOLLOW_PER_PAGE = 12
|
||||
|
||||
included do
|
||||
before_action :set_instance_presenter
|
||||
|
||||
after_action :set_link_headers, if: -> { request.format.nil? || request.format == :html }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_instance_presenter
|
||||
@instance_presenter = InstancePresenter.new
|
||||
end
|
||||
|
||||
def set_link_headers
|
||||
response.headers['Link'] = LinkHeader.new(
|
||||
[
|
||||
|
|
|
@ -4,10 +4,10 @@ module WebAppControllerConcern
|
|||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
prepend_before_action :redirect_unauthenticated_to_permalinks!
|
||||
before_action :set_app_body_class
|
||||
|
||||
vary_by 'Accept, Accept-Language, Cookie'
|
||||
|
||||
before_action :redirect_unauthenticated_to_permalinks!
|
||||
before_action :set_app_body_class
|
||||
end
|
||||
|
||||
def skip_csrf_meta_tags?
|
||||
|
@ -22,7 +22,9 @@ module WebAppControllerConcern
|
|||
return if user_signed_in? && current_account.moved_to_account_id.nil?
|
||||
|
||||
redirect_path = PermalinkRedirector.new(request.path).redirect_path
|
||||
return if redirect_path.blank?
|
||||
|
||||
redirect_to(redirect_path) if redirect_path.present?
|
||||
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless user_signed_in?
|
||||
redirect_to(redirect_path)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -49,7 +49,7 @@ class FiltersController < ApplicationController
|
|||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:custom_filter).permit(:title, :expires_in, :filter_action, :exclude_follows, :exclude_localusers, context: [], keywords_attributes: [:id, :keyword, :whole_word, :_destroy])
|
||||
params.require(:custom_filter).permit(:title, :expires_in, :filter_action, :exclude_follows, :exclude_localusers, :with_quote, context: [], keywords_attributes: [:id, :keyword, :whole_word, :_destroy])
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
class FollowerAccountsController < ApplicationController
|
||||
include AccountControllerConcern
|
||||
include SignatureVerification
|
||||
include WebAppControllerConcern
|
||||
|
||||
vary_by -> { public_fetch_mode? ? 'Accept, Accept-Language, Cookie' : 'Accept, Accept-Language, Cookie, Signature' }
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
class FollowingAccountsController < ApplicationController
|
||||
include AccountControllerConcern
|
||||
include SignatureVerification
|
||||
include WebAppControllerConcern
|
||||
|
||||
vary_by -> { public_fetch_mode? ? 'Accept, Accept-Language, Cookie' : 'Accept, Accept-Language, Cookie, Signature' }
|
||||
|
||||
|
|
|
@ -3,15 +3,7 @@
|
|||
class HomeController < ApplicationController
|
||||
include WebAppControllerConcern
|
||||
|
||||
before_action :set_instance_presenter
|
||||
|
||||
def index
|
||||
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless user_signed_in?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_instance_presenter
|
||||
@instance_presenter = InstancePresenter.new
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,15 +5,7 @@ class PrivacyController < ApplicationController
|
|||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
before_action :set_instance_presenter
|
||||
|
||||
def show
|
||||
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless user_signed_in?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_instance_presenter
|
||||
@instance_presenter = InstancePresenter.new
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,7 +10,6 @@ class StatusesController < ApplicationController
|
|||
|
||||
before_action :require_account_signature!, only: [:show, :activity], if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :set_status
|
||||
before_action :set_instance_presenter
|
||||
before_action :redirect_to_original, only: :show
|
||||
before_action :set_body_classes, only: :embed
|
||||
|
||||
|
@ -72,10 +71,6 @@ class StatusesController < ApplicationController
|
|||
not_found
|
||||
end
|
||||
|
||||
def set_instance_presenter
|
||||
@instance_presenter = InstancePresenter.new
|
||||
end
|
||||
|
||||
def redirect_to_original
|
||||
redirect_to(ActivityPub::TagManager.instance.url_for(@status.reblog), allow_other_host: true) if @status.reblog?
|
||||
end
|
||||
|
|
|
@ -14,7 +14,6 @@ class TagsController < ApplicationController
|
|||
before_action :set_local
|
||||
before_action :set_tag
|
||||
before_action :set_statuses, if: -> { request.format == :rss }
|
||||
before_action :set_instance_presenter
|
||||
|
||||
skip_before_action :require_functional!, unless: :limited_federation_mode?
|
||||
|
||||
|
@ -49,10 +48,6 @@ class TagsController < ApplicationController
|
|||
@statuses = cache_collection(TagFeed.new(@tag, nil, local: @local).get(limit_param), Status)
|
||||
end
|
||||
|
||||
def set_instance_presenter
|
||||
@instance_presenter = InstancePresenter.new
|
||||
end
|
||||
|
||||
def limit_param
|
||||
params[:limit].present? ? [params[:limit].to_i, PAGE_SIZE_MAX].min : PAGE_SIZE
|
||||
end
|
||||
|
|
11
app/helpers/admin/announcements_helper.rb
Normal file
11
app/helpers/admin/announcements_helper.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin::AnnouncementsHelper
|
||||
def datetime_pattern
|
||||
'[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}(:[0-9]{2}){1,2}'
|
||||
end
|
||||
|
||||
def datetime_placeholder
|
||||
Time.zone.now.strftime('%FT%R')
|
||||
end
|
||||
end
|
11
app/helpers/invites_helper.rb
Normal file
11
app/helpers/invites_helper.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module InvitesHelper
|
||||
def invites_max_uses_options
|
||||
[1, 5, 10, 25, 50, 100]
|
||||
end
|
||||
|
||||
def invites_expires_options
|
||||
[30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week]
|
||||
end
|
||||
end
|
|
@ -17,7 +17,8 @@ module KmyblueCapabilitiesHelper
|
|||
:kmyblue_bookmark_category,
|
||||
:kmyblue_quote,
|
||||
:kmyblue_searchability_limited,
|
||||
:kmyblue_visibility_public_unlisted,
|
||||
:kmyblue_searchability_public_unlisted,
|
||||
:kmyblue_circle_history,
|
||||
]
|
||||
|
||||
capabilities << :profile_search unless Chewy.enabled?
|
||||
|
@ -25,6 +26,7 @@ module KmyblueCapabilitiesHelper
|
|||
capabilities << :emoji_reaction
|
||||
capabilities << :enable_wide_emoji_reaction
|
||||
end
|
||||
capabilities << :kmyblue_visibility_public_unlisted if Setting.enable_public_unlisted_visibility
|
||||
|
||||
capabilities
|
||||
end
|
||||
|
|
|
@ -230,6 +230,24 @@ module LanguagesHelper
|
|||
'sr-Latn': 'Srpski (latinica)',
|
||||
}.freeze
|
||||
|
||||
# Helper for self.sorted_locale_keys
|
||||
private_class_method def self.locale_name_for_sorting(locale)
|
||||
if locale.blank? || locale == 'und'
|
||||
'000'
|
||||
elsif (supported_locale = SUPPORTED_LOCALES[locale.to_sym])
|
||||
ASCIIFolding.new.fold(supported_locale[1]).downcase
|
||||
elsif (regional_locale = REGIONAL_LOCALE_NAMES[locale.to_sym])
|
||||
ASCIIFolding.new.fold(regional_locale).downcase
|
||||
else
|
||||
locale
|
||||
end
|
||||
end
|
||||
|
||||
# Sort locales by native name for dropdown menus
|
||||
def self.sorted_locale_keys(locale_keys)
|
||||
locale_keys.sort_by { |key, _| locale_name_for_sorting(key) }
|
||||
end
|
||||
|
||||
def native_locale_name(locale)
|
||||
if locale.blank? || locale == 'und'
|
||||
I18n.t('generic.none')
|
||||
|
@ -254,6 +272,7 @@ module LanguagesHelper
|
|||
|
||||
def valid_locale_or_nil(str)
|
||||
return if str.blank?
|
||||
return str if valid_locale?(str)
|
||||
|
||||
code, = str.to_s.split(/[_-]/) # Strip out the region from e.g. en_US or ja-JP
|
||||
|
||||
|
|
|
@ -5,8 +5,6 @@ module MascotHelper
|
|||
full_asset_url(instance_presenter.mascot&.file&.url || asset_pack_path('media/images/elephant_ui_plane.svg'))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def instance_presenter
|
||||
@instance_presenter ||= InstancePresenter.new
|
||||
end
|
||||
|
|
|
@ -3,11 +3,12 @@
|
|||
module RoutingHelper
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include Rails.application.routes.url_helpers
|
||||
include ActionView::Helpers::AssetTagHelper
|
||||
include Webpacker::Helper
|
||||
|
||||
included do
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
def default_url_options
|
||||
ActionMailer::Base.default_url_options
|
||||
end
|
||||
|
|
|
@ -2,7 +2,11 @@
|
|||
|
||||
module SettingsHelper
|
||||
def filterable_languages
|
||||
LanguagesHelper::SUPPORTED_LOCALES.keys
|
||||
LanguagesHelper.sorted_locale_keys(LanguagesHelper::SUPPORTED_LOCALES.keys)
|
||||
end
|
||||
|
||||
def ui_languages
|
||||
LanguagesHelper.sorted_locale_keys(I18n.available_locales)
|
||||
end
|
||||
|
||||
def session_device_icon(session)
|
||||
|
|
|
@ -80,6 +80,10 @@ export function importFetchedStatuses(statuses) {
|
|||
processStatus(status.reblog);
|
||||
}
|
||||
|
||||
if (status.quote && status.quote.id) {
|
||||
processStatus(status.quote);
|
||||
}
|
||||
|
||||
if (status.poll && status.poll.id) {
|
||||
pushUnique(polls, normalizePoll(status.poll, getState().getIn(['polls', status.poll.id])));
|
||||
}
|
||||
|
|
|
@ -85,6 +85,11 @@ export function normalizeStatus(status, normalOldStatus) {
|
|||
normalStatus.spoiler_text = normalOldStatus.get('spoiler_text');
|
||||
normalStatus.hidden = normalOldStatus.get('hidden');
|
||||
|
||||
// for quoted post
|
||||
if (!normalStatus.filtered && normalOldStatus.get('filtered')) {
|
||||
normalStatus.filtered = normalOldStatus.get('filtered');
|
||||
}
|
||||
|
||||
if (normalOldStatus.get('translation')) {
|
||||
normalStatus.translation = normalOldStatus.get('translation');
|
||||
}
|
||||
|
|
|
@ -45,6 +45,21 @@ describe('computeHashtagBarForStatus', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('does not truncate the contents when the last child is a text node', () => {
|
||||
const status = createStatus(
|
||||
'this is a #<a class="zrl" href="https://example.com/search?tag=test">test</a>. Some more text',
|
||||
['test'],
|
||||
);
|
||||
|
||||
const { hashtagsInBar, statusContentProps } =
|
||||
computeHashtagBarForStatus(status);
|
||||
|
||||
expect(hashtagsInBar).toEqual([]);
|
||||
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
|
||||
`"this is a #<a class="zrl" href="https://example.com/search?tag=test">test</a>. Some more text"`,
|
||||
);
|
||||
});
|
||||
|
||||
it('extract tags from the last line', () => {
|
||||
const status = createStatus(
|
||||
'<p>Simple text</p><p><a href="test">#hashtag</a></p>',
|
||||
|
|
503
app/javascript/mastodon/components/compacted_status.jsx
Normal file
503
app/javascript/mastodon/components/compacted_status.jsx
Normal file
|
@ -0,0 +1,503 @@
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
|
||||
import AttachmentList from 'mastodon/components/attachment_list';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
|
||||
import Card from '../features/status/components/card';
|
||||
// We use the component (and not the container) since we do not want
|
||||
// to use the progress bar to show download progress
|
||||
import Bundle from '../features/ui/components/bundle';
|
||||
import { MediaGallery, Video, Audio } from '../features/ui/util/async-components';
|
||||
import { displayMedia } from '../initial_state';
|
||||
|
||||
import { Avatar } from './avatar';
|
||||
import { DisplayName } from './display_name';
|
||||
import { getHashtagBarForStatus } from './hashtag_bar';
|
||||
import { RelativeTimestamp } from './relative_timestamp';
|
||||
import StatusContent from './status_content';
|
||||
|
||||
const domParser = new DOMParser();
|
||||
|
||||
export const textForScreenReader = (intl, status, rebloggedByText = false) => {
|
||||
const displayName = status.getIn(['account', 'display_name']);
|
||||
|
||||
const spoilerText = status.getIn(['translation', 'spoiler_text']) || status.get('spoiler_text');
|
||||
const contentHtml = status.getIn(['translation', 'contentHtml']) || status.get('contentHtml');
|
||||
const contentText = domParser.parseFromString(contentHtml, 'text/html').documentElement.textContent;
|
||||
|
||||
const values = [
|
||||
displayName.length === 0 ? status.getIn(['account', 'acct']).split('@')[0] : displayName,
|
||||
spoilerText && status.get('hidden') ? spoilerText : contentText,
|
||||
intl.formatDate(status.get('created_at'), { hour: '2-digit', minute: '2-digit', month: 'short', day: 'numeric' }),
|
||||
status.getIn(['account', 'acct']),
|
||||
];
|
||||
|
||||
if (rebloggedByText) {
|
||||
values.push(rebloggedByText);
|
||||
}
|
||||
|
||||
return values.join(', ');
|
||||
};
|
||||
|
||||
export const defaultMediaVisibility = (status) => {
|
||||
if (!status) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
|
||||
status = status.get('reblog');
|
||||
}
|
||||
|
||||
return (displayMedia !== 'hide_all' && !status.get('sensitive') || displayMedia === 'show_all');
|
||||
};
|
||||
|
||||
const messages = defineMessages({
|
||||
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
||||
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
|
||||
login_short: { id: 'privacy.login.short', defaultMessage: 'Login only' },
|
||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
||||
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Limited menbers only' },
|
||||
mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual followers only' },
|
||||
circle_short: { id: 'privacy.circle.short', defaultMessage: 'Circle members only' },
|
||||
personal_short: { id: 'privacy.personal.short', defaultMessage: 'Yourself only' },
|
||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
||||
edited: { id: 'status.edited', defaultMessage: 'Edited {date}' },
|
||||
});
|
||||
|
||||
class CompactedStatus extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
status: ImmutablePropTypes.map,
|
||||
previousId: PropTypes.string,
|
||||
nextInReplyToId: PropTypes.string,
|
||||
rootId: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
onOpenMedia: PropTypes.func,
|
||||
onOpenVideo: PropTypes.func,
|
||||
onHeightChange: PropTypes.func,
|
||||
onToggleHidden: PropTypes.func,
|
||||
onToggleCollapsed: PropTypes.func,
|
||||
onTranslate: PropTypes.func,
|
||||
onInteractionModal: PropTypes.func,
|
||||
muted: PropTypes.bool,
|
||||
hidden: PropTypes.bool,
|
||||
unread: PropTypes.bool,
|
||||
onMoveUp: PropTypes.func,
|
||||
onMoveDown: PropTypes.func,
|
||||
showThread: PropTypes.bool,
|
||||
getScrollPosition: PropTypes.func,
|
||||
updateScrollBottom: PropTypes.func,
|
||||
cacheMediaWidth: PropTypes.func,
|
||||
cachedMediaWidth: PropTypes.number,
|
||||
};
|
||||
|
||||
// Avoid checking props that are functions (and whose equality will always
|
||||
// evaluate to false. See react-immutable-pure-component for usage.
|
||||
updateOnProps = [
|
||||
'status',
|
||||
'muted',
|
||||
'hidden',
|
||||
'unread',
|
||||
];
|
||||
|
||||
state = {
|
||||
showMedia: defaultMediaVisibility(this.props.status),
|
||||
statusId: undefined,
|
||||
forceFilter: undefined,
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
if (nextProps.status && nextProps.status.get('id') !== prevState.statusId) {
|
||||
return {
|
||||
showMedia: defaultMediaVisibility(nextProps.status),
|
||||
statusId: nextProps.status.get('id'),
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
handleToggleMediaVisibility = () => {
|
||||
this.setState({ showMedia: !this.state.showMedia });
|
||||
};
|
||||
|
||||
handleClick = e => {
|
||||
if (e && (e.button !== 0 || e.ctrlKey || e.metaKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
this.handleHotkeyOpen();
|
||||
};
|
||||
|
||||
handlePrependAccountClick = e => {
|
||||
this.handleAccountClick(e, false);
|
||||
};
|
||||
|
||||
handleAccountClick = (e, proper = true) => {
|
||||
if (e && (e.button !== 0 || e.ctrlKey || e.metaKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
this._openProfile(proper);
|
||||
};
|
||||
|
||||
handleExpandedToggle = () => {
|
||||
this.props.onToggleHidden(this._properStatus());
|
||||
};
|
||||
|
||||
handleCollapsedToggle = isCollapsed => {
|
||||
this.props.onToggleCollapsed(this._properStatus(), isCollapsed);
|
||||
};
|
||||
|
||||
handleTranslate = () => {
|
||||
this.props.onTranslate(this._properStatus());
|
||||
};
|
||||
|
||||
getAttachmentAspectRatio () {
|
||||
const attachments = this._properStatus().get('media_attachments');
|
||||
|
||||
if (attachments.getIn([0, 'type']) === 'video') {
|
||||
return `${attachments.getIn([0, 'meta', 'original', 'width'])} / ${attachments.getIn([0, 'meta', 'original', 'height'])}`;
|
||||
} else if (attachments.getIn([0, 'type']) === 'audio') {
|
||||
return '16 / 9';
|
||||
} else {
|
||||
return (attachments.size === 1 && attachments.getIn([0, 'meta', 'small', 'aspect'])) ? attachments.getIn([0, 'meta', 'small', 'aspect']) : '3 / 2'
|
||||
}
|
||||
}
|
||||
|
||||
renderLoadingMediaGallery = () => {
|
||||
return (
|
||||
<div className='media-gallery' style={{ aspectRatio: this.getAttachmentAspectRatio() }} />
|
||||
);
|
||||
};
|
||||
|
||||
renderLoadingVideoPlayer = () => {
|
||||
return (
|
||||
<div className='video-player' style={{ aspectRatio: this.getAttachmentAspectRatio() }} />
|
||||
);
|
||||
};
|
||||
|
||||
renderLoadingAudioPlayer = () => {
|
||||
return (
|
||||
<div className='audio-player' style={{ aspectRatio: this.getAttachmentAspectRatio() }} />
|
||||
);
|
||||
};
|
||||
|
||||
handleOpenVideo = (options) => {
|
||||
const status = this._properStatus();
|
||||
const lang = status.getIn(['translation', 'language']) || status.get('language');
|
||||
this.props.onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), lang, options);
|
||||
};
|
||||
|
||||
handleOpenMedia = (media, index) => {
|
||||
const status = this._properStatus();
|
||||
const lang = status.getIn(['translation', 'language']) || status.get('language');
|
||||
this.props.onOpenMedia(status.get('id'), media, index, lang);
|
||||
};
|
||||
|
||||
handleHotkeyOpenMedia = e => {
|
||||
const { onOpenMedia, onOpenVideo } = this.props;
|
||||
const status = this._properStatus();
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
if (status.get('media_attachments').size > 0) {
|
||||
const lang = status.getIn(['translation', 'language']) || status.get('language');
|
||||
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||
onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), lang, { startTime: 0 });
|
||||
} else {
|
||||
onOpenMedia(status.get('id'), status.get('media_attachments'), 0, lang);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleHotkeyOpen = () => {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick();
|
||||
return;
|
||||
}
|
||||
|
||||
const { router } = this.context;
|
||||
const status = this._properStatus();
|
||||
|
||||
if (!router) {
|
||||
return;
|
||||
}
|
||||
|
||||
router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
|
||||
};
|
||||
|
||||
handleHotkeyOpenProfile = () => {
|
||||
this._openProfile();
|
||||
};
|
||||
|
||||
_openProfile = (proper = true) => {
|
||||
const { router } = this.context;
|
||||
const status = proper ? this._properStatus() : this.props.status;
|
||||
|
||||
if (!router) {
|
||||
return;
|
||||
}
|
||||
|
||||
router.history.push(`/@${status.getIn(['account', 'acct'])}`);
|
||||
};
|
||||
|
||||
handleHotkeyMoveUp = e => {
|
||||
this.props.onMoveUp(this.props.status.get('id'), e.target.getAttribute('data-featured'));
|
||||
};
|
||||
|
||||
handleHotkeyMoveDown = e => {
|
||||
this.props.onMoveDown(this.props.status.get('id'), e.target.getAttribute('data-featured'));
|
||||
};
|
||||
|
||||
handleHotkeyToggleHidden = () => {
|
||||
this.props.onToggleHidden(this._properStatus());
|
||||
};
|
||||
|
||||
handleHotkeyToggleSensitive = () => {
|
||||
this.handleToggleMediaVisibility();
|
||||
};
|
||||
|
||||
_properStatus () {
|
||||
const { status } = this.props;
|
||||
|
||||
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
|
||||
return status.get('reblog');
|
||||
} else {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
handleRef = c => {
|
||||
this.node = c;
|
||||
};
|
||||
|
||||
render () {
|
||||
const { intl, hidden, featured, unread, showThread, previousId, nextInReplyToId, rootId } = this.props;
|
||||
|
||||
let { status } = this.props;
|
||||
|
||||
if (status === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handlers = this.props.muted ? {} : {
|
||||
open: this.handleHotkeyOpen,
|
||||
openProfile: this.handleHotkeyOpenProfile,
|
||||
moveUp: this.handleHotkeyMoveUp,
|
||||
moveDown: this.handleHotkeyMoveDown,
|
||||
toggleHidden: this.handleHotkeyToggleHidden,
|
||||
toggleSensitive: this.handleHotkeyToggleSensitive,
|
||||
openMedia: this.handleHotkeyOpenMedia,
|
||||
};
|
||||
|
||||
let media, isCardMediaWithSensitive, prepend, rebloggedByText;
|
||||
|
||||
if (hidden) {
|
||||
return (
|
||||
<HotKeys handlers={handlers}>
|
||||
<div ref={this.handleRef} className={classNames('status__wrapper', { focusable: !this.props.muted })} tabIndex={0}>
|
||||
<span>{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}</span>
|
||||
<span>{status.get('content')}</span>
|
||||
</div>
|
||||
</HotKeys>
|
||||
);
|
||||
}
|
||||
|
||||
const connectUp = previousId && previousId === status.get('in_reply_to_id');
|
||||
const connectToRoot = rootId && rootId === status.get('in_reply_to_id');
|
||||
const connectReply = nextInReplyToId && nextInReplyToId === status.get('id');
|
||||
|
||||
if (showThread && status.get('in_reply_to_id') && status.get('in_reply_to_account_id') === status.getIn(['account', 'id'])) {
|
||||
const display_name_html = { __html: status.getIn(['account', 'display_name_html']) };
|
||||
|
||||
prepend = (
|
||||
<div className='status__prepend'>
|
||||
<div className='status__prepend-icon-wrapper'><Icon id='reply' className='status__prepend-icon' fixedWidth /></div>
|
||||
<FormattedMessage id='status.replied_to' defaultMessage='Replied to {name}' values={{ name: <a onClick={this.handlePrependAccountClick} data-id={status.getIn(['account', 'id'])} href={`/@${status.getIn(['account', 'acct'])}`} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></a> }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (status.get('quote_muted')) {
|
||||
const minHandlers = {
|
||||
moveUp: this.handleHotkeyMoveUp,
|
||||
moveDown: this.handleHotkeyMoveDown,
|
||||
};
|
||||
|
||||
return (
|
||||
<HotKeys handlers={minHandlers}>
|
||||
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
|
||||
<div className='status__wrapper status__wrapper__compact status__wrapper--filtered focusable' tabIndex={0} ref={this.handleRef} onClick={this.handleClick}>
|
||||
<FormattedMessage id='status.quote_filtered' defaultMessage='This quote is filtered because of muting, blocking or domain blocking' />
|
||||
</div>
|
||||
</HotKeys>
|
||||
);
|
||||
}
|
||||
|
||||
isCardMediaWithSensitive = false;
|
||||
|
||||
if (status.get('media_attachments').size > 0) {
|
||||
const language = status.getIn(['translation', 'language']) || status.get('language');
|
||||
|
||||
if (this.props.muted) {
|
||||
media = (
|
||||
<AttachmentList
|
||||
compact
|
||||
media={status.get('media_attachments')}
|
||||
/>
|
||||
);
|
||||
} else if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
|
||||
const attachment = status.getIn(['media_attachments', 0]);
|
||||
const description = attachment.getIn(['translation', 'description']) || attachment.get('description');
|
||||
|
||||
media = (
|
||||
<Bundle fetchComponent={Audio} loading={this.renderLoadingAudioPlayer} >
|
||||
{Component => (
|
||||
<Component
|
||||
src={attachment.get('url')}
|
||||
alt={description}
|
||||
lang={language}
|
||||
poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
|
||||
backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
|
||||
foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])}
|
||||
accentColor={attachment.getIn(['meta', 'colors', 'accent'])}
|
||||
duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
|
||||
width={this.props.cachedMediaWidth}
|
||||
height={110}
|
||||
cacheWidth={this.props.cacheMediaWidth}
|
||||
sensitive={status.get('sensitive')}
|
||||
blurhash={attachment.get('blurhash')}
|
||||
visible={this.state.showMedia}
|
||||
onToggleVisibility={this.handleToggleMediaVisibility}
|
||||
/>
|
||||
)}
|
||||
</Bundle>
|
||||
);
|
||||
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||
const attachment = status.getIn(['media_attachments', 0]);
|
||||
const description = attachment.getIn(['translation', 'description']) || attachment.get('description');
|
||||
|
||||
media = (
|
||||
<Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
|
||||
{Component => (
|
||||
<Component
|
||||
preview={attachment.get('preview_url')}
|
||||
frameRate={attachment.getIn(['meta', 'original', 'frame_rate'])}
|
||||
aspectRatio={`${attachment.getIn(['meta', 'original', 'width'])} / ${attachment.getIn(['meta', 'original', 'height'])}`}
|
||||
blurhash={attachment.get('blurhash')}
|
||||
src={attachment.get('url')}
|
||||
alt={description}
|
||||
lang={language}
|
||||
sensitive={status.get('sensitive')}
|
||||
onOpenVideo={this.handleOpenVideo}
|
||||
visible={this.state.showMedia}
|
||||
onToggleVisibility={this.handleToggleMediaVisibility}
|
||||
/>
|
||||
)}
|
||||
</Bundle>
|
||||
);
|
||||
} else {
|
||||
media = (
|
||||
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}>
|
||||
{Component => (
|
||||
<Component
|
||||
compact
|
||||
media={status.get('media_attachments')}
|
||||
lang={language}
|
||||
sensitive={status.get('sensitive')}
|
||||
height={110}
|
||||
onOpenMedia={this.handleOpenMedia}
|
||||
cacheWidth={this.props.cacheMediaWidth}
|
||||
defaultWidth={this.props.cachedMediaWidth}
|
||||
visible={this.state.showMedia}
|
||||
onToggleVisibility={this.handleToggleMediaVisibility}
|
||||
/>
|
||||
)}
|
||||
</Bundle>
|
||||
);
|
||||
}
|
||||
} else if (status.get('card') && !this.props.muted) {
|
||||
media = (
|
||||
<Card
|
||||
onOpenMedia={this.handleOpenMedia}
|
||||
card={status.get('card')}
|
||||
compact
|
||||
sensitive={status.get('sensitive') && !status.get('spoiler_text')}
|
||||
/>
|
||||
);
|
||||
isCardMediaWithSensitive = status.get('spoiler_text').length > 0;
|
||||
}
|
||||
|
||||
const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status);
|
||||
const expanded = !status.get('hidden') || status.get('spoiler_text').length === 0;
|
||||
|
||||
return (
|
||||
<HotKeys handlers={handlers}>
|
||||
<div className={classNames('status__wrapper', 'status__wrapper__compact', `status__wrapper-${status.get('visibility_ex')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef} data-nosnippet={status.getIn(['account', 'noindex'], true) || undefined}>
|
||||
{prepend}
|
||||
|
||||
<div className={classNames('status', `status-${status.get('visibility_ex')}`, { 'status-reply': !!status.get('in_reply_to_id'), 'status--in-thread': !!rootId, 'status--first-in-thread': previousId && (!connectUp || connectToRoot), muted: this.props.muted })} data-id={status.get('id')}>
|
||||
{(connectReply || connectUp || connectToRoot) && <div className={classNames('status__line', { 'status__line--full': connectReply, 'status__line--first': !status.get('in_reply_to_id') && !connectToRoot })} />}
|
||||
|
||||
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
|
||||
<div onClick={this.handleClick} className='status__info'>
|
||||
<a href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} className='status__relative-time' target='_blank' rel='noopener noreferrer'>
|
||||
<RelativeTimestamp timestamp={status.get('created_at')} />{status.get('edited_at') && <abbr title={intl.formatMessage(messages.edited, { date: intl.formatDate(status.get('edited_at'), { hour12: false, year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) })}> *</abbr>}
|
||||
</a>
|
||||
|
||||
<a onClick={this.handleAccountClick} href={`/@${status.getIn(['account', 'acct'])}`} title={status.getIn(['account', 'acct'])} className='status__display-name' target='_blank' rel='noopener noreferrer'>
|
||||
<div className='status__avatar status__avatar__compact'>
|
||||
<Avatar account={status.get('account')} size={24} inline />
|
||||
</div>
|
||||
|
||||
<DisplayName account={status.get('account')} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<StatusContent
|
||||
status={status}
|
||||
onClick={this.handleClick}
|
||||
expanded={expanded}
|
||||
onExpandedToggle={this.handleExpandedToggle}
|
||||
onTranslate={this.handleTranslate}
|
||||
collapsible
|
||||
onCollapsedToggle={this.handleCollapsedToggle}
|
||||
{...statusContentProps}
|
||||
/>
|
||||
|
||||
{(!isCardMediaWithSensitive || !status.get('hidden')) && media}
|
||||
|
||||
{(!status.get('spoiler_text') || expanded) && hashtagBar}
|
||||
</div>
|
||||
</div>
|
||||
</HotKeys>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default injectIntl(CompactedStatus);
|
|
@ -109,7 +109,7 @@ export function computeHashtagBarForStatus(status: StatusLike): {
|
|||
|
||||
const lastChild = template.content.lastChild;
|
||||
|
||||
if (!lastChild) return defaultResult;
|
||||
if (!lastChild || lastChild.nodeType === Node.TEXT_NODE) return defaultResult;
|
||||
|
||||
template.content.removeChild(lastChild);
|
||||
const contentWithoutLastLine = template;
|
||||
|
|
|
@ -236,6 +236,7 @@ class MediaGallery extends PureComponent {
|
|||
visible: PropTypes.bool,
|
||||
autoplay: PropTypes.bool,
|
||||
onToggleVisibility: PropTypes.func,
|
||||
compact: PropTypes.bool,
|
||||
};
|
||||
|
||||
state = {
|
||||
|
@ -306,7 +307,7 @@ class MediaGallery extends PureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { media, lang, intl, sensitive, defaultWidth, autoplay } = this.props;
|
||||
const { media, lang, intl, sensitive, defaultWidth, autoplay, compact } = this.props;
|
||||
const { visible } = this.state;
|
||||
const width = this.state.width || defaultWidth;
|
||||
|
||||
|
@ -359,9 +360,10 @@ class MediaGallery extends PureComponent {
|
|||
const columnClass = (size === 9) ? 'media-gallery--column3' :
|
||||
(size === 10 || size === 11 || size === 12 || size === 13 || size === 14 || size === 15 || size === 16) ? 'media-gallery--column4' :
|
||||
'media-gallery--column2';
|
||||
const compactClass = compact ? 'media-gallery__compact' : null;
|
||||
|
||||
return (
|
||||
<div className={classNames('media-gallery', rowClass, columnClass)} style={style} ref={this.handleRef}>
|
||||
<div className={classNames('media-gallery', rowClass, columnClass, compactClass)} style={style} ref={this.handleRef}>
|
||||
<div className={classNames('spoiler-button', { 'spoiler-button--minified': visible && !uncached, 'spoiler-button--click-thru': uncached })}>
|
||||
{spoilerButton}
|
||||
</div>
|
||||
|
|
|
@ -78,7 +78,7 @@ class ScrollableList extends PureComponent {
|
|||
const clientHeight = this.getClientHeight();
|
||||
const offset = scrollHeight - scrollTop - clientHeight;
|
||||
|
||||
if (400 > offset && this.props.onLoadMore && this.props.hasMore && !this.props.isLoading) {
|
||||
if (scrollTop > 0 && offset < 400 && this.props.onLoadMore && this.props.hasMore && !this.props.isLoading) {
|
||||
this.props.onLoadMore();
|
||||
}
|
||||
|
||||
|
|
|
@ -13,12 +13,13 @@ import AttachmentList from 'mastodon/components/attachment_list';
|
|||
import { Icon } from 'mastodon/components/icon';
|
||||
import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_placeholder';
|
||||
|
||||
import CompactedStatusContainer from '../containers/compacted_status_container'
|
||||
import Card from '../features/status/components/card';
|
||||
// We use the component (and not the container) since we do not want
|
||||
// to use the progress bar to show download progress
|
||||
import Bundle from '../features/ui/components/bundle';
|
||||
import { MediaGallery, Video, Audio } from '../features/ui/util/async-components';
|
||||
import { displayMedia, enableEmojiReaction, showEmojiReactionOnTimeline } from '../initial_state';
|
||||
import { displayMedia, enableEmojiReaction, showEmojiReactionOnTimeline, showQuoteInHome, showQuoteInPublic } from '../initial_state';
|
||||
|
||||
import { Avatar } from './avatar';
|
||||
import { AvatarOverlay } from './avatar_overlay';
|
||||
|
@ -87,6 +88,7 @@ class Status extends ImmutablePureComponent {
|
|||
static propTypes = {
|
||||
status: ImmutablePropTypes.map,
|
||||
account: ImmutablePropTypes.map,
|
||||
contextType: PropTypes.string,
|
||||
previousId: PropTypes.string,
|
||||
nextInReplyToId: PropTypes.string,
|
||||
rootId: PropTypes.string,
|
||||
|
@ -357,15 +359,17 @@ class Status extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
render () {
|
||||
const { intl, hidden, featured, unread, showThread, scrollKey, pictureInPicture, previousId, nextInReplyToId, rootId } = this.props;
|
||||
const { intl, hidden, featured, unread, muted, showThread, scrollKey, pictureInPicture, previousId, nextInReplyToId, rootId } = this.props;
|
||||
|
||||
let { status, account, ...other } = this.props;
|
||||
|
||||
const contextType = (this.props.contextType || '').split(':')[0];
|
||||
|
||||
if (status === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handlers = this.props.muted ? {} : {
|
||||
const handlers = muted ? {} : {
|
||||
reply: this.handleHotkeyReply,
|
||||
favourite: this.handleHotkeyFavourite,
|
||||
boost: this.handleHotkeyBoost,
|
||||
|
@ -384,7 +388,7 @@ class Status extends ImmutablePureComponent {
|
|||
if (hidden) {
|
||||
return (
|
||||
<HotKeys handlers={handlers}>
|
||||
<div ref={this.handleRef} className={classNames('status__wrapper', { focusable: !this.props.muted })} tabIndex={0}>
|
||||
<div ref={this.handleRef} className={classNames('status__wrapper', { focusable: !muted })} tabIndex={0}>
|
||||
<span>{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}</span>
|
||||
<span>{status.get('content')}</span>
|
||||
</div>
|
||||
|
@ -412,25 +416,6 @@ class Status extends ImmutablePureComponent {
|
|||
|
||||
let visibilityIcon = visibilityIconInfo[status.get('limited_scope') || status.get('visibility_ex')] || visibilityIconInfo[status.get('visibility')];
|
||||
|
||||
if (this.state.forceFilter === undefined ? matchedFilters : this.state.forceFilter) {
|
||||
const minHandlers = this.props.muted ? {} : {
|
||||
moveUp: this.handleHotkeyMoveUp,
|
||||
moveDown: this.handleHotkeyMoveDown,
|
||||
};
|
||||
|
||||
return (
|
||||
<HotKeys handlers={minHandlers}>
|
||||
<div className='status__wrapper status__wrapper--filtered focusable' tabIndex={0} ref={this.handleRef}>
|
||||
<FormattedMessage id='status.filtered' defaultMessage='Filtered' />: {matchedFilters.join(', ')}.
|
||||
{' '}
|
||||
<button className='status__wrapper--filtered__button' onClick={this.handleUnfilterClick}>
|
||||
<FormattedMessage id='status.show_filter_reason' defaultMessage='Show anyway' />
|
||||
</button>
|
||||
</div>
|
||||
</HotKeys>
|
||||
);
|
||||
}
|
||||
|
||||
if (featured) {
|
||||
prepend = (
|
||||
<div className='status__prepend'>
|
||||
|
@ -471,6 +456,63 @@ class Status extends ImmutablePureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
if (account === undefined || account === null) {
|
||||
statusAvatar = <Avatar account={status.get('account')} size={46} />;
|
||||
} else {
|
||||
statusAvatar = <AvatarOverlay account={status.get('account')} friend={account} />;
|
||||
}
|
||||
|
||||
if (this.state.forceFilter === undefined ? matchedFilters : this.state.forceFilter) {
|
||||
const minHandlers = muted ? {} : {
|
||||
moveUp: this.handleHotkeyMoveUp,
|
||||
moveDown: this.handleHotkeyMoveDown,
|
||||
};
|
||||
|
||||
if (status.get('filter_action') === 'half_warn') {
|
||||
return (
|
||||
<HotKeys handlers={minHandlers}>
|
||||
<div className='status__wrapper status__wrapper--filtered focusable' tabIndex={0} ref={this.handleRef}>
|
||||
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
|
||||
<div onClick={this.handleClick} className='status__info'>
|
||||
<a href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} className='status__relative-time' target='_blank' rel='noopener noreferrer'>
|
||||
<span className='status__visibility-icon'><Icon id={visibilityIcon.icon} title={visibilityIcon.text} /></span>
|
||||
<RelativeTimestamp timestamp={status.get('created_at')} />{status.get('edited_at') && <abbr title={intl.formatMessage(messages.edited, { date: intl.formatDate(status.get('edited_at'), { hour12: false, year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) })}> *</abbr>}
|
||||
</a>
|
||||
|
||||
<a onClick={this.handleAccountClick} href={`/@${status.getIn(['account', 'acct'])}`} title={status.getIn(['account', 'acct'])} className='status__display-name' target='_blank' rel='noopener noreferrer'>
|
||||
<div className='status__avatar'>
|
||||
{statusAvatar}
|
||||
</div>
|
||||
|
||||
<DisplayName account={status.get('account')} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div >
|
||||
<FormattedMessage id='status.filtered' defaultMessage='Filtered' />: {matchedFilters.join(', ')}.
|
||||
{' '}
|
||||
<button className='status__wrapper--filtered__button' onClick={this.handleUnfilterClick}>
|
||||
<FormattedMessage id='status.show_filter_reason' defaultMessage='Show anyway' />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</HotKeys>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<HotKeys handlers={minHandlers}>
|
||||
<div className='status__wrapper status__wrapper--filtered focusable' tabIndex={0} ref={this.handleRef}>
|
||||
<FormattedMessage id='status.filtered' defaultMessage='Filtered' />: {matchedFilters.join(', ')}.
|
||||
{' '}
|
||||
<button className='status__wrapper--filtered__button' onClick={this.handleUnfilterClick}>
|
||||
<FormattedMessage id='status.show_filter_reason' defaultMessage='Show anyway' />
|
||||
</button>
|
||||
</div>
|
||||
</HotKeys>
|
||||
);
|
||||
}
|
||||
|
||||
isCardMediaWithSensitive = false;
|
||||
|
||||
if (pictureInPicture.get('inUse')) {
|
||||
|
@ -478,7 +520,7 @@ class Status extends ImmutablePureComponent {
|
|||
} else if (status.get('media_attachments').size > 0) {
|
||||
const language = status.getIn(['translation', 'language']) || status.get('language');
|
||||
|
||||
if (this.props.muted) {
|
||||
if (muted) {
|
||||
media = (
|
||||
<AttachmentList
|
||||
compact
|
||||
|
@ -556,7 +598,7 @@ class Status extends ImmutablePureComponent {
|
|||
</Bundle>
|
||||
);
|
||||
}
|
||||
} else if (status.get('card') && !this.props.muted) {
|
||||
} else if (status.get('card') && !muted) {
|
||||
media = (
|
||||
<Card
|
||||
onOpenMedia={this.handleOpenMedia}
|
||||
|
@ -568,12 +610,6 @@ class Status extends ImmutablePureComponent {
|
|||
isCardMediaWithSensitive = status.get('spoiler_text').length > 0;
|
||||
}
|
||||
|
||||
if (account === undefined || account === null) {
|
||||
statusAvatar = <Avatar account={status.get('account')} size={46} />;
|
||||
} else {
|
||||
statusAvatar = <AvatarOverlay account={status.get('account')} friend={account} />;
|
||||
}
|
||||
|
||||
visibilityIcon = visibilityIconInfo[status.get('limited_scope') || status.get('visibility_ex')] || visibilityIconInfo[status.get('visibility')];
|
||||
|
||||
let emojiReactionsBar = null;
|
||||
|
@ -588,20 +624,24 @@ class Status extends ImmutablePureComponent {
|
|||
const expanded = !status.get('hidden') || status.get('spoiler_text').length === 0;
|
||||
|
||||
const withLimited = status.get('visibility_ex') === 'limited' && status.get('limited_scope') ? <span className='status__visibility-icon'><Icon id='get-pocket' title='Limited' /></span> : null;
|
||||
const withReference = status.get('status_references_count') > 0 ? <span className='status__visibility-icon'><Icon id='link' title='Reference' /></span> : null;
|
||||
const withQuote = status.get('quote_id') ? <span className='status__visibility-icon'><Icon id='quote-right' title='Quote' /></span> : null;
|
||||
const withReference = (!withQuote && status.get('status_references_count') > 0) ? <span className='status__visibility-icon'><Icon id='link' title='Reference' /></span> : null;
|
||||
const withExpiration = status.get('expires_at') ? <span className='status__visibility-icon'><Icon id='clock-o' title='Expiration' /></span> : null;
|
||||
|
||||
const quote = !muted && status.get('quote_id') && (['public', 'community'].includes(contextType) ? showQuoteInPublic : showQuoteInHome) && <CompactedStatusContainer id={status.get('quote_id')} />
|
||||
|
||||
return (
|
||||
<HotKeys handlers={handlers}>
|
||||
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility_ex')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef} data-nosnippet={status.getIn(['account', 'noindex'], true) || undefined}>
|
||||
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility_ex')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, focusable: !muted })} tabIndex={muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef} data-nosnippet={status.getIn(['account', 'noindex'], true) || undefined}>
|
||||
{prepend}
|
||||
|
||||
<div className={classNames('status', `status-${status.get('visibility_ex')}`, { 'status-reply': !!status.get('in_reply_to_id'), 'status--in-thread': !!rootId, 'status--first-in-thread': previousId && (!connectUp || connectToRoot), muted: this.props.muted })} data-id={status.get('id')}>
|
||||
<div className={classNames('status', `status-${status.get('visibility_ex')}`, { 'status-reply': !!status.get('in_reply_to_id'), 'status--in-thread': !!rootId, 'status--first-in-thread': previousId && (!connectUp || connectToRoot), muted: muted })} data-id={status.get('id')}>
|
||||
{(connectReply || connectUp || connectToRoot) && <div className={classNames('status__line', { 'status__line--full': connectReply, 'status__line--first': !status.get('in_reply_to_id') && !connectToRoot })} />}
|
||||
|
||||
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
|
||||
<div onClick={this.handleClick} className='status__info'>
|
||||
<a href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} className='status__relative-time' target='_blank' rel='noopener noreferrer'>
|
||||
{withQuote}
|
||||
{withReference}
|
||||
{withExpiration}
|
||||
{withLimited}
|
||||
|
@ -629,6 +669,8 @@ class Status extends ImmutablePureComponent {
|
|||
{...statusContentProps}
|
||||
/>
|
||||
|
||||
{(!status.get('spoiler_text') || expanded) && quote}
|
||||
|
||||
{(!isCardMediaWithSensitive || !status.get('hidden')) && media}
|
||||
|
||||
{(!status.get('spoiler_text') || expanded) && hashtagBar}
|
||||
|
|
|
@ -298,6 +298,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
const account = status.get('account');
|
||||
const writtenByMe = status.getIn(['account', 'id']) === me;
|
||||
const isRemote = status.getIn(['account', 'username']) !== status.getIn(['account', 'acct']);
|
||||
const allowQuote = status.getIn(['account', 'other_settings', 'allow_quote']);
|
||||
|
||||
let menu = [];
|
||||
|
||||
|
@ -332,7 +333,10 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
|
||||
if (publicStatus) {
|
||||
menu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference });
|
||||
menu.push({ text: intl.formatMessage(messages.quote), action: this.handleQuote });
|
||||
|
||||
if (allowQuote) {
|
||||
menu.push({ text: intl.formatMessage(messages.quote), action: this.handleQuote });
|
||||
}
|
||||
}
|
||||
|
||||
menu.push({ text: intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClickOriginal });
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
import { injectIntl } from 'react-intl';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { openModal } from '../actions/modal';
|
||||
import {
|
||||
hideStatus,
|
||||
revealStatus,
|
||||
toggleStatusCollapse,
|
||||
translateStatus,
|
||||
undoStatusTranslation,
|
||||
} from '../actions/statuses';
|
||||
import CompactedStatus from '../components/compacted_status';
|
||||
import { makeGetStatus, makeGetPictureInPicture } from '../selectors';
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getStatus = makeGetStatus();
|
||||
const getPictureInPicture = makeGetPictureInPicture();
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
status: getStatus(state, props),
|
||||
nextInReplyToId: props.nextId ? state.getIn(['statuses', props.nextId, 'in_reply_to_id']) : null,
|
||||
pictureInPicture: getPictureInPicture(state, props),
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
onTranslate (status) {
|
||||
if (status.get('translation')) {
|
||||
dispatch(undoStatusTranslation(status.get('id'), status.get('poll')));
|
||||
} else {
|
||||
dispatch(translateStatus(status.get('id')));
|
||||
}
|
||||
},
|
||||
|
||||
onOpenMedia (statusId, media, index, lang) {
|
||||
dispatch(openModal({
|
||||
modalType: 'MEDIA',
|
||||
modalProps: { statusId, media, index, lang },
|
||||
}));
|
||||
},
|
||||
|
||||
onOpenVideo (statusId, media, lang, options) {
|
||||
dispatch(openModal({
|
||||
modalType: 'VIDEO',
|
||||
modalProps: { statusId, media, lang, options },
|
||||
}));
|
||||
},
|
||||
|
||||
onToggleHidden (status) {
|
||||
if (status.get('hidden')) {
|
||||
dispatch(revealStatus(status.get('id')));
|
||||
} else {
|
||||
dispatch(hideStatus(status.get('id')));
|
||||
}
|
||||
},
|
||||
|
||||
onToggleCollapsed (status, isCollapsed) {
|
||||
dispatch(toggleStatusCollapse(status.get('id'), isCollapsed));
|
||||
},
|
||||
|
||||
onInteractionModal (type, status) {
|
||||
dispatch(openModal({
|
||||
modalType: 'INTERACTION',
|
||||
modalProps: {
|
||||
type,
|
||||
accountId: status.getIn(['account', 'id']),
|
||||
url: status.get('uri'),
|
||||
},
|
||||
}));
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(CompactedStatus));
|
|
@ -80,6 +80,8 @@ const makeMapStateToProps = () => {
|
|||
|
||||
const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
|
||||
|
||||
contextType,
|
||||
|
||||
onReply (status, router) {
|
||||
dispatch((_, getState) => {
|
||||
let state = getState();
|
||||
|
|
|
@ -15,6 +15,8 @@ import { IconButton } from '../../../components/icon_button';
|
|||
const messages = defineMessages({
|
||||
public_short: { id: 'searchability.public.short', defaultMessage: 'Public' },
|
||||
public_long: { id: 'searchability.public.long', defaultMessage: 'Anyone can find' },
|
||||
public_unlisted_short: { id: 'searchability.public_unlisted.short', defaultMessage: 'Public unlisted' },
|
||||
public_unlisted_long: { id: 'searchability.public_unlisted.long', defaultMessage: 'Local users and followers can find' },
|
||||
private_short: { id: 'searchability.unlisted.short', defaultMessage: 'Followers' },
|
||||
private_long: { id: 'searchability.unlisted.long', defaultMessage: 'Your followers can find' },
|
||||
direct_short: { id: 'searchability.private.short', defaultMessage: 'Reactionners' },
|
||||
|
@ -223,6 +225,7 @@ class SearchabilityDropdown extends PureComponent {
|
|||
|
||||
this.options = [
|
||||
{ icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) },
|
||||
{ icon: 'cloud', value: 'public_unlisted', text: formatMessage(messages.public_unlisted_short), meta: formatMessage(messages.public_unlisted_long) },
|
||||
{ icon: 'unlock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) },
|
||||
{ icon: 'lock', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
|
||||
{ icon: 'at', value: 'limited', text: formatMessage(messages.limited_short), meta: formatMessage(messages.limited_long) },
|
||||
|
|
|
@ -236,6 +236,7 @@ class ActionBar extends PureComponent {
|
|||
const account = status.get('account');
|
||||
const writtenByMe = status.getIn(['account', 'id']) === me;
|
||||
const isRemote = status.getIn(['account', 'username']) !== status.getIn(['account', 'acct']);
|
||||
const allowQuote = status.getIn(['account', 'other_settings', 'allow_quote']);
|
||||
|
||||
let menu = [];
|
||||
|
||||
|
@ -259,7 +260,10 @@ class ActionBar extends PureComponent {
|
|||
|
||||
if (publicStatus) {
|
||||
menu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference });
|
||||
menu.push({ text: intl.formatMessage(messages.quote), action: this.handleQuote });
|
||||
|
||||
if (allowQuote) {
|
||||
menu.push({ text: intl.formatMessage(messages.quote), action: this.handleQuote });
|
||||
}
|
||||
}
|
||||
menu.push({ text: intl.formatMessage(messages.bookmark_category), action: this.handleBookmarkCategoryAdderClick });
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ const messages = defineMessages({
|
|||
personal_short: { id: 'privacy.personal.short', defaultMessage: 'Yourself only' },
|
||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
||||
searchability_public_short: { id: 'searchability.public.short', defaultMessage: 'Public' },
|
||||
searchability_public_unlisted_short: { id: 'searchability.public_unlisted.short', defaultMessage: 'Public unlisted' },
|
||||
searchability_private_short: { id: 'searchability.unlisted.short', defaultMessage: 'Followers' },
|
||||
searchability_direct_short: { id: 'searchability.private.short', defaultMessage: 'Reactionners' },
|
||||
searchability_limited_short: { id: 'searchability.direct.short', defaultMessage: 'Self only' },
|
||||
|
@ -270,6 +271,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
|||
|
||||
const searchabilityIconInfo = {
|
||||
'public': { icon: 'globe', text: intl.formatMessage(messages.searchability_public_short) },
|
||||
'public_unlisted': { icon: 'cloud', text: intl.formatMessage(messages.searchability_public_unlisted_short) },
|
||||
'private': { icon: 'unlock', text: intl.formatMessage(messages.searchability_private_short) },
|
||||
'direct': { icon: 'lock', text: intl.formatMessage(messages.searchability_direct_short) },
|
||||
'limited': { icon: 'at', text: intl.formatMessage(messages.searchability_limited_short) },
|
||||
|
|
|
@ -233,6 +233,8 @@ class Status extends ImmutablePureComponent {
|
|||
|
||||
componentDidMount () {
|
||||
attachFullscreenListener(this.onFullScreenChange);
|
||||
|
||||
this._scrollStatusIntoView();
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps (nextProps) {
|
||||
|
@ -638,10 +640,10 @@ class Status extends ImmutablePureComponent {
|
|||
this.node = c;
|
||||
};
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
const { status, ancestorsIds, multiColumn } = this.props;
|
||||
_scrollStatusIntoView () {
|
||||
const { status, multiColumn } = this.props;
|
||||
|
||||
if (status && (ancestorsIds.size > prevProps.ancestorsIds.size || prevProps.status?.get('id') !== status.get('id'))) {
|
||||
if (status) {
|
||||
window.requestAnimationFrame(() => {
|
||||
this.node?.querySelector('.detailed-status__wrapper')?.scrollIntoView(true);
|
||||
|
||||
|
@ -658,6 +660,14 @@ class Status extends ImmutablePureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
const { status, ancestorsIds } = this.props;
|
||||
|
||||
if (status && (ancestorsIds.size > prevProps.ancestorsIds.size || prevProps.status?.get('id') !== status.get('id'))) {
|
||||
this._scrollStatusIntoView();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
detachFullscreenListener(this.onFullScreenChange);
|
||||
}
|
||||
|
@ -666,6 +676,22 @@ class Status extends ImmutablePureComponent {
|
|||
this.setState({ fullscreen: isFullscreen() });
|
||||
};
|
||||
|
||||
shouldUpdateScroll = (prevRouterProps, { location }) => {
|
||||
// Do not change scroll when opening a modal
|
||||
if (location.state?.mastodonModalKey !== prevRouterProps?.location?.state?.mastodonModalKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Scroll to focused post if it is loaded
|
||||
const child = this.node?.querySelector('.detailed-status__wrapper');
|
||||
if (child) {
|
||||
return [0, child.offsetTop];
|
||||
}
|
||||
|
||||
// Do not scroll otherwise, `componentDidUpdate` will take care of that
|
||||
return false;
|
||||
};
|
||||
|
||||
render () {
|
||||
let ancestors, descendants, references;
|
||||
const { isLoading, status, ancestorsIds, descendantsIds, referenceIds, intl, domain, multiColumn, pictureInPicture } = this.props;
|
||||
|
@ -723,7 +749,7 @@ class Status extends ImmutablePureComponent {
|
|||
)}
|
||||
/>
|
||||
|
||||
<ScrollContainer scrollKey='thread'>
|
||||
<ScrollContainer scrollKey='thread' shouldUpdateScroll={this.shouldUpdateScroll}>
|
||||
<div className={classNames('scrollable', { fullscreen })} ref={this.setRef}>
|
||||
{references}
|
||||
{ancestors}
|
||||
|
|
|
@ -64,6 +64,7 @@
|
|||
* @property {boolean} enable_local_privacy
|
||||
* @property {boolean} enable_dtl_menu
|
||||
* @property {boolean=} expand_spoilers
|
||||
* @property {boolean} hide_blocking_quote
|
||||
* @property {boolean} hide_recent_emojis
|
||||
* @property {boolean} limited_federation_mode
|
||||
* @property {string} locale
|
||||
|
@ -78,6 +79,8 @@
|
|||
* @property {boolean} search_enabled
|
||||
* @property {boolean} trends_enabled
|
||||
* @property {boolean} show_emoji_reaction_on_timeline
|
||||
* @property {boolean} show_quote_in_home
|
||||
* @property {boolean} show_quote_in_public
|
||||
* @property {string} simple_timeline_menu
|
||||
* @property {boolean} single_user_mode
|
||||
* @property {string} source_url
|
||||
|
@ -136,6 +139,7 @@ export const enableLoginPrivacy = getMeta('enable_login_privacy');
|
|||
export const enableDtlMenu = getMeta('enable_dtl_menu');
|
||||
export const expandSpoilers = getMeta('expand_spoilers');
|
||||
export const forceSingleColumn = !getMeta('advanced_layout');
|
||||
export const hideBlockingQuote = getMeta('hide_blocking_quote');
|
||||
export const hideRecentEmojis = getMeta('hide_recent_emojis');
|
||||
export const limitedFederationMode = getMeta('limited_federation_mode');
|
||||
export const mascot = getMeta('mascot');
|
||||
|
@ -149,6 +153,8 @@ export const repository = getMeta('repository');
|
|||
export const searchEnabled = getMeta('search_enabled');
|
||||
export const trendsEnabled = getMeta('trends_enabled');
|
||||
export const showEmojiReactionOnTimeline = getMeta('show_emoji_reaction_on_timeline');
|
||||
export const showQuoteInHome = getMeta('show_quote_in_home');
|
||||
export const showQuoteInPublic = getMeta('show_quote_in_public');
|
||||
export const showTrends = getMeta('show_trends');
|
||||
export const simpleTimelineMenu = getMeta('simple_timeline_menu');
|
||||
export const singleUserMode = getMeta('single_user_mode');
|
||||
|
|
|
@ -190,6 +190,7 @@
|
|||
"conversation.open": "কথপোকথন দেখান",
|
||||
"conversation.with": "{names} এর সঙ্গে",
|
||||
"copypaste.copied": "অনুলিপিকৃত",
|
||||
"copypaste.copy_to_clipboard": "ক্লিপবোর্ডে কপি করুন",
|
||||
"directory.federated": "পরিচিত ফেডিভারসের থেকে",
|
||||
"directory.local": "শুধু {domain} থেকে",
|
||||
"directory.new_arrivals": "নতুন আগত",
|
||||
|
|
|
@ -629,6 +629,8 @@
|
|||
"searchability.private.short": "Reactionners",
|
||||
"searchability.public.long": "Anyone can find",
|
||||
"searchability.public.short": "Everyone",
|
||||
"searchability.public_unlisted.long": "Local users and followers can find",
|
||||
"searchability.public_unlisted.short": "Local and followers",
|
||||
"searchability.unlisted.long": "Your followers and reactionners can find",
|
||||
"searchability.unlisted.short": "Followers and reactionners",
|
||||
"search_popout.domain": "domain",
|
||||
|
@ -692,7 +694,7 @@
|
|||
"status.open": "Expand this post",
|
||||
"status.pin": "Pin on profile",
|
||||
"status.pinned": "Pinned post",
|
||||
"status.quote": "Ref (quote in other servers)",
|
||||
"status.quote": "Quote",
|
||||
"status.read_more": "Read more",
|
||||
"status.reblog": "Boost",
|
||||
"status.reblog_private": "Boost with original visibility",
|
||||
|
|
|
@ -686,7 +686,7 @@
|
|||
"timeline_hint.resources.followers": "Seuraajat",
|
||||
"timeline_hint.resources.follows": "seurattua",
|
||||
"timeline_hint.resources.statuses": "Vanhemmat julkaisut",
|
||||
"trends.counter_by_accounts": "{count, plural, one {{counter} henkilö} other {{counter} henkilöä}} viimeisten {days, plural, one {päivän} other {{days} päivän}}",
|
||||
"trends.counter_by_accounts": "{count, plural, one {{counter} henkilö} other {{counter} henkilöä}} {days, plural, one {viimeisen päivän} other {viimeisten {days} päivän}} aikana",
|
||||
"trends.trending_now": "Suosittua nyt",
|
||||
"ui.beforeunload": "Luonnos häviää, jos poistut Mastodonista.",
|
||||
"units.short.billion": "{count} mrd.",
|
||||
|
|
|
@ -714,6 +714,8 @@
|
|||
"searchability.private.short": "反応者のみ",
|
||||
"searchability.public.long": "この投稿は誰でも検索できます",
|
||||
"searchability.public.short": "誰でも",
|
||||
"searchability.public_unlisted.long": "ローカルユーザーとフォロワーが検索できます",
|
||||
"searchability.public_unlisted.short": "ローカルとフォロワー",
|
||||
"searchability.unlisted.long": "この投稿はあなたのフォロワーと反応者だけが検索できます",
|
||||
"searchability.unlisted.short": "フォロワーと反応者",
|
||||
"search_popout.domain": "ドメイン",
|
||||
|
@ -778,7 +780,7 @@
|
|||
"status.open": "詳細を表示",
|
||||
"status.pin": "プロフィールに固定表示",
|
||||
"status.pinned": "固定された投稿",
|
||||
"status.quote": "参照 (他サーバーで引用扱い)",
|
||||
"status.quote": "引用",
|
||||
"status.read_more": "もっと見る",
|
||||
"status.reblog": "ブースト",
|
||||
"status.reblog_private": "ブースト",
|
||||
|
|
|
@ -102,7 +102,7 @@
|
|||
"bundle_modal_error.message": "컴포넌트를 불러오는 중 문제가 발생했습니다.",
|
||||
"bundle_modal_error.retry": "다시 시도",
|
||||
"closed_registrations.other_server_instructions": "마스토돈은 분산화 되어 있기 때문에, 다른 서버에서 계정을 만들더라도 이 서버와 상호작용 할 수 있습니다.",
|
||||
"closed_registrations_modal.description": "{domain}은 현재 가입이 막혀있는 상태입니다, 만약 마스토돈을 이용하기 위해 꼭 {domain}을 사용할 필요는 없다는 사실을 인지해 두세요.",
|
||||
"closed_registrations_modal.description": "{domain}은 현재 가입이 막혀있는 상태입니다, 마스토돈을 이용하기 위해 꼭 {domain}을 사용할 필요는 없다는 사실을 인지해 두세요.",
|
||||
"closed_registrations_modal.find_another_server": "다른 서버 찾기",
|
||||
"closed_registrations_modal.preamble": "마스토돈은 분산화 되어 있습니다, 그렇기 때문에 어디에서 계정을 생성하든, 이 서버에 있는 누구와도 팔로우와 상호작용을 할 수 있습니다. 심지어는 스스로 서버를 만드는 것도 가능합니다!",
|
||||
"closed_registrations_modal.title": "마스토돈에서 가입",
|
||||
|
|
|
@ -161,13 +161,13 @@
|
|||
"compose_form.spoiler.unmarked": "Pievienot satura brīdinājumu",
|
||||
"compose_form.spoiler_placeholder": "Ieraksti savu brīdinājumu šeit",
|
||||
"confirmation_modal.cancel": "Atcelt",
|
||||
"confirmations.block.block_and_report": "Bloķēt un Ziņot",
|
||||
"confirmations.block.block_and_report": "Bloķēt un ziņot",
|
||||
"confirmations.block.confirm": "Bloķēt",
|
||||
"confirmations.block.message": "Vai tiešām vēlies bloķēt {name}?",
|
||||
"confirmations.cancel_follow_request.confirm": "Atsaukt pieprasījumu",
|
||||
"confirmations.cancel_follow_request.message": "Vai tiešām vēlies atsaukt pieprasījumu sekot {name}?",
|
||||
"confirmations.delete.confirm": "Dzēst",
|
||||
"confirmations.delete.message": "Vai tiešām vēlies dzēst šo ziņu?",
|
||||
"confirmations.delete.message": "Vai tiešām vēlies dzēst šo ierakstu?",
|
||||
"confirmations.delete_list.confirm": "Dzēst",
|
||||
"confirmations.delete_list.message": "Vai tiešam vēlies neatgriezeniski dzēst šo sarakstu?",
|
||||
"confirmations.discard_edit_media.confirm": "Atmest",
|
||||
|
@ -244,7 +244,7 @@
|
|||
"empty_column.public": "Šeit vēl nekā nav! Ieraksti ko publiski vai pieseko lietotājiem no citiem serveriem",
|
||||
"error.unexpected_crash.explanation": "Koda kļūdas vai pārlūkprogrammas saderības problēmas dēļ šo lapu nevarēja parādīt pareizi.",
|
||||
"error.unexpected_crash.explanation_addons": "Šo lapu nevarēja parādīt pareizi. Šo kļūdu, iespējams, izraisīja pārlūkprogrammas papildinājums vai automātiskās tulkošanas rīki.",
|
||||
"error.unexpected_crash.next_steps": "Mēģini atsvaidzināt lapu. Ja tas nepalīdz, vari lietot Mastodon, izmantojot citu pārlūkprogrammu vai lietotni.",
|
||||
"error.unexpected_crash.next_steps": "Mēģini atsvaidzināt lapu. Ja tas nepalīdz, iespējams, varēsi lietot Mastodon, izmantojot citu pārlūkprogrammu vai lietotni.",
|
||||
"error.unexpected_crash.next_steps_addons": "Mēģini tos atspējot un atsvaidzināt lapu. Ja tas nepalīdz, iespējams, varēsi lietot Mastodon, izmantojot citu pārlūkprogrammu vai lietotni.",
|
||||
"errors.unexpected_crash.copy_stacktrace": "Kopēt stacktrace uz starpliktuvi",
|
||||
"errors.unexpected_crash.report_issue": "Ziņot par problēmu",
|
||||
|
@ -309,11 +309,11 @@
|
|||
"home.column_settings.show_replies": "Rādīt atbildes",
|
||||
"home.explore_prompt.body": "Tavā mājas plūsmā būs dažādu ziņu sajaukums no atsaucēm, kurām esi izvēlējies sekot, personām, kurām esi izvēlējies sekot, un ziņām, kuras tās izceļ. Ja tas šķiet pārāk kluss, iespējams, vēlēsies:",
|
||||
"home.explore_prompt.title": "Šī ir tava Mastodon mājvieta.",
|
||||
"home.hide_announcements": "Slēpt anonsus",
|
||||
"home.hide_announcements": "Slēpt paziņojumus",
|
||||
"home.pending_critical_update.body": "Lūdzu, pēc iespējas ātrāk atjaunini savu Mastodon serveri!",
|
||||
"home.pending_critical_update.link": "Skatīt jauninājumus",
|
||||
"home.pending_critical_update.title": "Pieejams kritisks drošības jauninājums!",
|
||||
"home.show_announcements": "Rādīt anonsus",
|
||||
"home.show_announcements": "Rādīt paziņojumus",
|
||||
"interaction_modal.description.favourite": "Ar Mastodon kontu tu vari pievienot šo ziņu izlasei, lai informētu autoru, ka to novērtē, un saglabātu to vēlākai lasīšanai.",
|
||||
"interaction_modal.description.follow": "Ar Mastodon kontu tu vari sekot {name}, lai saņemtu viņu ziņas savā mājas plūsmā.",
|
||||
"interaction_modal.description.reblog": "Izmantojot kontu Mastodon, tu vari izcelt šo ziņu, lai kopīgotu to ar saviem sekotājiem.",
|
||||
|
@ -369,7 +369,7 @@
|
|||
"lightbox.close": "Aizvērt",
|
||||
"lightbox.compress": "Saspiest attēla skata lodziņu",
|
||||
"lightbox.expand": "Izvērst attēla skata lodziņu",
|
||||
"lightbox.next": "Nākamais",
|
||||
"lightbox.next": "Tālāk",
|
||||
"lightbox.previous": "Iepriekšējais",
|
||||
"limited_account_hint.action": "Tik un tā rādīt profilu",
|
||||
"limited_account_hint.title": "{domain} moderatori ir paslēpuši šo profilu.",
|
||||
|
@ -422,8 +422,8 @@
|
|||
"navigation_bar.search": "Meklēt",
|
||||
"navigation_bar.security": "Drošība",
|
||||
"not_signed_in_indicator.not_signed_in": "Lai piekļūtu šim resursam, tev ir jāpierakstās.",
|
||||
"notification.admin.report": "{name} sūdzējās par {target}",
|
||||
"notification.admin.sign_up": "{name} pierakstījās",
|
||||
"notification.admin.report": "{name} ziņoja par {target}",
|
||||
"notification.admin.sign_up": "{name} ir pierakstījies",
|
||||
"notification.favourite": "{name} pievienoja tavu ziņu izlasei",
|
||||
"notification.follow": "{name} uzsāka tev sekot",
|
||||
"notification.follow_request": "{name} nosūtīja tev sekošanas pieprasījumu",
|
||||
|
@ -435,7 +435,7 @@
|
|||
"notification.update": "{name} rediģēja ierakstu",
|
||||
"notifications.clear": "Notīrīt paziņojumus",
|
||||
"notifications.clear_confirmation": "Vai tiešām vēlies neatgriezeniski notīrīt visus savus paziņojumus?",
|
||||
"notifications.column_settings.admin.report": "Jaunas sūdzības:",
|
||||
"notifications.column_settings.admin.report": "Jauni ziņojumi:",
|
||||
"notifications.column_settings.admin.sign_up": "Jaunas pierakstīšanās:",
|
||||
"notifications.column_settings.alert": "Darbvirsmas paziņojumi",
|
||||
"notifications.column_settings.favourite": "Izlase:",
|
||||
|
@ -445,7 +445,7 @@
|
|||
"notifications.column_settings.follow": "Jauni sekotāji:",
|
||||
"notifications.column_settings.follow_request": "Jauni sekošanas pieprasījumi:",
|
||||
"notifications.column_settings.mention": "Pieminējumi:",
|
||||
"notifications.column_settings.poll": "Aptauju rezultāti:",
|
||||
"notifications.column_settings.poll": "Aptaujas rezultāti:",
|
||||
"notifications.column_settings.push": "Uznirstošie paziņojumi",
|
||||
"notifications.column_settings.reblog": "Pastiprinātie ieraksti:",
|
||||
"notifications.column_settings.show": "Rādīt kolonnā",
|
||||
|
@ -457,13 +457,13 @@
|
|||
"notifications.filter.all": "Visi",
|
||||
"notifications.filter.boosts": "Pastiprinātie ieraksti",
|
||||
"notifications.filter.favourites": "Izlases",
|
||||
"notifications.filter.follows": "Sekošana",
|
||||
"notifications.filter.follows": "Seko",
|
||||
"notifications.filter.mentions": "Pieminējumi",
|
||||
"notifications.filter.polls": "Aptauju rezultāti",
|
||||
"notifications.filter.polls": "Aptaujas rezultāti",
|
||||
"notifications.filter.statuses": "Jaunumi no cilvēkiem, kuriem tu seko",
|
||||
"notifications.grant_permission": "Piešķirt atļauju.",
|
||||
"notifications.group": "{count} paziņojumi",
|
||||
"notifications.mark_as_read": "Atzīmēt visus paziņojumus kā izlasītus",
|
||||
"notifications.mark_as_read": "Atzīmēt katru paziņojumu kā izlasītu",
|
||||
"notifications.permission_denied": "Darbvirsmas paziņojumi nav pieejami, jo iepriekš tika noraidīts pārlūka atļauju pieprasījums",
|
||||
"notifications.permission_denied_alert": "Darbvirsmas paziņojumus nevar iespējot, jo pārlūkprogrammai atļauja tika iepriekš atteikta",
|
||||
"notifications.permission_required": "Darbvirsmas paziņojumi nav pieejami, jo nav piešķirta nepieciešamā atļauja.",
|
||||
|
@ -563,25 +563,25 @@
|
|||
"report.reasons.spam": "Tas ir spams",
|
||||
"report.reasons.spam_description": "Ļaunprātīgas saites, viltus iesaistīšana vai atkārtotas atbildes",
|
||||
"report.reasons.violation": "Tas pārkāpj servera noteikumus",
|
||||
"report.reasons.violation_description": "Tu zini, ka tas pārkāpj konkrētus noteikumus",
|
||||
"report.reasons.violation_description": "Tu zini, ka tas pārkāpj īpašus noteikumus",
|
||||
"report.rules.subtitle": "Atlasi visus atbilstošos",
|
||||
"report.rules.title": "Kuri noteikumi tiek pārkāpti?",
|
||||
"report.statuses.subtitle": "Atlasi visus atbilstošos",
|
||||
"report.statuses.title": "Vai ir kādi ieraksti, kas atbalsta šo sūdzību?",
|
||||
"report.submit": "Iesniegt",
|
||||
"report.target": "Sūdzība par {target}",
|
||||
"report.thanks.take_action": "Vari veikt šīs darbības, lai kontrolētu Mastodon redzamo saturu:",
|
||||
"report.target": "Ziņošana par: {target}",
|
||||
"report.thanks.take_action": "Tālāk ir norādītas iespējas, kā kontrolēt Mastodon redzamo saturu:",
|
||||
"report.thanks.take_action_actionable": "Kamēr mēs to izskatām, tu vari veikt darbības pret @{name}:",
|
||||
"report.thanks.title": "Vai nevēlies to redzēt?",
|
||||
"report.thanks.title_actionable": "Paldies, ka ziņoji, mēs to izskatīsim.",
|
||||
"report.unfollow": "Pārtraukt sekot @{name}",
|
||||
"report.unfollow_explanation": "Tu seko šim kontam. Lai vairs neredzētu viņu ziņas savā mājas plūsmā, pārtrauc viņiem sekot.",
|
||||
"report_notification.attached_statuses": "{count, plural, one {Pievienots {count} ieraksts} other {Pievienoti {count} ieraksti}}",
|
||||
"report_notification.attached_statuses": "Pievienoti {count, plural,one {{count} sūtījums} other {{count} sūtījumi}}",
|
||||
"report_notification.categories.legal": "Tiesisks",
|
||||
"report_notification.categories.other": "Cita",
|
||||
"report_notification.categories.spam": "Spams",
|
||||
"report_notification.categories.violation": "Noteikumu pārkāpums",
|
||||
"report_notification.open": "Atvērt sūdzību",
|
||||
"report_notification.open": "Atvērt ziņojumu",
|
||||
"search.no_recent_searches": "Nav nesen veiktu meklējumu",
|
||||
"search.placeholder": "Meklēšana",
|
||||
"search.quick_action.account_search": "Profili atbilst {x}",
|
||||
|
@ -628,7 +628,7 @@
|
|||
"status.direct_indicator": "Pieminēts privāti",
|
||||
"status.edit": "Rediģēt",
|
||||
"status.edited": "Rediģēts {date}",
|
||||
"status.edited_x_times": "Rediģēts {count, plural, one {{count} reizi} other {{count} reizes}}",
|
||||
"status.edited_x_times": "Rediģēts {count, plural, one {{count} reize} other {{count} reizes}}",
|
||||
"status.embed": "Iestrādāt",
|
||||
"status.favourite": "Iecienīts",
|
||||
"status.filter": "Filtrē šo ziņu",
|
||||
|
@ -656,8 +656,8 @@
|
|||
"status.remove_bookmark": "Noņemt grāmatzīmi",
|
||||
"status.replied_to": "Atbildēja {name}",
|
||||
"status.reply": "Atbildēt",
|
||||
"status.replyAll": "Atbildēt uz pavedienu",
|
||||
"status.report": "Sūdzēties par @{name}",
|
||||
"status.replyAll": "Atbildēt uz tematu",
|
||||
"status.report": "Ziņot par @{name}",
|
||||
"status.sensitive_warning": "Sensitīvs saturs",
|
||||
"status.share": "Kopīgot",
|
||||
"status.show_filter_reason": "Tomēr rādīt",
|
||||
|
@ -668,7 +668,7 @@
|
|||
"status.show_original": "Rādīt oriģinālu",
|
||||
"status.title.with_attachments": "{user} publicējis {attachmentCount, plural, one {pielikumu} other {{attachmentCount} pielikumus}}",
|
||||
"status.translate": "Tulkot",
|
||||
"status.translated_from_with": "Tulkots no {lang}, izmantojot {provider}",
|
||||
"status.translated_from_with": "Tulkots no {lang} izmantojot {provider}",
|
||||
"status.uncached_media_warning": "Priekšskatījums nav pieejams",
|
||||
"status.unmute_conversation": "Noņemt sarunas apklusinājumu",
|
||||
"status.unpin": "Noņemt profila piespraudumu",
|
||||
|
@ -681,16 +681,16 @@
|
|||
"time_remaining.hours": "{number, plural, one {Atlikusi # stunda} other {Atlikušas # stundas}}",
|
||||
"time_remaining.minutes": "{number, plural, one {Atlikusi # minūte} other {Atlikušas # minūtes}}",
|
||||
"time_remaining.moments": "Atlikuši daži mirkļi",
|
||||
"time_remaining.seconds": "{number, plural, one {Atlikusi # sekunde} other {Atlikušas # sekundes}}",
|
||||
"time_remaining.seconds": "Atlikušas {number, plural, one {# sekunde} other {# sekundes}}",
|
||||
"timeline_hint.remote_resource_not_displayed": "{resource} no citiem serveriem nav parādīti.",
|
||||
"timeline_hint.resources.followers": "Sekotāji",
|
||||
"timeline_hint.resources.follows": "Sekojošie",
|
||||
"timeline_hint.resources.follows": "Seko",
|
||||
"timeline_hint.resources.statuses": "Vecāki ieraksti",
|
||||
"trends.counter_by_accounts": "{count, plural, one {{counter} persona} other {{counter} cilvēki}} par {days, plural, one {# dienu} other {{days} dienām}}",
|
||||
"trends.trending_now": "Aktuālās tendences",
|
||||
"ui.beforeunload": "Ja pametīsiet Mastodon, jūsu melnraksts tiks zaudēts.",
|
||||
"ui.beforeunload": "Ja pametīsit Mastodonu, jūsu melnraksts tiks zaudēts.",
|
||||
"units.short.billion": "{count}Mjd",
|
||||
"units.short.million": "{count}Mjn",
|
||||
"units.short.million": "{count}M",
|
||||
"units.short.thousand": "{count}Tk",
|
||||
"upload_area.title": "Velc un nomet, lai augšupielādētu",
|
||||
"upload_button.label": "Pievienot bildi, video vai audio datni",
|
||||
|
@ -707,7 +707,7 @@
|
|||
"upload_modal.apply": "Pielietot",
|
||||
"upload_modal.applying": "Pielieto…",
|
||||
"upload_modal.choose_image": "Izvēlēties attēlu",
|
||||
"upload_modal.description_placeholder": "Raibais runcis Rīgā ratu rumbā rūc",
|
||||
"upload_modal.description_placeholder": "Raibais runcis rīgā ratu rumbā rūc",
|
||||
"upload_modal.detect_text": "Noteikt tekstu no attēla",
|
||||
"upload_modal.edit_media": "Rediģēt multividi",
|
||||
"upload_modal.hint": "Noklikšķini vai velc apli priekšskatījumā, lai izvēlētos fokusa punktu, kas vienmēr būs redzams visos sīktēlos.",
|
||||
|
|
|
@ -593,6 +593,7 @@
|
|||
"search_results.all": "Semua",
|
||||
"search_results.hashtags": "Tanda pagar",
|
||||
"search_results.nothing_found": "Tidak dapat menemui apa-apa untuk istilah carian tersebut",
|
||||
"search_results.see_all": "Lihat semua",
|
||||
"search_results.statuses": "Hantaran",
|
||||
"search_results.title": "Mencari {q}",
|
||||
"server_banner.about_active_users": "Pengguna pelayan ini sepanjang 30 hari yang lalu (Pengguna Aktif Bulanan)",
|
||||
|
|
|
@ -312,6 +312,7 @@
|
|||
"home.hide_announcements": "ကြေညာချက်များကို ဖျောက်ပါ",
|
||||
"home.pending_critical_update.body": "သင့် Mastodon ဆာဗာ အမြန်ဆုံး အပ်ဒိတ်လုပ်ပါ။",
|
||||
"home.pending_critical_update.link": "အပ်ဒိတ်များကြည့်ရန်",
|
||||
"home.pending_critical_update.title": "အရေးကြီးသည့် လုံခြုံရေးအပ်ဒိတ် ရနိုင်ပါမည်။",
|
||||
"home.show_announcements": "ကြေညာချက်များကို ပြပါ",
|
||||
"interaction_modal.description.favourite": "Mastodon အကောင့်ဖြင့် ဤပို့စ်ကို သင် favorite ပြုလုပ်ကြောင်း စာရေးသူအား အသိပေးပြီး နောက်ပိုင်းတွင် သိမ်းဆည်းနိုင်သည်။",
|
||||
"interaction_modal.description.follow": "Mastodon အကောင့်ဖြင့် သင်၏ ပင်မစာမျက်နှာတွင် ၎င်းတို့၏ ပို့စ်များကို ရရှိရန်အတွက် {name} ကို စောင့်ကြည့်နိုင်ပါသည်။",
|
||||
|
@ -594,6 +595,7 @@
|
|||
"search_popout.options": "ရွေးချယ်ထားသည်များ ရှာဖွေရန်",
|
||||
"search_popout.quick_actions": "အမြန်လုပ်ဆောင်မှုများ",
|
||||
"search_popout.recent": "လတ်တလော ရှာဖွေမှုများ",
|
||||
"search_popout.specific_date": "သီးခြားရက်စွဲ",
|
||||
"search_popout.user": "အသုံးပြုသူ",
|
||||
"search_results.accounts": "စာမျက်နှာ",
|
||||
"search_results.all": "အားလုံး",
|
||||
|
|
|
@ -537,7 +537,7 @@
|
|||
"relative_time.today": "сегодня",
|
||||
"reply_indicator.cancel": "Отмена",
|
||||
"report.block": "Заблокировать",
|
||||
"report.block_explanation": "В перестаните видеть посты этого пользователя, а он(а) больше не сможет подписаться на вас и читать ваши посты. Он(а) сможет понять что вы заблокировали его/её.",
|
||||
"report.block_explanation": "Вы перестанете видеть посты этого пользователя, и он(а) больше не сможет подписаться на вас и читать ваши посты. Он(а) сможет понять, что вы заблокировали его/её.",
|
||||
"report.categories.legal": "Правовая информация",
|
||||
"report.categories.other": "Другое",
|
||||
"report.categories.spam": "Спам",
|
||||
|
|
|
@ -1,79 +1,88 @@
|
|||
{
|
||||
"about.blocks": "මැදිහත්කරණ සේවාදායක",
|
||||
"about.contact": "සබඳතාව:",
|
||||
"about.disclaimer": "මාස්ටඩන් යනු නිදහස් විවෘත මූලාශ්ර මෘදුකාංගයකි. එය මාස්ටඩන් gGmbH හි වෙළඳ නාමයකි.",
|
||||
"about.domain_blocks.suspended.title": "අත්හිටුවා ඇත",
|
||||
"about.rules": "සේවාදායකයේ නීති",
|
||||
"account.account_note_header": "සටහන",
|
||||
"account.add_or_remove_from_list": "ලැයිස්තු වලින් එකතු හෝ ඉවත් කරන්න",
|
||||
"account.badges.bot": "ස්වයං ක්රමලේඛය",
|
||||
"account.badges.bot": "ස්වයංක්රියයි",
|
||||
"account.badges.group": "සමූහය",
|
||||
"account.block": "@{name} අවහිර කරන්න",
|
||||
"account.block_domain": "{domain} වසම අවහිර කරන්න",
|
||||
"account.block_short": "අවහිර",
|
||||
"account.blocked": "අවහිර කර ඇත",
|
||||
"account.browse_more_on_origin_server": "මුල් පැතිකඩෙහි තවත් පිරික්සන්න",
|
||||
"account.cancel_follow_request": "Withdraw follow request",
|
||||
"account.disable_notifications": "@{name} පළ කරන විට මට දැනුම් නොදෙන්න",
|
||||
"account.domain_blocked": "වසම අවහිර කර ඇත",
|
||||
"account.edit_profile": "පැතිකඩ සංස්කරණය",
|
||||
"account.enable_notifications": "@{name} පළ කරන විට මට දැනුම් දෙන්න",
|
||||
"account.endorse": "පැතිකඩෙහි විශේෂාංගය",
|
||||
"account.featured_tags.last_status_never": "ලිපි නැත",
|
||||
"account.follow": "අනුගමනය",
|
||||
"account.followers": "අනුගාමිකයින්",
|
||||
"account.followers.empty": "කිසිවෙක් අනුගමනය කර නැත.",
|
||||
"account.followers_counter": "{count, plural, one {{counter} අනුගාමිකයෙක්} other {{counter} අනුගාමිකයින්}}",
|
||||
"account.following": "අනුගමනය",
|
||||
"account.following_counter": "{count, plural, one {අනුගාමිකයින් {counter}} other {අනුගාමිකයින් {counter}}}",
|
||||
"account.followers_counter": "{count, plural, one {අනුගාමිකයින් {counter}} other {අනුගාමිකයින් {counter}}}",
|
||||
"account.following": "අනුගමන",
|
||||
"account.following_counter": "{count, plural, one {අනුගමන {counter}} other {අනුගමන {counter}}}",
|
||||
"account.follows.empty": "තවමත් කිසිවෙක් අනුගමනය නොකරයි.",
|
||||
"account.follows_you": "ඔබව අනුගමනය කරයි",
|
||||
"account.hide_reblogs": "@{name}සිට බූස්ට් සඟවන්න",
|
||||
"account.go_to_profile": "පැතිකඩට යන්න",
|
||||
"account.joined_short": "එක් වූ දිනය",
|
||||
"account.link_verified_on": "මෙම සබැඳියේ අයිතිය {date} දී පරීක්ෂා කෙරිණි",
|
||||
"account.locked_info": "මෙම ගිණුමේ රහස්යතා තත්ත්වය අගුලු දමා ඇත. හිමිකරු ඔවුන් අනුගමනය කළ හැක්කේ කාටදැයි හස්තීයව සමාලෝචනය කරයි.",
|
||||
"account.media": "මාධ්යය",
|
||||
"account.mention": "@{name} සැඳහුම",
|
||||
"account.media": "මාධ්ය",
|
||||
"account.mention": "@{name} සඳහන් කරන්ක",
|
||||
"account.mute": "@{name} නිහඬ කරන්න",
|
||||
"account.mute_short": "නිහඬ",
|
||||
"account.muted": "නිහඬ කළා",
|
||||
"account.posts": "ලිපි",
|
||||
"account.posts_with_replies": "ටූට්ස් සහ පිළිතුරු",
|
||||
"account.posts_with_replies": "ලිපි සහ පිළිතුරු",
|
||||
"account.report": "@{name} වාර්තා කරන්න",
|
||||
"account.requested": "අනුමැතිය බලාපොරොත්තුවෙන්",
|
||||
"account.share": "@{name} ගේ පැතිකඩ බෙදාගන්න",
|
||||
"account.show_reblogs": "@{name}සිට බූස්ට් පෙන්වන්න",
|
||||
"account.statuses_counter": "{count, plural, one {{counter} ටූට්} other {{counter} ටූට්ස්}}",
|
||||
"account.statuses_counter": "{count, plural, one {ලිපි {counter}} other {ලිපි {counter}}}",
|
||||
"account.unblock": "@{name} අනවහිර කරන්න",
|
||||
"account.unblock_domain": "{domain} වසම අනවහිර කරන්න",
|
||||
"account.unblock_short": "අනවහිර",
|
||||
"account.unendorse": "පැතිකඩෙහි විශේෂාංග නොකරන්න",
|
||||
"account.unfollow": "අනුගමනය නොකරන්න",
|
||||
"account.unmute": "@{name}නිහඬ නොකරන්න",
|
||||
"account.unmute_short": "නොනිහඬ",
|
||||
"account_note.placeholder": "සටහන යෙදීමට ඔබන්න",
|
||||
"admin.dashboard.daily_retention": "ලියාපදිංචි වීමෙන් පසු දිනකට පරිශීලක රඳවා ගැනීමේ අනුපාතය",
|
||||
"admin.dashboard.monthly_retention": "ලියාපදිංචි වීමෙන් පසු මාසය අනුව පරිශීලක රඳවා ගැනීමේ අනුපාතය",
|
||||
"admin.dashboard.retention.average": "සාමාන්යය",
|
||||
"admin.dashboard.retention.cohort": "ලියාපදිංචි වීමේ මාසය",
|
||||
"admin.dashboard.retention.cohort": "ලියාපදිංචි මාසය",
|
||||
"admin.dashboard.retention.cohort_size": "නව පරිශ්රීලකයින්",
|
||||
"alert.rate_limited.message": "{retry_time, time, medium} කට පසුව උත්සාහ කරන්න.",
|
||||
"alert.rate_limited.title": "මිල සීමා සහිතයි",
|
||||
"alert.unexpected.message": "අනපේක්ෂිත දෝෂයක් ඇතිවුනා.",
|
||||
"alert.rate_limited.title": "අනුපාතනය වී ඇත",
|
||||
"alert.unexpected.message": "අනපේක්ෂිත දෝෂයක් සිදු විය.",
|
||||
"alert.unexpected.title": "අපොයි!",
|
||||
"announcement.announcement": "නිවේදනය",
|
||||
"attachments_list.unprocessed": "(සැකසුම් නොකළ)",
|
||||
"audio.hide": "හඬපටය සඟවන්න",
|
||||
"autosuggest_hashtag.per_week": "සතියකට {count}",
|
||||
"boost_modal.combo": "ඊළඟ වතාවේ මෙය මඟ හැරීමට ඔබට {combo} එබිය හැක",
|
||||
"boost_modal.combo": "ඊළඟ වතාවේ මෙය මඟ හැරීමට {combo} එබීමට හැකිය",
|
||||
"bundle_column_error.copy_stacktrace": "දෝෂ වාර්තාවේ පිටපතක්",
|
||||
"bundle_column_error.error.title": "අපොයි!",
|
||||
"bundle_column_error.network.title": "ජාලයේ දෝෂයකි",
|
||||
"bundle_column_error.retry": "නැවත උත්සාහ කරන්න",
|
||||
"bundle_column_error.return": "ආපසු මුලට යන්න",
|
||||
"bundle_column_error.routing.title": "404",
|
||||
"bundle_modal_error.close": "වසන්න",
|
||||
"bundle_modal_error.message": "මෙම සංරචකය පූරණය කිරීමේදී යම් දෙයක් වැරදී ඇත.",
|
||||
"bundle_modal_error.message": "මෙම සංරචකය පූරණයේ දී යම් දෙයක් වැරදී ඇත.",
|
||||
"bundle_modal_error.retry": "නැවත උත්සාහ කරන්න",
|
||||
"closed_registrations_modal.find_another_server": "වෙනත් සේවාදායක",
|
||||
"closed_registrations_modal.title": "මාස්ටඩන් හි ලියාපදිංචි වන්න",
|
||||
"column.about": "පිලිබඳව",
|
||||
"column.blocks": "අවහිර කළ අය",
|
||||
"column.bookmarks": "පොත් යොමු",
|
||||
"column.community": "දේශීය කාලරේඛාව",
|
||||
"column.bookmarks": "පොත්යොමු",
|
||||
"column.community": "ස්ථානීය කාලරේඛාව",
|
||||
"column.direct": "පෞද්ගලික සැඳහුම්",
|
||||
"column.directory": "පැතිකඩ පිරික්සන්න",
|
||||
"column.domain_blocks": "අවහිර කළ වසම්",
|
||||
"column.favourites": "ප්රියතමයන්",
|
||||
"column.firehose": "සජීව සංග්රහ",
|
||||
"column.follow_requests": "අනුගමන ඉල්ලීම්",
|
||||
"column.home": "මුල් පිටුව",
|
||||
"column.lists": "ලේඛන",
|
||||
"column.lists": "ලැයිස්තු",
|
||||
"column.mutes": "නිහඬ කළ අය",
|
||||
"column.notifications": "දැනුම්දීම්",
|
||||
"column.pins": "ඇමිණූ ලිපි",
|
||||
"column.public": "ෆෙඩරේටඩ් කාලරේඛාව",
|
||||
"column.public": "ඒකාබද්ධ කාලරේඛාව",
|
||||
"column_back_button.label": "ආපසු",
|
||||
"column_header.hide_settings": "සැකසුම් සඟවන්න",
|
||||
"column_header.moveLeft_settings": "තීරුව වමට ගෙනයන්න",
|
||||
|
@ -87,70 +96,62 @@
|
|||
"community.column_settings.remote_only": "දුරස්ථව පමණයි",
|
||||
"compose.language.change": "භාෂාව සංශෝධනය",
|
||||
"compose.language.search": "භාෂා සොයන්න...",
|
||||
"compose.published.body": "ලිපිය පළ විය.",
|
||||
"compose.published.open": "අරින්න",
|
||||
"compose.saved.body": "ලිපිය සුරැකිණි.",
|
||||
"compose_form.direct_message_warning_learn_more": "තව දැනගන්න",
|
||||
"compose_form.encryption_warning": "Mastodon හි පළ කිරීම් අන්තයේ සිට අවසානය දක්වා සංකේතනය කර නොමැත. Mastodon හරහා කිසිදු සංවේදී තොරතුරක් බෙදා නොගන්න.",
|
||||
"compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
|
||||
"compose_form.lock_disclaimer": "ඔබගේ ගිණුම {locked}නොවේ. ඔබගේ අනුගාමිකයින්ට පමණක් පළ කිරීම් බැලීමට ඕනෑම කෙනෙකුට ඔබව අනුගමනය කළ හැක.",
|
||||
"compose_form.encryption_warning": "මාස්ටඩන් වෙත පළ කරන දෑ අන්ත සංකේතනයෙන් ආරක්ෂා නොවේ. මාස්ටඩන් හරහා කිසිදු සංවේදී තොරතුරක් බෙදා නොගන්න.",
|
||||
"compose_form.lock_disclaimer.lock": "අගුළු දමා ඇත",
|
||||
"compose_form.placeholder": "ඔබගේ සිතුවිලි මොනවාද?",
|
||||
"compose_form.poll.add_option": "තේරීමක් යොදන්න",
|
||||
"compose_form.poll.duration": "මත විමසීමේ කාලය",
|
||||
"compose_form.poll.option_placeholder": "තේරීම {number}",
|
||||
"compose_form.poll.remove_option": "මෙම ඉවත් කරන්න",
|
||||
"compose_form.poll.switch_to_multiple": "තේරීම් කිහිපයක් ඉඩ දීම සඳහා මත විමසුම වෙනස් කරන්න",
|
||||
"compose_form.poll.switch_to_single": "තනි තේරීමකට ඉඩ දීම සඳහා මත විමසුම වෙනස් කරන්න",
|
||||
"compose_form.poll.switch_to_multiple": "තේරීම් කිහිපයකට මත විමසුම වෙනස් කරන්න",
|
||||
"compose_form.poll.switch_to_single": "තනි තේරීමකට මත විමසුම වෙනස් කරන්න",
|
||||
"compose_form.publish": "ප්රකාශනය",
|
||||
"compose_form.publish_form": "නව ලිපිය",
|
||||
"compose_form.publish_loud": "{publish}!",
|
||||
"compose_form.save_changes": "වෙනස්කම් සුරකින්න",
|
||||
"compose_form.sensitive.hide": "{count, plural, one {මාධ්ය සංවේදී ලෙස සලකුණු කරන්න} other {මාධ්ය සංවේදී ලෙස සලකුණු කරන්න}}",
|
||||
"compose_form.sensitive.marked": "{count, plural, one {මාධ්ය සංවේදී ලෙස සලකුණු කර ඇත} other {මාධ්ය සංවේදී ලෙස සලකුණු කර ඇත}}",
|
||||
"compose_form.sensitive.unmarked": "{count, plural, one {මාධ්ය සංවේදී ලෙස සලකුණු කර නැත} other {මාධ්ය සංවේදී ලෙස සලකුණු කර නැත}}",
|
||||
"compose_form.spoiler.marked": "අනතුරු ඇඟවීම පිටුපස පෙළ සඟවා ඇත",
|
||||
"compose_form.spoiler.unmarked": "ප්රයෝජනය සඟවා නැත",
|
||||
"compose_form.spoiler.marked": "අන්තර්ගත අවවාදය ඉවත් කරන්න",
|
||||
"compose_form.spoiler.unmarked": "අන්තර්ගත අවවාදයක් එක් කරන්න",
|
||||
"compose_form.spoiler_placeholder": "අවවාදය මෙහි ලියන්න",
|
||||
"confirmation_modal.cancel": "අවලංගු",
|
||||
"confirmations.block.block_and_report": "අවහිර කර වාර්තා කරන්න",
|
||||
"confirmations.block.confirm": "අවහිර",
|
||||
"confirmations.block.message": "ඔබට {name} අවහිර කිරීමට වුවමනා ද?",
|
||||
"confirmations.delete.confirm": "මකන්න",
|
||||
"confirmations.delete.message": "ඔබට මෙම තත්ත්වය මැකීමට අවශ්ය බව විශ්වාසද?",
|
||||
"confirmations.delete.message": "ඔබට මෙම ලිපිය මැකීමට වුවමනා ද?",
|
||||
"confirmations.delete_list.confirm": "මකන්න",
|
||||
"confirmations.delete_list.message": "ඔබට මෙම ලැයිස්තුව ස්ථිරවම මැකීමට අවශ්ය බව විශ්වාසද?",
|
||||
"confirmations.delete_list.message": "ඔබට මෙම ලැයිස්තුව සදහටම මැකීමට වුවමනා ද?",
|
||||
"confirmations.discard_edit_media.confirm": "ඉවත ලන්න",
|
||||
"confirmations.discard_edit_media.message": "ඔබට මාධ්ය විස්තරයට හෝ පෙරදසුනට නොසුරකින ලද වෙනස්කම් තිබේ, කෙසේ වෙතත් ඒවා ඉවත දමන්නද?",
|
||||
"confirmations.domain_block.confirm": "සම්පූර්ණ වසම අවහිර කරන්න",
|
||||
"confirmations.domain_block.message": "ඔබට සම්පූර්ණ {domain}අවහිර කිරීමට අවශ්ය බව ඔබට සැබවින්ම විශ්වාසද? බොහෝ අවස්ථාවලදී ඉලක්කගත බ්ලොක් හෝ නිශ්ශබ්ද කිරීම් කිහිපයක් ප්රමාණවත් වන අතර වඩාත් යෝග්ය වේ. ඔබ කිසිදු පොදු කාලරාමුවක හෝ ඔබගේ දැනුම්දීම් වල එම වසමේ අන්තර්ගතය නොදකිනු ඇත. එම වසමෙන් ඔබගේ අනුගාමිකයින් ඉවත් කරනු ලැබේ.",
|
||||
"confirmations.edit.confirm": "සංස්කරණය",
|
||||
"confirmations.logout.confirm": "නික්මෙන්න",
|
||||
"confirmations.logout.message": "ඔබට නික්මෙන්න අවශ්ය බව විශ්වාසද?",
|
||||
"confirmations.mute.confirm": "නිශ්ශබ්ද",
|
||||
"confirmations.mute.explanation": "මෙය ඔවුන්ගෙන් පළ කිරීම් සහ ඒවා සඳහන් කරන පළ කිරීම් සඟවයි, නමුත් එය ඔවුන්ට ඔබේ පළ කිරීම් බැලීමට සහ ඔබව අනුගමනය කිරීමට තවමත් ඉඩ ලබා දේ.",
|
||||
"confirmations.mute.message": "ඔබට {name} නිශ්ශබ්ද කිරීමට අවශ්ය බව විශ්වාසද?",
|
||||
"confirmations.redraft.confirm": "මකන්න සහ නැවත කෙටුම්පත් කරන්න",
|
||||
"confirmations.mute.message": "{name} නිහඬ කිරීමට වුවමනා ද?",
|
||||
"confirmations.reply.confirm": "පිළිතුර",
|
||||
"confirmations.reply.message": "දැන් පිළිතුරු දීම ඔබ දැනට රචනා කරන පණිවිඩය උඩින් ලියයි. ඔබට ඉදිරියට යාමට අවශ්ය බව විශ්වාසද?",
|
||||
"confirmations.unfollow.confirm": "අනුගමනය නොකරන්න",
|
||||
"confirmations.unfollow.message": "ඔබට {name}අනුගමනය නොකිරීමට අවශ්ය බව විශ්වාසද?",
|
||||
"conversation.delete": "සංවාදය මකන්න",
|
||||
"conversation.mark_as_read": "කියවූ බව යොදන්න",
|
||||
"conversation.open": "සංවාදය බලන්න",
|
||||
"conversation.with": "{names} සමඟ",
|
||||
"copypaste.copied": "පිටපත් විය",
|
||||
"directory.federated": "දන්නා fediverse වලින්",
|
||||
"copypaste.copy_to_clipboard": "පසුරුපුවරුවට පිටපතක්",
|
||||
"directory.federated": "දන්නා ෆෙඩිවර්ස් වෙතින්",
|
||||
"directory.local": "{domain} වෙතින් පමණි",
|
||||
"directory.new_arrivals": "නව පැමිණීම්",
|
||||
"directory.recently_active": "මෑත දී සක්රියයි",
|
||||
"dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
|
||||
"dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
|
||||
"embed.instructions": "පහත කේතය පිටපත් කිරීමෙන් මෙම තත්ත්වය ඔබේ වෙබ් අඩවියට ඇතුළත් කරන්න.",
|
||||
"embed.preview": "එය පෙනෙන්නේ කෙසේද යන්න මෙන්න:",
|
||||
"disabled_account_banner.account_settings": "ගිණුමේ සැකසුම්",
|
||||
"embed.instructions": "පහත කේතය පිටපත් කිරීමෙන් මෙම ලිපිය ඔබගේ අඩවියට කාවද්දන්න.",
|
||||
"embed.preview": "මෙන්න එය පෙනෙන අන්දම:",
|
||||
"emoji_button.activity": "ක්රියාකාරකම",
|
||||
"emoji_button.clear": "මකන්න",
|
||||
"emoji_button.custom": "අභිරුචි",
|
||||
"emoji_button.flags": "කොඩි",
|
||||
"emoji_button.food": "ආහාර සහ පාන",
|
||||
"emoji_button.label": "ඉමොජි යොදන්න",
|
||||
"emoji_button.nature": "ස්වභාවික",
|
||||
"emoji_button.nature": "සොබාදහම",
|
||||
"emoji_button.not_found": "ගැළපෙන ඉමෝජි හමු නොවිණි",
|
||||
"emoji_button.objects": "වස්තූන්",
|
||||
"emoji_button.people": "මිනිසුන්",
|
||||
|
@ -163,155 +164,146 @@
|
|||
"empty_column.account_timeline": "මෙහි ලිපි නැත!",
|
||||
"empty_column.account_unavailable": "පැතිකඩ නොතිබේ",
|
||||
"empty_column.blocks": "කිසිදු පරිශීලකයෙකු අවහිර කර නැත.",
|
||||
"empty_column.bookmarked_statuses": "ඔබට තවමත් පිටු සලකුණු කළ මෙවලම් කිසිවක් නොමැත. ඔබ එකක් පිටු සලකුණු කළ විට, එය මෙහි පෙන්වනු ඇත.",
|
||||
"empty_column.community": "දේශීය කාලරේඛාව හිස් ය. පන්දුව පෙරළීමට ප්රසිද්ධියේ යමක් ලියන්න!",
|
||||
"empty_column.bookmarked_statuses": "ඔබ සතුව පොත්යොමු තබන ලද ලිපි කිසිවක් නැත. ඔබ පොත්යොමුවක් තබන විට, එය මෙහි දිස්වනු ඇත.",
|
||||
"empty_column.domain_blocks": "අවහිර කරන ලද වසම් නැත.",
|
||||
"empty_column.explore_statuses": "දැන් කිසිවක් නැඹුරු නොවේ. පසුව නැවත පරීක්ෂා කරන්න!",
|
||||
"empty_column.follow_requests": "ඔබට තවමත් අනුගමනය කිරීමේ ඉල්ලීම් කිසිවක් නොමැත. ඔබට එකක් ලැබුණු විට, එය මෙහි පෙන්වනු ඇත.",
|
||||
"empty_column.hashtag": "මෙම හැෂ් ටැග් එකේ තවම කිසිවක් නොමැත.",
|
||||
"empty_column.home": "ඔබගේ නිවසේ කාලරේඛාව හිස්ය! එය පිරවීම සඳහා තවත් පුද්ගලයින් අනුගමනය කරන්න. {suggestions}",
|
||||
"empty_column.list": "මෙම ලැයිස්තුවේ තවමත් කිසිවක් නොමැත. මෙම ලැයිස්තුවේ සාමාජිකයන් නව තත්ව පළ කරන විට, ඔවුන් මෙහි දිස් වනු ඇත.",
|
||||
"empty_column.follow_requests": "ඔබට තවමත් අනුගමන ඉල්ලීම් ලැබී නැත. ඉල්ලීමක් ලැබුණු විට, එය මෙහි පෙන්වනු ඇත.",
|
||||
"empty_column.home": "මුල් පිටුව හිස් ය! මෙය පිරවීමට බොහෝ පුද්ගලයින් අනුගමනය කරන්න.",
|
||||
"empty_column.lists": "ඔබට තවමත් ලැයිස්තු කිසිවක් නැත. ඔබ එකක් සාදන විට, එය මෙහි පෙන්වනු ඇත.",
|
||||
"empty_column.mutes": "ඔබ තවමත් කිසිදු පරිශීලකයෙකු නිහඬ කර නැත.",
|
||||
"empty_column.notifications": "ඔබට තවම දැනුම්දීම් කිසිවක් නැත. වෙනත් පුද්ගලයින් ඔබ සමඟ අන්තර් ක්රියා කරන විට, ඔබ එය මෙහි දකිනු ඇත.",
|
||||
"empty_column.public": "මෙහි කිසිවක් නැත! යමක් ප්රසිද්ධියේ ලියන්න, නැතහොත් එය පිරවීම සඳහා වෙනත් සේවාදායකයන්ගෙන් පරිශීලකයන් හස්තීයව අනුගමනය කරන්න",
|
||||
"empty_column.notifications": "ඔබට දැනුම්දීම් ලැබී නැත. අන් අය සහ ඔබ අතර අන්යෝන්ය බලපවත්වන දෑ මෙහි දිස්වනු ඇත.",
|
||||
"error.unexpected_crash.explanation": "අපගේ කේතයේ දෝෂයක් හෝ බ්රවුසර ගැළපුම් ගැටලුවක් හේතුවෙන්, මෙම පිටුව නිවැරදිව ප්රදර්ශනය කළ නොහැක.",
|
||||
"error.unexpected_crash.explanation_addons": "මෙම පිටුව නිවැරදිව ප්රදර්ශනය කළ නොහැක. මෙම දෝෂය බ්රවුසර ඇඩෝනයක් හෝ ස්වයංක්රීය පරිවර්තන මෙවලම් නිසා ඇති විය හැක.",
|
||||
"error.unexpected_crash.next_steps": "පිටුව නැවුම් කිරීමට උත්සාහ කරන්න. එය උදව් නොකළහොත්, ඔබට තවමත් වෙනත් බ්රවුසරයක් හෝ ස්වදේශීය යෙදුමක් හරහා Mastodon භාවිත කිරීමට හැකි වේ.",
|
||||
"error.unexpected_crash.next_steps_addons": "ඒවා අක්රිය කර පිටුව නැවුම් කිරීමට උත්සාහ කරන්න. එය උදව් නොකළහොත්, ඔබට තවමත් වෙනත් බ්රවුසරයක් හෝ ස්වදේශීය යෙදුමක් හරහා Mastodon භාවිත කිරීමට හැකි වේ.",
|
||||
"errors.unexpected_crash.copy_stacktrace": "ස්ටැක්ට්රේස් පසුරු පුවරුවට පිටපත් කරන්න",
|
||||
"error.unexpected_crash.next_steps": "පිටුව නැවුම් කර බලන්න. එයින් ඵලක් නොවේ නම්, වෙනත් අතිරික්සුවක් හෝ නිසග යෙදුමක් හරහා මාස්ටඩන් භාවිතා කරන්න.",
|
||||
"error.unexpected_crash.next_steps_addons": "ඒවා අබල කර පිටුව නැවුම් කරන්න. එයින් ඵලක් නොවේ නම්, වෙනත් අතිරික්සුවක් හෝ නිසග යෙදුමක් හරහා මාස්ටඩන් භාවිතා කරන්න.",
|
||||
"errors.unexpected_crash.report_issue": "ගැටළුව වාර්තාව",
|
||||
"explore.search_results": "සෙවුම් ප්රතිඵල",
|
||||
"explore.title": "ගවේශණය",
|
||||
"explore.suggested_follows": "පුද්ගලයින්",
|
||||
"explore.title": "ගවේශනය",
|
||||
"explore.trending_links": "පුවත්",
|
||||
"explore.trending_statuses": "ලිපි",
|
||||
"filter_modal.added.expired_title": "පෙරහන ඉකුත්ය!",
|
||||
"filter_modal.added.review_and_configure_title": "පෙරහන් සැකසුම්",
|
||||
"filter_modal.added.settings_link": "සැකසුම් පිටුව",
|
||||
"filter_modal.added.title": "පෙරහන එක් කළා!",
|
||||
"filter_modal.select_filter.expired": "ඉකුත්ය",
|
||||
"filter_modal.select_filter.prompt_new": "නව ප්රවර්ගය: {name}",
|
||||
"filter_modal.select_filter.search": "සොයන්න හෝ සාදන්න",
|
||||
"follow_request.authorize": "අවසරලත්",
|
||||
"filter_modal.select_filter.title": "මෙම ලිපිය පෙරන්න",
|
||||
"filter_modal.title.status": "ලිපියක් පෙරන්න",
|
||||
"firehose.local": "මෙම සේවාදායකය",
|
||||
"firehose.remote": "වෙනත් සේවාදායක",
|
||||
"follow_request.reject": "ප්රතික්ෂේප",
|
||||
"follow_requests.unlocked_explanation": "ඔබගේ ගිණුම අගුලු දමා නොතිබුණද, {domain} කාර්ය මණ්ඩලය සිතුවේ ඔබට මෙම ගිණුම් වලින් ලැබෙන ඉල්ලීම් හස්තීයව සමාලෝචනය කිරීමට අවශ්ය විය හැකි බවයි.",
|
||||
"footer.about": "පිළිබඳව",
|
||||
"footer.directory": "පැතිකඩ නාමාවලිය",
|
||||
"footer.get_app": "යෙදුම ගන්න",
|
||||
"footer.invite": "ආරාධනා කරන්න",
|
||||
"footer.keyboard_shortcuts": "යතුරුපුවරුවේ කෙටිමං",
|
||||
"footer.privacy_policy": "රහස්යතා ප්රතිපත්තිය",
|
||||
"footer.source_code": "මූලාශ්ර කේතය බලන්න",
|
||||
"footer.status": "තත්වය",
|
||||
"generic.saved": "සුරැකිණි",
|
||||
"getting_started.heading": "පටන් ගන්න",
|
||||
"hashtag.column_header.tag_mode.all": "සහ {additional}",
|
||||
"hashtag.column_header.tag_mode.any": "හෝ {additional}",
|
||||
"hashtag.column_header.tag_mode.none": "{additional}නොමැතිව",
|
||||
"hashtag.column_settings.select.no_options_message": "යෝජනා හමු නොවිණි",
|
||||
"hashtag.column_settings.select.placeholder": "හැෂ් ටැග්…ඇතුලත් කරන්න",
|
||||
"hashtag.column_settings.tag_mode.all": "මේ සියල්ලම",
|
||||
"hashtag.column_settings.tag_mode.any": "ඇතුළත් එකක්",
|
||||
"hashtag.column_settings.tag_mode.none": "මේ කිසිවක් නැත",
|
||||
"hashtag.column_settings.tag_toggle": "මෙම තීරුවේ අමතර ටැග් ඇතුළත් කරන්න",
|
||||
"home.actions.go_to_explore": "නැගී එන දෑ බලන්න",
|
||||
"home.actions.go_to_suggestions": "පුද්ගලයින් සොයන්න",
|
||||
"home.column_settings.basic": "මූලික",
|
||||
"home.column_settings.show_reblogs": "බූස්ට් පෙන්වන්න",
|
||||
"home.column_settings.show_replies": "පිළිතුරු පෙන්වන්න",
|
||||
"home.explore_prompt.title": "මෙය ඔබගේ මාස්ටඩන් මුල් පිටුවයි.",
|
||||
"home.hide_announcements": "නිවේදන සඟවන්න",
|
||||
"home.pending_critical_update.link": "යාවත්කාල බලන්න",
|
||||
"home.show_announcements": "නිවේදන පෙන්වන්න",
|
||||
"intervals.full.days": "{number, plural, one {# දින} other {# දින}}",
|
||||
"intervals.full.hours": "{number, plural, one {# පැය} other {# පැය}}",
|
||||
"intervals.full.minutes": "{number, plural, one {විනාඩි #} other {# මිනිත්තු}}",
|
||||
"interaction_modal.on_this_server": "මෙම සේවාදායකයෙහි",
|
||||
"intervals.full.days": "{number, plural, one {දවස් #} other {දවස් #}}",
|
||||
"intervals.full.hours": "{number, plural, one {පැය #} other {පැය #}}",
|
||||
"intervals.full.minutes": "{number, plural, one {විනාඩි #} other {විනාඩි #}}",
|
||||
"keyboard_shortcuts.back": "ආපසු යාත්රණය",
|
||||
"keyboard_shortcuts.blocked": "අවහිර කළ පරිශීලක ලැයිස්තුව විවෘත කිරීමට",
|
||||
"keyboard_shortcuts.boost": "වැඩි කිරීමට",
|
||||
"keyboard_shortcuts.column": "එක් තීරුවක තත්ත්වය නාභිගත කිරීමට",
|
||||
"keyboard_shortcuts.compose": "රචනා පාඨ ප්රදේශය නාභිගත කිරීමට",
|
||||
"keyboard_shortcuts.description": "සවිස්තරය",
|
||||
"keyboard_shortcuts.direct": "to open direct messages column",
|
||||
"keyboard_shortcuts.down": "ලැයිස්තුවේ පහළට ගමන් කිරීමට",
|
||||
"keyboard_shortcuts.down": "ලැයිස්තුවේ පහළට ගෙනයන්න",
|
||||
"keyboard_shortcuts.enter": "ලිපිය අරින්න",
|
||||
"keyboard_shortcuts.favourites": "ප්රියතමයන් ලැයිස්තුව අරින්න",
|
||||
"keyboard_shortcuts.federated": "ෆෙඩරේටඩ් කාලරාමුව විවෘත කිරීමට",
|
||||
"keyboard_shortcuts.heading": "යතුරුපුවරු කෙටිමං",
|
||||
"keyboard_shortcuts.home": "නිවසේ කාලරේඛාව විවෘත කිරීමට",
|
||||
"keyboard_shortcuts.hotkey": "උණු යතුර",
|
||||
"keyboard_shortcuts.legend": "මෙම පුරාවෘත්තය ප්රදර්ශනය කිරීමට",
|
||||
"keyboard_shortcuts.local": "දේශීය කාලරේඛාව විවෘත කිරීමට",
|
||||
"keyboard_shortcuts.mention": "කතුවරයා සඳහන් කිරීමට",
|
||||
"keyboard_shortcuts.muted": "නිශ්ශබ්ද පරිශීලක ලැයිස්තුව විවෘත කිරීමට",
|
||||
"keyboard_shortcuts.muted": "නිහඬ කළ අය පෙන්වන්න",
|
||||
"keyboard_shortcuts.my_profile": "ඔබගේ පැතිකඩ අරින්න",
|
||||
"keyboard_shortcuts.notifications": "දැනුම්දීම් තීරුව විවෘත කිරීමට",
|
||||
"keyboard_shortcuts.open_media": "මාධ්ය අරින්න",
|
||||
"keyboard_shortcuts.pinned": "ඇමිණූ ලිපි ලේඛනය අරින්න",
|
||||
"keyboard_shortcuts.pinned": "ඇමිණූ ලිපි ලැයිස්තුව අරින්න",
|
||||
"keyboard_shortcuts.profile": "කතෘගේ පැතිකඩ අරින්න",
|
||||
"keyboard_shortcuts.reply": "පිළිතුරු දීමට",
|
||||
"keyboard_shortcuts.requests": "පහත ඉල්ලීම් ලැයිස්තුව විවෘත කිරීමට",
|
||||
"keyboard_shortcuts.search": "සෙවුම් අවධානය යොමු කිරීමට",
|
||||
"keyboard_shortcuts.spoilers": "CW ක්ෂේත්රය පෙන්වීමට/සැඟවීමට",
|
||||
"keyboard_shortcuts.spoilers": "CW ක්ෂේත්රය පෙන්වන්න/සඟවන්න",
|
||||
"keyboard_shortcuts.start": "\"පටන් ගන්න\" තීරුව අරින්න",
|
||||
"keyboard_shortcuts.toggle_hidden": "CW පිටුපස පෙළ පෙන්වීමට/සැඟවීමට",
|
||||
"keyboard_shortcuts.toggle_sensitivity": "මාධ්ය පෙන්වන්න/සඟවන්න",
|
||||
"keyboard_shortcuts.toot": "නව ලිපියක් අරඹන්න",
|
||||
"keyboard_shortcuts.unfocus": "අවධානය යොමු නොකිරීමට textarea/search රචනා කරන්න",
|
||||
"keyboard_shortcuts.up": "ලැයිස්තුවේ ඉහළට යාමට",
|
||||
"keyboard_shortcuts.up": "ලැයිස්තුවේ ඉහළට ගෙනයන්න",
|
||||
"lightbox.close": "වසන්න",
|
||||
"lightbox.compress": "රූප බැලීමේ කොටුව සම්පීඩනය කරන්න",
|
||||
"lightbox.expand": "රූප දර්ශන පෙට්ටිය දිග හරින්න",
|
||||
"lightbox.next": "ඊළඟ",
|
||||
"lightbox.previous": "පෙර",
|
||||
"limited_account_hint.action": "කෙසේ හෝ පැතිකඩ පෙන්වන්න",
|
||||
"lists.account.add": "ලේඛනයට දමන්න",
|
||||
"lists.account.remove": "ලේඛනයෙන් ඉවතලන්න",
|
||||
"lists.delete": "ලේඛනය මකන්න",
|
||||
"lists.edit": "ලේඛනය සංස්කරණය",
|
||||
"lists.account.add": "ලැයිස්තුවට දමන්න",
|
||||
"lists.account.remove": "ලැයිස්තුවෙන් ඉවතලන්න",
|
||||
"lists.delete": "ලැයිස්තුව මකන්න",
|
||||
"lists.edit": "ලැයිස්තුව සංස්කරණය",
|
||||
"lists.edit.submit": "සිරැසිය සංශෝධනය",
|
||||
"lists.new.create": "ලැයිස්තුව එකතු කරන්න",
|
||||
"lists.new.title_placeholder": "නව ලැයිස්තු මාතෘකාව",
|
||||
"lists.replies_policy.followed": "අනුගමනය කරන ඕනෑම පරිශීලකයෙක්",
|
||||
"lists.replies_policy.list": "ලැයිස්තුවේ සාමාජිකයන්",
|
||||
"lists.new.title_placeholder": "නව ලැයිස්තුවේ සිරැසිය",
|
||||
"lists.replies_policy.list": "ලැයිස්තුවේ සාමාජිකයින්",
|
||||
"lists.replies_policy.none": "කිසිවෙක් නැත",
|
||||
"lists.replies_policy.title": "පිළිතුරු පෙන්වන්න:",
|
||||
"lists.search": "ඔබ අනුගමනය කරන පුද්ගලයින් අතර සොයන්න",
|
||||
"lists.subheading": "ඔබගේ ලේඛන",
|
||||
"load_pending": "{count, plural, one {# නව අයිතමයක්} other {නව අයිතම #ක්}}",
|
||||
"lists.subheading": "ඔබගේ ලැයිස්තු",
|
||||
"loading_indicator.label": "පූරණය වෙමින්...",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {රූපය සඟවන්න} other {පින්තූර සඟවන්න}}",
|
||||
"mute_modal.duration": "පරාසය",
|
||||
"mute_modal.hide_notifications": "මෙම පරිශීලකයාගෙන් දැනුම්දීම් සඟවන්නද?",
|
||||
"mute_modal.indefinite": "අවිනිශ්චිත",
|
||||
"mute_modal.hide_notifications": "මෙම පුද්ගලයාගේ දැනුම්දීම් සඟවන්නද?",
|
||||
"navigation_bar.about": "පිළිබඳව",
|
||||
"navigation_bar.blocks": "අවහිර කළ අය",
|
||||
"navigation_bar.bookmarks": "පොත්යොමු",
|
||||
"navigation_bar.community_timeline": "දේශීය කාලරේඛාව",
|
||||
"navigation_bar.compose": "නව ටූට් සාදන්න",
|
||||
"navigation_bar.discover": "සොයා ගන්න",
|
||||
"navigation_bar.community_timeline": "ස්ථානීය කාලරේඛාව",
|
||||
"navigation_bar.compose": "නව ලිපියක් ලියන්න",
|
||||
"navigation_bar.direct": "පෞද්ගලික සැඳහුම්",
|
||||
"navigation_bar.domain_blocks": "අවහිර කළ වසම්",
|
||||
"navigation_bar.edit_profile": "පැතිකඩ සංස්කරණය",
|
||||
"navigation_bar.explore": "ගවේෂණය කරන්න",
|
||||
"navigation_bar.explore": "ගවේශනය",
|
||||
"navigation_bar.favourites": "ප්රියතමයන්",
|
||||
"navigation_bar.filters": "නිහඬ කළ වචන",
|
||||
"navigation_bar.follow_requests": "අනුගමන ඉල්ලීම්",
|
||||
"navigation_bar.follows_and_followers": "අනුගමනය හා අනුගාමිකයින්",
|
||||
"navigation_bar.lists": "ලේඛන",
|
||||
"navigation_bar.follows_and_followers": "අනුගමන හා අනුගාමික",
|
||||
"navigation_bar.lists": "ලැයිස්තු",
|
||||
"navigation_bar.logout": "නික්මෙන්න",
|
||||
"navigation_bar.mutes": "නිහඬ කළ අය",
|
||||
"navigation_bar.personal": "පුද්ගලික",
|
||||
"navigation_bar.pins": "ඇමිණූ ලිපි",
|
||||
"navigation_bar.preferences": "අභිප්රේත",
|
||||
"navigation_bar.public_timeline": "ෆෙඩරේටඩ් කාලරේඛාව",
|
||||
"navigation_bar.public_timeline": "ඒකාබද්ධ කාලරේඛාව",
|
||||
"navigation_bar.search": "සොයන්න",
|
||||
"navigation_bar.security": "ආරක්ෂාව",
|
||||
"not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.",
|
||||
"notification.admin.report": "{name} වාර්තා {target}",
|
||||
"notification.admin.sign_up": "{name} අත්සන් කර ඇත",
|
||||
"notification.follow": "{name} ඔබව අනුගමනය කළා",
|
||||
"notification.follow_request": "{name} ඔබව අනුගමනය කිරීමට ඉල්ලා ඇත",
|
||||
"notification.mention": "{name} ඔබව සඳහන් කර ඇත",
|
||||
"notification.own_poll": "ඔබගේ මත විමසුම නිමයි",
|
||||
"notification.poll": "ඔබ ඡන්දය දුන් මත විමසුමක් නිමයි",
|
||||
"notification.reblog": "{name} ඔබේ තත්ත්වය ඉහළ නැංවීය",
|
||||
"notification.status": "{name} දැන් පළ කළා",
|
||||
"notification.update": "{name} පළ කිරීමක් සංස්කරණය කළා",
|
||||
"notification.update": "{name} ලිපියක් සංස්කරණය කළා",
|
||||
"notifications.clear": "දැනුම්දීම් මකන්න",
|
||||
"notifications.clear_confirmation": "ඔබට ඔබගේ සියලු දැනුම්දීම් ස්ථිරවම හිස් කිරීමට අවශ්ය බව විශ්වාසද?",
|
||||
"notifications.clear_confirmation": "දැනුම්දීම් සියල්ල හිස් කිරීමට වුවමනා ද?",
|
||||
"notifications.column_settings.admin.report": "නව වාර්තා:",
|
||||
"notifications.column_settings.admin.sign_up": "නව ලියාපදිංචි:",
|
||||
"notifications.column_settings.alert": "වැඩතල දැනුම්දීම්",
|
||||
"notifications.column_settings.favourite": "ප්රියතමයන්:",
|
||||
"notifications.column_settings.filter_bar.advanced": "සියළු ප්රවර්ග පෙන්වන්න",
|
||||
"notifications.column_settings.filter_bar.category": "ඉක්මන් පෙරහන් තීරුව",
|
||||
"notifications.column_settings.filter_bar.show_bar": "පෙරහන් තීරුව පෙන්වන්න",
|
||||
"notifications.column_settings.follow": "නව අනුගාමිකයින්:",
|
||||
"notifications.column_settings.follow_request": "නව අනුගමන ඉල්ලීම්:",
|
||||
"notifications.column_settings.mention": "සැඳහුම්:",
|
||||
"notifications.column_settings.poll": "ඡන්ද ප්රතිඵල:",
|
||||
"notifications.column_settings.poll": "මත විමසුමේ ප්රතිඵල:",
|
||||
"notifications.column_settings.push": "තල්ලු දැනුම්දීම්",
|
||||
"notifications.column_settings.reblog": "තල්ලු කිරීම්:",
|
||||
"notifications.column_settings.show": "තීරුවෙහි පෙන්වන්න",
|
||||
"notifications.column_settings.sound": "ශබ්දය වාදනය",
|
||||
"notifications.column_settings.status": "නව ලිපි:",
|
||||
|
@ -319,38 +311,26 @@
|
|||
"notifications.column_settings.unread_notifications.highlight": "නොකියවූ දැනුම්දීම් ඉස්මතු කරන්න",
|
||||
"notifications.column_settings.update": "සංශෝධන:",
|
||||
"notifications.filter.all": "සියල්ල",
|
||||
"notifications.filter.boosts": "බූස්ට් කරයි",
|
||||
"notifications.filter.favourites": "ප්රියතමයන්",
|
||||
"notifications.filter.follows": "අනුගමනය",
|
||||
"notifications.filter.mentions": "සැඳහුම්",
|
||||
"notifications.filter.polls": "ඡන්ද ප්රතිඵල",
|
||||
"notifications.filter.statuses": "ඔබ අනුගමනය කරන පුද්ගලයින්ගෙන් යාවත්කාලීන",
|
||||
"notifications.grant_permission": "අවසර දෙන්න.",
|
||||
"notifications.filter.polls": "මත විමසුමේ ප්රතිඵල",
|
||||
"notifications.group": "දැනුම්දීම් {count}",
|
||||
"notifications.mark_as_read": "සියළු දැනුම්දීම් කියවූ බව යොදන්න",
|
||||
"notifications.permission_denied": "කලින් ප්රතික්ෂේප කළ බ්රවුසර අවසර ඉල්ලීම හේතුවෙන් ඩෙස්ක්ටොප් දැනුම්දීම් නොමැත",
|
||||
"notifications.permission_denied_alert": "බ්රවුසර අවසරය පෙර ප්රතික්ෂේප කර ඇති බැවින්, ඩෙස්ක්ටොප් දැනුම්දීම් සබල කළ නොහැක",
|
||||
"notifications.permission_required": "අවශ්ය අවසරය ලබා දී නොමැති නිසා ඩෙස්ක්ටොප් දැනුම්දීම් නොමැත.",
|
||||
"notifications_permission_banner.enable": "වැඩතල දැනුම්දීම් සබල කරන්න",
|
||||
"notifications_permission_banner.how_to_control": "Mastodon විවෘතව නොමැති විට දැනුම්දීම් ලබා ගැනීමට, ඩෙස්ක්ටොප් දැනුම්දීම් සබල කරන්න. ඔබට ඒවා සක්රිය කළ පසු ඉහත {icon} බොත්තම හරහා ඩෙස්ක්ටොප් දැනුම්දීම් ජනනය කරන්නේ කුමන ආකාරයේ අන්තර්ක්රියාද යන්න නිවැරදිව පාලනය කළ හැක.",
|
||||
"notifications_permission_banner.title": "කිසිම දෙයක් අතපසු කරන්න එපා",
|
||||
"onboarding.actions.go_to_explore": "See what's trending",
|
||||
"onboarding.actions.go_to_home": "Go to your home feed",
|
||||
"onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
|
||||
"onboarding.follows.title": "Popular on Mastodon",
|
||||
"onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
|
||||
"onboarding.start.skip": "Want to skip right ahead?",
|
||||
"onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
|
||||
"onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
|
||||
"onboarding.steps.publish_status.body": "Say hello to the world.",
|
||||
"onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.",
|
||||
"onboarding.steps.setup_profile.title": "Customize your profile",
|
||||
"onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
|
||||
"onboarding.steps.share_profile.title": "Share your profile",
|
||||
"notifications_permission_banner.title": "කිසිවක් අතපසු නොකරන්න",
|
||||
"onboarding.compose.template": "ආයුබෝ #මාස්ටඩන්!",
|
||||
"onboarding.share.title": "ඔබගේ පැතිකඩ බෙදාගන්න",
|
||||
"onboarding.steps.publish_status.title": "පළමු ලිපිය පළ කරන්න",
|
||||
"onboarding.steps.setup_profile.title": "ඔබගේ පැතිකඩ අභිරුචිකරණය",
|
||||
"onboarding.steps.share_profile.body": "මාස්ටඩන් හි ඔබව සොයා ගන්නේ කෙසේදැයි යහළුවන්ට දන්වන්න",
|
||||
"onboarding.steps.share_profile.title": "ඔබගේ පැතිකඩ බෙදාගන්න",
|
||||
"poll.closed": "වසා ඇත",
|
||||
"poll.refresh": "නැවුම් කරන්න",
|
||||
"poll.reveal": "ප්රතිඵල බලන්න",
|
||||
"poll.vote": "ඡන්දය",
|
||||
"poll.voted": "ඔබ මෙම උත්තරයට ඡන්දය දී ඇත",
|
||||
"poll_button.add_poll": "මත විමසුමක් යොදන්න",
|
||||
"poll_button.add_poll": "මත විමසුමක් අරඹන්න",
|
||||
"poll_button.remove_poll": "මත විමසුම ඉවතලන්න",
|
||||
"privacy.change": "ලිපියේ රහස්යතාව සංශෝධනය",
|
||||
"privacy.direct.long": "සඳහන් කළ අයට දිස්වෙයි",
|
||||
|
@ -363,6 +343,7 @@
|
|||
"refresh": "නැවුම් කරන්න",
|
||||
"regeneration_indicator.label": "පූරණය වෙමින්…",
|
||||
"relative_time.days": "ද. {number}",
|
||||
"relative_time.full.days": "{number, plural, one {දවස් #} other {දවස් #}} කට පෙර",
|
||||
"relative_time.full.hours": "{number, plural, one {පැය #} other {පැය #}} කට පෙර",
|
||||
"relative_time.full.just_now": "මේ දැන්",
|
||||
"relative_time.full.minutes": "{number, plural, one {විනාඩි #} other {විනාඩි #}} කට පෙර",
|
||||
|
@ -377,21 +358,20 @@
|
|||
"report.categories.other": "වෙනත්",
|
||||
"report.categories.spam": "ආයාචිත",
|
||||
"report.categories.violation": "අන්තර්ගතය නිසා සේවාදායකයේ නීතියක් හෝ කිහිපයක් කඩ වේ",
|
||||
"report.category.subtitle": "හොඳම ගැලපීම තෝරන්න",
|
||||
"report.category.title": "මෙම {type}සමඟ සිදුවන්නේ කුමක්දැයි අපට කියන්න",
|
||||
"report.category.title_account": "පැතිකඩ",
|
||||
"report.category.title_status": "තැපැල්",
|
||||
"report.category.title_status": "ලිපිය",
|
||||
"report.close": "අහවරයි",
|
||||
"report.comment.title": "අප දැනගත යුතු යැයි ඔබ සිතන තවත් යමක් තිබේද?",
|
||||
"report.forward": "{target} වෙත හරවන්න",
|
||||
"report.forward_hint": "ගිණුම වෙනත් සේවාදායකයකින්. වාර්තාවේ නිර්නාමික පිටපතක් එතනටත් එවන්න?",
|
||||
"report.mute": "නිහඬ",
|
||||
"report.mute_explanation": "ඔබට ඔවුන්ගේ පෝස්ට් නොපෙනේ. ඔවුන්ට තවමත් ඔබව අනුගමනය කිරීමට සහ ඔබේ පළ කිරීම් දැකීමට හැකි අතර ඒවා නිශ්ශබ්ද කර ඇති බව නොදැනේ.",
|
||||
"report.mute_explanation": "ඔබ ඔවුන්ගේ ලිපි නොදකිනු ඇත. ඔවුන්ට තවමත් ඔබව අනුගමනයට සහ ඔබගේ ලිපි දැකීමට හැකි අතර ඔවුන්ව නිහඬ කර ඇති බව දැන ගැනීමට නොහැකිය.",
|
||||
"report.next": "ඊළඟ",
|
||||
"report.placeholder": "අමතර අදහස්",
|
||||
"report.reasons.dislike": "මම එයට අකැමතියි",
|
||||
"report.reasons.dislike_description": "ඒක බලන්න ඕන දෙයක් නෙවෙයි",
|
||||
"report.reasons.other": "ඒක වෙන දෙයක්",
|
||||
"report.reasons.other": "එය වෙනත් දෙයක්",
|
||||
"report.reasons.other_description": "ගැටළුව වෙනත් වර්ග වලට නොගැලපේ",
|
||||
"report.reasons.spam": "එය අයාචිතයි",
|
||||
"report.reasons.spam_description": "අනිෂ්ට සබැඳි, ව්යාජ නියැලීම, හෝ පුනරාවර්තන පිළිතුරු",
|
||||
|
@ -400,23 +380,24 @@
|
|||
"report.rules.subtitle": "අදාළ සියල්ල තෝරන්න",
|
||||
"report.rules.title": "කුමන නීති උල්ලංඝනය කරන්නේද?",
|
||||
"report.statuses.subtitle": "අදාළ සියල්ල තෝරන්න",
|
||||
"report.statuses.title": "මෙම වාර්තාව උපස්ථ කරන පෝස්ට් තිබේද?",
|
||||
"report.statuses.title": "මෙම වාර්තාව උපස්ථ කළ ලිපි තිබේ ද?",
|
||||
"report.submit": "යොමන්න",
|
||||
"report.target": "වාර්තාව {target}",
|
||||
"report.thanks.take_action": "Mastodon හි ඔබ දකින දේ පාලනය කිරීම සඳහා ඔබේ විකල්ප මෙන්න:",
|
||||
"report.target": "{target} වාර්තා කිරීම",
|
||||
"report.thanks.take_action": "මාස්ටඩන් හි ඔබ දකින දෑ පාලනයට තිබෙන විකල්ප:",
|
||||
"report.thanks.take_action_actionable": "අපි මෙය සමාලෝචනය කරන අතරතුර, ඔබට @{name}ට එරෙහිව පියවර ගත හැක:",
|
||||
"report.thanks.title": "මෙය නොපෙන්විය යුතුද?",
|
||||
"report.thanks.title_actionable": "වාර්තා කිරීමට ස්තූතියි, අපි මේ ගැන සොයා බලමු.",
|
||||
"report.unfollow": "@{name}අනුගමනය නොකරන්න",
|
||||
"report.unfollow_explanation": "ඔබ මෙම ගිණුම අනුගමනය කරයි. ඔබේ නිවසේ සංග්රහයේ ඔවුන්ගේ පළ කිරීම් තවදුරටත් නොදැකීමට, ඒවා අනුගමනය නොකරන්න.",
|
||||
"report.unfollow_explanation": "ඔබ මෙම ගිණුම අනුගමනය කරයි. ඔබගේ මුල් පිටුවේ ඔවුන්ගේ ලිපි නොදැකීමට, ඔවුන්ව තවදුරටත් අනුගමනය නොකරන්න.",
|
||||
"report_notification.attached_statuses": "{count, plural, one {ලිපි {count}} other {ලිපි {count} ක්}} අමුණා ඇත",
|
||||
"report_notification.categories.other": "වෙනත්",
|
||||
"report_notification.categories.spam": "ආයාචිත",
|
||||
"report_notification.categories.violation": "නීතිය කඩ කිරීම",
|
||||
"report_notification.open": "විවෘත වාර්තාව",
|
||||
"search.placeholder": "සොයන්න",
|
||||
"search.quick_action.open_url": "ලිපිනය මාස්ටඩන්හි අරින්න",
|
||||
"search.search_or_paste": "සොයන්න හෝ ඒ.ස.නි. අලවන්න",
|
||||
"search_results.all": "සියල්ල",
|
||||
"search_results.hashtags": "හැෂ් ටැග්",
|
||||
"search_results.nothing_found": "මෙම සෙවුම් පද සඳහා කිසිවක් සොයාගත නොහැකි විය",
|
||||
"search_results.see_all": "සියල්ල බලන්න",
|
||||
"search_results.statuses": "ලිපි",
|
||||
|
@ -424,24 +405,23 @@
|
|||
"server_banner.learn_more": "තව දැනගන්න",
|
||||
"sign_in_banner.create_account": "ගිණුමක් සාදන්න",
|
||||
"sign_in_banner.sign_in": "පිවිසෙන්න",
|
||||
"status.admin_account": "@{name}සඳහා මධ්යස්ථ අතුරුමුහුණත විවෘත කරන්න",
|
||||
"status.admin_status": "මධ්යස්ථ අතුරුමුහුණතෙහි මෙම තත්ත්වය විවෘත කරන්න",
|
||||
"status.admin_status": "මෙම ලිපිය මැදිහත්කරණ අතුරුමුහුණතෙහි අරින්න",
|
||||
"status.block": "@{name} අවහිර",
|
||||
"status.bookmark": "පොත්යොමුවක්",
|
||||
"status.cannot_reblog": "මෙම තනතුර වැඩි කළ නොහැක",
|
||||
"status.copy": "තත්වයට සබැඳිය පිටපත් කරන්න",
|
||||
"status.delete": "මකන්න",
|
||||
"status.detailed_status": "විස්තරාත්මක සංවාද දැක්ම",
|
||||
"status.edit": "සංස්කරණය",
|
||||
"status.edited": "සංශෝධිතයි {date}",
|
||||
"status.edited_x_times": "සංශෝධිතයි {count, plural, one {වාර {count}} other {වාර {count}}}",
|
||||
"status.embed": "කාවැද්දූ",
|
||||
"status.filter": "මෙම ලිපිය පෙරන්න",
|
||||
"status.filtered": "පෙරන ලද",
|
||||
"status.hide": "ලිපිය සඟවන්න",
|
||||
"status.history.created": "{name} නිර්මාණය {date}",
|
||||
"status.history.edited": "{name} සංස්කරණය {date}",
|
||||
"status.load_more": "තව පූරණය",
|
||||
"status.media_hidden": "මාධ්ය සඟවා ඇත",
|
||||
"status.mention": "@{name} සැඳහුම",
|
||||
"status.mention": "@{name} සඳහන් කරන්ක",
|
||||
"status.more": "තව",
|
||||
"status.mute": "@{name} නිහඬව",
|
||||
"status.mute_conversation": "සංවාදය නිහඬව",
|
||||
|
@ -449,14 +429,10 @@
|
|||
"status.pin": "පැතිකඩට අමුණන්න",
|
||||
"status.pinned": "ඇමිණූ ලිපියකි",
|
||||
"status.read_more": "තව කියවන්න",
|
||||
"status.reblog": "බූස්ට් කරන්න",
|
||||
"status.reblog_private": "මුල් දෘශ්යතාව සමඟ වැඩි කරන්න",
|
||||
"status.reblogs.empty": "තාම කවුරුත් මේ toot එක boost කරලා නැහැ. යමෙකු එසේ කළ විට, ඔවුන් මෙහි පෙන්වනු ඇත.",
|
||||
"status.redraft": "මකන්න සහ නැවත කෙටුම්පත",
|
||||
"status.remove_bookmark": "පොත්යොමුව ඉවතලන්න",
|
||||
"status.reply": "පිළිතුරු",
|
||||
"status.replyAll": "නූලට පිළිතුරු දෙන්න",
|
||||
"status.report": "@{name} වාර්තාව",
|
||||
"status.report": "@{name} වාර්තා කරන්න",
|
||||
"status.sensitive_warning": "සංවේදී අන්තර්ගතයකි",
|
||||
"status.share": "බෙදාගන්න",
|
||||
"status.show_filter_reason": "කෙසේ වුවද පෙන්වන්න",
|
||||
|
@ -464,29 +440,29 @@
|
|||
"status.show_less_all": "සියල්ල අඩුවෙන් පෙන්වන්න",
|
||||
"status.show_more": "තවත් පෙන්වන්න",
|
||||
"status.show_more_all": "සියල්ල වැඩියෙන් පෙන්වන්න",
|
||||
"status.title.with_attachments": "{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}",
|
||||
"status.translate": "පරිවර්තනය",
|
||||
"status.translated_from_with": "{provider} මගින් {lang} භාෂාවෙන් පරිවර්තනය කර ඇත",
|
||||
"status.uncached_media_warning": "පෙරදසුන නැත",
|
||||
"status.unmute_conversation": "සංවාදය නොනිහඬ",
|
||||
"status.unpin": "පැතිකඩෙන් ගළවන්න",
|
||||
"subscribed_languages.save": "වෙනස්කම් සුරකින්න",
|
||||
"tabs_bar.home": "මුල් පිටුව",
|
||||
"tabs_bar.notifications": "දැනුම්දීම්",
|
||||
"time_remaining.days": "{number, plural, one {# දින} other {# දින}} අත්හැරියා",
|
||||
"time_remaining.hours": "{number, plural, one {# පැය} other {# පැය}} අත්හැරියා",
|
||||
"time_remaining.minutes": "{number, plural, one {විනාඩි #} other {# මිනිත්තු}} අත්හැරියා",
|
||||
"time_remaining.moments": "ඉතිරිව ඇති මොහොත",
|
||||
"time_remaining.seconds": "{number, plural, one {# දෙවැනි} other {# තත්පර}} අත්හැරියා",
|
||||
"time_remaining.days": "{number, plural, one {දවස් #} other {දවස් #}} ක් ඉතිරිය",
|
||||
"time_remaining.hours": "{number, plural, one {පැය #} other {පැය #}} ක් ඉතිරිය",
|
||||
"time_remaining.minutes": "{number, plural, one {විනාඩි #} other {විනාඩි #}} ක් ඉතිරිය",
|
||||
"time_remaining.seconds": "{number, plural, one {තත්පර #} other {තත්පර #}} ක් ඉතිරිය",
|
||||
"timeline_hint.remote_resource_not_displayed": "වෙනත් සේවාදායකයන්ගෙන් {resource} දර්ශනය නොවේ.",
|
||||
"timeline_hint.resources.followers": "අනුගාමිකයින්",
|
||||
"timeline_hint.resources.follows": "අනුගමනය",
|
||||
"timeline_hint.resources.follows": "අනුගමන",
|
||||
"timeline_hint.resources.statuses": "පරණ ලිපි",
|
||||
"trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}",
|
||||
"trends.trending_now": "දැන් ප්රවණතාවය",
|
||||
"trends.trending_now": "දැන් නැගී එන",
|
||||
"ui.beforeunload": "ඔබ මාස්ටඩන් හැර ගියහොත් කටුපිටපත අහිමි වේ.",
|
||||
"units.short.billion": "{count}බී",
|
||||
"units.short.million": "ද.ල. {count}",
|
||||
"units.short.thousand": "{count}කි",
|
||||
"upload_area.title": "උඩුගතයට ඇද දමන්න",
|
||||
"upload_button.label": "රූප, දෘශ්යක හෝ හඬපට යොදන්න",
|
||||
"upload_button.label": "රූප, දෘශ්යක හෝ හඬපට අමුණන්න",
|
||||
"upload_error.limit": "සීමාව ඉක්මවා ඇත.",
|
||||
"upload_error.poll": "මත විමසුම් සමඟ ගොනු යෙදීමට ඉඩ නොදේ.",
|
||||
"upload_form.audio_description": "නොඇසෙන අය සඳහා විස්තර කරන්න",
|
||||
|
@ -507,6 +483,7 @@
|
|||
"upload_modal.preview_label": "පෙරදසුන ({ratio})",
|
||||
"upload_progress.label": "උඩුගත වෙමින්...",
|
||||
"upload_progress.processing": "සැකසෙමින්…",
|
||||
"username.taken": "නම දැනටමත් අරගෙන ඇත",
|
||||
"video.close": "දෘශ්යකය වසන්න",
|
||||
"video.download": "ගොනුව බාගන්න",
|
||||
"video.exit_fullscreen": "පූර්ණ තිරයෙන් පිටවන්න",
|
||||
|
|
|
@ -48,11 +48,11 @@
|
|||
"account.media": "Médiá",
|
||||
"account.mention": "Spomeň @{name}",
|
||||
"account.moved_to": "{name} uvádza, že jeho/jej nový účet je teraz:",
|
||||
"account.mute": "Nevšímaj si @{name}",
|
||||
"account.mute_notifications_short": "Stíš oboznámenia",
|
||||
"account.mute_short": "Nevšímaj si",
|
||||
"account.muted": "Nevšímaný/á",
|
||||
"account.no_bio": "Nieje uvedený žiadny popis.",
|
||||
"account.mute": "Stíš @{name}",
|
||||
"account.mute_notifications_short": "Stíš oznámenia",
|
||||
"account.mute_short": "Stíš",
|
||||
"account.muted": "Stíšený",
|
||||
"account.no_bio": "Nie je uvedený žiadny popis.",
|
||||
"account.open_original_page": "Otvor pôvodnú stránku",
|
||||
"account.posts": "Príspevky",
|
||||
"account.posts_with_replies": "Príspevky a odpovede",
|
||||
|
@ -307,8 +307,9 @@
|
|||
"home.column_settings.basic": "Základné",
|
||||
"home.column_settings.show_reblogs": "Ukáž vyzdvihnuté",
|
||||
"home.column_settings.show_replies": "Ukáž odpovede",
|
||||
"home.explore_prompt.body": "Váš domovský informačný kanál bude obsahovať mix príspevkov z mriežok, ktoré ste sa rozhodli sledovať, ľudí, ktorých ste sa rozhodli sledovať, a príspevkov, ktoré preferujú. Ak sa vám to zdá príliš málo, možno budete chcieť:",
|
||||
"home.explore_prompt.title": "Toto je tvoja domovina v rámci Mastodonu.",
|
||||
"home.hide_announcements": "Skry oboznámenia",
|
||||
"home.hide_announcements": "Skry oznámenia",
|
||||
"home.pending_critical_update.body": "Prosím aktualizuj si svoj Mastodon server, ako náhle to bude možné!",
|
||||
"home.pending_critical_update.link": "Pozri aktualizácie",
|
||||
"home.pending_critical_update.title": "Je dostupná kritická bezpečnostná aktualizácia!",
|
||||
|
|
|
@ -79,16 +79,16 @@
|
|||
"admin.impact_report.instance_accounts": "Профілі облікових записів буде видалено",
|
||||
"admin.impact_report.instance_followers": "Підписники, яких можуть втратити наші користувачі",
|
||||
"admin.impact_report.instance_follows": "Підписники, яких можуть втратити їхні користувачі",
|
||||
"admin.impact_report.title": "Наслідки",
|
||||
"alert.rate_limited.message": "Спробуйте ще раз через {retry_time, time, medium}.",
|
||||
"admin.impact_report.title": "Підсумки впливу",
|
||||
"alert.rate_limited.message": "Спробуйте ще раз за {retry_time, time, medium}.",
|
||||
"alert.rate_limited.title": "Швидкість обмежена",
|
||||
"alert.unexpected.message": "Сталася неочікувана помилка.",
|
||||
"alert.unexpected.title": "Ой!",
|
||||
"announcement.announcement": "Оголошення",
|
||||
"attachments_list.unprocessed": "(не оброблено)",
|
||||
"audio.hide": "Сховати аудіо",
|
||||
"autosuggest_hashtag.per_week": "{count} в тиждень",
|
||||
"boost_modal.combo": "Ви можете натиснути {combo}, щоб пропустити це наступного разу",
|
||||
"autosuggest_hashtag.per_week": "{count} на тиждень",
|
||||
"boost_modal.combo": "Ви можете натиснути {combo}, щоби пропустити це наступного разу",
|
||||
"bundle_column_error.copy_stacktrace": "Копіювати звіт про помилку",
|
||||
"bundle_column_error.error.body": "Неможливо показати запитану сторінку. Це може бути спричинено помилкою у нашому коді, або через проблему сумісності з браузером.",
|
||||
"bundle_column_error.error.title": "О, ні!",
|
||||
|
@ -140,7 +140,7 @@
|
|||
"compose.saved.body": "Допис збережено.",
|
||||
"compose_form.direct_message_warning_learn_more": "Дізнатися більше",
|
||||
"compose_form.encryption_warning": "Дописи на Mastodon не захищені шифруванням. Не поширюйте жодну делікатну інформацію.",
|
||||
"compose_form.hashtag_warning": "Цей допис не буде зображений у жодній стрічці гештеґу, оскільки він прихований. Тільки публічні дописи можуть бути знайдені за гештеґом.",
|
||||
"compose_form.hashtag_warning": "Цього допису не буде під жодним гештеґом, оскільки він не є загальнодоступним. За гештеґом можна шукати лише публічні дописи.",
|
||||
"compose_form.lock_disclaimer": "Ваш обліковий запис не {locked}. Будь-який користувач може підписатися на вас та переглядати ваші дописи для підписників.",
|
||||
"compose_form.lock_disclaimer.lock": "приватний",
|
||||
"compose_form.placeholder": "Що у вас на думці?",
|
||||
|
@ -151,7 +151,7 @@
|
|||
"compose_form.poll.switch_to_multiple": "Дозволити вибір декількох відповідей",
|
||||
"compose_form.poll.switch_to_single": "Перемкнути у режим вибору однієї відповіді",
|
||||
"compose_form.publish": "Опублікувати",
|
||||
"compose_form.publish_form": "Опублікувати",
|
||||
"compose_form.publish_form": "Новий допис",
|
||||
"compose_form.publish_loud": "{publish}!",
|
||||
"compose_form.save_changes": "Зберегти зміни",
|
||||
"compose_form.sensitive.hide": "{count, plural, one {Позначити медіа делікатним} other {Позначити медіа делікатними}}",
|
||||
|
@ -206,7 +206,7 @@
|
|||
"dismissable_banner.explore_tags": "Ці хештеги зараз набирають популярності серед людей на цьому та інших серверах децентралізованої мережі. Хештеги, які використовуються більшою кількістю людей, мають вищий рейтинг.",
|
||||
"dismissable_banner.public_timeline": "Це найновіші загальнодоступні дописи від людей в соціальній мережі, на які підписані люди в {domain}.",
|
||||
"embed.instructions": "Вбудуйте цей допис до вашого вебсайту, скопіювавши код нижче.",
|
||||
"embed.preview": "Ось як він виглядатиме:",
|
||||
"embed.preview": "Ось який вигляд це матиме:",
|
||||
"emoji_button.activity": "Діяльність",
|
||||
"emoji_button.clear": "Очистити",
|
||||
"emoji_button.custom": "Власні",
|
||||
|
@ -227,7 +227,7 @@
|
|||
"empty_column.account_unavailable": "Профіль недоступний",
|
||||
"empty_column.blocks": "Ви ще не заблокували жодного користувача.",
|
||||
"empty_column.bookmarked_statuses": "У вас ще немає дописів у закладках. Коли ви щось додасте до закладок, воно з'явиться тут.",
|
||||
"empty_column.community": "Локальна стрічка пуста. Напишіть щось, щоб розігріти народ!",
|
||||
"empty_column.community": "Локальна стрічка порожня. Напишіть щось, щоб розігріти народ!",
|
||||
"empty_column.direct": "У вас ще немає жодних особистих згадок. Коли ви надсилаєте чи отримуєте повідомлення, воно з'явиться тут.",
|
||||
"empty_column.domain_blocks": "Тут поки немає прихованих доменів.",
|
||||
"empty_column.explore_statuses": "Нема нічого популярного. Подивіться пізніше!",
|
||||
|
@ -240,10 +240,10 @@
|
|||
"empty_column.list": "Цей список порожній. Коли його учасники додадуть нові дописи, вони з'являться тут.",
|
||||
"empty_column.lists": "У вас ще немає списків. Коли ви їх створите, вони з'являться тут.",
|
||||
"empty_column.mutes": "Ви ще не приховали жодного користувача.",
|
||||
"empty_column.notifications": "У вас ще немає сповіщень. Переписуйтесь з іншими користувачами, щоб почати розмову.",
|
||||
"empty_column.notifications": "У вас ще немає сповіщень. Коли інші люди почнуть взаємодіяти з вами, ви побачите їх тут.",
|
||||
"empty_column.public": "Тут поки нічого немає! Опублікуйте щось, або вручну підпишіться на користувачів інших серверів, щоб заповнити стрічку",
|
||||
"error.unexpected_crash.explanation": "Через помилку у нашому коді або несумісність браузера, ця сторінка не може бути зображена коректно.",
|
||||
"error.unexpected_crash.explanation_addons": "Неможливо правильно показати цю сторінку. Ймовірно, цю помилку викликано додатком браузера або автоматичним засобом перекладу.",
|
||||
"error.unexpected_crash.explanation_addons": "Неможливо правильно показати цю сторінку. Ймовірно, цю помилку спричинило розширення браузера або автоматичний засіб перекладу.",
|
||||
"error.unexpected_crash.next_steps": "Спробуйте перезавантажити сторінку. Якщо це не допоможе, ви все ще зможете використовувати Mastodon через інший браузер або рідний застосунок.",
|
||||
"error.unexpected_crash.next_steps_addons": "Спробуйте їх вимкнути та оновити сторінку. Якщо це не допомагає, ви можете використовувати Mastodon через інший браузер або окремий застосунок.",
|
||||
"errors.unexpected_crash.copy_stacktrace": "Скопіювати трасування стека у буфер обміну",
|
||||
|
@ -394,7 +394,7 @@
|
|||
"moved_to_account_banner.text": "Ваш обліковий запис {disabledAccount} наразі вимкнений, оскільки вас перенесено до {movedToAccount}.",
|
||||
"mute_modal.duration": "Тривалість",
|
||||
"mute_modal.hide_notifications": "Сховати сповіщення цього користувача?",
|
||||
"mute_modal.indefinite": "Назавжди",
|
||||
"mute_modal.indefinite": "Невизначений строк",
|
||||
"navigation_bar.about": "Про застосунок",
|
||||
"navigation_bar.advanced_interface": "Відкрити в розширеному вебінтерфейсі",
|
||||
"navigation_bar.blocks": "Заблоковані користувачі",
|
||||
|
@ -428,7 +428,7 @@
|
|||
"notification.follow": "{name} підписалися на вас",
|
||||
"notification.follow_request": "{name} відправили запит на підписку",
|
||||
"notification.mention": "{name} згадали вас",
|
||||
"notification.own_poll": "Ваше опитування завершено",
|
||||
"notification.own_poll": "Ваше опитування завершилося",
|
||||
"notification.poll": "Опитування, у якому ви голосували, скінчилося",
|
||||
"notification.reblog": "{name} поширює ваш допис",
|
||||
"notification.status": "{name} щойно дописує",
|
||||
|
@ -480,7 +480,7 @@
|
|||
"onboarding.follows.title": "Персоналізуйте домашню стрічку",
|
||||
"onboarding.share.lead": "Розкажіть людям про те, як вони можуть знайти вас на Mastodon!",
|
||||
"onboarding.share.message": "Я {username} на #Mastodon! Стежте за мною на {url}",
|
||||
"onboarding.share.next_steps": "Можливі наступні кроки:",
|
||||
"onboarding.share.next_steps": "Можливі такі кроки:",
|
||||
"onboarding.share.title": "Поділитися своїм профілем",
|
||||
"onboarding.start.lead": "Тепер ви — частина Mastodon, унікальної децентралізованої платформи соціальних медіа, де ви, а не алгоритми керують вашими вподобаннями. Розпочнімо роботу:",
|
||||
"onboarding.start.skip": "Хочете пропустити?",
|
||||
|
@ -694,7 +694,7 @@
|
|||
"units.short.thousand": "{count} тис",
|
||||
"upload_area.title": "Перетягніть сюди, щоб завантажити",
|
||||
"upload_button.label": "Додати зображення, відео або аудіо",
|
||||
"upload_error.limit": "Ліміт завантаження файлів перевищено.",
|
||||
"upload_error.limit": "Ви перевищили ліміт завантаження файлів.",
|
||||
"upload_error.poll": "Не можна завантажувати файли до опитувань.",
|
||||
"upload_form.audio_description": "Опишіть для людей із вадами слуху",
|
||||
"upload_form.description": "Опишіть для людей з вадами зору",
|
||||
|
@ -713,7 +713,7 @@
|
|||
"upload_modal.hint": "Клацніть або перетягніть коло на превʼю, щоб обрати точку, яку буде завжди видно на мініатюрах.",
|
||||
"upload_modal.preparing_ocr": "Підготовка OCR…",
|
||||
"upload_modal.preview_label": "Переглянути ({ratio})",
|
||||
"upload_progress.label": "Завантаження...",
|
||||
"upload_progress.label": "Вивантаження...",
|
||||
"upload_progress.processing": "Обробка…",
|
||||
"username.taken": "Це ім'я користувача вже зайнято. Спробуйте інше",
|
||||
"video.close": "Закрити відео",
|
||||
|
|
|
@ -11,6 +11,7 @@ const normalizeFilter = (state, filter) => {
|
|||
filter_action: filter.filter_action,
|
||||
keywords: filter.keywords,
|
||||
expires_at: filter.expires_at ? Date.parse(filter.expires_at) : null,
|
||||
with_quote: filter.with_quote,
|
||||
});
|
||||
|
||||
if (is(state.get(filter.id), normalizedFilter)) {
|
||||
|
|
|
@ -3,7 +3,7 @@ import { createSelector } from 'reselect';
|
|||
|
||||
import { toServerSideType } from 'mastodon/utils/filters';
|
||||
|
||||
import { me } from '../initial_state';
|
||||
import { me, hideBlockingQuote } from '../initial_state';
|
||||
|
||||
const getAccountBase = (state, id) => state.getIn(['accounts', id], null);
|
||||
const getAccountCounters = (state, id) => state.getIn(['accounts_counters', id], null);
|
||||
|
@ -37,38 +37,57 @@ export const makeGetStatus = () => {
|
|||
[
|
||||
(state, { id }) => state.getIn(['statuses', id]),
|
||||
(state, { id }) => state.getIn(['statuses', state.getIn(['statuses', id, 'reblog'])]),
|
||||
(state, { id }) => state.getIn(['statuses', state.getIn(['statuses', id, 'quote_id'])]),
|
||||
(state, { id }) => state.getIn(['statuses', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'quote_id'])]),
|
||||
(state, { id }) => state.getIn(['accounts', state.getIn(['statuses', id, 'account'])]),
|
||||
(state, { id }) => state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'account'])]),
|
||||
getFilters,
|
||||
],
|
||||
|
||||
(statusBase, statusReblog, accountBase, accountReblog, filters) => {
|
||||
(statusBase, statusReblog, statusQuote, statusReblogQuote, accountBase, accountReblog, filters) => {
|
||||
if (!statusBase || statusBase.get('isLoading')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (statusReblog) {
|
||||
statusReblog = statusReblog.set('account', accountReblog);
|
||||
statusQuote = statusReblogQuote;
|
||||
} else {
|
||||
statusReblog = null;
|
||||
}
|
||||
|
||||
if (hideBlockingQuote && (statusReblog || statusBase).getIn(['quote', 'quote_muted'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let filtered = false;
|
||||
let filterAction = 'warn';
|
||||
if ((accountReblog || accountBase).get('id') !== me && filters) {
|
||||
let filterResults = statusReblog?.get('filtered') || statusBase.get('filtered') || ImmutableList();
|
||||
const quoteFilterResults = statusQuote?.get('filtered');
|
||||
if (quoteFilterResults) {
|
||||
const filterWithQuote = quoteFilterResults.some((result) => filters.getIn([result.get('filter'), 'with_quote']));
|
||||
if (filterWithQuote) {
|
||||
filterResults = filterResults.concat(quoteFilterResults);
|
||||
}
|
||||
}
|
||||
|
||||
if (filterResults.some((result) => filters.getIn([result.get('filter'), 'filter_action']) === 'hide')) {
|
||||
return null;
|
||||
}
|
||||
filterResults = filterResults.filter(result => filters.has(result.get('filter')));
|
||||
if (!filterResults.isEmpty()) {
|
||||
filtered = filterResults.map(result => filters.getIn([result.get('filter'), 'title']));
|
||||
filterAction = filterResults.some((result) => filters.getIn([result.get('filter'), 'filter_action']) === 'warn') ? 'warn' : 'half_warn';
|
||||
}
|
||||
}
|
||||
|
||||
return statusBase.withMutations(map => {
|
||||
map.set('reblog', statusReblog);
|
||||
map.set('quote', statusQuote);
|
||||
map.set('account', accountBase);
|
||||
map.set('matched_filters', filtered);
|
||||
map.set('filter_action', filterAction);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
|
|
@ -1733,6 +1733,11 @@ a.account__display-name {
|
|||
.status__avatar {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
|
||||
&.status__avatar__compact {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.muted {
|
||||
|
@ -8483,6 +8488,13 @@ noscript {
|
|||
.status__wrapper {
|
||||
position: relative;
|
||||
|
||||
&.status__wrapper__compact {
|
||||
border-radius: 4px;
|
||||
border: 1px solid $ui-primary-color;
|
||||
margin-block-start: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.unread {
|
||||
&::before {
|
||||
content: '';
|
||||
|
|
|
@ -26,7 +26,7 @@ class AccountStatusesFilter
|
|||
scope.merge!(no_reblogs_scope) if exclude_reblogs?
|
||||
scope.merge!(hashtag_scope) if tagged?
|
||||
|
||||
available_searchabilities = [:public, :unlisted, :private, :direct, :limited, nil]
|
||||
available_searchabilities = [:public, :public_unlisted, :unlisted, :private, :direct, :limited, nil]
|
||||
available_visibilities = [:public, :public_unlisted, :login, :unlisted, :private, :direct, :limited]
|
||||
|
||||
available_searchabilities = [:public] if domain_block&.reject_send_not_public_searchability
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
class ActivityPub::Activity::Accept < ActivityPub::Activity
|
||||
def perform
|
||||
return accept_follow_for_relay if relay_follow?
|
||||
return accept_follow_for_friend if friend_follow?
|
||||
return accept_follow!(follow_request_from_object) unless follow_request_from_object.nil?
|
||||
|
||||
case @object['type']
|
||||
|
@ -43,6 +44,18 @@ class ActivityPub::Activity::Accept < ActivityPub::Activity
|
|||
relay.present?
|
||||
end
|
||||
|
||||
def accept_follow_for_friend
|
||||
friend.update!(active_state: :accepted, passive_state: :idle)
|
||||
end
|
||||
|
||||
def friend
|
||||
@friend ||= FriendDomain.find_by(domain: @account.domain, active_follow_activity_id: object_uri, active_state: [:pending, :accepted]) if @account.domain.present?
|
||||
end
|
||||
|
||||
def friend_follow?
|
||||
friend.present?
|
||||
end
|
||||
|
||||
def target_uri
|
||||
@target_uri ||= value_or_id(@object['actor'])
|
||||
end
|
||||
|
|
|
@ -93,9 +93,9 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
|
||||
resolve_thread(@status)
|
||||
fetch_replies(@status)
|
||||
process_references!
|
||||
distribute
|
||||
forward_for_reply
|
||||
process_references!
|
||||
join_group!
|
||||
end
|
||||
|
||||
|
@ -114,7 +114,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
end
|
||||
|
||||
def process_status_params
|
||||
@status_parser = ActivityPub::Parser::StatusParser.new(@json, followers_collection: @account.followers_url, object: @object, account: @account)
|
||||
@status_parser = ActivityPub::Parser::StatusParser.new(@json, followers_collection: @account.followers_url, object: @object, account: @account, friend_domain: friend_domain?)
|
||||
|
||||
@params = {
|
||||
uri: @status_parser.uri,
|
||||
|
@ -256,17 +256,20 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
|
||||
emoji = CustomEmoji.find_by(shortcode: custom_emoji_parser.shortcode, domain: @account.domain)
|
||||
|
||||
return unless emoji.nil? || custom_emoji_parser.image_remote_url != emoji.image_remote_url || (custom_emoji_parser.updated_at && custom_emoji_parser.updated_at >= emoji.updated_at)
|
||||
return unless emoji.nil? ||
|
||||
custom_emoji_parser.image_remote_url != emoji.image_remote_url ||
|
||||
(custom_emoji_parser.updated_at && custom_emoji_parser.updated_at >= emoji.updated_at) ||
|
||||
custom_emoji_parser.license != emoji.license
|
||||
|
||||
begin
|
||||
emoji ||= CustomEmoji.new(
|
||||
domain: @account.domain,
|
||||
shortcode: custom_emoji_parser.shortcode,
|
||||
uri: custom_emoji_parser.uri,
|
||||
is_sensitive: custom_emoji_parser.is_sensitive,
|
||||
license: custom_emoji_parser.license
|
||||
uri: custom_emoji_parser.uri
|
||||
)
|
||||
emoji.image_remote_url = custom_emoji_parser.image_remote_url
|
||||
emoji.license = custom_emoji_parser.license
|
||||
emoji.is_sensitive = custom_emoji_parser.is_sensitive
|
||||
emoji.save
|
||||
rescue Seahorse::Client::NetworkingError => e
|
||||
Rails.logger.warn "Error storing emoji: #{e}"
|
||||
|
@ -444,7 +447,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
|
||||
def related_to_local_activity?
|
||||
fetch? || followed_by_local_accounts? || requested_through_relay? ||
|
||||
responds_to_followed_account? || addresses_local_accounts?
|
||||
responds_to_followed_account? || addresses_local_accounts? || quote_local? || free_friend_domain?
|
||||
end
|
||||
|
||||
def responds_to_followed_account?
|
||||
|
@ -485,10 +488,30 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
|||
|
||||
def process_references!
|
||||
references = @object['references'].nil? ? [] : ActivityPub::FetchReferencesService.new.call(@status, @object['references'])
|
||||
quote = @object['quote'] || @object['quoteUrl'] || @object['quoteURL'] || @object['_misskey_quote']
|
||||
references << quote if quote
|
||||
|
||||
ProcessReferencesService.perform_worker_async(@status, [], references)
|
||||
ProcessReferencesService.call_service_without_error(@status, [], references, [quote].compact)
|
||||
end
|
||||
|
||||
def quote_local?
|
||||
url = quote
|
||||
|
||||
if url.present?
|
||||
ResolveURLService.new.call(url, on_behalf_of: @account, local_only: true).present?
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def free_friend_domain?
|
||||
FriendDomain.free_receivings.exists?(domain: @account.domain)
|
||||
end
|
||||
|
||||
def friend_domain?
|
||||
FriendDomain.enabled.find_by(domain: @account.domain)&.accepted?
|
||||
end
|
||||
|
||||
def quote
|
||||
@quote ||= @object['quote'] || @object['quoteUrl'] || @object['quoteURL'] || @object['_misskey_quote']
|
||||
end
|
||||
|
||||
def join_group!
|
||||
|
|
|
@ -4,6 +4,8 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
|
|||
def perform
|
||||
if @account.uri == object_uri
|
||||
delete_person
|
||||
elsif object_uri == ActivityPub::TagManager::COLLECTIONS[:public]
|
||||
delete_friend
|
||||
else
|
||||
delete_note
|
||||
end
|
||||
|
@ -42,6 +44,11 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
|
|||
end
|
||||
end
|
||||
|
||||
def delete_friend
|
||||
friend = FriendDomain.find_by(domain: @account.domain)
|
||||
friend&.destroy
|
||||
end
|
||||
|
||||
def forwarder
|
||||
@forwarder ||= ActivityPub::Forwarder.new(@account, @json, @status)
|
||||
end
|
||||
|
|
|
@ -4,6 +4,8 @@ class ActivityPub::Activity::Follow < ActivityPub::Activity
|
|||
include Payloadable
|
||||
|
||||
def perform
|
||||
return request_follow_for_friend if friend_follow?
|
||||
|
||||
target_account = account_from_uri(object_uri)
|
||||
|
||||
return if target_account.nil? || !target_account.local? || delete_arrived_first?(@json['id'])
|
||||
|
@ -30,7 +32,7 @@ class ActivityPub::Activity::Follow < ActivityPub::Activity
|
|||
|
||||
follow_request = FollowRequest.create!(account: @account, target_account: target_account, uri: @json['id'])
|
||||
|
||||
if target_account.locked? || @account.silenced? || block_straight_follow? || (@account.bot? && target_account.user&.setting_lock_follow_from_bot)
|
||||
if target_account.locked? || @account.silenced? || block_straight_follow? || ((@account.bot? || proxy_account?) && target_account.user&.setting_lock_follow_from_bot)
|
||||
LocalNotificationWorker.perform_async(target_account.id, follow_request.id, 'FollowRequest', 'follow_request')
|
||||
else
|
||||
AuthorizeFollowService.new.call(@account, target_account)
|
||||
|
@ -43,6 +45,40 @@ class ActivityPub::Activity::Follow < ActivityPub::Activity
|
|||
ActivityPub::DeliveryWorker.perform_async(json, target_account.id, @account.inbox_url)
|
||||
end
|
||||
|
||||
def request_follow_for_friend
|
||||
already_accepted = false
|
||||
|
||||
if friend.present?
|
||||
already_accepted = friend.accepted?
|
||||
friend.update!(passive_state: :pending, active_state: :idle, passive_follow_activity_id: @json['id'])
|
||||
else
|
||||
@friend = FriendDomain.new(domain: @account.domain, passive_state: :pending, passive_follow_activity_id: @json['id'])
|
||||
@friend.initialize_inbox_url!
|
||||
@friend.save!
|
||||
end
|
||||
|
||||
if already_accepted || Setting.unlocked_friend
|
||||
friend.accept!
|
||||
|
||||
# Notify for admin even if unlocked
|
||||
notify_staff_about_pending_friend_server! unless already_accepted
|
||||
else
|
||||
notify_staff_about_pending_friend_server!
|
||||
end
|
||||
end
|
||||
|
||||
def friend
|
||||
@friend ||= FriendDomain.find_by(domain: @account.domain) if @account.domain.present?
|
||||
end
|
||||
|
||||
def friend_follow?
|
||||
@json['object'] == ActivityPub::TagManager::COLLECTIONS[:public] && !block_friend?
|
||||
end
|
||||
|
||||
def block_friend?
|
||||
@block_friend ||= DomainBlock.reject_friend?(@account.domain) || DomainBlock.blocked?(@account.domain)
|
||||
end
|
||||
|
||||
def block_straight_follow?
|
||||
@block_straight_follow ||= DomainBlock.reject_straight_follow?(@account.domain)
|
||||
end
|
||||
|
@ -50,4 +86,35 @@ class ActivityPub::Activity::Follow < ActivityPub::Activity
|
|||
def block_new_follow?
|
||||
@block_new_follow ||= DomainBlock.reject_new_follow?(@account.domain)
|
||||
end
|
||||
|
||||
def proxy_account?
|
||||
(@account.username.downcase.include?('_proxy') ||
|
||||
@account.username.downcase.end_with?('proxy') ||
|
||||
@account.username.downcase.include?('_bot_') ||
|
||||
@account.username.downcase.end_with?('bot') ||
|
||||
@account.display_name&.downcase&.include?('proxy') ||
|
||||
@account.display_name&.include?('プロキシ') ||
|
||||
@account.note&.include?('プロキシ')) &&
|
||||
(@account.following_count.zero? || @account.following_count > @account.followers_count) &&
|
||||
proxyable_software?
|
||||
end
|
||||
|
||||
def proxyable_software?
|
||||
info = instance_info
|
||||
return false if info.nil?
|
||||
|
||||
%w(misskey calckey firefish meisskey cherrypick).include?(info.software)
|
||||
end
|
||||
|
||||
def instance_info
|
||||
@instance_info ||= InstanceInfo.find_by(domain: @account.domain)
|
||||
end
|
||||
|
||||
def notify_staff_about_pending_friend_server!
|
||||
User.those_who_can(:manage_federation).includes(:account).find_each do |u|
|
||||
next unless u.allows_pending_friend_server_emails?
|
||||
|
||||
AdminMailer.with(recipient: u.account).new_pending_friend_server(friend).deliver_later
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,7 +7,7 @@ class ActivityPub::Activity::Like < ActivityPub::Activity
|
|||
def perform
|
||||
@original_status = status_from_uri(object_uri)
|
||||
|
||||
return if @original_status.nil? || delete_arrived_first?(@json['id']) || reject_favourite?
|
||||
return if @original_status.nil? || delete_arrived_first?(@json['id']) || block_domain? || reject_favourite?
|
||||
|
||||
if shortcode.nil? || !Setting.enable_emoji_reaction
|
||||
process_favourite
|
||||
|
@ -34,19 +34,11 @@ class ActivityPub::Activity::Like < ActivityPub::Activity
|
|||
def process_emoji_reaction
|
||||
return if !@original_status.account.local? && !Setting.receive_other_servers_emoji_reaction
|
||||
|
||||
# custom emoji
|
||||
emoji = nil
|
||||
if emoji_tag.present?
|
||||
return if emoji_tag['id'].blank? || emoji_tag['name'].blank? || emoji_tag['icon'].blank? || emoji_tag['icon']['url'].blank?
|
||||
|
||||
image_url = emoji_tag['icon']['url']
|
||||
uri = emoji_tag['id']
|
||||
domain = URI.split(uri)[2]
|
||||
|
||||
emoji = CustomEmoji.find_or_create_by!(shortcode: shortcode, domain: domain) do |emoji_data|
|
||||
emoji_data.uri = uri
|
||||
emoji_data.image_remote_url = image_url
|
||||
end
|
||||
|
||||
Trends.statuses.register(@original_status)
|
||||
emoji = process_emoji(emoji_tag)
|
||||
return if emoji.nil?
|
||||
end
|
||||
|
||||
reaction = nil
|
||||
|
@ -58,12 +50,14 @@ class ActivityPub::Activity::Like < ActivityPub::Activity
|
|||
reaction = @original_status.emoji_reactions.create!(account: @account, name: shortcode, custom_emoji: emoji, uri: @json['id'])
|
||||
end
|
||||
|
||||
Trends.statuses.register(@original_status)
|
||||
write_stream(reaction)
|
||||
|
||||
if @original_status.account.local?
|
||||
NotifyService.new.call(@original_status.account, :emoji_reaction, reaction)
|
||||
forward_for_emoji_reaction
|
||||
relay_for_emoji_reaction
|
||||
relay_friend_for_emoji_reaction
|
||||
end
|
||||
rescue Seahorse::Client::NetworkingError
|
||||
nil
|
||||
|
@ -83,6 +77,14 @@ class ActivityPub::Activity::Like < ActivityPub::Activity
|
|||
end
|
||||
end
|
||||
|
||||
def relay_friend_for_emoji_reaction
|
||||
return unless @json['signature'].present? && @original_status.distributable_friend?
|
||||
|
||||
ActivityPub::DeliveryWorker.push_bulk(FriendDomain.distributables.pluck(:inbox_url)) do |inbox_url|
|
||||
[Oj.dump(@json), @original_status.account.id, inbox_url]
|
||||
end
|
||||
end
|
||||
|
||||
def shortcode
|
||||
return @shortcode if defined?(@shortcode)
|
||||
|
||||
|
@ -95,6 +97,47 @@ class ActivityPub::Activity::Like < ActivityPub::Activity
|
|||
end
|
||||
end
|
||||
|
||||
def process_emoji(tag)
|
||||
custom_emoji_parser = ActivityPub::Parser::CustomEmojiParser.new(tag)
|
||||
|
||||
return if custom_emoji_parser.shortcode.blank? || custom_emoji_parser.image_remote_url.blank?
|
||||
|
||||
domain = tag['domain'] || URI.split(custom_emoji_parser.uri)[2] || @account.domain
|
||||
domain = nil if domain == Rails.configuration.x.local_domain || domain == Rails.configuration.x.web_domain
|
||||
return if domain.present? && skip_download?(domain)
|
||||
|
||||
emoji = CustomEmoji.find_by(shortcode: custom_emoji_parser.shortcode, domain: domain)
|
||||
|
||||
return emoji unless emoji.nil? ||
|
||||
custom_emoji_parser.image_remote_url != emoji.image_remote_url ||
|
||||
(custom_emoji_parser.updated_at && custom_emoji_parser.updated_at >= emoji.updated_at) ||
|
||||
custom_emoji_parser.license != emoji.license
|
||||
|
||||
begin
|
||||
emoji ||= CustomEmoji.new(
|
||||
domain: domain,
|
||||
shortcode: custom_emoji_parser.shortcode,
|
||||
uri: custom_emoji_parser.uri
|
||||
)
|
||||
emoji.image_remote_url = custom_emoji_parser.image_remote_url
|
||||
emoji.license = custom_emoji_parser.license
|
||||
emoji.is_sensitive = custom_emoji_parser.is_sensitive
|
||||
emoji.save
|
||||
rescue Seahorse::Client::NetworkingError => e
|
||||
Rails.logger.warn "Error storing emoji: #{e}"
|
||||
end
|
||||
|
||||
emoji
|
||||
end
|
||||
|
||||
def skip_download?(domain)
|
||||
DomainBlock.reject_media?(domain)
|
||||
end
|
||||
|
||||
def block_domain?
|
||||
DomainBlock.blocked?(@account.domain)
|
||||
end
|
||||
|
||||
def misskey_favourite?
|
||||
misskey_shortcode = @json['_misskey_reaction']&.delete(':')
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
class ActivityPub::Activity::Reject < ActivityPub::Activity
|
||||
def perform
|
||||
return reject_follow_for_relay if relay_follow?
|
||||
return reject_follow_for_friend if friend_follow?
|
||||
return follow_request_from_object.reject! unless follow_request_from_object.nil?
|
||||
return UnfollowService.new.call(follow_from_object.account, @account) unless follow_from_object.nil?
|
||||
|
||||
|
@ -37,6 +38,18 @@ class ActivityPub::Activity::Reject < ActivityPub::Activity
|
|||
relay.present?
|
||||
end
|
||||
|
||||
def reject_follow_for_friend
|
||||
friend.update!(active_state: :rejected, passive_state: :idle)
|
||||
end
|
||||
|
||||
def friend
|
||||
@friend ||= FriendDomain.find_by(domain: @account.domain, active_follow_activity_id: object_uri, active_state: [:pending, :accepted]) if @account.domain.present?
|
||||
end
|
||||
|
||||
def friend_follow?
|
||||
friend.present?
|
||||
end
|
||||
|
||||
def target_uri
|
||||
@target_uri ||= value_or_id(@object['actor'])
|
||||
end
|
||||
|
|
|
@ -87,6 +87,8 @@ class ActivityPub::Activity::Undo < ActivityPub::Activity
|
|||
end
|
||||
|
||||
def undo_follow
|
||||
return remove_follow_from_friend if friend_follow?
|
||||
|
||||
target_account = account_from_uri(target_uri)
|
||||
|
||||
return if target_account.nil? || !target_account.local?
|
||||
|
@ -100,6 +102,18 @@ class ActivityPub::Activity::Undo < ActivityPub::Activity
|
|||
end
|
||||
end
|
||||
|
||||
def remove_follow_from_friend
|
||||
friend.destroy_without_signal!
|
||||
end
|
||||
|
||||
def friend
|
||||
@friend ||= FriendDomain.find_by(domain: @account.domain) if @account.domain.present? && @object['object'] == ActivityPub::TagManager::COLLECTIONS[:public]
|
||||
end
|
||||
|
||||
def friend_follow?
|
||||
friend.present?
|
||||
end
|
||||
|
||||
def undo_like_original
|
||||
status = status_from_uri(target_uri)
|
||||
|
||||
|
@ -132,6 +146,7 @@ class ActivityPub::Activity::Undo < ActivityPub::Activity
|
|||
if @original_status.account.local?
|
||||
forward_for_undo_emoji_reaction
|
||||
relay_for_undo_emoji_reaction
|
||||
relay_friend_for_undo_emoji_reaction
|
||||
end
|
||||
end
|
||||
else
|
||||
|
@ -170,6 +185,14 @@ class ActivityPub::Activity::Undo < ActivityPub::Activity
|
|||
end
|
||||
end
|
||||
|
||||
def relay_friend_for_undo_emoji_reaction
|
||||
return unless @json['signature'].present? && @original_status.distributable_friend?
|
||||
|
||||
ActivityPub::DeliveryWorker.push_bulk(FriendDomain.distributables.pluck(:inbox_url)) do |inbox_url|
|
||||
[Oj.dump(@json), @original_status.account.id, inbox_url]
|
||||
end
|
||||
end
|
||||
|
||||
def shortcode
|
||||
return @shortcode if defined?(@shortcode)
|
||||
|
||||
|
|
|
@ -21,6 +21,8 @@ module ActivityPub::CaseTransform
|
|||
value
|
||||
elsif value.start_with?('_:')
|
||||
"_:#{value.delete_prefix('_:').underscore.camelize(:lower)}"
|
||||
elsif LanguagesHelper::ISO_639_1_REGIONAL.key?(value.to_sym) # rubocop:disable Lint/DuplicateBranch
|
||||
value
|
||||
else
|
||||
value.underscore.camelize(:lower)
|
||||
end
|
||||
|
|
|
@ -30,6 +30,6 @@ class ActivityPub::Parser::CustomEmojiParser
|
|||
end
|
||||
|
||||
def license
|
||||
@json['license']
|
||||
@json['license'] || @json['licence']
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,6 +11,7 @@ class ActivityPub::Parser::StatusParser
|
|||
@object = magic_values[:object] || json['object'] || json
|
||||
@magic_values = magic_values
|
||||
@account = magic_values[:account]
|
||||
@friend = magic_values[:friend_domain]
|
||||
end
|
||||
|
||||
def uri
|
||||
|
@ -76,9 +77,11 @@ class ActivityPub::Parser::StatusParser
|
|||
def visibility
|
||||
if audience_to.any? { |to| ActivityPub::TagManager.instance.public_collection?(to) }
|
||||
:public
|
||||
elsif audience_to.include?('kmyblue:LocalPublic') && @friend
|
||||
:public_unlisted
|
||||
elsif audience_cc.any? { |cc| ActivityPub::TagManager.instance.public_collection?(cc) }
|
||||
:unlisted
|
||||
elsif audience_to.include?('as:LoginOnly') || audience_to.include?('LoginUser')
|
||||
elsif audience_to.include?('kmyblue:LoginOnly') || audience_to.include?('as:LoginOnly') || audience_to.include?('LoginUser')
|
||||
:login
|
||||
elsif audience_to.include?(@magic_values[:followers_collection])
|
||||
:private
|
||||
|
@ -196,8 +199,10 @@ class ActivityPub::Parser::StatusParser
|
|||
nil
|
||||
elsif audience_searchable_by.any? { |uri| ActivityPub::TagManager.instance.public_collection?(uri) }
|
||||
:public
|
||||
elsif audience_searchable_by.include?('as:Limited')
|
||||
elsif audience_searchable_by.include?('kmyblue:Limited') || audience_searchable_by.include?('as:Limited')
|
||||
:limited
|
||||
elsif audience_searchable_by.include?('kmyblue:LocalPublic') && @friend
|
||||
:public_unlisted
|
||||
elsif audience_searchable_by.include?(@account.followers_url)
|
||||
:private
|
||||
else
|
||||
|
|
|
@ -99,7 +99,7 @@ class ActivityPub::TagManager
|
|||
when 'unlisted', 'public_unlisted', 'private'
|
||||
[account_followers_url(status.account)]
|
||||
when 'login'
|
||||
[account_followers_url(status.account), 'as:LoginOnly', 'LoginUser']
|
||||
[account_followers_url(status.account), 'as:LoginOnly', 'kmyblue:LoginOnly', 'LoginUser']
|
||||
when 'direct'
|
||||
if status.account.silenced?
|
||||
# Only notify followers if the account is locally silenced
|
||||
|
@ -126,6 +126,12 @@ class ActivityPub::TagManager
|
|||
end
|
||||
end
|
||||
|
||||
def to_for_friend(status)
|
||||
to = to(status)
|
||||
to << 'kmyblue:LocalPublic' if status.public_unlisted_visibility?
|
||||
to
|
||||
end
|
||||
|
||||
# Secondary audience of a status
|
||||
# Public statuses go out to followers as well
|
||||
# Unlisted statuses go to the public as well
|
||||
|
@ -147,7 +153,7 @@ class ActivityPub::TagManager
|
|||
end
|
||||
|
||||
def cc_for_misskey(status)
|
||||
if (status.account.user&.setting_reject_unlisted_subscription && status.visibility == 'unlisted') || (status.account.user&.setting_reject_public_unlisted_subscription && status.visibility == 'public_unlisted')
|
||||
if (status.account.user&.setting_reject_unlisted_subscription && status.unlisted_visibility?) || (status.account.user&.setting_reject_public_unlisted_subscription && status.public_unlisted_visibility?)
|
||||
cc = cc_private_visibility(status)
|
||||
cc << uri_for(status.reblog.account) if status.reblog?
|
||||
return cc
|
||||
|
@ -243,7 +249,7 @@ class ActivityPub::TagManager
|
|||
when 'direct'
|
||||
status.conversation_id.present? ? [uri_for(status.conversation)] : []
|
||||
when 'limited'
|
||||
['as:Limited']
|
||||
['as:Limited', 'kmyblue:Limited']
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
@ -251,6 +257,12 @@ class ActivityPub::TagManager
|
|||
searchable_by.concat(mentions_uris(status)).compact
|
||||
end
|
||||
|
||||
def searchable_by_for_friend(status)
|
||||
searchable = searchable_by(status)
|
||||
searchable << 'kmyblue:LocalPublic' if status.compute_searchability_local == 'public_unlisted'
|
||||
searchable
|
||||
end
|
||||
|
||||
def account_searchable_by(account)
|
||||
case account.compute_searchability_activitypub
|
||||
when 'public'
|
||||
|
@ -258,7 +270,7 @@ class ActivityPub::TagManager
|
|||
when 'private', 'direct'
|
||||
[account_followers_url(account)]
|
||||
when 'limited'
|
||||
['as:Limited']
|
||||
['as:Limited', 'kmyblue:Limited']
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
|
|
@ -25,7 +25,8 @@ class Admin::Metrics::Dimension::SoftwareVersionsDimension < Admin::Metrics::Dim
|
|||
end
|
||||
|
||||
def ruby_version
|
||||
value = "#{RUBY_VERSION}p#{RUBY_PATCHLEVEL}"
|
||||
yjit = defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled?
|
||||
value = "#{RUBY_VERSION}p#{RUBY_PATCHLEVEL}#{yjit ? ' +YJIT' : ''}"
|
||||
|
||||
{
|
||||
key: 'ruby',
|
||||
|
|
|
@ -11,7 +11,7 @@ class Admin::Metrics::Dimension::SpaceUsageDimension < Admin::Metrics::Dimension
|
|||
protected
|
||||
|
||||
def perform_query
|
||||
[postgresql_size, redis_size, media_size]
|
||||
[postgresql_size, redis_size, media_size, search_size].compact
|
||||
end
|
||||
|
||||
def postgresql_size
|
||||
|
@ -65,4 +65,22 @@ class Admin::Metrics::Dimension::SpaceUsageDimension < Admin::Metrics::Dimension
|
|||
redis.info
|
||||
end
|
||||
end
|
||||
|
||||
def search_size
|
||||
return unless Chewy.enabled?
|
||||
|
||||
client_info = Chewy.client.info
|
||||
|
||||
value = Chewy.client.indices.stats['indices'].values.sum { |index_data| index_data['primaries']['store']['size_in_bytes'] }
|
||||
|
||||
{
|
||||
key: 'search',
|
||||
human_key: client_info.dig('version', 'distribution') == 'opensearch' ? 'OpenSearch' : 'Elasticsearch',
|
||||
value: value.to_s,
|
||||
unit: 'bytes',
|
||||
human_value: number_to_human_size(value),
|
||||
}
|
||||
rescue Faraday::ConnectionFailed, Elasticsearch::Transport::Transport::Error
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
|
|
@ -76,14 +76,35 @@ class Admin::SystemCheck::ElasticsearchCheck < Admin::SystemCheck::BaseCheck
|
|||
end
|
||||
|
||||
def compatible_version?
|
||||
return false if running_version.nil?
|
||||
|
||||
Gem::Version.new(running_version) >= Gem::Version.new(required_version) ||
|
||||
Gem::Version.new(compatible_wire_version) >= Gem::Version.new(required_version)
|
||||
running_version_ok? || compatible_wire_version_ok?
|
||||
rescue ArgumentError
|
||||
false
|
||||
end
|
||||
|
||||
def running_version_ok?
|
||||
return false if running_version.blank?
|
||||
|
||||
gem_version_running >= gem_version_required
|
||||
end
|
||||
|
||||
def compatible_wire_version_ok?
|
||||
return false if compatible_wire_version.blank?
|
||||
|
||||
gem_version_compatible_wire >= gem_version_required
|
||||
end
|
||||
|
||||
def gem_version_running
|
||||
Gem::Version.new(running_version)
|
||||
end
|
||||
|
||||
def gem_version_required
|
||||
Gem::Version.new(required_version)
|
||||
end
|
||||
|
||||
def gem_version_compatible_wire
|
||||
Gem::Version.new(compatible_wire_version)
|
||||
end
|
||||
|
||||
def mismatched_indexes
|
||||
@mismatched_indexes ||= INDEXES.filter_map do |klass|
|
||||
klass.base_name if Chewy.client.indices.get_mapping[klass.index_name]&.deep_symbolize_keys != klass.mappings_hash
|
||||
|
|
|
@ -58,7 +58,7 @@ class FeedManager
|
|||
# @param [Boolean] update
|
||||
# @return [Boolean]
|
||||
def push_to_home(account, status, update: false)
|
||||
return false unless add_to_feed(:home, account.id, status, aggregate_reblogs: account.user&.aggregates_reblogs?)
|
||||
return false unless add_to_feed(:home, account.id, status, aggregate_reblogs: account.user&.aggregates_reblogs?, update: update)
|
||||
|
||||
trim(:home, account.id)
|
||||
PushUpdateWorker.perform_async(account.id, status.id, "timeline:#{account.id}", { 'update' => update }) if push_update_required?("timeline:#{account.id}")
|
||||
|
@ -83,7 +83,7 @@ class FeedManager
|
|||
# @param [Boolean] update
|
||||
# @return [Boolean]
|
||||
def push_to_list(list, status, update: false)
|
||||
return false if filter_from_list?(status, list) || !add_to_feed(:list, list.id, status, aggregate_reblogs: list.account.user&.aggregates_reblogs?)
|
||||
return false if filter_from_list?(status, list) || !add_to_feed(:list, list.id, status, aggregate_reblogs: list.account.user&.aggregates_reblogs?, update: update)
|
||||
|
||||
trim(:list, list.id)
|
||||
PushUpdateWorker.perform_async(list.account_id, status.id, "timeline:list:#{list.id}", { 'update' => update }) if push_update_required?("timeline:list:#{list.id}")
|
||||
|
@ -91,7 +91,7 @@ class FeedManager
|
|||
end
|
||||
|
||||
def push_to_antenna(antenna, status, update: false)
|
||||
return false unless add_to_feed(:antenna, antenna.id, status, aggregate_reblogs: antenna.account.user&.aggregates_reblogs?)
|
||||
return false unless add_to_feed(:antenna, antenna.id, status, aggregate_reblogs: antenna.account.user&.aggregates_reblogs?, update: update)
|
||||
|
||||
trim(:antenna, antenna.id)
|
||||
PushUpdateWorker.perform_async(antenna.account_id, status.id, "timeline:antenna:#{antenna.id}", { 'update' => update }) if push_update_required?("timeline:antenna:#{antenna.id}")
|
||||
|
@ -497,7 +497,9 @@ class FeedManager
|
|||
# @param [Status] status
|
||||
# @param [Boolean] aggregate_reblogs
|
||||
# @return [Boolean]
|
||||
def add_to_feed(timeline_type, account_id, status, aggregate_reblogs: true)
|
||||
def add_to_feed(timeline_type, account_id, status, aggregate_reblogs: true, update: false)
|
||||
return true if update
|
||||
|
||||
timeline_key = key(timeline_type, account_id)
|
||||
reblog_key = key(timeline_type, account_id, 'reblogs')
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ class Importer::StatusesIndexImporter < Importer::BaseImporter
|
|||
to_index.map do |object|
|
||||
# This is unlikely to happen, but the post may have been
|
||||
# un-interacted with since it was queued for indexing
|
||||
if object.searchable_by.empty? && %w(public private).exclude?(object.searchability)
|
||||
if object.searchable_by.empty? && %w(public public_unlisted private).exclude?(object.searchability)
|
||||
deleted += 1
|
||||
{ delete: { _id: object.id } }
|
||||
else
|
||||
|
|
|
@ -89,10 +89,10 @@ class SearchQueryTransformer < Parslet::Transform
|
|||
public_index,
|
||||
searchability_limited,
|
||||
]
|
||||
definition_should << searchability_public if %i(public).include?(@searchability)
|
||||
definition_should << searchability_private if %i(public unlisted private).include?(@searchability)
|
||||
definition_should << searchable_by_me if %i(public unlisted private direct).include?(@searchability)
|
||||
definition_should << self_posts if %i(public unlisted private direct).exclude?(@searchability)
|
||||
definition_should << searchability_public if %i(public public_unlisted).include?(@searchability)
|
||||
definition_should << searchability_private if %i(public public_unlisted unlisted private).include?(@searchability)
|
||||
definition_should << searchable_by_me if %i(public public_unlisted unlisted private direct).include?(@searchability)
|
||||
definition_should << self_posts if %i(public public_unlisted unlisted private direct).exclude?(@searchability)
|
||||
|
||||
{
|
||||
bool: {
|
||||
|
@ -199,8 +199,8 @@ class SearchQueryTransformer < Parslet::Transform
|
|||
def following_account_ids
|
||||
return @following_account_ids if defined?(@following_account_ids)
|
||||
|
||||
account_exists_sql = Account.where('accounts.id = follows.target_account_id').where(searchability: %w(public private)).reorder(nil).select(1).to_sql
|
||||
status_exists_sql = Status.where('statuses.account_id = follows.target_account_id').where(reblog_of_id: nil).where(searchability: %w(public private)).reorder(nil).select(1).to_sql
|
||||
account_exists_sql = Account.where('accounts.id = follows.target_account_id').where(searchability: %w(public public_unlisted private)).reorder(nil).select(1).to_sql
|
||||
status_exists_sql = Status.where('statuses.account_id = follows.target_account_id').where(reblog_of_id: nil).where(searchability: %w(public public_unlisted private)).reorder(nil).select(1).to_sql
|
||||
following_accounts = Follow.where(account_id: @options[:current_account].id).merge(Account.where("EXISTS (#{account_exists_sql})").or(Account.where("EXISTS (#{status_exists_sql})")))
|
||||
@following_account_ids = following_accounts.pluck(:target_account_id)
|
||||
end
|
||||
|
|
|
@ -10,7 +10,7 @@ class StatusReachFinder
|
|||
end
|
||||
|
||||
def inboxes
|
||||
(reached_account_inboxes + followers_inboxes + relay_inboxes).uniq
|
||||
(reached_account_inboxes + followers_inboxes + relay_inboxes + nolocal_friend_inboxes).uniq
|
||||
end
|
||||
|
||||
def inboxes_for_misskey
|
||||
|
@ -21,6 +21,10 @@ class StatusReachFinder
|
|||
end
|
||||
end
|
||||
|
||||
def inboxes_for_friend
|
||||
(reached_account_inboxes_for_friend + followers_inboxes_for_friend + friend_inboxes).uniq
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def reached_account_inboxes
|
||||
|
@ -32,7 +36,7 @@ class StatusReachFinder
|
|||
elsif @status.limited_visibility?
|
||||
Account.where(id: mentioned_account_ids).where.not(domain: banned_domains).inboxes
|
||||
else
|
||||
Account.where(id: reached_account_ids).where.not(domain: banned_domains).inboxes
|
||||
Account.where(id: reached_account_ids).where.not(domain: banned_domains + friend_domains).inboxes
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -42,7 +46,17 @@ class StatusReachFinder
|
|||
elsif @status.limited_visibility?
|
||||
Account.where(id: mentioned_account_ids).where(domain: banned_domains_for_misskey).inboxes
|
||||
else
|
||||
Account.where(id: reached_account_ids).where(domain: banned_domains_for_misskey).inboxes
|
||||
Account.where(id: reached_account_ids).where(domain: banned_domains_for_misskey - friend_domains).inboxes
|
||||
end
|
||||
end
|
||||
|
||||
def reached_account_inboxes_for_friend
|
||||
if @status.reblog?
|
||||
[]
|
||||
elsif @status.limited_visibility?
|
||||
Account.where(id: mentioned_account_ids).where.not(domain: banned_domains).inboxes
|
||||
else
|
||||
Account.where(id: reached_account_ids, domain: friend_domains).where.not(domain: banned_domains - friend_domains).inboxes
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -54,6 +68,7 @@ class StatusReachFinder
|
|||
reblogs_account_ids,
|
||||
favourites_account_ids,
|
||||
replies_account_ids,
|
||||
quoted_account_id,
|
||||
].tap do |arr|
|
||||
arr.flatten!
|
||||
arr.compact!
|
||||
|
@ -88,23 +103,37 @@ class StatusReachFinder
|
|||
@status.replies.pluck(:account_id) if distributable? || unsafe?
|
||||
end
|
||||
|
||||
def quoted_account_id
|
||||
@status.quote.account_id if @status.quote?
|
||||
end
|
||||
|
||||
def followers_inboxes
|
||||
if @status.in_reply_to_local_account? && distributable?
|
||||
@status.account.followers.or(@status.thread.account.followers.not_domain_blocked_by_account(@status.account)).where.not(domain: banned_domains).inboxes
|
||||
@status.account.followers.or(@status.thread.account.followers.not_domain_blocked_by_account(@status.account)).where.not(domain: banned_domains + friend_domains).inboxes
|
||||
elsif @status.direct_visibility? || @status.limited_visibility?
|
||||
[]
|
||||
else
|
||||
@status.account.followers.where.not(domain: banned_domains).inboxes
|
||||
@status.account.followers.where.not(domain: banned_domains + friend_domains).inboxes
|
||||
end
|
||||
end
|
||||
|
||||
def followers_inboxes_for_misskey
|
||||
if @status.in_reply_to_local_account? && distributable?
|
||||
@status.account.followers.or(@status.thread.account.followers.not_domain_blocked_by_account(@status.account)).where(domain: banned_domains_for_misskey).inboxes
|
||||
@status.account.followers.or(@status.thread.account.followers.not_domain_blocked_by_account(@status.account)).where(domain: banned_domains_for_misskey - friend_domains).inboxes
|
||||
elsif @status.direct_visibility? || @status.limited_visibility?
|
||||
[]
|
||||
else
|
||||
@status.account.followers.where(domain: banned_domains_for_misskey).inboxes
|
||||
@status.account.followers.where(domain: banned_domains_for_misskey - friend_domains).inboxes
|
||||
end
|
||||
end
|
||||
|
||||
def followers_inboxes_for_friend
|
||||
if @status.in_reply_to_local_account? && distributable?
|
||||
@status.account.followers.or(@status.thread.account.followers.not_domain_blocked_by_account(@status.account)).where(domain: friend_domains).inboxes
|
||||
elsif @status.direct_visibility? || @status.limited_visibility?
|
||||
[]
|
||||
else
|
||||
@status.account.followers.where(domain: friend_domains).inboxes
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -116,6 +145,22 @@ class StatusReachFinder
|
|||
end
|
||||
end
|
||||
|
||||
def friend_inboxes
|
||||
if @status.public_visibility? || @status.public_unlisted_visibility? || (@status.unlisted_visibility? && (@status.public_searchability? || @status.public_unlisted_searchability?))
|
||||
DeliveryFailureTracker.without_unavailable(FriendDomain.distributables.where(delivery_local: true).pluck(:inbox_url))
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def nolocal_friend_inboxes
|
||||
if @status.public_visibility?
|
||||
DeliveryFailureTracker.without_unavailable(FriendDomain.distributables.where(delivery_local: false).pluck(:inbox_url))
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def distributable?
|
||||
@status.public_visibility? || @status.unlisted_visibility? || @status.public_unlisted_visibility?
|
||||
end
|
||||
|
@ -124,6 +169,13 @@ class StatusReachFinder
|
|||
@options[:unsafe]
|
||||
end
|
||||
|
||||
def friend_domains
|
||||
return @friend_domains if defined?(@friend_domains)
|
||||
|
||||
@friend_domains = FriendDomain.deliver_locals.pluck(:domain)
|
||||
@friend_domains -= UnavailableDomain.where(domain: @friend_domains).pluck(:domain)
|
||||
end
|
||||
|
||||
def banned_domains
|
||||
return @banned_domains if @banned_domains
|
||||
|
||||
|
|
|
@ -35,6 +35,14 @@ class AdminMailer < ApplicationMailer
|
|||
end
|
||||
end
|
||||
|
||||
def new_pending_friend_server(friend_server)
|
||||
@friend = friend_server
|
||||
|
||||
locale_for_account(@me) do
|
||||
mail subject: default_i18n_subject(instance: @instance, domain: @friend.domain)
|
||||
end
|
||||
end
|
||||
|
||||
def new_trends(links, tags, statuses)
|
||||
@links = links
|
||||
@tags = tags
|
||||
|
|
|
@ -70,7 +70,7 @@ class Account < ApplicationRecord
|
|||
BACKGROUND_REFRESH_INTERVAL = 1.week.freeze
|
||||
|
||||
USERNAME_RE = /[a-z0-9_]+([a-z0-9_.-]+[a-z0-9_]+)?/i
|
||||
MENTION_RE = %r{(?<=^|[^/[:word:]])@((#{USERNAME_RE})(?:@[[:word:].-]+[[:word:]]+)?)}i
|
||||
MENTION_RE = %r{(?<![=/[:word:]])@((#{USERNAME_RE})(?:@[[:word:].-]+[[:word:]]+)?)}i
|
||||
URL_PREFIX_RE = %r{\Ahttp(s?)://[^/]+}
|
||||
USERNAME_ONLY_RE = /\A#{USERNAME_RE}\z/i
|
||||
|
||||
|
@ -132,7 +132,7 @@ class Account < ApplicationRecord
|
|||
scope :searchable, -> { without_unapproved.without_suspended.where(moved_to_account_id: nil) }
|
||||
scope :discoverable, -> { searchable.without_silenced.where(discoverable: true).joins(:account_stat) }
|
||||
scope :followable_by, ->(account) { joins(arel_table.join(Follow.arel_table, Arel::Nodes::OuterJoin).on(arel_table[:id].eq(Follow.arel_table[:target_account_id]).and(Follow.arel_table[:account_id].eq(account.id))).join_sources).where(Follow.arel_table[:id].eq(nil)).joins(arel_table.join(FollowRequest.arel_table, Arel::Nodes::OuterJoin).on(arel_table[:id].eq(FollowRequest.arel_table[:target_account_id]).and(FollowRequest.arel_table[:account_id].eq(account.id))).join_sources).where(FollowRequest.arel_table[:id].eq(nil)) }
|
||||
scope :by_recent_status, -> { order(Arel.sql('account_stats.last_status_at DESC NULLS LAST')) }
|
||||
scope :by_recent_status, -> { includes(:account_stat).merge(AccountStat.order('last_status_at DESC NULLS LAST')).references(:account_stat) }
|
||||
scope :by_recent_sign_in, -> { order(Arel.sql('users.current_sign_in_at DESC NULLS LAST')) }
|
||||
scope :popular, -> { order('account_stats.followers_count desc') }
|
||||
scope :by_domain_and_subdomains, ->(domain) { where(domain: Instance.by_domain_and_subdomains(domain).select(:domain)) }
|
||||
|
@ -330,6 +330,13 @@ class Account < ApplicationRecord
|
|||
true
|
||||
end
|
||||
|
||||
def allow_quote?
|
||||
return user.setting_allow_quote if local? && user.present?
|
||||
return settings['allow_quote'] if settings.present? && settings.key?('allow_quote')
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def public_statuses_count
|
||||
hide_statuses_count? ? 0 : statuses_count
|
||||
end
|
||||
|
@ -407,6 +414,7 @@ class Account < ApplicationRecord
|
|||
'hide_followers_count' => hide_followers_count?,
|
||||
'translatable_private' => translatable_private?,
|
||||
'link_preview' => link_preview?,
|
||||
'allow_quote' => allow_quote?,
|
||||
}
|
||||
if Setting.enable_emoji_reaction
|
||||
config = config.merge({
|
||||
|
|
|
@ -111,6 +111,22 @@ module HasUserSettings
|
|||
settings['web.use_system_font']
|
||||
end
|
||||
|
||||
def setting_show_quote_in_home
|
||||
settings['web.show_quote_in_home']
|
||||
end
|
||||
|
||||
def setting_show_quote_in_public
|
||||
settings['web.show_quote_in_public']
|
||||
end
|
||||
|
||||
def setting_hide_blocking_quote
|
||||
settings['web.hide_blocking_quote']
|
||||
end
|
||||
|
||||
def setting_allow_quote
|
||||
settings['allow_quote']
|
||||
end
|
||||
|
||||
def setting_noindex
|
||||
settings['noindex']
|
||||
end
|
||||
|
@ -127,10 +143,6 @@ module HasUserSettings
|
|||
settings['link_preview']
|
||||
end
|
||||
|
||||
def setting_single_ref_to_quote
|
||||
settings['single_ref_to_quote']
|
||||
end
|
||||
|
||||
def setting_dtl_force_with_tag
|
||||
settings['dtl_force_with_tag']&.to_sym || :none
|
||||
end
|
||||
|
@ -251,6 +263,10 @@ module HasUserSettings
|
|||
settings['notification_emails.pending_account']
|
||||
end
|
||||
|
||||
def allows_pending_friend_server_emails?
|
||||
settings['notification_emails.pending_friend_server']
|
||||
end
|
||||
|
||||
def allows_appeal_emails?
|
||||
settings['notification_emails.appeal']
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@ module StatusSearchConcern
|
|||
|
||||
included do
|
||||
scope :indexable, -> { without_reblogs.where(visibility: [:public, :login], searchability: nil).joins(:account).where(account: { indexable: true }) }
|
||||
scope :remote_dynamic_searchability, -> { remote.where(searchability: [:public, :private]) }
|
||||
scope :remote_dynamic_searchability, -> { remote.where(searchability: [:public, :public_unlisted, :private]) }
|
||||
end
|
||||
|
||||
def searchable_by
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
# action :integer default("warn"), not null
|
||||
# exclude_follows :boolean default(FALSE), not null
|
||||
# exclude_localusers :boolean default(FALSE), not null
|
||||
# with_quote :boolean default(TRUE), not null
|
||||
#
|
||||
|
||||
class CustomFilter < ApplicationRecord
|
||||
|
@ -33,7 +34,7 @@ class CustomFilter < ApplicationRecord
|
|||
include Expireable
|
||||
include Redisable
|
||||
|
||||
enum action: { warn: 0, hide: 1 }, _suffix: :action
|
||||
enum action: { warn: 0, hide: 1, half_warn: 2 }, _suffix: :action
|
||||
|
||||
belongs_to :account
|
||||
has_many :keywords, class_name: 'CustomFilterKeyword', inverse_of: :custom_filter, dependent: :destroy
|
||||
|
@ -103,11 +104,15 @@ class CustomFilter < ApplicationRecord
|
|||
|
||||
if rules[:keywords].present?
|
||||
match = rules[:keywords].match(status.proper.searchable_text)
|
||||
match = rules[:keywords].match(status.proper.references.pluck(:text).join("\n\n")) if match.nil? && status.proper.references.exists?
|
||||
if match.nil? && filter.with_quote && status.proper.references.exists?
|
||||
match = rules[:keywords].match(status.proper.references.pluck(:text).join("\n\n"))
|
||||
match = rules[:keywords].match(status.proper.references.pluck(:spoiler_text).join("\n\n")) if match.nil?
|
||||
end
|
||||
end
|
||||
keyword_matches = [match.to_s] unless match.nil?
|
||||
|
||||
status_matches = [status.id, status.reblog_of_id].compact & rules[:status_ids] if rules[:status_ids].present?
|
||||
reference_ids = filter.with_quote ? status.proper.references.pluck(:id) : []
|
||||
status_matches = ([status.id, status.reblog_of_id] + reference_ids).compact & rules[:status_ids] if rules[:status_ids].present?
|
||||
|
||||
next if keyword_matches.blank? && status_matches.blank?
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
# hidden_anonymous :boolean default(FALSE), not null
|
||||
# detect_invalid_subscription :boolean default(FALSE), not null
|
||||
# reject_reply_exclude_followers :boolean default(FALSE), not null
|
||||
# reject_friend :boolean default(FALSE), not null
|
||||
#
|
||||
|
||||
class DomainBlock < ApplicationRecord
|
||||
|
@ -44,7 +45,16 @@ class DomainBlock < ApplicationRecord
|
|||
|
||||
scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
|
||||
scope :with_user_facing_limitations, -> { where(hidden: false) }
|
||||
scope :with_limitations, -> { where(severity: [:silence, :suspend]).or(where(reject_media: true)).or(where(reject_favourite: true)).or(where(reject_reply: true)).or(where(reject_reply_exclude_followers: true)).or(where(reject_new_follow: true)).or(where(reject_straight_follow: true)) }
|
||||
scope :with_limitations, lambda {
|
||||
where(severity: [:silence, :suspend])
|
||||
.or(where(reject_media: true))
|
||||
.or(where(reject_favourite: true))
|
||||
.or(where(reject_reply: true))
|
||||
.or(where(reject_reply_exclude_followers: true))
|
||||
.or(where(reject_new_follow: true))
|
||||
.or(where(reject_straight_follow: true))
|
||||
.or(where(reject_friend: true))
|
||||
}
|
||||
scope :by_severity, -> { order(Arel.sql('(CASE severity WHEN 0 THEN 1 WHEN 1 THEN 2 WHEN 2 THEN 0 END), domain')) }
|
||||
|
||||
def to_log_human_identifier
|
||||
|
@ -68,6 +78,7 @@ class DomainBlock < ApplicationRecord
|
|||
reject_hashtag? ? :reject_hashtag : nil,
|
||||
reject_straight_follow? ? :reject_straight_follow : nil,
|
||||
reject_new_follow? ? :reject_new_follow : nil,
|
||||
reject_friend? ? :reject_friend : nil,
|
||||
detect_invalid_subscription? ? :detect_invalid_subscription : nil,
|
||||
reject_reports? ? :reject_reports : nil].reject { |policy| policy == :noop || policy.nil? }
|
||||
end
|
||||
|
@ -110,6 +121,10 @@ class DomainBlock < ApplicationRecord
|
|||
!!rule_for(domain)&.reject_new_follow?
|
||||
end
|
||||
|
||||
def reject_friend?(domain)
|
||||
!!rule_for(domain)&.reject_friend?
|
||||
end
|
||||
|
||||
def detect_invalid_subscription?(domain)
|
||||
!!rule_for(domain)&.detect_invalid_subscription?
|
||||
end
|
||||
|
|
|
@ -43,6 +43,10 @@ class EmojiReaction < ApplicationRecord
|
|||
custom_emoji? && !custom_emoji.local?
|
||||
end
|
||||
|
||||
def sign?
|
||||
status&.distributable_friend?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def refresh_cache
|
||||
|
|
|
@ -48,6 +48,7 @@ class Form::AdminSettings
|
|||
enable_emoji_reaction
|
||||
check_lts_version_only
|
||||
enable_public_unlisted_visibility
|
||||
unlocked_friend
|
||||
).freeze
|
||||
|
||||
INTEGER_KEYS = %i(
|
||||
|
@ -76,6 +77,7 @@ class Form::AdminSettings
|
|||
enable_emoji_reaction
|
||||
check_lts_version_only
|
||||
enable_public_unlisted_visibility
|
||||
unlocked_friend
|
||||
).freeze
|
||||
|
||||
UPLOAD_KEYS = %i(
|
||||
|
|
|
@ -43,14 +43,14 @@ class Form::Import
|
|||
validate :validate_data
|
||||
|
||||
def guessed_type
|
||||
return :muting if csv_data.headers.include?('Hide notifications')
|
||||
return :following if csv_data.headers.include?('Show boosts') || csv_data.headers.include?('Notify on new posts') || csv_data.headers.include?('Languages')
|
||||
return :following if data.original_filename&.start_with?('follows') || data.original_filename&.start_with?('following_accounts')
|
||||
return :blocking if data.original_filename&.start_with?('blocks') || data.original_filename&.start_with?('blocked_accounts')
|
||||
return :muting if data.original_filename&.start_with?('mutes') || data.original_filename&.start_with?('muted_accounts')
|
||||
return :domain_blocking if data.original_filename&.start_with?('domain_blocks') || data.original_filename&.start_with?('blocked_domains')
|
||||
return :bookmarks if data.original_filename&.start_with?('bookmarks')
|
||||
return :lists if data.original_filename&.start_with?('lists')
|
||||
return :muting if csv_headers_match?('Hide notifications')
|
||||
return :following if csv_headers_match?('Show boosts') || csv_headers_match?('Notify on new posts') || csv_headers_match?('Languages')
|
||||
return :following if file_name_matches?('follows') || file_name_matches?('following_accounts')
|
||||
return :blocking if file_name_matches?('blocks') || file_name_matches?('blocked_accounts')
|
||||
return :muting if file_name_matches?('mutes') || file_name_matches?('muted_accounts')
|
||||
return :domain_blocking if file_name_matches?('domain_blocks') || file_name_matches?('blocked_domains')
|
||||
return :bookmarks if file_name_matches?('bookmarks')
|
||||
return :lists if file_name_matches?('lists')
|
||||
end
|
||||
|
||||
# Whether the uploaded CSV file seems to correspond to a different import type than the one selected
|
||||
|
@ -79,6 +79,14 @@ class Form::Import
|
|||
|
||||
private
|
||||
|
||||
def file_name_matches?(string)
|
||||
data.original_filename&.start_with?(string)
|
||||
end
|
||||
|
||||
def csv_headers_match?(string)
|
||||
csv_data.headers.include?(string)
|
||||
end
|
||||
|
||||
def default_csv_headers
|
||||
case type.to_sym
|
||||
when :following, :blocking, :muting
|
||||
|
|
180
app/models/friend_domain.rb
Normal file
180
app/models/friend_domain.rb
Normal file
|
@ -0,0 +1,180 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: friend_domains
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# domain :string default(""), not null
|
||||
# inbox_url :string default(""), not null
|
||||
# active_state :integer default("idle"), not null
|
||||
# passive_state :integer default("idle"), not null
|
||||
# active_follow_activity_id :string
|
||||
# passive_follow_activity_id :string
|
||||
# available :boolean default(TRUE), not null
|
||||
# pseudo_relay :boolean default(FALSE), not null
|
||||
# allow_all_posts :boolean default(TRUE), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# delivery_local :boolean default(TRUE), not null
|
||||
#
|
||||
|
||||
class FriendDomain < ApplicationRecord
|
||||
validates :domain, presence: true, uniqueness: true, if: :will_save_change_to_domain?
|
||||
validates :inbox_url, presence: true, uniqueness: true, if: :will_save_change_to_inbox_url?
|
||||
|
||||
enum active_state: { idle: 0, pending: 1, accepted: 2, rejected: 3 }, _prefix: :i_am
|
||||
enum passive_state: { idle: 0, pending: 1, accepted: 2, rejected: 3 }, _prefix: :they_are
|
||||
|
||||
scope :by_domain_and_subdomains, ->(domain) { where(domain: Instance.by_domain_and_subdomains(domain).select(:domain)) }
|
||||
scope :enabled, -> { where(active_state: :accepted).or(FriendDomain.where(passive_state: :accepted)).where(available: true) }
|
||||
scope :distributables, -> { enabled.where(pseudo_relay: true) }
|
||||
scope :deliver_locals, -> { enabled.where(delivery_local: true) }
|
||||
scope :free_receivings, -> { enabled.where(allow_all_posts: true) }
|
||||
|
||||
before_destroy :ensure_disabled
|
||||
after_commit :set_default_inbox_url
|
||||
|
||||
def accepted?
|
||||
i_am_accepted? || they_are_accepted?
|
||||
end
|
||||
|
||||
def pending?
|
||||
!accepted? && (i_am_pending? || they_are_pending?)
|
||||
end
|
||||
|
||||
def idle?
|
||||
(i_am_idle? || i_am_rejected?) && (they_are_idle? || they_are_rejected?)
|
||||
end
|
||||
|
||||
def follow!
|
||||
activity_id = ActivityPub::TagManager.instance.generate_uri_for(nil)
|
||||
payload = Oj.dump(follow_activity(activity_id))
|
||||
|
||||
update!(active_state: :pending, passive_state: :idle, active_follow_activity_id: activity_id)
|
||||
DeliveryFailureTracker.reset!(inbox_url)
|
||||
ActivityPub::DeliveryWorker.perform_async(payload, some_local_account.id, inbox_url)
|
||||
end
|
||||
|
||||
def unfollow!
|
||||
activity_id = ActivityPub::TagManager.instance.generate_uri_for(nil)
|
||||
payload = Oj.dump(unfollow_activity(activity_id))
|
||||
|
||||
update!(active_state: :idle, passive_state: :idle, active_follow_activity_id: nil)
|
||||
DeliveryFailureTracker.reset!(inbox_url)
|
||||
ActivityPub::DeliveryWorker.perform_async(payload, some_local_account.id, inbox_url)
|
||||
end
|
||||
|
||||
def accept!
|
||||
return if they_are_idle?
|
||||
|
||||
activity_id = passive_follow_activity_id
|
||||
payload = Oj.dump(accept_follow_activity(activity_id))
|
||||
|
||||
update!(passive_state: :accepted, active_state: :idle)
|
||||
DeliveryFailureTracker.reset!(inbox_url)
|
||||
ActivityPub::DeliveryWorker.perform_async(payload, some_local_account.id, inbox_url)
|
||||
end
|
||||
|
||||
def reject!
|
||||
return if they_are_idle?
|
||||
|
||||
activity_id = passive_follow_activity_id
|
||||
payload = Oj.dump(reject_follow_activity(activity_id))
|
||||
|
||||
update!(passive_state: :rejected, active_state: :idle, passive_follow_activity_id: nil)
|
||||
DeliveryFailureTracker.reset!(inbox_url)
|
||||
ActivityPub::DeliveryWorker.perform_async(payload, some_local_account.id, inbox_url)
|
||||
end
|
||||
|
||||
def destroy_without_signal!
|
||||
self.active_state = :idle
|
||||
self.passive_state = :idle
|
||||
destroy!
|
||||
end
|
||||
|
||||
def initialize_inbox_url!
|
||||
self.inbox_url = default_inbox_url
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def default_inbox_url
|
||||
"https://#{domain}/inbox"
|
||||
end
|
||||
|
||||
def delete_for_friend!
|
||||
activity_id = ActivityPub::TagManager.instance.generate_uri_for(nil)
|
||||
payload = Oj.dump(delete_follow_activity(activity_id))
|
||||
|
||||
DeliveryFailureTracker.reset!(inbox_url)
|
||||
ActivityPub::DeliveryWorker.perform_async(payload, some_local_account.id, inbox_url)
|
||||
end
|
||||
|
||||
def follow_activity(activity_id)
|
||||
{
|
||||
'@context': ActivityPub::TagManager::CONTEXT,
|
||||
id: activity_id,
|
||||
type: 'Follow',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(some_local_account),
|
||||
object: ActivityPub::TagManager::COLLECTIONS[:public],
|
||||
}
|
||||
end
|
||||
|
||||
def unfollow_activity(activity_id)
|
||||
{
|
||||
'@context': ActivityPub::TagManager::CONTEXT,
|
||||
id: activity_id,
|
||||
type: 'Undo',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(some_local_account),
|
||||
object: {
|
||||
id: active_follow_activity_id,
|
||||
type: 'Follow',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(some_local_account),
|
||||
object: ActivityPub::TagManager::COLLECTIONS[:public],
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
def accept_follow_activity(activity_id)
|
||||
{
|
||||
'@context': ActivityPub::TagManager::CONTEXT,
|
||||
id: "#{activity_id}#accepts/friends",
|
||||
type: 'Accept',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(some_local_account),
|
||||
object: activity_id,
|
||||
}
|
||||
end
|
||||
|
||||
def reject_follow_activity(activity_id)
|
||||
{
|
||||
'@context': ActivityPub::TagManager::CONTEXT,
|
||||
id: "#{activity_id}#rejects/friends",
|
||||
type: 'Reject',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(some_local_account),
|
||||
object: activity_id,
|
||||
}
|
||||
end
|
||||
|
||||
def delete_follow_activity(activity_id)
|
||||
{
|
||||
'@context': ActivityPub::TagManager::CONTEXT,
|
||||
id: "#{activity_id}#delete/friends",
|
||||
type: 'Delete',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(some_local_account),
|
||||
object: ActivityPub::TagManager::COLLECTIONS[:public],
|
||||
}
|
||||
end
|
||||
|
||||
def some_local_account
|
||||
@some_local_account ||= Account.representative
|
||||
end
|
||||
|
||||
def ensure_disabled
|
||||
delete_for_friend! unless id.nil? || (i_am_idle? && they_are_idle?)
|
||||
end
|
||||
|
||||
def set_default_inbox_url
|
||||
self.inbox_url = default_inbox_url if inbox_url.blank?
|
||||
end
|
||||
end
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue