Merge pull request #2727 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changes up to 5652ca6135
This commit is contained in:
commit
b2e7af830b
|
@ -1,5 +1,5 @@
|
|||
# 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
|
||||
# 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.
|
||||
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.
|
||||
RUN gem install foreman
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
},
|
||||
|
||||
"onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}",
|
||||
"postCreateCommand": ".devcontainer/post-create.sh",
|
||||
"postCreateCommand": "bin/setup",
|
||||
"waitFor": "postCreateCommand",
|
||||
|
||||
"customizations": {
|
||||
|
|
|
@ -23,12 +23,14 @@
|
|||
}
|
||||
},
|
||||
|
||||
"remoteUser": "root",
|
||||
|
||||
"otherPortsAttributes": {
|
||||
"onAutoForward": "silent"
|
||||
},
|
||||
|
||||
"onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}",
|
||||
"postCreateCommand": ".devcontainer/post-create.sh",
|
||||
"postCreateCommand": "bin/setup",
|
||||
"waitFor": "postCreateCommand",
|
||||
|
||||
"customizations": {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
app:
|
||||
working_dir: /workspaces/mastodon/
|
||||
|
|
|
@ -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
|
|
@ -14,7 +14,7 @@ runs:
|
|||
shell: bash
|
||||
run: |
|
||||
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
|
||||
uses: ruby/setup-ruby@v1
|
||||
|
|
|
@ -133,7 +133,7 @@ jobs:
|
|||
uses: ./.github/actions/setup-ruby
|
||||
with:
|
||||
ruby-version: ${{ matrix.ruby-version}}
|
||||
additional-system-dependencies: ffmpeg imagemagick libpam-dev
|
||||
additional-system-dependencies: ffmpeg libpam-dev
|
||||
|
||||
- name: Load database schema
|
||||
run: './bin/rails db:create db:schema:load db:seed'
|
||||
|
@ -148,6 +148,93 @@ jobs:
|
|||
env:
|
||||
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:
|
||||
name: End to End testing
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -209,7 +296,7 @@ jobs:
|
|||
uses: ./.github/actions/setup-ruby
|
||||
with:
|
||||
ruby-version: ${{ matrix.ruby-version}}
|
||||
additional-system-dependencies: ffmpeg imagemagick
|
||||
additional-system-dependencies: ffmpeg
|
||||
|
||||
- name: Set up Javascript environment
|
||||
uses: ./.github/actions/setup-javascript
|
||||
|
@ -329,7 +416,7 @@ jobs:
|
|||
uses: ./.github/actions/setup-ruby
|
||||
with:
|
||||
ruby-version: ${{ matrix.ruby-version}}
|
||||
additional-system-dependencies: ffmpeg imagemagick
|
||||
additional-system-dependencies: ffmpeg
|
||||
|
||||
- name: Set up Javascript environment
|
||||
uses: ./.github/actions/setup-javascript
|
||||
|
|
19
.nanoignore
19
.nanoignore
|
@ -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/
|
|
@ -1 +1 @@
|
|||
3.3.1
|
||||
3.3.2
|
||||
|
|
15
Dockerfile
15
Dockerfile
|
@ -1,5 +1,8 @@
|
|||
# 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
|
||||
# the extended buildx capabilities used in this file.
|
||||
# Make sure multiarch TARGETPLATFORM is available for interpolation
|
||||
|
@ -7,22 +10,22 @@
|
|||
ARG TARGETPLATFORM=${TARGETPLATFORM}
|
||||
ARG BUILDPLATFORM=${BUILDPLATFORM}
|
||||
|
||||
# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.3.1"]
|
||||
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.2"
|
||||
# # Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"]
|
||||
ARG NODE_MAJOR_VERSION="20"
|
||||
# Debian image to use for base image, change with [--build-arg DEBIAN_VERSION="bookworm"]
|
||||
ARG DEBIAN_VERSION="bookworm"
|
||||
# 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
|
||||
# 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
|
||||
|
||||
# Resulting version string is vX.X.X-MASTODON_VERSION_PRERELEASE+MASTODON_VERSION_METADATA
|
||||
# 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"]
|
||||
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=""
|
||||
|
||||
# Allow Ruby on Rails to serve static files
|
||||
|
@ -43,6 +46,8 @@ ENV \
|
|||
# Apply Mastodon version information
|
||||
MASTODON_VERSION_PRERELEASE="${MASTODON_VERSION_PRERELEASE}" \
|
||||
MASTODON_VERSION_METADATA="${MASTODON_VERSION_METADATA}" \
|
||||
# Enable libvips
|
||||
MASTODON_USE_LIBVIPS=true \
|
||||
# Apply Mastodon static files and YJIT options
|
||||
RAILS_SERVE_STATIC_FILES=${RAILS_SERVE_STATIC_FILES} \
|
||||
RUBY_YJIT_ENABLE=${RUBY_YJIT_ENABLE} \
|
||||
|
@ -97,7 +102,7 @@ RUN \
|
|||
curl \
|
||||
ffmpeg \
|
||||
file \
|
||||
imagemagick \
|
||||
libvips42 \
|
||||
libjemalloc2 \
|
||||
patchelf \
|
||||
procps \
|
||||
|
|
1
Gemfile
1
Gemfile
|
@ -23,6 +23,7 @@ gem 'fog-core', '<= 2.4.0'
|
|||
gem 'fog-openstack', '~> 1.0', require: false
|
||||
gem 'kt-paperclip', '~> 7.2'
|
||||
gem 'md-paperclip-azure', '~> 2.2', require: false
|
||||
gem 'ruby-vips', '~> 2.2', require: false
|
||||
|
||||
gem 'active_model_serializers', '~> 0.10'
|
||||
gem 'addressable', '~> 2.8'
|
||||
|
|
133
Gemfile.lock
133
Gemfile.lock
|
@ -10,35 +10,35 @@ GIT
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (7.1.3.3)
|
||||
actionpack (= 7.1.3.3)
|
||||
activesupport (= 7.1.3.3)
|
||||
actioncable (7.1.3.4)
|
||||
actionpack (= 7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
zeitwerk (~> 2.6)
|
||||
actionmailbox (7.1.3.3)
|
||||
actionpack (= 7.1.3.3)
|
||||
activejob (= 7.1.3.3)
|
||||
activerecord (= 7.1.3.3)
|
||||
activestorage (= 7.1.3.3)
|
||||
activesupport (= 7.1.3.3)
|
||||
actionmailbox (7.1.3.4)
|
||||
actionpack (= 7.1.3.4)
|
||||
activejob (= 7.1.3.4)
|
||||
activerecord (= 7.1.3.4)
|
||||
activestorage (= 7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
mail (>= 2.7.1)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
actionmailer (7.1.3.3)
|
||||
actionpack (= 7.1.3.3)
|
||||
actionview (= 7.1.3.3)
|
||||
activejob (= 7.1.3.3)
|
||||
activesupport (= 7.1.3.3)
|
||||
actionmailer (7.1.3.4)
|
||||
actionpack (= 7.1.3.4)
|
||||
actionview (= 7.1.3.4)
|
||||
activejob (= 7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
rails-dom-testing (~> 2.2)
|
||||
actionpack (7.1.3.3)
|
||||
actionview (= 7.1.3.3)
|
||||
activesupport (= 7.1.3.3)
|
||||
actionpack (7.1.3.4)
|
||||
actionview (= 7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
nokogiri (>= 1.8.5)
|
||||
racc
|
||||
rack (>= 2.2.4)
|
||||
|
@ -46,15 +46,15 @@ GEM
|
|||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.2)
|
||||
rails-html-sanitizer (~> 1.6)
|
||||
actiontext (7.1.3.3)
|
||||
actionpack (= 7.1.3.3)
|
||||
activerecord (= 7.1.3.3)
|
||||
activestorage (= 7.1.3.3)
|
||||
activesupport (= 7.1.3.3)
|
||||
actiontext (7.1.3.4)
|
||||
actionpack (= 7.1.3.4)
|
||||
activerecord (= 7.1.3.4)
|
||||
activestorage (= 7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (7.1.3.3)
|
||||
activesupport (= 7.1.3.3)
|
||||
actionview (7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.11)
|
||||
rails-dom-testing (~> 2.2)
|
||||
|
@ -64,22 +64,22 @@ GEM
|
|||
activemodel (>= 4.1)
|
||||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||
activejob (7.1.3.3)
|
||||
activesupport (= 7.1.3.3)
|
||||
activejob (7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (7.1.3.3)
|
||||
activesupport (= 7.1.3.3)
|
||||
activerecord (7.1.3.3)
|
||||
activemodel (= 7.1.3.3)
|
||||
activesupport (= 7.1.3.3)
|
||||
activemodel (7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
activerecord (7.1.3.4)
|
||||
activemodel (= 7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
timeout (>= 0.4.0)
|
||||
activestorage (7.1.3.3)
|
||||
actionpack (= 7.1.3.3)
|
||||
activejob (= 7.1.3.3)
|
||||
activerecord (= 7.1.3.3)
|
||||
activesupport (= 7.1.3.3)
|
||||
activestorage (7.1.3.4)
|
||||
actionpack (= 7.1.3.4)
|
||||
activejob (= 7.1.3.4)
|
||||
activerecord (= 7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
marcel (~> 1.0)
|
||||
activesupport (7.1.3.3)
|
||||
activesupport (7.1.3.4)
|
||||
base64
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
|
@ -168,7 +168,7 @@ GEM
|
|||
climate_control (1.2.0)
|
||||
cocoon (1.2.15)
|
||||
color_diff (0.1)
|
||||
concurrent-ruby (1.2.3)
|
||||
concurrent-ruby (1.3.1)
|
||||
connection_pool (2.4.1)
|
||||
cose (1.3.0)
|
||||
cbor (~> 0.5.9)
|
||||
|
@ -424,7 +424,7 @@ GEM
|
|||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2024.0507)
|
||||
mini_mime (1.1.5)
|
||||
mini_portile2 (2.8.6)
|
||||
mini_portile2 (2.8.7)
|
||||
minitest (5.23.1)
|
||||
msgpack (1.7.2)
|
||||
multi_json (1.15.0)
|
||||
|
@ -434,7 +434,7 @@ GEM
|
|||
uri
|
||||
net-http-persistent (4.0.2)
|
||||
connection_pool (~> 2.2)
|
||||
net-imap (0.4.11)
|
||||
net-imap (0.4.12)
|
||||
date
|
||||
net-protocol
|
||||
net-ldap (0.19.0)
|
||||
|
@ -579,14 +579,14 @@ GEM
|
|||
orm_adapter (0.5.0)
|
||||
ox (2.14.18)
|
||||
parallel (1.24.0)
|
||||
parser (3.3.1.0)
|
||||
parser (3.3.2.0)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
parslet (2.0.0)
|
||||
pastel (0.8.0)
|
||||
tty-color (~> 0.5)
|
||||
pg (1.5.6)
|
||||
pghero (3.4.1)
|
||||
pghero (3.5.0)
|
||||
activerecord (>= 6)
|
||||
premailer (1.23.0)
|
||||
addressable
|
||||
|
@ -597,7 +597,7 @@ GEM
|
|||
net-smtp
|
||||
premailer (~> 1.7, >= 1.7.9)
|
||||
private_address_check (0.5.0)
|
||||
propshaft (0.8.0)
|
||||
propshaft (0.9.0)
|
||||
actionpack (>= 7.0.0)
|
||||
activesupport (>= 7.0.0)
|
||||
rack
|
||||
|
@ -634,20 +634,20 @@ GEM
|
|||
rackup (1.0.0)
|
||||
rack (< 3)
|
||||
webrick
|
||||
rails (7.1.3.3)
|
||||
actioncable (= 7.1.3.3)
|
||||
actionmailbox (= 7.1.3.3)
|
||||
actionmailer (= 7.1.3.3)
|
||||
actionpack (= 7.1.3.3)
|
||||
actiontext (= 7.1.3.3)
|
||||
actionview (= 7.1.3.3)
|
||||
activejob (= 7.1.3.3)
|
||||
activemodel (= 7.1.3.3)
|
||||
activerecord (= 7.1.3.3)
|
||||
activestorage (= 7.1.3.3)
|
||||
activesupport (= 7.1.3.3)
|
||||
rails (7.1.3.4)
|
||||
actioncable (= 7.1.3.4)
|
||||
actionmailbox (= 7.1.3.4)
|
||||
actionmailer (= 7.1.3.4)
|
||||
actionpack (= 7.1.3.4)
|
||||
actiontext (= 7.1.3.4)
|
||||
actionview (= 7.1.3.4)
|
||||
activejob (= 7.1.3.4)
|
||||
activemodel (= 7.1.3.4)
|
||||
activerecord (= 7.1.3.4)
|
||||
activestorage (= 7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 7.1.3.3)
|
||||
railties (= 7.1.3.4)
|
||||
rails-controller-testing (1.0.5)
|
||||
actionpack (>= 5.0.1.rc1)
|
||||
actionview (>= 5.0.1.rc1)
|
||||
|
@ -662,9 +662,9 @@ GEM
|
|||
rails-i18n (7.0.9)
|
||||
i18n (>= 0.7, < 2)
|
||||
railties (>= 6.0.0, < 8)
|
||||
railties (7.1.3.3)
|
||||
actionpack (= 7.1.3.3)
|
||||
activesupport (= 7.1.3.3)
|
||||
railties (7.1.3.4)
|
||||
actionpack (= 7.1.3.4)
|
||||
activesupport (= 7.1.3.4)
|
||||
irb
|
||||
rackup (>= 1.0.0)
|
||||
rake (>= 12.2)
|
||||
|
@ -686,7 +686,7 @@ GEM
|
|||
redlock (1.3.2)
|
||||
redis (>= 3.0.0, < 6.0)
|
||||
regexp_parser (2.9.2)
|
||||
reline (0.5.7)
|
||||
reline (0.5.8)
|
||||
io-console (~> 0.5)
|
||||
request_store (1.6.0)
|
||||
rack (>= 1.4)
|
||||
|
@ -726,7 +726,7 @@ GEM
|
|||
rspec-mocks (~> 3.0)
|
||||
sidekiq (>= 5, < 8)
|
||||
rspec-support (3.13.1)
|
||||
rubocop (1.64.0)
|
||||
rubocop (1.64.1)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (>= 3.17.0)
|
||||
parallel (~> 1.10)
|
||||
|
@ -751,7 +751,7 @@ GEM
|
|||
rack (>= 1.1)
|
||||
rubocop (>= 1.33.0, < 2.0)
|
||||
rubocop-ast (>= 1.31.1, < 2.0)
|
||||
rubocop-rspec (2.29.2)
|
||||
rubocop-rspec (2.30.0)
|
||||
rubocop (~> 1.40)
|
||||
rubocop-capybara (~> 2.17)
|
||||
rubocop-factory_bot (~> 2.22)
|
||||
|
@ -763,6 +763,8 @@ GEM
|
|||
ruby-saml (1.16.0)
|
||||
nokogiri (>= 1.13.10)
|
||||
rexml
|
||||
ruby-vips (2.2.1)
|
||||
ffi (~> 1.12)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
rufus-scheduler (3.9.1)
|
||||
|
@ -895,7 +897,7 @@ GEM
|
|||
xorcist (1.1.3)
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
zeitwerk (2.6.14)
|
||||
zeitwerk (2.6.15)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
@ -1023,6 +1025,7 @@ DEPENDENCIES
|
|||
rubocop-rspec
|
||||
ruby-prof
|
||||
ruby-progressbar (~> 1.13)
|
||||
ruby-vips (~> 2.2)
|
||||
rubyzip (~> 2.3)
|
||||
sanitize (~> 6.0)
|
||||
scenic (~> 1.7)
|
||||
|
@ -1050,7 +1053,7 @@ DEPENDENCIES
|
|||
xorcist (~> 1.1)
|
||||
|
||||
RUBY VERSION
|
||||
ruby 3.3.1p55
|
||||
ruby 3.3.2p78
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.9
|
||||
2.5.11
|
||||
|
|
|
@ -88,7 +88,7 @@ Mastodon acts as an OAuth2 provider, so 3rd party apps can use the REST and Stre
|
|||
- **Ruby** 3.1+
|
||||
- **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
|
||||
|
||||
|
@ -117,11 +117,13 @@ To set up **MacOS** for native development, complete the following steps:
|
|||
|
||||
### 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
|
||||
- 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`
|
||||
|
||||
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).
|
||||
|
|
|
@ -151,6 +151,12 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
|||
vb.customize ["modifyvm", :id, "--nictype2", "virtio"]
|
||||
end
|
||||
|
||||
config.vm.provider :libvirt do |libvirt|
|
||||
libvirt.cpus = 3
|
||||
libvirt.memory = 8192
|
||||
end
|
||||
|
||||
|
||||
# This uses the vagrant-hostsupdater plugin, and lets you
|
||||
# access the development site at http://mastodon.local.
|
||||
# If you change it, also change it in .env.vagrant before provisioning
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
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 :require_user!
|
||||
|
||||
|
|
|
@ -60,8 +60,4 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
|
|||
def records_continue?
|
||||
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit).permit(:limit).merge(core_params)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -60,8 +60,4 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
|
|||
def records_continue?
|
||||
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit).permit(:limit).merge(core_params)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,8 +16,6 @@ class Api::V1::Admin::CanonicalEmailBlocksController < Api::BaseController
|
|||
after_action :verify_authorized
|
||||
after_action :insert_pagination_headers, only: :index
|
||||
|
||||
PAGINATION_PARAMS = %i(limit).freeze
|
||||
|
||||
def index
|
||||
authorize :canonical_email_block, :index?
|
||||
render json: @canonical_email_blocks, each_serializer: REST::Admin::CanonicalEmailBlockSerializer
|
||||
|
@ -80,8 +78,4 @@ class Api::V1::Admin::CanonicalEmailBlocksController < Api::BaseController
|
|||
def records_continue?
|
||||
@canonical_email_blocks.size == limit_param(LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,8 +14,6 @@ class Api::V1::Admin::DomainAllowsController < Api::BaseController
|
|||
after_action :verify_authorized
|
||||
after_action :insert_pagination_headers, only: :index
|
||||
|
||||
PAGINATION_PARAMS = %i(limit).freeze
|
||||
|
||||
def index
|
||||
authorize :domain_allow, :index?
|
||||
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)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.permit(:domain)
|
||||
end
|
||||
|
|
|
@ -14,8 +14,6 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController
|
|||
after_action :verify_authorized
|
||||
after_action :insert_pagination_headers, only: :index
|
||||
|
||||
PAGINATION_PARAMS = %i(limit).freeze
|
||||
|
||||
def index
|
||||
authorize :domain_block, :index?
|
||||
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)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.permit(:domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate)
|
||||
end
|
||||
|
|
|
@ -14,10 +14,6 @@ class Api::V1::Admin::EmailDomainBlocksController < Api::BaseController
|
|||
after_action :verify_authorized
|
||||
after_action :insert_pagination_headers, only: :index
|
||||
|
||||
PAGINATION_PARAMS = %i(
|
||||
limit
|
||||
).freeze
|
||||
|
||||
def index
|
||||
authorize :email_domain_block, :index?
|
||||
render json: @email_domain_blocks, each_serializer: REST::Admin::EmailDomainBlockSerializer
|
||||
|
@ -73,8 +69,4 @@ class Api::V1::Admin::EmailDomainBlocksController < Api::BaseController
|
|||
def records_continue?
|
||||
@email_domain_blocks.size == limit_param(LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,10 +14,6 @@ class Api::V1::Admin::IpBlocksController < Api::BaseController
|
|||
after_action :verify_authorized
|
||||
after_action :insert_pagination_headers, only: :index
|
||||
|
||||
PAGINATION_PARAMS = %i(
|
||||
limit
|
||||
).freeze
|
||||
|
||||
def index
|
||||
authorize :ip_block, :index?
|
||||
render json: @ip_blocks, each_serializer: REST::Admin::IpBlockSerializer
|
||||
|
@ -78,8 +74,4 @@ class Api::V1::Admin::IpBlocksController < Api::BaseController
|
|||
def records_continue?
|
||||
@ip_blocks.size == limit_param(LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,7 +12,6 @@ class Api::V1::Admin::TagsController < Api::BaseController
|
|||
after_action :verify_authorized
|
||||
|
||||
LIMIT = 100
|
||||
PAGINATION_PARAMS = %i(limit).freeze
|
||||
|
||||
def index
|
||||
authorize :tag, :index?
|
||||
|
@ -59,8 +58,4 @@ class Api::V1::Admin::TagsController < Api::BaseController
|
|||
def records_continue?
|
||||
@tags.size == limit_param(LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,8 +12,6 @@ class Api::V1::Admin::Trends::Links::PreviewCardProvidersController < Api::BaseC
|
|||
after_action :verify_authorized
|
||||
after_action :insert_pagination_headers, only: :index
|
||||
|
||||
PAGINATION_PARAMS = %i(limit).freeze
|
||||
|
||||
def index
|
||||
authorize :preview_card_provider, :index?
|
||||
|
||||
|
@ -57,8 +55,4 @@ class Api::V1::Admin::Trends::Links::PreviewCardProvidersController < Api::BaseC
|
|||
def records_continue?
|
||||
@providers.size == limit_param(LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -43,8 +43,4 @@ class Api::V1::BlocksController < Api::BaseController
|
|||
def records_continue?
|
||||
paginated_blocks.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit).permit(:limit).merge(core_params)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -46,8 +46,4 @@ class Api::V1::BookmarksController < Api::BaseController
|
|||
def records_continue?
|
||||
results.size == limit_param(DEFAULT_STATUSES_LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit).permit(:limit).merge(core_params)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -72,8 +72,4 @@ class Api::V1::ConversationsController < Api::BaseController
|
|||
def records_continue?
|
||||
@conversations.size == limit_param(LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit).permit(:limit).merge(core_params)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -44,8 +44,4 @@ class Api::V1::Crypto::EncryptedMessagesController < Api::BaseController
|
|||
def records_continue?
|
||||
@encrypted_messages.size == limit_param(LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit).permit(:limit).merge(core_params)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -54,10 +54,6 @@ class Api::V1::DomainBlocksController < Api::BaseController
|
|||
@blocks.size == limit_param(BLOCK_LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit).permit(:limit).merge(core_params)
|
||||
end
|
||||
|
||||
def domain_block_params
|
||||
params.permit(:domain)
|
||||
end
|
||||
|
|
|
@ -48,10 +48,6 @@ class Api::V1::EndorsementsController < Api::BaseController
|
|||
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit).permit(:limit).merge(core_params)
|
||||
end
|
||||
|
||||
def unlimited?
|
||||
params[:limit] == '0'
|
||||
end
|
||||
|
|
|
@ -46,8 +46,4 @@ class Api::V1::FavouritesController < Api::BaseController
|
|||
def records_continue?
|
||||
results.size == limit_param(DEFAULT_STATUSES_LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit).permit(:limit).merge(core_params)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -67,8 +67,4 @@ class Api::V1::FollowRequestsController < Api::BaseController
|
|||
def records_continue?
|
||||
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit).permit(:limit).merge(core_params)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -37,8 +37,4 @@ class Api::V1::FollowedTagsController < Api::BaseController
|
|||
def records_continue?
|
||||
@results.size == limit_param(TAGS_LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit).permit(:limit).merge(core_params)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@ class Api::V1::Instances::ExtendedDescriptionsController < Api::V1::Instances::B
|
|||
|
||||
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
|
||||
super if limited_federation_mode?
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@ class Api::V1::Instances::PeersController < Api::V1::Instances::BaseController
|
|||
|
||||
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
|
||||
super if limited_federation_mode?
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@ class Api::V1::Instances::RulesController < Api::V1::Instances::BaseController
|
|||
|
||||
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
|
||||
super if limited_federation_mode?
|
||||
end
|
||||
|
|
|
@ -6,7 +6,7 @@ class Api::V1::InstancesController < Api::BaseController
|
|||
|
||||
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
|
||||
super if limited_federation_mode?
|
||||
end
|
||||
|
|
|
@ -75,10 +75,6 @@ class Api::V1::Lists::AccountsController < Api::BaseController
|
|||
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit).permit(:limit).merge(core_params)
|
||||
end
|
||||
|
||||
def unlimited?
|
||||
params[:limit] == '0'
|
||||
end
|
||||
|
|
|
@ -43,8 +43,4 @@ class Api::V1::MutesController < Api::BaseController
|
|||
def records_continue?
|
||||
paginated_mutes.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit).permit(:limit).merge(core_params)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -43,10 +43,6 @@ class Api::V1::ScheduledStatusesController < Api::BaseController
|
|||
params.permit(:scheduled_at)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit).permit(:limit).merge(core_params)
|
||||
end
|
||||
|
||||
def next_path
|
||||
api_v1_scheduled_statuses_url pagination_params(max_id: pagination_max_id) if records_continue?
|
||||
end
|
||||
|
|
|
@ -53,8 +53,4 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::V1::Statuses::Bas
|
|||
def records_continue?
|
||||
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit).permit(:limit).merge(core_params)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -49,8 +49,4 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::V1::Statuses::Base
|
|||
def records_continue?
|
||||
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit).permit(:limit).merge(core_params)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -191,8 +191,4 @@ class Api::V1::StatusesController < Api::BaseController
|
|||
def serialized_accounts(accounts)
|
||||
ActiveModel::Serializer::CollectionSerializer.new(accounts, serializer: REST::AccountSerializer)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit).permit(:limit).merge(core_params)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -34,10 +34,6 @@ class Api::V1::Trends::LinksController < Api::BaseController
|
|||
scope
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit).permit(:limit).merge(core_params)
|
||||
end
|
||||
|
||||
def next_path
|
||||
api_v1_trends_links_url pagination_params(offset: offset_param + limit_param(DEFAULT_LINKS_LIMIT)) if records_continue?
|
||||
end
|
||||
|
|
|
@ -32,10 +32,6 @@ class Api::V1::Trends::StatusesController < Api::BaseController
|
|||
scope
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit).permit(:limit).merge(core_params)
|
||||
end
|
||||
|
||||
def next_path
|
||||
api_v1_trends_statuses_url pagination_params(offset: offset_param + limit_param(DEFAULT_STATUSES_LIMIT)) if records_continue?
|
||||
end
|
||||
|
|
|
@ -30,10 +30,6 @@ class Api::V1::Trends::TagsController < Api::BaseController
|
|||
Trends.tags.query.allowed
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit).permit(:limit).merge(core_params)
|
||||
end
|
||||
|
||||
def next_path
|
||||
api_v1_trends_tags_url pagination_params(offset: offset_param + limit_param(DEFAULT_TAGS_LIMIT)) if records_continue?
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -3,6 +3,8 @@
|
|||
module Api::Pagination
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
PAGINATION_PARAMS = %i(limit).freeze
|
||||
|
||||
protected
|
||||
|
||||
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?
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params
|
||||
.slice(*PAGINATION_PARAMS)
|
||||
.permit(*PAGINATION_PARAMS)
|
||||
.merge(core_params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def insert_pagination_headers
|
||||
|
|
|
@ -13,7 +13,7 @@ class Settings::ApplicationsController < Settings::BaseController
|
|||
def new
|
||||
@application = Doorkeeper::Application.new(
|
||||
redirect_uri: Doorkeeper.configuration.native_redirect_uri,
|
||||
scopes: 'read:me'
|
||||
scopes: 'profile'
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -199,6 +199,7 @@ class AccountTimeline extends ImmutablePureComponent {
|
|||
emptyMessage={emptyMessage}
|
||||
bindToDocument={!multiColumn}
|
||||
timelineId='account'
|
||||
withCounters
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
|
|
|
@ -414,6 +414,7 @@
|
|||
"limited_account_hint.action": "Показване на профила въпреки това",
|
||||
"limited_account_hint.title": "Този профил е бил скрит от модераторите на {domain}.",
|
||||
"link_preview.author": "От {name}",
|
||||
"link_preview.more_from_author": "Още от {name}",
|
||||
"lists.account.add": "Добавяне към списък",
|
||||
"lists.account.remove": "Премахване от списъка",
|
||||
"lists.delete": "Изтриване на списъка",
|
||||
|
|
|
@ -221,6 +221,8 @@
|
|||
"domain_pill.activitypub_like_language": "ActivityPub er \"sproget\", Mastodon taler med andre sociale netværk.",
|
||||
"domain_pill.server": "Server",
|
||||
"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.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>.",
|
||||
|
@ -412,6 +414,7 @@
|
|||
"limited_account_hint.action": "Vis profil alligevel",
|
||||
"limited_account_hint.title": "Denne profil er blevet skjult af {domain}-moderatorerne.",
|
||||
"link_preview.author": "Af {name}",
|
||||
"link_preview.more_from_author": "Mere fra {name}",
|
||||
"lists.account.add": "Føj til liste",
|
||||
"lists.account.remove": "Fjern fra liste",
|
||||
"lists.delete": "Slet liste",
|
||||
|
|
|
@ -414,6 +414,7 @@
|
|||
"limited_account_hint.action": "Profil trotzdem anzeigen",
|
||||
"limited_account_hint.title": "Dieses Profil wurde von den Moderator*innen von {domain} ausgeblendet.",
|
||||
"link_preview.author": "Von {name}",
|
||||
"link_preview.more_from_author": "Mehr von {name}",
|
||||
"lists.account.add": "Zur Liste hinzufügen",
|
||||
"lists.account.remove": "Von der Liste entfernen",
|
||||
"lists.delete": "Liste löschen",
|
||||
|
|
|
@ -414,6 +414,7 @@
|
|||
"limited_account_hint.action": "Mostrar perfil de todos modos",
|
||||
"limited_account_hint.title": "Este perfil ha sido ocultado por los moderadores de {domain}.",
|
||||
"link_preview.author": "Por {name}",
|
||||
"link_preview.more_from_author": "Más de {name}",
|
||||
"lists.account.add": "Añadir a lista",
|
||||
"lists.account.remove": "Quitar de lista",
|
||||
"lists.delete": "Borrar lista",
|
||||
|
|
|
@ -414,6 +414,7 @@
|
|||
"limited_account_hint.action": "Mostrar perfil de todos modos",
|
||||
"limited_account_hint.title": "Este perfil ha sido ocultado por los moderadores de {domain}.",
|
||||
"link_preview.author": "Por {name}",
|
||||
"link_preview.more_from_author": "Más de {name}",
|
||||
"lists.account.add": "Añadir a lista",
|
||||
"lists.account.remove": "Quitar de lista",
|
||||
"lists.delete": "Borrar lista",
|
||||
|
|
|
@ -414,6 +414,7 @@
|
|||
"limited_account_hint.action": "Vís vangamynd kortini",
|
||||
"limited_account_hint.title": "Hesin vangin er fjaldur av kjakleiðarunum á {domain}.",
|
||||
"link_preview.author": "Av {name}",
|
||||
"link_preview.more_from_author": "Meira frá {name}",
|
||||
"lists.account.add": "Legg afturat lista",
|
||||
"lists.account.remove": "Tak av lista",
|
||||
"lists.delete": "Strika lista",
|
||||
|
|
|
@ -89,9 +89,12 @@
|
|||
"announcement.announcement": "Oankundiging",
|
||||
"attachments_list.unprocessed": "(net ferwurke)",
|
||||
"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_more": "Mear toane",
|
||||
"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.you_wont_see_mentions": "Jo sjogge gjin berjochten mear dy’t dizze account fermelde.",
|
||||
"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.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_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 dy’t Mastodon mei oare sosjale netwurken sprekt.",
|
||||
"domain_pill.server": "Server",
|
||||
"domain_pill.their_handle": "Harren fediverse-adres:",
|
||||
"domain_pill.their_server": "Harren digitale thús, wer’t 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.whats_in_a_handle": "Wat is in fediverse-adres?",
|
||||
"domain_pill.who_they_are": "Omdat jo oan in fediverse-adres sjen kinne hoe’t 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:",
|
||||
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||
"embed.preview": "Sa komt it der út te sjen:",
|
||||
|
|
|
@ -414,6 +414,7 @@
|
|||
"limited_account_hint.action": "הצג חשבון בכל זאת",
|
||||
"limited_account_hint.title": "פרופיל המשתמש הזה הוסתר על ידי המנחים של {domain}.",
|
||||
"link_preview.author": "מאת {name}",
|
||||
"link_preview.more_from_author": "עוד מאת {name}",
|
||||
"lists.account.add": "הוסף לרשימה",
|
||||
"lists.account.remove": "הסר מרשימה",
|
||||
"lists.delete": "מחיקת רשימה",
|
||||
|
|
|
@ -414,6 +414,7 @@
|
|||
"limited_account_hint.action": "Profil megjelenítése mindenképpen",
|
||||
"limited_account_hint.title": "Ezt a profilt {domain} moderátorai elrejtették.",
|
||||
"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.remove": "Eltávolítás a listából",
|
||||
"lists.delete": "Lista törlése",
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
"account.open_original_page": "Aperir le pagina original",
|
||||
"account.posts": "Messages",
|
||||
"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_follow": "{name} ha requestate de sequer te",
|
||||
"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_wont_know": "Ille non sapera que ille ha essite blocate.",
|
||||
"domain_block_modal.title": "Blocar dominio?",
|
||||
"domain_block_modal.you_will_lose_followers": "Omne sequitores ab 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_will_lose_followers": "Tote tu sequitores de iste servitor essera removite.",
|
||||
"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_like_language": "ActivityPub es como le linguage commun que Mastodon parla con altere retes social.",
|
||||
"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_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.report_issue": "Signalar un defecto",
|
||||
"errors.unexpected_crash.report_issue": "Reportar problema",
|
||||
"explore.search_results": "Resultatos de recerca",
|
||||
"explore.suggested_follows": "Personas",
|
||||
"explore.title": "Explorar",
|
||||
|
@ -389,7 +389,7 @@
|
|||
"keyboard_shortcuts.hotkey": "Clave accelerator",
|
||||
"keyboard_shortcuts.legend": "Monstrar iste legenda",
|
||||
"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.my_profile": "Aperir tu profilo",
|
||||
"keyboard_shortcuts.notifications": "Aperir columna de notificationes",
|
||||
|
@ -414,6 +414,7 @@
|
|||
"limited_account_hint.action": "Monstrar profilo in omne caso",
|
||||
"limited_account_hint.title": "Iste profilo ha essite celate per le moderatores de {domain}.",
|
||||
"link_preview.author": "Per {name}",
|
||||
"link_preview.more_from_author": "Plus de {name}",
|
||||
"lists.account.add": "Adder al lista",
|
||||
"lists.account.remove": "Remover del lista",
|
||||
"lists.delete": "Deler lista",
|
||||
|
@ -467,14 +468,14 @@
|
|||
"navigation_bar.search": "Cercar",
|
||||
"navigation_bar.security": "Securitate",
|
||||
"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.favourite": "{name} ha marcate tu message como favorite",
|
||||
"notification.follow": "{name} te ha sequite",
|
||||
"notification.follow_request": "{name} ha requestate de sequer te",
|
||||
"notification.mention": "{name} te ha mentionate",
|
||||
"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_disable": "Tu conto ha essite disactivate.",
|
||||
"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.update": "{name} ha modificate un message",
|
||||
"notification_requests.accept": "Acceptar",
|
||||
"notification_requests.dismiss": "Dimitter",
|
||||
"notification_requests.dismiss": "Clauder",
|
||||
"notification_requests.notifications_from": "Notificationes de {name}",
|
||||
"notification_requests.title": "Notificationes filtrate",
|
||||
"notifications.clear": "Rader 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.alert": "Notificationes de scriptorio",
|
||||
"notifications.column_settings.favourite": "Favorites:",
|
||||
|
@ -621,7 +622,7 @@
|
|||
"relative_time.today": "hodie",
|
||||
"reply_indicator.attachments": "{count, plural, one {# annexo} other {# annexos}}",
|
||||
"reply_indicator.cancel": "Cancellar",
|
||||
"reply_indicator.poll": "Inquesta",
|
||||
"reply_indicator.poll": "Sondage",
|
||||
"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.categories.legal": "Juridic",
|
||||
|
@ -635,7 +636,7 @@
|
|||
"report.close": "Facite",
|
||||
"report.comment.title": "Ha il altere cosas que nos deberea saper?",
|
||||
"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_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",
|
||||
|
@ -655,11 +656,11 @@
|
|||
"report.statuses.subtitle": "Selige tote le responsas appropriate",
|
||||
"report.statuses.title": "Existe alcun messages que appoia iste reporto?",
|
||||
"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_actionable": "Durante que nos revide isto, tu pote prender mesuras contra @{name}:",
|
||||
"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_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",
|
||||
|
@ -677,7 +678,7 @@
|
|||
"search.quick_action.status_search": "Messages correspondente a {x}",
|
||||
"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_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.options": "Optiones de recerca",
|
||||
"search_popout.quick_actions": "Actiones rapide",
|
||||
|
@ -746,7 +747,7 @@
|
|||
"status.replied_to": "Respondite a {name}",
|
||||
"status.reply": "Responder",
|
||||
"status.replyAll": "Responder al discussion",
|
||||
"status.report": "Signalar @{name}",
|
||||
"status.report": "Reportar @{name}",
|
||||
"status.sensitive_warning": "Contento sensibile",
|
||||
"status.share": "Compartir",
|
||||
"status.show_filter_reason": "Monstrar in omne caso",
|
||||
|
@ -757,7 +758,7 @@
|
|||
"status.show_original": "Monstrar original",
|
||||
"status.title.with_attachments": "{user} ha publicate {attachmentCount, plural, one {un annexo} other {{attachmentCount} annexos}}",
|
||||
"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.unmute_conversation": "Non plus silentiar conversation",
|
||||
"status.unpin": "Disfixar del profilo",
|
||||
|
@ -796,7 +797,7 @@
|
|||
"upload_modal.choose_image": "Seliger un imagine",
|
||||
"upload_modal.description_placeholder": "Cinque expertos del zoo jam bibeva whisky frigide",
|
||||
"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.preparing_ocr": "Preparation del OCR…",
|
||||
"upload_modal.preview_label": "Previsualisation ({ratio})",
|
||||
|
|
|
@ -414,6 +414,7 @@
|
|||
"limited_account_hint.action": "Birta notandasniðið samt",
|
||||
"limited_account_hint.title": "Þetta notandasnið hefur verið falið af umsjónarmönnum {domain}.",
|
||||
"link_preview.author": "Eftir {name}",
|
||||
"link_preview.more_from_author": "Meira frá {name}",
|
||||
"lists.account.add": "Bæta á lista",
|
||||
"lists.account.remove": "Fjarlægja af lista",
|
||||
"lists.delete": "Eyða lista",
|
||||
|
|
|
@ -414,7 +414,7 @@
|
|||
"limited_account_hint.action": "그래도 프로필 보기",
|
||||
"limited_account_hint.title": "이 프로필은 {domain}의 중재자에 의해 숨겨진 상태입니다.",
|
||||
"link_preview.author": "{name}",
|
||||
"link_preview.more_from_author": "{name} 더 둘러보기",
|
||||
"link_preview.more_from_author": "{name} 프로필 보기",
|
||||
"lists.account.add": "리스트에 추가",
|
||||
"lists.account.remove": "리스트에서 제거",
|
||||
"lists.delete": "리스트 삭제",
|
||||
|
|
|
@ -398,6 +398,7 @@
|
|||
"limited_account_hint.action": "Amostra el profil entanto",
|
||||
"limited_account_hint.title": "Este profil fue eskondido por los moderadores de {domain}.",
|
||||
"link_preview.author": "Publikasyon de {name}",
|
||||
"link_preview.more_from_author": "Mas de {name}",
|
||||
"lists.account.add": "Adjusta a lista",
|
||||
"lists.account.remove": "Kita de lista",
|
||||
"lists.delete": "Efasa lista",
|
||||
|
|
|
@ -217,7 +217,7 @@
|
|||
"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_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.server": "Serveris",
|
||||
"domain_pill.their_handle": "Jų socialinis medijos vardas:",
|
||||
|
@ -414,6 +414,7 @@
|
|||
"limited_account_hint.action": "Vis tiek rodyti profilį",
|
||||
"limited_account_hint.title": "Šį profilį paslėpė {domain} prižiūrėtojai.",
|
||||
"link_preview.author": "Sukūrė {name}",
|
||||
"link_preview.more_from_author": "Daugiau iš {name}",
|
||||
"lists.account.add": "Pridėti į sąrašą",
|
||||
"lists.account.remove": "Pašalinti iš sąrašo",
|
||||
"lists.delete": "Ištrinti sąrašą",
|
||||
|
@ -432,7 +433,15 @@
|
|||
"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ų}}",
|
||||
"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.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.advanced_interface": "Atidaryti išplėstinę žiniatinklio sąsają",
|
||||
"navigation_bar.blocks": "Užblokuoti naudotojai",
|
||||
|
@ -477,6 +486,7 @@
|
|||
"notification.own_poll": "Tavo apklausa baigėsi",
|
||||
"notification.poll": "Apklausa, kurioje balsavai, pasibaigė",
|
||||
"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.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ė",
|
||||
|
@ -561,7 +571,7 @@
|
|||
"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.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.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ų.",
|
||||
|
@ -632,7 +642,7 @@
|
|||
"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_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.violation": "Tai pažeidžia serverio taisykles",
|
||||
"report.reasons.violation_description": "Žinai, kad tai pažeidžia konkrečias taisykles",
|
||||
|
|
|
@ -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.show_less": "Rā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",
|
||||
"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.",
|
||||
|
@ -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.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.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.message": "Vai tiešām vēlies izrakstīties?",
|
||||
"confirmations.mute.confirm": "Apklusināt",
|
||||
|
@ -377,6 +379,7 @@
|
|||
"limited_account_hint.action": "Tik un tā rādīt profilu",
|
||||
"limited_account_hint.title": "{domain} moderatori ir paslēpuši šo profilu.",
|
||||
"link_preview.author": "Pēc {name}",
|
||||
"link_preview.more_from_author": "Vairāk no {name}",
|
||||
"lists.account.add": "Pievienot sarakstam",
|
||||
"lists.account.remove": "Noņemt no saraksta",
|
||||
"lists.delete": "Izdzēst sarakstu",
|
||||
|
@ -444,16 +447,19 @@
|
|||
"notification.relationships_severance_event": "Zaudēti savienojumi ar {name}",
|
||||
"notification.relationships_severance_event.learn_more": "Uzzināt vairāk",
|
||||
"notification.status": "{name} tikko publicēja",
|
||||
"notification.update": "{name} rediģēja ierakstu",
|
||||
"notification.update": "{name} laboja ierakstu",
|
||||
"notification_requests.accept": "Pieņemt",
|
||||
"notification_requests.dismiss": "Noraidīt",
|
||||
"notification_requests.notifications_from": "Paziņojumi no {name}",
|
||||
"notification_requests.title": "Atlasītie paziņojumi",
|
||||
"notifications.clear": "Notīrīt paziņojumus",
|
||||
"notifications.clear_confirmation": "Vai tiešām vēlies neatgriezeniski notīrīt visus savus paziņojumus?",
|
||||
"notifications.column_settings.admin.report": "Jauni ziņojumi:",
|
||||
"notifications.column_settings.admin.sign_up": "Jaunas pierakstīšanās:",
|
||||
"notifications.column_settings.alert": "Darbvirsmas paziņojumi",
|
||||
"notifications.column_settings.favourite": "Izlase:",
|
||||
"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_request": "Jauni sekošanas pieprasījumi:",
|
||||
"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.policy.filter_new_accounts_title": "Jauni konti",
|
||||
"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.title": "Atlasīt paziņojumus no…",
|
||||
"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.title": "Nekad nepalaid neko garām",
|
||||
|
@ -539,7 +547,9 @@
|
|||
"privacy.direct.short": "Noteikti cilvēki",
|
||||
"privacy.private.long": "Tikai Tavi sekotāji",
|
||||
"privacy.private.short": "Sekotāji",
|
||||
"privacy.public.long": "Jebkurš Mastodon un ārpus tā",
|
||||
"privacy.public.short": "Publiska",
|
||||
"privacy.unlisted.long": "Mazāk algoritmisku fanfaru",
|
||||
"privacy_policy.last_updated": "Pēdējo reizi atjaunināta {date}",
|
||||
"privacy_policy.title": "Privātuma politika",
|
||||
"recommended": "Ieteicams",
|
||||
|
@ -557,6 +567,7 @@
|
|||
"relative_time.minutes": "{number}m",
|
||||
"relative_time.seconds": "{number}s",
|
||||
"relative_time.today": "šodien",
|
||||
"reply_indicator.attachments": "{count, plural, zero{# pielikumu} one {# pielikums} other {# pielikumi}}",
|
||||
"reply_indicator.cancel": "Atcelt",
|
||||
"reply_indicator.poll": "Aptauja",
|
||||
"report.block": "Bloķēt",
|
||||
|
@ -630,7 +641,7 @@
|
|||
"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.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.learn_more": "Uzzināt vairāk",
|
||||
"server_banner.server_stats": "Servera statistika:",
|
||||
|
@ -652,9 +663,10 @@
|
|||
"status.direct_indicator": "Pieminēts privāti",
|
||||
"status.edit": "Labot",
|
||||
"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.favourite": "Izlasē",
|
||||
"status.favourites": "{count, plural, zero {izlasēs} one {izlasē} other {izlasēs}}",
|
||||
"status.filter": "Filtrē šo ziņu",
|
||||
"status.filtered": "Filtrēts",
|
||||
"status.hide": "Slēpt ierakstu",
|
||||
|
@ -675,6 +687,7 @@
|
|||
"status.reblog": "Pastiprināt",
|
||||
"status.reblog_private": "Pastiprināt, nemainot redzamību",
|
||||
"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.redraft": "Dzēst un pārrakstīt",
|
||||
"status.remove_bookmark": "Noņemt grāmatzīmi",
|
||||
|
|
|
@ -17,9 +17,12 @@
|
|||
"account.badges.group": "गट",
|
||||
"account.block": "@{name} यांना ब्लॉक करा",
|
||||
"account.block_domain": "{domain} पासून सर्व लपवा",
|
||||
"account.block_short": "अवरोध",
|
||||
"account.blocked": "ब्लॉक केले आहे",
|
||||
"account.browse_more_on_origin_server": "मूळ प्रोफाइलवर अधिक ब्राउझ करा",
|
||||
"account.cancel_follow_request": "फॉलो विनंती मागे घ्या",
|
||||
"account.copy": "दुवा कॉपी करा",
|
||||
"account.direct": "खाजगीरित्या उल्लेखीत @{name}",
|
||||
"account.disable_notifications": "जेव्हा @{name} पोस्ट करतात तेव्हा मला सूचित करणे थांबवा",
|
||||
"account.domain_blocked": "Domain hidden",
|
||||
"account.edit_profile": "प्रोफाइल एडिट करा",
|
||||
|
@ -29,6 +32,7 @@
|
|||
"account.featured_tags.last_status_never": "पोस्ट नाहीत",
|
||||
"account.featured_tags.title": "{name} चे वैशिष्ट्यीकृत हॅशटॅग",
|
||||
"account.follow": "अनुयायी व्हा",
|
||||
"account.follow_back": "आपणही अनुसरण करा",
|
||||
"account.followers": "अनुयायी",
|
||||
"account.followers.empty": "ह्या वापरकर्त्याचा आतापर्यंत कोणी अनुयायी नाही.",
|
||||
"account.followers_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}",
|
||||
|
@ -45,6 +49,7 @@
|
|||
"account.mention": "@{name} चा उल्लेख करा",
|
||||
"account.moved_to": "{name} ने सूचित केले आहे की त्यांचे नवीन खाते आता आहे:",
|
||||
"account.mute": "@{name} ला मूक कारा",
|
||||
"account.mute_short": "नि:शब्द",
|
||||
"account.muted": "मौन",
|
||||
"account.open_original_page": "मूळ पृष्ठ उघडा",
|
||||
"account.posts": "Toots",
|
||||
|
|
|
@ -414,6 +414,7 @@
|
|||
"limited_account_hint.action": "Alsnog het profiel tonen",
|
||||
"limited_account_hint.title": "Dit profiel is door de moderatoren van {domain} verborgen.",
|
||||
"link_preview.author": "Door {name}",
|
||||
"link_preview.more_from_author": "Meer van {name}",
|
||||
"lists.account.add": "Aan lijst toevoegen",
|
||||
"lists.account.remove": "Uit lijst verwijderen",
|
||||
"lists.delete": "Lijst verwijderen",
|
||||
|
|
|
@ -391,6 +391,7 @@
|
|||
"limited_account_hint.action": "Aj tak zobraziť profil",
|
||||
"limited_account_hint.title": "Tento profil bol skrytý správcami servera {domain}.",
|
||||
"link_preview.author": "Autor: {name}",
|
||||
"link_preview.more_from_author": "Viac od {name}",
|
||||
"lists.account.add": "Pridať do zoznamu",
|
||||
"lists.account.remove": "Odstrániť zo zoznamu",
|
||||
"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}.",
|
||||
"mute_modal.hide_from_notifications": "Ukryť z upozornení",
|
||||
"mute_modal.hide_options": "Skryť možnosti",
|
||||
"mute_modal.indefinite": "Pokiaľ ich neodtíšim",
|
||||
"mute_modal.show_options": "Zobraziť možnosti",
|
||||
"mute_modal.title": "Stíšiť užívateľa?",
|
||||
"navigation_bar.about": "O tomto serveri",
|
||||
|
|
|
@ -414,6 +414,7 @@
|
|||
"limited_account_hint.action": "Shfaqe profilin sido qoftë",
|
||||
"limited_account_hint.title": "Ky profil është fshehur nga moderatorët e {domain}.",
|
||||
"link_preview.author": "Nga {name}",
|
||||
"link_preview.more_from_author": "Më tepër nga {name}",
|
||||
"lists.account.add": "Shto në listë",
|
||||
"lists.account.remove": "Hiqe nga lista",
|
||||
"lists.delete": "Fshije listën",
|
||||
|
|
|
@ -414,6 +414,7 @@
|
|||
"limited_account_hint.action": "Ipak prikaži profil",
|
||||
"limited_account_hint.title": "Ovaj profil su sakrili moderatori {domain}.",
|
||||
"link_preview.author": "Po {name}",
|
||||
"link_preview.more_from_author": "Više od {name}",
|
||||
"lists.account.add": "Dodaj na listu",
|
||||
"lists.account.remove": "Ukloni sa liste",
|
||||
"lists.delete": "Izbriši listu",
|
||||
|
|
|
@ -414,6 +414,7 @@
|
|||
"limited_account_hint.action": "Ипак прикажи профил",
|
||||
"limited_account_hint.title": "Овај профил су сакрили модератори {domain}.",
|
||||
"link_preview.author": "По {name}",
|
||||
"link_preview.more_from_author": "Више од {name}",
|
||||
"lists.account.add": "Додај на листу",
|
||||
"lists.account.remove": "Уклони са листе",
|
||||
"lists.delete": "Избриши листу",
|
||||
|
|
|
@ -414,6 +414,7 @@
|
|||
"limited_account_hint.action": "Visa profil ändå",
|
||||
"limited_account_hint.title": "Denna profil har dolts av {domain}s moderatorer.",
|
||||
"link_preview.author": "Av {name}",
|
||||
"link_preview.more_from_author": "Mer från {name}",
|
||||
"lists.account.add": "Lägg till i lista",
|
||||
"lists.account.remove": "Ta bort från lista",
|
||||
"lists.delete": "Radera lista",
|
||||
|
|
|
@ -414,6 +414,7 @@
|
|||
"limited_account_hint.action": "แสดงโปรไฟล์ต่อไป",
|
||||
"limited_account_hint.title": "มีการซ่อนโปรไฟล์นี้โดยผู้กลั่นกรองของ {domain}",
|
||||
"link_preview.author": "โดย {name}",
|
||||
"link_preview.more_from_author": "เพิ่มเติมจาก {name}",
|
||||
"lists.account.add": "เพิ่มไปยังรายการ",
|
||||
"lists.account.remove": "เอาออกจากรายการ",
|
||||
"lists.delete": "ลบรายการ",
|
||||
|
|
|
@ -414,6 +414,7 @@
|
|||
"limited_account_hint.action": "Yine de profili göster",
|
||||
"limited_account_hint.title": "Bu profil {domain} moderatörleri tarafından gizlendi.",
|
||||
"link_preview.author": "Yazar: {name}",
|
||||
"link_preview.more_from_author": "{name} kişisinden daha fazlası",
|
||||
"lists.account.add": "Listeye ekle",
|
||||
"lists.account.remove": "Listeden kaldır",
|
||||
"lists.delete": "Listeyi sil",
|
||||
|
|
|
@ -414,6 +414,7 @@
|
|||
"limited_account_hint.action": "Усе одно показати профіль",
|
||||
"limited_account_hint.title": "Цей профіль сховали модератори {domain}.",
|
||||
"link_preview.author": "Від {name}",
|
||||
"link_preview.more_from_author": "Більше від {name}",
|
||||
"lists.account.add": "Додати до списку",
|
||||
"lists.account.remove": "Вилучити зі списку",
|
||||
"lists.delete": "Видалити список",
|
||||
|
|
|
@ -10,7 +10,7 @@ class Admin::Metrics::Dimension::SoftwareVersionsDimension < Admin::Metrics::Dim
|
|||
protected
|
||||
|
||||
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
|
||||
|
||||
def mastodon_version
|
||||
|
@ -71,6 +71,17 @@ class Admin::Metrics::Dimension::SoftwareVersionsDimension < Admin::Metrics::Dim
|
|||
nil
|
||||
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
|
||||
@redis_info ||= if redis.is_a?(Redis::Namespace)
|
||||
redis.redis.info
|
||||
|
|
|
@ -11,6 +11,9 @@ class ScopeTransformer < Parslet::Transform
|
|||
@namespace = scope[:namespace]&.to_s
|
||||
@access = scope[:access] ? [scope[:access].to_s] : DEFAULT_ACCESS.dup
|
||||
@term = scope[:term]&.to_s || DEFAULT_TERM
|
||||
|
||||
# # override for profile scope which is read only
|
||||
@access = %w(read) if @term == 'profile'
|
||||
end
|
||||
|
||||
def key
|
||||
|
|
|
@ -69,7 +69,7 @@ module Attachmentable
|
|||
original_extension = Paperclip::Interpolations.extension(attachment, :original)
|
||||
proper_extension = extensions_for_mime_type.first.to_s
|
||||
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
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -13,6 +13,7 @@
|
|||
# from_account_id :bigint(8) not null
|
||||
# type :string
|
||||
# filtered :boolean default(FALSE), not null
|
||||
# group_key :string
|
||||
#
|
||||
|
||||
class Notification < ApplicationRecord
|
||||
|
@ -136,6 +137,67 @@ class Notification < ApplicationRecord
|
|||
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)
|
||||
notifications.group_by(&:type).each do |type, grouped_notifications|
|
||||
associations = TARGET_STATUS_INCLUDES_BY_TYPE[type]
|
||||
|
|
|
@ -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
|
|
@ -57,7 +57,11 @@ class PreviewCard < ApplicationRecord
|
|||
has_one :trend, class_name: 'PreviewCardTrend', inverse_of: :preview_card, dependent: :destroy
|
||||
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_attachment_content_type :image, content_type: IMAGE_MIME_TYPES
|
||||
|
|
|
@ -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
|
||||
|
||||
validates :endpoint, presence: true
|
||||
validates :endpoint, presence: true, url: true
|
||||
validates :key_p256dh, presence: true
|
||||
validates :key_auth, presence: true
|
||||
|
||||
validates_with WebPushKeyValidator
|
||||
|
||||
delegate :locale, to: :associated_user
|
||||
|
||||
def encrypt(payload)
|
||||
|
|
|
@ -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
|
|
@ -3,6 +3,9 @@
|
|||
class NotifyService < BaseService
|
||||
include Redisable
|
||||
|
||||
MAXIMUM_GROUP_SPAN_HOURS = 12
|
||||
MAXIMUM_GROUP_GAP_TIME = 4.hours.to_i
|
||||
|
||||
NON_EMAIL_TYPES = %i(
|
||||
admin.report
|
||||
admin.sign_up
|
||||
|
@ -183,6 +186,7 @@ class NotifyService < BaseService
|
|||
return if dismiss?
|
||||
|
||||
@notification.filtered = filter?
|
||||
@notification.group_key = notification_group_key
|
||||
@notification.save!
|
||||
|
||||
# It's possible the underlying activity has been deleted
|
||||
|
@ -202,6 +206,24 @@ class NotifyService < BaseService
|
|||
|
||||
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?
|
||||
DismissCondition.new(@notification).dismiss?
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
- if self_destruct?
|
||||
.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
|
||||
= render partial: 'status', locals: { user: @user, strikes: @strikes }
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
.simple_form
|
||||
%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
|
||||
%ul.no-list
|
||||
|
|
|
@ -44,6 +44,6 @@
|
|||
%body{ class: body_classes }
|
||||
= 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-wordmark.svg'
|
||||
|
|
|
@ -21,5 +21,5 @@
|
|||
%body.embed
|
||||
= 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'
|
||||
|
|
|
@ -27,7 +27,7 @@ require_relative '../lib/sanitize_ext/sanitize_config'
|
|||
require_relative '../lib/redis/namespace_extensions'
|
||||
require_relative '../lib/paperclip/url_generator_extensions'
|
||||
require_relative '../lib/paperclip/attachment_extensions'
|
||||
require_relative '../lib/paperclip/lazy_thumbnail'
|
||||
|
||||
require_relative '../lib/paperclip/gif_transcoder'
|
||||
require_relative '../lib/paperclip/media_type_spoof_detector_extensions'
|
||||
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/active_record/database_tasks_extensions'
|
||||
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'
|
||||
|
||||
Bundler.require(:pam_authentication) if ENV['PAM_ENABLED'] == 'true'
|
||||
|
@ -98,6 +100,14 @@ module Mastodon
|
|||
|
||||
config.before_configuration do
|
||||
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
|
||||
|
||||
config.to_prepare do
|
||||
|
|
|
@ -74,7 +74,8 @@ Doorkeeper.configure do
|
|||
# For more information go to
|
||||
# https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes
|
||||
default_scopes :read
|
||||
optional_scopes :write,
|
||||
optional_scopes :profile,
|
||||
:write,
|
||||
:'write:accounts',
|
||||
:'write:blocks',
|
||||
:'write:bookmarks',
|
||||
|
@ -89,7 +90,6 @@ Doorkeeper.configure do
|
|||
:'write:reports',
|
||||
:'write:statuses',
|
||||
:read,
|
||||
:'read:me',
|
||||
:'read:accounts',
|
||||
:'read:blocks',
|
||||
:'read:bookmarks',
|
||||
|
|
|
@ -6,5 +6,5 @@
|
|||
# Use this to limit dissemination of sensitive information.
|
||||
# See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors.
|
||||
Rails.application.config.filter_parameters += [
|
||||
:passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
|
||||
:passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
|
||||
]
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
ActiveSupport::Notifications.subscribe(/rack_attack/) do |_name, _start, _finish, _request_id, payload|
|
||||
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}")
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -19,7 +19,7 @@ ia:
|
|||
account:
|
||||
attributes:
|
||||
username:
|
||||
invalid: debe continer solmente litteras, numeros e tractos de sublineamento
|
||||
invalid: debe continer solmente litteras, numeros e lineettas basse
|
||||
reserved: es reservate
|
||||
admin/webhook:
|
||||
attributes:
|
||||
|
|
|
@ -466,7 +466,7 @@ ca:
|
|||
status: Estat
|
||||
suppress: Suprimeix les recomanacions de seguiment
|
||||
suppressed: Suprimit
|
||||
title: Seguir les recomanacions
|
||||
title: Recomanacions de comptes a seguir
|
||||
unsuppress: Restaurar les recomanacions de seguiment
|
||||
instances:
|
||||
availability:
|
||||
|
|
|
@ -1673,6 +1673,7 @@ da:
|
|||
domain_block: Serversuspendering (%{target_name})
|
||||
user_domain_block: "%{target_name} blev blokeret"
|
||||
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.
|
||||
purged: Oplysninger om denne server er blevet renset af serveradministratoreren.
|
||||
type: Begivenhed
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue