Merge commit '5652ca613582df03e5b838626078981414f3b897' into glitch-soc/merge-upstream

shrike
Claire 2024-06-06 12:27:26 +02:00
commit 30b00ca2b5
136 changed files with 3195 additions and 1823 deletions

View File

@ -1,5 +1,5 @@
# For details, see https://github.com/devcontainers/images/tree/main/src/ruby # For details, see https://github.com/devcontainers/images/tree/main/src/ruby
FROM mcr.microsoft.com/devcontainers/ruby:1-3.2-bullseye FROM mcr.microsoft.com/devcontainers/ruby:1-3.3-bookworm
# Install Rails # Install Rails
# RUN gem install rails webdrivers # RUN gem install rails webdrivers
@ -9,7 +9,7 @@ RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSI
# [Optional] Uncomment this section to install additional OS packages. # [Optional] Uncomment this section to install additional OS packages.
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install --no-install-recommends libicu-dev libidn11-dev ffmpeg imagemagick libpam-dev && apt-get -y install --no-install-recommends libicu-dev libidn11-dev ffmpeg imagemagick libvips42 libpam-dev
# [Optional] Uncomment this line to install additional gems. # [Optional] Uncomment this line to install additional gems.
RUN gem install foreman RUN gem install foreman

View File

@ -37,7 +37,7 @@
}, },
"onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}", "onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}",
"postCreateCommand": ".devcontainer/post-create.sh", "postCreateCommand": "bin/setup",
"waitFor": "postCreateCommand", "waitFor": "postCreateCommand",
"customizations": { "customizations": {

View File

@ -23,12 +23,14 @@
} }
}, },
"remoteUser": "root",
"otherPortsAttributes": { "otherPortsAttributes": {
"onAutoForward": "silent" "onAutoForward": "silent"
}, },
"onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}", "onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}",
"postCreateCommand": ".devcontainer/post-create.sh", "postCreateCommand": "bin/setup",
"waitFor": "postCreateCommand", "waitFor": "postCreateCommand",
"customizations": { "customizations": {

View File

@ -1,5 +1,3 @@
version: '3'
services: services:
app: app:
working_dir: /workspaces/mastodon/ working_dir: /workspaces/mastodon/

View File

@ -1,27 +0,0 @@
#!/bin/bash
set -e # Fail the whole script on first error
# Fetch Ruby gem dependencies
bundle config path 'vendor/bundle'
bundle config with 'development test'
bundle install
# Make Gemfile.lock pristine again
git checkout -- Gemfile.lock
# Fetch Javascript dependencies
corepack prepare
yarn install --immutable
# [re]create, migrate, and seed the test database
RAILS_ENV=test ./bin/rails db:setup
# [re]create, migrate, and seed the development database
RAILS_ENV=development ./bin/rails db:setup
# Precompile assets for development
RAILS_ENV=development ./bin/rails assets:precompile
# Precompile assets for test
RAILS_ENV=test ./bin/rails assets:precompile

View File

@ -14,7 +14,7 @@ runs:
shell: bash shell: bash
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y libicu-dev libidn11-dev ${{ inputs.additional-system-dependencies }} sudo apt-get install -y libicu-dev libidn11-dev libvips42 ${{ inputs.additional-system-dependencies }}
- name: Set up Ruby - name: Set up Ruby
uses: ruby/setup-ruby@v1 uses: ruby/setup-ruby@v1

View File

@ -133,7 +133,7 @@ jobs:
uses: ./.github/actions/setup-ruby uses: ./.github/actions/setup-ruby
with: with:
ruby-version: ${{ matrix.ruby-version}} ruby-version: ${{ matrix.ruby-version}}
additional-system-dependencies: ffmpeg imagemagick libpam-dev additional-system-dependencies: ffmpeg libpam-dev
- name: Load database schema - name: Load database schema
run: './bin/rails db:create db:schema:load db:seed' run: './bin/rails db:create db:schema:load db:seed'
@ -148,6 +148,93 @@ jobs:
env: env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
test-libvips:
name: Libvips tests
runs-on: ubuntu-24.04
needs:
- build
services:
postgres:
image: postgres:14-alpine
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:
DB_HOST: localhost
DB_USER: postgres
DB_PASS: postgres
DISABLE_SIMPLECOV: ${{ matrix.ruby-version != '.ruby-version' }}
RAILS_ENV: test
ALLOW_NOPAM: true
PAM_ENABLED: true
PAM_DEFAULT_SERVICE: pam_test
PAM_CONTROLLED_SERVICE: pam_test_controlled
OIDC_ENABLED: true
OIDC_SCOPE: read
SAML_ENABLED: true
CAS_ENABLED: true
BUNDLE_WITH: 'pam_authentication test'
GITHUB_RSPEC: ${{ matrix.ruby-version == '.ruby-version' && github.event.pull_request && 'true' }}
MASTODON_USE_LIBVIPS: true
strategy:
fail-fast: false
matrix:
ruby-version:
- '3.1'
- '3.2'
- '.ruby-version'
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
path: './'
name: ${{ github.sha }}
- name: Expand archived asset artifacts
run: |
tar xvzf artifacts.tar.gz
- name: Set up Ruby environment
uses: ./.github/actions/setup-ruby
with:
ruby-version: ${{ matrix.ruby-version}}
additional-system-dependencies: ffmpeg libpam-dev libyaml-dev
- name: Load database schema
run: './bin/rails db:create db:schema:load db:seed'
- run: bin/rspec --tag paperclip_processing
- name: Upload coverage reports to Codecov
if: matrix.ruby-version == '.ruby-version'
uses: codecov/codecov-action@v4
with:
files: coverage/lcov/mastodon.lcov
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
test-e2e: test-e2e:
name: End to End testing name: End to End testing
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -209,7 +296,7 @@ jobs:
uses: ./.github/actions/setup-ruby uses: ./.github/actions/setup-ruby
with: with:
ruby-version: ${{ matrix.ruby-version}} ruby-version: ${{ matrix.ruby-version}}
additional-system-dependencies: ffmpeg imagemagick additional-system-dependencies: ffmpeg
- name: Set up Javascript environment - name: Set up Javascript environment
uses: ./.github/actions/setup-javascript uses: ./.github/actions/setup-javascript
@ -329,7 +416,7 @@ jobs:
uses: ./.github/actions/setup-ruby uses: ./.github/actions/setup-ruby
with: with:
ruby-version: ${{ matrix.ruby-version}} ruby-version: ${{ matrix.ruby-version}}
additional-system-dependencies: ffmpeg imagemagick additional-system-dependencies: ffmpeg
- name: Set up Javascript environment - name: Set up Javascript environment
uses: ./.github/actions/setup-javascript uses: ./.github/actions/setup-javascript

View File

@ -1,19 +0,0 @@
.DS_Store
.git/
.gitignore
.bundle/
.cache/
config/deploy/*
coverage
docs/
.env
log/*.log
neo4j/
node_modules/
public/assets/
public/system/
spec/
tmp/
.vagrant/
vendor/bundle/

2
.nvmrc
View File

@ -1 +1 @@
20.13 20.14

View File

@ -1 +1 @@
3.3.1 3.3.2

View File

@ -1,5 +1,8 @@
# syntax=docker/dockerfile:1.7 # syntax=docker/dockerfile:1.7
# This file is designed for production server deployment, not local development work
# For a containerized local dev environment, see: https://github.com/mastodon/mastodon/blob/main/README.md#docker
# Please see https://docs.docker.com/engine/reference/builder for information about # Please see https://docs.docker.com/engine/reference/builder for information about
# the extended buildx capabilities used in this file. # the extended buildx capabilities used in this file.
# Make sure multiarch TARGETPLATFORM is available for interpolation # Make sure multiarch TARGETPLATFORM is available for interpolation
@ -7,22 +10,22 @@
ARG TARGETPLATFORM=${TARGETPLATFORM} ARG TARGETPLATFORM=${TARGETPLATFORM}
ARG BUILDPLATFORM=${BUILDPLATFORM} ARG BUILDPLATFORM=${BUILDPLATFORM}
# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.3.1"] # Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.3.x"]
ARG RUBY_VERSION="3.3.1" ARG RUBY_VERSION="3.3.2"
# # 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"]
ARG NODE_MAJOR_VERSION="20" ARG NODE_MAJOR_VERSION="20"
# Debian image to use for base image, change with [--build-arg DEBIAN_VERSION="bookworm"] # Debian image to use for base image, change with [--build-arg DEBIAN_VERSION="bookworm"]
ARG DEBIAN_VERSION="bookworm" ARG DEBIAN_VERSION="bookworm"
# Node image to use for base image based on combined variables (ex: 20-bookworm-slim) # Node image to use for base image based on combined variables (ex: 20-bookworm-slim)
FROM docker.io/node:${NODE_MAJOR_VERSION}-${DEBIAN_VERSION}-slim as node FROM docker.io/node:${NODE_MAJOR_VERSION}-${DEBIAN_VERSION}-slim as node
# Ruby image to use for base image based on combined variables (ex: 3.3.1-slim-bookworm) # Ruby image to use for base image based on combined variables (ex: 3.3.x-slim-bookworm)
FROM docker.io/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} as ruby FROM docker.io/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} as ruby
# Resulting version string is vX.X.X-MASTODON_VERSION_PRERELEASE+MASTODON_VERSION_METADATA # Resulting version string is vX.X.X-MASTODON_VERSION_PRERELEASE+MASTODON_VERSION_METADATA
# Example: v4.2.0-nightly.2023.11.09+something # Example: v4.2.0-nightly.2023.11.09+something
# Overwrite existence of 'alpha.0' in version.rb [--build-arg MASTODON_VERSION_PRERELEASE="nightly.2023.11.09"] # Overwrite existence of 'alpha.0' in version.rb [--build-arg MASTODON_VERSION_PRERELEASE="nightly.2023.11.09"]
ARG MASTODON_VERSION_PRERELEASE="" ARG MASTODON_VERSION_PRERELEASE=""
# Append build metadata or fork information to version.rb [--build-arg MASTODON_VERSION_METADATA="something"] # Append build metadata or fork information to version.rb [--build-arg MASTODON_VERSION_METADATA="pr-12345"]
ARG MASTODON_VERSION_METADATA="" ARG MASTODON_VERSION_METADATA=""
# Allow Ruby on Rails to serve static files # Allow Ruby on Rails to serve static files
@ -43,6 +46,8 @@ ENV \
# Apply Mastodon version information # Apply Mastodon version information
MASTODON_VERSION_PRERELEASE="${MASTODON_VERSION_PRERELEASE}" \ MASTODON_VERSION_PRERELEASE="${MASTODON_VERSION_PRERELEASE}" \
MASTODON_VERSION_METADATA="${MASTODON_VERSION_METADATA}" \ MASTODON_VERSION_METADATA="${MASTODON_VERSION_METADATA}" \
# Enable libvips
MASTODON_USE_LIBVIPS=true \
# Apply Mastodon static files and YJIT options # Apply Mastodon static files and YJIT options
RAILS_SERVE_STATIC_FILES=${RAILS_SERVE_STATIC_FILES} \ RAILS_SERVE_STATIC_FILES=${RAILS_SERVE_STATIC_FILES} \
RUBY_YJIT_ENABLE=${RUBY_YJIT_ENABLE} \ RUBY_YJIT_ENABLE=${RUBY_YJIT_ENABLE} \
@ -97,7 +102,7 @@ RUN \
curl \ curl \
ffmpeg \ ffmpeg \
file \ file \
imagemagick \ libvips42 \
libjemalloc2 \ libjemalloc2 \
patchelf \ patchelf \
procps \ procps \

View File

@ -23,6 +23,7 @@ gem 'fog-core', '<= 2.4.0'
gem 'fog-openstack', '~> 1.0', require: false gem 'fog-openstack', '~> 1.0', require: false
gem 'kt-paperclip', '~> 7.2' gem 'kt-paperclip', '~> 7.2'
gem 'md-paperclip-azure', '~> 2.2', require: false gem 'md-paperclip-azure', '~> 2.2', require: false
gem 'ruby-vips', '~> 2.2', require: false
gem 'active_model_serializers', '~> 0.10' gem 'active_model_serializers', '~> 0.10'
gem 'addressable', '~> 2.8' gem 'addressable', '~> 2.8'

View File

@ -10,35 +10,35 @@ GIT
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (7.1.3.3) actioncable (7.1.3.4)
actionpack (= 7.1.3.3) actionpack (= 7.1.3.4)
activesupport (= 7.1.3.3) activesupport (= 7.1.3.4)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
actionmailbox (7.1.3.3) actionmailbox (7.1.3.4)
actionpack (= 7.1.3.3) actionpack (= 7.1.3.4)
activejob (= 7.1.3.3) activejob (= 7.1.3.4)
activerecord (= 7.1.3.3) activerecord (= 7.1.3.4)
activestorage (= 7.1.3.3) activestorage (= 7.1.3.4)
activesupport (= 7.1.3.3) activesupport (= 7.1.3.4)
mail (>= 2.7.1) mail (>= 2.7.1)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
actionmailer (7.1.3.3) actionmailer (7.1.3.4)
actionpack (= 7.1.3.3) actionpack (= 7.1.3.4)
actionview (= 7.1.3.3) actionview (= 7.1.3.4)
activejob (= 7.1.3.3) activejob (= 7.1.3.4)
activesupport (= 7.1.3.3) activesupport (= 7.1.3.4)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
actionpack (7.1.3.3) actionpack (7.1.3.4)
actionview (= 7.1.3.3) actionview (= 7.1.3.4)
activesupport (= 7.1.3.3) activesupport (= 7.1.3.4)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
racc racc
rack (>= 2.2.4) rack (>= 2.2.4)
@ -46,15 +46,15 @@ GEM
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
actiontext (7.1.3.3) actiontext (7.1.3.4)
actionpack (= 7.1.3.3) actionpack (= 7.1.3.4)
activerecord (= 7.1.3.3) activerecord (= 7.1.3.4)
activestorage (= 7.1.3.3) activestorage (= 7.1.3.4)
activesupport (= 7.1.3.3) activesupport (= 7.1.3.4)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (7.1.3.3) actionview (7.1.3.4)
activesupport (= 7.1.3.3) activesupport (= 7.1.3.4)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.11) erubi (~> 1.11)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
@ -64,22 +64,22 @@ GEM
activemodel (>= 4.1) activemodel (>= 4.1)
case_transform (>= 0.2) case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3) jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (7.1.3.3) activejob (7.1.3.4)
activesupport (= 7.1.3.3) activesupport (= 7.1.3.4)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (7.1.3.3) activemodel (7.1.3.4)
activesupport (= 7.1.3.3) activesupport (= 7.1.3.4)
activerecord (7.1.3.3) activerecord (7.1.3.4)
activemodel (= 7.1.3.3) activemodel (= 7.1.3.4)
activesupport (= 7.1.3.3) activesupport (= 7.1.3.4)
timeout (>= 0.4.0) timeout (>= 0.4.0)
activestorage (7.1.3.3) activestorage (7.1.3.4)
actionpack (= 7.1.3.3) actionpack (= 7.1.3.4)
activejob (= 7.1.3.3) activejob (= 7.1.3.4)
activerecord (= 7.1.3.3) activerecord (= 7.1.3.4)
activesupport (= 7.1.3.3) activesupport (= 7.1.3.4)
marcel (~> 1.0) marcel (~> 1.0)
activesupport (7.1.3.3) activesupport (7.1.3.4)
base64 base64
bigdecimal bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
@ -168,7 +168,7 @@ GEM
climate_control (1.2.0) climate_control (1.2.0)
cocoon (1.2.15) cocoon (1.2.15)
color_diff (0.1) color_diff (0.1)
concurrent-ruby (1.2.3) concurrent-ruby (1.3.1)
connection_pool (2.4.1) connection_pool (2.4.1)
cose (1.3.0) cose (1.3.0)
cbor (~> 0.5.9) cbor (~> 0.5.9)
@ -424,7 +424,7 @@ GEM
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2024.0507) mime-types-data (3.2024.0507)
mini_mime (1.1.5) mini_mime (1.1.5)
mini_portile2 (2.8.6) mini_portile2 (2.8.7)
minitest (5.23.1) minitest (5.23.1)
msgpack (1.7.2) msgpack (1.7.2)
multi_json (1.15.0) multi_json (1.15.0)
@ -434,7 +434,7 @@ GEM
uri uri
net-http-persistent (4.0.2) net-http-persistent (4.0.2)
connection_pool (~> 2.2) connection_pool (~> 2.2)
net-imap (0.4.11) net-imap (0.4.12)
date date
net-protocol net-protocol
net-ldap (0.19.0) net-ldap (0.19.0)
@ -579,14 +579,14 @@ GEM
orm_adapter (0.5.0) orm_adapter (0.5.0)
ox (2.14.18) ox (2.14.18)
parallel (1.24.0) parallel (1.24.0)
parser (3.3.1.0) parser (3.3.2.0)
ast (~> 2.4.1) ast (~> 2.4.1)
racc racc
parslet (2.0.0) parslet (2.0.0)
pastel (0.8.0) pastel (0.8.0)
tty-color (~> 0.5) tty-color (~> 0.5)
pg (1.5.6) pg (1.5.6)
pghero (3.4.1) pghero (3.5.0)
activerecord (>= 6) activerecord (>= 6)
premailer (1.23.0) premailer (1.23.0)
addressable addressable
@ -597,7 +597,7 @@ GEM
net-smtp net-smtp
premailer (~> 1.7, >= 1.7.9) premailer (~> 1.7, >= 1.7.9)
private_address_check (0.5.0) private_address_check (0.5.0)
propshaft (0.8.0) propshaft (0.9.0)
actionpack (>= 7.0.0) actionpack (>= 7.0.0)
activesupport (>= 7.0.0) activesupport (>= 7.0.0)
rack rack
@ -634,20 +634,20 @@ GEM
rackup (1.0.0) rackup (1.0.0)
rack (< 3) rack (< 3)
webrick webrick
rails (7.1.3.3) rails (7.1.3.4)
actioncable (= 7.1.3.3) actioncable (= 7.1.3.4)
actionmailbox (= 7.1.3.3) actionmailbox (= 7.1.3.4)
actionmailer (= 7.1.3.3) actionmailer (= 7.1.3.4)
actionpack (= 7.1.3.3) actionpack (= 7.1.3.4)
actiontext (= 7.1.3.3) actiontext (= 7.1.3.4)
actionview (= 7.1.3.3) actionview (= 7.1.3.4)
activejob (= 7.1.3.3) activejob (= 7.1.3.4)
activemodel (= 7.1.3.3) activemodel (= 7.1.3.4)
activerecord (= 7.1.3.3) activerecord (= 7.1.3.4)
activestorage (= 7.1.3.3) activestorage (= 7.1.3.4)
activesupport (= 7.1.3.3) activesupport (= 7.1.3.4)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 7.1.3.3) railties (= 7.1.3.4)
rails-controller-testing (1.0.5) rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1) actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1)
@ -662,9 +662,9 @@ GEM
rails-i18n (7.0.9) rails-i18n (7.0.9)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 8) railties (>= 6.0.0, < 8)
railties (7.1.3.3) railties (7.1.3.4)
actionpack (= 7.1.3.3) actionpack (= 7.1.3.4)
activesupport (= 7.1.3.3) activesupport (= 7.1.3.4)
irb irb
rackup (>= 1.0.0) rackup (>= 1.0.0)
rake (>= 12.2) rake (>= 12.2)
@ -686,7 +686,7 @@ GEM
redlock (1.3.2) redlock (1.3.2)
redis (>= 3.0.0, < 6.0) redis (>= 3.0.0, < 6.0)
regexp_parser (2.9.2) regexp_parser (2.9.2)
reline (0.5.7) reline (0.5.8)
io-console (~> 0.5) io-console (~> 0.5)
request_store (1.6.0) request_store (1.6.0)
rack (>= 1.4) rack (>= 1.4)
@ -726,7 +726,7 @@ GEM
rspec-mocks (~> 3.0) rspec-mocks (~> 3.0)
sidekiq (>= 5, < 8) sidekiq (>= 5, < 8)
rspec-support (3.13.1) rspec-support (3.13.1)
rubocop (1.64.0) rubocop (1.64.1)
json (~> 2.3) json (~> 2.3)
language_server-protocol (>= 3.17.0) language_server-protocol (>= 3.17.0)
parallel (~> 1.10) parallel (~> 1.10)
@ -751,7 +751,7 @@ 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.29.2) rubocop-rspec (2.30.0)
rubocop (~> 1.40) rubocop (~> 1.40)
rubocop-capybara (~> 2.17) rubocop-capybara (~> 2.17)
rubocop-factory_bot (~> 2.22) rubocop-factory_bot (~> 2.22)
@ -763,6 +763,8 @@ GEM
ruby-saml (1.16.0) ruby-saml (1.16.0)
nokogiri (>= 1.13.10) nokogiri (>= 1.13.10)
rexml rexml
ruby-vips (2.2.1)
ffi (~> 1.12)
ruby2_keywords (0.0.5) ruby2_keywords (0.0.5)
rubyzip (2.3.2) rubyzip (2.3.2)
rufus-scheduler (3.9.1) rufus-scheduler (3.9.1)
@ -895,7 +897,7 @@ GEM
xorcist (1.1.3) xorcist (1.1.3)
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
zeitwerk (2.6.14) zeitwerk (2.6.15)
PLATFORMS PLATFORMS
ruby ruby
@ -1023,6 +1025,7 @@ DEPENDENCIES
rubocop-rspec rubocop-rspec
ruby-prof ruby-prof
ruby-progressbar (~> 1.13) ruby-progressbar (~> 1.13)
ruby-vips (~> 2.2)
rubyzip (~> 2.3) rubyzip (~> 2.3)
sanitize (~> 6.0) sanitize (~> 6.0)
scenic (~> 1.7) scenic (~> 1.7)
@ -1050,7 +1053,7 @@ DEPENDENCIES
xorcist (~> 1.1) xorcist (~> 1.1)
RUBY VERSION RUBY VERSION
ruby 3.3.1p55 ruby 3.3.2p78
BUNDLED WITH BUNDLED WITH
2.5.9 2.5.11

View File

@ -88,7 +88,7 @@ Mastodon acts as an OAuth2 provider, so 3rd party apps can use the REST and Stre
- **Ruby** 3.1+ - **Ruby** 3.1+
- **Node.js** 18+ - **Node.js** 18+
The repository includes deployment configurations for **Docker and docker-compose** as well as specific platforms like **Heroku**, **Scalingo**, and **Nanobox**. For Helm charts, reference the [mastodon/chart repository](https://github.com/mastodon/chart). The [**standalone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the documentation. The repository includes deployment configurations for **Docker and docker-compose** as well as specific platforms like **Heroku**, and **Scalingo**. For Helm charts, reference the [mastodon/chart repository](https://github.com/mastodon/chart). The [**standalone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the documentation.
## Development ## Development
@ -117,11 +117,13 @@ To set up **MacOS** for native development, complete the following steps:
### Docker ### Docker
For development with **Docker**, complete the following steps: For production hosting and deployment with **Docker**, use the `Dockerfile` and
`docker-compose.yml` in the project root directory. To create a local
development environment with **Docker**, complete the following steps:
- Install Docker Desktop - Install Docker Desktop
- Run `docker compose -f .devcontainer/docker-compose.yml up -d` - Run `docker compose -f .devcontainer/docker-compose.yml up -d`
- Run `docker compose -f .devcontainer/docker-compose.yml exec app .devcontainer/post-create.sh` - Run `docker compose -f .devcontainer/docker-compose.yml exec app bin/setup`
- Finally, run `docker compose -f .devcontainer/docker-compose.yml exec app bin/dev` - Finally, run `docker compose -f .devcontainer/docker-compose.yml exec app bin/dev`
If you are using an IDE with [support for the Development Container specification](https://containers.dev/supporting), it will run the above `docker compose` commands automatically. For **Visual Studio Code** this requires the [Dev Container extension](https://containers.dev/supporting#dev-containers). If you are using an IDE with [support for the Development Container specification](https://containers.dev/supporting), it will run the above `docker compose` commands automatically. For **Visual Studio Code** this requires the [Dev Container extension](https://containers.dev/supporting#dev-containers).

6
Vagrantfile vendored
View File

@ -151,6 +151,12 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
vb.customize ["modifyvm", :id, "--nictype2", "virtio"] vb.customize ["modifyvm", :id, "--nictype2", "virtio"]
end end
config.vm.provider :libvirt do |libvirt|
libvirt.cpus = 3
libvirt.memory = 8192
end
# This uses the vagrant-hostsupdater plugin, and lets you # This uses the vagrant-hostsupdater plugin, and lets you
# access the development site at http://mastodon.local. # access the development site at http://mastodon.local.
# If you change it, also change it in .env.vagrant before provisioning # If you change it, also change it in .env.vagrant before provisioning

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::Accounts::CredentialsController < Api::BaseController class Api::V1::Accounts::CredentialsController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:accounts', :'read:me' }, except: [:update] before_action -> { doorkeeper_authorize! :profile, :read, :'read:accounts' }, except: [:update]
before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, only: [:update] before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, only: [:update]
before_action :require_user! before_action :require_user!

View File

@ -60,8 +60,4 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
def records_continue? def records_continue?
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end end

View File

@ -60,8 +60,4 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
def records_continue? def records_continue?
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end end

View File

@ -16,8 +16,6 @@ class Api::V1::Admin::CanonicalEmailBlocksController < Api::BaseController
after_action :verify_authorized after_action :verify_authorized
after_action :insert_pagination_headers, only: :index after_action :insert_pagination_headers, only: :index
PAGINATION_PARAMS = %i(limit).freeze
def index def index
authorize :canonical_email_block, :index? authorize :canonical_email_block, :index?
render json: @canonical_email_blocks, each_serializer: REST::Admin::CanonicalEmailBlockSerializer render json: @canonical_email_blocks, each_serializer: REST::Admin::CanonicalEmailBlockSerializer
@ -80,8 +78,4 @@ class Api::V1::Admin::CanonicalEmailBlocksController < Api::BaseController
def records_continue? def records_continue?
@canonical_email_blocks.size == limit_param(LIMIT) @canonical_email_blocks.size == limit_param(LIMIT)
end end
def pagination_params(core_params)
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
end
end end

View File

@ -14,8 +14,6 @@ class Api::V1::Admin::DomainAllowsController < Api::BaseController
after_action :verify_authorized after_action :verify_authorized
after_action :insert_pagination_headers, only: :index after_action :insert_pagination_headers, only: :index
PAGINATION_PARAMS = %i(limit).freeze
def index def index
authorize :domain_allow, :index? authorize :domain_allow, :index?
render json: @domain_allows, each_serializer: REST::Admin::DomainAllowSerializer render json: @domain_allows, each_serializer: REST::Admin::DomainAllowSerializer
@ -77,10 +75,6 @@ class Api::V1::Admin::DomainAllowsController < Api::BaseController
@domain_allows.size == limit_param(LIMIT) @domain_allows.size == limit_param(LIMIT)
end end
def pagination_params(core_params)
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
end
def resource_params def resource_params
params.permit(:domain) params.permit(:domain)
end end

View File

@ -14,8 +14,6 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController
after_action :verify_authorized after_action :verify_authorized
after_action :insert_pagination_headers, only: :index after_action :insert_pagination_headers, only: :index
PAGINATION_PARAMS = %i(limit).freeze
def index def index
authorize :domain_block, :index? authorize :domain_block, :index?
render json: @domain_blocks, each_serializer: REST::Admin::DomainBlockSerializer render json: @domain_blocks, each_serializer: REST::Admin::DomainBlockSerializer
@ -93,10 +91,6 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController
@domain_blocks.size == limit_param(LIMIT) @domain_blocks.size == limit_param(LIMIT)
end end
def pagination_params(core_params)
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
end
def resource_params def resource_params
params.permit(:domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate) params.permit(:domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate)
end end

View File

@ -14,10 +14,6 @@ class Api::V1::Admin::EmailDomainBlocksController < Api::BaseController
after_action :verify_authorized after_action :verify_authorized
after_action :insert_pagination_headers, only: :index after_action :insert_pagination_headers, only: :index
PAGINATION_PARAMS = %i(
limit
).freeze
def index def index
authorize :email_domain_block, :index? authorize :email_domain_block, :index?
render json: @email_domain_blocks, each_serializer: REST::Admin::EmailDomainBlockSerializer render json: @email_domain_blocks, each_serializer: REST::Admin::EmailDomainBlockSerializer
@ -73,8 +69,4 @@ class Api::V1::Admin::EmailDomainBlocksController < Api::BaseController
def records_continue? def records_continue?
@email_domain_blocks.size == limit_param(LIMIT) @email_domain_blocks.size == limit_param(LIMIT)
end end
def pagination_params(core_params)
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
end
end end

View File

@ -14,10 +14,6 @@ class Api::V1::Admin::IpBlocksController < Api::BaseController
after_action :verify_authorized after_action :verify_authorized
after_action :insert_pagination_headers, only: :index after_action :insert_pagination_headers, only: :index
PAGINATION_PARAMS = %i(
limit
).freeze
def index def index
authorize :ip_block, :index? authorize :ip_block, :index?
render json: @ip_blocks, each_serializer: REST::Admin::IpBlockSerializer render json: @ip_blocks, each_serializer: REST::Admin::IpBlockSerializer
@ -78,8 +74,4 @@ class Api::V1::Admin::IpBlocksController < Api::BaseController
def records_continue? def records_continue?
@ip_blocks.size == limit_param(LIMIT) @ip_blocks.size == limit_param(LIMIT)
end end
def pagination_params(core_params)
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
end
end end

View File

@ -12,7 +12,6 @@ class Api::V1::Admin::TagsController < Api::BaseController
after_action :verify_authorized after_action :verify_authorized
LIMIT = 100 LIMIT = 100
PAGINATION_PARAMS = %i(limit).freeze
def index def index
authorize :tag, :index? authorize :tag, :index?
@ -59,8 +58,4 @@ class Api::V1::Admin::TagsController < Api::BaseController
def records_continue? def records_continue?
@tags.size == limit_param(LIMIT) @tags.size == limit_param(LIMIT)
end end
def pagination_params(core_params)
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
end
end end

View File

@ -12,8 +12,6 @@ class Api::V1::Admin::Trends::Links::PreviewCardProvidersController < Api::BaseC
after_action :verify_authorized after_action :verify_authorized
after_action :insert_pagination_headers, only: :index after_action :insert_pagination_headers, only: :index
PAGINATION_PARAMS = %i(limit).freeze
def index def index
authorize :preview_card_provider, :index? authorize :preview_card_provider, :index?
@ -57,8 +55,4 @@ class Api::V1::Admin::Trends::Links::PreviewCardProvidersController < Api::BaseC
def records_continue? def records_continue?
@providers.size == limit_param(LIMIT) @providers.size == limit_param(LIMIT)
end end
def pagination_params(core_params)
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
end
end end

View File

@ -43,8 +43,4 @@ class Api::V1::BlocksController < Api::BaseController
def records_continue? def records_continue?
paginated_blocks.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) paginated_blocks.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end end

View File

@ -46,8 +46,4 @@ class Api::V1::BookmarksController < Api::BaseController
def records_continue? def records_continue?
results.size == limit_param(DEFAULT_STATUSES_LIMIT) results.size == limit_param(DEFAULT_STATUSES_LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end end

View File

@ -72,8 +72,4 @@ class Api::V1::ConversationsController < Api::BaseController
def records_continue? def records_continue?
@conversations.size == limit_param(LIMIT) @conversations.size == limit_param(LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end end

View File

@ -44,8 +44,4 @@ class Api::V1::Crypto::EncryptedMessagesController < Api::BaseController
def records_continue? def records_continue?
@encrypted_messages.size == limit_param(LIMIT) @encrypted_messages.size == limit_param(LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end end

View File

@ -54,10 +54,6 @@ class Api::V1::DomainBlocksController < Api::BaseController
@blocks.size == limit_param(BLOCK_LIMIT) @blocks.size == limit_param(BLOCK_LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
def domain_block_params def domain_block_params
params.permit(:domain) params.permit(:domain)
end end

View File

@ -48,10 +48,6 @@ class Api::V1::EndorsementsController < Api::BaseController
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
def unlimited? def unlimited?
params[:limit] == '0' params[:limit] == '0'
end end

View File

@ -46,8 +46,4 @@ class Api::V1::FavouritesController < Api::BaseController
def records_continue? def records_continue?
results.size == limit_param(DEFAULT_STATUSES_LIMIT) results.size == limit_param(DEFAULT_STATUSES_LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end end

View File

@ -67,8 +67,4 @@ class Api::V1::FollowRequestsController < Api::BaseController
def records_continue? def records_continue?
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end end

View File

@ -37,8 +37,4 @@ class Api::V1::FollowedTagsController < Api::BaseController
def records_continue? def records_continue?
@results.size == limit_param(TAGS_LIMIT) @results.size == limit_param(TAGS_LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end end

View File

@ -5,7 +5,7 @@ class Api::V1::Instances::ExtendedDescriptionsController < Api::V1::Instances::B
before_action :set_extended_description before_action :set_extended_description
# Override `current_user` to avoid reading session cookies unless in whitelist mode # Override `current_user` to avoid reading session cookies unless in limited federation mode
def current_user def current_user
super if limited_federation_mode? super if limited_federation_mode?
end end

View File

@ -5,7 +5,7 @@ class Api::V1::Instances::PeersController < Api::V1::Instances::BaseController
skip_around_action :set_locale skip_around_action :set_locale
# Override `current_user` to avoid reading session cookies unless in whitelist mode # Override `current_user` to avoid reading session cookies unless in limited federation mode
def current_user def current_user
super if limited_federation_mode? super if limited_federation_mode?
end end

View File

@ -5,7 +5,7 @@ class Api::V1::Instances::RulesController < Api::V1::Instances::BaseController
before_action :set_rules before_action :set_rules
# Override `current_user` to avoid reading session cookies unless in whitelist mode # Override `current_user` to avoid reading session cookies unless in limited federation mode
def current_user def current_user
super if limited_federation_mode? super if limited_federation_mode?
end end

View File

@ -6,7 +6,7 @@ class Api::V1::InstancesController < Api::BaseController
vary_by '' vary_by ''
# Override `current_user` to avoid reading session cookies unless in whitelist mode # Override `current_user` to avoid reading session cookies unless in limited federation mode
def current_user def current_user
super if limited_federation_mode? super if limited_federation_mode?
end end

View File

@ -75,10 +75,6 @@ class Api::V1::Lists::AccountsController < Api::BaseController
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
def unlimited? def unlimited?
params[:limit] == '0' params[:limit] == '0'
end end

View File

@ -43,8 +43,4 @@ class Api::V1::MutesController < Api::BaseController
def records_continue? def records_continue?
paginated_mutes.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) paginated_mutes.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end end

View File

@ -43,10 +43,6 @@ class Api::V1::ScheduledStatusesController < Api::BaseController
params.permit(:scheduled_at) params.permit(:scheduled_at)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
def next_path def next_path
api_v1_scheduled_statuses_url pagination_params(max_id: pagination_max_id) if records_continue? api_v1_scheduled_statuses_url pagination_params(max_id: pagination_max_id) if records_continue?
end end

View File

@ -53,8 +53,4 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::V1::Statuses::Bas
def records_continue? def records_continue?
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end end

View File

@ -49,8 +49,4 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::V1::Statuses::Base
def records_continue? def records_continue?
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end end

View File

@ -191,8 +191,4 @@ class Api::V1::StatusesController < Api::BaseController
def serialized_accounts(accounts) def serialized_accounts(accounts)
ActiveModel::Serializer::CollectionSerializer.new(accounts, serializer: REST::AccountSerializer) ActiveModel::Serializer::CollectionSerializer.new(accounts, serializer: REST::AccountSerializer)
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
end end

View File

@ -0,0 +1,52 @@
# frozen_string_literal: true
class Api::V1::Timelines::LinkController < Api::V1::Timelines::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: :show, if: :require_auth?
before_action :set_preview_card
before_action :set_statuses
PERMITTED_PARAMS = %i(
url
limit
).freeze
def show
cache_if_unauthenticated!
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
end
private
def require_auth?
!Setting.timeline_preview
end
def set_preview_card
@preview_card = PreviewCard.joins(:trend).merge(PreviewCardTrend.allowed).find_by!(url: params[:url])
end
def set_statuses
@statuses = @preview_card.nil? ? [] : preload_collection(link_timeline_statuses, Status)
end
def link_timeline_statuses
link_feed.get(
limit_param(DEFAULT_STATUSES_LIMIT),
params[:max_id],
params[:since_id],
params[:min_id]
)
end
def link_feed
LinkFeed.new(@preview_card, current_account)
end
def next_path
api_v1_timelines_link_url next_path_params
end
def prev_path
api_v1_timelines_link_url prev_path_params
end
end

View File

@ -34,10 +34,6 @@ class Api::V1::Trends::LinksController < Api::BaseController
scope scope
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
def next_path def next_path
api_v1_trends_links_url pagination_params(offset: offset_param + limit_param(DEFAULT_LINKS_LIMIT)) if records_continue? api_v1_trends_links_url pagination_params(offset: offset_param + limit_param(DEFAULT_LINKS_LIMIT)) if records_continue?
end end

View File

@ -32,10 +32,6 @@ class Api::V1::Trends::StatusesController < Api::BaseController
scope scope
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
def next_path def next_path
api_v1_trends_statuses_url pagination_params(offset: offset_param + limit_param(DEFAULT_STATUSES_LIMIT)) if records_continue? api_v1_trends_statuses_url pagination_params(offset: offset_param + limit_param(DEFAULT_STATUSES_LIMIT)) if records_continue?
end end

View File

@ -30,10 +30,6 @@ class Api::V1::Trends::TagsController < Api::BaseController
Trends.tags.query.allowed Trends.tags.query.allowed
end end
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
def next_path def next_path
api_v1_trends_tags_url pagination_params(offset: offset_param + limit_param(DEFAULT_TAGS_LIMIT)) if records_continue? api_v1_trends_tags_url pagination_params(offset: offset_param + limit_param(DEFAULT_TAGS_LIMIT)) if records_continue?
end end

View File

@ -0,0 +1,91 @@
# frozen_string_literal: true
class Api::V2Alpha::NotificationsController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:notifications' }, except: [:clear, :dismiss]
before_action -> { doorkeeper_authorize! :write, :'write:notifications' }, only: [:clear, :dismiss]
before_action :require_user!
after_action :insert_pagination_headers, only: :index
DEFAULT_NOTIFICATIONS_LIMIT = 40
def index
with_read_replica do
@notifications = load_notifications
@group_metadata = load_group_metadata
@relationships = StatusRelationshipsPresenter.new(target_statuses_from_notifications, current_user&.account_id)
end
render json: @notifications.map { |notification| NotificationGroup.from_notification(notification) }, each_serializer: REST::NotificationGroupSerializer, relationships: @relationships, group_metadata: @group_metadata
end
def show
@notification = current_account.notifications.without_suspended.find_by!(group_key: params[:id])
render json: NotificationGroup.from_notification(@notification), serializer: REST::NotificationGroupSerializer
end
def clear
current_account.notifications.delete_all
render_empty
end
def dismiss
current_account.notifications.where(group_key: params[:id]).destroy_all
render_empty
end
private
def load_notifications
notifications = browserable_account_notifications.includes(from_account: [:account_stat, :user]).to_a_grouped_paginated_by_id(
limit_param(DEFAULT_NOTIFICATIONS_LIMIT),
params_slice(:max_id, :since_id, :min_id)
)
Notification.preload_cache_collection_target_statuses(notifications) do |target_statuses|
preload_collection(target_statuses, Status)
end
end
def load_group_metadata
return {} if @notifications.empty?
browserable_account_notifications
.where(group_key: @notifications.filter_map(&:group_key))
.where(id: (@notifications.last.id)..(@notifications.first.id))
.group(:group_key)
.pluck(:group_key, 'min(notifications.id) as min_id', 'max(notifications.id) as max_id', 'max(notifications.created_at) as latest_notification_at')
.to_h { |group_key, min_id, max_id, latest_notification_at| [group_key, { min_id: min_id, max_id: max_id, latest_notification_at: latest_notification_at }] }
end
def browserable_account_notifications
current_account.notifications.without_suspended.browserable(
types: Array(browserable_params[:types]),
exclude_types: Array(browserable_params[:exclude_types]),
include_filtered: truthy_param?(:include_filtered)
)
end
def target_statuses_from_notifications
@notifications.filter_map(&:target_status)
end
def next_path
api_v2_alpha_notifications_url pagination_params(max_id: pagination_max_id) unless @notifications.empty?
end
def prev_path
api_v2_alpha_notifications_url pagination_params(min_id: pagination_since_id) unless @notifications.empty?
end
def pagination_collection
@notifications
end
def browserable_params
params.permit(:include_filtered, types: [], exclude_types: [])
end
def pagination_params(core_params)
params.slice(:limit, :types, :exclude_types, :include_filtered).permit(:limit, :include_filtered, types: [], exclude_types: []).merge(core_params)
end
end

View File

@ -3,6 +3,8 @@
module Api::Pagination module Api::Pagination
extend ActiveSupport::Concern extend ActiveSupport::Concern
PAGINATION_PARAMS = %i(limit).freeze
protected protected
def pagination_max_id def pagination_max_id
@ -24,6 +26,13 @@ module Api::Pagination
render json: { error: 'Pagination values for `offset` and `limit` must be positive' }, status: 400 if pagination_options_invalid? render json: { error: 'Pagination values for `offset` and `limit` must be positive' }, status: 400 if pagination_options_invalid?
end end
def pagination_params(core_params)
params
.slice(*PAGINATION_PARAMS)
.permit(*PAGINATION_PARAMS)
.merge(core_params)
end
private private
def insert_pagination_headers def insert_pagination_headers

View File

@ -13,7 +13,7 @@ class Settings::ApplicationsController < Settings::BaseController
def new def new
@application = Doorkeeper::Application.new( @application = Doorkeeper::Application.new(
redirect_uri: Doorkeeper.configuration.native_redirect_uri, redirect_uri: Doorkeeper.configuration.native_redirect_uri,
scopes: 'read:me' scopes: 'profile'
) )
end end

View File

@ -199,6 +199,7 @@ class AccountTimeline extends ImmutablePureComponent {
emptyMessage={emptyMessage} emptyMessage={emptyMessage}
bindToDocument={!multiColumn} bindToDocument={!multiColumn}
timelineId='account' timelineId='account'
withCounters
/> />
</Column> </Column>
); );

View File

@ -414,6 +414,7 @@
"limited_account_hint.action": "Показване на профила въпреки това", "limited_account_hint.action": "Показване на профила въпреки това",
"limited_account_hint.title": "Този профил е бил скрит от модераторите на {domain}.", "limited_account_hint.title": "Този профил е бил скрит от модераторите на {domain}.",
"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": "Изтриване на списъка",

View File

@ -221,6 +221,8 @@
"domain_pill.activitypub_like_language": "ActivityPub er \"sproget\", Mastodon taler med andre sociale netværk.", "domain_pill.activitypub_like_language": "ActivityPub er \"sproget\", Mastodon taler med andre sociale netværk.",
"domain_pill.server": "Server", "domain_pill.server": "Server",
"domain_pill.their_handle": "Vedkommendes handle:", "domain_pill.their_handle": "Vedkommendes handle:",
"domain_pill.their_server": "Det digitale hjem, hvor alle indlæggene findes.",
"domain_pill.their_username": "Entydig identifikator på denne server. Det er muligt at finde brugere med samme brugernavn på forskellige servere.",
"domain_pill.username": "Brugernavn", "domain_pill.username": "Brugernavn",
"domain_pill.whats_in_a_handle": "Hvad er der i et handle (@brugernavn)?", "domain_pill.whats_in_a_handle": "Hvad er der i et handle (@brugernavn)?",
"domain_pill.who_they_are": "Da et handle fortæller, hvem nogen er, og hvor de er, kan man interagere med folk på tværs af det sociale net af <button>ActivityPub-drevne platforme</button>.", "domain_pill.who_they_are": "Da et handle fortæller, hvem nogen er, og hvor de er, kan man interagere med folk på tværs af det sociale net af <button>ActivityPub-drevne platforme</button>.",
@ -412,6 +414,7 @@
"limited_account_hint.action": "Vis profil alligevel", "limited_account_hint.action": "Vis profil alligevel",
"limited_account_hint.title": "Denne profil er blevet skjult af {domain}-moderatorerne.", "limited_account_hint.title": "Denne profil er blevet skjult af {domain}-moderatorerne.",
"link_preview.author": "Af {name}", "link_preview.author": "Af {name}",
"link_preview.more_from_author": "Mere fra {name}",
"lists.account.add": "Føj til liste", "lists.account.add": "Føj til liste",
"lists.account.remove": "Fjern fra liste", "lists.account.remove": "Fjern fra liste",
"lists.delete": "Slet liste", "lists.delete": "Slet liste",

View File

@ -414,6 +414,7 @@
"limited_account_hint.action": "Profil trotzdem anzeigen", "limited_account_hint.action": "Profil trotzdem anzeigen",
"limited_account_hint.title": "Dieses Profil wurde von den Moderator*innen von {domain} ausgeblendet.", "limited_account_hint.title": "Dieses Profil wurde von den Moderator*innen von {domain} ausgeblendet.",
"link_preview.author": "Von {name}", "link_preview.author": "Von {name}",
"link_preview.more_from_author": "Mehr von {name}",
"lists.account.add": "Zur Liste hinzufügen", "lists.account.add": "Zur Liste hinzufügen",
"lists.account.remove": "Von der Liste entfernen", "lists.account.remove": "Von der Liste entfernen",
"lists.delete": "Liste löschen", "lists.delete": "Liste löschen",

View File

@ -414,6 +414,7 @@
"limited_account_hint.action": "Mostrar perfil de todos modos", "limited_account_hint.action": "Mostrar perfil de todos modos",
"limited_account_hint.title": "Este perfil ha sido ocultado por los moderadores de {domain}.", "limited_account_hint.title": "Este perfil ha sido ocultado por los moderadores de {domain}.",
"link_preview.author": "Por {name}", "link_preview.author": "Por {name}",
"link_preview.more_from_author": "Más de {name}",
"lists.account.add": "Añadir a lista", "lists.account.add": "Añadir a lista",
"lists.account.remove": "Quitar de lista", "lists.account.remove": "Quitar de lista",
"lists.delete": "Borrar lista", "lists.delete": "Borrar lista",

View File

@ -414,6 +414,7 @@
"limited_account_hint.action": "Mostrar perfil de todos modos", "limited_account_hint.action": "Mostrar perfil de todos modos",
"limited_account_hint.title": "Este perfil ha sido ocultado por los moderadores de {domain}.", "limited_account_hint.title": "Este perfil ha sido ocultado por los moderadores de {domain}.",
"link_preview.author": "Por {name}", "link_preview.author": "Por {name}",
"link_preview.more_from_author": "Más de {name}",
"lists.account.add": "Añadir a lista", "lists.account.add": "Añadir a lista",
"lists.account.remove": "Quitar de lista", "lists.account.remove": "Quitar de lista",
"lists.delete": "Borrar lista", "lists.delete": "Borrar lista",

View File

@ -414,6 +414,7 @@
"limited_account_hint.action": "Vís vangamynd kortini", "limited_account_hint.action": "Vís vangamynd kortini",
"limited_account_hint.title": "Hesin vangin er fjaldur av kjakleiðarunum á {domain}.", "limited_account_hint.title": "Hesin vangin er fjaldur av kjakleiðarunum á {domain}.",
"link_preview.author": "Av {name}", "link_preview.author": "Av {name}",
"link_preview.more_from_author": "Meira frá {name}",
"lists.account.add": "Legg afturat lista", "lists.account.add": "Legg afturat lista",
"lists.account.remove": "Tak av lista", "lists.account.remove": "Tak av lista",
"lists.delete": "Strika lista", "lists.delete": "Strika lista",

View File

@ -89,9 +89,12 @@
"announcement.announcement": "Oankundiging", "announcement.announcement": "Oankundiging",
"attachments_list.unprocessed": "(net ferwurke)", "attachments_list.unprocessed": "(net ferwurke)",
"audio.hide": "Audio ferstopje", "audio.hide": "Audio ferstopje",
"block_modal.remote_users_caveat": "Wy freegje de server {domain} om jo beslút te respektearjen. It neilibben hjirfan is echter net garandearre, omdat guon servers blokkaden oars ynterpretearje kinne. Iepenbiere berjochten binne mooglik noch hieltyd sichtber foar net-oanmelde brûkers.",
"block_modal.show_less": "Minder toane", "block_modal.show_less": "Minder toane",
"block_modal.show_more": "Mear toane", "block_modal.show_more": "Mear toane",
"block_modal.they_cant_mention": "Sy kinne jo net fermelde of folgje.", "block_modal.they_cant_mention": "Sy kinne jo net fermelde of folgje.",
"block_modal.they_cant_see_posts": "De persoan kin jo berjochten net sjen en jo ek net harren berjochten.",
"block_modal.they_will_know": "De persoan kin sjen dat dy blokkearre wurdt.",
"block_modal.title": "Brûker blokkearje?", "block_modal.title": "Brûker blokkearje?",
"block_modal.you_wont_see_mentions": "Jo sjogge gjin berjochten mear dyt dizze account fermelde.", "block_modal.you_wont_see_mentions": "Jo sjogge gjin berjochten mear dyt dizze account fermelde.",
"boost_modal.combo": "Jo kinne op {combo} drukke om dit de folgjende kear oer te slaan", "boost_modal.combo": "Jo kinne op {combo} drukke om dit de folgjende kear oer te slaan",
@ -214,11 +217,15 @@
"domain_block_modal.title": "Domein blokkearje?", "domain_block_modal.title": "Domein blokkearje?",
"domain_block_modal.you_will_lose_followers": "Al jo folgers fan dizze server wurde ûntfolge.", "domain_block_modal.you_will_lose_followers": "Al jo folgers fan dizze server wurde ûntfolge.",
"domain_block_modal.you_wont_see_posts": "Jo sjogge gjin berjochten of meldingen mear fan brûkers op dizze server.", "domain_block_modal.you_wont_see_posts": "Jo sjogge gjin berjochten of meldingen mear fan brûkers op dizze server.",
"domain_pill.activitypub_lets_connect": "It soarget derfoar dat jo net allinnich mar ferbine en kommunisearje kinne mei minsken op Mastodon, mar ek mei oare sosjale apps.",
"domain_pill.activitypub_like_language": "ActivityPub is de taal dyt Mastodon mei oare sosjale netwurken sprekt.",
"domain_pill.server": "Server", "domain_pill.server": "Server",
"domain_pill.their_handle": "Harren fediverse-adres:", "domain_pill.their_handle": "Harren fediverse-adres:",
"domain_pill.their_server": "Harren digitale thús, wert al harren berjochten binne.", "domain_pill.their_server": "Harren digitale thús, wert al harren berjochten binne.",
"domain_pill.their_username": "Harren unike identifikaasje-adres op harren server. It is mooglik dat der brûkers mei deselde brûkersnamme op ferskate servers te finen binne.",
"domain_pill.username": "Brûkersnamme", "domain_pill.username": "Brûkersnamme",
"domain_pill.whats_in_a_handle": "Wat is in fediverse-adres?", "domain_pill.whats_in_a_handle": "Wat is in fediverse-adres?",
"domain_pill.who_they_are": "Omdat jo oan in fediverse-adres sjen kinne hoet ien hjit en op hokker server dy sit, kinne jo mei minsken op it troch <button>ActivityPub oandreaune</button> sosjale web (fediverse) kommunisearje.",
"domain_pill.your_handle": "Jo fediverse-adres:", "domain_pill.your_handle": "Jo fediverse-adres:",
"embed.instructions": "Embed this status on your website by copying the code below.", "embed.instructions": "Embed this status on your website by copying the code below.",
"embed.preview": "Sa komt it der út te sjen:", "embed.preview": "Sa komt it der út te sjen:",

View File

@ -414,6 +414,7 @@
"limited_account_hint.action": "הצג חשבון בכל זאת", "limited_account_hint.action": "הצג חשבון בכל זאת",
"limited_account_hint.title": "פרופיל המשתמש הזה הוסתר על ידי המנחים של {domain}.", "limited_account_hint.title": "פרופיל המשתמש הזה הוסתר על ידי המנחים של {domain}.",
"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": "מחיקת רשימה",

View File

@ -414,6 +414,7 @@
"limited_account_hint.action": "Profil megjelenítése mindenképpen", "limited_account_hint.action": "Profil megjelenítése mindenképpen",
"limited_account_hint.title": "Ezt a profilt {domain} moderátorai elrejtették.", "limited_account_hint.title": "Ezt a profilt {domain} moderátorai elrejtették.",
"link_preview.author": "{name} szerint", "link_preview.author": "{name} szerint",
"link_preview.more_from_author": "Több tőle: {name}",
"lists.account.add": "Hozzáadás a listához", "lists.account.add": "Hozzáadás a listához",
"lists.account.remove": "Eltávolítás a listából", "lists.account.remove": "Eltávolítás a listából",
"lists.delete": "Lista törlése", "lists.delete": "Lista törlése",

View File

@ -58,7 +58,7 @@
"account.open_original_page": "Aperir le pagina original", "account.open_original_page": "Aperir le pagina original",
"account.posts": "Messages", "account.posts": "Messages",
"account.posts_with_replies": "Messages e responsas", "account.posts_with_replies": "Messages e responsas",
"account.report": "Signalar @{name}", "account.report": "Reportar @{name}",
"account.requested": "Attendente le approbation. Clicca pro cancellar le requesta de sequer", "account.requested": "Attendente le approbation. Clicca pro cancellar le requesta de sequer",
"account.requested_follow": "{name} ha requestate de sequer te", "account.requested_follow": "{name} ha requestate de sequer te",
"account.share": "Compartir profilo de @{name}", "account.share": "Compartir profilo de @{name}",
@ -215,8 +215,8 @@
"domain_block_modal.they_cant_follow": "Necuno de iste servitor pote sequer te.", "domain_block_modal.they_cant_follow": "Necuno de iste servitor pote sequer te.",
"domain_block_modal.they_wont_know": "Ille non sapera que ille ha essite blocate.", "domain_block_modal.they_wont_know": "Ille non sapera que ille ha essite blocate.",
"domain_block_modal.title": "Blocar dominio?", "domain_block_modal.title": "Blocar dominio?",
"domain_block_modal.you_will_lose_followers": "Omne sequitores ab iste servitor essera removite.", "domain_block_modal.you_will_lose_followers": "Tote tu sequitores de iste servitor essera removite.",
"domain_block_modal.you_wont_see_posts": "Tu non videra messages e notificationes ab usatores sur iste servitor.", "domain_block_modal.you_wont_see_posts": "Tu non videra messages e notificationes de usatores sur iste servitor.",
"domain_pill.activitypub_lets_connect": "Illo te permitte connecter e interager con personas non solmente sur Mastodon, ma tamben sur altere applicationes social.", "domain_pill.activitypub_lets_connect": "Illo te permitte connecter e interager con personas non solmente sur Mastodon, ma tamben sur altere applicationes social.",
"domain_pill.activitypub_like_language": "ActivityPub es como le linguage commun que Mastodon parla con altere retes social.", "domain_pill.activitypub_like_language": "ActivityPub es como le linguage commun que Mastodon parla con altere retes social.",
"domain_pill.server": "Servitor", "domain_pill.server": "Servitor",
@ -274,7 +274,7 @@
"error.unexpected_crash.next_steps": "Tenta refrescar le pagina. Si isto non remedia le problema, es possibile que tu pote totevia usar Mastodon per medio de un altere navigator o application native.", "error.unexpected_crash.next_steps": "Tenta refrescar le pagina. Si isto non remedia le problema, es possibile que tu pote totevia usar Mastodon per medio de un altere navigator o application native.",
"error.unexpected_crash.next_steps_addons": "Tenta disactivar istes e refrescar le pagina. Si isto non remedia le problema, es possibile que tu pote totevia usar Mastodon per medio de un altere navigator o application native.", "error.unexpected_crash.next_steps_addons": "Tenta disactivar istes e refrescar le pagina. Si isto non remedia le problema, es possibile que tu pote totevia usar Mastodon per medio de un altere navigator o application native.",
"errors.unexpected_crash.copy_stacktrace": "Copiar le traciamento del pila al area de transferentia", "errors.unexpected_crash.copy_stacktrace": "Copiar le traciamento del pila al area de transferentia",
"errors.unexpected_crash.report_issue": "Signalar un defecto", "errors.unexpected_crash.report_issue": "Reportar problema",
"explore.search_results": "Resultatos de recerca", "explore.search_results": "Resultatos de recerca",
"explore.suggested_follows": "Personas", "explore.suggested_follows": "Personas",
"explore.title": "Explorar", "explore.title": "Explorar",
@ -389,7 +389,7 @@
"keyboard_shortcuts.hotkey": "Clave accelerator", "keyboard_shortcuts.hotkey": "Clave accelerator",
"keyboard_shortcuts.legend": "Monstrar iste legenda", "keyboard_shortcuts.legend": "Monstrar iste legenda",
"keyboard_shortcuts.local": "Aperir le chronologia local", "keyboard_shortcuts.local": "Aperir le chronologia local",
"keyboard_shortcuts.mention": "Mentionar le author", "keyboard_shortcuts.mention": "Mentionar le autor",
"keyboard_shortcuts.muted": "Aperir lista de usatores silentiate", "keyboard_shortcuts.muted": "Aperir lista de usatores silentiate",
"keyboard_shortcuts.my_profile": "Aperir tu profilo", "keyboard_shortcuts.my_profile": "Aperir tu profilo",
"keyboard_shortcuts.notifications": "Aperir columna de notificationes", "keyboard_shortcuts.notifications": "Aperir columna de notificationes",
@ -414,6 +414,7 @@
"limited_account_hint.action": "Monstrar profilo in omne caso", "limited_account_hint.action": "Monstrar profilo in omne caso",
"limited_account_hint.title": "Iste profilo ha essite celate per le moderatores de {domain}.", "limited_account_hint.title": "Iste profilo ha essite celate per le moderatores de {domain}.",
"link_preview.author": "Per {name}", "link_preview.author": "Per {name}",
"link_preview.more_from_author": "Plus de {name}",
"lists.account.add": "Adder al lista", "lists.account.add": "Adder al lista",
"lists.account.remove": "Remover del lista", "lists.account.remove": "Remover del lista",
"lists.delete": "Deler lista", "lists.delete": "Deler lista",
@ -467,14 +468,14 @@
"navigation_bar.search": "Cercar", "navigation_bar.search": "Cercar",
"navigation_bar.security": "Securitate", "navigation_bar.security": "Securitate",
"not_signed_in_indicator.not_signed_in": "Es necessari aperir session pro acceder a iste ressource.", "not_signed_in_indicator.not_signed_in": "Es necessari aperir session pro acceder a iste ressource.",
"notification.admin.report": "{name} ha signalate {target}", "notification.admin.report": "{name} ha reportate {target}",
"notification.admin.sign_up": "{name} se ha inscribite", "notification.admin.sign_up": "{name} se ha inscribite",
"notification.favourite": "{name} ha marcate tu message como favorite", "notification.favourite": "{name} ha marcate tu message como favorite",
"notification.follow": "{name} te ha sequite", "notification.follow": "{name} te ha sequite",
"notification.follow_request": "{name} ha requestate de sequer te", "notification.follow_request": "{name} ha requestate de sequer te",
"notification.mention": "{name} te ha mentionate", "notification.mention": "{name} te ha mentionate",
"notification.moderation-warning.learn_more": "Apprender plus", "notification.moderation-warning.learn_more": "Apprender plus",
"notification.moderation_warning": "Tu ha recepite un aviso de moderation", "notification.moderation_warning": "Tu ha recipite un advertimento de moderation",
"notification.moderation_warning.action_delete_statuses": "Alcunes de tu messages ha essite removite.", "notification.moderation_warning.action_delete_statuses": "Alcunes de tu messages ha essite removite.",
"notification.moderation_warning.action_disable": "Tu conto ha essite disactivate.", "notification.moderation_warning.action_disable": "Tu conto ha essite disactivate.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Alcunes de tu messages ha essite marcate como sensibile.", "notification.moderation_warning.action_mark_statuses_as_sensitive": "Alcunes de tu messages ha essite marcate como sensibile.",
@ -493,12 +494,12 @@
"notification.status": "{name} ha justo ora publicate", "notification.status": "{name} ha justo ora publicate",
"notification.update": "{name} ha modificate un message", "notification.update": "{name} ha modificate un message",
"notification_requests.accept": "Acceptar", "notification_requests.accept": "Acceptar",
"notification_requests.dismiss": "Dimitter", "notification_requests.dismiss": "Clauder",
"notification_requests.notifications_from": "Notificationes de {name}", "notification_requests.notifications_from": "Notificationes de {name}",
"notification_requests.title": "Notificationes filtrate", "notification_requests.title": "Notificationes filtrate",
"notifications.clear": "Rader notificationes", "notifications.clear": "Rader notificationes",
"notifications.clear_confirmation": "Es tu secur que tu vole rader permanentemente tote tu notificationes?", "notifications.clear_confirmation": "Es tu secur que tu vole rader permanentemente tote tu notificationes?",
"notifications.column_settings.admin.report": "Nove signalationes:", "notifications.column_settings.admin.report": "Nove reportos:",
"notifications.column_settings.admin.sign_up": "Nove inscriptiones:", "notifications.column_settings.admin.sign_up": "Nove inscriptiones:",
"notifications.column_settings.alert": "Notificationes de scriptorio", "notifications.column_settings.alert": "Notificationes de scriptorio",
"notifications.column_settings.favourite": "Favorites:", "notifications.column_settings.favourite": "Favorites:",
@ -621,7 +622,7 @@
"relative_time.today": "hodie", "relative_time.today": "hodie",
"reply_indicator.attachments": "{count, plural, one {# annexo} other {# annexos}}", "reply_indicator.attachments": "{count, plural, one {# annexo} other {# annexos}}",
"reply_indicator.cancel": "Cancellar", "reply_indicator.cancel": "Cancellar",
"reply_indicator.poll": "Inquesta", "reply_indicator.poll": "Sondage",
"report.block": "Blocar", "report.block": "Blocar",
"report.block_explanation": "Tu non videra le messages de iste persona. Ille non potera vider tu messages o sequer te. Ille potera saper de esser blocate.", "report.block_explanation": "Tu non videra le messages de iste persona. Ille non potera vider tu messages o sequer te. Ille potera saper de esser blocate.",
"report.categories.legal": "Juridic", "report.categories.legal": "Juridic",
@ -635,7 +636,7 @@
"report.close": "Facite", "report.close": "Facite",
"report.comment.title": "Ha il altere cosas que nos deberea saper?", "report.comment.title": "Ha il altere cosas que nos deberea saper?",
"report.forward": "Reinviar a {target}", "report.forward": "Reinviar a {target}",
"report.forward_hint": "Le conto es de un altere servitor. Inviar un copia anonymisate del signalation a illo tamben?", "report.forward_hint": "Le conto es de un altere servitor. Inviar un copia anonymisate del reporto a illo tamben?",
"report.mute": "Silentiar", "report.mute": "Silentiar",
"report.mute_explanation": "Tu non videra le messages de iste persona. Ille pote totevia sequer te e vider tu messages e non sapera de esser silentiate.", "report.mute_explanation": "Tu non videra le messages de iste persona. Ille pote totevia sequer te e vider tu messages e non sapera de esser silentiate.",
"report.next": "Sequente", "report.next": "Sequente",
@ -655,11 +656,11 @@
"report.statuses.subtitle": "Selige tote le responsas appropriate", "report.statuses.subtitle": "Selige tote le responsas appropriate",
"report.statuses.title": "Existe alcun messages que appoia iste reporto?", "report.statuses.title": "Existe alcun messages que appoia iste reporto?",
"report.submit": "Submitter", "report.submit": "Submitter",
"report.target": "Signalamento de {target}", "report.target": "Reportage de {target}",
"report.thanks.take_action": "Ecce tu optiones pro controlar lo que tu vide sur Mastodon:", "report.thanks.take_action": "Ecce tu optiones pro controlar lo que tu vide sur Mastodon:",
"report.thanks.take_action_actionable": "Durante que nos revide isto, tu pote prender mesuras contra @{name}:", "report.thanks.take_action_actionable": "Durante que nos revide isto, tu pote prender mesuras contra @{name}:",
"report.thanks.title": "Non vole vider isto?", "report.thanks.title": "Non vole vider isto?",
"report.thanks.title_actionable": "Gratias pro signalar, nos investigara isto.", "report.thanks.title_actionable": "Gratias pro reportar, nos investigara isto.",
"report.unfollow": "Cessar de sequer @{name}", "report.unfollow": "Cessar de sequer @{name}",
"report.unfollow_explanation": "Tu seque iste conto. Pro non plus vider su messages in tu fluxo de initio, cessa de sequer lo.", "report.unfollow_explanation": "Tu seque iste conto. Pro non plus vider su messages in tu fluxo de initio, cessa de sequer lo.",
"report_notification.attached_statuses": "{count, plural, one {{count} message} other {{count} messages}} annexate", "report_notification.attached_statuses": "{count, plural, one {{count} message} other {{count} messages}} annexate",
@ -677,7 +678,7 @@
"search.quick_action.status_search": "Messages correspondente a {x}", "search.quick_action.status_search": "Messages correspondente a {x}",
"search.search_or_paste": "Cerca o colla un URL", "search.search_or_paste": "Cerca o colla un URL",
"search_popout.full_text_search_disabled_message": "Non disponibile sur {domain}.", "search_popout.full_text_search_disabled_message": "Non disponibile sur {domain}.",
"search_popout.full_text_search_logged_out_message": "Solmente disponibile al initiar le session.", "search_popout.full_text_search_logged_out_message": "Solmente disponibile post aperir session.",
"search_popout.language_code": "Codice de lingua ISO", "search_popout.language_code": "Codice de lingua ISO",
"search_popout.options": "Optiones de recerca", "search_popout.options": "Optiones de recerca",
"search_popout.quick_actions": "Actiones rapide", "search_popout.quick_actions": "Actiones rapide",
@ -746,7 +747,7 @@
"status.replied_to": "Respondite a {name}", "status.replied_to": "Respondite a {name}",
"status.reply": "Responder", "status.reply": "Responder",
"status.replyAll": "Responder al discussion", "status.replyAll": "Responder al discussion",
"status.report": "Signalar @{name}", "status.report": "Reportar @{name}",
"status.sensitive_warning": "Contento sensibile", "status.sensitive_warning": "Contento sensibile",
"status.share": "Compartir", "status.share": "Compartir",
"status.show_filter_reason": "Monstrar in omne caso", "status.show_filter_reason": "Monstrar in omne caso",
@ -757,7 +758,7 @@
"status.show_original": "Monstrar original", "status.show_original": "Monstrar original",
"status.title.with_attachments": "{user} ha publicate {attachmentCount, plural, one {un annexo} other {{attachmentCount} annexos}}", "status.title.with_attachments": "{user} ha publicate {attachmentCount, plural, one {un annexo} other {{attachmentCount} annexos}}",
"status.translate": "Traducer", "status.translate": "Traducer",
"status.translated_from_with": "Traducite ab {lang} usante {provider}", "status.translated_from_with": "Traducite de {lang} usante {provider}",
"status.uncached_media_warning": "Previsualisation non disponibile", "status.uncached_media_warning": "Previsualisation non disponibile",
"status.unmute_conversation": "Non plus silentiar conversation", "status.unmute_conversation": "Non plus silentiar conversation",
"status.unpin": "Disfixar del profilo", "status.unpin": "Disfixar del profilo",
@ -796,7 +797,7 @@
"upload_modal.choose_image": "Seliger un imagine", "upload_modal.choose_image": "Seliger un imagine",
"upload_modal.description_placeholder": "Cinque expertos del zoo jam bibeva whisky frigide", "upload_modal.description_placeholder": "Cinque expertos del zoo jam bibeva whisky frigide",
"upload_modal.detect_text": "Deteger texto de un imagine", "upload_modal.detect_text": "Deteger texto de un imagine",
"upload_modal.edit_media": "Modificar le medio", "upload_modal.edit_media": "Modificar multimedia",
"upload_modal.hint": "Clicca o trahe le circulo sur le previsualisation pro eliger le puncto focal que essera sempre visibile sur tote le miniaturas.", "upload_modal.hint": "Clicca o trahe le circulo sur le previsualisation pro eliger le puncto focal que essera sempre visibile sur tote le miniaturas.",
"upload_modal.preparing_ocr": "Preparation del OCR…", "upload_modal.preparing_ocr": "Preparation del OCR…",
"upload_modal.preview_label": "Previsualisation ({ratio})", "upload_modal.preview_label": "Previsualisation ({ratio})",

View File

@ -414,6 +414,7 @@
"limited_account_hint.action": "Birta notandasniðið samt", "limited_account_hint.action": "Birta notandasniðið samt",
"limited_account_hint.title": "Þetta notandasnið hefur verið falið af umsjónarmönnum {domain}.", "limited_account_hint.title": "Þetta notandasnið hefur verið falið af umsjónarmönnum {domain}.",
"link_preview.author": "Eftir {name}", "link_preview.author": "Eftir {name}",
"link_preview.more_from_author": "Meira frá {name}",
"lists.account.add": "Bæta á lista", "lists.account.add": "Bæta á lista",
"lists.account.remove": "Fjarlægja af lista", "lists.account.remove": "Fjarlægja af lista",
"lists.delete": "Eyða lista", "lists.delete": "Eyða lista",

View File

@ -414,7 +414,7 @@
"limited_account_hint.action": "그래도 프로필 보기", "limited_account_hint.action": "그래도 프로필 보기",
"limited_account_hint.title": "이 프로필은 {domain}의 중재자에 의해 숨겨진 상태입니다.", "limited_account_hint.title": "이 프로필은 {domain}의 중재자에 의해 숨겨진 상태입니다.",
"link_preview.author": "{name}", "link_preview.author": "{name}",
"link_preview.more_from_author": "{name} 더 둘러보기", "link_preview.more_from_author": "{name} 프로필 보기",
"lists.account.add": "리스트에 추가", "lists.account.add": "리스트에 추가",
"lists.account.remove": "리스트에서 제거", "lists.account.remove": "리스트에서 제거",
"lists.delete": "리스트 삭제", "lists.delete": "리스트 삭제",

View File

@ -398,6 +398,7 @@
"limited_account_hint.action": "Amostra el profil entanto", "limited_account_hint.action": "Amostra el profil entanto",
"limited_account_hint.title": "Este profil fue eskondido por los moderadores de {domain}.", "limited_account_hint.title": "Este profil fue eskondido por los moderadores de {domain}.",
"link_preview.author": "Publikasyon de {name}", "link_preview.author": "Publikasyon de {name}",
"link_preview.more_from_author": "Mas de {name}",
"lists.account.add": "Adjusta a lista", "lists.account.add": "Adjusta a lista",
"lists.account.remove": "Kita de lista", "lists.account.remove": "Kita de lista",
"lists.delete": "Efasa lista", "lists.delete": "Efasa lista",

View File

@ -217,7 +217,7 @@
"domain_block_modal.title": "Blokuoti domeną?", "domain_block_modal.title": "Blokuoti domeną?",
"domain_block_modal.you_will_lose_followers": "Visi tavo sekėjai iš šio serverio bus pašalinti.", "domain_block_modal.you_will_lose_followers": "Visi tavo sekėjai iš šio serverio bus pašalinti.",
"domain_block_modal.you_wont_see_posts": "Nematysi naudotojų įrašų ar pranešimų šiame serveryje.", "domain_block_modal.you_wont_see_posts": "Nematysi naudotojų įrašų ar pranešimų šiame serveryje.",
"domain_pill.activitypub_lets_connect": "Tai leidžia tau sąveikauti su žmonėmis ne tik Mastodon, bet ir įvairiose socialinėse programėlėse.", "domain_pill.activitypub_lets_connect": "Tai leidžia tau prisijungti ir bendrauti su žmonėmis ne tik Mastodon, bet ir įvairiose socialinėse programėlėse.",
"domain_pill.activitypub_like_language": "ActivityPub tai tarsi kalba, kuria Mastodon kalba su kitais socialiniais tinklais.", "domain_pill.activitypub_like_language": "ActivityPub tai tarsi kalba, kuria Mastodon kalba su kitais socialiniais tinklais.",
"domain_pill.server": "Serveris", "domain_pill.server": "Serveris",
"domain_pill.their_handle": "Jų socialinis medijos vardas:", "domain_pill.their_handle": "Jų socialinis medijos vardas:",
@ -414,6 +414,7 @@
"limited_account_hint.action": "Vis tiek rodyti profilį", "limited_account_hint.action": "Vis tiek rodyti profilį",
"limited_account_hint.title": "Šį profilį paslėpė {domain} prižiūrėtojai.", "limited_account_hint.title": "Šį profilį paslėpė {domain} prižiūrėtojai.",
"link_preview.author": "Sukūrė {name}", "link_preview.author": "Sukūrė {name}",
"link_preview.more_from_author": "Daugiau iš {name}",
"lists.account.add": "Pridėti į sąrašą", "lists.account.add": "Pridėti į sąrašą",
"lists.account.remove": "Pašalinti iš sąrašo", "lists.account.remove": "Pašalinti iš sąrašo",
"lists.delete": "Ištrinti sąrašą", "lists.delete": "Ištrinti sąrašą",
@ -432,7 +433,15 @@
"loading_indicator.label": "Kraunama…", "loading_indicator.label": "Kraunama…",
"media_gallery.toggle_visible": "{number, plural, one {Slėpti vaizdą} few {Slėpti vaizdus} many {Slėpti vaizdo} other {Slėpti vaizdų}}", "media_gallery.toggle_visible": "{number, plural, one {Slėpti vaizdą} few {Slėpti vaizdus} many {Slėpti vaizdo} other {Slėpti vaizdų}}",
"moved_to_account_banner.text": "Tavo paskyra {disabledAccount} šiuo metu išjungta, nes persikėlei į {movedToAccount}.", "moved_to_account_banner.text": "Tavo paskyra {disabledAccount} šiuo metu išjungta, nes persikėlei į {movedToAccount}.",
"mute_modal.hide_from_notifications": "Slėpti nuo pranešimų",
"mute_modal.hide_options": "Slėpti parinktis",
"mute_modal.indefinite": "Kol atšauksiu jų nutildymą",
"mute_modal.show_options": "Rodyti parinktis", "mute_modal.show_options": "Rodyti parinktis",
"mute_modal.they_can_mention_and_follow": "Jie gali tave paminėti ir sekti, bet tu jų nematysi.",
"mute_modal.they_wont_know": "Jie nežinos, kad buvo nutildyti.",
"mute_modal.title": "Nutildyti naudotoją?",
"mute_modal.you_wont_see_mentions": "Nematysi įrašus, kuriuose jie paminimi.",
"mute_modal.you_wont_see_posts": "Jie vis tiek gali matyti tavo įrašus, bet tu nematysi jų.",
"navigation_bar.about": "Apie", "navigation_bar.about": "Apie",
"navigation_bar.advanced_interface": "Atidaryti išplėstinę žiniatinklio sąsają", "navigation_bar.advanced_interface": "Atidaryti išplėstinę žiniatinklio sąsają",
"navigation_bar.blocks": "Užblokuoti naudotojai", "navigation_bar.blocks": "Užblokuoti naudotojai",
@ -477,6 +486,7 @@
"notification.own_poll": "Tavo apklausa baigėsi", "notification.own_poll": "Tavo apklausa baigėsi",
"notification.poll": "Apklausa, kurioje balsavai, pasibaigė", "notification.poll": "Apklausa, kurioje balsavai, pasibaigė",
"notification.reblog": "{name} pakėlė tavo įrašą", "notification.reblog": "{name} pakėlė tavo įrašą",
"notification.relationships_severance_event": "Prarasti sąryšiai su {name}",
"notification.relationships_severance_event.learn_more": "Sužinoti daugiau", "notification.relationships_severance_event.learn_more": "Sužinoti daugiau",
"notification.relationships_severance_event.user_domain_block": "Tu užblokavai {target}. Pašalinama {followersCount} savo sekėjų ir {followingCount, plural, one {# paskyrą} few {# paskyrai} many {# paskyros} other {# paskyrų}}, kurios seki.", "notification.relationships_severance_event.user_domain_block": "Tu užblokavai {target}. Pašalinama {followersCount} savo sekėjų ir {followingCount, plural, one {# paskyrą} few {# paskyrai} many {# paskyros} other {# paskyrų}}, kurios seki.",
"notification.status": "{name} ką tik paskelbė", "notification.status": "{name} ką tik paskelbė",
@ -561,7 +571,7 @@
"onboarding.steps.setup_profile.title": "Suasmenink savo profilį", "onboarding.steps.setup_profile.title": "Suasmenink savo profilį",
"onboarding.steps.share_profile.body": "Leisk draugams sužinoti, kaip tave rasti Mastodon.", "onboarding.steps.share_profile.body": "Leisk draugams sužinoti, kaip tave rasti Mastodon.",
"onboarding.steps.share_profile.title": "Bendrink savo Mastodon profilį", "onboarding.steps.share_profile.title": "Bendrink savo Mastodon profilį",
"onboarding.tips.2fa": "<strong>Ar žinojai?</strong> Savo paskyrą gali apsaugoti nustatęs (-usi) dviejų veiksnių tapatybės nustatymą paskyros nustatymuose. Jis veikia su bet kuria pasirinkta TOTP programėle, telefono numeris nebūtinas.", "onboarding.tips.2fa": "<strong>Ar žinojai?</strong> Savo paskyrą gali apsaugoti nustatant dviejų veiksnių tapatybės nustatymą paskyros nustatymuose. Jis veikia su bet kuria pasirinkta TOTP programėle, telefono numeris nebūtinas.",
"onboarding.tips.accounts_from_other_servers": "<strong>Ar žinojai?</strong> Kadangi Mastodon decentralizuotas, kai kurie profiliai, su kuriais susidursi, bus talpinami ne tavo, o kituose serveriuose. Ir vis tiek galėsi su jais sklandžiai bendrauti! Jų serveris yra antroje naudotojo vardo pusėje.", "onboarding.tips.accounts_from_other_servers": "<strong>Ar žinojai?</strong> Kadangi Mastodon decentralizuotas, kai kurie profiliai, su kuriais susidursi, bus talpinami ne tavo, o kituose serveriuose. Ir vis tiek galėsi su jais sklandžiai bendrauti! Jų serveris yra antroje naudotojo vardo pusėje.",
"onboarding.tips.migration": "<strong>Ar žinojai?</strong> Jei manai, kad {domain} serveris ateityje tau netiks, gali persikelti į kitą Mastodon serverį neprarandant savo sekėjų. Gali net talpinti savo paties serverį.", "onboarding.tips.migration": "<strong>Ar žinojai?</strong> Jei manai, kad {domain} serveris ateityje tau netiks, gali persikelti į kitą Mastodon serverį neprarandant savo sekėjų. Gali net talpinti savo paties serverį.",
"onboarding.tips.verification": "<strong>Ar žinojai?</strong> Savo paskyrą gali patvirtinti pateikęs (-usi) nuorodą į Mastodon profilį savo interneto svetainėje ir pridėjęs (-usi) svetainę prie savo profilio. Nereikia jokių mokesčių ar dokumentų.", "onboarding.tips.verification": "<strong>Ar žinojai?</strong> Savo paskyrą gali patvirtinti pateikęs (-usi) nuorodą į Mastodon profilį savo interneto svetainėje ir pridėjęs (-usi) svetainę prie savo profilio. Nereikia jokių mokesčių ar dokumentų.",
@ -632,7 +642,7 @@
"report.reasons.legal_description": "Manai, kad tai pažeidžia tavo arba serverio šalies įstatymus", "report.reasons.legal_description": "Manai, kad tai pažeidžia tavo arba serverio šalies įstatymus",
"report.reasons.other": "Tai kažkas kita", "report.reasons.other": "Tai kažkas kita",
"report.reasons.other_description": "Problema netinka kitoms kategorijoms", "report.reasons.other_description": "Problema netinka kitoms kategorijoms",
"report.reasons.spam": "Tai šlamštas", "report.reasons.spam": "Tai šlamštas",
"report.reasons.spam_description": "Kenkėjiškos nuorodos, netikras įsitraukimas arba pasikartojantys atsakymai", "report.reasons.spam_description": "Kenkėjiškos nuorodos, netikras įsitraukimas arba pasikartojantys atsakymai",
"report.reasons.violation": "Tai pažeidžia serverio taisykles", "report.reasons.violation": "Tai pažeidžia serverio taisykles",
"report.reasons.violation_description": "Žinai, kad tai pažeidžia konkrečias taisykles", "report.reasons.violation_description": "Žinai, kad tai pažeidžia konkrečias taisykles",

View File

@ -92,6 +92,8 @@
"block_modal.remote_users_caveat": "Mēs vaicāsim serverim {domain} ņemt vērā Tavu lēmumu. Tomēr atbilstība nav nodrošināta, jo atsevišķi serveri var apstrādāt bloķēšanu citādi. Publiski ieraksti joprojām var būt redzami lietotājiem, kuri nav pieteikušies.", "block_modal.remote_users_caveat": "Mēs vaicāsim serverim {domain} ņemt vērā Tavu lēmumu. Tomēr atbilstība nav nodrošināta, jo atsevišķi serveri var apstrādāt bloķēšanu citādi. Publiski ieraksti joprojām var būt redzami lietotājiem, kuri nav pieteikušies.",
"block_modal.show_less": "Rādīt mazāk", "block_modal.show_less": "Rādīt mazāk",
"block_modal.show_more": "Parādīt mazāk", "block_modal.show_more": "Parādīt mazāk",
"block_modal.they_cant_mention": "Nevar Tevi pieminēt vai sekot Tev.",
"block_modal.they_cant_see_posts": "Nevar redzēt Tavus ierakstus, un Tu neredzēsi lietotāja.",
"boost_modal.combo": "Nospied {combo}, lai nākamreiz šo izlaistu", "boost_modal.combo": "Nospied {combo}, lai nākamreiz šo izlaistu",
"bundle_column_error.copy_stacktrace": "Kopēt kļūdu ziņojumu", "bundle_column_error.copy_stacktrace": "Kopēt kļūdu ziņojumu",
"bundle_column_error.error.body": "Pieprasīto lapu nevarēja atveidot. Tas varētu būt saistīts ar kļūdu mūsu kodā, vai tā ir pārlūkprogrammas saderības problēma.", "bundle_column_error.error.body": "Pieprasīto lapu nevarēja atveidot. Tas varētu būt saistīts ar kļūdu mūsu kodā, vai tā ir pārlūkprogrammas saderības problēma.",
@ -173,7 +175,7 @@
"confirmations.discard_edit_media.message": "Ir nesaglabātas izmaiņas informācijas nesēja aprakstā vai priekšskatījumā. Vēlies tās atmest tik un tā?", "confirmations.discard_edit_media.message": "Ir nesaglabātas izmaiņas informācijas nesēja aprakstā vai priekšskatījumā. Vēlies tās atmest tik un tā?",
"confirmations.domain_block.message": "Vai tu tiešām vēlies bloķēt visu domēnu {domain}? Parasti pietiek, ja nobloķē vai apklusini kādu. Tu neredzēsi saturu vai paziņojumus no šī domēna nevienā laika līnijā. Tavi sekotāji no šī domēna tiks noņemti.", "confirmations.domain_block.message": "Vai tu tiešām vēlies bloķēt visu domēnu {domain}? Parasti pietiek, ja nobloķē vai apklusini kādu. Tu neredzēsi saturu vai paziņojumus no šī domēna nevienā laika līnijā. Tavi sekotāji no šī domēna tiks noņemti.",
"confirmations.edit.confirm": "Labot", "confirmations.edit.confirm": "Labot",
"confirmations.edit.message": "Rediģējot, tiks pārrakstīts ziņojums, kuru tu šobrīd raksti. Vai tiešām vēlies turpināt?", "confirmations.edit.message": "Labošana pārrakstīs ziņojumu, kas šobrīd tiek sastādīts. Vai tiešām turpināt?",
"confirmations.logout.confirm": "Iziet", "confirmations.logout.confirm": "Iziet",
"confirmations.logout.message": "Vai tiešām vēlies izrakstīties?", "confirmations.logout.message": "Vai tiešām vēlies izrakstīties?",
"confirmations.mute.confirm": "Apklusināt", "confirmations.mute.confirm": "Apklusināt",
@ -377,6 +379,7 @@
"limited_account_hint.action": "Tik un tā rādīt profilu", "limited_account_hint.action": "Tik un tā rādīt profilu",
"limited_account_hint.title": "{domain} moderatori ir paslēpuši šo profilu.", "limited_account_hint.title": "{domain} moderatori ir paslēpuši šo profilu.",
"link_preview.author": "Pēc {name}", "link_preview.author": "Pēc {name}",
"link_preview.more_from_author": "Vairāk no {name}",
"lists.account.add": "Pievienot sarakstam", "lists.account.add": "Pievienot sarakstam",
"lists.account.remove": "Noņemt no saraksta", "lists.account.remove": "Noņemt no saraksta",
"lists.delete": "Izdzēst sarakstu", "lists.delete": "Izdzēst sarakstu",
@ -444,16 +447,19 @@
"notification.relationships_severance_event": "Zaudēti savienojumi ar {name}", "notification.relationships_severance_event": "Zaudēti savienojumi ar {name}",
"notification.relationships_severance_event.learn_more": "Uzzināt vairāk", "notification.relationships_severance_event.learn_more": "Uzzināt vairāk",
"notification.status": "{name} tikko publicēja", "notification.status": "{name} tikko publicēja",
"notification.update": "{name} rediģēja ierakstu", "notification.update": "{name} laboja ierakstu",
"notification_requests.accept": "Pieņemt", "notification_requests.accept": "Pieņemt",
"notification_requests.dismiss": "Noraidīt", "notification_requests.dismiss": "Noraidīt",
"notification_requests.notifications_from": "Paziņojumi no {name}", "notification_requests.notifications_from": "Paziņojumi no {name}",
"notification_requests.title": "Atlasītie paziņojumi",
"notifications.clear": "Notīrīt paziņojumus", "notifications.clear": "Notīrīt paziņojumus",
"notifications.clear_confirmation": "Vai tiešām vēlies neatgriezeniski notīrīt visus savus paziņojumus?", "notifications.clear_confirmation": "Vai tiešām vēlies neatgriezeniski notīrīt visus savus paziņojumus?",
"notifications.column_settings.admin.report": "Jauni ziņojumi:", "notifications.column_settings.admin.report": "Jauni ziņojumi:",
"notifications.column_settings.admin.sign_up": "Jaunas pierakstīšanās:", "notifications.column_settings.admin.sign_up": "Jaunas pierakstīšanās:",
"notifications.column_settings.alert": "Darbvirsmas paziņojumi", "notifications.column_settings.alert": "Darbvirsmas paziņojumi",
"notifications.column_settings.favourite": "Izlase:", "notifications.column_settings.favourite": "Izlase:",
"notifications.column_settings.filter_bar.advanced": "Attēlot visas kategorijas",
"notifications.column_settings.filter_bar.category": "Atrās atlasīšanas josla",
"notifications.column_settings.follow": "Jauni sekotāji:", "notifications.column_settings.follow": "Jauni sekotāji:",
"notifications.column_settings.follow_request": "Jauni sekošanas pieprasījumi:", "notifications.column_settings.follow_request": "Jauni sekošanas pieprasījumi:",
"notifications.column_settings.mention": "Pieminēšanas:", "notifications.column_settings.mention": "Pieminēšanas:",
@ -481,7 +487,9 @@
"notifications.permission_required": "Darbvirsmas paziņojumi nav pieejami, jo nav piešķirta nepieciešamā atļauja.", "notifications.permission_required": "Darbvirsmas paziņojumi nav pieejami, jo nav piešķirta nepieciešamā atļauja.",
"notifications.policy.filter_new_accounts_title": "Jauni konti", "notifications.policy.filter_new_accounts_title": "Jauni konti",
"notifications.policy.filter_not_followers_title": "Cilvēki, kuri Tev neseko", "notifications.policy.filter_not_followers_title": "Cilvēki, kuri Tev neseko",
"notifications.policy.filter_not_following_hint": "Līdz tos pašrocīgi apstiprināsi",
"notifications.policy.filter_not_following_title": "Cilvēki, kuriem Tu neseko", "notifications.policy.filter_not_following_title": "Cilvēki, kuriem Tu neseko",
"notifications.policy.title": "Atlasīt paziņojumus no…",
"notifications_permission_banner.enable": "Iespējot darbvirsmas paziņojumus", "notifications_permission_banner.enable": "Iespējot darbvirsmas paziņojumus",
"notifications_permission_banner.how_to_control": "Lai saņemtu paziņojumus, kad Mastodon nav atvērts, iespējo darbvirsmas paziņojumus. Vari precīzi kontrolēt, kāda veida mijiedarbības rada darbvirsmas paziņojumus, izmantojot augstāk redzamo pogu {icon}, kad tie būs iespējoti.", "notifications_permission_banner.how_to_control": "Lai saņemtu paziņojumus, kad Mastodon nav atvērts, iespējo darbvirsmas paziņojumus. Vari precīzi kontrolēt, kāda veida mijiedarbības rada darbvirsmas paziņojumus, izmantojot augstāk redzamo pogu {icon}, kad tie būs iespējoti.",
"notifications_permission_banner.title": "Nekad nepalaid neko garām", "notifications_permission_banner.title": "Nekad nepalaid neko garām",
@ -539,7 +547,9 @@
"privacy.direct.short": "Noteikti cilvēki", "privacy.direct.short": "Noteikti cilvēki",
"privacy.private.long": "Tikai Tavi sekotāji", "privacy.private.long": "Tikai Tavi sekotāji",
"privacy.private.short": "Sekotāji", "privacy.private.short": "Sekotāji",
"privacy.public.long": "Jebkurš Mastodon un ārpus tā",
"privacy.public.short": "Publiska", "privacy.public.short": "Publiska",
"privacy.unlisted.long": "Mazāk algoritmisku fanfaru",
"privacy_policy.last_updated": "Pēdējo reizi atjaunināta {date}", "privacy_policy.last_updated": "Pēdējo reizi atjaunināta {date}",
"privacy_policy.title": "Privātuma politika", "privacy_policy.title": "Privātuma politika",
"recommended": "Ieteicams", "recommended": "Ieteicams",
@ -557,6 +567,7 @@
"relative_time.minutes": "{number}m", "relative_time.minutes": "{number}m",
"relative_time.seconds": "{number}s", "relative_time.seconds": "{number}s",
"relative_time.today": "šodien", "relative_time.today": "šodien",
"reply_indicator.attachments": "{count, plural, zero{# pielikumu} one {# pielikums} other {# pielikumi}}",
"reply_indicator.cancel": "Atcelt", "reply_indicator.cancel": "Atcelt",
"reply_indicator.poll": "Aptauja", "reply_indicator.poll": "Aptauja",
"report.block": "Bloķēt", "report.block": "Bloķēt",
@ -630,7 +641,7 @@
"search_results.title": "Meklēt {q}", "search_results.title": "Meklēt {q}",
"server_banner.about_active_users": "Cilvēki, kas izmantojuši šo serveri pēdējo 30 dienu laikā (aktīvie lietotāji mēnesī)", "server_banner.about_active_users": "Cilvēki, kas izmantojuši šo serveri pēdējo 30 dienu laikā (aktīvie lietotāji mēnesī)",
"server_banner.active_users": "aktīvi lietotāji", "server_banner.active_users": "aktīvi lietotāji",
"server_banner.administered_by": "Administrē:", "server_banner.administered_by": "Pārvalda:",
"server_banner.introduction": "{domain} ir daļa no decentralizētā sociālā tīkla, ko nodrošina {mastodon}.", "server_banner.introduction": "{domain} ir daļa no decentralizētā sociālā tīkla, ko nodrošina {mastodon}.",
"server_banner.learn_more": "Uzzināt vairāk", "server_banner.learn_more": "Uzzināt vairāk",
"server_banner.server_stats": "Servera statistika:", "server_banner.server_stats": "Servera statistika:",
@ -652,9 +663,10 @@
"status.direct_indicator": "Pieminēts privāti", "status.direct_indicator": "Pieminēts privāti",
"status.edit": "Labot", "status.edit": "Labot",
"status.edited": "Pēdējoreiz labots {date}", "status.edited": "Pēdējoreiz labots {date}",
"status.edited_x_times": "Labots {count, plural, one {{count} reizi} other {{count} reizes}}", "status.edited_x_times": "Labots {count, plural, zero {{count} reižu} one {{count} reizi} other {{count} reizes}}",
"status.embed": "Iegult", "status.embed": "Iegult",
"status.favourite": "Izlasē", "status.favourite": "Izlasē",
"status.favourites": "{count, plural, zero {izlasēs} one {izlasē} other {izlasēs}}",
"status.filter": "Filtrē šo ziņu", "status.filter": "Filtrē šo ziņu",
"status.filtered": "Filtrēts", "status.filtered": "Filtrēts",
"status.hide": "Slēpt ierakstu", "status.hide": "Slēpt ierakstu",
@ -675,6 +687,7 @@
"status.reblog": "Pastiprināt", "status.reblog": "Pastiprināt",
"status.reblog_private": "Pastiprināt, nemainot redzamību", "status.reblog_private": "Pastiprināt, nemainot redzamību",
"status.reblogged_by": "{name} pastiprināja", "status.reblogged_by": "{name} pastiprināja",
"status.reblogs": "{count, plural, zero {pastiprinājumu} one {pastiprinājums} other {pastiprinājumi}}",
"status.reblogs.empty": "Neviens šo ierakstu vēl nav pastiprinājis. Kad būs, tie parādīsies šeit.", "status.reblogs.empty": "Neviens šo ierakstu vēl nav pastiprinājis. Kad būs, tie parādīsies šeit.",
"status.redraft": "Dzēst un pārrakstīt", "status.redraft": "Dzēst un pārrakstīt",
"status.remove_bookmark": "Noņemt grāmatzīmi", "status.remove_bookmark": "Noņemt grāmatzīmi",

View File

@ -17,9 +17,12 @@
"account.badges.group": "गट", "account.badges.group": "गट",
"account.block": "@{name} यांना ब्लॉक करा", "account.block": "@{name} यांना ब्लॉक करा",
"account.block_domain": "{domain} पासून सर्व लपवा", "account.block_domain": "{domain} पासून सर्व लपवा",
"account.block_short": "अवरोध",
"account.blocked": "ब्लॉक केले आहे", "account.blocked": "ब्लॉक केले आहे",
"account.browse_more_on_origin_server": "मूळ प्रोफाइलवर अधिक ब्राउझ करा", "account.browse_more_on_origin_server": "मूळ प्रोफाइलवर अधिक ब्राउझ करा",
"account.cancel_follow_request": "फॉलो विनंती मागे घ्या", "account.cancel_follow_request": "फॉलो विनंती मागे घ्या",
"account.copy": "दुवा कॉपी करा",
"account.direct": "खाजगीरित्या उल्लेखीत @{name}",
"account.disable_notifications": "जेव्हा @{name} पोस्ट करतात तेव्हा मला सूचित करणे थांबवा", "account.disable_notifications": "जेव्हा @{name} पोस्ट करतात तेव्हा मला सूचित करणे थांबवा",
"account.domain_blocked": "Domain hidden", "account.domain_blocked": "Domain hidden",
"account.edit_profile": "प्रोफाइल एडिट करा", "account.edit_profile": "प्रोफाइल एडिट करा",
@ -29,6 +32,7 @@
"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} Toot} other {{counter} Toots}}", "account.followers_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}",
@ -45,6 +49,7 @@
"account.mention": "@{name} चा उल्लेख करा", "account.mention": "@{name} चा उल्लेख करा",
"account.moved_to": "{name} ने सूचित केले आहे की त्यांचे नवीन खाते आता आहे:", "account.moved_to": "{name} ने सूचित केले आहे की त्यांचे नवीन खाते आता आहे:",
"account.mute": "@{name} ला मूक कारा", "account.mute": "@{name} ला मूक कारा",
"account.mute_short": "नि:शब्द",
"account.muted": "मौन", "account.muted": "मौन",
"account.open_original_page": "मूळ पृष्ठ उघडा", "account.open_original_page": "मूळ पृष्ठ उघडा",
"account.posts": "Toots", "account.posts": "Toots",

View File

@ -414,6 +414,7 @@
"limited_account_hint.action": "Alsnog het profiel tonen", "limited_account_hint.action": "Alsnog het profiel tonen",
"limited_account_hint.title": "Dit profiel is door de moderatoren van {domain} verborgen.", "limited_account_hint.title": "Dit profiel is door de moderatoren van {domain} verborgen.",
"link_preview.author": "Door {name}", "link_preview.author": "Door {name}",
"link_preview.more_from_author": "Meer van {name}",
"lists.account.add": "Aan lijst toevoegen", "lists.account.add": "Aan lijst toevoegen",
"lists.account.remove": "Uit lijst verwijderen", "lists.account.remove": "Uit lijst verwijderen",
"lists.delete": "Lijst verwijderen", "lists.delete": "Lijst verwijderen",

View File

@ -391,6 +391,7 @@
"limited_account_hint.action": "Aj tak zobraziť profil", "limited_account_hint.action": "Aj tak zobraziť profil",
"limited_account_hint.title": "Tento profil bol skrytý správcami servera {domain}.", "limited_account_hint.title": "Tento profil bol skrytý správcami servera {domain}.",
"link_preview.author": "Autor: {name}", "link_preview.author": "Autor: {name}",
"link_preview.more_from_author": "Viac od {name}",
"lists.account.add": "Pridať do zoznamu", "lists.account.add": "Pridať do zoznamu",
"lists.account.remove": "Odstrániť zo zoznamu", "lists.account.remove": "Odstrániť zo zoznamu",
"lists.delete": "Vymazať zoznam", "lists.delete": "Vymazať zoznam",
@ -411,6 +412,7 @@
"moved_to_account_banner.text": "Váš účet {disabledAccount} je momentálne deaktivovaný, pretože ste sa presunuli na {movedToAccount}.", "moved_to_account_banner.text": "Váš účet {disabledAccount} je momentálne deaktivovaný, pretože ste sa presunuli na {movedToAccount}.",
"mute_modal.hide_from_notifications": "Ukryť z upozornení", "mute_modal.hide_from_notifications": "Ukryť z upozornení",
"mute_modal.hide_options": "Skryť možnosti", "mute_modal.hide_options": "Skryť možnosti",
"mute_modal.indefinite": "Pokiaľ ich neodtíšim",
"mute_modal.show_options": "Zobraziť možnosti", "mute_modal.show_options": "Zobraziť možnosti",
"mute_modal.title": "Stíšiť užívateľa?", "mute_modal.title": "Stíšiť užívateľa?",
"navigation_bar.about": "O tomto serveri", "navigation_bar.about": "O tomto serveri",

View File

@ -414,6 +414,7 @@
"limited_account_hint.action": "Shfaqe profilin sido qoftë", "limited_account_hint.action": "Shfaqe profilin sido qoftë",
"limited_account_hint.title": "Ky profil është fshehur nga moderatorët e {domain}.", "limited_account_hint.title": "Ky profil është fshehur nga moderatorët e {domain}.",
"link_preview.author": "Nga {name}", "link_preview.author": "Nga {name}",
"link_preview.more_from_author": "Më tepër nga {name}",
"lists.account.add": "Shto në listë", "lists.account.add": "Shto në listë",
"lists.account.remove": "Hiqe nga lista", "lists.account.remove": "Hiqe nga lista",
"lists.delete": "Fshije listën", "lists.delete": "Fshije listën",

View File

@ -414,6 +414,7 @@
"limited_account_hint.action": "Ipak prikaži profil", "limited_account_hint.action": "Ipak prikaži profil",
"limited_account_hint.title": "Ovaj profil su sakrili moderatori {domain}.", "limited_account_hint.title": "Ovaj profil su sakrili moderatori {domain}.",
"link_preview.author": "Po {name}", "link_preview.author": "Po {name}",
"link_preview.more_from_author": "Više od {name}",
"lists.account.add": "Dodaj na listu", "lists.account.add": "Dodaj na listu",
"lists.account.remove": "Ukloni sa liste", "lists.account.remove": "Ukloni sa liste",
"lists.delete": "Izbriši listu", "lists.delete": "Izbriši listu",

View File

@ -414,6 +414,7 @@
"limited_account_hint.action": "Ипак прикажи профил", "limited_account_hint.action": "Ипак прикажи профил",
"limited_account_hint.title": "Овај профил су сакрили модератори {domain}.", "limited_account_hint.title": "Овај профил су сакрили модератори {domain}.",
"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": "Избриши листу",

View File

@ -414,6 +414,7 @@
"limited_account_hint.action": "Visa profil ändå", "limited_account_hint.action": "Visa profil ändå",
"limited_account_hint.title": "Denna profil har dolts av {domain}s moderatorer.", "limited_account_hint.title": "Denna profil har dolts av {domain}s moderatorer.",
"link_preview.author": "Av {name}", "link_preview.author": "Av {name}",
"link_preview.more_from_author": "Mer från {name}",
"lists.account.add": "Lägg till i lista", "lists.account.add": "Lägg till i lista",
"lists.account.remove": "Ta bort från lista", "lists.account.remove": "Ta bort från lista",
"lists.delete": "Radera lista", "lists.delete": "Radera lista",

View File

@ -414,6 +414,7 @@
"limited_account_hint.action": "แสดงโปรไฟล์ต่อไป", "limited_account_hint.action": "แสดงโปรไฟล์ต่อไป",
"limited_account_hint.title": "มีการซ่อนโปรไฟล์นี้โดยผู้กลั่นกรองของ {domain}", "limited_account_hint.title": "มีการซ่อนโปรไฟล์นี้โดยผู้กลั่นกรองของ {domain}",
"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": "ลบรายการ",

View File

@ -414,6 +414,7 @@
"limited_account_hint.action": "Yine de profili göster", "limited_account_hint.action": "Yine de profili göster",
"limited_account_hint.title": "Bu profil {domain} moderatörleri tarafından gizlendi.", "limited_account_hint.title": "Bu profil {domain} moderatörleri tarafından gizlendi.",
"link_preview.author": "Yazar: {name}", "link_preview.author": "Yazar: {name}",
"link_preview.more_from_author": "{name} kişisinden daha fazlası",
"lists.account.add": "Listeye ekle", "lists.account.add": "Listeye ekle",
"lists.account.remove": "Listeden kaldır", "lists.account.remove": "Listeden kaldır",
"lists.delete": "Listeyi sil", "lists.delete": "Listeyi sil",

View File

@ -414,6 +414,7 @@
"limited_account_hint.action": "Усе одно показати профіль", "limited_account_hint.action": "Усе одно показати профіль",
"limited_account_hint.title": "Цей профіль сховали модератори {domain}.", "limited_account_hint.title": "Цей профіль сховали модератори {domain}.",
"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": "Видалити список",

View File

@ -10,7 +10,7 @@ class Admin::Metrics::Dimension::SoftwareVersionsDimension < Admin::Metrics::Dim
protected protected
def perform_query def perform_query
[mastodon_version, ruby_version, postgresql_version, redis_version, elasticsearch_version].compact [mastodon_version, ruby_version, postgresql_version, redis_version, elasticsearch_version, libvips_version].compact
end end
def mastodon_version def mastodon_version
@ -71,6 +71,17 @@ class Admin::Metrics::Dimension::SoftwareVersionsDimension < Admin::Metrics::Dim
nil nil
end end
def libvips_version
return unless Rails.configuration.x.use_vips
{
key: 'libvips',
human_key: 'libvips',
value: Vips.version_string,
human_value: Vips.version_string,
}
end
def redis_info def redis_info
@redis_info ||= if redis.is_a?(Redis::Namespace) @redis_info ||= if redis.is_a?(Redis::Namespace)
redis.redis.info redis.redis.info

View File

@ -11,6 +11,9 @@ class ScopeTransformer < Parslet::Transform
@namespace = scope[:namespace]&.to_s @namespace = scope[:namespace]&.to_s
@access = scope[:access] ? [scope[:access].to_s] : DEFAULT_ACCESS.dup @access = scope[:access] ? [scope[:access].to_s] : DEFAULT_ACCESS.dup
@term = scope[:term]&.to_s || DEFAULT_TERM @term = scope[:term]&.to_s || DEFAULT_TERM
# # override for profile scope which is read only
@access = %w(read) if @term == 'profile'
end end
def key def key

View File

@ -69,7 +69,7 @@ module Attachmentable
original_extension = Paperclip::Interpolations.extension(attachment, :original) original_extension = Paperclip::Interpolations.extension(attachment, :original)
proper_extension = extensions_for_mime_type.first.to_s proper_extension = extensions_for_mime_type.first.to_s
extension = extensions_for_mime_type.include?(original_extension) ? original_extension : proper_extension extension = extensions_for_mime_type.include?(original_extension) ? original_extension : proper_extension
extension = 'jpeg' if extension == 'jpe' extension = 'jpeg' if ['jpe', 'jfif'].include?(extension)
extension extension
end end

35
app/models/link_feed.rb Normal file
View File

@ -0,0 +1,35 @@
# frozen_string_literal: true
class LinkFeed < PublicFeed
# @param [PreviewCard] preview_card
# @param [Account] account
# @param [Hash] options
def initialize(preview_card, account, options = {})
@preview_card = preview_card
super(account, options)
end
# @param [Integer] limit
# @param [Integer] max_id
# @param [Integer] since_id
# @param [Integer] min_id
# @return [Array<Status>]
def get(limit, max_id = nil, since_id = nil, min_id = nil)
scope = public_scope
scope.merge!(discoverable)
scope.merge!(attached_to_preview_card)
scope.to_a_paginated_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id)
end
private
def attached_to_preview_card
Status.joins(:preview_cards_status).where(preview_cards_status: { preview_card_id: @preview_card.id })
end
def discoverable
Account.discoverable
end
end

View File

@ -13,6 +13,7 @@
# from_account_id :bigint(8) not null # from_account_id :bigint(8) not null
# type :string # type :string
# filtered :boolean default(FALSE), not null # filtered :boolean default(FALSE), not null
# group_key :string
# #
class Notification < ApplicationRecord class Notification < ApplicationRecord
@ -136,6 +137,67 @@ class Notification < ApplicationRecord
end end
end end
# This returns notifications from the request page, but with at most one notification per group.
# Notifications that have no `group_key` each count as a separate group.
def paginate_groups_by_max_id(limit, max_id: nil, since_id: nil)
query = reorder(id: :desc)
query = query.where(id: ...max_id) if max_id.present?
query = query.where(id: (since_id + 1)...) if since_id.present?
unscoped
.with_recursive(
grouped_notifications: [
query
.select('notifications.*', "ARRAY[COALESCE(notifications.group_key, 'ungrouped-' || notifications.id)] groups")
.limit(1),
query
.joins('CROSS JOIN grouped_notifications')
.where('notifications.id < grouped_notifications.id')
.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))")
.limit(1),
]
)
.from('grouped_notifications AS notifications')
.order(id: :desc)
.limit(limit)
end
# Differs from :paginate_groups_by_max_id in that it gives the results immediately following min_id,
# whereas since_id gives the items with largest id, but with since_id as a cutoff.
# Results will be in ascending order by id.
def paginate_groups_by_min_id(limit, max_id: nil, min_id: nil)
query = reorder(id: :asc)
query = query.where(id: (min_id + 1)...) if min_id.present?
query = query.where(id: ...max_id) if max_id.present?
unscoped
.with_recursive(
grouped_notifications: [
query
.select('notifications.*', "ARRAY[COALESCE(notifications.group_key, 'ungrouped-' || notifications.id)] groups")
.limit(1),
query
.joins('CROSS JOIN grouped_notifications')
.where('notifications.id > grouped_notifications.id')
.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))")
.limit(1),
]
)
.from('grouped_notifications AS notifications')
.order(id: :asc)
.limit(limit)
end
def to_a_grouped_paginated_by_id(limit, options = {})
if options[:min_id].present?
paginate_groups_by_min_id(limit, min_id: options[:min_id], max_id: options[:max_id]).reverse
else
paginate_groups_by_max_id(limit, max_id: options[:max_id], since_id: options[:since_id]).to_a
end
end
def preload_cache_collection_target_statuses(notifications, &_block) def preload_cache_collection_target_statuses(notifications, &_block)
notifications.group_by(&:type).each do |type, grouped_notifications| notifications.group_by(&:type).each do |type, grouped_notifications|
associations = TARGET_STATUS_INCLUDES_BY_TYPE[type] associations = TARGET_STATUS_INCLUDES_BY_TYPE[type]

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
class NotificationGroup < ActiveModelSerializers::Model
attributes :group_key, :sample_accounts, :notifications_count, :notification
def self.from_notification(notification)
if notification.group_key.present?
# TODO: caching and preloading
sample_accounts = notification.account.notifications.where(group_key: notification.group_key).order(id: :desc).limit(3).map(&:from_account)
notifications_count = notification.account.notifications.where(group_key: notification.group_key).count
else
sample_accounts = [notification.from_account]
notifications_count = 1
end
NotificationGroup.new(
notification: notification,
group_key: notification.group_key || "ungrouped-#{notification.id}",
sample_accounts: sample_accounts,
notifications_count: notifications_count
)
end
delegate :type,
:target_status,
:report,
:account_relationship_severance_event,
to: :notification, prefix: false
end

View File

@ -57,7 +57,11 @@ class PreviewCard < ApplicationRecord
has_one :trend, class_name: 'PreviewCardTrend', inverse_of: :preview_card, dependent: :destroy has_one :trend, class_name: 'PreviewCardTrend', inverse_of: :preview_card, dependent: :destroy
belongs_to :author_account, class_name: 'Account', optional: true belongs_to :author_account, class_name: 'Account', optional: true
has_attached_file :image, processors: [:thumbnail, :blurhash_transcoder], styles: ->(f) { image_styles(f) }, convert_options: { all: '-quality 90 +profile "!icc,*" +set date:modify +set date:create +set date:timestamp' }, validate_media_type: false has_attached_file :image,
processors: [Rails.configuration.x.use_vips ? :lazy_thumbnail : :thumbnail, :blurhash_transcoder],
styles: ->(f) { image_styles(f) },
convert_options: { all: '-quality 90 +profile "!icc,*" +set date:modify +set date:create +set date:timestamp' },
validate_media_type: false
validates :url, presence: true, uniqueness: true, url: true validates :url, presence: true, uniqueness: true, url: true
validates_attachment_content_type :image, content_type: IMAGE_MIME_TYPES validates_attachment_content_type :image, content_type: IMAGE_MIME_TYPES

View File

@ -21,10 +21,12 @@ class Web::PushSubscription < ApplicationRecord
has_one :session_activation, foreign_key: 'web_push_subscription_id', inverse_of: :web_push_subscription, dependent: nil has_one :session_activation, foreign_key: 'web_push_subscription_id', inverse_of: :web_push_subscription, dependent: nil
validates :endpoint, presence: true validates :endpoint, presence: true, url: true
validates :key_p256dh, presence: true validates :key_p256dh, presence: true
validates :key_auth, presence: true validates :key_auth, presence: true
validates_with WebPushKeyValidator
delegate :locale, to: :associated_user delegate :locale, to: :associated_user
def encrypt(payload) def encrypt(payload)

View File

@ -0,0 +1,45 @@
# frozen_string_literal: true
class REST::NotificationGroupSerializer < ActiveModel::Serializer
attributes :group_key, :notifications_count, :type
attribute :page_min_id, if: :paginated?
attribute :page_max_id, if: :paginated?
attribute :latest_page_notification_at, if: :paginated?
has_many :sample_accounts, serializer: REST::AccountSerializer
belongs_to :target_status, key: :status, if: :status_type?, serializer: REST::StatusSerializer
belongs_to :report, if: :report_type?, serializer: REST::ReportSerializer
belongs_to :account_relationship_severance_event, key: :event, if: :relationship_severance_event?, serializer: REST::AccountRelationshipSeveranceEventSerializer
def status_type?
[:favourite, :reblog, :status, :mention, :poll, :update].include?(object.type)
end
def report_type?
object.type == :'admin.report'
end
def relationship_severance_event?
object.type == :severed_relationships
end
def page_min_id
range = instance_options[:group_metadata][object.group_key]
range.present? ? range[:min_id].to_s : object.notification.id.to_s
end
def page_max_id
range = instance_options[:group_metadata][object.group_key]
range.present? ? range[:max_id].to_s : object.notification.id.to_s
end
def latest_page_notification_at
range = instance_options[:group_metadata][object.group_key]
range.present? ? range[:latest_notification_at] : object.notification.created_at
end
def paginated?
instance_options[:group_metadata].present?
end
end

View File

@ -3,6 +3,9 @@
class NotifyService < BaseService class NotifyService < BaseService
include Redisable include Redisable
MAXIMUM_GROUP_SPAN_HOURS = 12
MAXIMUM_GROUP_GAP_TIME = 4.hours.to_i
NON_EMAIL_TYPES = %i( NON_EMAIL_TYPES = %i(
admin.report admin.report
admin.sign_up admin.sign_up
@ -183,6 +186,7 @@ class NotifyService < BaseService
return if dismiss? return if dismiss?
@notification.filtered = filter? @notification.filtered = filter?
@notification.group_key = notification_group_key
@notification.save! @notification.save!
# It's possible the underlying activity has been deleted # It's possible the underlying activity has been deleted
@ -202,6 +206,24 @@ class NotifyService < BaseService
private private
def notification_group_key
return nil if @notification.filtered || %i(favourite reblog).exclude?(@notification.type)
type_prefix = "#{@notification.type}-#{@notification.target_status.id}"
redis_key = "notif-group/#{@recipient.id}/#{type_prefix}"
hour_bucket = @notification.activity.created_at.utc.to_i / 1.hour.to_i
# Reuse previous group if it does not span too large an amount of time
previous_bucket = redis.get(redis_key).to_i
hour_bucket = previous_bucket if hour_bucket < previous_bucket + MAXIMUM_GROUP_SPAN_HOURS
# Do not track groups past a given inactivity time
# We do not concern ourselves with race conditions since we use hour buckets
redis.set(redis_key, hour_bucket, ex: MAXIMUM_GROUP_GAP_TIME)
"#{type_prefix}-#{hour_bucket}"
end
def dismiss? def dismiss?
DismissCondition.new(@notification).dismiss? DismissCondition.new(@notification).dismiss?
end end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
class WebPushKeyValidator < ActiveModel::Validator
def validate(subscription)
begin
Webpush::Encryption.encrypt('validation_test', subscription.key_p256dh, subscription.key_auth)
rescue ArgumentError, OpenSSL::PKey::EC::Point::Error
subscription.errors.add(:base, I18n.t('crypto.errors.invalid_key'))
end
end
end

View File

@ -3,7 +3,7 @@
- if self_destruct? - if self_destruct?
.flash-message.warning .flash-message.warning
= t('auth.status.self_destruct', domain: ENV.fetch('LOCAL_DOMAIN')) = t('auth.status.self_destruct', domain: Rails.configuration.x.local_domain)
- else - else
= render partial: 'status', locals: { user: @user, strikes: @strikes } = render partial: 'status', locals: { user: @user, strikes: @strikes }

View File

@ -3,7 +3,7 @@
.simple_form .simple_form
%h1.title= t('self_destruct.title') %h1.title= t('self_destruct.title')
%p.lead= t('self_destruct.lead_html', domain: ENV.fetch('LOCAL_DOMAIN')) %p.lead= t('self_destruct.lead_html', domain: Rails.configuration.x.local_domain)
.form-footer .form-footer
%ul.no-list %ul.no-list

View File

@ -44,6 +44,6 @@
%body{ class: body_classes } %body{ class: body_classes }
= content_for?(:content) ? yield(:content) : yield = content_for?(:content) ? yield(:content) : yield
.logo-resources{ 'tabindex' => '-1', 'inert' => true, 'aria-hidden' => true } .logo-resources{ 'tabindex' => '-1', 'inert' => true, 'aria-hidden' => 'true' }
= inline_svg_tag 'logo-symbol-icon.svg' = inline_svg_tag 'logo-symbol-icon.svg'
= inline_svg_tag 'logo-symbol-wordmark.svg' = inline_svg_tag 'logo-symbol-wordmark.svg'

View File

@ -21,5 +21,5 @@
%body.embed %body.embed
= yield = yield
.logo-resources{ 'tabindex' => '-1', 'inert' => true, 'aria-hidden' => true } .logo-resources{ 'tabindex' => '-1', 'inert' => true, 'aria-hidden' => 'true' }
= inline_svg_tag 'logo-symbol-icon.svg' = inline_svg_tag 'logo-symbol-icon.svg'

View File

@ -27,7 +27,7 @@ require_relative '../lib/sanitize_ext/sanitize_config'
require_relative '../lib/redis/namespace_extensions' require_relative '../lib/redis/namespace_extensions'
require_relative '../lib/paperclip/url_generator_extensions' require_relative '../lib/paperclip/url_generator_extensions'
require_relative '../lib/paperclip/attachment_extensions' require_relative '../lib/paperclip/attachment_extensions'
require_relative '../lib/paperclip/lazy_thumbnail'
require_relative '../lib/paperclip/gif_transcoder' require_relative '../lib/paperclip/gif_transcoder'
require_relative '../lib/paperclip/media_type_spoof_detector_extensions' require_relative '../lib/paperclip/media_type_spoof_detector_extensions'
require_relative '../lib/paperclip/transcoder' require_relative '../lib/paperclip/transcoder'
@ -51,6 +51,8 @@ require_relative '../lib/rails/engine_extensions'
require_relative '../lib/action_dispatch/remote_ip_extensions' require_relative '../lib/action_dispatch/remote_ip_extensions'
require_relative '../lib/active_record/database_tasks_extensions' require_relative '../lib/active_record/database_tasks_extensions'
require_relative '../lib/active_record/batches' require_relative '../lib/active_record/batches'
require_relative '../lib/active_record/with_recursive'
require_relative '../lib/arel/union_parenthesizing'
require_relative '../lib/simple_navigation/item_extensions' require_relative '../lib/simple_navigation/item_extensions'
Bundler.require(:pam_authentication) if ENV['PAM_ENABLED'] == 'true' Bundler.require(:pam_authentication) if ENV['PAM_ENABLED'] == 'true'
@ -98,6 +100,14 @@ module Mastodon
config.before_configuration do config.before_configuration do
require 'mastodon/redis_config' require 'mastodon/redis_config'
config.x.use_vips = ENV['MASTODON_USE_LIBVIPS'] == 'true'
if config.x.use_vips
require_relative '../lib/paperclip/vips_lazy_thumbnail'
else
require_relative '../lib/paperclip/lazy_thumbnail'
end
end end
config.to_prepare do config.to_prepare do

View File

@ -74,7 +74,8 @@ Doorkeeper.configure do
# For more information go to # For more information go to
# https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes # https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes
default_scopes :read default_scopes :read
optional_scopes :write, optional_scopes :profile,
:write,
:'write:accounts', :'write:accounts',
:'write:blocks', :'write:blocks',
:'write:bookmarks', :'write:bookmarks',
@ -89,7 +90,6 @@ Doorkeeper.configure do
:'write:reports', :'write:reports',
:'write:statuses', :'write:statuses',
:read, :read,
:'read:me',
:'read:accounts', :'read:accounts',
:'read:blocks', :'read:blocks',
:'read:bookmarks', :'read:bookmarks',

View File

@ -6,5 +6,5 @@
# Use this to limit dissemination of sensitive information. # Use this to limit dissemination of sensitive information.
# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors.
Rails.application.config.filter_parameters += [ Rails.application.config.filter_parameters += [
:passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
] ]

View File

@ -3,7 +3,7 @@
ActiveSupport::Notifications.subscribe(/rack_attack/) do |_name, _start, _finish, _request_id, payload| ActiveSupport::Notifications.subscribe(/rack_attack/) do |_name, _start, _finish, _request_id, payload|
req = payload[:request] req = payload[:request]
next unless [:throttle, :blacklist].include? req.env['rack.attack.match_type'] next unless [:throttle, :blocklist].include? req.env['rack.attack.match_type']
Rails.logger.info("Rate limit hit (#{req.env['rack.attack.match_type']}): #{req.ip} #{req.request_method} #{req.fullpath}") Rails.logger.info("Rate limit hit (#{req.env['rack.attack.match_type']}): #{req.ip} #{req.request_method} #{req.fullpath}")
end end

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
if Rails.configuration.x.use_vips
ENV['VIPS_BLOCK_UNTRUSTED'] = 'true'
require 'vips'
abort('Incompatible libvips version, please install libvips >= 8.13') unless Vips.at_least_libvips?(8, 13)
Vips.block('VipsForeign', true)
%w(
VipsForeignLoadNsgif
VipsForeignLoadJpeg
VipsForeignLoadPng
VipsForeignLoadWebp
VipsForeignLoadHeif
VipsForeignSavePng
VipsForeignSaveSpng
VipsForeignSaveJpeg
VipsForeignSaveWebp
).each do |operation|
Vips.block(operation, false)
end
Vips.block_untrusted(true)
end

View File

@ -19,7 +19,7 @@ ia:
account: account:
attributes: attributes:
username: username:
invalid: debe continer solmente litteras, numeros e tractos de sublineamento invalid: debe continer solmente litteras, numeros e lineettas basse
reserved: es reservate reserved: es reservate
admin/webhook: admin/webhook:
attributes: attributes:

View File

@ -466,7 +466,7 @@ ca:
status: Estat status: Estat
suppress: Suprimeix les recomanacions de seguiment suppress: Suprimeix les recomanacions de seguiment
suppressed: Suprimit suppressed: Suprimit
title: Seguir les recomanacions title: Recomanacions de comptes a seguir
unsuppress: Restaurar les recomanacions de seguiment unsuppress: Restaurar les recomanacions de seguiment
instances: instances:
availability: availability:

View File

@ -1673,6 +1673,7 @@ da:
domain_block: Serversuspendering (%{target_name}) domain_block: Serversuspendering (%{target_name})
user_domain_block: "%{target_name} blev blokeret" user_domain_block: "%{target_name} blev blokeret"
lost_followers: Tabte følgere lost_followers: Tabte følgere
lost_follows: Mistet følger
preamble: Der kan mistes fulgte objekter og følgere, når et domæne blokeres eller moderatorerne beslutter at suspendere en ekstern server. Når det sker, kan der downloades lister over afbrudte relationer til inspektion og mulig import på anden server. preamble: Der kan mistes fulgte objekter og følgere, når et domæne blokeres eller moderatorerne beslutter at suspendere en ekstern server. Når det sker, kan der downloades lister over afbrudte relationer til inspektion og mulig import på anden server.
purged: Oplysninger om denne server er blevet renset af serveradministratoreren. purged: Oplysninger om denne server er blevet renset af serveradministratoreren.
type: Begivenhed type: Begivenhed

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