Merge remote-tracking branch 'upstream/main'
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
commit
6f73d7eedd
295 changed files with 4425 additions and 2546 deletions
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
ignore:
|
||||
# devise-two-factor advisory about brute-forcing TOTP
|
||||
# We have rate-limits on authentication endpoints in place (including second
|
||||
# factor verification) since Mastodon v3.2.0
|
||||
- CVE-2024-0227
|
|
@ -1,20 +1,15 @@
|
|||
# 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
|
||||
# Install node version from .nvmrc
|
||||
WORKDIR /app
|
||||
COPY .nvmrc .
|
||||
RUN /bin/bash --login -i -c "nvm install"
|
||||
|
||||
ARG NODE_VERSION="20"
|
||||
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"
|
||||
# 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 libvips42 libpam-dev
|
||||
|
||||
# [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
|
||||
|
||||
# [Optional] Uncomment this line to install additional gems.
|
||||
RUN gem install foreman
|
||||
|
||||
# [Optional] Uncomment this line to install global node packages.
|
||||
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && corepack enable" 2>&1
|
||||
|
||||
COPY welcome-message.txt /usr/local/etc/vscode-dev-containers/first-run-notice.txt
|
||||
# Move welcome message to where VS Code expects it
|
||||
COPY .devcontainer/welcome-message.txt /usr/local/etc/vscode-dev-containers/first-run-notice.txt
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "Mastodon on GitHub Codespaces",
|
||||
"dockerComposeFile": "../docker-compose.yml",
|
||||
"dockerComposeFile": "../compose.yaml",
|
||||
"service": "app",
|
||||
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
|
||||
|
||||
|
@ -23,6 +23,8 @@
|
|||
}
|
||||
},
|
||||
|
||||
"remoteUser": "root",
|
||||
|
||||
"otherPortsAttributes": {
|
||||
"onAutoForward": "silent"
|
||||
},
|
||||
|
@ -37,7 +39,7 @@
|
|||
},
|
||||
|
||||
"onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}",
|
||||
"postCreateCommand": ".devcontainer/post-create.sh",
|
||||
"postCreateCommand": "bin/setup",
|
||||
"waitFor": "postCreateCommand",
|
||||
|
||||
"customizations": {
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
app:
|
||||
working_dir: /workspaces/mastodon/
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
context: ..
|
||||
dockerfile: .devcontainer/Dockerfile
|
||||
volumes:
|
||||
- ../..:/workspaces:cached
|
||||
- ..:/workspaces/mastodon:cached
|
||||
environment:
|
||||
RAILS_ENV: development
|
||||
NODE_ENV: development
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "Mastodon on local machine",
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
"dockerComposeFile": "compose.yaml",
|
||||
"service": "app",
|
||||
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
|
||||
|
||||
|
@ -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,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
|
|
@ -1,8 +1,7 @@
|
|||
👋 Welcome to "Mastodon" in GitHub Codespaces!
|
||||
👋 Welcome to your Mastodon Dev Container!
|
||||
|
||||
🛠️ Your environment is fully setup with all the required software.
|
||||
🛠️ Your environment is fully setup with all the required software.
|
||||
|
||||
🔍 To explore VS Code to its fullest, search using the Command Palette (Cmd/Ctrl + Shift + P or F1).
|
||||
|
||||
📝 Edit away, run your app as usual, and we'll automatically make it available for you to access.
|
||||
💥 Run `bin/dev` to start the application processes.
|
||||
|
||||
🥼 Run `RAILS_ENV=test bin/rails assets:precompile && RAILS_ENV=test bin/rspec` to run the test suite.
|
||||
|
|
|
@ -349,6 +349,9 @@ module.exports = defineConfig({
|
|||
// Disable formatting rules that have been enabled in the base config
|
||||
'indent': 'off',
|
||||
|
||||
// This is not needed as we use noImplicitReturns, which handles this in addition to understanding types
|
||||
'consistent-return': 'off',
|
||||
|
||||
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
|
||||
|
||||
'@typescript-eslint/consistent-type-definitions': ['warn', 'interface'],
|
||||
|
|
2
.github/actions/setup-ruby/action.yml
vendored
2
.github/actions/setup-ruby/action.yml
vendored
|
@ -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
|
||||
|
|
4
.github/codecov.yml
vendored
4
.github/codecov.yml
vendored
|
@ -3,9 +3,9 @@ coverage:
|
|||
status:
|
||||
project:
|
||||
default:
|
||||
# Github status check is not blocking
|
||||
# GitHub status check is not blocking
|
||||
informational: true
|
||||
patch:
|
||||
default:
|
||||
# Github status check is not blocking
|
||||
# GitHub status check is not blocking
|
||||
informational: true
|
||||
|
|
3
.github/renovate.json5
vendored
3
.github/renovate.json5
vendored
|
@ -2,6 +2,7 @@
|
|||
$schema: 'https://docs.renovatebot.com/renovate-schema.json',
|
||||
extends: [
|
||||
'config:recommended',
|
||||
'customManagers:dockerfileVersions',
|
||||
':labels(dependencies)',
|
||||
':prConcurrentLimitNone', // Remove limit for open PRs at any time.
|
||||
':prHourlyLimit2', // Rate limit PR creation to a maximum of two per hour.
|
||||
|
@ -59,7 +60,7 @@
|
|||
dependencyDashboardApproval: true,
|
||||
},
|
||||
{
|
||||
// Update Github Actions and Docker images weekly
|
||||
// Update GitHub Actions and Docker images weekly
|
||||
matchManagers: ['github-actions', 'dockerfile', 'docker-compose'],
|
||||
extends: ['schedule:weekly'],
|
||||
},
|
||||
|
|
2
.github/workflows/build-container-image.yml
vendored
2
.github/workflows/build-container-image.yml
vendored
|
@ -68,7 +68,7 @@ jobs:
|
|||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Log in to the Github Container registry
|
||||
- name: Log in to the GitHub Container registry
|
||||
if: contains(inputs.push_to_images, 'ghcr.io')
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
|
|
13
.github/workflows/bundler-audit.yml
vendored
13
.github/workflows/bundler-audit.yml
vendored
|
@ -6,14 +6,12 @@ on:
|
|||
paths:
|
||||
- 'Gemfile*'
|
||||
- '.ruby-version'
|
||||
- '.bundler-audit.yml'
|
||||
- '.github/workflows/bundler-audit.yml'
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
- 'Gemfile*'
|
||||
- '.ruby-version'
|
||||
- '.bundler-audit.yml'
|
||||
- '.github/workflows/bundler-audit.yml'
|
||||
|
||||
schedule:
|
||||
|
@ -23,12 +21,17 @@ jobs:
|
|||
security:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
BUNDLE_ONLY: development
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Ruby environment
|
||||
uses: ./.github/actions/setup-ruby
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
bundler-cache: true
|
||||
|
||||
- name: Run bundler-audit
|
||||
run: bundle exec bundler-audit
|
||||
run: bundle exec bundler-audit check --update
|
||||
|
|
4
.github/workflows/crowdin-download.yml
vendored
4
.github/workflows/crowdin-download.yml
vendored
|
@ -58,13 +58,13 @@ jobs:
|
|||
title: 'New Crowdin Translations (automated)'
|
||||
author: 'GitHub Actions <noreply@github.com>'
|
||||
body: |
|
||||
New Crowdin translations, automated with Github Actions
|
||||
New Crowdin translations, automated with GitHub Actions
|
||||
|
||||
See `.github/workflows/crowdin-download.yml`
|
||||
|
||||
This PR will be updated every day with new translations.
|
||||
|
||||
Due to a limitation in Github Actions, checks are not running on this PR without manual action.
|
||||
Due to a limitation in GitHub Actions, checks are not running on this PR without manual action.
|
||||
If you want to run the checks, then close and re-open it.
|
||||
branch: i18n/crowdin/translations
|
||||
base: main
|
||||
|
|
10
.github/workflows/lint-haml.yml
vendored
10
.github/workflows/lint-haml.yml
vendored
|
@ -26,12 +26,18 @@ on:
|
|||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
BUNDLE_ONLY: development
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Ruby environment
|
||||
uses: ./.github/actions/setup-ruby
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
bundler-cache: true
|
||||
|
||||
- name: Run haml-lint
|
||||
run: |
|
||||
|
|
13
.github/workflows/lint-ruby.yml
vendored
13
.github/workflows/lint-ruby.yml
vendored
|
@ -27,19 +27,24 @@ jobs:
|
|||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
BUNDLE_ONLY: development
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Ruby environment
|
||||
uses: ./.github/actions/setup-ruby
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
bundler-cache: true
|
||||
|
||||
- name: Set-up RuboCop Problem Matcher
|
||||
uses: r7kamura/rubocop-problem-matchers-action@v1
|
||||
|
||||
- name: Run rubocop
|
||||
run: bundle exec rubocop
|
||||
run: bin/rubocop
|
||||
|
||||
- name: Run brakeman
|
||||
if: always() # Run both checks, even if the first failed
|
||||
run: bundle exec brakeman
|
||||
run: bin/brakeman
|
||||
|
|
2
.github/workflows/rebase-needed.yml
vendored
2
.github/workflows/rebase-needed.yml
vendored
|
@ -17,7 +17,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Check for merge conflicts
|
||||
uses: eps1lon/actions-label-merge-conflict@releases/2.x
|
||||
uses: eps1lon/actions-label-merge-conflict@v3
|
||||
with:
|
||||
dirtyLabel: 'rebase needed :construction:'
|
||||
repoToken: '${{ secrets.GITHUB_TOKEN }}'
|
||||
|
|
95
.github/workflows/test-migrations-two-step.yml
vendored
95
.github/workflows/test-migrations-two-step.yml
vendored
|
@ -1,95 +0,0 @@
|
|||
name: Test two step migrations
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'dependabot/**'
|
||||
- 'renovate/**'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
pre_job:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
outputs:
|
||||
should_skip: ${{ steps.skip_check.outputs.should_skip }}
|
||||
|
||||
steps:
|
||||
- id: skip_check
|
||||
uses: fkirc/skip-duplicate-actions@v5
|
||||
with:
|
||||
paths: '["Gemfile*", ".ruby-version", "**/*.rb", ".github/workflows/test-migrations-two-step.yml", "lib/tasks/tests.rake"]'
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
needs: pre_job
|
||||
if: needs.pre_job.outputs.should_skip != 'true'
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
matrix:
|
||||
postgres:
|
||||
- 14-alpine
|
||||
- 15-alpine
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:${{ matrix.postgres}}
|
||||
env:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_USER: postgres
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
env:
|
||||
CONTINUOUS_INTEGRATION: true
|
||||
DB_HOST: localhost
|
||||
DB_USER: postgres
|
||||
DB_PASS: postgres
|
||||
DISABLE_SIMPLECOV: true
|
||||
RAILS_ENV: test
|
||||
BUNDLE_CLEAN: true
|
||||
BUNDLE_FROZEN: true
|
||||
BUNDLE_WITHOUT: 'development production'
|
||||
BUNDLE_JOBS: 3
|
||||
BUNDLE_RETRY: 3
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Ruby environment
|
||||
uses: ./.github/actions/setup-ruby
|
||||
|
||||
- name: Create database
|
||||
run: './bin/rails db:create'
|
||||
|
||||
- name: Run historical migrations with data population
|
||||
run: './bin/rails tests:migrations:prepare_database'
|
||||
env:
|
||||
SKIP_POST_DEPLOYMENT_MIGRATIONS: true
|
||||
|
||||
- name: Run all remaining pre-deployment migrations
|
||||
run: './bin/rails db:migrate'
|
||||
env:
|
||||
SKIP_POST_DEPLOYMENT_MIGRATIONS: true
|
||||
|
||||
- name: Run all post-deployment migrations
|
||||
run: './bin/rails db:migrate'
|
||||
|
||||
- name: Check migration result
|
||||
run: './bin/rails tests:migrations:check_database'
|
|
@ -1,4 +1,5 @@
|
|||
name: Test one step migrations
|
||||
name: Historical data migration test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
|
@ -17,7 +18,7 @@ jobs:
|
|||
- id: skip_check
|
||||
uses: fkirc/skip-duplicate-actions@v5
|
||||
with:
|
||||
paths: '["Gemfile*", ".ruby-version", "**/*.rb", ".github/workflows/test-migrations-one-step.yml", "lib/tasks/tests.rake"]'
|
||||
paths: '["Gemfile*", ".ruby-version", "**/*.rb", ".github/workflows/test-migrations.yml", "lib/tasks/tests.rake"]'
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -40,9 +41,9 @@ jobs:
|
|||
POSTGRES_USER: postgres
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
--health-interval 10ms
|
||||
--health-timeout 3s
|
||||
--health-retries 50
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
|
@ -50,14 +51,13 @@ jobs:
|
|||
image: redis:7-alpine
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
--health-interval 10ms
|
||||
--health-timeout 3s
|
||||
--health-retries 50
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
env:
|
||||
CONTINUOUS_INTEGRATION: true
|
||||
DB_HOST: localhost
|
||||
DB_USER: postgres
|
||||
DB_PASS: postgres
|
||||
|
@ -65,7 +65,7 @@ jobs:
|
|||
RAILS_ENV: test
|
||||
BUNDLE_CLEAN: true
|
||||
BUNDLE_FROZEN: true
|
||||
BUNDLE_WITHOUT: 'development production'
|
||||
BUNDLE_WITHOUT: 'development:production'
|
||||
BUNDLE_JOBS: 3
|
||||
BUNDLE_RETRY: 3
|
||||
|
||||
|
@ -75,14 +75,19 @@ jobs:
|
|||
- name: Set up Ruby environment
|
||||
uses: ./.github/actions/setup-ruby
|
||||
|
||||
- name: Create database
|
||||
run: './bin/rails db:create'
|
||||
- name: Test "one step migration" flow
|
||||
run: |
|
||||
bin/rails db:drop
|
||||
bin/rails db:create
|
||||
bin/rails tests:migrations:prepare_database
|
||||
bin/rails db:migrate
|
||||
bin/rails tests:migrations:check_database
|
||||
|
||||
- name: Run historical migrations with data population
|
||||
run: './bin/rails tests:migrations:prepare_database'
|
||||
|
||||
- name: Run all remaining migrations
|
||||
run: './bin/rails db:migrate'
|
||||
|
||||
- name: Check migration result
|
||||
run: './bin/rails tests:migrations:check_database'
|
||||
- name: Test "two step migration" flow
|
||||
run: |
|
||||
bin/rails db:drop
|
||||
bin/rails db:create
|
||||
SKIP_POST_DEPLOYMENT_MIGRATIONS=true bin/rails tests:migrations:prepare_database
|
||||
SKIP_POST_DEPLOYMENT_MIGRATIONS=true bin/rails db:migrate
|
||||
bin/rails db:migrate
|
||||
bin/rails tests:migrations:check_database
|
147
.github/workflows/test-ruby.yml
vendored
147
.github/workflows/test-ruby.yml
vendored
|
@ -28,11 +28,7 @@ jobs:
|
|||
env:
|
||||
RAILS_ENV: ${{ matrix.mode }}
|
||||
BUNDLE_WITH: ${{ matrix.mode }}
|
||||
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY: precompile_placeholder
|
||||
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT: precompile_placeholder
|
||||
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY: precompile_placeholder
|
||||
OTP_SECRET: precompile_placeholder
|
||||
SECRET_KEY_BASE: precompile_placeholder
|
||||
SECRET_KEY_BASE_DUMMY: 1
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
@ -77,9 +73,9 @@ jobs:
|
|||
POSTGRES_USER: postgres
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
--health-interval 10ms
|
||||
--health-timeout 3s
|
||||
--health-retries 50
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
|
@ -87,9 +83,9 @@ jobs:
|
|||
image: redis:7-alpine
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
--health-interval 10ms
|
||||
--health-timeout 3s
|
||||
--health-retries 50
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
|
@ -133,7 +129,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 +144,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 10ms
|
||||
--health-timeout 3s
|
||||
--health-retries 50
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10ms
|
||||
--health-timeout 3s
|
||||
--health-retries 50
|
||||
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
|
||||
|
@ -163,9 +246,9 @@ jobs:
|
|||
POSTGRES_USER: postgres
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
--health-interval 10ms
|
||||
--health-timeout 3s
|
||||
--health-retries 50
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
|
@ -173,9 +256,9 @@ jobs:
|
|||
image: redis:7-alpine
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
--health-interval 10ms
|
||||
--health-timeout 3s
|
||||
--health-retries 50
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
|
@ -209,7 +292,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
|
||||
|
@ -248,9 +331,9 @@ jobs:
|
|||
POSTGRES_USER: postgres
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
--health-interval 10ms
|
||||
--health-timeout 3s
|
||||
--health-retries 50
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
|
@ -258,9 +341,9 @@ jobs:
|
|||
image: redis:7-alpine
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
--health-interval 10ms
|
||||
--health-timeout 3s
|
||||
--health-retries 50
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
|
@ -271,9 +354,9 @@ jobs:
|
|||
xpack.security.enabled: false
|
||||
options: >-
|
||||
--health-cmd "curl http://localhost:9200/_cluster/health"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 10
|
||||
--health-interval 2s
|
||||
--health-timeout 3s
|
||||
--health-retries 50
|
||||
ports:
|
||||
- 9200:9200
|
||||
|
||||
|
@ -285,9 +368,9 @@ jobs:
|
|||
DISABLE_SECURITY_PLUGIN: true
|
||||
options: >-
|
||||
--health-cmd "curl http://localhost:9200/_cluster/health"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 10
|
||||
--health-interval 2s
|
||||
--health-timeout 3s
|
||||
--health-retries 50
|
||||
ports:
|
||||
- 9200:9200
|
||||
|
||||
|
@ -329,7 +412,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/
|
259
.rubocop.yml
259
.rubocop.yml
|
@ -1,7 +1,34 @@
|
|||
# Can be removed once all rules are addressed or moved to this file as documented overrides
|
||||
inherit_from: .rubocop_todo.yml
|
||||
---
|
||||
AllCops:
|
||||
CacheRootDirectory: tmp
|
||||
DisplayCopNames: true
|
||||
DisplayStyleGuide: true
|
||||
Exclude:
|
||||
- db/schema.rb
|
||||
- bin/*
|
||||
- node_modules/**/*
|
||||
- Vagrantfile
|
||||
- vendor/**/*
|
||||
- config/initializers/json_ld*
|
||||
- lib/mastodon/migration_helpers.rb
|
||||
- lib/templates/**/*
|
||||
ExtraDetails: true
|
||||
NewCops: enable
|
||||
TargetRubyVersion: 3.1 # Oldest supported ruby version
|
||||
UseCache: true
|
||||
|
||||
inherit_from:
|
||||
- .rubocop/layout.yml
|
||||
- .rubocop/metrics.yml
|
||||
- .rubocop/naming.yml
|
||||
- .rubocop/rails.yml
|
||||
- .rubocop/rspec_rails.yml
|
||||
- .rubocop/rspec.yml
|
||||
- .rubocop/style.yml
|
||||
- .rubocop/custom.yml
|
||||
- .rubocop_todo.yml
|
||||
- .rubocop/strict.yml
|
||||
|
||||
# Used for merging with exclude lists with .rubocop_todo.yml
|
||||
inherit_mode:
|
||||
merge:
|
||||
- Exclude
|
||||
|
@ -12,229 +39,3 @@ require:
|
|||
- rubocop-rspec_rails
|
||||
- rubocop-performance
|
||||
- rubocop-capybara
|
||||
- ./lib/linter/rubocop_middle_dot
|
||||
|
||||
AllCops:
|
||||
TargetRubyVersion: 3.1 # Set to minimum supported version of CI
|
||||
DisplayCopNames: true
|
||||
DisplayStyleGuide: true
|
||||
ExtraDetails: true
|
||||
UseCache: true
|
||||
CacheRootDirectory: tmp
|
||||
NewCops: enable # Opt-in to newly added rules
|
||||
Exclude:
|
||||
- db/schema.rb
|
||||
- 'bin/*'
|
||||
- 'node_modules/**/*'
|
||||
- 'Vagrantfile'
|
||||
- 'vendor/**/*'
|
||||
- 'config/initializers/json_ld*' # Generated files
|
||||
- 'lib/mastodon/migration_helpers.rb' # Vendored from GitLab
|
||||
- 'lib/templates/**/*'
|
||||
|
||||
# Reason: Prefer Hashes without extreme indentation
|
||||
# https://docs.rubocop.org/rubocop/cops_layout.html#layoutfirsthashelementindentation
|
||||
Layout/FirstHashElementIndentation:
|
||||
EnforcedStyle: consistent
|
||||
|
||||
# Reason: Currently disabled in .rubocop_todo.yml
|
||||
# https://docs.rubocop.org/rubocop/cops_layout.html#layoutlinelength
|
||||
Layout/LineLength:
|
||||
Max: 300 # Default of 120 causes a duplicate entry in generated todo file
|
||||
|
||||
## Disable most Metrics/*Length cops
|
||||
# Reason: those are often triggered and force significant refactors when this happend
|
||||
# but the team feel they are not really improving the code quality.
|
||||
|
||||
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsblocklength
|
||||
Metrics/BlockLength:
|
||||
Enabled: false
|
||||
|
||||
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsclasslength
|
||||
Metrics/ClassLength:
|
||||
Enabled: false
|
||||
|
||||
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsmethodlength
|
||||
Metrics/MethodLength:
|
||||
Enabled: false
|
||||
|
||||
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsmodulelength
|
||||
Metrics/ModuleLength:
|
||||
Enabled: false
|
||||
|
||||
## End Disable Metrics/*Length cops
|
||||
|
||||
# Reason: Currently disabled in .rubocop_todo.yml
|
||||
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsabcsize
|
||||
Metrics/AbcSize:
|
||||
Exclude:
|
||||
- 'lib/mastodon/cli/*.rb'
|
||||
|
||||
# Reason: Currently disabled in .rubocop_todo.yml
|
||||
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricscyclomaticcomplexity
|
||||
Metrics/CyclomaticComplexity:
|
||||
Exclude:
|
||||
- lib/mastodon/cli/*.rb
|
||||
|
||||
# Reason:
|
||||
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsparameterlists
|
||||
Metrics/ParameterLists:
|
||||
CountKeywordArgs: false
|
||||
|
||||
# Reason: Prefer seeing a variable name
|
||||
# https://docs.rubocop.org/rubocop/cops_naming.html#namingblockforwarding
|
||||
Naming/BlockForwarding:
|
||||
EnforcedStyle: explicit
|
||||
|
||||
# Reason: Prevailing style is argument file paths
|
||||
# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsfilepath
|
||||
Rails/FilePath:
|
||||
EnforcedStyle: arguments
|
||||
|
||||
# Reason: Prevailing style uses numeric status codes, matches RSpec/Rails/HttpStatus
|
||||
# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railshttpstatus
|
||||
Rails/HttpStatus:
|
||||
EnforcedStyle: numeric
|
||||
|
||||
# Reason: Conflicts with `Lint/UselessMethodDefinition` for inherited controller actions
|
||||
# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railslexicallyscopedactionfilter
|
||||
Rails/LexicallyScopedActionFilter:
|
||||
Exclude:
|
||||
- 'app/controllers/auth/*'
|
||||
|
||||
# Reason: These tasks are doing local work which do not need full env loaded
|
||||
# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsrakeenvironment
|
||||
Rails/RakeEnvironment:
|
||||
Exclude:
|
||||
- 'lib/tasks/auto_annotate_models.rake'
|
||||
- 'lib/tasks/emojis.rake'
|
||||
- 'lib/tasks/mastodon.rake'
|
||||
- 'lib/tasks/repo.rake'
|
||||
- 'lib/tasks/statistics.rake'
|
||||
|
||||
# Reason: There are appropriate times to use these features
|
||||
# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsskipsmodelvalidations
|
||||
Rails/SkipsModelValidations:
|
||||
Enabled: false
|
||||
|
||||
# Reason: We want to preserve the ability to migrate from arbitrary old versions,
|
||||
# and cannot guarantee that every installation has run every migration as they upgrade.
|
||||
# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsunusedignoredcolumns
|
||||
Rails/UnusedIgnoredColumns:
|
||||
Enabled: false
|
||||
|
||||
# Reason: Prevailing style choice
|
||||
# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsnegateinclude
|
||||
Rails/NegateInclude:
|
||||
Enabled: false
|
||||
|
||||
# Reason: Enforce default limit, but allow some elements to span lines
|
||||
# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecexamplelength
|
||||
RSpec/ExampleLength:
|
||||
CountAsOne: ['array', 'heredoc', 'method_call']
|
||||
|
||||
# Reason: Deprecated cop, will be removed in 3.0, replaced by SpecFilePathFormat
|
||||
# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecfilepath
|
||||
RSpec/FilePath:
|
||||
Enabled: false
|
||||
|
||||
# Reason:
|
||||
# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecnamedsubject
|
||||
RSpec/NamedSubject:
|
||||
EnforcedStyle: named_only
|
||||
|
||||
# Reason: Prevailing style choice
|
||||
# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecnottonot
|
||||
RSpec/NotToNot:
|
||||
EnforcedStyle: to_not
|
||||
|
||||
# Reason: Match overrides from Rspec/FilePath rule above
|
||||
# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecspecfilepathformat
|
||||
RSpec/SpecFilePathFormat:
|
||||
CustomTransform:
|
||||
ActivityPub: activitypub
|
||||
DeepL: deepl
|
||||
FetchOEmbedService: fetch_oembed_service
|
||||
OEmbedController: oembed_controller
|
||||
OStatus: ostatus
|
||||
|
||||
# Reason: Prevailing style uses numeric status codes, matches Rails/HttpStatus
|
||||
# https://docs.rubocop.org/rubocop-rspec/cops_rspec_rails.html#rspecrailshttpstatus
|
||||
RSpecRails/HttpStatus:
|
||||
EnforcedStyle: numeric
|
||||
|
||||
# Reason:
|
||||
# https://docs.rubocop.org/rubocop/cops_style.html#styleclassandmodulechildren
|
||||
Style/ClassAndModuleChildren:
|
||||
Enabled: false
|
||||
|
||||
# Reason: Classes mostly self-document with their names
|
||||
# https://docs.rubocop.org/rubocop/cops_style.html#styledocumentation
|
||||
Style/Documentation:
|
||||
Enabled: false
|
||||
|
||||
# Reason: Route redirects are not token-formatted and must be skipped
|
||||
# https://docs.rubocop.org/rubocop/cops_style.html#styleformatstringtoken
|
||||
Style/FormatStringToken:
|
||||
inherit_mode:
|
||||
merge:
|
||||
- AllowedMethods # The rubocop-rails config adds `redirect`
|
||||
AllowedMethods:
|
||||
- redirect_with_vary
|
||||
|
||||
# Reason: Prevailing style choice
|
||||
# https://docs.rubocop.org/rubocop/cops_style.html#stylehashaslastarrayitem
|
||||
Style/HashAsLastArrayItem:
|
||||
Enabled: false
|
||||
|
||||
# Reason: Enforce modern Ruby style
|
||||
# https://docs.rubocop.org/rubocop/cops_style.html#stylehashsyntax
|
||||
Style/HashSyntax:
|
||||
EnforcedStyle: ruby19_no_mixed_keys
|
||||
EnforcedShorthandSyntax: either
|
||||
|
||||
# Reason:
|
||||
# https://docs.rubocop.org/rubocop/cops_style.html#stylenumericliterals
|
||||
Style/NumericLiterals:
|
||||
AllowedPatterns:
|
||||
- \d{4}_\d{2}_\d{2}_\d{6} # For DB migration date version number readability
|
||||
|
||||
# Reason:
|
||||
# https://docs.rubocop.org/rubocop/cops_style.html#stylepercentliteraldelimiters
|
||||
Style/PercentLiteralDelimiters:
|
||||
PreferredDelimiters:
|
||||
'%i': '()'
|
||||
'%w': '()'
|
||||
|
||||
# Reason: Prefer less indentation in conditional assignments
|
||||
# https://docs.rubocop.org/rubocop/cops_style.html#styleredundantbegin
|
||||
Style/RedundantBegin:
|
||||
Enabled: false
|
||||
|
||||
# Reason: Prevailing style choice
|
||||
# https://docs.rubocop.org/rubocop/cops_style.html#styleredundantfetchblock
|
||||
Style/RedundantFetchBlock:
|
||||
Enabled: false
|
||||
|
||||
# Reason: Overridden to reduce implicit StandardError rescues
|
||||
# https://docs.rubocop.org/rubocop/cops_style.html#stylerescuestandarderror
|
||||
Style/RescueStandardError:
|
||||
EnforcedStyle: implicit
|
||||
|
||||
# Reason: Originally disabled for CodeClimate, and no config consensus has been found
|
||||
# https://docs.rubocop.org/rubocop/cops_style.html#stylesymbolarray
|
||||
Style/SymbolArray:
|
||||
Enabled: false
|
||||
|
||||
# Reason:
|
||||
# https://docs.rubocop.org/rubocop/cops_style.html#styletrailingcommainarrayliteral
|
||||
Style/TrailingCommaInArrayLiteral:
|
||||
EnforcedStyleForMultiline: 'comma'
|
||||
|
||||
# Reason:
|
||||
# https://docs.rubocop.org/rubocop/cops_style.html#styletrailingcommainhashliteral
|
||||
Style/TrailingCommaInHashLiteral:
|
||||
EnforcedStyleForMultiline: 'comma'
|
||||
|
||||
Style/MiddleDot:
|
||||
Enabled: true
|
||||
|
|
6
.rubocop/custom.yml
Normal file
6
.rubocop/custom.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
require:
|
||||
- ../lib/linter/rubocop_middle_dot
|
||||
|
||||
Style/MiddleDot:
|
||||
Enabled: true
|
6
.rubocop/layout.yml
Normal file
6
.rubocop/layout.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
Layout/FirstHashElementIndentation:
|
||||
EnforcedStyle: consistent
|
||||
|
||||
Layout/LineLength:
|
||||
Max: 300 # Default of 120 causes a duplicate entry in generated todo file
|
23
.rubocop/metrics.yml
Normal file
23
.rubocop/metrics.yml
Normal file
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
Metrics/AbcSize:
|
||||
Exclude:
|
||||
- lib/mastodon/cli/*.rb
|
||||
|
||||
Metrics/BlockLength:
|
||||
Enabled: false
|
||||
|
||||
Metrics/ClassLength:
|
||||
Enabled: false
|
||||
|
||||
Metrics/CyclomaticComplexity:
|
||||
Exclude:
|
||||
- lib/mastodon/cli/*.rb
|
||||
|
||||
Metrics/MethodLength:
|
||||
Enabled: false
|
||||
|
||||
Metrics/ModuleLength:
|
||||
Enabled: false
|
||||
|
||||
Metrics/ParameterLists:
|
||||
CountKeywordArgs: false
|
3
.rubocop/naming.yml
Normal file
3
.rubocop/naming.yml
Normal file
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
Naming/BlockForwarding:
|
||||
EnforcedStyle: explicit
|
27
.rubocop/rails.yml
Normal file
27
.rubocop/rails.yml
Normal file
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
Rails/FilePath:
|
||||
EnforcedStyle: arguments
|
||||
|
||||
Rails/HttpStatus:
|
||||
EnforcedStyle: numeric
|
||||
|
||||
Rails/LexicallyScopedActionFilter:
|
||||
Exclude:
|
||||
- app/controllers/auth/* # Conflicts with `Lint/UselessMethodDefinition` for inherited controller actions
|
||||
|
||||
Rails/NegateInclude:
|
||||
Enabled: false
|
||||
|
||||
Rails/RakeEnvironment:
|
||||
Exclude: # Tasks are doing local work which do not need full env loaded
|
||||
- lib/tasks/auto_annotate_models.rake
|
||||
- lib/tasks/emojis.rake
|
||||
- lib/tasks/mastodon.rake
|
||||
- lib/tasks/repo.rake
|
||||
- lib/tasks/statistics.rake
|
||||
|
||||
Rails/SkipsModelValidations:
|
||||
Enabled: false
|
||||
|
||||
Rails/UnusedIgnoredColumns:
|
||||
Enabled: false # Preserve ability to migrate from arbitrary old versions
|
27
.rubocop/rspec.yml
Normal file
27
.rubocop/rspec.yml
Normal file
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
RSpec/ExampleLength:
|
||||
CountAsOne: ['array', 'heredoc', 'method_call']
|
||||
Max: 20 # Override default of 5
|
||||
|
||||
RSpec/MultipleExpectations:
|
||||
Max: 10 # Overrides default of 1
|
||||
|
||||
RSpec/MultipleMemoizedHelpers:
|
||||
Max: 20 # Overrides default of 5
|
||||
|
||||
RSpec/NamedSubject:
|
||||
EnforcedStyle: named_only
|
||||
|
||||
RSpec/NestedGroups:
|
||||
Max: 10 # Overrides default of 3
|
||||
|
||||
RSpec/NotToNot:
|
||||
EnforcedStyle: to_not
|
||||
|
||||
RSpec/SpecFilePathFormat:
|
||||
CustomTransform:
|
||||
ActivityPub: activitypub
|
||||
DeepL: deepl
|
||||
FetchOEmbedService: fetch_oembed_service
|
||||
OEmbedController: oembed_controller
|
||||
OStatus: ostatus
|
3
.rubocop/rspec_rails.yml
Normal file
3
.rubocop/rspec_rails.yml
Normal file
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
RSpecRails/HttpStatus:
|
||||
EnforcedStyle: numeric
|
19
.rubocop/strict.yml
Normal file
19
.rubocop/strict.yml
Normal file
|
@ -0,0 +1,19 @@
|
|||
Lint/Debugger: # Remove any `binding.pry`
|
||||
Enabled: true
|
||||
Exclude: []
|
||||
|
||||
RSpec/Focus: # Require full spec run on CI
|
||||
Enabled: true
|
||||
Exclude: []
|
||||
|
||||
Rails/Output: # Remove any `puts` debugging
|
||||
Enabled: true
|
||||
Exclude: []
|
||||
|
||||
Rails/FindEach: # Using `each` could impact performance, use `find_each`
|
||||
Enabled: true
|
||||
Exclude: []
|
||||
|
||||
Rails/UniqBeforePluck: # Require `uniq.pluck` and not `pluck.uniq`
|
||||
Enabled: true
|
||||
Exclude: []
|
47
.rubocop/style.yml
Normal file
47
.rubocop/style.yml
Normal file
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
Style/ClassAndModuleChildren:
|
||||
Enabled: false
|
||||
|
||||
Style/Documentation:
|
||||
Enabled: false
|
||||
|
||||
Style/FormatStringToken:
|
||||
AllowedMethods:
|
||||
- redirect_with_vary # Route redirects are not token-formatted
|
||||
inherit_mode:
|
||||
merge:
|
||||
- AllowedMethods
|
||||
|
||||
Style/HashAsLastArrayItem:
|
||||
Enabled: false
|
||||
|
||||
Style/HashSyntax:
|
||||
EnforcedShorthandSyntax: either
|
||||
EnforcedStyle: ruby19_no_mixed_keys
|
||||
|
||||
Style/NumericLiterals:
|
||||
AllowedPatterns:
|
||||
- \d{4}_\d{2}_\d{2}_\d{6}
|
||||
|
||||
Style/PercentLiteralDelimiters:
|
||||
PreferredDelimiters:
|
||||
'%i': ()
|
||||
'%w': ()
|
||||
|
||||
Style/RedundantBegin:
|
||||
Enabled: false
|
||||
|
||||
Style/RedundantFetchBlock:
|
||||
Enabled: false
|
||||
|
||||
Style/RescueStandardError:
|
||||
EnforcedStyle: implicit
|
||||
|
||||
Style/SymbolArray:
|
||||
Enabled: false
|
||||
|
||||
Style/TrailingCommaInArrayLiteral:
|
||||
EnforcedStyleForMultiline: comma
|
||||
|
||||
Style/TrailingCommaInHashLiteral:
|
||||
EnforcedStyleForMultiline: comma
|
|
@ -27,21 +27,6 @@ Metrics/CyclomaticComplexity:
|
|||
Metrics/PerceivedComplexity:
|
||||
Max: 27
|
||||
|
||||
# Configuration parameters: CountAsOne.
|
||||
RSpec/ExampleLength:
|
||||
Max: 18
|
||||
|
||||
RSpec/MultipleExpectations:
|
||||
Max: 7
|
||||
|
||||
# Configuration parameters: AllowSubject.
|
||||
RSpec/MultipleMemoizedHelpers:
|
||||
Max: 17
|
||||
|
||||
# Configuration parameters: AllowedGroups.
|
||||
RSpec/NestedGroups:
|
||||
Max: 6
|
||||
|
||||
Rails/OutputSafety:
|
||||
Exclude:
|
||||
- 'config/initializers/simple_form.rb'
|
||||
|
|
|
@ -1 +1 @@
|
|||
3.3.2
|
||||
3.3.3
|
||||
|
|
98
Dockerfile
98
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,24 @@
|
|||
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"]
|
||||
# renovate: datasource=docker depName=docker.io/ruby
|
||||
ARG RUBY_VERSION="3.3.3"
|
||||
# # Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"]
|
||||
# renovate: datasource=node-version depName=node
|
||||
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="bark"
|
||||
# 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="dev"
|
||||
|
||||
# Allow Ruby on Rails to serve static files
|
||||
|
@ -60,7 +65,9 @@ ENV \
|
|||
DEBIAN_FRONTEND="noninteractive" \
|
||||
PATH="${PATH}:/opt/ruby/bin:/opt/mastodon/bin" \
|
||||
# Optimize jemalloc 5.x performance
|
||||
MALLOC_CONF="narenas:2,background_thread:true,thp:never,dirty_decay_ms:1000,muzzy_decay_ms:0"
|
||||
MALLOC_CONF="narenas:2,background_thread:true,thp:never,dirty_decay_ms:1000,muzzy_decay_ms:0" \
|
||||
# Enable libvips, should not be changed
|
||||
MASTODON_USE_LIBVIPS=true
|
||||
|
||||
# Set default shell used for running commands
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-o", "errexit", "-c"]
|
||||
|
@ -97,7 +104,6 @@ RUN \
|
|||
curl \
|
||||
ffmpeg \
|
||||
file \
|
||||
imagemagick \
|
||||
libjemalloc2 \
|
||||
patchelf \
|
||||
procps \
|
||||
|
@ -131,18 +137,31 @@ RUN \
|
|||
--mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
|
||||
# Install build tools and bundler dependencies from APT
|
||||
apt-get install -y --no-install-recommends \
|
||||
g++ \
|
||||
gcc \
|
||||
build-essential \
|
||||
git \
|
||||
libgdbm-dev \
|
||||
libglib2.0-dev \
|
||||
libgmp-dev \
|
||||
libicu-dev \
|
||||
libidn-dev \
|
||||
libpq-dev \
|
||||
libssl-dev \
|
||||
make \
|
||||
meson \
|
||||
pkg-config \
|
||||
shared-mime-info \
|
||||
zlib1g-dev \
|
||||
# libvips components
|
||||
libcgif-dev \
|
||||
libexif-dev \
|
||||
libexpat1-dev \
|
||||
libgirepository1.0-dev \
|
||||
libheif-dev \
|
||||
libimagequant-dev \
|
||||
libjpeg62-turbo-dev \
|
||||
liblcms2-dev \
|
||||
liborc-dev \
|
||||
libspng-dev \
|
||||
libtiff-dev \
|
||||
libwebp-dev \
|
||||
;
|
||||
|
||||
RUN \
|
||||
|
@ -151,6 +170,26 @@ RUN \
|
|||
corepack enable; \
|
||||
corepack prepare --activate;
|
||||
|
||||
# Create temporary libvips specific build layer from build layer
|
||||
FROM build as libvips
|
||||
|
||||
# libvips version to compile, change with [--build-arg VIPS_VERSION="8.15.2"]
|
||||
# renovate: datasource=github-releases depName=libvips packageName=libvips/libvips
|
||||
ARG VIPS_VERSION=8.15.2
|
||||
# libvips download URL, change with [--build-arg VIPS_URL="https://github.com/libvips/libvips/releases/download"]
|
||||
ARG VIPS_URL=https://github.com/libvips/libvips/releases/download
|
||||
|
||||
WORKDIR /usr/local/libvips/src
|
||||
|
||||
RUN \
|
||||
curl -sSL -o vips-${VIPS_VERSION}.tar.xz ${VIPS_URL}/v${VIPS_VERSION}/vips-${VIPS_VERSION}.tar.xz; \
|
||||
tar xf vips-${VIPS_VERSION}.tar.xz; \
|
||||
cd vips-${VIPS_VERSION}; \
|
||||
meson setup build --prefix /usr/local/libvips --libdir=lib -Ddeprecated=false -Dintrospection=disabled -Dmodules=disabled -Dexamples=false; \
|
||||
cd build; \
|
||||
ninja; \
|
||||
ninja install;
|
||||
|
||||
# Create temporary bundler specific build layer from build layer
|
||||
FROM build as bundler
|
||||
|
||||
|
@ -200,16 +239,16 @@ COPY . /opt/mastodon/
|
|||
COPY --from=yarn /opt/mastodon /opt/mastodon/
|
||||
COPY --from=bundler /opt/mastodon /opt/mastodon/
|
||||
COPY --from=bundler /usr/local/bundle/ /usr/local/bundle/
|
||||
# Copy libvips components to layer for precompiler
|
||||
COPY --from=libvips /usr/local/libvips/bin /usr/local/bin
|
||||
COPY --from=libvips /usr/local/libvips/lib /usr/local/lib
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
RUN \
|
||||
ldconfig; \
|
||||
# Use Ruby on Rails to create Mastodon assets
|
||||
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=precompile_placeholder \
|
||||
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=precompile_placeholder \
|
||||
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=precompile_placeholder \
|
||||
OTP_SECRET=precompile_placeholder \
|
||||
SECRET_KEY_BASE=precompile_placeholder \
|
||||
SECRET_KEY_BASE_DUMMY=1 \
|
||||
bundle exec rails assets:precompile; \
|
||||
# Cleanup temporary files
|
||||
rm -fr /opt/mastodon/tmp;
|
||||
|
@ -229,12 +268,27 @@ RUN \
|
|||
--mount=type=cache,id=yarn-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/yarn,sharing=locked \
|
||||
# Apt update install non-dev versions of necessary components
|
||||
apt-get install -y --no-install-recommends \
|
||||
libssl3 \
|
||||
libpq5 \
|
||||
libexpat1 \
|
||||
libglib2.0-0 \
|
||||
libicu72 \
|
||||
libidn12 \
|
||||
libpq5 \
|
||||
libreadline8 \
|
||||
libssl3 \
|
||||
libyaml-0-2 \
|
||||
# libvips components
|
||||
libcgif0 \
|
||||
libexif12 \
|
||||
libheif1 \
|
||||
libimagequant0 \
|
||||
libjpeg62-turbo \
|
||||
liblcms2-2 \
|
||||
liborc-0.4-0 \
|
||||
libspng0 \
|
||||
libtiff6 \
|
||||
libwebp7 \
|
||||
libwebpdemux2 \
|
||||
libwebpmux3 \
|
||||
;
|
||||
|
||||
# Copy Mastodon sources into final layer
|
||||
|
@ -245,9 +299,17 @@ COPY --from=precompiler /opt/mastodon/public/packs /opt/mastodon/public/packs
|
|||
COPY --from=precompiler /opt/mastodon/public/assets /opt/mastodon/public/assets
|
||||
# Copy bundler components to layer
|
||||
COPY --from=bundler /usr/local/bundle/ /usr/local/bundle/
|
||||
# Copy libvips components to layer
|
||||
COPY --from=libvips /usr/local/libvips/bin /usr/local/bin
|
||||
COPY --from=libvips /usr/local/libvips/lib /usr/local/lib
|
||||
|
||||
RUN \
|
||||
# Precompile bootsnap code for faster Rails startup
|
||||
ldconfig; \
|
||||
# Smoketest media processors
|
||||
vips -v;
|
||||
|
||||
RUN \
|
||||
# Precompile bootsnap code for faster Rails startup
|
||||
bundle exec bootsnap precompile --gemfile app/ lib/;
|
||||
|
||||
RUN \
|
||||
|
|
6
Gemfile
6
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'
|
||||
|
@ -56,7 +57,7 @@ gem 'hiredis', '~> 0.6'
|
|||
gem 'htmlentities', '~> 4.3'
|
||||
gem 'http', '~> 5.2.0'
|
||||
gem 'http_accept_language', '~> 2.1'
|
||||
gem 'httplog', '~> 1.6.2'
|
||||
gem 'httplog', '~> 1.7.0'
|
||||
gem 'i18n'
|
||||
gem 'idn-ruby', require: 'idn'
|
||||
gem 'inline_svg'
|
||||
|
@ -106,7 +107,7 @@ gem 'private_address_check', '~> 0.5'
|
|||
gem 'opentelemetry-api', '~> 1.2.5'
|
||||
|
||||
group :opentelemetry do
|
||||
gem 'opentelemetry-exporter-otlp', '~> 0.26.3', require: false
|
||||
gem 'opentelemetry-exporter-otlp', '~> 0.27.0', require: false
|
||||
gem 'opentelemetry-instrumentation-active_job', '~> 0.7.1', require: false
|
||||
gem 'opentelemetry-instrumentation-active_model_serializers', '~> 0.20.1', require: false
|
||||
gem 'opentelemetry-instrumentation-concurrent_ruby', '~> 0.21.2', require: false
|
||||
|
@ -170,6 +171,7 @@ group :development do
|
|||
gem 'rubocop-performance', require: false
|
||||
gem 'rubocop-rails', require: false
|
||||
gem 'rubocop-rspec', require: false
|
||||
gem 'rubocop-rspec_rails', require: false
|
||||
|
||||
# Annotates modules with schema
|
||||
gem 'annotate', '~> 3.2'
|
||||
|
|
187
Gemfile.lock
187
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)
|
||||
|
@ -100,17 +100,17 @@ GEM
|
|||
attr_required (1.0.2)
|
||||
awrence (1.2.1)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.929.0)
|
||||
aws-sdk-core (3.196.1)
|
||||
aws-partitions (1.940.0)
|
||||
aws-sdk-core (3.197.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.8)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.81.0)
|
||||
aws-sdk-core (~> 3, >= 3.193.0)
|
||||
aws-sdk-kms (1.83.0)
|
||||
aws-sdk-core (~> 3, >= 3.197.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.151.0)
|
||||
aws-sdk-core (~> 3, >= 3.194.0)
|
||||
aws-sdk-s3 (1.152.3)
|
||||
aws-sdk-core (~> 3, >= 3.197.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.8)
|
||||
aws-sigv4 (1.8.0)
|
||||
|
@ -168,7 +168,7 @@ GEM
|
|||
climate_control (1.2.0)
|
||||
cocoon (1.2.15)
|
||||
color_diff (0.1)
|
||||
concurrent-ruby (1.3.1)
|
||||
concurrent-ruby (1.3.3)
|
||||
connection_pool (2.4.1)
|
||||
cose (1.3.0)
|
||||
cbor (~> 0.5.9)
|
||||
|
@ -272,7 +272,7 @@ GEM
|
|||
fog-json (1.2.0)
|
||||
fog-core
|
||||
multi_json (~> 1.10)
|
||||
fog-openstack (1.1.1)
|
||||
fog-openstack (1.1.3)
|
||||
fog-core (~> 2.1)
|
||||
fog-json (>= 1.0)
|
||||
formatador (1.1.0)
|
||||
|
@ -321,7 +321,7 @@ GEM
|
|||
http-form_data (2.3.0)
|
||||
http_accept_language (2.1.1)
|
||||
httpclient (2.8.3)
|
||||
httplog (1.6.3)
|
||||
httplog (1.7.0)
|
||||
rack (>= 2.0)
|
||||
rainbow (>= 2.0.0)
|
||||
i18n (1.14.5)
|
||||
|
@ -422,9 +422,9 @@ GEM
|
|||
memory_profiler (1.0.1)
|
||||
mime-types (3.5.2)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2024.0507)
|
||||
mime-types-data (3.2024.0604)
|
||||
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)
|
||||
|
@ -445,7 +445,7 @@ GEM
|
|||
net-smtp (0.5.0)
|
||||
net-protocol
|
||||
nio4r (2.7.3)
|
||||
nokogiri (1.16.5)
|
||||
nokogiri (1.16.6)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
nsa (0.3.0)
|
||||
|
@ -453,7 +453,7 @@ GEM
|
|||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
sidekiq (>= 3.5)
|
||||
statsd-ruby (~> 1.4, >= 1.4.0)
|
||||
oj (3.16.3)
|
||||
oj (3.16.4)
|
||||
bigdecimal (>= 3.0)
|
||||
omniauth (2.1.2)
|
||||
hashie (>= 3.4.6)
|
||||
|
@ -489,7 +489,7 @@ GEM
|
|||
opentelemetry-api (1.2.5)
|
||||
opentelemetry-common (0.20.1)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-exporter-otlp (0.26.3)
|
||||
opentelemetry-exporter-otlp (0.27.0)
|
||||
google-protobuf (~> 3.14)
|
||||
googleapis-common-protos-types (~> 1.3)
|
||||
opentelemetry-api (~> 1.1)
|
||||
|
@ -498,6 +498,10 @@ GEM
|
|||
opentelemetry-semantic_conventions
|
||||
opentelemetry-helpers-sql-obfuscation (0.1.0)
|
||||
opentelemetry-common (~> 0.20)
|
||||
opentelemetry-instrumentation-action_mailer (0.1.0)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-instrumentation-active_support (~> 0.1)
|
||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||
opentelemetry-instrumentation-action_pack (0.9.0)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||
|
@ -551,8 +555,9 @@ GEM
|
|||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-common (~> 0.20.0)
|
||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||
opentelemetry-instrumentation-rails (0.30.1)
|
||||
opentelemetry-instrumentation-rails (0.30.2)
|
||||
opentelemetry-api (~> 1.0)
|
||||
opentelemetry-instrumentation-action_mailer (~> 0.1.0)
|
||||
opentelemetry-instrumentation-action_pack (~> 0.9.0)
|
||||
opentelemetry-instrumentation-action_view (~> 0.7.0)
|
||||
opentelemetry-instrumentation-active_job (~> 0.7.0)
|
||||
|
@ -578,15 +583,15 @@ GEM
|
|||
opentelemetry-api (~> 1.0)
|
||||
orm_adapter (0.5.0)
|
||||
ox (2.14.18)
|
||||
parallel (1.24.0)
|
||||
parser (3.3.2.0)
|
||||
parallel (1.25.1)
|
||||
parser (3.3.3.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
|
||||
|
@ -634,20 +639,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 +667,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,15 +691,15 @@ 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)
|
||||
responders (3.1.1)
|
||||
actionpack (>= 5.2)
|
||||
railties (>= 5.2)
|
||||
rexml (3.2.8)
|
||||
strscan (>= 3.0.9)
|
||||
rexml (3.3.0)
|
||||
strscan
|
||||
rotp (6.3.0)
|
||||
rouge (4.2.1)
|
||||
rpam2 (4.0.2)
|
||||
|
@ -739,9 +744,7 @@ GEM
|
|||
unicode-display_width (>= 2.4.0, < 3.0)
|
||||
rubocop-ast (1.31.3)
|
||||
parser (>= 3.3.1.0)
|
||||
rubocop-capybara (2.20.0)
|
||||
rubocop (~> 1.41)
|
||||
rubocop-factory_bot (2.25.1)
|
||||
rubocop-capybara (2.21.0)
|
||||
rubocop (~> 1.41)
|
||||
rubocop-performance (1.21.0)
|
||||
rubocop (>= 1.48.1, < 2.0)
|
||||
|
@ -751,25 +754,25 @@ GEM
|
|||
rack (>= 1.1)
|
||||
rubocop (>= 1.33.0, < 2.0)
|
||||
rubocop-ast (>= 1.31.1, < 2.0)
|
||||
rubocop-rspec (2.29.2)
|
||||
rubocop (~> 1.40)
|
||||
rubocop-capybara (~> 2.17)
|
||||
rubocop-factory_bot (~> 2.22)
|
||||
rubocop-rspec_rails (~> 2.28)
|
||||
rubocop-rspec_rails (2.28.3)
|
||||
rubocop (~> 1.40)
|
||||
rubocop-rspec (3.0.1)
|
||||
rubocop (~> 1.61)
|
||||
rubocop-rspec_rails (2.30.0)
|
||||
rubocop (~> 1.61)
|
||||
rubocop-rspec (~> 3, >= 3.0.1)
|
||||
ruby-prof (1.7.0)
|
||||
ruby-progressbar (1.13.0)
|
||||
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)
|
||||
fugit (~> 1.1, >= 1.1.6)
|
||||
safety_net_attestation (0.4.0)
|
||||
jwt (~> 2.0)
|
||||
sanitize (6.1.0)
|
||||
sanitize (6.1.1)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.12.0)
|
||||
scenic (1.8.0)
|
||||
|
@ -895,7 +898,7 @@ GEM
|
|||
xorcist (1.1.3)
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
zeitwerk (2.6.14)
|
||||
zeitwerk (2.6.15)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
@ -945,7 +948,7 @@ DEPENDENCIES
|
|||
htmlentities (~> 4.3)
|
||||
http (~> 5.2.0)
|
||||
http_accept_language (~> 2.1)
|
||||
httplog (~> 1.6.2)
|
||||
httplog (~> 1.7.0)
|
||||
i18n
|
||||
i18n-tasks (~> 1.0)
|
||||
idn-ruby
|
||||
|
@ -976,7 +979,7 @@ DEPENDENCIES
|
|||
omniauth-saml (~> 2.0)
|
||||
omniauth_openid_connect (~> 0.6.1)
|
||||
opentelemetry-api (~> 1.2.5)
|
||||
opentelemetry-exporter-otlp (~> 0.26.3)
|
||||
opentelemetry-exporter-otlp (~> 0.27.0)
|
||||
opentelemetry-instrumentation-active_job (~> 0.7.1)
|
||||
opentelemetry-instrumentation-active_model_serializers (~> 0.20.1)
|
||||
opentelemetry-instrumentation-concurrent_ruby (~> 0.21.2)
|
||||
|
@ -1021,8 +1024,10 @@ DEPENDENCIES
|
|||
rubocop-performance
|
||||
rubocop-rails
|
||||
rubocop-rspec
|
||||
rubocop-rspec_rails
|
||||
ruby-prof
|
||||
ruby-progressbar (~> 1.13)
|
||||
ruby-vips (~> 2.2)
|
||||
rubyzip (~> 2.3)
|
||||
sanitize (~> 6.0)
|
||||
scenic (~> 1.7)
|
||||
|
@ -1050,7 +1055,7 @@ DEPENDENCIES
|
|||
xorcist (~> 1.1)
|
||||
|
||||
RUBY VERSION
|
||||
ruby 3.3.1p55
|
||||
ruby 3.3.2p78
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.9
|
||||
2.5.11
|
||||
|
|
68
README.md
68
README.md
|
@ -62,7 +62,7 @@ Mastodon acts as an OAuth2 provider, so 3rd party apps can use the REST and Stre
|
|||
### Tech stack
|
||||
|
||||
- **Ruby on Rails** powers the REST API and other web pages
|
||||
- **React.js** and Redux are used for the dynamic parts of the interface
|
||||
- **React.js** and **Redux** are used for the dynamic parts of the interface
|
||||
- **Node.js** powers the streaming API
|
||||
|
||||
### Requirements
|
||||
|
@ -72,7 +72,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
|
||||
|
||||
|
@ -86,40 +86,51 @@ A **Vagrant** configuration is included for development purposes. To use it, com
|
|||
- Run `vagrant ssh -c "cd /vagrant && bin/dev"`
|
||||
- Open `http://mastodon.local` in your browser
|
||||
|
||||
### MacOS
|
||||
### macOS
|
||||
|
||||
To set up **MacOS** for native development, complete the following steps:
|
||||
To set up **macOS** for native development, complete the following steps:
|
||||
|
||||
- Use a Ruby version manager to install the specified version from `.ruby-version`
|
||||
- Run `bundle` to install required gems
|
||||
- Run `brew install postgresql@14 redis imagemagick libidn` to install required dependencies
|
||||
- Navigate to Mastodon's root directory and run `brew install nvm` then `nvm use` to use the version from `.nvmrc`
|
||||
- Run `yarn` to install required packages
|
||||
- Run `corepack enable && corepack prepare`
|
||||
- Run `RAILS_ENV=development bundle exec rails db:setup`
|
||||
- Finally, run `bin/dev` which will launch the local services via `overmind` (if installed) or `foreman`
|
||||
- Install [Homebrew] and run `brew install postgresql@14 redis imagemagick
|
||||
libidn nvm` to install the required project dependencies
|
||||
- Use a Ruby version manager to activate the ruby in `.ruby-version` and run
|
||||
`nvm use` to activate the node version from `.nvmrc`
|
||||
- Run the `bin/setup` script, which will install the required ruby gems and node
|
||||
packages and prepare the database for local development
|
||||
- Finally, run the `bin/dev` script which will launch services via `overmind`
|
||||
(if installed) or `foreman`
|
||||
|
||||
### 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.
|
||||
|
||||
- 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`
|
||||
- Finally, run `docker compose -f .devcontainer/docker-compose.yml exec app bin/dev`
|
||||
For local development, install and launch [Docker], and run:
|
||||
|
||||
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).
|
||||
```shell
|
||||
docker compose -f .devcontainer/compose.yaml up -d
|
||||
docker compose -f .devcontainer/compose.yaml exec app bin/setup
|
||||
docker compose -f .devcontainer/compose.yaml exec app bin/dev
|
||||
```
|
||||
|
||||
### Dev Containers
|
||||
|
||||
Within IDEs that support the [Development Containers] specification, start the
|
||||
"Mastodon on local machine" container from the editor. The necessary `docker
|
||||
compose` commands to build and setup the container should run automatically. For
|
||||
**Visual Studio Code** this requires installing the [Dev Container extension].
|
||||
|
||||
### GitHub Codespaces
|
||||
|
||||
To get you coding in just a few minutes, GitHub Codespaces provides a web-based version of Visual Studio Code and a cloud-hosted development environment fully configured with the software needed for this project..
|
||||
[GitHub Codespaces] provides a web-based version of VS Code and a cloud hosted
|
||||
development environment configured with the software needed for this project.
|
||||
|
||||
- Click this button to create a new codespace:<br>
|
||||
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=52281283&devcontainer_path=.devcontainer%2Fcodespaces%2Fdevcontainer.json)
|
||||
- Wait for the environment to build. This will take a few minutes.
|
||||
- When the editor is ready, run `bin/dev` in the terminal.
|
||||
- After a few seconds, a popup will appear with a button labeled _Open in Browser_. This will open Mastodon.
|
||||
- On the _Ports_ tab, right click on the “stream” row and select _Port visibility_ → _Public_.
|
||||
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)][codespace]
|
||||
|
||||
- Click the button to create a new codespace, and confirm the options
|
||||
- Wait for the environment to build (takes a few minutes)
|
||||
- When the editor is ready, run `bin/dev` in the terminal
|
||||
- Wait for an _Open in Browser_ prompt. This will open Mastodon
|
||||
- On the _Ports_ tab "stream" setting change _Port visibility_ → _Public_
|
||||
|
||||
## Contributing
|
||||
|
||||
|
@ -138,3 +149,10 @@ This program is free software: you can redistribute it and/or modify it under th
|
|||
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
[codespace]: https://codespaces.new/mastodon/mastodon?quickstart=1&devcontainer_path=.devcontainer%2Fcodespaces%2Fdevcontainer.json
|
||||
[Dev Container extension]: https://containers.dev/supporting#dev-containers
|
||||
[Development Containers]: https://containers.dev/supporting
|
||||
[Docker]: https://docs.docker.com
|
||||
[GitHub Codespaces]: https://docs.github.com/en/codespaces
|
||||
[Homebrew]: https://brew.sh
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
If you believe you've identified a security vulnerability in Mastodon (a bug that allows something to happen that shouldn't be possible), you can either:
|
||||
|
||||
- open a [Github security issue on the Mastodon project](https://github.com/mastodon/mastodon/security/advisories/new)
|
||||
- open a [GitHub security issue on the Mastodon project](https://github.com/mastodon/mastodon/security/advisories/new)
|
||||
- reach us at <security@joinmastodon.org>
|
||||
|
||||
You should _not_ report such issues on public GitHub issues or in other public spaces to give us time to publish a fix for the issue without exposing Mastodon's users to increased risk.
|
||||
|
|
6
Vagrantfile
vendored
6
Vagrantfile
vendored
|
@ -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
|
||||
|
|
|
@ -4,6 +4,18 @@ module Admin
|
|||
class DomainBlocksController < BaseController
|
||||
before_action :set_domain_block, only: [:destroy, :edit, :update]
|
||||
|
||||
PERMITTED_PARAMS = %i(
|
||||
domain
|
||||
obfuscate
|
||||
private_comment
|
||||
public_comment
|
||||
reject_media
|
||||
reject_reports
|
||||
severity
|
||||
).freeze
|
||||
|
||||
PERMITTED_UPDATE_PARAMS = PERMITTED_PARAMS.without(:domain).freeze
|
||||
|
||||
def batch
|
||||
authorize :domain_block, :create?
|
||||
@form = Form::DomainBlockBatch.new(form_domain_block_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
|
@ -88,11 +100,17 @@ module Admin
|
|||
end
|
||||
|
||||
def update_params
|
||||
params.require(:domain_block).permit(:severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate)
|
||||
params
|
||||
.require(:domain_block)
|
||||
.slice(*PERMITTED_UPDATE_PARAMS)
|
||||
.permit(*PERMITTED_UPDATE_PARAMS)
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate)
|
||||
params
|
||||
.require(:domain_block)
|
||||
.slice(*PERMITTED_PARAMS)
|
||||
.permit(*PERMITTED_PARAMS)
|
||||
end
|
||||
|
||||
def form_domain_block_batch_params
|
||||
|
|
|
@ -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!
|
||||
|
||||
|
|
|
@ -13,6 +13,13 @@ class Api::V1::Admin::TagsController < Api::BaseController
|
|||
|
||||
LIMIT = 100
|
||||
|
||||
PERMITTED_PARAMS = %i(
|
||||
display_name
|
||||
listable
|
||||
trendable
|
||||
usable
|
||||
).freeze
|
||||
|
||||
def index
|
||||
authorize :tag, :index?
|
||||
render json: @tags, each_serializer: REST::Admin::TagSerializer
|
||||
|
@ -40,7 +47,9 @@ class Api::V1::Admin::TagsController < Api::BaseController
|
|||
end
|
||||
|
||||
def tag_params
|
||||
params.permit(:display_name, :trendable, :usable, :listable)
|
||||
params
|
||||
.slice(*PERMITTED_PARAMS)
|
||||
.permit(*PERMITTED_PARAMS)
|
||||
end
|
||||
|
||||
def next_path
|
||||
|
|
52
app/controllers/api/v1/timelines/link_controller.rb
Normal file
52
app/controllers/api/v1/timelines/link_controller.rb
Normal file
|
@ -0,0 +1,52 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Timelines::LinkController < Api::V1::Timelines::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: :show, if: :require_auth?
|
||||
before_action :set_preview_card
|
||||
before_action :set_statuses
|
||||
|
||||
PERMITTED_PARAMS = %i(
|
||||
url
|
||||
limit
|
||||
).freeze
|
||||
|
||||
def show
|
||||
cache_if_unauthenticated!
|
||||
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def require_auth?
|
||||
!Setting.timeline_preview
|
||||
end
|
||||
|
||||
def set_preview_card
|
||||
@preview_card = PreviewCard.joins(:trend).merge(PreviewCardTrend.allowed).find_by!(url: params[:url])
|
||||
end
|
||||
|
||||
def set_statuses
|
||||
@statuses = @preview_card.nil? ? [] : preload_collection(link_timeline_statuses, Status)
|
||||
end
|
||||
|
||||
def link_timeline_statuses
|
||||
link_feed.get(
|
||||
limit_param(DEFAULT_STATUSES_LIMIT),
|
||||
params[:max_id],
|
||||
params[:since_id],
|
||||
params[:min_id]
|
||||
)
|
||||
end
|
||||
|
||||
def link_feed
|
||||
LinkFeed.new(@preview_card, current_account)
|
||||
end
|
||||
|
||||
def next_path
|
||||
api_v1_timelines_link_url next_path_params
|
||||
end
|
||||
|
||||
def prev_path
|
||||
api_v1_timelines_link_url prev_path_params
|
||||
end
|
||||
end
|
91
app/controllers/api/v2_alpha/notifications_controller.rb
Normal file
91
app/controllers/api/v2_alpha/notifications_controller.rb
Normal file
|
@ -0,0 +1,91 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V2Alpha::NotificationsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:notifications' }, except: [:clear, :dismiss]
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:notifications' }, only: [:clear, :dismiss]
|
||||
before_action :require_user!
|
||||
after_action :insert_pagination_headers, only: :index
|
||||
|
||||
DEFAULT_NOTIFICATIONS_LIMIT = 40
|
||||
|
||||
def index
|
||||
with_read_replica do
|
||||
@notifications = load_notifications
|
||||
@group_metadata = load_group_metadata
|
||||
@relationships = StatusRelationshipsPresenter.new(target_statuses_from_notifications, current_user&.account_id)
|
||||
end
|
||||
|
||||
render json: @notifications.map { |notification| NotificationGroup.from_notification(notification) }, each_serializer: REST::NotificationGroupSerializer, relationships: @relationships, group_metadata: @group_metadata
|
||||
end
|
||||
|
||||
def show
|
||||
@notification = current_account.notifications.without_suspended.find_by!(group_key: params[:id])
|
||||
render json: NotificationGroup.from_notification(@notification), serializer: REST::NotificationGroupSerializer
|
||||
end
|
||||
|
||||
def clear
|
||||
current_account.notifications.delete_all
|
||||
render_empty
|
||||
end
|
||||
|
||||
def dismiss
|
||||
current_account.notifications.where(group_key: params[:id]).destroy_all
|
||||
render_empty
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_notifications
|
||||
notifications = browserable_account_notifications.includes(from_account: [:account_stat, :user]).to_a_grouped_paginated_by_id(
|
||||
limit_param(DEFAULT_NOTIFICATIONS_LIMIT),
|
||||
params_slice(:max_id, :since_id, :min_id)
|
||||
)
|
||||
|
||||
Notification.preload_cache_collection_target_statuses(notifications) do |target_statuses|
|
||||
preload_collection(target_statuses, Status)
|
||||
end
|
||||
end
|
||||
|
||||
def load_group_metadata
|
||||
return {} if @notifications.empty?
|
||||
|
||||
browserable_account_notifications
|
||||
.where(group_key: @notifications.filter_map(&:group_key))
|
||||
.where(id: (@notifications.last.id)..(@notifications.first.id))
|
||||
.group(:group_key)
|
||||
.pluck(:group_key, 'min(notifications.id) as min_id', 'max(notifications.id) as max_id', 'max(notifications.created_at) as latest_notification_at')
|
||||
.to_h { |group_key, min_id, max_id, latest_notification_at| [group_key, { min_id: min_id, max_id: max_id, latest_notification_at: latest_notification_at }] }
|
||||
end
|
||||
|
||||
def browserable_account_notifications
|
||||
current_account.notifications.without_suspended.browserable(
|
||||
types: Array(browserable_params[:types]),
|
||||
exclude_types: Array(browserable_params[:exclude_types]),
|
||||
include_filtered: truthy_param?(:include_filtered)
|
||||
)
|
||||
end
|
||||
|
||||
def target_statuses_from_notifications
|
||||
@notifications.filter_map(&:target_status)
|
||||
end
|
||||
|
||||
def next_path
|
||||
api_v2_alpha_notifications_url pagination_params(max_id: pagination_max_id) unless @notifications.empty?
|
||||
end
|
||||
|
||||
def prev_path
|
||||
api_v2_alpha_notifications_url pagination_params(min_id: pagination_since_id) unless @notifications.empty?
|
||||
end
|
||||
|
||||
def pagination_collection
|
||||
@notifications
|
||||
end
|
||||
|
||||
def browserable_params
|
||||
params.permit(:include_filtered, types: [], exclude_types: [])
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit, :types, :exclude_types, :include_filtered).permit(:limit, :include_filtered, types: [], exclude_types: []).merge(core_params)
|
||||
end
|
||||
end
|
|
@ -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
|
||||
|
||||
|
|
|
@ -15,15 +15,15 @@ module Admin::ActionLogsHelper
|
|||
link_to log.human_identifier, admin_roles_path(log.target_id)
|
||||
when 'Report'
|
||||
link_to "##{log.human_identifier.presence || log.target_id}", admin_report_path(log.target_id)
|
||||
when 'DomainBlock', 'DomainAllow', 'EmailDomainBlock', 'UnavailableDomain'
|
||||
link_to log.human_identifier, "https://#{log.human_identifier.presence}"
|
||||
when 'Instance', 'DomainBlock', 'DomainAllow', 'UnavailableDomain'
|
||||
log.human_identifier.present? ? link_to(log.human_identifier, admin_instance_path(log.human_identifier)) : I18n.t('admin.action_logs.unavailable_instance')
|
||||
when 'Status'
|
||||
link_to log.human_identifier, log.permalink
|
||||
when 'AccountWarning'
|
||||
link_to log.human_identifier, disputes_strike_path(log.target_id)
|
||||
when 'Announcement'
|
||||
link_to truncate(log.human_identifier), edit_admin_announcement_path(log.target_id)
|
||||
when 'IpBlock', 'Instance', 'CustomEmoji'
|
||||
when 'IpBlock', 'EmailDomainBlock', 'CustomEmoji'
|
||||
log.human_identifier
|
||||
when 'CanonicalEmailBlock'
|
||||
content_tag(:samp, (log.human_identifier.presence || '')[0...7], title: log.human_identifier)
|
||||
|
|
|
@ -68,13 +68,17 @@ export function importFetchedStatuses(statuses) {
|
|||
status.filtered.forEach(result => pushUnique(filters, result.filter));
|
||||
}
|
||||
|
||||
if (status.reblog && status.reblog.id) {
|
||||
if (status.reblog?.id) {
|
||||
processStatus(status.reblog);
|
||||
}
|
||||
|
||||
if (status.poll && status.poll.id) {
|
||||
if (status.poll?.id) {
|
||||
pushUnique(polls, normalizePoll(status.poll, getState().getIn(['polls', status.poll.id])));
|
||||
}
|
||||
|
||||
if (status.card?.author_account) {
|
||||
pushUnique(accounts, status.card.author_account);
|
||||
}
|
||||
}
|
||||
|
||||
statuses.forEach(processStatus);
|
||||
|
|
|
@ -36,6 +36,10 @@ export function normalizeStatus(status, normalOldStatus) {
|
|||
normalStatus.poll = status.poll.id;
|
||||
}
|
||||
|
||||
if (status.card?.author_account) {
|
||||
normalStatus.card = { ...status.card, author_account: status.card.author_account.id };
|
||||
}
|
||||
|
||||
if (status.filtered) {
|
||||
normalStatus.filtered = status.filtered.map(normalizeFilterResult);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import api, { getLinks } from '../api';
|
||||
|
||||
import { importFetchedStatuses } from './importer';
|
||||
import { importFetchedStatuses, importFetchedAccounts } from './importer';
|
||||
|
||||
export const TRENDS_TAGS_FETCH_REQUEST = 'TRENDS_TAGS_FETCH_REQUEST';
|
||||
export const TRENDS_TAGS_FETCH_SUCCESS = 'TRENDS_TAGS_FETCH_SUCCESS';
|
||||
|
@ -49,8 +49,11 @@ export const fetchTrendingLinks = () => (dispatch) => {
|
|||
dispatch(fetchTrendingLinksRequest());
|
||||
|
||||
api()
|
||||
.get('/api/v1/trends/links')
|
||||
.then(({ data }) => dispatch(fetchTrendingLinksSuccess(data)))
|
||||
.get('/api/v1/trends/links', { params: { limit: 20 } })
|
||||
.then(({ data }) => {
|
||||
dispatch(importFetchedAccounts(data.map(link => link.author_account).filter(account => !!account)));
|
||||
dispatch(fetchTrendingLinksSuccess(data));
|
||||
})
|
||||
.catch(err => dispatch(fetchTrendingLinksFail(err)));
|
||||
};
|
||||
|
||||
|
|
19
app/javascript/mastodon/components/more_from_author.jsx
Normal file
19
app/javascript/mastodon/components/more_from_author.jsx
Normal file
|
@ -0,0 +1,19 @@
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { AuthorLink } from 'mastodon/features/explore/components/author_link';
|
||||
|
||||
export const MoreFromAuthor = ({ accountId }) => (
|
||||
<div className='more-from-author'>
|
||||
<svg viewBox='0 0 79 79' className='logo logo--icon' role='img'>
|
||||
<use xlinkHref='#logo-symbol-icon' />
|
||||
</svg>
|
||||
|
||||
<FormattedMessage id='link_preview.more_from_author' defaultMessage='More from {name}' values={{ name: <AuthorLink accountId={accountId} /> }} />
|
||||
</div>
|
||||
);
|
||||
|
||||
MoreFromAuthor.propTypes = {
|
||||
accountId: PropTypes.string.isRequired,
|
||||
};
|
|
@ -42,10 +42,12 @@ class ServerBanner extends PureComponent {
|
|||
return (
|
||||
<div className='server-banner'>
|
||||
<div className='server-banner__introduction'>
|
||||
<FormattedMessage id='server_banner.introduction' defaultMessage='{domain} is part of the decentralized social network powered by {mastodon}.' values={{ domain: <strong>{domain}</strong>, mastodon: <a href='https://joinmastodon.org' target='_blank'>Mastodon</a> }} />
|
||||
<FormattedMessage id='server_banner.is_one_of_many' defaultMessage='{domain} is one of the many independent Mastodon servers you can use to participate in the fediverse.' values={{ domain: <strong>{domain}</strong>, mastodon: <a href='https://joinmastodon.org' target='_blank'>Mastodon</a> }} />
|
||||
</div>
|
||||
|
||||
<ServerHeroImage blurhash={server.getIn(['thumbnail', 'blurhash'])} src={server.getIn(['thumbnail', 'url'])} className='server-banner__hero' />
|
||||
<Link to='/about'>
|
||||
<ServerHeroImage blurhash={server.getIn(['thumbnail', 'blurhash'])} src={server.getIn(['thumbnail', 'url'])} className='server-banner__hero' />
|
||||
</Link>
|
||||
|
||||
<div className='server-banner__description'>
|
||||
{isLoading ? (
|
||||
|
@ -84,10 +86,6 @@ class ServerBanner extends PureComponent {
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr className='spacer' />
|
||||
|
||||
<Link className='button button--block button-secondary' to='/about'><FormattedMessage id='server_banner.learn_more' defaultMessage='Learn more' /></Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -199,6 +199,7 @@ class AccountTimeline extends ImmutablePureComponent {
|
|||
emptyMessage={emptyMessage}
|
||||
bindToDocument={!multiColumn}
|
||||
timelineId='account'
|
||||
withCounters
|
||||
/>
|
||||
</Column>
|
||||
);
|
||||
|
|
|
@ -110,18 +110,6 @@ class LanguageDropdownMenu extends PureComponent {
|
|||
}).map(result => result.obj);
|
||||
}
|
||||
|
||||
frequentlyUsed () {
|
||||
const { languages, value } = this.props;
|
||||
const current = languages.find(lang => lang[0] === value);
|
||||
const results = [];
|
||||
|
||||
if (current) {
|
||||
results.push(current);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
handleClick = e => {
|
||||
const value = e.currentTarget.getAttribute('data-index');
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { Avatar } from 'mastodon/components/avatar';
|
||||
import { useAppSelector } from 'mastodon/store';
|
||||
|
||||
export const AuthorLink = ({ accountId }) => {
|
||||
const account = useAppSelector(state => state.getIn(['accounts', accountId]));
|
||||
|
||||
return (
|
||||
<Link to={`/@${account.get('acct')}`} className='story__details__shared__author-link'>
|
||||
<Avatar account={account} size={16} />
|
||||
<bdi dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} />
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
AuthorLink.propTypes = {
|
||||
accountId: PropTypes.string.isRequired,
|
||||
};
|
|
@ -1,61 +1,89 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
import { useState, useCallback } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
|
||||
import { Blurhash } from 'mastodon/components/blurhash';
|
||||
import { accountsCountRenderer } from 'mastodon/components/hashtag';
|
||||
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
|
||||
import { ShortNumber } from 'mastodon/components/short_number';
|
||||
import { Skeleton } from 'mastodon/components/skeleton';
|
||||
|
||||
export default class Story extends PureComponent {
|
||||
import { AuthorLink } from './author_link';
|
||||
|
||||
static propTypes = {
|
||||
url: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
lang: PropTypes.string,
|
||||
publisher: PropTypes.string,
|
||||
publishedAt: PropTypes.string,
|
||||
author: PropTypes.string,
|
||||
sharedTimes: PropTypes.number,
|
||||
thumbnail: PropTypes.string,
|
||||
thumbnailDescription: PropTypes.string,
|
||||
blurhash: PropTypes.string,
|
||||
expanded: PropTypes.bool,
|
||||
};
|
||||
const sharesCountRenderer = (displayNumber, pluralReady) => (
|
||||
<FormattedMessage
|
||||
id='link_preview.shares'
|
||||
defaultMessage='{count, plural, one {{counter} post} other {{counter} posts}}'
|
||||
values={{
|
||||
count: pluralReady,
|
||||
counter: <strong>{displayNumber}</strong>,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
state = {
|
||||
thumbnailLoaded: false,
|
||||
};
|
||||
export const Story = ({
|
||||
url,
|
||||
title,
|
||||
lang,
|
||||
publisher,
|
||||
publishedAt,
|
||||
author,
|
||||
authorAccount,
|
||||
sharedTimes,
|
||||
thumbnail,
|
||||
thumbnailDescription,
|
||||
blurhash,
|
||||
expanded
|
||||
}) => {
|
||||
const [thumbnailLoaded, setThumbnailLoaded] = useState(false);
|
||||
|
||||
handleImageLoad = () => this.setState({ thumbnailLoaded: true });
|
||||
const handleImageLoad = useCallback(() => {
|
||||
setThumbnailLoaded(true);
|
||||
}, [setThumbnailLoaded]);
|
||||
|
||||
render () {
|
||||
const { expanded, url, title, lang, publisher, author, publishedAt, sharedTimes, thumbnail, thumbnailDescription, blurhash } = this.props;
|
||||
|
||||
const { thumbnailLoaded } = this.state;
|
||||
|
||||
return (
|
||||
<a className={classNames('story', { expanded })} href={url} target='blank' rel='noopener'>
|
||||
<div className='story__details'>
|
||||
<div className='story__details__publisher'>{publisher ? <span lang={lang}>{publisher}</span> : <Skeleton width={50} />}{publishedAt && <> · <RelativeTimestamp timestamp={publishedAt} /></>}</div>
|
||||
<div className='story__details__title' lang={lang}>{title ? title : <Skeleton />}</div>
|
||||
<div className='story__details__shared'>{author && <><FormattedMessage id='link_preview.author' defaultMessage='By {name}' values={{ name: <strong>{author}</strong> }} /> · </>}{typeof sharedTimes === 'number' ? <ShortNumber value={sharedTimes} renderer={accountsCountRenderer} /> : <Skeleton width={100} />}</div>
|
||||
return (
|
||||
<div className={classNames('story', { expanded })}>
|
||||
<div className='story__details'>
|
||||
<div className='story__details__publisher'>
|
||||
{publisher ? <span lang={lang}>{publisher}</span> : <Skeleton width={50} />}{publishedAt && <> · <RelativeTimestamp timestamp={publishedAt} /></>}
|
||||
</div>
|
||||
|
||||
<div className='story__thumbnail'>
|
||||
{thumbnail ? (
|
||||
<>
|
||||
<div className={classNames('story__thumbnail__preview', { 'story__thumbnail__preview--hidden': thumbnailLoaded })}><Blurhash hash={blurhash} /></div>
|
||||
<img src={thumbnail} onLoad={this.handleImageLoad} alt={thumbnailDescription} title={thumbnailDescription} lang={lang} />
|
||||
</>
|
||||
) : <Skeleton />}
|
||||
<a className='story__details__title' lang={lang} href={url} target='blank' rel='noopener'>
|
||||
{title ? title : <Skeleton />}
|
||||
</a>
|
||||
|
||||
<div className='story__details__shared'>
|
||||
{author ? <FormattedMessage id='link_preview.author' className='story__details__shared__author' defaultMessage='By {name}' values={{ name: authorAccount ? <AuthorLink accountId={authorAccount} /> : <strong>{author}</strong> }} /> : <span />}
|
||||
{typeof sharedTimes === 'number' ? <span className='story__details__shared__pill'><ShortNumber value={sharedTimes} renderer={sharesCountRenderer} /></span> : <Skeleton width='10ch' />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a className='story__thumbnail' href={url} target='blank' rel='noopener'>
|
||||
{thumbnail ? (
|
||||
<>
|
||||
<div className={classNames('story__thumbnail__preview', { 'story__thumbnail__preview--hidden': thumbnailLoaded })}><Blurhash hash={blurhash} /></div>
|
||||
<img src={thumbnail} onLoad={handleImageLoad} alt={thumbnailDescription} title={thumbnailDescription} lang={lang} />
|
||||
</>
|
||||
) : <Skeleton />}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
}
|
||||
Story.propTypes = {
|
||||
url: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
lang: PropTypes.string,
|
||||
publisher: PropTypes.string,
|
||||
publishedAt: PropTypes.string,
|
||||
author: PropTypes.string,
|
||||
authorAccount: PropTypes.string,
|
||||
sharedTimes: PropTypes.number,
|
||||
thumbnail: PropTypes.string,
|
||||
thumbnailDescription: PropTypes.string,
|
||||
blurhash: PropTypes.string,
|
||||
expanded: PropTypes.bool,
|
||||
};
|
||||
|
|
|
@ -13,7 +13,7 @@ import { DismissableBanner } from 'mastodon/components/dismissable_banner';
|
|||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
||||
import Story from './components/story';
|
||||
import { Story } from './components/story';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
links: state.getIn(['trends', 'links', 'items']),
|
||||
|
@ -75,6 +75,7 @@ class Links extends PureComponent {
|
|||
publisher={link.get('provider_name')}
|
||||
publishedAt={link.get('published_at')}
|
||||
author={link.get('author_name')}
|
||||
authorAccount={link.getIn(['author_account', 'id'])}
|
||||
sharedTimes={link.getIn(['history', 0, 'accounts']) * 1 + link.getIn(['history', 1, 'accounts']) * 1}
|
||||
thumbnail={link.get('image')}
|
||||
thumbnailDescription={link.get('image_description')}
|
||||
|
|
|
@ -6,7 +6,6 @@ import { PureComponent } from 'react';
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
|
||||
import Immutable from 'immutable';
|
||||
|
@ -15,9 +14,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
import DescriptionIcon from '@/material-icons/400-24px/description-fill.svg?react';
|
||||
import OpenInNewIcon from '@/material-icons/400-24px/open_in_new.svg?react';
|
||||
import PlayArrowIcon from '@/material-icons/400-24px/play_arrow-fill.svg?react';
|
||||
import { Avatar } from 'mastodon/components/avatar';
|
||||
import { Blurhash } from 'mastodon/components/blurhash';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { MoreFromAuthor } from 'mastodon/components/more_from_author';
|
||||
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
|
||||
import { useBlurhash } from 'mastodon/initial_state';
|
||||
|
||||
|
@ -59,20 +58,6 @@ const addAutoPlay = html => {
|
|||
return html;
|
||||
};
|
||||
|
||||
const MoreFromAuthor = ({ author }) => (
|
||||
<div className='more-from-author'>
|
||||
<svg viewBox='0 0 79 79' className='logo logo--icon' role='img'>
|
||||
<use xlinkHref='#logo-symbol-icon' />
|
||||
</svg>
|
||||
|
||||
<FormattedMessage id='link_preview.more_from_author' defaultMessage='More from {name}' values={{ name: <Link to={`/@${author.get('acct')}`}><Avatar account={author} size={16} /> {author.get('display_name')}</Link> }} />
|
||||
</div>
|
||||
);
|
||||
|
||||
MoreFromAuthor.propTypes = {
|
||||
author: ImmutablePropTypes.map,
|
||||
};
|
||||
|
||||
export default class Card extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -259,7 +244,7 @@ export default class Card extends PureComponent {
|
|||
{description}
|
||||
</a>
|
||||
|
||||
{showAuthor && <MoreFromAuthor author={card.get('author_account')} />}
|
||||
{showAuthor && <MoreFromAuthor accountId={card.get('author_account')} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -22,7 +22,8 @@ const SignInBanner = () => {
|
|||
if (sso_redirect) {
|
||||
return (
|
||||
<div className='sign-in-banner'>
|
||||
<p><FormattedMessage id='sign_in_banner.text' defaultMessage='Login to follow profiles or hashtags, favorite, share and reply to posts. You can also interact from your account on a different server.' /></p>
|
||||
<p><strong><FormattedMessage id='sign_in_banner.mastodon_is' defaultMessage="Mastodon is the best way to keep up with what's happening." /></strong></p>
|
||||
<p><FormattedMessage id='sign_in_banner.follow_anyone' defaultMessage='Follow anyone across the fediverse and see it all in chronological order. No algorithms, ads, or clickbait in sight.' /></p>
|
||||
<a href={sso_redirect} data-method='post' className='button button--block button-tertiary'><FormattedMessage id='sign_in_banner.sso_redirect' defaultMessage='Login or Register' /></a>
|
||||
</div>
|
||||
);
|
||||
|
@ -44,7 +45,8 @@ const SignInBanner = () => {
|
|||
|
||||
return (
|
||||
<div className='sign-in-banner'>
|
||||
<p><FormattedMessage id='sign_in_banner.text' defaultMessage='Login to follow profiles or hashtags, favorite, share and reply to posts. You can also interact from your account on a different server.' /></p>
|
||||
<p><strong><FormattedMessage id='sign_in_banner.mastodon_is' defaultMessage="Mastodon is the best way to keep up with what's happening." /></strong></p>
|
||||
<p><FormattedMessage id='sign_in_banner.follow_anyone' defaultMessage='Follow anyone across the fediverse and see it all in chronological order. No algorithms, ads, or clickbait in sight.' /></p>
|
||||
{signupButton}
|
||||
<a href='/auth/sign_in' className='button button--block button-tertiary'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Login' /></a>
|
||||
</div>
|
||||
|
|
|
@ -225,7 +225,11 @@
|
|||
"domain_pill.their_username": "مُعرّفُهم الفريد على الخادم. من الممكن العثور على مستخدمين بنفس اسم المستخدم على خوادم مختلفة.",
|
||||
"domain_pill.username": "اسم المستخدم",
|
||||
"domain_pill.whats_in_a_handle": "ما المقصود بالمُعرِّف؟",
|
||||
"domain_pill.who_they_are": "بما أن المعالجات تقول من هو الشخص ومكان وجوده، يمكنك التفاعل مع الناس عبر الشبكة الاجتماعية لـ <button>ActivityPub-Power منصات</button>.",
|
||||
"domain_pill.who_you_are": "لأن معالجتك تقول من أنت ومكان وجودك، يمكن الناس التفاعل معك عبر الشبكة الاجتماعية لـ <button>ActivityPub-Power منصات</button>.",
|
||||
"domain_pill.your_handle": "عنوانك الكامل:",
|
||||
"domain_pill.your_server": "منزلك الرقمي، حيث تعيش جميع مشاركاتك. لا تحب هذا؟ إنقل الخوادم في أي وقت واخضر متابعينك أيضًا.",
|
||||
"domain_pill.your_username": "معرفك الفريد على هذا الخادم. من الممكن العثور على مستخدمين بنفس إسم المستخدم على خوادم مختلفة.",
|
||||
"embed.instructions": "يمكنكم إدماج هذا المنشور على موقعكم الإلكتروني عن طريق نسخ الشفرة أدناه.",
|
||||
"embed.preview": "إليك ما سيبدو عليه:",
|
||||
"emoji_button.activity": "الأنشطة",
|
||||
|
@ -262,6 +266,7 @@
|
|||
"empty_column.list": "هذه القائمة فارغة مؤقتا و لكن سوف تمتلئ تدريجيا عندما يبدأ الأعضاء المُنتَمين إليها بنشر منشورات.",
|
||||
"empty_column.lists": "ليس عندك أية قائمة بعد. سوف تظهر قوائمك هنا إن قمت بإنشاء واحدة.",
|
||||
"empty_column.mutes": "لم تقم بكتم أي مستخدم بعد.",
|
||||
"empty_column.notification_requests": "لا يوجد شيء هنا. عندما تتلقى إشعارات جديدة، سوف تظهر هنا وفقًا لإعداداتك.",
|
||||
"empty_column.notifications": "لم تتلق أي إشعار بعدُ. تفاعل مع المستخدمين الآخرين لإنشاء محادثة.",
|
||||
"empty_column.public": "لا يوجد أي شيء هنا! قم بنشر شيء ما للعامة، أو اتبع المستخدمين الآخرين المتواجدين على الخوادم الأخرى لملء خيط المحادثات",
|
||||
"error.unexpected_crash.explanation": "نظرا لوجود خطأ في التعليمات البرمجية أو مشكلة توافق مع المتصفّح، تعذر عرض هذه الصفحة بشكل صحيح.",
|
||||
|
@ -292,6 +297,8 @@
|
|||
"filter_modal.select_filter.subtitle": "استخدم فئة موجودة أو قم بإنشاء فئة جديدة",
|
||||
"filter_modal.select_filter.title": "تصفية هذا المنشور",
|
||||
"filter_modal.title.status": "تصفية منشور",
|
||||
"filtered_notifications_banner.mentions": "{count, plural, one {إشارة} two {إشارتين} few {# إشارات} other {# إشارة}}",
|
||||
"filtered_notifications_banner.pending_requests": "إشعارات من {count, plural, zero {}=0 {لا أحد} one {شخص واحد قد تعرفه} two {شخصين قد تعرفهما} few {# أشخاص قد تعرفهم} many {# شخص قد تعرفهم} other {# شخص قد تعرفهم}}",
|
||||
"filtered_notifications_banner.title": "الإشعارات المصفاة",
|
||||
"firehose.all": "الكل",
|
||||
"firehose.local": "هذا الخادم",
|
||||
|
@ -301,6 +308,8 @@
|
|||
"follow_requests.unlocked_explanation": "حتى وإن كان حسابك غير مقفل، يعتقد فريق {domain} أنك قد ترغب في مراجعة طلبات المتابعة من هذه الحسابات يدوياً.",
|
||||
"follow_suggestions.curated_suggestion": "اختيار الموظفين",
|
||||
"follow_suggestions.dismiss": "لا تُظهرها مجدّدًا",
|
||||
"follow_suggestions.featured_longer": "مختار يدوياً من قِبل فريق {domain}",
|
||||
"follow_suggestions.friends_of_friends_longer": "مشهور بين الأشخاص الذين تتابعهم",
|
||||
"follow_suggestions.hints.featured": "تم اختيار هذا الملف الشخصي يدوياً من قبل فريق {domain}.",
|
||||
"follow_suggestions.hints.friends_of_friends": "هذا الملف الشخصي مشهور بين الأشخاص الذين تتابعهم.",
|
||||
"follow_suggestions.hints.most_followed": "هذا الملف الشخصي هو واحد من الأكثر متابعة على {domain}.",
|
||||
|
@ -405,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": "احذف القائمة",
|
||||
|
@ -465,10 +475,13 @@
|
|||
"notification.follow_request": "لقد طلب {name} متابعتك",
|
||||
"notification.mention": "{name} ذكرك",
|
||||
"notification.moderation-warning.learn_more": "اعرف المزيد",
|
||||
"notification.moderation_warning": "لقد تلقيت تحذيرًا بالإشراف",
|
||||
"notification.moderation_warning.action_delete_statuses": "تم إزالة بعض مشاركاتك.",
|
||||
"notification.moderation_warning.action_disable": "تم تعطيل حسابك.",
|
||||
"notification.moderation_warning.action_mark_statuses_as_sensitive": "بعض من منشوراتك تم تصنيفها على أنها حساسة.",
|
||||
"notification.moderation_warning.action_none": "لقد تلقى حسابك تحذيرا بالإشراف.",
|
||||
"notification.moderation_warning.action_sensitive": "سيتم وضع علامة على منشوراتك على أنها حساسة من الآن فصاعدا.",
|
||||
"notification.moderation_warning.action_silence": "لقد تم تقييد حسابك.",
|
||||
"notification.moderation_warning.action_suspend": "لقد تم تعليق حسابك.",
|
||||
"notification.own_poll": "انتهى استطلاعك للرأي",
|
||||
"notification.poll": "لقد انتهى استطلاع رأي شاركتَ فيه",
|
||||
|
|
|
@ -308,6 +308,8 @@
|
|||
"follow_requests.unlocked_explanation": "Ваш акаўнт не схаваны, аднак прадстаўнікі {domain} палічылі, што вы можаце захацець праглядзець запыты на падпіску з гэтых профіляў уручную.",
|
||||
"follow_suggestions.curated_suggestion": "Выбар адміністрацыі",
|
||||
"follow_suggestions.dismiss": "Не паказваць зноў",
|
||||
"follow_suggestions.featured_longer": "Адабраныя камандай {domain} уручную",
|
||||
"follow_suggestions.friends_of_friends_longer": "Папулярнае сярод людзей, на якіх Вы падпісаны",
|
||||
"follow_suggestions.hints.featured": "Гэты профіль быў выбраны ўручную камандай {domain}.",
|
||||
"follow_suggestions.hints.friends_of_friends": "Гэты профіль папулярны сярод людзей, на якіх вы падпісаліся.",
|
||||
"follow_suggestions.hints.most_followed": "Гэты профіль - адзін з профіляў з самай вялікай колькасцю падпісак на {domain}.",
|
||||
|
@ -315,6 +317,8 @@
|
|||
"follow_suggestions.hints.similar_to_recently_followed": "Гэты профіль падобны на профілі, на якія вы нядаўна падпісаліся.",
|
||||
"follow_suggestions.personalized_suggestion": "Персаналізаваная прапанова",
|
||||
"follow_suggestions.popular_suggestion": "Папулярная прапанова",
|
||||
"follow_suggestions.popular_suggestion_longer": "Папулярнае на {domain}",
|
||||
"follow_suggestions.similar_to_recently_followed_longer": "Падобныя профілі, за якімі вы нядаўна сачылі",
|
||||
"follow_suggestions.view_all": "Праглядзець усё",
|
||||
"follow_suggestions.who_to_follow": "На каго падпісацца",
|
||||
"followed_tags": "Падпіскі",
|
||||
|
@ -410,6 +414,7 @@
|
|||
"limited_account_hint.action": "Усе роўна паказваць профіль",
|
||||
"limited_account_hint.title": "Гэты профіль быў схаваны мадэратарамі",
|
||||
"link_preview.author": "Ад {name}",
|
||||
"link_preview.more_from_author": "Больш ад {name}",
|
||||
"lists.account.add": "Дадаць да спісу",
|
||||
"lists.account.remove": "Выдаліць са спісу",
|
||||
"lists.delete": "Выдаліць спіс",
|
||||
|
@ -458,7 +463,7 @@
|
|||
"navigation_bar.opened_in_classic_interface": "Допісы, уліковыя запісы і іншыя спецыфічныя старонкі па змоўчанні адчыняюцца ў класічным вэб-інтэрфейсе.",
|
||||
"navigation_bar.personal": "Асабістае",
|
||||
"navigation_bar.pins": "Замацаваныя допісы",
|
||||
"navigation_bar.preferences": "Параметры",
|
||||
"navigation_bar.preferences": "Налады",
|
||||
"navigation_bar.public_timeline": "Глабальная стужка",
|
||||
"navigation_bar.search": "Пошук",
|
||||
"navigation_bar.security": "Бяспека",
|
||||
|
@ -470,10 +475,22 @@
|
|||
"notification.follow_request": "{name} адправіў запыт на падпіску",
|
||||
"notification.mention": "{name} згадаў вас",
|
||||
"notification.moderation-warning.learn_more": "Даведацца больш",
|
||||
"notification.moderation_warning": "Вы атрымалі папярэджанне аб мадэрацыі",
|
||||
"notification.moderation_warning.action_delete_statuses": "Некаторыя вашыя допісы былі выдаленыя.",
|
||||
"notification.moderation_warning.action_disable": "Ваш уліковы запіс быў адключаны.",
|
||||
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Некаторыя з вашых допісаў былі пазначаныя як далікатныя.",
|
||||
"notification.moderation_warning.action_none": "Ваш уліковы запіс атрымаў папярэджанне ад мадэратараў.",
|
||||
"notification.moderation_warning.action_sensitive": "З гэтага моманту вашыя допісы будуць пазначаныя як далікатныя.",
|
||||
"notification.moderation_warning.action_silence": "Ваш уліковы запіс быў абмежаваны.",
|
||||
"notification.moderation_warning.action_suspend": "Ваш уліковы запіс быў прыпынены.",
|
||||
"notification.own_poll": "Ваша апытанне скончылася",
|
||||
"notification.poll": "Апытанне, дзе вы прынялі ўдзел, скончылася",
|
||||
"notification.reblog": "{name} пашырыў ваш допіс",
|
||||
"notification.relationships_severance_event": "Страціў сувязь з {name}",
|
||||
"notification.relationships_severance_event.account_suspension": "Адміністратар з {from} прыпыніў працу {target}, што азначае, што вы больш не можаце атрымліваць ад іх абнаўлення ці ўзаемадзейнічаць з імі.",
|
||||
"notification.relationships_severance_event.domain_block": "Адміністратар з {from} заблакіраваў {target}, у тым ліку {followersCount} вашых падпісчыка(-аў) і {followingCount, plural, one {# уліковы запіс} few {# уліковыя запісы} many {# уліковых запісаў} other {# уліковых запісаў}}.",
|
||||
"notification.relationships_severance_event.learn_more": "Даведацца больш",
|
||||
"notification.relationships_severance_event.user_domain_block": "Вы заблакіравалі {target} выдаліўшы {followersCount} сваіх падпісчыкаў і {followingCount, plural, one {# уліковы запіс} few {# уліковыя запісы} many {# уліковых запісаў} other {# уліковых запісаў}}, за якімі вы сочыце.",
|
||||
"notification.status": "Новы допіс ад {name}",
|
||||
"notification.update": "Допіс {name} адрэдагаваны",
|
||||
"notification_requests.accept": "Прыняць",
|
||||
|
|
|
@ -415,6 +415,7 @@
|
|||
"limited_account_hint.title": "Aquest perfil l'han amagat els moderadors de {domain}.",
|
||||
"link_preview.author": "Per {name}",
|
||||
"link_preview.more_from_author": "Més de {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} publicació} other {{counter} publicacions}}",
|
||||
"lists.account.add": "Afegeix a la llista",
|
||||
"lists.account.remove": "Elimina de la llista",
|
||||
"lists.delete": "Elimina la llista",
|
||||
|
|
|
@ -415,6 +415,7 @@
|
|||
"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}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} indlæg} other {{counter} indlæg}}",
|
||||
"lists.account.add": "Føj til liste",
|
||||
"lists.account.remove": "Fjern fra liste",
|
||||
"lists.delete": "Slet liste",
|
||||
|
|
|
@ -415,6 +415,7 @@
|
|||
"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}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} Beitrag} other {{counter} Beiträge}}",
|
||||
"lists.account.add": "Zur Liste hinzufügen",
|
||||
"lists.account.remove": "Von der Liste entfernen",
|
||||
"lists.delete": "Liste löschen",
|
||||
|
|
|
@ -415,6 +415,7 @@
|
|||
"limited_account_hint.title": "This profile has been hidden by the moderators of {domain}.",
|
||||
"link_preview.author": "By {name}",
|
||||
"link_preview.more_from_author": "More from {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} post} other {{counter} posts}}",
|
||||
"lists.account.add": "Add to list",
|
||||
"lists.account.remove": "Remove from list",
|
||||
"lists.delete": "Delete list",
|
||||
|
@ -695,13 +696,13 @@
|
|||
"server_banner.about_active_users": "People using this server during the last 30 days (Monthly Active Users)",
|
||||
"server_banner.active_users": "active users",
|
||||
"server_banner.administered_by": "Administered by:",
|
||||
"server_banner.introduction": "{domain} is part of the decentralized social network powered by {mastodon}.",
|
||||
"server_banner.learn_more": "Learn more",
|
||||
"server_banner.is_one_of_many": "{domain} is one of the many independent Mastodon servers you can use to participate in the fediverse.",
|
||||
"server_banner.server_stats": "Server stats:",
|
||||
"sign_in_banner.create_account": "Create account",
|
||||
"sign_in_banner.follow_anyone": "Follow anyone across the fediverse and see it all in chronological order. No algorithms, ads, or clickbait in sight.",
|
||||
"sign_in_banner.mastodon_is": "Mastodon is the best way to keep up with what's happening.",
|
||||
"sign_in_banner.sign_in": "Login",
|
||||
"sign_in_banner.sso_redirect": "Login or Register",
|
||||
"sign_in_banner.text": "Login to follow profiles or hashtags, favorite, share and reply to posts. You can also interact from your account on a different server.",
|
||||
"status.admin_account": "Open moderation interface for @{name}",
|
||||
"status.admin_domain": "Open moderation interface for {domain}",
|
||||
"status.admin_status": "Open this post in the moderation interface",
|
||||
|
|
|
@ -498,7 +498,14 @@
|
|||
"poll_button.add_poll": "Aldoni balotenketon",
|
||||
"poll_button.remove_poll": "Forigi balotenketon",
|
||||
"privacy.change": "Agordi mesaĝan privatecon",
|
||||
"privacy.direct.long": "Ĉiuj menciitaj en la afiŝo",
|
||||
"privacy.direct.short": "Specifaj homoj",
|
||||
"privacy.private.long": "Nur viaj sekvantoj",
|
||||
"privacy.private.short": "Sekvantoj",
|
||||
"privacy.public.long": "Ĉiujn ajn ĉe kaj ekster Mastodon",
|
||||
"privacy.public.short": "Publika",
|
||||
"privacy.unlisted.long": "Malpli algoritmaj fanfaroj",
|
||||
"privacy.unlisted.short": "Diskrete publika",
|
||||
"privacy_policy.last_updated": "Laste ĝisdatigita en {date}",
|
||||
"privacy_policy.title": "Politiko de privateco",
|
||||
"recommended": "Rekomendita",
|
||||
|
|
|
@ -415,6 +415,7 @@
|
|||
"limited_account_hint.title": "Este perfil fue ocultado por los moderadores de {domain}.",
|
||||
"link_preview.author": "Por {name}",
|
||||
"link_preview.more_from_author": "Más de {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} mensaje} other {{counter} mensajes}}",
|
||||
"lists.account.add": "Agregar a lista",
|
||||
"lists.account.remove": "Quitar de lista",
|
||||
"lists.delete": "Eliminar lista",
|
||||
|
|
|
@ -415,6 +415,7 @@
|
|||
"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}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}}",
|
||||
"lists.account.add": "Añadir a lista",
|
||||
"lists.account.remove": "Quitar de lista",
|
||||
"lists.delete": "Borrar lista",
|
||||
|
|
|
@ -415,6 +415,7 @@
|
|||
"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}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} publicación} other {{counter} publicaciones}}",
|
||||
"lists.account.add": "Añadir a lista",
|
||||
"lists.account.remove": "Quitar de lista",
|
||||
"lists.delete": "Borrar lista",
|
||||
|
|
|
@ -415,6 +415,7 @@
|
|||
"limited_account_hint.title": "Palvelimen {domain} valvojat ovat piilottaneet tämän käyttäjätilin.",
|
||||
"link_preview.author": "Julkaissut {name}",
|
||||
"link_preview.more_from_author": "Lisää käyttäjältä {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} julkaisu} other {{counter} julkaisua}}",
|
||||
"lists.account.add": "Lisää listalle",
|
||||
"lists.account.remove": "Poista listalta",
|
||||
"lists.delete": "Poista lista",
|
||||
|
|
|
@ -415,6 +415,7 @@
|
|||
"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}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} postur} other {{counter} postar}}",
|
||||
"lists.account.add": "Legg afturat lista",
|
||||
"lists.account.remove": "Tak av lista",
|
||||
"lists.delete": "Strika lista",
|
||||
|
|
|
@ -156,7 +156,7 @@
|
|||
"compose_form.poll.duration": "Durée du sondage",
|
||||
"compose_form.poll.multiple": "Choix multiple",
|
||||
"compose_form.poll.option_placeholder": "Option {number}",
|
||||
"compose_form.poll.single": "Choisissez-en un",
|
||||
"compose_form.poll.single": "Choix unique",
|
||||
"compose_form.poll.switch_to_multiple": "Changer le sondage pour autoriser plusieurs choix",
|
||||
"compose_form.poll.switch_to_single": "Changer le sondage pour n'autoriser qu'un seul choix",
|
||||
"compose_form.poll.type": "Style",
|
||||
|
@ -585,9 +585,9 @@
|
|||
"privacy.private.short": "Abonnés",
|
||||
"privacy.public.long": "Tout le monde sur et en dehors de Mastodon",
|
||||
"privacy.public.short": "Public",
|
||||
"privacy.unlisted.additional": "Cette option se comporte exactement comme l'option publique, sauf que le message n'apparaîtra pas dans les flux en direct, les hashtags, l'exploration ou la recherche Mastodon, même si vous avez opté pour l'option publique pour l'ensemble de votre compte.",
|
||||
"privacy.unlisted.additional": "Se comporte exactement comme « public », sauf que le message n'apparaîtra pas dans les flux en direct, les hashtags, explorer ou la recherche Mastodon, même si vous les avez activé au niveau de votre compte.",
|
||||
"privacy.unlisted.long": "Moins de fanfares algorithmiques",
|
||||
"privacy.unlisted.short": "Public calme",
|
||||
"privacy.unlisted.short": "Public discret",
|
||||
"privacy_policy.last_updated": "Dernière mise à jour {date}",
|
||||
"privacy_policy.title": "Politique de confidentialité",
|
||||
"recommended": "Recommandé",
|
||||
|
|
|
@ -156,7 +156,7 @@
|
|||
"compose_form.poll.duration": "Durée du sondage",
|
||||
"compose_form.poll.multiple": "Choix multiple",
|
||||
"compose_form.poll.option_placeholder": "Option {number}",
|
||||
"compose_form.poll.single": "Choisissez-en un",
|
||||
"compose_form.poll.single": "Choix unique",
|
||||
"compose_form.poll.switch_to_multiple": "Changer le sondage pour autoriser plusieurs choix",
|
||||
"compose_form.poll.switch_to_single": "Modifier le sondage pour autoriser qu'un seul choix",
|
||||
"compose_form.poll.type": "Style",
|
||||
|
@ -585,9 +585,9 @@
|
|||
"privacy.private.short": "Abonnés",
|
||||
"privacy.public.long": "Tout le monde sur et en dehors de Mastodon",
|
||||
"privacy.public.short": "Public",
|
||||
"privacy.unlisted.additional": "Cette option se comporte exactement comme l'option publique, sauf que le message n'apparaîtra pas dans les flux en direct, les hashtags, l'exploration ou la recherche Mastodon, même si vous avez opté pour l'option publique pour l'ensemble de votre compte.",
|
||||
"privacy.unlisted.additional": "Se comporte exactement comme « public », sauf que le message n'apparaîtra pas dans les flux en direct, les hashtags, explorer ou la recherche Mastodon, même si vous les avez activé au niveau de votre compte.",
|
||||
"privacy.unlisted.long": "Moins de fanfares algorithmiques",
|
||||
"privacy.unlisted.short": "Public calme",
|
||||
"privacy.unlisted.short": "Public discret",
|
||||
"privacy_policy.last_updated": "Dernière mise à jour {date}",
|
||||
"privacy_policy.title": "Politique de confidentialité",
|
||||
"recommended": "Recommandé",
|
||||
|
|
|
@ -415,6 +415,7 @@
|
|||
"limited_account_hint.title": "Este perfil foi agochado pola moderación de {domain}.",
|
||||
"link_preview.author": "Por {name}",
|
||||
"link_preview.more_from_author": "Máis de {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} publicación} other {{counter} publicacións}}",
|
||||
"lists.account.add": "Engadir á listaxe",
|
||||
"lists.account.remove": "Eliminar da listaxe",
|
||||
"lists.delete": "Eliminar listaxe",
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
"account.block_domain": "Blocar dominio {domain}",
|
||||
"account.block_short": "Blocar",
|
||||
"account.blocked": "Blocate",
|
||||
"account.browse_more_on_origin_server": "Percurrer plus sur le profilo original",
|
||||
"account.browse_more_on_origin_server": "Explorar plus sur le profilo original",
|
||||
"account.cancel_follow_request": "Cancellar sequimento",
|
||||
"account.copy": "Copiar ligamine a profilo",
|
||||
"account.direct": "Mentionar privatemente @{name}",
|
||||
|
@ -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}",
|
||||
|
@ -111,7 +111,7 @@
|
|||
"bundle_modal_error.message": "Un error ha occurrite durante le cargamento de iste componente.",
|
||||
"bundle_modal_error.retry": "Tentar novemente",
|
||||
"closed_registrations.other_server_instructions": "Perque Mastodon es decentralisate, tu pote crear un conto sur un altere servitor e totevia interager con iste servitor.",
|
||||
"closed_registrations_modal.description": "Crear un conto in {domain} actualmente non es possibile, ma considera que non es necessari haber un conto specificamente sur {domain} pro usar Mastodon.",
|
||||
"closed_registrations_modal.description": "Crear un conto sur {domain} non es actualmente possibile, ma considera que non es necessari haber un conto specificamente sur {domain} pro usar Mastodon.",
|
||||
"closed_registrations_modal.find_another_server": "Cercar un altere servitor",
|
||||
"closed_registrations_modal.preamble": "Mastodon es decentralisate, dunque, non importa ubi tu crea tu conto, tu pote sequer e communicar con omne persona sur iste servitor. Tu pote mesmo hospitar tu proprie servitor!",
|
||||
"closed_registrations_modal.title": "Crear un conto sur Mastodon",
|
||||
|
@ -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",
|
||||
|
@ -468,7 +468,7 @@
|
|||
"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",
|
||||
|
@ -499,7 +499,7 @@
|
|||
"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:",
|
||||
|
@ -636,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",
|
||||
|
@ -656,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",
|
||||
|
@ -747,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",
|
||||
|
|
|
@ -299,6 +299,11 @@
|
|||
"follow_suggestions.dismiss": "Jangan tampilkan lagi",
|
||||
"follow_suggestions.hints.featured": "Profil ini telah dipilih sendiri oleh tim {domain}.",
|
||||
"follow_suggestions.hints.friends_of_friends": "Profil ini populer di kalangan orang yang anda ikuti.",
|
||||
"follow_suggestions.personalized_suggestion": "Saran yang dipersonalisasi",
|
||||
"follow_suggestions.popular_suggestion": "Saran populer",
|
||||
"follow_suggestions.popular_suggestion_longer": "Populer di {domain}",
|
||||
"follow_suggestions.similar_to_recently_followed_longer": "Serupa dengan profil yang baru Anda ikuti",
|
||||
"follow_suggestions.view_all": "Lihat semua",
|
||||
"followed_tags": "Tagar yang diikuti",
|
||||
"footer.about": "Tentang",
|
||||
"footer.directory": "Direktori profil",
|
||||
|
@ -324,6 +329,7 @@
|
|||
"home.column_settings.show_reblogs": "Tampilkan boost",
|
||||
"home.column_settings.show_replies": "Tampilkan balasan",
|
||||
"home.hide_announcements": "Sembunyikan pengumuman",
|
||||
"home.pending_critical_update.link": "Lihat pembaruan",
|
||||
"home.show_announcements": "Tampilkan pengumuman",
|
||||
"interaction_modal.description.follow": "Dengan sebuah akun di Mastodon, Anda bisa mengikuti {name} untuk menerima kirimannya di beranda Anda.",
|
||||
"interaction_modal.description.reblog": "Dengan sebuah akun di Mastodon, Anda bisa mem-boost kiriman ini untuk membagikannya ke pengikut Anda sendiri.",
|
||||
|
@ -375,6 +381,7 @@
|
|||
"lightbox.previous": "Sebelumnya",
|
||||
"limited_account_hint.action": "Tetap tampilkan profil",
|
||||
"limited_account_hint.title": "Profil ini telah disembunyikan oleh moderator {domain}.",
|
||||
"link_preview.author": "Oleh {name}",
|
||||
"lists.account.add": "Tambah ke daftar",
|
||||
"lists.account.remove": "Hapus dari daftar",
|
||||
"lists.delete": "Hapus daftar",
|
||||
|
@ -389,8 +396,11 @@
|
|||
"lists.search": "Cari di antara orang yang Anda ikuti",
|
||||
"lists.subheading": "Daftar Anda",
|
||||
"load_pending": "{count, plural, other {# item baru}}",
|
||||
"loading_indicator.label": "Memuat…",
|
||||
"media_gallery.toggle_visible": "Tampil/Sembunyikan",
|
||||
"moved_to_account_banner.text": "Akun {disabledAccount} Anda kini dinonaktifkan karena Anda pindah ke {movedToAccount}.",
|
||||
"mute_modal.hide_options": "Sembunyikan opsi",
|
||||
"mute_modal.title": "Bisukan pengguna?",
|
||||
"navigation_bar.about": "Tentang",
|
||||
"navigation_bar.blocks": "Pengguna diblokir",
|
||||
"navigation_bar.bookmarks": "Markah",
|
||||
|
|
|
@ -415,6 +415,7 @@
|
|||
"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}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} færsla} other {{counter} færslur}}",
|
||||
"lists.account.add": "Bæta á lista",
|
||||
"lists.account.remove": "Fjarlægja af lista",
|
||||
"lists.delete": "Eyða lista",
|
||||
|
|
|
@ -415,6 +415,7 @@
|
|||
"limited_account_hint.title": "Questo profilo è stato nascosto dai moderatori di {domain}.",
|
||||
"link_preview.author": "Di {name}",
|
||||
"link_preview.more_from_author": "Altro da {name}",
|
||||
"link_preview.shares": "{count, plural,one {{counter} post}other {{counter} post}}",
|
||||
"lists.account.add": "Aggiungi all'elenco",
|
||||
"lists.account.remove": "Rimuovi dall'elenco",
|
||||
"lists.delete": "Elimina elenco",
|
||||
|
|
|
@ -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": "리스트 삭제",
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
{
|
||||
"about.contact": "Ratio:",
|
||||
"about.domain_blocks.no_reason_available": "Ratio abdere est",
|
||||
"about.domain_blocks.silenced.explanation": "Tua profilia atque tuum contentum ab hac serve praecipue non videbis, nisi explōrēs expresse aut subsequeris et optēs.",
|
||||
"account.account_note_header": "Annotatio",
|
||||
"account.add_or_remove_from_list": "Adde aut ēripe ex tabellīs",
|
||||
"account.badges.bot": "Robotum",
|
||||
"account.badges.group": "Congregatio",
|
||||
"account.block": "Impedire @{name}",
|
||||
|
@ -11,11 +13,21 @@
|
|||
"account.domain_blocked": "Dominium impeditum",
|
||||
"account.edit_profile": "Recolere notionem",
|
||||
"account.featured_tags.last_status_never": "Nulla contributa",
|
||||
"account.featured_tags.title": "Hashtag notātī {name}",
|
||||
"account.followers_counter": "{count, plural, one {{counter} Sectator} other {{counter} Sectatores}}",
|
||||
"account.following_counter": "{count, plural, one {{counter} Sequens} other {{counter} Sequentes}}",
|
||||
"account.moved_to": "{name} significavit eum suam rationem novam nunc esse:",
|
||||
"account.muted": "Confutatus",
|
||||
"account.requested_follow": "{name} postulavit ut te sequeretur",
|
||||
"account.statuses_counter": "{count, plural, one {{counter} Nuntius} other {{counter} Nuntii}}",
|
||||
"account.unblock_short": "Solvere impedimentum",
|
||||
"account_note.placeholder": "Click to add a note",
|
||||
"admin.dashboard.retention.average": "Mediocritas",
|
||||
"admin.impact_report.instance_accounts": "Rationes perfiles hoc deleret",
|
||||
"alert.unexpected.message": "Error inopinatus occurrit.",
|
||||
"announcement.announcement": "Proclamatio",
|
||||
"attachments_list.unprocessed": "(immūtātus)",
|
||||
"block_modal.you_wont_see_mentions": "Nuntios quibus eos commemorant non videbis.",
|
||||
"bundle_column_error.error.title": "Eheu!",
|
||||
"bundle_column_error.retry": "Retemptare",
|
||||
"bundle_column_error.routing.title": "CCCCIIII",
|
||||
|
@ -32,30 +44,60 @@
|
|||
"compose_form.direct_message_warning_learn_more": "Discere plura",
|
||||
"compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.",
|
||||
"compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
|
||||
"compose_form.lock_disclaimer": "Tua ratio non est {clausa}. Quisquis te sequi potest ut visum accipiat nuntios tuos tantum pro sectatoribus.",
|
||||
"compose_form.lock_disclaimer.lock": "clausum",
|
||||
"compose_form.placeholder": "What is on your mind?",
|
||||
"compose_form.publish_form": "Barrire",
|
||||
"compose_form.spoiler.marked": "Text is hidden behind warning",
|
||||
"compose_form.spoiler.unmarked": "Text is not hidden",
|
||||
"compose_form.spoiler.unmarked": "Adde praeconium contentūs",
|
||||
"confirmations.block.confirm": "Impedire",
|
||||
"confirmations.delete.confirm": "Oblitterare",
|
||||
"confirmations.delete.message": "Are you sure you want to delete this status?",
|
||||
"confirmations.delete_list.confirm": "Oblitterare",
|
||||
"confirmations.discard_edit_media.message": "Habēs mutationēs in descriptionem vel prōspectum medii quae nōn sunt servātae; eas dēmittam?",
|
||||
"confirmations.mute.confirm": "Confutare",
|
||||
"confirmations.reply.confirm": "Respondere",
|
||||
"disabled_account_banner.account_settings": "Praeferentiae ratiōnis",
|
||||
"disabled_account_banner.text": "Ratio tua {disabledAccount} debilitata est.",
|
||||
"dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
|
||||
"dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
|
||||
"domain_block_modal.you_will_lose_followers": "Omnes sectatores tuī ex hoc servō removēbuntur.",
|
||||
"domain_block_modal.you_wont_see_posts": "Nuntios aut notificātiōnēs ab usoribus in hōc servō nōn vidēbis.",
|
||||
"domain_pill.activitypub_like_language": "ActivityPub est velut lingua quam Mastodon cum aliīs sociālibus rētibus loquitur.",
|
||||
"domain_pill.your_handle": "Tuus nominulus:",
|
||||
"domain_pill.your_server": "Tua domus digitalis, ubi omnia tua nuntia habitant. Hanc non amas? Servēs trānsferāre potes quōcumque tempore et sectātōrēs tuōs simul addūcere.",
|
||||
"domain_pill.your_username": "Tuō singulāre id indicium in hōc servō est. Est possibile invenīre usōrēs cum eōdem nōmine in servīs aliīs.",
|
||||
"embed.instructions": "Embed this status on your website by copying the code below.",
|
||||
"emoji_button.activity": "Actiō",
|
||||
"emoji_button.food": "Cibus et potus",
|
||||
"emoji_button.people": "Homines",
|
||||
"emoji_button.search": "Quaerere...",
|
||||
"empty_column.account_suspended": "Rātiō suspēnsa",
|
||||
"empty_column.account_timeline": "Hic nulla contributa!",
|
||||
"empty_column.account_unavailable": "Notio non impetrabilis",
|
||||
"empty_column.home": "Your home timeline is empty! Follow more people to fill it up. {suggestions}",
|
||||
"empty_column.blocks": "Nondum quemquam usorem obsēcāvisti.",
|
||||
"empty_column.direct": "Nōn habēs adhūc ullo mentionēs prīvātās. Cum ūnam mīseris aut accipis, hīc apparēbit.",
|
||||
"empty_column.followed_tags": "Nōn adhūc aliquem hastāginem secūtus es. Cum id fēceris, hic ostendētur.",
|
||||
"empty_column.home": "Tua linea temporum domesticus vacua est! Sequere plures personas ut eam compleas.",
|
||||
"empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.",
|
||||
"empty_column.lists": "Nōn adhūc habēs ullo tabellās. Cum creās, hīc apparēbunt.",
|
||||
"empty_column.mutes": "Nondum quemquam usorem tacuisti.",
|
||||
"empty_column.notification_requests": "Omnia clara sunt! Nihil hic est. Cum novās notificātiōnēs accipīs, hic secundum tua praecepta apparebunt.",
|
||||
"empty_column.notifications": "Nōn adhūc habēs ullo notificātiōnēs. Cum aliī tē interagunt, hīc videbis.",
|
||||
"explore.trending_statuses": "Contributa",
|
||||
"filtered_notifications_banner.mentions": "{count, plural, one {mentiō} other {mentiōnēs}}",
|
||||
"firehose.all": "Omnis",
|
||||
"footer.about": "De",
|
||||
"generic.saved": "Servavit",
|
||||
"hashtag.column_settings.tag_mode.all": "Haec omnia",
|
||||
"hashtag.column_settings.tag_toggle": "Include additional tags in this column",
|
||||
"hashtag.counter_by_accounts": "{count, plural, one {{counter} particeps} other {{counter} participēs}}",
|
||||
"hashtag.counter_by_uses": "{count, plural, one {{counter} nuntius} other {{counter} nuntii}}",
|
||||
"hashtag.counter_by_uses_today": "{count, plural, one {{counter} nuntius} other {{counter} nuntii}} hodie",
|
||||
"hashtags.and_other": "…et {count, plural, other {# plus}}",
|
||||
"intervals.full.days": "{number, plural, one {# die} other {# dies}}",
|
||||
"intervals.full.hours": "{number, plural, one {# hora} other {# horae}}",
|
||||
"intervals.full.minutes": "{number, plural, one {# minutum} other {# minuta}}",
|
||||
"keyboard_shortcuts.back": "Re navigare",
|
||||
"keyboard_shortcuts.blocked": "Aperire listam usorum obstructorum",
|
||||
"keyboard_shortcuts.boost": "Inlustrare publicatio",
|
||||
|
@ -89,17 +131,47 @@
|
|||
"keyboard_shortcuts.up": "to move up in the list",
|
||||
"lightbox.close": "Claudere",
|
||||
"lightbox.next": "Secundum",
|
||||
"lists.account.add": "Adde ad tabellās",
|
||||
"lists.new.create": "Addere tabella",
|
||||
"load_pending": "{count, plural, one {# novum item} other {# nova itema}}",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Cēla imaginem} other {Cēla imagines}}",
|
||||
"moved_to_account_banner.text": "Tua ratione {disabledAccount} interdum reposita est, quod ad {movedToAccount} migrāvisti.",
|
||||
"mute_modal.you_wont_see_mentions": "Non videbis nuntios quī eōs commemorant.",
|
||||
"navigation_bar.about": "De",
|
||||
"navigation_bar.domain_blocks": "Hidden domains",
|
||||
"not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.",
|
||||
"notification.reblog": "{name} boosted your status",
|
||||
"not_signed_in_indicator.not_signed_in": "Ad hunc locum pervenire oportet ut inīre facias.",
|
||||
"notification.admin.report": "{name} nuntiavit {target}",
|
||||
"notification.admin.sign_up": "{name} subscripsit",
|
||||
"notification.favourite": "{name} nuntium tuum favit",
|
||||
"notification.follow": "{name} te secutus est",
|
||||
"notification.follow_request": "{name} postulavit ut te sequeretur",
|
||||
"notification.mention": "{name} memoravi",
|
||||
"notification.moderation_warning": "Accepistī monitionem moderationis.",
|
||||
"notification.moderation_warning.action_disable": "Ratio tua debilitata est.",
|
||||
"notification.moderation_warning.action_none": "Tua ratiō monitum moderātiōnis accēpit.",
|
||||
"notification.moderation_warning.action_sensitive": "Tua nuntia hinc sensibiliter notabuntur.",
|
||||
"notification.moderation_warning.action_silence": "Ratio tua est limitata.",
|
||||
"notification.moderation_warning.action_suspend": "Ratio tua suspensus est.",
|
||||
"notification.own_poll": "Suffragium tuum terminatum est.",
|
||||
"notification.poll": "Electione in quam suffragium dedisti finita est.",
|
||||
"notification.reblog": "{name} tuum nuntium amplificavit.",
|
||||
"notification.relationships_severance_event.account_suspension": "Admin ab {from} {target} suspendit, quod significat nōn iam posse tē novitātēs ab eīs accipere aut cum eīs interagere.",
|
||||
"notification.relationships_severance_event.domain_block": "Admin ab {from} {target} obsēcāvit, includēns {followersCount} ex tuīs sectātōribus et {followingCount, plural, one {# ratione} other {# rationibus}} quās sequeris.",
|
||||
"notification.relationships_severance_event.user_domain_block": "Bloqueāstī {target}, removēns {followersCount} ex sectātōribus tuīs et {followingCount, plural, one {# rationem} other {# rationēs}} quōs sequeris.",
|
||||
"notification.status": "{name} nuper publicavit",
|
||||
"notification.update": "{name} nuntium correxit",
|
||||
"notification_requests.accept": "Accipe",
|
||||
"notifications.filter.all": "Omnia",
|
||||
"notifications.filter.polls": "Eventus electionis",
|
||||
"notifications.group": "Notificātiōnēs",
|
||||
"onboarding.actions.go_to_explore": "See what's trending",
|
||||
"onboarding.actions.go_to_home": "Go to your home feed",
|
||||
"onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
|
||||
"onboarding.follows.lead": "Tua domus feed est principalis via Mastodon experīrī. Quō plūrēs persōnas sequeris, eō actīvior et interessantior erit. Ad tē incipiendum, ecce quaedam suāsiones:",
|
||||
"onboarding.follows.title": "Popular on Mastodon",
|
||||
"onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:",
|
||||
"onboarding.profile.display_name_hint": "Tuum nomen completum aut tuum nomen ludens...",
|
||||
"onboarding.start.lead": "Nunc pars es Mastodonis, singularis, socialis medii platformae decentralis ubi—non algorismus—tuam ipsius experientiam curas. Incipiāmus in nova hac socialis regione:",
|
||||
"onboarding.start.skip": "Want to skip right ahead?",
|
||||
"onboarding.start.title": "Perfecisti eam!",
|
||||
"onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.",
|
||||
"onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}",
|
||||
"onboarding.steps.publish_status.body": "Say hello to the world.",
|
||||
|
@ -107,29 +179,48 @@
|
|||
"onboarding.steps.setup_profile.title": "Customize your profile",
|
||||
"onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!",
|
||||
"onboarding.steps.share_profile.title": "Share your profile",
|
||||
"onboarding.tips.2fa": "<strong>Scisne?</strong> Tūam ratiōnem sēcūrāre potes duōrum elementōrum authentīcātiōnem in ratiōnis tuī praeferentiīs statuendō. Cum ūllā app TOTP ex tuā ēlēctiōne operātur, numerus tēlephōnicus necessārius nōn est!",
|
||||
"onboarding.tips.accounts_from_other_servers": "<strong>Scisne?</strong> Quoniam Mastodon dēcentrālis est, nōnnulla profīlia quae invenīs in servīs aliīs quam tuōrum erunt hospitāta. Tamen cum eīs sine impedīmentō interāgere potes! Servus eōrum in alterā parte nōminis eōrum est!",
|
||||
"onboarding.tips.migration": "<strong>Scisne?</strong> Sī sentīs {domain} tibi in futūrō nōn esse optimam servī ēlēctiōnem, ad alium servum Mastodon sine amittendō sectātōribus tuīs migrāre potes. Etiam tuum servum hospitārī potes!",
|
||||
"onboarding.tips.verification": "<strong>Scisne?</strong> Tūam ratiōnem verificāre potes iungendō nexum ad prōfīlium Mastodon tuum in propriā pāginā interrētiā et addendō pāginam ad prōfīlium tuum. Nullae pecūniae aut documenta necessāria sunt!",
|
||||
"poll.closed": "Clausum",
|
||||
"poll.total_people": "{count, plural, one {# persona} other {# personae}}",
|
||||
"poll.total_votes": "{count, plural, one {# suffragium} other {# suffragia}}",
|
||||
"poll.vote": "Eligere",
|
||||
"poll.voted": "Elegisti hoc responsum",
|
||||
"poll.votes": "{votes, plural, one {# sufragium} other {# sufragia}}",
|
||||
"poll_button.add_poll": "Addere electionem",
|
||||
"poll_button.remove_poll": "Auferre electionem",
|
||||
"privacy.change": "Adjust status privacy",
|
||||
"privacy.public.short": "Coram publico",
|
||||
"regeneration_indicator.sublabel": "Tua domus feed praeparātur!",
|
||||
"relative_time.full.days": "{number, plural, one {# ante die} other {# ante dies}}",
|
||||
"relative_time.full.hours": "{number, plural, one {# ante horam} other {# ante horas}}",
|
||||
"relative_time.full.just_now": "nunc",
|
||||
"relative_time.full.minutes": "{number, plural, one {# ante minutum} other {# ante minuta}}",
|
||||
"relative_time.full.seconds": "{number, plural, one {# ante secundum} other {# ante secunda}}",
|
||||
"relative_time.just_now": "nunc",
|
||||
"relative_time.today": "hodie",
|
||||
"reply_indicator.attachments": "{count, plural, one {# annexus} other {# annexūs}}",
|
||||
"report.block": "Impedimentum",
|
||||
"report.block_explanation": "Non videbis eorum nuntios. Non poterunt vidēre tuōs nuntios aut tē sequī. Intelligere poterunt sē obstrūctōs esse.",
|
||||
"report.categories.other": "Altera",
|
||||
"report.category.title_account": "notio",
|
||||
"report.category.title_status": "contributum",
|
||||
"report.close": "Confectum",
|
||||
"report.mute": "Confutare",
|
||||
"report.mute_explanation": "Non videbis eōrum nuntiōs. Possunt adhuc tē sequī et tuōs nuntiōs vidēre, nec sciēbunt sē tacitōs esse.",
|
||||
"report.next": "Secundum",
|
||||
"report.placeholder": "Type or paste additional comments",
|
||||
"report.placeholder": "Commentāriī adiūnctī",
|
||||
"report.submit": "Mittere",
|
||||
"report.target": "Report {target}",
|
||||
"report_notification.attached_statuses": "{count, plural, one {# post} other {# posts}} attached",
|
||||
"report_notification.attached_statuses": "{count, plural, one {{count} nuntius} other {{count} nuntii}} attachiatus",
|
||||
"report_notification.categories.other": "Altera",
|
||||
"search.placeholder": "Quaerere",
|
||||
"search_results.all": "Omnis",
|
||||
"server_banner.active_users": "Usūrāriī āctīvī",
|
||||
"server_banner.administered_by": "Administratur:",
|
||||
"server_banner.introduction": "{domain} pars est de rete sociali decentralizato a {mastodon} propulsato.",
|
||||
"server_banner.learn_more": "Discere plura",
|
||||
"sign_in_banner.sign_in": "Sign in",
|
||||
"status.admin_status": "Open this status in the moderation interface",
|
||||
|
@ -139,13 +230,29 @@
|
|||
"status.delete": "Oblitterare",
|
||||
"status.edit": "Recolere",
|
||||
"status.edited_x_times": "Edited {count, plural, one {# time} other {# times}}",
|
||||
"status.favourites": "{count, plural, one {favoritum} other {favorita}}",
|
||||
"status.history.created": "{name} creatum {date}",
|
||||
"status.history.edited": "{name} correxit {date}",
|
||||
"status.open": "Expand this status",
|
||||
"status.title.with_attachments": "{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}",
|
||||
"status.reblogged_by": "{name} adiuvavit",
|
||||
"status.reblogs": "{count, plural, one {auctus} other {auctūs}}",
|
||||
"status.title.with_attachments": "{user} publicavit {attachmentCount, plural, one {unum annexum} other {{attachmentCount} annexa}}",
|
||||
"tabs_bar.home": "Domi",
|
||||
"time_remaining.days": "{number, plural, one {# die} other {# dies}} restant",
|
||||
"time_remaining.hours": "{number, plural, one {# hora} other {# horae}} restant",
|
||||
"time_remaining.minutes": "{number, plural, one {# minutum} other {# minuta}} restant",
|
||||
"time_remaining.seconds": "{number, plural, one {# secundum} other {# secunda}} restant",
|
||||
"timeline_hint.remote_resource_not_displayed": "{resource} ab aliīs servīs nōn ostenduntur.",
|
||||
"timeline_hint.resources.statuses": "Contributa pristina",
|
||||
"trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}",
|
||||
"trends.counter_by_accounts": "{count, plural, one {{counter} persōna} other {{counter} persōnae}} in {days, plural, one {diē prīdiē} other {diēbus praeteritīs {days}}}",
|
||||
"ui.beforeunload": "Si Mastodon discesseris, tua epitome peribit.",
|
||||
"units.short.billion": "{count} millia milionum",
|
||||
"units.short.million": "{count} milionum",
|
||||
"units.short.thousand": "{count} millia",
|
||||
"upload_button.label": "Imaginēs, vīdeō aut fīle audītūs adde",
|
||||
"upload_form.audio_description": "Describe for people who are hard of hearing",
|
||||
"upload_form.edit": "Recolere",
|
||||
"upload_modal.description_placeholder": "A velox brunneis vulpes salit super piger canis",
|
||||
"upload_progress.label": "Uploading…",
|
||||
"video.mute": "Confutare soni"
|
||||
}
|
||||
|
|
|
@ -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:",
|
||||
|
@ -433,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",
|
||||
|
@ -478,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ė",
|
||||
|
|
|
@ -415,6 +415,7 @@
|
|||
"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}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} bericht} other {{counter} berichten}}",
|
||||
"lists.account.add": "Aan lijst toevoegen",
|
||||
"lists.account.remove": "Uit lijst verwijderen",
|
||||
"lists.delete": "Lijst verwijderen",
|
||||
|
|
|
@ -414,6 +414,8 @@
|
|||
"limited_account_hint.action": "Vis profilen likevel",
|
||||
"limited_account_hint.title": "Denne profilen er skjult av moderatorane på {domain}.",
|
||||
"link_preview.author": "Av {name}",
|
||||
"link_preview.more_from_author": "Meir frå {name}",
|
||||
"link_preview.shares": "{count, plural,one {{counter} innlegg} other {{counter} innlegg}}",
|
||||
"lists.account.add": "Legg til i liste",
|
||||
"lists.account.remove": "Fjern frå liste",
|
||||
"lists.delete": "Slett liste",
|
||||
|
|
|
@ -415,6 +415,7 @@
|
|||
"limited_account_hint.title": "Ten profil został ukryty przez moderatorów {domain}.",
|
||||
"link_preview.author": "{name}",
|
||||
"link_preview.more_from_author": "Więcej od {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} wpis} few {{counter} wpisy} many {{counter} wpisów} other {{counter} wpisów}}",
|
||||
"lists.account.add": "Dodaj do listy",
|
||||
"lists.account.remove": "Usunąć z listy",
|
||||
"lists.delete": "Usuń listę",
|
||||
|
|
|
@ -415,6 +415,7 @@
|
|||
"limited_account_hint.title": "Este perfil foi ocultado pelos moderadores de {domain}.",
|
||||
"link_preview.author": "Por {name}",
|
||||
"link_preview.more_from_author": "Mais de {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} publicação} other {{counter} publicações}}",
|
||||
"lists.account.add": "Adicionar à lista",
|
||||
"lists.account.remove": "Remover da lista",
|
||||
"lists.delete": "Eliminar lista",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -415,6 +415,7 @@
|
|||
"limited_account_hint.title": "Profil so moderatorji strežnika {domain} skrili.",
|
||||
"link_preview.author": "Avtor_ica {name}",
|
||||
"link_preview.more_from_author": "Več od {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} objava} two {{counter} objavi} few {{counter} objave} other {{counter} objav}}",
|
||||
"lists.account.add": "Dodaj na seznam",
|
||||
"lists.account.remove": "Odstrani s seznama",
|
||||
"lists.delete": "Izbriši seznam",
|
||||
|
|
|
@ -415,6 +415,7 @@
|
|||
"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}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} inlägg} other {{counter} inlägg}}",
|
||||
"lists.account.add": "Lägg till i lista",
|
||||
"lists.account.remove": "Ta bort från lista",
|
||||
"lists.delete": "Radera lista",
|
||||
|
|
|
@ -26,8 +26,9 @@
|
|||
"account.featured_tags.last_status_never": "کوئی مراسلہ نہیں",
|
||||
"account.featured_tags.title": "{name} کے نمایاں ہیش ٹیگز",
|
||||
"account.follow": "پیروی کریں",
|
||||
"account.follow_back": "اکاؤنٹ کو فالو بیک ",
|
||||
"account.followers": "پیروکار",
|
||||
"account.followers.empty": "\"ہنوز اس صارف کی کوئی پیروی نہیں کرتا\".",
|
||||
"account.followers.empty": "ہنوز اس صارف کی کوئی پیروی نہیں کرتا.",
|
||||
"account.followers_counter": "{count, plural,one {{counter} پیروکار} other {{counter} پیروکار}}",
|
||||
"account.following": "فالو کر رہے ہیں",
|
||||
"account.following_counter": "{count, plural, one {{counter} پیروی کر رہے ہیں} other {{counter} پیروی کر رہے ہیں}}",
|
||||
|
@ -46,6 +47,7 @@
|
|||
"account.mute_notifications_short": "نوٹیفیکیشنز کو خاموش کریں",
|
||||
"account.mute_short": "خاموش",
|
||||
"account.muted": "خاموش کردہ",
|
||||
"account.mutual": "میوچول اکاؤنٹ",
|
||||
"account.no_bio": "کوئی تفصیل نہیں دی گئی۔",
|
||||
"account.open_original_page": "اصل صفحہ کھولیں",
|
||||
"account.posts": "ٹوٹ",
|
||||
|
@ -64,7 +66,8 @@
|
|||
"account.unmute": "@{name} کو با آواز کریں",
|
||||
"account.unmute_notifications_short": "نوٹیفیکیشنز کو خاموش نہ کریں",
|
||||
"account.unmute_short": "کو خاموش نہ کریں",
|
||||
"account_note.placeholder": "Click to add a note",
|
||||
"admin.dashboard.daily_retention": "ایڈمن ڈیش بورڈ کو ڈیلی چیک ان کریں",
|
||||
"admin.dashboard.monthly_retention": "ایڈمن کیش بورڈ کو منتھلی چیک ان کریں",
|
||||
"admin.dashboard.retention.average": "اوسط",
|
||||
"admin.dashboard.retention.cohort_size": "نئے یسرز",
|
||||
"alert.rate_limited.message": "\"{retry_time, time, medium} کے بعد کوشش کریں\".",
|
||||
|
|
|
@ -415,6 +415,7 @@
|
|||
"limited_account_hint.title": "此账号资料已被 {domain} 管理员隐藏。",
|
||||
"link_preview.author": "由 {name}",
|
||||
"link_preview.more_from_author": "查看 {name} 的更多内容",
|
||||
"link_preview.shares": "{count, plural, other {{counter} 条嘟文}}",
|
||||
"lists.account.add": "添加到列表",
|
||||
"lists.account.remove": "从列表中移除",
|
||||
"lists.delete": "删除列表",
|
||||
|
|
|
@ -415,6 +415,7 @@
|
|||
"limited_account_hint.title": "此個人檔案已被 {domain} 的管理員隱藏。",
|
||||
"link_preview.author": "來自 {name}",
|
||||
"link_preview.more_from_author": "來自 {name} 之更多內容",
|
||||
"link_preview.shares": "{count, plural, other {{count} 則嘟文}}",
|
||||
"lists.account.add": "新增至列表",
|
||||
"lists.account.remove": "自列表中移除",
|
||||
"lists.delete": "刪除列表",
|
||||
|
|
|
@ -903,9 +903,15 @@ body > [data-popper-placement] {
|
|||
padding: 10px;
|
||||
|
||||
p {
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
color: $darker-text-color;
|
||||
margin-bottom: 20px;
|
||||
|
||||
strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $secondary-text-color;
|
||||
text-decoration: none;
|
||||
|
@ -1411,10 +1417,15 @@ body > [data-popper-placement] {
|
|||
.audio-player,
|
||||
.attachment-list,
|
||||
.picture-in-picture-placeholder,
|
||||
.more-from-author,
|
||||
.status-card,
|
||||
.hashtag-bar {
|
||||
margin-inline-start: $thread-margin;
|
||||
width: calc(100% - ($thread-margin));
|
||||
width: calc(100% - $thread-margin);
|
||||
}
|
||||
|
||||
.more-from-author {
|
||||
width: calc(100% - $thread-margin + 2px);
|
||||
}
|
||||
|
||||
.status__content__read-more-button {
|
||||
|
@ -4129,6 +4140,13 @@ a.status-card {
|
|||
border-end-start-radius: 0;
|
||||
}
|
||||
|
||||
.status-card.bottomless .status-card__image,
|
||||
.status-card.bottomless .status-card__image-image,
|
||||
.status-card.bottomless .status-card__image-preview {
|
||||
border-end-end-radius: 0;
|
||||
border-end-start-radius: 0;
|
||||
}
|
||||
|
||||
.status-card.expanded > a {
|
||||
width: 100%;
|
||||
}
|
||||
|
@ -8784,43 +8802,80 @@ noscript {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
color: $primary-text-color;
|
||||
text-decoration: none;
|
||||
padding: 15px;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid var(--background-border-color);
|
||||
gap: 15px;
|
||||
gap: 16px;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
color: $highlight-text-color;
|
||||
|
||||
.story__details__publisher,
|
||||
.story__details__shared {
|
||||
color: $highlight-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
&__details {
|
||||
flex: 1 1 auto;
|
||||
|
||||
&__publisher {
|
||||
color: $darker-text-color;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
display: block;
|
||||
font-size: 19px;
|
||||
line-height: 24px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
text-decoration: none;
|
||||
color: $primary-text-color;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
color: $highlight-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
&__shared {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: $darker-text-color;
|
||||
gap: 8px;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
|
||||
& > span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
&__pill {
|
||||
background: var(--surface-variant-background-color);
|
||||
border-radius: 4px;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
padding: 4px 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
&__author-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: $primary-text-color;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
color: $highlight-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
strong {
|
||||
|
@ -8885,14 +8940,14 @@ noscript {
|
|||
}
|
||||
|
||||
.server-banner {
|
||||
padding: 20px 0;
|
||||
|
||||
&__introduction {
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
color: $darker-text-color;
|
||||
margin-bottom: 20px;
|
||||
|
||||
strong {
|
||||
font-weight: 600;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
a {
|
||||
|
@ -8920,6 +8975,9 @@ noscript {
|
|||
}
|
||||
|
||||
&__description {
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
color: $darker-text-color;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
|
@ -9891,14 +9949,14 @@ noscript {
|
|||
color: inherit;
|
||||
text-decoration: none;
|
||||
padding: 4px 12px;
|
||||
background: $ui-base-color;
|
||||
background: var(--surface-variant-background-color);
|
||||
border-radius: 4px;
|
||||
font-weight: 500;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
background: lighten($ui-base-color, 4%);
|
||||
background: var(--surface-variant-active-background-color);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10229,6 +10287,7 @@ noscript {
|
|||
}
|
||||
|
||||
.more-from-author {
|
||||
box-sizing: border-box;
|
||||
font-size: 14px;
|
||||
color: $darker-text-color;
|
||||
background: var(--surface-background-color);
|
||||
|
|
|
@ -613,9 +613,10 @@ code {
|
|||
font-family: inherit;
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
max-width: 140px;
|
||||
max-width: 50%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
|
|
|
@ -106,4 +106,6 @@ $font-monospace: 'mastodon-font-monospace' !default;
|
|||
--background-color: #{darken($ui-base-color, 8%)};
|
||||
--background-color-tint: #{rgba(darken($ui-base-color, 8%), 0.9)};
|
||||
--surface-background-color: #{darken($ui-base-color, 4%)};
|
||||
--surface-variant-background-color: #{$ui-base-color};
|
||||
--surface-variant-active-background-color: #{lighten($ui-base-color, 4%)};
|
||||
}
|
||||
|
|
10
app/lib/access_grant_extension.rb
Normal file
10
app/lib/access_grant_extension.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module AccessGrantExtension
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
scope :expired, -> { where.not(expires_in: nil).where('created_at + MAKE_INTERVAL(secs => expires_in) < NOW()') }
|
||||
scope :revoked, -> { where.not(revoked_at: nil).where(revoked_at: ...Time.now.utc) }
|
||||
end
|
||||
end
|
|
@ -9,6 +9,10 @@ module AccessTokenExtension
|
|||
has_many :web_push_subscriptions, class_name: 'Web::PushSubscription', inverse_of: :access_token
|
||||
|
||||
after_commit :push_to_streaming_api
|
||||
|
||||
scope :expired, -> { where.not(expires_in: nil).where('created_at + MAKE_INTERVAL(secs => expires_in) < NOW()') }
|
||||
scope :not_revoked, -> { where(revoked_at: nil) }
|
||||
scope :revoked, -> { where.not(revoked_at: nil).where(revoked_at: ...Time.now.utc) }
|
||||
end
|
||||
|
||||
def revoke(clock = Time)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ActivityPub::Activity::Flag < ActivityPub::Activity
|
||||
COMMENT_SIZE_LIMIT = 5000
|
||||
|
||||
def perform
|
||||
return if skip_reports?
|
||||
|
||||
|
@ -38,6 +40,6 @@ class ActivityPub::Activity::Flag < ActivityPub::Activity
|
|||
end
|
||||
|
||||
def report_comment
|
||||
(@json['content'] || '')[0...5000]
|
||||
(@json['content'] || '')[0...COMMENT_SIZE_LIMIT]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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, imagemagick_version, ffmpeg_version].compact
|
||||
end
|
||||
|
||||
def mastodon_version
|
||||
|
@ -28,8 +28,8 @@ class Admin::Metrics::Dimension::SoftwareVersionsDimension < Admin::Metrics::Dim
|
|||
{
|
||||
key: 'ruby',
|
||||
human_key: 'Ruby',
|
||||
value: "#{RUBY_VERSION}p#{RUBY_PATCHLEVEL}",
|
||||
human_value: RUBY_DESCRIPTION,
|
||||
value: RUBY_DESCRIPTION,
|
||||
human_value: "#{RUBY_VERSION}p#{RUBY_PATCHLEVEL}",
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -71,6 +71,45 @@ 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 imagemagick_version
|
||||
return if Rails.configuration.x.use_vips
|
||||
|
||||
version = `convert -version`.match(/Version: ImageMagick ([\d\.]+)/)[1]
|
||||
|
||||
{
|
||||
key: 'imagemagick',
|
||||
human_key: 'ImageMagick',
|
||||
value: version,
|
||||
human_value: version,
|
||||
}
|
||||
rescue Errno::ENOENT
|
||||
nil
|
||||
end
|
||||
|
||||
def ffmpeg_version
|
||||
version = `ffmpeg -version`.match(/ffmpeg version ([\d\.]+)/)[1]
|
||||
|
||||
{
|
||||
key: 'ffmpeg',
|
||||
human_key: 'FFmpeg',
|
||||
value: version,
|
||||
human_value: version,
|
||||
}
|
||||
rescue Errno::ENOENT
|
||||
nil
|
||||
end
|
||||
|
||||
def redis_info
|
||||
@redis_info ||= if redis.is_a?(Redis::Namespace)
|
||||
redis.redis.info
|
||||
|
|
|
@ -43,7 +43,7 @@ class Admin::Metrics::Measure::InstanceAccountsMeasure < Admin::Metrics::Measure
|
|||
SELECT count(*) FROM new_accounts
|
||||
) AS value
|
||||
FROM (
|
||||
SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
|
||||
#{generated_series_days}
|
||||
) AS axis
|
||||
SQL
|
||||
end
|
||||
|
|
|
@ -44,7 +44,7 @@ class Admin::Metrics::Measure::InstanceFollowersMeasure < Admin::Metrics::Measur
|
|||
SELECT count(*) FROM new_followers
|
||||
) AS value
|
||||
FROM (
|
||||
SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
|
||||
#{generated_series_days}
|
||||
) AS axis
|
||||
SQL
|
||||
end
|
||||
|
|
|
@ -44,7 +44,7 @@ class Admin::Metrics::Measure::InstanceFollowsMeasure < Admin::Metrics::Measure:
|
|||
SELECT count(*) FROM new_follows
|
||||
) AS value
|
||||
FROM (
|
||||
SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period
|
||||
#{generated_series_days}
|
||||
) AS axis
|
||||
SQL
|
||||
end
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue