Merge commit '3a191b3797dde1daf79cd748a14b87240532d543' into glitch-soc/merge-upstream

pull/2744/head
Claire 2024-06-17 13:41:58 +02:00
commit 677f73f793
61 changed files with 598 additions and 477 deletions

View File

@ -1,6 +0,0 @@
---
ignore:
# devise-two-factor advisory about brute-forcing TOTP
# We have rate-limits on authentication endpoints in place (including second
# factor verification) since Mastodon v3.2.0
- CVE-2024-0227

View File

@ -366,6 +366,9 @@ module.exports = defineConfig({
// Disable formatting rules that have been enabled in the base config // Disable formatting rules that have been enabled in the base config
'indent': 'off', 'indent': 'off',
// This is not needed as we use noImplicitReturns, which handles this in addition to understanding types
'consistent-return': 'off',
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
'@typescript-eslint/consistent-type-definitions': ['warn', 'interface'], '@typescript-eslint/consistent-type-definitions': ['warn', 'interface'],

View File

@ -6,14 +6,12 @@ on:
paths: paths:
- 'Gemfile*' - 'Gemfile*'
- '.ruby-version' - '.ruby-version'
- '.bundler-audit.yml'
- '.github/workflows/bundler-audit.yml' - '.github/workflows/bundler-audit.yml'
pull_request: pull_request:
paths: paths:
- 'Gemfile*' - 'Gemfile*'
- '.ruby-version' - '.ruby-version'
- '.bundler-audit.yml'
- '.github/workflows/bundler-audit.yml' - '.github/workflows/bundler-audit.yml'
schedule: schedule:
@ -23,12 +21,17 @@ jobs:
security: security:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
BUNDLE_ONLY: development
steps: steps:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up Ruby environment - name: Set up Ruby
uses: ./.github/actions/setup-ruby uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Run bundler-audit - name: Run bundler-audit
run: bundle exec bundler-audit run: bundle exec bundler-audit check --update

View File

@ -26,12 +26,18 @@ on:
jobs: jobs:
lint: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
BUNDLE_ONLY: development
steps: steps:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up Ruby environment - name: Set up Ruby
uses: ./.github/actions/setup-ruby uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Run haml-lint - name: Run haml-lint
run: | run: |

View File

@ -27,18 +27,23 @@ jobs:
lint: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
BUNDLE_ONLY: development
steps: steps:
- name: Clone repository - name: Clone repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set up Ruby environment - name: Set up Ruby
uses: ./.github/actions/setup-ruby uses: ruby/setup-ruby@v1
with:
bundler-cache: true
- name: Set-up RuboCop Problem Matcher - name: Set-up RuboCop Problem Matcher
uses: r7kamura/rubocop-problem-matchers-action@v1 uses: r7kamura/rubocop-problem-matchers-action@v1
- name: Run rubocop - name: Run rubocop
run: bundle exec rubocop run: bin/rubocop
- name: Run brakeman - name: Run brakeman
if: always() # Run both checks, even if the first failed if: always() # Run both checks, even if the first failed

View File

@ -1,95 +0,0 @@
name: Test two step migrations
on:
push:
branches-ignore:
- 'dependabot/**'
- 'renovate/**'
pull_request:
jobs:
pre_job:
runs-on: ubuntu-latest
outputs:
should_skip: ${{ steps.skip_check.outputs.should_skip }}
steps:
- id: skip_check
uses: fkirc/skip-duplicate-actions@v5
with:
paths: '["Gemfile*", ".ruby-version", "**/*.rb", ".github/workflows/test-migrations-two-step.yml", "lib/tasks/tests.rake"]'
test:
runs-on: ubuntu-latest
needs: pre_job
if: needs.pre_job.outputs.should_skip != 'true'
strategy:
fail-fast: false
matrix:
postgres:
- 14-alpine
- 15-alpine
services:
postgres:
image: postgres:${{ matrix.postgres}}
env:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:7-alpine
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 6379:6379
env:
CONTINUOUS_INTEGRATION: true
DB_HOST: localhost
DB_USER: postgres
DB_PASS: postgres
DISABLE_SIMPLECOV: true
RAILS_ENV: test
BUNDLE_CLEAN: true
BUNDLE_FROZEN: true
BUNDLE_WITHOUT: 'development production'
BUNDLE_JOBS: 3
BUNDLE_RETRY: 3
steps:
- uses: actions/checkout@v4
- name: Set up Ruby environment
uses: ./.github/actions/setup-ruby
- name: Create database
run: './bin/rails db:create'
- name: Run historical migrations with data population
run: './bin/rails tests:migrations:prepare_database'
env:
SKIP_POST_DEPLOYMENT_MIGRATIONS: true
- name: Run all remaining pre-deployment migrations
run: './bin/rails db:migrate'
env:
SKIP_POST_DEPLOYMENT_MIGRATIONS: true
- name: Run all post-deployment migrations
run: './bin/rails db:migrate'
- name: Check migration result
run: './bin/rails tests:migrations:check_database'

View File

@ -1,4 +1,5 @@
name: Test one step migrations name: Historical data migration test
on: on:
push: push:
branches-ignore: branches-ignore:
@ -17,7 +18,7 @@ jobs:
- id: skip_check - id: skip_check
uses: fkirc/skip-duplicate-actions@v5 uses: fkirc/skip-duplicate-actions@v5
with: with:
paths: '["Gemfile*", ".ruby-version", "**/*.rb", ".github/workflows/test-migrations-one-step.yml", "lib/tasks/tests.rake"]' paths: '["Gemfile*", ".ruby-version", "**/*.rb", ".github/workflows/test-migrations.yml", "lib/tasks/tests.rake"]'
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -57,7 +58,6 @@ jobs:
- 6379:6379 - 6379:6379
env: env:
CONTINUOUS_INTEGRATION: true
DB_HOST: localhost DB_HOST: localhost
DB_USER: postgres DB_USER: postgres
DB_PASS: postgres DB_PASS: postgres
@ -65,7 +65,7 @@ jobs:
RAILS_ENV: test RAILS_ENV: test
BUNDLE_CLEAN: true BUNDLE_CLEAN: true
BUNDLE_FROZEN: true BUNDLE_FROZEN: true
BUNDLE_WITHOUT: 'development production' BUNDLE_WITHOUT: 'development:production'
BUNDLE_JOBS: 3 BUNDLE_JOBS: 3
BUNDLE_RETRY: 3 BUNDLE_RETRY: 3
@ -75,14 +75,19 @@ jobs:
- name: Set up Ruby environment - name: Set up Ruby environment
uses: ./.github/actions/setup-ruby uses: ./.github/actions/setup-ruby
- name: Create database - name: Test "one step migration" flow
run: './bin/rails db:create' run: |
bin/rails db:drop
bin/rails db:create
bin/rails tests:migrations:prepare_database
bin/rails db:migrate
bin/rails tests:migrations:check_database
- name: Run historical migrations with data population - name: Test "two step migration" flow
run: './bin/rails tests:migrations:prepare_database' run: |
bin/rails db:drop
- name: Run all remaining migrations bin/rails db:create
run: './bin/rails db:migrate' SKIP_POST_DEPLOYMENT_MIGRATIONS=true bin/rails tests:migrations:prepare_database
SKIP_POST_DEPLOYMENT_MIGRATIONS=true bin/rails db:migrate
- name: Check migration result bin/rails db:migrate
run: './bin/rails tests:migrations:check_database' bin/rails tests:migrations:check_database

View File

@ -1,7 +1,34 @@
# Can be removed once all rules are addressed or moved to this file as documented overrides ---
inherit_from: .rubocop_todo.yml AllCops:
CacheRootDirectory: tmp
DisplayCopNames: true
DisplayStyleGuide: true
Exclude:
- db/schema.rb
- bin/*
- node_modules/**/*
- Vagrantfile
- vendor/**/*
- config/initializers/json_ld*
- lib/mastodon/migration_helpers.rb
- lib/templates/**/*
ExtraDetails: true
NewCops: enable
TargetRubyVersion: 3.1 # Oldest supported ruby version
UseCache: true
inherit_from:
- .rubocop/layout.yml
- .rubocop/metrics.yml
- .rubocop/naming.yml
- .rubocop/rails.yml
- .rubocop/rspec_rails.yml
- .rubocop/rspec.yml
- .rubocop/style.yml
- .rubocop/custom.yml
- .rubocop_todo.yml
- .rubocop/strict.yml
# Used for merging with exclude lists with .rubocop_todo.yml
inherit_mode: inherit_mode:
merge: merge:
- Exclude - Exclude
@ -12,229 +39,3 @@ require:
- rubocop-rspec_rails - rubocop-rspec_rails
- rubocop-performance - rubocop-performance
- rubocop-capybara - rubocop-capybara
- ./lib/linter/rubocop_middle_dot
AllCops:
TargetRubyVersion: 3.1 # Set to minimum supported version of CI
DisplayCopNames: true
DisplayStyleGuide: true
ExtraDetails: true
UseCache: true
CacheRootDirectory: tmp
NewCops: enable # Opt-in to newly added rules
Exclude:
- db/schema.rb
- 'bin/*'
- 'node_modules/**/*'
- 'Vagrantfile'
- 'vendor/**/*'
- 'config/initializers/json_ld*' # Generated files
- 'lib/mastodon/migration_helpers.rb' # Vendored from GitLab
- 'lib/templates/**/*'
# Reason: Prefer Hashes without extreme indentation
# https://docs.rubocop.org/rubocop/cops_layout.html#layoutfirsthashelementindentation
Layout/FirstHashElementIndentation:
EnforcedStyle: consistent
# Reason: Currently disabled in .rubocop_todo.yml
# https://docs.rubocop.org/rubocop/cops_layout.html#layoutlinelength
Layout/LineLength:
Max: 300 # Default of 120 causes a duplicate entry in generated todo file
## Disable most Metrics/*Length cops
# Reason: those are often triggered and force significant refactors when this happend
# but the team feel they are not really improving the code quality.
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsblocklength
Metrics/BlockLength:
Enabled: false
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsclasslength
Metrics/ClassLength:
Enabled: false
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsmethodlength
Metrics/MethodLength:
Enabled: false
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsmodulelength
Metrics/ModuleLength:
Enabled: false
## End Disable Metrics/*Length cops
# Reason: Currently disabled in .rubocop_todo.yml
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsabcsize
Metrics/AbcSize:
Exclude:
- 'lib/mastodon/cli/*.rb'
# Reason: Currently disabled in .rubocop_todo.yml
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricscyclomaticcomplexity
Metrics/CyclomaticComplexity:
Exclude:
- lib/mastodon/cli/*.rb
# Reason:
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsparameterlists
Metrics/ParameterLists:
CountKeywordArgs: false
# Reason: Prefer seeing a variable name
# https://docs.rubocop.org/rubocop/cops_naming.html#namingblockforwarding
Naming/BlockForwarding:
EnforcedStyle: explicit
# Reason: Prevailing style is argument file paths
# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsfilepath
Rails/FilePath:
EnforcedStyle: arguments
# Reason: Prevailing style uses numeric status codes, matches RSpec/Rails/HttpStatus
# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railshttpstatus
Rails/HttpStatus:
EnforcedStyle: numeric
# Reason: Conflicts with `Lint/UselessMethodDefinition` for inherited controller actions
# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railslexicallyscopedactionfilter
Rails/LexicallyScopedActionFilter:
Exclude:
- 'app/controllers/auth/*'
# Reason: These tasks are doing local work which do not need full env loaded
# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsrakeenvironment
Rails/RakeEnvironment:
Exclude:
- 'lib/tasks/auto_annotate_models.rake'
- 'lib/tasks/emojis.rake'
- 'lib/tasks/mastodon.rake'
- 'lib/tasks/repo.rake'
- 'lib/tasks/statistics.rake'
# Reason: There are appropriate times to use these features
# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsskipsmodelvalidations
Rails/SkipsModelValidations:
Enabled: false
# Reason: We want to preserve the ability to migrate from arbitrary old versions,
# and cannot guarantee that every installation has run every migration as they upgrade.
# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsunusedignoredcolumns
Rails/UnusedIgnoredColumns:
Enabled: false
# Reason: Prevailing style choice
# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsnegateinclude
Rails/NegateInclude:
Enabled: false
# Reason: Enforce default limit, but allow some elements to span lines
# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecexamplelength
RSpec/ExampleLength:
CountAsOne: ['array', 'heredoc', 'method_call']
# Reason: Deprecated cop, will be removed in 3.0, replaced by SpecFilePathFormat
# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecfilepath
RSpec/FilePath:
Enabled: false
# Reason:
# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecnamedsubject
RSpec/NamedSubject:
EnforcedStyle: named_only
# Reason: Prevailing style choice
# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecnottonot
RSpec/NotToNot:
EnforcedStyle: to_not
# Reason: Match overrides from Rspec/FilePath rule above
# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecspecfilepathformat
RSpec/SpecFilePathFormat:
CustomTransform:
ActivityPub: activitypub
DeepL: deepl
FetchOEmbedService: fetch_oembed_service
OEmbedController: oembed_controller
OStatus: ostatus
# Reason: Prevailing style uses numeric status codes, matches Rails/HttpStatus
# https://docs.rubocop.org/rubocop-rspec/cops_rspec_rails.html#rspecrailshttpstatus
RSpecRails/HttpStatus:
EnforcedStyle: numeric
# Reason:
# https://docs.rubocop.org/rubocop/cops_style.html#styleclassandmodulechildren
Style/ClassAndModuleChildren:
Enabled: false
# Reason: Classes mostly self-document with their names
# https://docs.rubocop.org/rubocop/cops_style.html#styledocumentation
Style/Documentation:
Enabled: false
# Reason: Route redirects are not token-formatted and must be skipped
# https://docs.rubocop.org/rubocop/cops_style.html#styleformatstringtoken
Style/FormatStringToken:
inherit_mode:
merge:
- AllowedMethods # The rubocop-rails config adds `redirect`
AllowedMethods:
- redirect_with_vary
# Reason: Prevailing style choice
# https://docs.rubocop.org/rubocop/cops_style.html#stylehashaslastarrayitem
Style/HashAsLastArrayItem:
Enabled: false
# Reason: Enforce modern Ruby style
# https://docs.rubocop.org/rubocop/cops_style.html#stylehashsyntax
Style/HashSyntax:
EnforcedStyle: ruby19_no_mixed_keys
EnforcedShorthandSyntax: either
# Reason:
# https://docs.rubocop.org/rubocop/cops_style.html#stylenumericliterals
Style/NumericLiterals:
AllowedPatterns:
- \d{4}_\d{2}_\d{2}_\d{6} # For DB migration date version number readability
# Reason:
# https://docs.rubocop.org/rubocop/cops_style.html#stylepercentliteraldelimiters
Style/PercentLiteralDelimiters:
PreferredDelimiters:
'%i': '()'
'%w': '()'
# Reason: Prefer less indentation in conditional assignments
# https://docs.rubocop.org/rubocop/cops_style.html#styleredundantbegin
Style/RedundantBegin:
Enabled: false
# Reason: Prevailing style choice
# https://docs.rubocop.org/rubocop/cops_style.html#styleredundantfetchblock
Style/RedundantFetchBlock:
Enabled: false
# Reason: Overridden to reduce implicit StandardError rescues
# https://docs.rubocop.org/rubocop/cops_style.html#stylerescuestandarderror
Style/RescueStandardError:
EnforcedStyle: implicit
# Reason: Originally disabled for CodeClimate, and no config consensus has been found
# https://docs.rubocop.org/rubocop/cops_style.html#stylesymbolarray
Style/SymbolArray:
Enabled: false
# Reason:
# https://docs.rubocop.org/rubocop/cops_style.html#styletrailingcommainarrayliteral
Style/TrailingCommaInArrayLiteral:
EnforcedStyleForMultiline: 'comma'
# Reason:
# https://docs.rubocop.org/rubocop/cops_style.html#styletrailingcommainhashliteral
Style/TrailingCommaInHashLiteral:
EnforcedStyleForMultiline: 'comma'
Style/MiddleDot:
Enabled: true

6
.rubocop/custom.yml Normal file
View File

@ -0,0 +1,6 @@
---
require:
- ../lib/linter/rubocop_middle_dot
Style/MiddleDot:
Enabled: true

6
.rubocop/layout.yml Normal file
View File

@ -0,0 +1,6 @@
---
Layout/FirstHashElementIndentation:
EnforcedStyle: consistent
Layout/LineLength:
Max: 300 # Default of 120 causes a duplicate entry in generated todo file

23
.rubocop/metrics.yml Normal file
View File

@ -0,0 +1,23 @@
---
Metrics/AbcSize:
Exclude:
- lib/mastodon/cli/*.rb
Metrics/BlockLength:
Enabled: false
Metrics/ClassLength:
Enabled: false
Metrics/CyclomaticComplexity:
Exclude:
- lib/mastodon/cli/*.rb
Metrics/MethodLength:
Enabled: false
Metrics/ModuleLength:
Enabled: false
Metrics/ParameterLists:
CountKeywordArgs: false

3
.rubocop/naming.yml Normal file
View File

@ -0,0 +1,3 @@
---
Naming/BlockForwarding:
EnforcedStyle: explicit

27
.rubocop/rails.yml Normal file
View File

@ -0,0 +1,27 @@
---
Rails/FilePath:
EnforcedStyle: arguments
Rails/HttpStatus:
EnforcedStyle: numeric
Rails/LexicallyScopedActionFilter:
Exclude:
- app/controllers/auth/* # Conflicts with `Lint/UselessMethodDefinition` for inherited controller actions
Rails/NegateInclude:
Enabled: false
Rails/RakeEnvironment:
Exclude: # Tasks are doing local work which do not need full env loaded
- lib/tasks/auto_annotate_models.rake
- lib/tasks/emojis.rake
- lib/tasks/mastodon.rake
- lib/tasks/repo.rake
- lib/tasks/statistics.rake
Rails/SkipsModelValidations:
Enabled: false
Rails/UnusedIgnoredColumns:
Enabled: false # Preserve ability to migrate from arbitrary old versions

17
.rubocop/rspec.yml Normal file
View File

@ -0,0 +1,17 @@
---
RSpec/ExampleLength:
CountAsOne: ['array', 'heredoc', 'method_call']
RSpec/NamedSubject:
EnforcedStyle: named_only
RSpec/NotToNot:
EnforcedStyle: to_not
RSpec/SpecFilePathFormat:
CustomTransform:
ActivityPub: activitypub
DeepL: deepl
FetchOEmbedService: fetch_oembed_service
OEmbedController: oembed_controller
OStatus: ostatus

3
.rubocop/rspec_rails.yml Normal file
View File

@ -0,0 +1,3 @@
---
RSpecRails/HttpStatus:
EnforcedStyle: numeric

19
.rubocop/strict.yml Normal file
View File

@ -0,0 +1,19 @@
Lint/Debugger: # Remove any `binding.pry`
Enabled: true
Exclude: []
RSpec/Focus: # Require full spec run on CI
Enabled: true
Exclude: []
Rails/Output: # Remove any `puts` debugging
Enabled: true
Exclude: []
Rails/FindEach: # Using `each` could impact performance, use `find_each`
Enabled: true
Exclude: []
Rails/UniqBeforePluck: # Require `uniq.pluck` and not `pluck.uniq`
Enabled: true
Exclude: []

47
.rubocop/style.yml Normal file
View File

@ -0,0 +1,47 @@
---
Style/ClassAndModuleChildren:
Enabled: false
Style/Documentation:
Enabled: false
Style/FormatStringToken:
AllowedMethods:
- redirect_with_vary # Route redirects are not token-formatted
inherit_mode:
merge:
- AllowedMethods
Style/HashAsLastArrayItem:
Enabled: false
Style/HashSyntax:
EnforcedShorthandSyntax: either
EnforcedStyle: ruby19_no_mixed_keys
Style/NumericLiterals:
AllowedPatterns:
- \d{4}_\d{2}_\d{2}_\d{6}
Style/PercentLiteralDelimiters:
PreferredDelimiters:
'%i': ()
'%w': ()
Style/RedundantBegin:
Enabled: false
Style/RedundantFetchBlock:
Enabled: false
Style/RescueStandardError:
EnforcedStyle: implicit
Style/SymbolArray:
Enabled: false
Style/TrailingCommaInArrayLiteral:
EnforcedStyleForMultiline: comma
Style/TrailingCommaInHashLiteral:
EnforcedStyleForMultiline: comma

View File

@ -1 +1 @@
3.3.2 3.3.3

View File

@ -12,7 +12,7 @@ ARG BUILDPLATFORM=${BUILDPLATFORM}
# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.3.x"] # Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.3.x"]
# renovate: datasource=docker depName=docker.io/ruby # renovate: datasource=docker depName=docker.io/ruby
ARG RUBY_VERSION="3.3.2" ARG RUBY_VERSION="3.3.3"
# # Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"] # # Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"]
# renovate: datasource=node-version depName=node # renovate: datasource=node-version depName=node
ARG NODE_MAJOR_VERSION="20" ARG NODE_MAJOR_VERSION="20"

View File

@ -171,6 +171,7 @@ group :development do
gem 'rubocop-performance', require: false gem 'rubocop-performance', require: false
gem 'rubocop-rails', require: false gem 'rubocop-rails', require: false
gem 'rubocop-rspec', require: false gem 'rubocop-rspec', require: false
gem 'rubocop-rspec_rails', require: false
# Annotates modules with schema # Annotates modules with schema
gem 'annotate', '~> 3.2' gem 'annotate', '~> 3.2'

View File

@ -109,7 +109,7 @@ GEM
aws-sdk-kms (1.83.0) aws-sdk-kms (1.83.0)
aws-sdk-core (~> 3, >= 3.197.0) aws-sdk-core (~> 3, >= 3.197.0)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.152.1) aws-sdk-s3 (1.152.2)
aws-sdk-core (~> 3, >= 3.197.0) aws-sdk-core (~> 3, >= 3.197.0)
aws-sdk-kms (~> 1) aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.8) aws-sigv4 (~> 1.8)
@ -584,7 +584,7 @@ GEM
orm_adapter (0.5.0) orm_adapter (0.5.0)
ox (2.14.18) ox (2.14.18)
parallel (1.25.1) parallel (1.25.1)
parser (3.3.2.0) parser (3.3.3.0)
ast (~> 2.4.1) ast (~> 2.4.1)
racc racc
parslet (2.0.0) parslet (2.0.0)
@ -698,8 +698,8 @@ GEM
responders (3.1.1) responders (3.1.1)
actionpack (>= 5.2) actionpack (>= 5.2)
railties (>= 5.2) railties (>= 5.2)
rexml (3.2.8) rexml (3.3.0)
strscan (>= 3.0.9) strscan
rotp (6.3.0) rotp (6.3.0)
rouge (4.2.1) rouge (4.2.1)
rpam2 (4.0.2) rpam2 (4.0.2)
@ -746,8 +746,6 @@ GEM
parser (>= 3.3.1.0) parser (>= 3.3.1.0)
rubocop-capybara (2.21.0) rubocop-capybara (2.21.0)
rubocop (~> 1.41) rubocop (~> 1.41)
rubocop-factory_bot (2.25.1)
rubocop (~> 1.41)
rubocop-performance (1.21.0) rubocop-performance (1.21.0)
rubocop (>= 1.48.1, < 2.0) rubocop (>= 1.48.1, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0) rubocop-ast (>= 1.31.1, < 2.0)
@ -756,13 +754,11 @@ GEM
rack (>= 1.1) rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0) rubocop (>= 1.33.0, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0) rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rspec (2.31.0) rubocop-rspec (3.0.1)
rubocop (~> 1.40) rubocop (~> 1.61)
rubocop-capybara (~> 2.17) rubocop-rspec_rails (2.30.0)
rubocop-factory_bot (~> 2.22) rubocop (~> 1.61)
rubocop-rspec_rails (~> 2.28) rubocop-rspec (~> 3, >= 3.0.1)
rubocop-rspec_rails (2.28.3)
rubocop (~> 1.40)
ruby-prof (1.7.0) ruby-prof (1.7.0)
ruby-progressbar (1.13.0) ruby-progressbar (1.13.0)
ruby-saml (1.16.0) ruby-saml (1.16.0)
@ -776,7 +772,7 @@ GEM
fugit (~> 1.1, >= 1.1.6) fugit (~> 1.1, >= 1.1.6)
safety_net_attestation (0.4.0) safety_net_attestation (0.4.0)
jwt (~> 2.0) jwt (~> 2.0)
sanitize (6.1.0) sanitize (6.1.1)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.12.0) nokogiri (>= 1.12.0)
scenic (1.8.0) scenic (1.8.0)
@ -1028,6 +1024,7 @@ DEPENDENCIES
rubocop-performance rubocop-performance
rubocop-rails rubocop-rails
rubocop-rspec rubocop-rspec
rubocop-rspec_rails
ruby-prof ruby-prof
ruby-progressbar (~> 1.13) ruby-progressbar (~> 1.13)
ruby-vips (~> 2.2) ruby-vips (~> 2.2)

View File

@ -78,7 +78,7 @@ Mastodon acts as an OAuth2 provider, so 3rd party apps can use the REST and Stre
### Tech stack ### Tech stack
- **Ruby on Rails** powers the REST API and other web pages - **Ruby on Rails** powers the REST API and other web pages
- **React.js** and Redux are used for the dynamic parts of the interface - **React.js** and **Redux** are used for the dynamic parts of the interface
- **Node.js** powers the streaming API - **Node.js** powers the streaming API
### Requirements ### Requirements

View File

@ -68,13 +68,17 @@ export function importFetchedStatuses(statuses) {
status.filtered.forEach(result => pushUnique(filters, result.filter)); status.filtered.forEach(result => pushUnique(filters, result.filter));
} }
if (status.reblog && status.reblog.id) { if (status.reblog?.id) {
processStatus(status.reblog); processStatus(status.reblog);
} }
if (status.poll && status.poll.id) { if (status.poll?.id) {
pushUnique(polls, normalizePoll(status.poll, getState().getIn(['polls', status.poll.id]))); pushUnique(polls, normalizePoll(status.poll, getState().getIn(['polls', status.poll.id])));
} }
if (status.card?.author_account) {
pushUnique(accounts, status.card.author_account);
}
} }
statuses.forEach(processStatus); statuses.forEach(processStatus);

View File

@ -36,6 +36,10 @@ export function normalizeStatus(status, normalOldStatus) {
normalStatus.poll = status.poll.id; normalStatus.poll = status.poll.id;
} }
if (status.card?.author_account) {
normalStatus.card = { ...status.card, author_account: status.card.author_account.id };
}
if (status.filtered) { if (status.filtered) {
normalStatus.filtered = status.filtered.map(normalizeFilterResult); normalStatus.filtered = status.filtered.map(normalizeFilterResult);
} }

View File

@ -1,6 +1,6 @@
import api, { getLinks } from '../api'; import api, { getLinks } from '../api';
import { importFetchedStatuses } from './importer'; import { importFetchedStatuses, importFetchedAccounts } from './importer';
export const TRENDS_TAGS_FETCH_REQUEST = 'TRENDS_TAGS_FETCH_REQUEST'; export const TRENDS_TAGS_FETCH_REQUEST = 'TRENDS_TAGS_FETCH_REQUEST';
export const TRENDS_TAGS_FETCH_SUCCESS = 'TRENDS_TAGS_FETCH_SUCCESS'; export const TRENDS_TAGS_FETCH_SUCCESS = 'TRENDS_TAGS_FETCH_SUCCESS';
@ -49,8 +49,11 @@ export const fetchTrendingLinks = () => (dispatch) => {
dispatch(fetchTrendingLinksRequest()); dispatch(fetchTrendingLinksRequest());
api() api()
.get('/api/v1/trends/links') .get('/api/v1/trends/links', { params: { limit: 20 } })
.then(({ data }) => dispatch(fetchTrendingLinksSuccess(data))) .then(({ data }) => {
dispatch(importFetchedAccounts(data.map(link => link.author_account).filter(account => !!account)));
dispatch(fetchTrendingLinksSuccess(data));
})
.catch(err => dispatch(fetchTrendingLinksFail(err))); .catch(err => dispatch(fetchTrendingLinksFail(err)));
}; };

View File

@ -0,0 +1,19 @@
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
import { AuthorLink } from 'mastodon/features/explore/components/author_link';
export const MoreFromAuthor = ({ accountId }) => (
<div className='more-from-author'>
<svg viewBox='0 0 79 79' className='logo logo--icon' role='img'>
<use xlinkHref='#logo-symbol-icon' />
</svg>
<FormattedMessage id='link_preview.more_from_author' defaultMessage='More from {name}' values={{ name: <AuthorLink accountId={accountId} /> }} />
</div>
);
MoreFromAuthor.propTypes = {
accountId: PropTypes.string.isRequired,
};

View File

@ -0,0 +1,21 @@
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom';
import { Avatar } from 'mastodon/components/avatar';
import { useAppSelector } from 'mastodon/store';
export const AuthorLink = ({ accountId }) => {
const account = useAppSelector(state => state.getIn(['accounts', accountId]));
return (
<Link to={`/@${account.get('acct')}`} className='story__details__shared__author-link'>
<Avatar account={account} size={16} />
<bdi dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} />
</Link>
);
};
AuthorLink.propTypes = {
accountId: PropTypes.string.isRequired,
};

View File

@ -1,61 +1,89 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { PureComponent } from 'react'; import { useState, useCallback } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { Blurhash } from 'mastodon/components/blurhash'; import { Blurhash } from 'mastodon/components/blurhash';
import { accountsCountRenderer } from 'mastodon/components/hashtag';
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp'; import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
import { ShortNumber } from 'mastodon/components/short_number'; import { ShortNumber } from 'mastodon/components/short_number';
import { Skeleton } from 'mastodon/components/skeleton'; import { Skeleton } from 'mastodon/components/skeleton';
export default class Story extends PureComponent { import { AuthorLink } from './author_link';
static propTypes = { const sharesCountRenderer = (displayNumber, pluralReady) => (
url: PropTypes.string, <FormattedMessage
title: PropTypes.string, id='link_preview.shares'
lang: PropTypes.string, defaultMessage='{count, plural, one {{counter} post} other {{counter} posts}}'
publisher: PropTypes.string, values={{
publishedAt: PropTypes.string, count: pluralReady,
author: PropTypes.string, counter: <strong>{displayNumber}</strong>,
sharedTimes: PropTypes.number, }}
thumbnail: PropTypes.string, />
thumbnailDescription: PropTypes.string, );
blurhash: PropTypes.string,
expanded: PropTypes.bool,
};
state = { export const Story = ({
thumbnailLoaded: false, url,
}; title,
lang,
publisher,
publishedAt,
author,
authorAccount,
sharedTimes,
thumbnail,
thumbnailDescription,
blurhash,
expanded
}) => {
const [thumbnailLoaded, setThumbnailLoaded] = useState(false);
handleImageLoad = () => this.setState({ thumbnailLoaded: true }); const handleImageLoad = useCallback(() => {
setThumbnailLoaded(true);
}, [setThumbnailLoaded]);
render () { return (
const { expanded, url, title, lang, publisher, author, publishedAt, sharedTimes, thumbnail, thumbnailDescription, blurhash } = this.props; <div className={classNames('story', { expanded })}>
<div className='story__details'>
const { thumbnailLoaded } = this.state; <div className='story__details__publisher'>
{publisher ? <span lang={lang}>{publisher}</span> : <Skeleton width={50} />}{publishedAt && <> · <RelativeTimestamp timestamp={publishedAt} /></>}
return (
<a className={classNames('story', { expanded })} href={url} target='blank' rel='noopener'>
<div className='story__details'>
<div className='story__details__publisher'>{publisher ? <span lang={lang}>{publisher}</span> : <Skeleton width={50} />}{publishedAt && <> · <RelativeTimestamp timestamp={publishedAt} /></>}</div>
<div className='story__details__title' lang={lang}>{title ? title : <Skeleton />}</div>
<div className='story__details__shared'>{author && <><FormattedMessage id='link_preview.author' defaultMessage='By {name}' values={{ name: <strong>{author}</strong> }} /> · </>}{typeof sharedTimes === 'number' ? <ShortNumber value={sharedTimes} renderer={accountsCountRenderer} /> : <Skeleton width={100} />}</div>
</div> </div>
<div className='story__thumbnail'> <a className='story__details__title' lang={lang} href={url} target='blank' rel='noopener'>
{thumbnail ? ( {title ? title : <Skeleton />}
<> </a>
<div className={classNames('story__thumbnail__preview', { 'story__thumbnail__preview--hidden': thumbnailLoaded })}><Blurhash hash={blurhash} /></div>
<img src={thumbnail} onLoad={this.handleImageLoad} alt={thumbnailDescription} title={thumbnailDescription} lang={lang} /> <div className='story__details__shared'>
</> {author ? <FormattedMessage id='link_preview.author' className='story__details__shared__author' defaultMessage='By {name}' values={{ name: authorAccount ? <AuthorLink accountId={authorAccount} /> : <strong>{author}</strong> }} /> : <span />}
) : <Skeleton />} {typeof sharedTimes === 'number' ? <span className='story__details__shared__pill'><ShortNumber value={sharedTimes} renderer={sharesCountRenderer} /></span> : <Skeleton width='10ch' />}
</div> </div>
</div>
<a className='story__thumbnail' href={url} target='blank' rel='noopener'>
{thumbnail ? (
<>
<div className={classNames('story__thumbnail__preview', { 'story__thumbnail__preview--hidden': thumbnailLoaded })}><Blurhash hash={blurhash} /></div>
<img src={thumbnail} onLoad={handleImageLoad} alt={thumbnailDescription} title={thumbnailDescription} lang={lang} />
</>
) : <Skeleton />}
</a> </a>
); </div>
} );
};
} Story.propTypes = {
url: PropTypes.string,
title: PropTypes.string,
lang: PropTypes.string,
publisher: PropTypes.string,
publishedAt: PropTypes.string,
author: PropTypes.string,
authorAccount: PropTypes.string,
sharedTimes: PropTypes.number,
thumbnail: PropTypes.string,
thumbnailDescription: PropTypes.string,
blurhash: PropTypes.string,
expanded: PropTypes.bool,
};

View File

@ -13,7 +13,7 @@ import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { WithRouterPropTypes } from 'mastodon/utils/react_router';
import Story from './components/story'; import { Story } from './components/story';
const mapStateToProps = state => ({ const mapStateToProps = state => ({
links: state.getIn(['trends', 'links', 'items']), links: state.getIn(['trends', 'links', 'items']),
@ -75,6 +75,7 @@ class Links extends PureComponent {
publisher={link.get('provider_name')} publisher={link.get('provider_name')}
publishedAt={link.get('published_at')} publishedAt={link.get('published_at')}
author={link.get('author_name')} author={link.get('author_name')}
authorAccount={link.getIn(['author_account', 'id'])}
sharedTimes={link.getIn(['history', 0, 'accounts']) * 1 + link.getIn(['history', 1, 'accounts']) * 1} sharedTimes={link.getIn(['history', 0, 'accounts']) * 1 + link.getIn(['history', 1, 'accounts']) * 1}
thumbnail={link.get('image')} thumbnail={link.get('image')}
thumbnailDescription={link.get('image_description')} thumbnailDescription={link.get('image_description')}

View File

@ -6,7 +6,6 @@ import { PureComponent } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { Link } from 'react-router-dom';
import Immutable from 'immutable'; import Immutable from 'immutable';
@ -15,9 +14,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import DescriptionIcon from '@/material-icons/400-24px/description-fill.svg?react'; import DescriptionIcon from '@/material-icons/400-24px/description-fill.svg?react';
import OpenInNewIcon from '@/material-icons/400-24px/open_in_new.svg?react'; import OpenInNewIcon from '@/material-icons/400-24px/open_in_new.svg?react';
import PlayArrowIcon from '@/material-icons/400-24px/play_arrow-fill.svg?react'; import PlayArrowIcon from '@/material-icons/400-24px/play_arrow-fill.svg?react';
import { Avatar } from 'mastodon/components/avatar';
import { Blurhash } from 'mastodon/components/blurhash'; import { Blurhash } from 'mastodon/components/blurhash';
import { Icon } from 'mastodon/components/icon'; import { Icon } from 'mastodon/components/icon';
import { MoreFromAuthor } from 'mastodon/components/more_from_author';
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp'; import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
import { useBlurhash } from 'mastodon/initial_state'; import { useBlurhash } from 'mastodon/initial_state';
@ -59,20 +58,6 @@ const addAutoPlay = html => {
return html; return html;
}; };
const MoreFromAuthor = ({ author }) => (
<div className='more-from-author'>
<svg viewBox='0 0 79 79' className='logo logo--icon' role='img'>
<use xlinkHref='#logo-symbol-icon' />
</svg>
<FormattedMessage id='link_preview.more_from_author' defaultMessage='More from {name}' values={{ name: <Link to={`/@${author.get('acct')}`}><Avatar account={author} size={16} /> {author.get('display_name')}</Link> }} />
</div>
);
MoreFromAuthor.propTypes = {
author: ImmutablePropTypes.map,
};
export default class Card extends PureComponent { export default class Card extends PureComponent {
static propTypes = { static propTypes = {
@ -259,7 +244,7 @@ export default class Card extends PureComponent {
{description} {description}
</a> </a>
{showAuthor && <MoreFromAuthor author={card.get('author_account')} />} {showAuthor && <MoreFromAuthor accountId={card.get('author_account')} />}
</> </>
); );
} }

View File

@ -308,6 +308,8 @@
"follow_requests.unlocked_explanation": "Ваш акаўнт не схаваны, аднак прадстаўнікі {domain} палічылі, што вы можаце захацець праглядзець запыты на падпіску з гэтых профіляў уручную.", "follow_requests.unlocked_explanation": "Ваш акаўнт не схаваны, аднак прадстаўнікі {domain} палічылі, што вы можаце захацець праглядзець запыты на падпіску з гэтых профіляў уручную.",
"follow_suggestions.curated_suggestion": "Выбар адміністрацыі", "follow_suggestions.curated_suggestion": "Выбар адміністрацыі",
"follow_suggestions.dismiss": "Не паказваць зноў", "follow_suggestions.dismiss": "Не паказваць зноў",
"follow_suggestions.featured_longer": "Адабраныя камандай {domain} уручную",
"follow_suggestions.friends_of_friends_longer": "Папулярнае сярод людзей, на якіх Вы падпісаны",
"follow_suggestions.hints.featured": "Гэты профіль быў выбраны ўручную камандай {domain}.", "follow_suggestions.hints.featured": "Гэты профіль быў выбраны ўручную камандай {domain}.",
"follow_suggestions.hints.friends_of_friends": "Гэты профіль папулярны сярод людзей, на якіх вы падпісаліся.", "follow_suggestions.hints.friends_of_friends": "Гэты профіль папулярны сярод людзей, на якіх вы падпісаліся.",
"follow_suggestions.hints.most_followed": "Гэты профіль - адзін з профіляў з самай вялікай колькасцю падпісак на {domain}.", "follow_suggestions.hints.most_followed": "Гэты профіль - адзін з профіляў з самай вялікай колькасцю падпісак на {domain}.",
@ -315,6 +317,8 @@
"follow_suggestions.hints.similar_to_recently_followed": "Гэты профіль падобны на профілі, на якія вы нядаўна падпісаліся.", "follow_suggestions.hints.similar_to_recently_followed": "Гэты профіль падобны на профілі, на якія вы нядаўна падпісаліся.",
"follow_suggestions.personalized_suggestion": "Персаналізаваная прапанова", "follow_suggestions.personalized_suggestion": "Персаналізаваная прапанова",
"follow_suggestions.popular_suggestion": "Папулярная прапанова", "follow_suggestions.popular_suggestion": "Папулярная прапанова",
"follow_suggestions.popular_suggestion_longer": "Папулярнае на {domain}",
"follow_suggestions.similar_to_recently_followed_longer": "Падобныя профілі, за якімі вы нядаўна сачылі",
"follow_suggestions.view_all": "Праглядзець усё", "follow_suggestions.view_all": "Праглядзець усё",
"follow_suggestions.who_to_follow": "На каго падпісацца", "follow_suggestions.who_to_follow": "На каго падпісацца",
"followed_tags": "Падпіскі", "followed_tags": "Падпіскі",
@ -410,6 +414,7 @@
"limited_account_hint.action": "Усе роўна паказваць профіль", "limited_account_hint.action": "Усе роўна паказваць профіль",
"limited_account_hint.title": "Гэты профіль быў схаваны мадэратарамі", "limited_account_hint.title": "Гэты профіль быў схаваны мадэратарамі",
"link_preview.author": "Ад {name}", "link_preview.author": "Ад {name}",
"link_preview.more_from_author": "Больш ад {name}",
"lists.account.add": "Дадаць да спісу", "lists.account.add": "Дадаць да спісу",
"lists.account.remove": "Выдаліць са спісу", "lists.account.remove": "Выдаліць са спісу",
"lists.delete": "Выдаліць спіс", "lists.delete": "Выдаліць спіс",
@ -458,7 +463,7 @@
"navigation_bar.opened_in_classic_interface": "Допісы, уліковыя запісы і іншыя спецыфічныя старонкі па змоўчанні адчыняюцца ў класічным вэб-інтэрфейсе.", "navigation_bar.opened_in_classic_interface": "Допісы, уліковыя запісы і іншыя спецыфічныя старонкі па змоўчанні адчыняюцца ў класічным вэб-інтэрфейсе.",
"navigation_bar.personal": "Асабістае", "navigation_bar.personal": "Асабістае",
"navigation_bar.pins": "Замацаваныя допісы", "navigation_bar.pins": "Замацаваныя допісы",
"navigation_bar.preferences": "Параметры", "navigation_bar.preferences": "Налады",
"navigation_bar.public_timeline": "Глабальная стужка", "navigation_bar.public_timeline": "Глабальная стужка",
"navigation_bar.search": "Пошук", "navigation_bar.search": "Пошук",
"navigation_bar.security": "Бяспека", "navigation_bar.security": "Бяспека",
@ -470,10 +475,22 @@
"notification.follow_request": "{name} адправіў запыт на падпіску", "notification.follow_request": "{name} адправіў запыт на падпіску",
"notification.mention": "{name} згадаў вас", "notification.mention": "{name} згадаў вас",
"notification.moderation-warning.learn_more": "Даведацца больш", "notification.moderation-warning.learn_more": "Даведацца больш",
"notification.moderation_warning": "Вы атрымалі папярэджанне аб мадэрацыі",
"notification.moderation_warning.action_delete_statuses": "Некаторыя вашыя допісы былі выдаленыя.",
"notification.moderation_warning.action_disable": "Ваш уліковы запіс быў адключаны.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Некаторыя з вашых допісаў былі пазначаныя як далікатныя.",
"notification.moderation_warning.action_none": "Ваш уліковы запіс атрымаў папярэджанне ад мадэратараў.",
"notification.moderation_warning.action_sensitive": "З гэтага моманту вашыя допісы будуць пазначаныя як далікатныя.",
"notification.moderation_warning.action_silence": "Ваш уліковы запіс быў абмежаваны.",
"notification.moderation_warning.action_suspend": "Ваш уліковы запіс быў прыпынены.",
"notification.own_poll": "Ваша апытанне скончылася", "notification.own_poll": "Ваша апытанне скончылася",
"notification.poll": "Апытанне, дзе вы прынялі ўдзел, скончылася", "notification.poll": "Апытанне, дзе вы прынялі ўдзел, скончылася",
"notification.reblog": "{name} пашырыў ваш допіс", "notification.reblog": "{name} пашырыў ваш допіс",
"notification.relationships_severance_event": "Страціў сувязь з {name}",
"notification.relationships_severance_event.account_suspension": "Адміністратар з {from} прыпыніў працу {target}, што азначае, што вы больш не можаце атрымліваць ад іх абнаўлення ці ўзаемадзейнічаць з імі.",
"notification.relationships_severance_event.domain_block": "Адміністратар з {from} заблакіраваў {target}, у тым ліку {followersCount} вашых падпісчыка(-аў) і {followingCount, plural, one {# уліковы запіс} few {# уліковыя запісы} many {# уліковых запісаў} other {# уліковых запісаў}}.",
"notification.relationships_severance_event.learn_more": "Даведацца больш", "notification.relationships_severance_event.learn_more": "Даведацца больш",
"notification.relationships_severance_event.user_domain_block": "Вы заблакіравалі {target} выдаліўшы {followersCount} сваіх падпісчыкаў і {followingCount, plural, one {# уліковы запіс} few {# уліковыя запісы} many {# уліковых запісаў} other {# уліковых запісаў}}, за якімі вы сочыце.",
"notification.status": "Новы допіс ад {name}", "notification.status": "Новы допіс ад {name}",
"notification.update": "Допіс {name} адрэдагаваны", "notification.update": "Допіс {name} адрэдагаваны",
"notification_requests.accept": "Прыняць", "notification_requests.accept": "Прыняць",

View File

@ -415,6 +415,7 @@
"limited_account_hint.title": "This profile has been hidden by the moderators of {domain}.", "limited_account_hint.title": "This profile has been hidden by the moderators of {domain}.",
"link_preview.author": "By {name}", "link_preview.author": "By {name}",
"link_preview.more_from_author": "More from {name}", "link_preview.more_from_author": "More from {name}",
"link_preview.shares": "{count, plural, one {{counter} post} other {{counter} posts}}",
"lists.account.add": "Add to list", "lists.account.add": "Add to list",
"lists.account.remove": "Remove from list", "lists.account.remove": "Remove from list",
"lists.delete": "Delete list", "lists.delete": "Delete list",

View File

@ -26,8 +26,9 @@
"account.featured_tags.last_status_never": "کوئی مراسلہ نہیں", "account.featured_tags.last_status_never": "کوئی مراسلہ نہیں",
"account.featured_tags.title": "{name} کے نمایاں ہیش ٹیگز", "account.featured_tags.title": "{name} کے نمایاں ہیش ٹیگز",
"account.follow": "پیروی کریں", "account.follow": "پیروی کریں",
"account.follow_back": "اکاؤنٹ کو فالو بیک ",
"account.followers": "پیروکار", "account.followers": "پیروکار",
"account.followers.empty": "\"ہنوز اس صارف کی کوئی پیروی نہیں کرتا\".", "account.followers.empty": "ہنوز اس صارف کی کوئی پیروی نہیں کرتا.",
"account.followers_counter": "{count, plural,one {{counter} پیروکار} other {{counter} پیروکار}}", "account.followers_counter": "{count, plural,one {{counter} پیروکار} other {{counter} پیروکار}}",
"account.following": "فالو کر رہے ہیں", "account.following": "فالو کر رہے ہیں",
"account.following_counter": "{count, plural, one {{counter} پیروی کر رہے ہیں} other {{counter} پیروی کر رہے ہیں}}", "account.following_counter": "{count, plural, one {{counter} پیروی کر رہے ہیں} other {{counter} پیروی کر رہے ہیں}}",
@ -46,6 +47,7 @@
"account.mute_notifications_short": "نوٹیفیکیشنز کو خاموش کریں", "account.mute_notifications_short": "نوٹیفیکیشنز کو خاموش کریں",
"account.mute_short": "خاموش", "account.mute_short": "خاموش",
"account.muted": "خاموش کردہ", "account.muted": "خاموش کردہ",
"account.mutual": "میوچول اکاؤنٹ",
"account.no_bio": "کوئی تفصیل نہیں دی گئی۔", "account.no_bio": "کوئی تفصیل نہیں دی گئی۔",
"account.open_original_page": "اصل صفحہ کھولیں", "account.open_original_page": "اصل صفحہ کھولیں",
"account.posts": "ٹوٹ", "account.posts": "ٹوٹ",
@ -64,7 +66,8 @@
"account.unmute": "@{name} کو با آواز کریں", "account.unmute": "@{name} کو با آواز کریں",
"account.unmute_notifications_short": "نوٹیفیکیشنز کو خاموش نہ کریں", "account.unmute_notifications_short": "نوٹیفیکیشنز کو خاموش نہ کریں",
"account.unmute_short": "کو خاموش نہ کریں", "account.unmute_short": "کو خاموش نہ کریں",
"account_note.placeholder": "Click to add a note", "admin.dashboard.daily_retention": "ایڈمن ڈیش بورڈ کو ڈیلی چیک ان کریں",
"admin.dashboard.monthly_retention": "ایڈمن کیش بورڈ کو منتھلی چیک ان کریں",
"admin.dashboard.retention.average": "اوسط", "admin.dashboard.retention.average": "اوسط",
"admin.dashboard.retention.cohort_size": "نئے یسرز", "admin.dashboard.retention.cohort_size": "نئے یسرز",
"alert.rate_limited.message": "\"{retry_time, time, medium} کے بعد کوشش کریں\".", "alert.rate_limited.message": "\"{retry_time, time, medium} کے بعد کوشش کریں\".",

View File

@ -1411,10 +1411,15 @@ body > [data-popper-placement] {
.audio-player, .audio-player,
.attachment-list, .attachment-list,
.picture-in-picture-placeholder, .picture-in-picture-placeholder,
.more-from-author,
.status-card, .status-card,
.hashtag-bar { .hashtag-bar {
margin-inline-start: $thread-margin; margin-inline-start: $thread-margin;
width: calc(100% - ($thread-margin)); width: calc(100% - $thread-margin);
}
.more-from-author {
width: calc(100% - $thread-margin + 2px);
} }
.status__content__read-more-button { .status__content__read-more-button {
@ -4129,6 +4134,13 @@ a.status-card {
border-end-start-radius: 0; border-end-start-radius: 0;
} }
.status-card.bottomless .status-card__image,
.status-card.bottomless .status-card__image-image,
.status-card.bottomless .status-card__image-preview {
border-end-end-radius: 0;
border-end-start-radius: 0;
}
.status-card.expanded > a { .status-card.expanded > a {
width: 100%; width: 100%;
} }
@ -8784,43 +8796,80 @@ noscript {
display: flex; display: flex;
align-items: center; align-items: center;
color: $primary-text-color; color: $primary-text-color;
text-decoration: none; padding: 16px;
padding: 15px;
border-bottom: 1px solid var(--background-border-color); border-bottom: 1px solid var(--background-border-color);
gap: 15px; gap: 16px;
&:last-child { &:last-child {
border-bottom: 0; border-bottom: 0;
} }
&:hover,
&:active,
&:focus {
color: $highlight-text-color;
.story__details__publisher,
.story__details__shared {
color: $highlight-text-color;
}
}
&__details { &__details {
flex: 1 1 auto; flex: 1 1 auto;
&__publisher { &__publisher {
color: $darker-text-color; color: $darker-text-color;
margin-bottom: 8px; margin-bottom: 8px;
font-size: 14px;
line-height: 20px;
} }
&__title { &__title {
display: block;
font-size: 19px; font-size: 19px;
line-height: 24px; line-height: 24px;
font-weight: 500; font-weight: 500;
margin-bottom: 8px; margin-bottom: 8px;
text-decoration: none;
color: $primary-text-color;
&:hover,
&:active,
&:focus {
color: $highlight-text-color;
}
} }
&__shared { &__shared {
display: flex;
align-items: center;
color: $darker-text-color; color: $darker-text-color;
gap: 8px;
justify-content: space-between;
font-size: 14px;
line-height: 20px;
& > span {
display: flex;
align-items: center;
gap: 4px;
}
&__pill {
background: var(--surface-variant-background-color);
border-radius: 4px;
color: inherit;
text-decoration: none;
padding: 4px 12px;
font-size: 12px;
font-weight: 500;
line-height: 16px;
}
&__author-link {
display: inline-flex;
align-items: center;
gap: 4px;
color: $primary-text-color;
font-weight: 500;
text-decoration: none;
&:hover,
&:active,
&:focus {
color: $highlight-text-color;
}
}
} }
strong { strong {
@ -9891,14 +9940,14 @@ noscript {
color: inherit; color: inherit;
text-decoration: none; text-decoration: none;
padding: 4px 12px; padding: 4px 12px;
background: $ui-base-color; background: var(--surface-variant-background-color);
border-radius: 4px; border-radius: 4px;
font-weight: 500; font-weight: 500;
&:hover, &:hover,
&:focus, &:focus,
&:active { &:active {
background: lighten($ui-base-color, 4%); background: var(--surface-variant-active-background-color);
} }
} }
@ -10229,6 +10278,7 @@ noscript {
} }
.more-from-author { .more-from-author {
box-sizing: border-box;
font-size: 14px; font-size: 14px;
color: $darker-text-color; color: $darker-text-color;
background: var(--surface-background-color); background: var(--surface-background-color);

View File

@ -106,4 +106,6 @@ $font-monospace: 'mastodon-font-monospace' !default;
--background-color: #{darken($ui-base-color, 8%)}; --background-color: #{darken($ui-base-color, 8%)};
--background-color-tint: #{rgba(darken($ui-base-color, 8%), 0.9)}; --background-color-tint: #{rgba(darken($ui-base-color, 8%), 0.9)};
--surface-background-color: #{darken($ui-base-color, 4%)}; --surface-background-color: #{darken($ui-base-color, 4%)};
--surface-variant-background-color: #{$ui-base-color};
--surface-variant-active-background-color: #{lighten($ui-base-color, 4%)};
} }

View File

@ -470,10 +470,7 @@ class FeedManager
check_for_blocks = status.active_mentions.pluck(:account_id) check_for_blocks = status.active_mentions.pluck(:account_id)
check_for_blocks.push(status.in_reply_to_account) if status.reply? && !status.in_reply_to_account_id.nil? check_for_blocks.push(status.in_reply_to_account) if status.reply? && !status.in_reply_to_account_id.nil?
should_filter = blocks_or_mutes?(receiver_id, check_for_blocks, :mentions) # Filter if it's from someone I blocked, in reply to someone I blocked, or mentioning someone I blocked (or muted) blocks_or_mutes?(receiver_id, check_for_blocks, :mentions) # Filter if it's from someone I blocked, in reply to someone I blocked, or mentioning someone I blocked (or muted)
should_filter ||= status.account.silenced? && !Follow.exists?(account_id: receiver_id, target_account_id: status.account_id) # Filter if the account is silenced and I'm not following them
should_filter
end end
# Check if status should not be added to the linear direct message feed # Check if status should not be added to the linear direct message feed

View File

@ -152,6 +152,7 @@ class Notification < ApplicationRecord
.limit(1), .limit(1),
query query
.joins('CROSS JOIN grouped_notifications') .joins('CROSS JOIN grouped_notifications')
.where('array_length(grouped_notifications.groups, 1) < :limit', limit: limit)
.where('notifications.id < grouped_notifications.id') .where('notifications.id < grouped_notifications.id')
.where.not("COALESCE(notifications.group_key, 'ungrouped-' || notifications.id) = ANY(grouped_notifications.groups)") .where.not("COALESCE(notifications.group_key, 'ungrouped-' || notifications.id) = ANY(grouped_notifications.groups)")
.select('notifications.*', "array_append(grouped_notifications.groups, COALESCE(notifications.group_key, 'ungrouped-' || notifications.id))") .select('notifications.*', "array_append(grouped_notifications.groups, COALESCE(notifications.group_key, 'ungrouped-' || notifications.id))")
@ -179,6 +180,7 @@ class Notification < ApplicationRecord
.limit(1), .limit(1),
query query
.joins('CROSS JOIN grouped_notifications') .joins('CROSS JOIN grouped_notifications')
.where('array_length(grouped_notifications.groups, 1) < :limit', limit: limit)
.where('notifications.id > grouped_notifications.id') .where('notifications.id > grouped_notifications.id')
.where.not("COALESCE(notifications.group_key, 'ungrouped-' || notifications.id) = ANY(grouped_notifications.groups)") .where.not("COALESCE(notifications.group_key, 'ungrouped-' || notifications.id) = ANY(grouped_notifications.groups)")
.select('notifications.*', "array_append(grouped_notifications.groups, COALESCE(notifications.group_key, 'ungrouped-' || notifications.id))") .select('notifications.*', "array_append(grouped_notifications.groups, COALESCE(notifications.group_key, 'ungrouped-' || notifications.id))")

View File

@ -45,6 +45,6 @@ class REST::NotificationGroupSerializer < ActiveModel::Serializer
end end
def paginated? def paginated?
instance_options[:group_metadata].present? !instance_options[:group_metadata].nil?
end end
end end

View File

@ -19,8 +19,8 @@ class BackupService < BaseService
def build_outbox_json!(file) def build_outbox_json!(file)
skeleton = serialize(collection_presenter, ActivityPub::CollectionSerializer) skeleton = serialize(collection_presenter, ActivityPub::CollectionSerializer)
skeleton[:@context] = full_context skeleton['@context'] = full_context
skeleton[:orderedItems] = ['!PLACEHOLDER!'] skeleton['orderedItems'] = ['!PLACEHOLDER!']
skeleton = Oj.dump(skeleton) skeleton = Oj.dump(skeleton)
prepend, append = skeleton.split('"!PLACEHOLDER!"') prepend, append = skeleton.split('"!PLACEHOLDER!"')
add_comma = false add_comma = false

27
bin/rubocop Executable file
View File

@ -0,0 +1,27 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
#
# This file was generated by Bundler.
#
# The application 'rubocop' is installed as part of a gem, and
# this file is here to facilitate running it.
#
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
bundle_binstub = File.expand_path("bundle", __dir__)
if File.file?(bundle_binstub)
if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
load(bundle_binstub)
else
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
end
end
require "rubygems"
require "bundler/setup"
load Gem.bin_path("rubocop", "rubocop")

View File

@ -1 +1,27 @@
---
ur: ur:
activerecord:
attributes:
poll:
expires_at: ڈیڈ لائن
options: اپنی مرضی
user:
agreement: سروس کا معاہدہ
email: ای میل ایڈریسز
locale: لوکل
password: پاس ورڈ
user/account:
username: یوزر نیم
user/invite_request:
text: وجہ
errors:
models:
account:
attributes:
username:
invalid: صرف حروف، نمبر اور انڈر سکور پر مشتمل ہونا چاہیے۔
reserved: محفوظ ہے
admin/webhook:
attributes:
url:
invalid: ایک درست URL نہیں ہے۔

View File

@ -291,6 +291,7 @@ be:
update_custom_emoji_html: "%{name} абнавіў эмодзі %{target}" update_custom_emoji_html: "%{name} абнавіў эмодзі %{target}"
update_domain_block_html: "%{name} абнавіў блакіроўку дамена для %{target}" update_domain_block_html: "%{name} абнавіў блакіроўку дамена для %{target}"
update_ip_block_html: "%{name} змяніў правіла для IP %{target}" update_ip_block_html: "%{name} змяніў правіла для IP %{target}"
update_report_html: "%{name} абнавіў скаргу %{target}"
update_status_html: "%{name} абнавіў допіс %{target}" update_status_html: "%{name} абнавіў допіс %{target}"
update_user_role_html: "%{name} змяніў ролю %{target}" update_user_role_html: "%{name} змяніў ролю %{target}"
deleted_account: выдалены ўліковы запіс deleted_account: выдалены ўліковы запіс
@ -779,6 +780,7 @@ be:
desc_html: Гэта функцыянальнасць залежыць ад знешніх скрыптоў hCaptcha, што можа быць праблемай бяспекі і прыватнасці. Акрамя таго, <strong>гэта можа зрабіць працэс рэгістрацыі значна менш даступным для некаторых людзей, асабліва інвалідаў</strong>. Па гэтых прычынах, калі ласка, разгледзьце альтэрнатыўныя меры, такія як рэгістрацыя на аснове зацвярджэння або запрашэння. desc_html: Гэта функцыянальнасць залежыць ад знешніх скрыптоў hCaptcha, што можа быць праблемай бяспекі і прыватнасці. Акрамя таго, <strong>гэта можа зрабіць працэс рэгістрацыі значна менш даступным для некаторых людзей, асабліва інвалідаў</strong>. Па гэтых прычынах, калі ласка, разгледзьце альтэрнатыўныя меры, такія як рэгістрацыя на аснове зацвярджэння або запрашэння.
title: Патрабаваць ад новых карыстальнікаў рашэння CAPTCHA для пацверджання іх уліковага запісу title: Патрабаваць ад новых карыстальнікаў рашэння CAPTCHA для пацверджання іх уліковага запісу
content_retention: content_retention:
danger_zone: Небяспечная зона
preamble: Кантралюйце, як створаны карыстальнікамі кантэнт захоўваецца ў Mastodon. preamble: Кантралюйце, як створаны карыстальнікамі кантэнт захоўваецца ў Mastodon.
title: Утрыманне кантэнту title: Утрыманне кантэнту
default_noindex: default_noindex:
@ -983,6 +985,7 @@ be:
delete: Выдаліць delete: Выдаліць
edit_preset: Рэдагаваць шаблон папярэджання edit_preset: Рэдагаваць шаблон папярэджання
empty: Вы яшчэ не вызначылі ніякіх шаблонаў папярэджанняў. empty: Вы яшчэ не вызначылі ніякіх шаблонаў папярэджанняў.
title: Папярэджальныя прадусталёўкі
webhooks: webhooks:
add_new: Дадаць канцавую кропку add_new: Дадаць канцавую кропку
delete: Выдаліць delete: Выдаліць

View File

@ -135,6 +135,7 @@ be:
media: Мультымедыйныя далучэнні media: Мультымедыйныя далучэнні
mutes: Ігнараваныя mutes: Ігнараваныя
notifications: Апавяшчэнні notifications: Апавяшчэнні
profile: Ваш профіль Mastodon
push: Push-апавяшчэнні push: Push-апавяшчэнні
reports: Скаргі reports: Скаргі
search: Пошук search: Пошук
@ -165,6 +166,7 @@ be:
admin:write:reports: мадэраваць скаргі admin:write:reports: мадэраваць скаргі
crypto: выкарыстоўваць скразное шыфраванне (end-to-end) crypto: выкарыстоўваць скразное шыфраванне (end-to-end)
follow: змяняць зносіны ўліковага запісу follow: змяняць зносіны ўліковага запісу
profile: чытаць толькі інфармацыю профілю вашага ўліковага запісу
push: атрымліваць push-апавяшчэнні push: атрымліваць push-апавяшчэнні
read: чытаць усе даныя вашага ўліковага запісу read: чытаць усе даныя вашага ўліковага запісу
read:accounts: бачыць інфармацыю аб уліковых запісах read:accounts: бачыць інфармацыю аб уліковых запісах

View File

@ -135,6 +135,7 @@ cs:
media: Mediální přílohy media: Mediální přílohy
mutes: Skrytí mutes: Skrytí
notifications: Oznámení notifications: Oznámení
profile: Váš Mastodon profil
push: Push oznámení push: Push oznámení
reports: Hlášení reports: Hlášení
search: Hledání search: Hledání

View File

@ -135,6 +135,7 @@ fy:
media: Mediabylagen media: Mediabylagen
mutes: Negearre mutes: Negearre
notifications: Meldingen notifications: Meldingen
profile: Jo Mastodon-profyl
push: Pushmeldingen push: Pushmeldingen
reports: Rapportaazjes reports: Rapportaazjes
search: Sykje search: Sykje
@ -165,6 +166,7 @@ fy:
admin:write:reports: moderaasjemaatregelen nimme yn rapportaazjes admin:write:reports: moderaasjemaatregelen nimme yn rapportaazjes
crypto: ein-ta-ein-fersifering brûke crypto: ein-ta-ein-fersifering brûke
follow: relaasjes tusken accounts bewurkje follow: relaasjes tusken accounts bewurkje
profile: allinnich de profylgegevens fan jo account lêze
push: jo pushmeldingen ûntfange push: jo pushmeldingen ûntfange
read: alle gegevens fan jo account lêze read: alle gegevens fan jo account lêze
read:accounts: accountynformaasje besjen read:accounts: accountynformaasje besjen

View File

@ -135,6 +135,7 @@ ia:
media: Annexos multimedial media: Annexos multimedial
mutes: Silentiates mutes: Silentiates
notifications: Notificationes notifications: Notificationes
profile: Tu profilo de Mastodon
push: Notificationes push push: Notificationes push
reports: Reportos reports: Reportos
search: Cercar search: Cercar
@ -165,6 +166,7 @@ ia:
admin:write:reports: exequer actiones de moderation sur reportos admin:write:reports: exequer actiones de moderation sur reportos
crypto: usar cryptation de puncta a puncta crypto: usar cryptation de puncta a puncta
follow: modificar relationes inter contos follow: modificar relationes inter contos
profile: leger solmente le information de profilo de tu conto
push: reciper tu notificationes push push: reciper tu notificationes push
read: leger tote le datos de tu conto read: leger tote le datos de tu conto
read:accounts: vider informationes de contos read:accounts: vider informationes de contos

View File

@ -135,6 +135,7 @@ sl:
media: Predstavnostne priloge media: Predstavnostne priloge
mutes: Utišani mutes: Utišani
notifications: Obvestila notifications: Obvestila
profile: Vaš profil Mastodon
push: Potisna obvestila push: Potisna obvestila
reports: Prijave reports: Prijave
search: Iskanje search: Iskanje
@ -165,6 +166,7 @@ sl:
admin:write:reports: izvedi moderirana dejanja na prijavah admin:write:reports: izvedi moderirana dejanja na prijavah
crypto: Uporabi šifriranje od konca do konca crypto: Uporabi šifriranje od konca do konca
follow: spremeni razmerja med računi follow: spremeni razmerja med računi
profile: preberi le podatke profila računa
push: prejmi potisna obvestila push: prejmi potisna obvestila
read: preberi vse podatke svojega računa read: preberi vse podatke svojega računa
read:accounts: oglejte si podrobnosti računov read:accounts: oglejte si podrobnosti računov

View File

@ -166,6 +166,7 @@ sv:
admin:write:reports: utföra modereringsåtgärder på rapporter admin:write:reports: utföra modereringsåtgärder på rapporter
crypto: använd obruten kryptering crypto: använd obruten kryptering
follow: modifiera kontorelationer follow: modifiera kontorelationer
profile: läs endast ditt kontos profilinformation
push: ta emot dina push-notiser push: ta emot dina push-notiser
read: läsa dina kontodata read: läsa dina kontodata
read:accounts: se kontoinformation read:accounts: se kontoinformation

View File

@ -285,6 +285,7 @@ fy:
update_custom_emoji_html: Emoji %{target} is troch %{name} bywurke update_custom_emoji_html: Emoji %{target} is troch %{name} bywurke
update_domain_block_html: "%{name} hat de domeinblokkade bywurke foar %{target}" update_domain_block_html: "%{name} hat de domeinblokkade bywurke foar %{target}"
update_ip_block_html: "%{name} hat de rigel foar IP %{target} wizige" update_ip_block_html: "%{name} hat de rigel foar IP %{target} wizige"
update_report_html: Rapportaazje %{target} is troch %{name} bywurke
update_status_html: "%{name} hat de berjochten %{target} bywurke" update_status_html: "%{name} hat de berjochten %{target} bywurke"
update_user_role_html: "%{name} hat de rol %{target} wizige" update_user_role_html: "%{name} hat de rol %{target} wizige"
deleted_account: fuortsmiten account deleted_account: fuortsmiten account

View File

@ -77,10 +77,15 @@ be:
warn: Схаваць адфільтраваны кантэнт за папярэджаннем з назвай фільтру warn: Схаваць адфільтраваны кантэнт за папярэджаннем з назвай фільтру
form_admin_settings: form_admin_settings:
activity_api_enabled: Падлік лакальна апублікаваных пастоў, актыўных карыстальнікаў і новых рэгістрацый у тыдзень activity_api_enabled: Падлік лакальна апублікаваных пастоў, актыўных карыстальнікаў і новых рэгістрацый у тыдзень
app_icon: WEBP, PNG, GIF ці JPG. Заменіце прадвызначаны значок праграмы на мабільных прыладах карыстальніцкім значком.
backups_retention_period: Карыстальнікі могуць ствараць архівы сваіх допісаў для наступнай запампоўкі. Пры станоўчай колькасці дзён гэтыя архівы будуць аўтаматычна выдаляцца са сховішча пасля заканчэння названай колькасці дзён.
bootstrap_timeline_accounts: Гэтыя ўліковыя запісы будуць замацаваны ў топе рэкамендацый для новых карыстальнікаў. bootstrap_timeline_accounts: Гэтыя ўліковыя запісы будуць замацаваны ў топе рэкамендацый для новых карыстальнікаў.
closed_registrations_message: Паказваецца, калі рэгістрацыя закрытая closed_registrations_message: Паказваецца, калі рэгістрацыя закрытая
content_cache_retention_period: Усе допісы з іншых сервераў (уключаючы пашырэнні і адказы) будуць выдаленыя праз паказаную колькасць дзён, незалежна ад таго, як лакальны карыстальнік узаемадзейнічаў з гэтымі допісамі. Гэта датычыцца і тых допісаў, якія лакальны карыстальнік пазначыў у закладкі або ўпадабанае. Прыватныя згадкі паміж карыстальнікамі з розных інстанс таксама будуць страчаныя і не змогуць быць адноўлены. Выкарыстанне гэтай налады прызначана для асобнікаў спецыяльнага прызначэння і парушае многія чаканні карыстальнікаў пры выкарыстанні ў агульных мэтах.
custom_css: Вы можаце прымяняць карыстальніцкія стылі ў вэб-версіі Mastodon. custom_css: Вы можаце прымяняць карыстальніцкія стылі ў вэб-версіі Mastodon.
favicon: WEBP, PNG, GIF ці JPG. Замяняе прадвызначаны favicon Mastodon на ўласны значок.
mascot: Замяняе ілюстрацыю ў пашыраным вэб-інтэрфейсе. mascot: Замяняе ілюстрацыю ў пашыраным вэб-інтэрфейсе.
media_cache_retention_period: Медыяфайлы з допісаў, зробленых выдаленымі карыстальнікамі, кэшыруюцца на вашым серверы. Пры станоўчым значэнні медыяфайлы будуць выдалены праз пазначаную колькасць дзён. Калі медыядадзеныя будуць запытаны пасля выдалення, яны будуць загружаны паўторна, калі зыходны кантэнт усё яшчэ даступны. У сувязі з абмежаваннямі на частату абнаўлення відарысаў іншых сайтаў, рэкамендуецца ўсталяваць значэнне не менш за 14 дзён, інакш відарысы не будуць загружацца па запыце раней за гэты тэрмін.
peers_api_enabled: Спіс даменных імён, з якімі сутыкнуўся гэты сервер у федэсвеце. Дадзеныя аб тым, ці знаходзіцеся вы з пэўным серверам у федэрацыі, не ўключаныя, ёсць толькі тое, што ваш сервер ведае пра гэта. Гэта выкарыстоўваецца сэрвісамі, якія збіраюць статыстыку па федэрацыі ў агульным сэнсе. peers_api_enabled: Спіс даменных імён, з якімі сутыкнуўся гэты сервер у федэсвеце. Дадзеныя аб тым, ці знаходзіцеся вы з пэўным серверам у федэрацыі, не ўключаныя, ёсць толькі тое, што ваш сервер ведае пра гэта. Гэта выкарыстоўваецца сэрвісамі, якія збіраюць статыстыку па федэрацыі ў агульным сэнсе.
profile_directory: Дырэкторыя профіляў змяшчае спіс усіх карыстальнікаў, якія вырашылі быць бачнымі. profile_directory: Дырэкторыя профіляў змяшчае спіс усіх карыстальнікаў, якія вырашылі быць бачнымі.
require_invite_text: Калі рэгістрацыя патрабуе ручнога пацвержання, зрабіце поле "Чаму вы хочаце далучыцца?" абавязковым require_invite_text: Калі рэгістрацыя патрабуе ручнога пацвержання, зрабіце поле "Чаму вы хочаце далучыцца?" абавязковым
@ -240,6 +245,7 @@ be:
backups_retention_period: Працягласць захавання архіву карыстальніка backups_retention_period: Працягласць захавання архіву карыстальніка
bootstrap_timeline_accounts: Заўсёды раіць гэтыя ўліковыя запісы новым карыстальнікам bootstrap_timeline_accounts: Заўсёды раіць гэтыя ўліковыя запісы новым карыстальнікам
closed_registrations_message: Уласнае паведамленне, калі рэгістрацыя немагчымая closed_registrations_message: Уласнае паведамленне, калі рэгістрацыя немагчымая
content_cache_retention_period: Перыяд захоўвання выдаленага змесціва
custom_css: CSS карыстальніка custom_css: CSS карыстальніка
mascot: Уласны маскот(спадчына) mascot: Уласны маскот(спадчына)
media_cache_retention_period: Працягласць захавання кэшу для медыя media_cache_retention_period: Працягласць захавання кэшу для медыя

View File

@ -77,11 +77,13 @@ fy:
warn: Ferstopje de filtere ynhâld efter in warskôging, mei de titel fan it filter as warskôgingstekst warn: Ferstopje de filtere ynhâld efter in warskôging, mei de titel fan it filter as warskôgingstekst
form_admin_settings: form_admin_settings:
activity_api_enabled: Tal lokaal publisearre artikelen, aktive brûkers en nije registraasjes yn wyklikse werjefte activity_api_enabled: Tal lokaal publisearre artikelen, aktive brûkers en nije registraasjes yn wyklikse werjefte
app_icon: WEBP, PNG, GIF of JPG. Ferfangt op mobile apparaten it standert app-pictogram mei in oanpast piktogram.
backups_retention_period: Brûkers hawwe de mooglikheid om argiven fan harren berjochten te generearjen om letter te downloaden. Wannear ynsteld op in positive wearde, wurde dizze argiven automatysk fuortsmiten út jo ûnthâld nei it opjûne oantal dagen. backups_retention_period: Brûkers hawwe de mooglikheid om argiven fan harren berjochten te generearjen om letter te downloaden. Wannear ynsteld op in positive wearde, wurde dizze argiven automatysk fuortsmiten út jo ûnthâld nei it opjûne oantal dagen.
bootstrap_timeline_accounts: Dizze accounts wurde boppe oan de oanrekommandaasjes oan nije brûkers toand. Meardere brûkersnammen troch kommas skiede. bootstrap_timeline_accounts: Dizze accounts wurde boppe oan de oanrekommandaasjes oan nije brûkers toand. Meardere brûkersnammen troch kommas skiede.
closed_registrations_message: Werjûn wanneart registraasje fan nije accounts útskeakele is closed_registrations_message: Werjûn wanneart registraasje fan nije accounts útskeakele is
content_cache_retention_period: Alle berjochten fan oare servers (ynklusyf boosts en reaksjes) wurde fuortsmiten nei it opjûne oantal dagen, nettsjinsteande iennige lokale brûkersynteraksje mei dy berjochten. Dit oanbelanget ek berjochten dyt in lokale brûker oan harren blêdwizers tafoege hat of as favoryt markearre hat. Priveeberjochten tusken brûkers fan ferskate servers gean ek ferlern en binne ûnmooglik te werstellen. It gebrûk fan dizze ynstelling is bedoeld foar servers dyt in spesjaal doel tsjinje en oertrêdet in protte brûkersferwachtingen wanneart dizze foar algemien gebrûk ymplemintearre wurdt. content_cache_retention_period: Alle berjochten fan oare servers (ynklusyf boosts en reaksjes) wurde fuortsmiten nei it opjûne oantal dagen, nettsjinsteande iennige lokale brûkersynteraksje mei dy berjochten. Dit oanbelanget ek berjochten dyt in lokale brûker oan harren blêdwizers tafoege hat of as favoryt markearre hat. Priveeberjochten tusken brûkers fan ferskate servers gean ek ferlern en binne ûnmooglik te werstellen. It gebrûk fan dizze ynstelling is bedoeld foar servers dyt in spesjaal doel tsjinje en oertrêdet in protte brûkersferwachtingen wanneart dizze foar algemien gebrûk ymplemintearre wurdt.
custom_css: Jo kinne oanpaste CSS tapasse op de webferzje fan dizze Mastodon-server. custom_css: Jo kinne oanpaste CSS tapasse op de webferzje fan dizze Mastodon-server.
favicon: WEBP, PNG, GIF of JPG. Ferfangt de standert Mastodon-favicon mei in oanpast piktogram.
mascot: Oerskriuwt de yllustraasje yn de avansearre webomjouwing. mascot: Oerskriuwt de yllustraasje yn de avansearre webomjouwing.
media_cache_retention_period: Mediabestannen fan berjochten fan eksterne brûkers wurde op jo server yn de buffer bewarre. Wannear ynsteld op in positive wearde, wurde media fuortsmiten nei it opjûne oantal dagen. As de mediagegevens opfrege wurde neidat se fuortsmiten binne, wurde se opnij download wannear de orizjinele ynhâld noch hieltyd beskikber is. Fanwegen beheiningen op hoe faak keppelingsfoarbylden websites fan tredden rieplachtsje, wurdt oanrekommandearre om dizze wearde yn te stellen op op syn minste 14 dagen. Oars wurde keppelingsfoarbylden net op oanfraach bywurke. media_cache_retention_period: Mediabestannen fan berjochten fan eksterne brûkers wurde op jo server yn de buffer bewarre. Wannear ynsteld op in positive wearde, wurde media fuortsmiten nei it opjûne oantal dagen. As de mediagegevens opfrege wurde neidat se fuortsmiten binne, wurde se opnij download wannear de orizjinele ynhâld noch hieltyd beskikber is. Fanwegen beheiningen op hoe faak keppelingsfoarbylden websites fan tredden rieplachtsje, wurdt oanrekommandearre om dizze wearde yn te stellen op op syn minste 14 dagen. Oars wurde keppelingsfoarbylden net op oanfraach bywurke.
peers_api_enabled: In list mei domeinnammen, dêrt dizze server yn fediverse kontakt hân mei hat. Hjir wurdt gjin data dield, oft jo mei in bepaalde server federearrest, mar alinnich, dat jo server dat wit. Dit wurdt foar tsjinsten brûkt, dyt statistiken oer federaasje yn algemiene sin sammelet. peers_api_enabled: In list mei domeinnammen, dêrt dizze server yn fediverse kontakt hân mei hat. Hjir wurdt gjin data dield, oft jo mei in bepaalde server federearrest, mar alinnich, dat jo server dat wit. Dit wurdt foar tsjinsten brûkt, dyt statistiken oer federaasje yn algemiene sin sammelet.

View File

@ -77,11 +77,13 @@ sv:
warn: Dölj det filtrerade innehållet bakom en varning som visar filtrets rubrik warn: Dölj det filtrerade innehållet bakom en varning som visar filtrets rubrik
form_admin_settings: form_admin_settings:
activity_api_enabled: Antalet lokalt publicerade inlägg, aktiva användare och nya registrerade konton per vecka activity_api_enabled: Antalet lokalt publicerade inlägg, aktiva användare och nya registrerade konton per vecka
app_icon: WEBP, PNG, GIF eller JPG. Använd istället för appens egna ikon på mobila enheter.
backups_retention_period: Användare har möjlighet att generera arkiv av sina inlägg för att ladda ned senare. När det sätts till ett positivt värde raderas dessa arkiv automatiskt från din lagring efter det angivna antalet dagar. backups_retention_period: Användare har möjlighet att generera arkiv av sina inlägg för att ladda ned senare. När det sätts till ett positivt värde raderas dessa arkiv automatiskt från din lagring efter det angivna antalet dagar.
bootstrap_timeline_accounts: Dessa konton kommer fästas högst upp i nya användares följrekommendationer. bootstrap_timeline_accounts: Dessa konton kommer fästas högst upp i nya användares följrekommendationer.
closed_registrations_message: Visas när nyregistreringar är avstängda closed_registrations_message: Visas när nyregistreringar är avstängda
content_cache_retention_period: Alla inlägg från andra servrar (inklusive booster och svar) kommer att raderas efter det angivna antalet dagar, utan hänsyn till någon lokal användarinteraktion med dessa inlägg. Detta inkluderar inlägg där en lokal användare har markerat det som bokmärke eller favoriter. Privata omnämnanden mellan användare från olika instanser kommer också att gå förlorade och blir omöjliga att återställa. Användningen av denna inställning är avsedd för specialfall och bryter många användarförväntningar när de implementeras för allmänt bruk. content_cache_retention_period: Alla inlägg från andra servrar (inklusive booster och svar) kommer att raderas efter det angivna antalet dagar, utan hänsyn till någon lokal användarinteraktion med dessa inlägg. Detta inkluderar inlägg där en lokal användare har markerat det som bokmärke eller favoriter. Privata omnämnanden mellan användare från olika instanser kommer också att gå förlorade och blir omöjliga att återställa. Användningen av denna inställning är avsedd för specialfall och bryter många användarförväntningar när de implementeras för allmänt bruk.
custom_css: Du kan använda anpassade stilar på webbversionen av Mastodon. custom_css: Du kan använda anpassade stilar på webbversionen av Mastodon.
favicon: WEBP, PNG, GIF eller JPG. Används på mobila enheter istället för appens egen ikon.
mascot: Åsidosätter illustrationen i det avancerade webbgränssnittet. mascot: Åsidosätter illustrationen i det avancerade webbgränssnittet.
media_cache_retention_period: Mediafiler från inlägg som gjorts av fjärranvändare cachas på din server. När inställd på ett positivt värde kommer media att raderas efter det angivna antalet dagar. Om mediadatat begärs efter att det har raderats, kommer det att laddas ned igen om källinnehållet fortfarande är tillgängligt. På grund av begränsningar för hur ofta förhandsgranskningskort för länkar hämtas från tredjepartswebbplatser, rekommenderas det att ange detta värde till minst 14 dagar, annars kommer förhandsgranskningskorten inte att uppdateras på begäran före den tiden. media_cache_retention_period: Mediafiler från inlägg som gjorts av fjärranvändare cachas på din server. När inställd på ett positivt värde kommer media att raderas efter det angivna antalet dagar. Om mediadatat begärs efter att det har raderats, kommer det att laddas ned igen om källinnehållet fortfarande är tillgängligt. På grund av begränsningar för hur ofta förhandsgranskningskort för länkar hämtas från tredjepartswebbplatser, rekommenderas det att ange detta värde till minst 14 dagar, annars kommer förhandsgranskningskorten inte att uppdateras på begäran före den tiden.
peers_api_enabled: En lista över domänen den här servern har stött på i fediversum. Ingen data inkluderas om du har federerat med servern, bara att din server känner till den. Detta används av tjänster som samlar statistik om federering i allmänhet. peers_api_enabled: En lista över domänen den här servern har stött på i fediversum. Ingen data inkluderas om du har federerat med servern, bara att din server känner till den. Detta används av tjänster som samlar statistik om federering i allmänhet.

View File

@ -951,6 +951,7 @@ sv:
delete: Radera delete: Radera
edit_preset: Redigera varningsförval edit_preset: Redigera varningsförval
empty: Du har inte definierat några varningsförval ännu. empty: Du har inte definierat några varningsförval ännu.
title: Varning förinställningar
webhooks: webhooks:
add_new: Lägg till slutpunkt add_new: Lägg till slutpunkt
delete: Ta bort delete: Ta bort

View File

@ -1,7 +1,6 @@
const config = { const config = {
'*': 'prettier --ignore-unknown --write', '*': 'prettier --ignore-unknown --write',
'Capfile|Gemfile|*.{rb,ruby,ru,rake}': 'Capfile|Gemfile|*.{rb,ruby,ru,rake}': 'bin/rubocop --force-exclusion -a',
'bundle exec rubocop --force-exclusion -a',
'*.{js,jsx,ts,tsx}': 'eslint --fix', '*.{js,jsx,ts,tsx}': 'eslint --fix',
'*.{css,scss}': 'stylelint --fix', '*.{css,scss}': 'stylelint --fix',
'*.haml': 'bundle exec haml-lint -a', '*.haml': 'bundle exec haml-lint -a',

View File

@ -1,7 +1,7 @@
{ {
"name": "@mastodon/mastodon", "name": "@mastodon/mastodon",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"packageManager": "yarn@4.2.2", "packageManager": "yarn@4.3.0",
"engines": { "engines": {
"node": ">=18" "node": ">=18"
}, },

View File

@ -213,13 +213,13 @@ RSpec.describe FeedManager do
expect(described_class.instance.filter?(:mentions, reply, bob)).to be true expect(described_class.instance.filter?(:mentions, reply, bob)).to be true
end end
it 'returns true for status by silenced account who recipient is not following' do it 'returns false for status by limited account who recipient is not following' do
status = Fabricate(:status, text: 'Hello world', account: alice) status = Fabricate(:status, text: 'Hello world', account: alice)
alice.silence! alice.silence!
expect(described_class.instance.filter?(:mentions, status, bob)).to be true expect(described_class.instance.filter?(:mentions, status, bob)).to be false
end end
it 'returns false for status by followed silenced account' do it 'returns false for status by followed limited account' do
status = Fabricate(:status, text: 'Hello world', account: alice) status = Fabricate(:status, text: 'Hello world', account: alice)
alice.silence! alice.silence!
bob.follow!(alice) bob.follow!(alice)

View File

@ -58,6 +58,7 @@ RSpec.describe 'Notifications' do
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(body_json_types.uniq).to eq ['mention'] expect(body_json_types.uniq).to eq ['mention']
expect(body_as_json[0][:page_min_id]).to_not be_nil
end end
end end

View File

@ -55,9 +55,11 @@ RSpec.describe BackupService do
end end
def expect_outbox_export def expect_outbox_export
json = export_json(:outbox) body = export_json_raw(:outbox)
json = Oj.load(body)
aggregate_failures do aggregate_failures do
expect(body.scan('@context').count).to eq 1
expect(json['@context']).to_not be_nil expect(json['@context']).to_not be_nil
expect(json['type']).to eq 'OrderedCollection' expect(json['type']).to eq 'OrderedCollection'
expect(json['totalItems']).to eq 2 expect(json['totalItems']).to eq 2
@ -85,8 +87,12 @@ RSpec.describe BackupService do
end end
end end
def export_json_raw(type)
read_zip_file(backup, "#{type}.json")
end
def export_json(type) def export_json(type)
Oj.load(read_zip_file(backup, "#{type}.json")) Oj.load(export_json_raw(type))
end end
def include_create_item(status) def include_create_item(status)

View File

@ -129,6 +129,35 @@ RSpec.describe NotifyService do
end end
end end
describe NotifyService::DismissCondition do
subject { described_class.new(notification) }
let(:activity) { Fabricate(:mention, status: Fabricate(:status)) }
let(:notification) { Fabricate(:notification, type: :mention, activity: activity, from_account: activity.status.account, account: activity.account) }
describe '#dismiss?' do
context 'when sender is silenced' do
before do
notification.from_account.silence!
end
it 'returns false' do
expect(subject.dismiss?).to be false
end
end
context 'when recipient has blocked sender' do
before do
notification.account.block!(notification.from_account)
end
it 'returns true' do
expect(subject.dismiss?).to be true
end
end
end
end
describe NotifyService::FilterCondition do describe NotifyService::FilterCondition do
subject { described_class.new(notification) } subject { described_class.new(notification) }

View File

@ -1,7 +1,7 @@
{ {
"name": "@mastodon/streaming", "name": "@mastodon/streaming",
"license": "AGPL-3.0-or-later", "license": "AGPL-3.0-or-later",
"packageManager": "yarn@4.2.2", "packageManager": "yarn@4.3.0",
"engines": { "engines": {
"node": ">=18" "node": ">=18"
}, },

View File

@ -13117,8 +13117,8 @@ __metadata:
linkType: hard linkType: hard
"pino@npm:^9.0.0": "pino@npm:^9.0.0":
version: 9.1.0 version: 9.2.0
resolution: "pino@npm:9.1.0" resolution: "pino@npm:9.2.0"
dependencies: dependencies:
atomic-sleep: "npm:^1.0.0" atomic-sleep: "npm:^1.0.0"
fast-redact: "npm:^3.1.1" fast-redact: "npm:^3.1.1"
@ -13133,7 +13133,7 @@ __metadata:
thread-stream: "npm:^3.0.0" thread-stream: "npm:^3.0.0"
bin: bin:
pino: bin.js pino: bin.js
checksum: 10c0/d060530ae2e4e8f21d04bb0f44f009f94d207d7f4337f508f618416514214ddaf1b29f8c5c265153a19ce3b6480b451461f40020f916ace9d53a5aa07624b79c checksum: 10c0/5fbd226ff7dab0961232b5aa5eca0530cdc5bb29f6bf17d929e42239293b1a587a26cc311db6abc1090c9dd57e8f7b031eae341b41d00d4a642b4f1736474c80
languageName: node languageName: node
linkType: hard linkType: hard
@ -15379,15 +15379,15 @@ __metadata:
linkType: hard linkType: hard
"sass@npm:^1.62.1": "sass@npm:^1.62.1":
version: 1.77.4 version: 1.77.5
resolution: "sass@npm:1.77.4" resolution: "sass@npm:1.77.5"
dependencies: dependencies:
chokidar: "npm:>=3.0.0 <4.0.0" chokidar: "npm:>=3.0.0 <4.0.0"
immutable: "npm:^4.0.0" immutable: "npm:^4.0.0"
source-map-js: "npm:>=0.6.2 <2.0.0" source-map-js: "npm:>=0.6.2 <2.0.0"
bin: bin:
sass: sass.js sass: sass.js
checksum: 10c0/b9cb4882bded282aabe38d011adfce375e1f282184fcf93dc3da5d5be834c6aa53c474c15634c351ef7bd85146cfd1cc81343654cc3bcf000d78e856da4225ef checksum: 10c0/9da049b0a3fadab419084d6becdf471e107cf6e3c8ac87cabea2feb845afac75e86c99e06ee721a5aa4f6a2d833ec5380137c4e540ab2f760edf1e4eb6139e69
languageName: node languageName: node
linkType: hard linkType: hard