Merge commit '34cd7d6585992c03298c175ab5d22ad059b58cdb' into glitch-soc/merge-upstream

Conflicts:
- `CONTRIBUTING.md`:
  Upstream changed the file, while we had a different one.
  Updated the common parts.
- `README.md`:
  Upstream changed the file, while we had a different one.
  Updated the common parts.
- `app/helpers/application_helper.rb`:
  Upstream added helpers where glitch-soc had extra ones.
  Added upstream's new helpers.
- `app/models/form/admin_settings.rb`:
  Upstream added some custom handling of one setting, while
  glitch-soc had additional code.
  Ported upstream's code.
- `lib/mastodon/version.rb`:
  Upstream moved some things to `config/mastodon.yml`.
  Did the same.
- `spec/requests/api/v1/accounts/credentials_spec.rb`:
  I don't know honestly.
This commit is contained in:
Claire 2025-01-10 20:36:25 +01:00
commit 155dc4bc4b
127 changed files with 1766 additions and 1224 deletions

View file

@ -1,10 +1,6 @@
[production]
defaults defaults
> 0.2% > 0.2%
firefox >= 78 firefox >= 78
ios >= 15.6 ios >= 15.6
not dead not dead
not OperaMini all not OperaMini all
[development]
supports es6-module

View file

@ -59,7 +59,7 @@ body:
Any additional technical details you may have, like logs or error traces Any additional technical details you may have, like logs or error traces
value: | value: |
If this is happening on your own Mastodon server, please fill out those: If this is happening on your own Mastodon server, please fill out those:
- Ruby version: (from `ruby --version`, eg. v3.3.5) - Ruby version: (from `ruby --version`, eg. v3.4.1)
- Node.js version: (from `node --version`, eg. v20.18.0) - Node.js version: (from `node --version`, eg. v20.18.0)
validations: validations:
required: false required: false

View file

@ -60,7 +60,7 @@ body:
value: | value: |
Please at least include those informations: Please at least include those informations:
- Operating system: (eg. Ubuntu 22.04) - Operating system: (eg. Ubuntu 22.04)
- Ruby version: (from `ruby --version`, eg. v3.3.5) - Ruby version: (from `ruby --version`, eg. v3.4.1)
- Node.js version: (from `node --version`, eg. v20.18.0) - Node.js version: (from `node --version`, eg. v20.18.0)
validations: validations:
required: false required: false

View file

@ -14,11 +14,6 @@
// If we do not want a package to be grouped with others, we need to set its groupName // If we do not want a package to be grouped with others, we need to set its groupName
// to `null` after any other rule set it to something. // to `null` after any other rule set it to something.
dependencyDashboardHeader: 'This issue lists Renovate updates and detected dependencies. Read the [Dependency Dashboard](https://docs.renovatebot.com/key-concepts/dashboard/) docs to learn more. Before approving any upgrade: read the description and comments in the [`renovate.json5` file](https://github.com/mastodon/mastodon/blob/main/.github/renovate.json5).', dependencyDashboardHeader: 'This issue lists Renovate updates and detected dependencies. Read the [Dependency Dashboard](https://docs.renovatebot.com/key-concepts/dashboard/) docs to learn more. Before approving any upgrade: read the description and comments in the [`renovate.json5` file](https://github.com/mastodon/mastodon/blob/main/.github/renovate.json5).',
constraints: {
// Mastodon should work on Ruby 3.4, but its test dependencies are currently uninstallable on Ruby 3.4.
// TODO: remove this once https://github.com/briandunn/flatware/issues/103 is fixed
ruby: '3.3',
},
postUpdateOptions: ['yarnDedupeHighest'], postUpdateOptions: ['yarnDedupeHighest'],
packageRules: [ packageRules: [
{ {

View file

@ -51,7 +51,7 @@ jobs:
# Create or update the pull request # Create or update the pull request
- name: Create Pull Request - name: Create Pull Request
uses: peter-evans/create-pull-request@v7.0.5 uses: peter-evans/create-pull-request@v7.0.6
with: with:
commit-message: 'New Crowdin translations' commit-message: 'New Crowdin translations'
title: 'New Crowdin Translations for ${{ github.base_ref || github.ref_name }} (automated)' title: 'New Crowdin Translations for ${{ github.base_ref || github.ref_name }} (automated)'

View file

@ -53,7 +53,7 @@ jobs:
# Create or update the pull request # Create or update the pull request
- name: Create Pull Request - name: Create Pull Request
uses: peter-evans/create-pull-request@v7.0.5 uses: peter-evans/create-pull-request@v7
with: with:
commit-message: 'New Crowdin translations' commit-message: 'New Crowdin translations'
title: 'New Crowdin Translations (automated)' title: 'New Crowdin Translations (automated)'

View file

@ -125,6 +125,7 @@ jobs:
matrix: matrix:
ruby-version: ruby-version:
- '3.2' - '3.2'
- '3.3'
- '.ruby-version' - '.ruby-version'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -226,6 +227,7 @@ jobs:
matrix: matrix:
ruby-version: ruby-version:
- '3.2' - '3.2'
- '3.3'
- '.ruby-version' - '.ruby-version'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -304,6 +306,7 @@ jobs:
matrix: matrix:
ruby-version: ruby-version:
- '3.2' - '3.2'
- '3.3'
- '.ruby-version' - '.ruby-version'
steps: steps:
@ -420,6 +423,7 @@ jobs:
matrix: matrix:
ruby-version: ruby-version:
- '3.2' - '3.2'
- '3.3'
- '.ruby-version' - '.ruby-version'
search-image: search-image:
- docker.elastic.co/elasticsearch/elasticsearch:7.17.13 - docker.elastic.co/elasticsearch/elasticsearch:7.17.13

2
.nvmrc
View file

@ -1 +1 @@
22.12 22.13

View file

@ -29,9 +29,6 @@ Style/IfUnlessModifier:
Style/KeywordArgumentsMerging: Style/KeywordArgumentsMerging:
Enabled: false Enabled: false
Style/MultipleComparison:
Enabled: false
Style/NumericLiterals: Style/NumericLiterals:
AllowedPatterns: AllowedPatterns:
- \d{4}_\d{2}_\d{2}_\d{6} - \d{4}_\d{2}_\d{2}_\d{6}

View file

@ -1,6 +1,6 @@
# This configuration was generated by # This configuration was generated by
# `rubocop --auto-gen-config --auto-gen-only-exclude --no-offense-counts --no-auto-gen-timestamp` # `rubocop --auto-gen-config --auto-gen-only-exclude --no-offense-counts --no-auto-gen-timestamp`
# using RuboCop version 1.69.1. # using RuboCop version 1.69.2.
# The point is for the user to remove these configuration records # The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base. # one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new # Note that changes in the inspected code, or installation of new
@ -8,7 +8,7 @@
Lint/NonLocalExitFromIterator: Lint/NonLocalExitFromIterator:
Exclude: Exclude:
- 'app/helpers/jsonld_helper.rb' - 'app/helpers/json_ld_helper.rb'
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes. # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
Metrics/AbcSize: Metrics/AbcSize:
@ -82,7 +82,7 @@ Style/MutableConstant:
# AllowedMethods: respond_to_missing? # AllowedMethods: respond_to_missing?
Style/OptionalBooleanParameter: Style/OptionalBooleanParameter:
Exclude: Exclude:
- 'app/helpers/jsonld_helper.rb' - 'app/helpers/json_ld_helper.rb'
- 'app/lib/admin/system_check/message.rb' - 'app/lib/admin/system_check/message.rb'
- 'app/lib/request.rb' - 'app/lib/request.rb'
- 'app/lib/webfinger.rb' - 'app/lib/webfinger.rb'

View file

@ -1 +1 @@
3.3.6 3.4.1

View file

@ -51,7 +51,7 @@ You can contribute in the following ways:
If your contributions are accepted into Mastodon, you can request to be paid through [our OpenCollective](https://opencollective.com/mastodon). If your contributions are accepted into Mastodon, you can request to be paid through [our OpenCollective](https://opencollective.com/mastodon).
Please review the org-level [contribution guidelines] for high-level acceptance Please review the org-level [contribution guidelines] for high-level acceptance
criteria guidance. criteria guidance and the [DEVELOPMENT] guide for environment-specific details.
[contribution guidelines]: https://github.com/mastodon/.github/blob/main/CONTRIBUTING.md [contribution guidelines]: https://github.com/mastodon/.github/blob/main/CONTRIBUTING.md
@ -94,3 +94,5 @@ It is not always possible to phrase every change in such a manner, but it is des
The [Mastodon documentation](https://docs.joinmastodon.org) is a statically generated site. You can [submit merge requests to mastodon/documentation](https://github.com/mastodon/documentation). The [Mastodon documentation](https://docs.joinmastodon.org) is a statically generated site. You can [submit merge requests to mastodon/documentation](https://github.com/mastodon/documentation).
</blockquote> </blockquote>
[DEVELOPMENT]: docs/DEVELOPMENT.md

View file

@ -10,9 +10,9 @@
ARG TARGETPLATFORM=${TARGETPLATFORM} ARG TARGETPLATFORM=${TARGETPLATFORM}
ARG BUILDPLATFORM=${BUILDPLATFORM} ARG BUILDPLATFORM=${BUILDPLATFORM}
# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.3.x"] # Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.4.x"]
# renovate: datasource=docker depName=docker.io/ruby # renovate: datasource=docker depName=docker.io/ruby
ARG RUBY_VERSION="3.3.6" ARG RUBY_VERSION="3.4.1"
# # Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"] # # Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"]
# renovate: datasource=node-version depName=node # renovate: datasource=node-version depName=node
ARG NODE_MAJOR_VERSION="22" ARG NODE_MAJOR_VERSION="22"
@ -20,7 +20,7 @@ ARG NODE_MAJOR_VERSION="22"
ARG DEBIAN_VERSION="bookworm" ARG DEBIAN_VERSION="bookworm"
# Node image to use for base image based on combined variables (ex: 20-bookworm-slim) # Node image to use for base image based on combined variables (ex: 20-bookworm-slim)
FROM docker.io/node:${NODE_MAJOR_VERSION}-${DEBIAN_VERSION}-slim AS node FROM docker.io/node:${NODE_MAJOR_VERSION}-${DEBIAN_VERSION}-slim AS node
# Ruby image to use for base image based on combined variables (ex: 3.3.x-slim-bookworm) # Ruby image to use for base image based on combined variables (ex: 3.4.x-slim-bookworm)
FROM docker.io/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} AS ruby FROM docker.io/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} AS ruby
# Resulting version string is vX.X.X-MASTODON_VERSION_PRERELEASE+MASTODON_VERSION_METADATA # Resulting version string is vX.X.X-MASTODON_VERSION_PRERELEASE+MASTODON_VERSION_METADATA

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
source 'https://rubygems.org' source 'https://rubygems.org'
ruby '>= 3.2.0', '< 3.5' ruby '>= 3.2.0', '< 3.5.0'
gem 'propshaft' gem 'propshaft'
gem 'puma', '~> 6.3' gem 'puma', '~> 6.3'
@ -108,7 +108,7 @@ group :opentelemetry do
gem 'opentelemetry-instrumentation-active_model_serializers', '~> 0.21.0', require: false gem 'opentelemetry-instrumentation-active_model_serializers', '~> 0.21.0', require: false
gem 'opentelemetry-instrumentation-concurrent_ruby', '~> 0.21.2', require: false gem 'opentelemetry-instrumentation-concurrent_ruby', '~> 0.21.2', require: false
gem 'opentelemetry-instrumentation-excon', '~> 0.22.0', require: false gem 'opentelemetry-instrumentation-excon', '~> 0.22.0', require: false
gem 'opentelemetry-instrumentation-faraday', '~> 0.24.1', require: false gem 'opentelemetry-instrumentation-faraday', '~> 0.25.0', require: false
gem 'opentelemetry-instrumentation-http', '~> 0.23.2', require: false gem 'opentelemetry-instrumentation-http', '~> 0.23.2', require: false
gem 'opentelemetry-instrumentation-http_client', '~> 0.22.3', require: false gem 'opentelemetry-instrumentation-http_client', '~> 0.22.3', require: false
gem 'opentelemetry-instrumentation-net_http', '~> 0.22.4', require: false gem 'opentelemetry-instrumentation-net_http', '~> 0.22.4', require: false

View file

@ -94,7 +94,7 @@ GEM
ast (2.4.2) ast (2.4.2)
attr_required (1.0.2) attr_required (1.0.2)
aws-eventstream (1.3.0) aws-eventstream (1.3.0)
aws-partitions (1.1029.0) aws-partitions (1.1032.0)
aws-sdk-core (3.214.1) aws-sdk-core (3.214.1)
aws-eventstream (~> 1, >= 1.3.0) aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0) aws-partitions (~> 1, >= 1.992.0)
@ -103,7 +103,7 @@ GEM
aws-sdk-kms (1.96.0) aws-sdk-kms (1.96.0)
aws-sdk-core (~> 3, >= 3.210.0) aws-sdk-core (~> 3, >= 3.210.0)
aws-sigv4 (~> 1.5) aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.176.1) aws-sdk-s3 (1.177.0)
aws-sdk-core (~> 3, >= 3.210.0) aws-sdk-core (~> 3, >= 3.210.0)
aws-sdk-kms (~> 1) aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5) aws-sigv4 (~> 1.5)
@ -160,7 +160,7 @@ GEM
cocoon (1.2.15) cocoon (1.2.15)
color_diff (0.1) color_diff (0.1)
concurrent-ruby (1.3.4) concurrent-ruby (1.3.4)
connection_pool (2.4.1) connection_pool (2.5.0)
cose (1.3.1) cose (1.3.1)
cbor (~> 0.5.9) cbor (~> 0.5.9)
openssl-signature_algorithm (~> 1.0) openssl-signature_algorithm (~> 1.0)
@ -233,16 +233,16 @@ GEM
faraday-net_http (3.4.0) faraday-net_http (3.4.0)
net-http (>= 0.5.0) net-http (>= 0.5.0)
fast_blank (1.0.1) fast_blank (1.0.1)
fastimage (2.3.1) fastimage (2.4.0)
ffi (1.17.1) ffi (1.17.1)
ffi-compiler (1.3.2) ffi-compiler (1.3.2)
ffi (>= 1.15.5) ffi (>= 1.15.5)
rake rake
flatware (2.3.3) flatware (2.3.4)
drb drb
thor (< 2.0) thor (< 2.0)
flatware-rspec (2.3.3) flatware-rspec (2.3.4)
flatware (= 2.3.3) flatware (= 2.3.4)
rspec (>= 3.6) rspec (>= 3.6)
fog-core (2.5.0) fog-core (2.5.0)
builder builder
@ -490,7 +490,7 @@ GEM
opentelemetry-instrumentation-active_job (0.7.8) opentelemetry-instrumentation-active_job (0.7.8)
opentelemetry-api (~> 1.0) opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-base (~> 0.22.1) opentelemetry-instrumentation-base (~> 0.22.1)
opentelemetry-instrumentation-active_model_serializers (0.21.0) opentelemetry-instrumentation-active_model_serializers (0.21.1)
opentelemetry-api (~> 1.0) opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-active_support (>= 0.7.0) opentelemetry-instrumentation-active_support (>= 0.7.0)
opentelemetry-instrumentation-base (~> 0.22.1) opentelemetry-instrumentation-base (~> 0.22.1)
@ -510,7 +510,7 @@ GEM
opentelemetry-instrumentation-excon (0.22.5) opentelemetry-instrumentation-excon (0.22.5)
opentelemetry-api (~> 1.0) opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-base (~> 0.22.1) opentelemetry-instrumentation-base (~> 0.22.1)
opentelemetry-instrumentation-faraday (0.24.8) opentelemetry-instrumentation-faraday (0.25.0)
opentelemetry-api (~> 1.0) opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-base (~> 0.22.1) opentelemetry-instrumentation-base (~> 0.22.1)
opentelemetry-instrumentation-http (0.23.5) opentelemetry-instrumentation-http (0.23.5)
@ -522,7 +522,7 @@ GEM
opentelemetry-instrumentation-net_http (0.22.8) opentelemetry-instrumentation-net_http (0.22.8)
opentelemetry-api (~> 1.0) opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-base (~> 0.22.1) opentelemetry-instrumentation-base (~> 0.22.1)
opentelemetry-instrumentation-pg (0.29.1) opentelemetry-instrumentation-pg (0.29.2)
opentelemetry-api (~> 1.0) opentelemetry-api (~> 1.0)
opentelemetry-helpers-sql-obfuscation opentelemetry-helpers-sql-obfuscation
opentelemetry-instrumentation-base (~> 0.22.1) opentelemetry-instrumentation-base (~> 0.22.1)
@ -709,7 +709,7 @@ GEM
rspec-mocks (~> 3.0) rspec-mocks (~> 3.0)
sidekiq (>= 5, < 8) sidekiq (>= 5, < 8)
rspec-support (3.13.2) rspec-support (3.13.2)
rubocop (1.69.2) rubocop (1.70.0)
json (~> 2.3) json (~> 2.3)
language_server-protocol (>= 3.17.0) language_server-protocol (>= 3.17.0)
parallel (~> 1.10) parallel (~> 1.10)
@ -723,7 +723,7 @@ GEM
parser (>= 3.3.1.0) parser (>= 3.3.1.0)
rubocop-capybara (2.21.0) rubocop-capybara (2.21.0)
rubocop (~> 1.41) rubocop (~> 1.41)
rubocop-performance (1.23.0) rubocop-performance (1.23.1)
rubocop (>= 1.48.1, < 2.0) rubocop (>= 1.48.1, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0) rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rails (2.28.0) rubocop-rails (2.28.0)
@ -744,7 +744,7 @@ GEM
ruby-vips (2.2.2) ruby-vips (2.2.2)
ffi (~> 1.12) ffi (~> 1.12)
logger logger
rubyzip (2.3.2) rubyzip (2.4.1)
rufus-scheduler (3.9.2) rufus-scheduler (3.9.2)
fugit (~> 1.1, >= 1.11.1) fugit (~> 1.1, >= 1.11.1)
safety_net_attestation (0.4.0) safety_net_attestation (0.4.0)
@ -809,7 +809,7 @@ GEM
unicode-display_width (>= 1.1.1, < 3) unicode-display_width (>= 1.1.1, < 3)
terrapin (1.0.1) terrapin (1.0.1)
climate_control climate_control
test-prof (1.4.3) test-prof (1.4.4)
thor (1.3.2) thor (1.3.2)
tilt (2.5.0) tilt (2.5.0)
timeout (0.4.3) timeout (0.4.3)
@ -963,7 +963,7 @@ DEPENDENCIES
opentelemetry-instrumentation-active_model_serializers (~> 0.21.0) opentelemetry-instrumentation-active_model_serializers (~> 0.21.0)
opentelemetry-instrumentation-concurrent_ruby (~> 0.21.2) opentelemetry-instrumentation-concurrent_ruby (~> 0.21.2)
opentelemetry-instrumentation-excon (~> 0.22.0) opentelemetry-instrumentation-excon (~> 0.22.0)
opentelemetry-instrumentation-faraday (~> 0.24.1) opentelemetry-instrumentation-faraday (~> 0.25.0)
opentelemetry-instrumentation-http (~> 0.23.2) opentelemetry-instrumentation-http (~> 0.23.2)
opentelemetry-instrumentation-http_client (~> 0.22.3) opentelemetry-instrumentation-http_client (~> 0.22.3)
opentelemetry-instrumentation-net_http (~> 0.22.4) opentelemetry-instrumentation-net_http (~> 0.22.4)
@ -1034,7 +1034,7 @@ DEPENDENCIES
xorcist (~> 1.1) xorcist (~> 1.1)
RUBY VERSION RUBY VERSION
ruby 3.3.6p108 ruby 3.4.1p0
BUNDLED WITH BUNDLED WITH
2.6.2 2.6.2

147
README.md
View file

@ -14,27 +14,27 @@ Mastodon Glitch Edition is a fork of [Mastodon](https://github.com/mastodon/mast
--- ---
<h1><picture> > [!NOTE]
<source media="(prefers-color-scheme: dark)" srcset="./lib/assets/wordmark.dark.png?raw=true"> > Want to learn more about Mastodon?
<source media="(prefers-color-scheme: light)" srcset="./lib/assets/wordmark.light.png?raw=true"> > Click below to find out more in a video.
<img alt="Mastodon" src="./lib/assets/wordmark.light.png?raw=true" height="34">
</picture></h1>
[![GitHub release](https://img.shields.io/github/release/mastodon/mastodon.svg)][releases] <p align="center">
[![Ruby Testing](https://github.com/mastodon/mastodon/actions/workflows/test-ruby.yml/badge.svg)](https://github.com/mastodon/mastodon/actions/workflows/test-ruby.yml) <a style="text-decoration:none" href="https://www.youtube.com/watch?v=IPSbNdBmWKE">
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/mastodon/localized.svg)][crowdin] <img alt="Mastodon hero image" src="https://github.com/user-attachments/assets/ef53f5e9-c0d8-484d-9f53-00efdebb92c3" />
</a>
</p>
[releases]: https://github.com/mastodon/mastodon/releases <p align="center">
[crowdin]: https://crowdin.com/project/mastodon <a style="text-decoration:none" href="https://github.com/mastodon/mastodon/releases">
<img src="https://img.shields.io/github/release/mastodon/mastodon.svg" alt="Release" /></a>
<a style="text-decoration:none" href="https://github.com/mastodon/mastodon/actions/workflows/test-ruby.yml">
<img src="https://github.com/mastodon/mastodon/actions/workflows/test-ruby.yml/badge.svg" alt="Ruby Testing" /></a>
<a style="text-decoration:none" href="https://crowdin.com/project/mastodon">
<img src="https://d322cqt584bo4o.cloudfront.net/mastodon/localized.svg" alt="Crowdin" /></a>
</p>
Mastodon is a **free, open-source social network server** based on ActivityPub where users can follow friends and discover new ones. On Mastodon, users can publish anything they want: links, pictures, text, and video. All Mastodon servers are interoperable as a federated network (users on one server can seamlessly communicate with users from another one, including non-Mastodon software that implements ActivityPub!) Mastodon is a **free, open-source social network server** based on ActivityPub where users can follow friends and discover new ones. On Mastodon, users can publish anything they want: links, pictures, text, and video. All Mastodon servers are interoperable as a federated network (users on one server can seamlessly communicate with users from another one, including non-Mastodon software that implements ActivityPub!)
Click below to **learn more** in a video:
[![Screenshot](https://blog.joinmastodon.org/2018/06/why-activitypub-is-the-future/ezgif-2-60f1b00403.gif)][youtube_demo]
[youtube_demo]: https://www.youtube.com/watch?v=IPSbNdBmWKE
## Navigation ## Navigation
- [Project homepage 🐘](https://joinmastodon.org) - [Project homepage 🐘](https://joinmastodon.org)
@ -53,25 +53,15 @@ Click below to **learn more** in a video:
<img src="/app/javascript/images/elephant_ui_working.svg?raw=true" align="right" width="30%" /> <img src="/app/javascript/images/elephant_ui_working.svg?raw=true" align="right" width="30%" />
### No vendor lock-in: Fully interoperable with any conforming platform **No vendor lock-in: Fully interoperable with any conforming platform** - It doesn't have to be Mastodon; whatever implements ActivityPub is part of the social network! [Learn more](https://blog.joinmastodon.org/2018/06/why-activitypub-is-the-future/)
It doesn't have to be Mastodon; whatever implements ActivityPub is part of the social network! [Learn more](https://blog.joinmastodon.org/2018/06/why-activitypub-is-the-future/) **Real-time, chronological timeline updates** - updates of people you're following appear in real-time in the UI via WebSockets. There's a firehose view as well!
### Real-time, chronological timeline updates **Media attachments like images and short videos** - upload and view images and WebM/MP4 videos attached to the updates. Videos with no audio track are treated like GIFs; normal videos loop continuously!
Updates of people you're following appear in real-time in the UI via WebSockets. There's a firehose view as well! **Safety and moderation tools** - Mastodon includes private posts, locked accounts, phrase filtering, muting, blocking, and all sorts of other features, along with a reporting and moderation system. [Learn more](https://blog.joinmastodon.org/2018/07/cage-the-mastodon/)
### Media attachments like images and short videos **OAuth2 and a straightforward REST API** - Mastodon acts as an OAuth2 provider, so 3rd party apps can use the REST and Streaming APIs. This results in a rich app ecosystem with a lot of choices!
Upload and view images and WebM/MP4 videos attached to the updates. Videos with no audio track are treated like GIFs; normal videos loop continuously!
### Safety and moderation tools
Mastodon includes private posts, locked accounts, phrase filtering, muting, blocking, and all sorts of other features, along with a reporting and moderation system. [Learn more](https://blog.joinmastodon.org/2018/07/cage-the-mastodon/)
### OAuth2 and a straightforward REST API
Mastodon acts as an OAuth2 provider, so 3rd party apps can use the REST and Streaming APIs. This results in a rich app ecosystem with a lot of choices!
## Deployment ## Deployment
@ -90,85 +80,40 @@ Mastodon acts as an OAuth2 provider, so 3rd party apps can use the REST and Stre
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. 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
### Vagrant
A **Vagrant** configuration is included for development purposes. To use it, complete the following steps:
- Install Vagrant and Virtualbox
- Install the `vagrant-hostsupdater` plugin: `vagrant plugin install vagrant-hostsupdater`
- Run `vagrant up`
- Run `vagrant ssh -c "cd /vagrant && bin/dev"`
- Open `http://mastodon.local` in your browser
### macOS
To set up **macOS** for native development, complete the following steps:
- 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 production hosting and deployment with **Docker**, use the `Dockerfile` and
`docker-compose.yml` in the project root directory.
For local development, install and launch [Docker], and run:
```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
[GitHub Codespaces] provides a web-based version of VS Code and a cloud hosted
development environment configured with the software needed for this project.
[![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 ## Contributing
Mastodon is **free, open-source software** licensed under **AGPLv3**. Mastodon is **free, open-source software** licensed under **AGPLv3**.
You can open issues for bugs you've found or features you think are missing. You can also submit pull requests to this repository or submit translations using Crowdin. To get started, take a look at [CONTRIBUTING.md](CONTRIBUTING.md). If your contributions are accepted into Mastodon, you can request to be paid through [our OpenCollective](https://opencollective.com/mastodon). You can open issues for bugs you've found or features you think are missing. You
can also submit pull requests to this repository or translations via Crowdin. To
get started, look at the [CONTRIBUTING] and [DEVELOPMENT] guides. For changes
accepted into Mastodon, you can request to be paid through our [OpenCollective].
**IRC channel**: #mastodon on irc.libera.chat **IRC channel**: #mastodon on [`irc.libera.chat`](https://libera.chat)
## License ## License
Copyright (C) 2016-2024 Eugen Rochko & other Mastodon contributors (see [AUTHORS.md](AUTHORS.md)) Copyright (c) 2016-2024 Eugen Rochko (+ [`mastodon authors`](AUTHORS.md))
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Licensed under GNU Affero General Public License as stated in the [LICENSE](LICENSE):
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. ```
Copyright (c) 2016-2024 Eugen Rochko & other Mastodon contributors
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/>. This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option) any
later version.
[codespace]: https://codespaces.new/mastodon/mastodon?quickstart=1&devcontainer_path=.devcontainer%2Fcodespaces%2Fdevcontainer.json This program is distributed in the hope that it will be useful, but WITHOUT
[Dev Container extension]: https://containers.dev/supporting#dev-containers ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
[Development Containers]: https://containers.dev/supporting FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
[Docker]: https://docs.docker.com details.
[GitHub Codespaces]: https://docs.github.com/en/codespaces
[Homebrew]: https://brew.sh 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/
```
[CONTRIBUTING]: CONTRIBUTING.md
[DEVELOPMENT]: docs/DEVELOPMENT.md
[OpenCollective]: https://opencollective.com/mastodon

View file

@ -80,10 +80,31 @@ class Api::V2::NotificationsController < Api::BaseController
return [] if @notifications.empty? return [] if @notifications.empty?
MastodonOTELTracer.in_span('Api::V2::NotificationsController#load_grouped_notifications') do MastodonOTELTracer.in_span('Api::V2::NotificationsController#load_grouped_notifications') do
NotificationGroup.from_notifications(@notifications, pagination_range: (@notifications.last.id)..(@notifications.first.id), grouped_types: params[:grouped_types]) pagination_range = (@notifications.last.id)..@notifications.first.id
# If the page is incomplete, we know we are on the last page
if incomplete_page?
if paginating_up?
pagination_range = @notifications.last.id...(params[:max_id]&.to_i)
else
range_start = params[:since_id]&.to_i
range_start += 1 unless range_start.nil?
pagination_range = range_start..(@notifications.first.id)
end
end
NotificationGroup.from_notifications(@notifications, pagination_range: pagination_range, grouped_types: params[:grouped_types])
end end
end end
def incomplete_page?
@notifications.size < limit_param(DEFAULT_NOTIFICATIONS_LIMIT)
end
def paginating_up?
params[:min_id].present?
end
def browserable_account_notifications def browserable_account_notifications
current_account.notifications.without_suspended.browserable( current_account.notifications.without_suspended.browserable(
types: Array(browserable_params[:types]), types: Array(browserable_params[:types]),

View file

@ -1,10 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class CustomCssController < ActionController::Base # rubocop:disable Rails/ApplicationController class CustomCssController < ActionController::Base # rubocop:disable Rails/ApplicationController
before_action :set_user_roles
def show def show
expires_in 3.minutes, public: true expires_in 1.month, public: true
render content_type: 'text/css' render content_type: 'text/css'
end end
@ -14,8 +12,4 @@ class CustomCssController < ActionController::Base # rubocop:disable Rails/Appli
Setting.custom_css Setting.custom_css
end end
helper_method :custom_css_styles helper_method :custom_css_styles
def set_user_roles
@user_roles = UserRole.providing_styles
end
end end

View file

@ -239,6 +239,14 @@ module ApplicationHelper
I18n.t 'user_mailer.welcome.hashtags_recent_count', people: number_with_delimiter(people), count: people I18n.t 'user_mailer.welcome.hashtags_recent_count', people: number_with_delimiter(people), count: people
end end
def app_store_url_ios
'https://apps.apple.com/app/mastodon-for-iphone-and-ipad/id1571998974'
end
def app_store_url_android
'https://play.google.com/store/apps/details?id=org.joinmastodon.android'
end
# glitch-soc addition to handle the multiple flavors # glitch-soc addition to handle the multiple flavors
def preload_locale_pack def preload_locale_pack
supported_locales = Themes.instance.flavour(current_flavour)['locales'] supported_locales = Themes.instance.flavour(current_flavour)['locales']

View file

@ -27,8 +27,31 @@ module ThemeHelper
end end
end end
def custom_stylesheet
if active_custom_stylesheet.present?
stylesheet_link_tag(
custom_css_path(active_custom_stylesheet),
host: root_url,
media: :all,
skip_pipeline: true
)
end
end
private private
def active_custom_stylesheet
if cached_custom_css_digest.present?
[:custom, cached_custom_css_digest.to_s.first(8)]
.compact_blank
.join('-')
end
end
def cached_custom_css_digest
Rails.cache.read(:setting_digest_custom_css)
end
def theme_color_for(theme) def theme_color_for(theme)
theme == 'mastodon-light' ? Themes::THEME_COLORS[:light] : Themes::THEME_COLORS[:dark] theme == 'mastodon-light' ? Themes::THEME_COLORS[:light] : Themes::THEME_COLORS[:dark]
end end

View file

@ -119,7 +119,11 @@ function loaded() {
formattedContent = dateFormat.format(datetime); formattedContent = dateFormat.format(datetime);
} }
content.title = formattedContent; const timeGiven = content.dateTime.includes('T');
content.title = timeGiven
? dateTimeFormat.format(datetime)
: dateFormat.format(datetime);
content.textContent = formattedContent; content.textContent = formattedContent;
}); });

View file

@ -1,4 +1,4 @@
import { Iterable, fromJS } from 'immutable'; import { fromJS, isIndexed } from 'immutable';
import { hydrateCompose } from './compose'; import { hydrateCompose } from './compose';
import { importFetchedAccounts } from './importer'; import { importFetchedAccounts } from './importer';
@ -9,8 +9,7 @@ export const STORE_HYDRATE_LAZY = 'STORE_HYDRATE_LAZY';
const convertState = rawState => const convertState = rawState =>
fromJS(rawState, (k, v) => fromJS(rawState, (k, v) =>
Iterable.isIndexed(v) ? v.toList() : v.toMap()); isIndexed(v) ? v.toList() : v.toMap());
export function hydrateStore(rawState) { export function hydrateStore(rawState) {
return dispatch => { return dispatch => {

View file

@ -1,6 +1,6 @@
import { Component } from 'react'; import { Component } from 'react';
import type { IntlShape } from 'react-intl'; import type { MessageDescriptor, PrimitiveType, IntlShape } from 'react-intl';
import { injectIntl, defineMessages } from 'react-intl'; import { injectIntl, defineMessages } from 'react-intl';
const messages = defineMessages({ const messages = defineMessages({
@ -102,7 +102,13 @@ const getUnitDelay = (units: string) => {
}; };
export const timeAgoString = ( export const timeAgoString = (
intl: Pick<IntlShape, 'formatDate' | 'formatMessage'>, intl: {
formatDate: IntlShape['formatDate'];
formatMessage: (
{ id, defaultMessage }: MessageDescriptor,
values?: Record<string, PrimitiveType>,
) => string;
},
date: Date, date: Date,
now: number, now: number,
year: number, year: number,

View file

@ -335,15 +335,29 @@ class Announcement extends ImmutablePureComponent {
const endsAt = announcement.get('ends_at') && new Date(announcement.get('ends_at')); const endsAt = announcement.get('ends_at') && new Date(announcement.get('ends_at'));
const now = new Date(); const now = new Date();
const hasTimeRange = startsAt && endsAt; const hasTimeRange = startsAt && endsAt;
const skipYear = hasTimeRange && startsAt.getFullYear() === endsAt.getFullYear() && endsAt.getFullYear() === now.getFullYear();
const skipEndDate = hasTimeRange && startsAt.getDate() === endsAt.getDate() && startsAt.getMonth() === endsAt.getMonth() && startsAt.getFullYear() === endsAt.getFullYear();
const skipTime = announcement.get('all_day'); const skipTime = announcement.get('all_day');
let timestamp = null;
if (hasTimeRange) {
const skipYear = startsAt.getFullYear() === endsAt.getFullYear() && endsAt.getFullYear() === now.getFullYear();
const skipEndDate = startsAt.getDate() === endsAt.getDate() && startsAt.getMonth() === endsAt.getMonth() && startsAt.getFullYear() === endsAt.getFullYear();
timestamp = (
<>
<FormattedDate value={startsAt} year={(skipYear || startsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month='short' day='2-digit' hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} /> - <FormattedDate value={endsAt} year={(skipYear || endsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month={skipEndDate ? undefined : 'short'} day={skipEndDate ? undefined : '2-digit'} hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} />
</>
);
} else {
const publishedAt = new Date(announcement.get('published_at'));
timestamp = (
<FormattedDate value={publishedAt} year={publishedAt.getFullYear() === now.getFullYear() ? undefined : 'numeric'} month='short' day='2-digit' hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} />
);
}
return ( return (
<div className='announcements__item'> <div className='announcements__item'>
<strong className='announcements__item__range'> <strong className='announcements__item__range'>
<FormattedMessage id='announcement.announcement' defaultMessage='Announcement' /> <FormattedMessage id='announcement.announcement' defaultMessage='Announcement' />
{hasTimeRange && <span> · <FormattedDate value={startsAt} year={(skipYear || startsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month='short' day='2-digit' hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} /> - <FormattedDate value={endsAt} year={(skipYear || endsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month={skipEndDate ? undefined : 'short'} day={skipEndDate ? undefined : '2-digit'} hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} /></span>} <span> · {timestamp}</span>
</strong> </strong>
<Content announcement={announcement} /> <Content announcement={announcement} />

View file

@ -457,6 +457,7 @@
"keyboard_shortcuts.toggle_hidden": "Dangos/cuddio testun tu ôl i CW", "keyboard_shortcuts.toggle_hidden": "Dangos/cuddio testun tu ôl i CW",
"keyboard_shortcuts.toggle_sensitivity": "Dangos/cuddio cyfryngau", "keyboard_shortcuts.toggle_sensitivity": "Dangos/cuddio cyfryngau",
"keyboard_shortcuts.toot": "Dechrau post newydd", "keyboard_shortcuts.toot": "Dechrau post newydd",
"keyboard_shortcuts.translate": "i gyfieithu postiad",
"keyboard_shortcuts.unfocus": "Dad-ffocysu ardal cyfansoddi testun/chwilio", "keyboard_shortcuts.unfocus": "Dad-ffocysu ardal cyfansoddi testun/chwilio",
"keyboard_shortcuts.up": "Symud yn uwch yn y rhestr", "keyboard_shortcuts.up": "Symud yn uwch yn y rhestr",
"lightbox.close": "Cau", "lightbox.close": "Cau",
@ -835,6 +836,7 @@
"status.reblogs.empty": "Does neb wedi hybio'r post yma eto. Pan y bydd rhywun yn gwneud, byddent yn ymddangos yma.", "status.reblogs.empty": "Does neb wedi hybio'r post yma eto. Pan y bydd rhywun yn gwneud, byddent yn ymddangos yma.",
"status.redraft": "Dileu ac ailddrafftio", "status.redraft": "Dileu ac ailddrafftio",
"status.remove_bookmark": "Tynnu nod tudalen", "status.remove_bookmark": "Tynnu nod tudalen",
"status.remove_favourite": "Tynnu o'r ffefrynnau",
"status.replied_in_thread": "Atebodd mewn edefyn", "status.replied_in_thread": "Atebodd mewn edefyn",
"status.replied_to": "Wedi ateb {name}", "status.replied_to": "Wedi ateb {name}",
"status.reply": "Ateb", "status.reply": "Ateb",

View file

@ -482,6 +482,7 @@
"lists.exclusive": "Skjul medlemmer i Hjem", "lists.exclusive": "Skjul medlemmer i Hjem",
"lists.exclusive_hint": "Er nogen er på denne liste, skjul personen i hjemme-feeds for at undgå at se vedkommendes indlæg to gange.", "lists.exclusive_hint": "Er nogen er på denne liste, skjul personen i hjemme-feeds for at undgå at se vedkommendes indlæg to gange.",
"lists.find_users_to_add": "Find brugere at tilføje", "lists.find_users_to_add": "Find brugere at tilføje",
"lists.list_members": "Liste over medlemmer",
"lists.list_members_count": "{count, plural, one {# medlem} other {# medlemmer}}", "lists.list_members_count": "{count, plural, one {# medlem} other {# medlemmer}}",
"lists.list_name": "Listetitel", "lists.list_name": "Listetitel",
"lists.new_list_name": "Ny listetitel", "lists.new_list_name": "Ny listetitel",

View file

@ -362,6 +362,7 @@
"footer.privacy_policy": "Privatlívspolitikkur", "footer.privacy_policy": "Privatlívspolitikkur",
"footer.source_code": "Vís keldukotuna", "footer.source_code": "Vís keldukotuna",
"footer.status": "Støða", "footer.status": "Støða",
"footer.terms_of_service": "Tænastutreytir",
"generic.saved": "Goymt", "generic.saved": "Goymt",
"getting_started.heading": "At byrja", "getting_started.heading": "At byrja",
"hashtag.admin_moderation": "Lat umsjónarmarkamót upp fyri #{name}", "hashtag.admin_moderation": "Lat umsjónarmarkamót upp fyri #{name}",
@ -858,6 +859,7 @@
"subscribed_languages.target": "Broyt haldaramál fyri {target}", "subscribed_languages.target": "Broyt haldaramál fyri {target}",
"tabs_bar.home": "Heim", "tabs_bar.home": "Heim",
"tabs_bar.notifications": "Fráboðanir", "tabs_bar.notifications": "Fráboðanir",
"terms_of_service.title": "Tænastutreytir",
"time_remaining.days": "{number, plural, one {# dagur} other {# dagar}} eftir", "time_remaining.days": "{number, plural, one {# dagur} other {# dagar}} eftir",
"time_remaining.hours": "{number, plural, one {# tími} other {# tímar}} eftir", "time_remaining.hours": "{number, plural, one {# tími} other {# tímar}} eftir",
"time_remaining.minutes": "{number, plural, one {# minuttur} other {# minuttir}} eftir", "time_remaining.minutes": "{number, plural, one {# minuttur} other {# minuttir}} eftir",

View file

@ -453,10 +453,11 @@
"keyboard_shortcuts.requests": "Követési kérések listájának megnyitása", "keyboard_shortcuts.requests": "Követési kérések listájának megnyitása",
"keyboard_shortcuts.search": "Fókuszálás a keresősávra", "keyboard_shortcuts.search": "Fókuszálás a keresősávra",
"keyboard_shortcuts.spoilers": "Tartalmi figyelmeztetés mező megjelenítése/elrejtése", "keyboard_shortcuts.spoilers": "Tartalmi figyelmeztetés mező megjelenítése/elrejtése",
"keyboard_shortcuts.start": "\"Első lépések\" oszlop megnyitása", "keyboard_shortcuts.start": "„Első lépések” oszlop megnyitása",
"keyboard_shortcuts.toggle_hidden": "Tartalmi figyelmeztetéssel ellátott szöveg megjelenítése/elrejtése", "keyboard_shortcuts.toggle_hidden": "Tartalmi figyelmeztetéssel ellátott szöveg megjelenítése/elrejtése",
"keyboard_shortcuts.toggle_sensitivity": "Média megjelenítése/elrejtése", "keyboard_shortcuts.toggle_sensitivity": "Média megjelenítése/elrejtése",
"keyboard_shortcuts.toot": "Új bejegyzés írása", "keyboard_shortcuts.toot": "Új bejegyzés írása",
"keyboard_shortcuts.translate": "Bejegyzés lefordítása",
"keyboard_shortcuts.unfocus": "Szerkesztés/keresés fókuszból való kivétele", "keyboard_shortcuts.unfocus": "Szerkesztés/keresés fókuszból való kivétele",
"keyboard_shortcuts.up": "Mozgás felfelé a listában", "keyboard_shortcuts.up": "Mozgás felfelé a listában",
"lightbox.close": "Bezárás", "lightbox.close": "Bezárás",

View file

@ -457,6 +457,7 @@
"keyboard_shortcuts.toggle_hidden": "Monstrar/celar texto detra advertimento de contento", "keyboard_shortcuts.toggle_hidden": "Monstrar/celar texto detra advertimento de contento",
"keyboard_shortcuts.toggle_sensitivity": "Monstrar/celar multimedia", "keyboard_shortcuts.toggle_sensitivity": "Monstrar/celar multimedia",
"keyboard_shortcuts.toot": "Initiar un nove message", "keyboard_shortcuts.toot": "Initiar un nove message",
"keyboard_shortcuts.translate": "a traducer un message",
"keyboard_shortcuts.unfocus": "Disfocalisar le area de composition de texto/de recerca", "keyboard_shortcuts.unfocus": "Disfocalisar le area de composition de texto/de recerca",
"keyboard_shortcuts.up": "Displaciar in alto in le lista", "keyboard_shortcuts.up": "Displaciar in alto in le lista",
"lightbox.close": "Clauder", "lightbox.close": "Clauder",
@ -836,6 +837,7 @@
"status.reblogs.empty": "Necuno ha ancora impulsate iste message. Quando alcuno lo face, le impulsos apparera hic.", "status.reblogs.empty": "Necuno ha ancora impulsate iste message. Quando alcuno lo face, le impulsos apparera hic.",
"status.redraft": "Deler e reconciper", "status.redraft": "Deler e reconciper",
"status.remove_bookmark": "Remover marcapagina", "status.remove_bookmark": "Remover marcapagina",
"status.remove_favourite": "Remover del favoritos",
"status.replied_in_thread": "Respondite in le discussion", "status.replied_in_thread": "Respondite in le discussion",
"status.replied_to": "Respondite a {name}", "status.replied_to": "Respondite a {name}",
"status.reply": "Responder", "status.reply": "Responder",

View file

@ -414,6 +414,7 @@
"interaction_modal.title.reblog": "{name} 님의 게시물을 부스트", "interaction_modal.title.reblog": "{name} 님의 게시물을 부스트",
"interaction_modal.title.reply": "{name} 님의 게시물에 답글", "interaction_modal.title.reply": "{name} 님의 게시물에 답글",
"interaction_modal.title.vote": "{name} 님의 투표에 참여", "interaction_modal.title.vote": "{name} 님의 투표에 참여",
"interaction_modal.username_prompt": "예시: {example}",
"intervals.full.days": "{number} 일", "intervals.full.days": "{number} 일",
"intervals.full.hours": "{number} 시간", "intervals.full.hours": "{number} 시간",
"intervals.full.minutes": "{number} 분", "intervals.full.minutes": "{number} 분",

View file

@ -52,7 +52,7 @@
"account.mute_notifications_short": "Izslēgt paziņojumu skaņu", "account.mute_notifications_short": "Izslēgt paziņojumu skaņu",
"account.mute_short": "Apklusināt", "account.mute_short": "Apklusināt",
"account.muted": "Apklusināts", "account.muted": "Apklusināts",
"account.mutual": "Savstarpējs", "account.mutual": "Abpusēji",
"account.no_bio": "Apraksts nav sniegts.", "account.no_bio": "Apraksts nav sniegts.",
"account.open_original_page": "Atvērt oriģinālo lapu", "account.open_original_page": "Atvērt oriģinālo lapu",
"account.posts": "Ieraksti", "account.posts": "Ieraksti",
@ -85,6 +85,7 @@
"alert.rate_limited.title": "Biežums ierobežots", "alert.rate_limited.title": "Biežums ierobežots",
"alert.unexpected.message": "Radās negaidīta kļūda.", "alert.unexpected.message": "Radās negaidīta kļūda.",
"alert.unexpected.title": "Ups!", "alert.unexpected.title": "Ups!",
"alt_text_badge.title": "Alt teksts",
"announcement.announcement": "Paziņojums", "announcement.announcement": "Paziņojums",
"annual_report.summary.archetype.oracle": "Orākuls", "annual_report.summary.archetype.oracle": "Orākuls",
"annual_report.summary.archetype.replier": "Sabiedriskais tauriņš", "annual_report.summary.archetype.replier": "Sabiedriskais tauriņš",

View file

@ -837,6 +837,7 @@
"status.reblogs.empty": "Ingen har framheva dette tutet enno. Om nokon gjer, så dukkar det opp her.", "status.reblogs.empty": "Ingen har framheva dette tutet enno. Om nokon gjer, så dukkar det opp her.",
"status.redraft": "Slett & skriv på nytt", "status.redraft": "Slett & skriv på nytt",
"status.remove_bookmark": "Fjern bokmerke", "status.remove_bookmark": "Fjern bokmerke",
"status.remove_favourite": "Fjern frå favorittar",
"status.replied_in_thread": "Svara i tråden", "status.replied_in_thread": "Svara i tråden",
"status.replied_to": "Svarte {name}", "status.replied_to": "Svarte {name}",
"status.reply": "Svar", "status.reply": "Svar",

View file

@ -406,6 +406,9 @@
"ignore_notifications_modal.not_followers_title": "Ignoruj powiadomienia od użytkowników którzy cię nie obserwują?", "ignore_notifications_modal.not_followers_title": "Ignoruj powiadomienia od użytkowników którzy cię nie obserwują?",
"ignore_notifications_modal.not_following_title": "Ignoruj powiadomienia od użytkowników których nie obserwujesz?", "ignore_notifications_modal.not_following_title": "Ignoruj powiadomienia od użytkowników których nie obserwujesz?",
"ignore_notifications_modal.private_mentions_title": "Ignoruj powiadomienia o nieproszonych wzmiankach prywatnych?", "ignore_notifications_modal.private_mentions_title": "Ignoruj powiadomienia o nieproszonych wzmiankach prywatnych?",
"interaction_modal.action.favourite": "Aby kontynuować, musisz dodać do ulubionych na swoim koncie.",
"interaction_modal.action.follow": "Aby kontynuować, musisz obserwować ze swojego konta.",
"interaction_modal.no_account_yet": "Nie masz jeszcze konta?",
"interaction_modal.on_another_server": "Na innym serwerze", "interaction_modal.on_another_server": "Na innym serwerze",
"interaction_modal.on_this_server": "Na tym serwerze", "interaction_modal.on_this_server": "Na tym serwerze",
"interaction_modal.title.favourite": "Polub wpis użytkownika {name}", "interaction_modal.title.favourite": "Polub wpis użytkownika {name}",

View file

@ -243,12 +243,12 @@
"dismissable_banner.explore_statuses": "Estas publicações através do fediverse estão ganhando atenção hoje. Publicações mais recentes com mais boosts e favoritos são classificados mais altamente.", "dismissable_banner.explore_statuses": "Estas publicações através do fediverse estão ganhando atenção hoje. Publicações mais recentes com mais boosts e favoritos são classificados mais altamente.",
"dismissable_banner.explore_tags": "Estas hashtags estão ganhando atenção hoje no fediverse. Hashtags usadas por muitas pessoas diferentes são classificadas mais altamente.", "dismissable_banner.explore_tags": "Estas hashtags estão ganhando atenção hoje no fediverse. Hashtags usadas por muitas pessoas diferentes são classificadas mais altamente.",
"dismissable_banner.public_timeline": "Estas são as publicações mais recentes das pessoas no fediverse que as pessoas do {domain} seguem.", "dismissable_banner.public_timeline": "Estas são as publicações mais recentes das pessoas no fediverse que as pessoas do {domain} seguem.",
"domain_block_modal.block": "Servidor de blocos.", "domain_block_modal.block": "Bloquear servidor",
"domain_block_modal.block_account_instead": "Bloco @(nome)", "domain_block_modal.block_account_instead": "Bloquear @{name}",
"domain_block_modal.they_can_interact_with_old_posts": "Pessoas deste servidor podem interagir com suas publicações antigas.", "domain_block_modal.they_can_interact_with_old_posts": "Pessoas deste servidor podem interagir com suas publicações antigas.",
"domain_block_modal.they_cant_follow": "Ninguém deste servidor pode lhe seguir.", "domain_block_modal.they_cant_follow": "Ninguém deste servidor pode lhe seguir.",
"domain_block_modal.they_wont_know": "Eles não saberão que foram bloqueados.", "domain_block_modal.they_wont_know": "Eles não saberão que foram bloqueados.",
"domain_block_modal.title": "Dominio do bloco", "domain_block_modal.title": "Bloquear domínio?",
"domain_block_modal.you_will_lose_num_followers": "Você perderá {followersCount, plural, one {{followersCountDisplay} seguidor} other {{followersCountDisplay} seguidores}} e {followingCount, plural, one {{followingCountDisplay} pessoa que você segue} other {{followingCountDisplay} pessoas que você segue}}.", "domain_block_modal.you_will_lose_num_followers": "Você perderá {followersCount, plural, one {{followersCountDisplay} seguidor} other {{followersCountDisplay} seguidores}} e {followingCount, plural, one {{followingCountDisplay} pessoa que você segue} other {{followingCountDisplay} pessoas que você segue}}.",
"domain_block_modal.you_will_lose_relationships": "Você irá perder todos os seguidores e pessoas que você segue neste servidor.", "domain_block_modal.you_will_lose_relationships": "Você irá perder todos os seguidores e pessoas que você segue neste servidor.",
"domain_block_modal.you_wont_see_posts": "Você não verá postagens ou notificações de usuários neste servidor.", "domain_block_modal.you_wont_see_posts": "Você não verá postagens ou notificações de usuários neste servidor.",

View file

@ -85,7 +85,7 @@
"alert.rate_limited.title": "Limite de tentativas", "alert.rate_limited.title": "Limite de tentativas",
"alert.unexpected.message": "Ocorreu um erro inesperado.", "alert.unexpected.message": "Ocorreu um erro inesperado.",
"alert.unexpected.title": "Bolas!", "alert.unexpected.title": "Bolas!",
"alt_text_badge.title": "Texto alternativo", "alt_text_badge.title": "Texto descritivo",
"announcement.announcement": "Mensagem de manutenção", "announcement.announcement": "Mensagem de manutenção",
"annual_report.summary.archetype.booster": "O caçador de frescura", "annual_report.summary.archetype.booster": "O caçador de frescura",
"annual_report.summary.archetype.lurker": "O espreitador", "annual_report.summary.archetype.lurker": "O espreitador",
@ -244,7 +244,7 @@
"dismissable_banner.explore_tags": "Estas etiquetas estão a ganhar força no fediverso atualmente. As etiquetas que são utilizadas por mais pessoas diferentes são classificadas numa posição mais elevada.", "dismissable_banner.explore_tags": "Estas etiquetas estão a ganhar força no fediverso atualmente. As etiquetas que são utilizadas por mais pessoas diferentes são classificadas numa posição mais elevada.",
"dismissable_banner.public_timeline": "Estas são as publicações públicas mais recentes de pessoas no fediverso que as pessoas em {domain} seguem.", "dismissable_banner.public_timeline": "Estas são as publicações públicas mais recentes de pessoas no fediverso que as pessoas em {domain} seguem.",
"domain_block_modal.block": "Bloquear servidor", "domain_block_modal.block": "Bloquear servidor",
"domain_block_modal.block_account_instead": "Bloquear antes @{name}", "domain_block_modal.block_account_instead": "Em vez disso, bloquear @{name}",
"domain_block_modal.they_can_interact_with_old_posts": "As pessoas deste servidor podem interagir com as tuas publicações antigas.", "domain_block_modal.they_can_interact_with_old_posts": "As pessoas deste servidor podem interagir com as tuas publicações antigas.",
"domain_block_modal.they_cant_follow": "Ninguém deste servidor pode seguir-te.", "domain_block_modal.they_cant_follow": "Ninguém deste servidor pode seguir-te.",
"domain_block_modal.they_wont_know": "Eles não saberão que foram bloqueados.", "domain_block_modal.they_wont_know": "Eles não saberão que foram bloqueados.",
@ -260,7 +260,7 @@
"domain_pill.their_username": "O identificador único dele no seu servidor. É possível encontrar utilizadores com o mesmo nome de utilizador em servidores diferentes.", "domain_pill.their_username": "O identificador único dele no seu servidor. É possível encontrar utilizadores com o mesmo nome de utilizador em servidores diferentes.",
"domain_pill.username": "Nome de utilizador", "domain_pill.username": "Nome de utilizador",
"domain_pill.whats_in_a_handle": "Em que consiste um identificador?", "domain_pill.whats_in_a_handle": "Em que consiste um identificador?",
"domain_pill.who_they_are": "Uma vez que os identificadores dizem quem é alguém e onde está, pode interagir com as pessoas através da rede social de <button>plataformas que suportam ActivityPub</button>.", "domain_pill.who_they_are": "Uma vez que os identificadores dizem quem é alguém e onde está, podes interagir com as pessoas através da rede social de <button>plataformas que suportam ActivityPub</button>.",
"domain_pill.who_you_are": "Uma vez que o teu identificador indica quem és e onde estás, as pessoas podem interagir contigo através da rede social de <button>plataformas que suportam ActivityPub</button>.", "domain_pill.who_you_are": "Uma vez que o teu identificador indica quem és e onde estás, as pessoas podem interagir contigo através da rede social de <button>plataformas que suportam ActivityPub</button>.",
"domain_pill.your_handle": "O teu identificador:", "domain_pill.your_handle": "O teu identificador:",
"domain_pill.your_server": "A tua casa digital, onde se encontram todas as tuas publicações. Não gostas deste? Muda de servidor a qualquer momento e leva também os teus seguidores.", "domain_pill.your_server": "A tua casa digital, onde se encontram todas as tuas publicações. Não gostas deste? Muda de servidor a qualquer momento e leva também os teus seguidores.",
@ -457,6 +457,7 @@
"keyboard_shortcuts.toggle_hidden": "mostrar / esconder texto atrás do aviso de conteúdo", "keyboard_shortcuts.toggle_hidden": "mostrar / esconder texto atrás do aviso de conteúdo",
"keyboard_shortcuts.toggle_sensitivity": "mostrar / ocultar multimédia", "keyboard_shortcuts.toggle_sensitivity": "mostrar / ocultar multimédia",
"keyboard_shortcuts.toot": "criar uma nova publicação", "keyboard_shortcuts.toot": "criar uma nova publicação",
"keyboard_shortcuts.translate": "traduzir uma publicação",
"keyboard_shortcuts.unfocus": "remover o foco da área de texto / pesquisa", "keyboard_shortcuts.unfocus": "remover o foco da área de texto / pesquisa",
"keyboard_shortcuts.up": "mover para cima na lista", "keyboard_shortcuts.up": "mover para cima na lista",
"lightbox.close": "Fechar", "lightbox.close": "Fechar",
@ -641,10 +642,10 @@
"notifications.policy.filter_hint": "Enviar para a caixa de notificações filtradas", "notifications.policy.filter_hint": "Enviar para a caixa de notificações filtradas",
"notifications.policy.filter_limited_accounts_hint": "Limitado pelos moderadores do servidor", "notifications.policy.filter_limited_accounts_hint": "Limitado pelos moderadores do servidor",
"notifications.policy.filter_limited_accounts_title": "Contas moderadas", "notifications.policy.filter_limited_accounts_title": "Contas moderadas",
"notifications.policy.filter_new_accounts.hint": "Criada {days, plural, one {no último dia} other {nos últimos # dias}}", "notifications.policy.filter_new_accounts.hint": "Criadas {days, plural, one {no último dia} other {nos últimos # dias}}",
"notifications.policy.filter_new_accounts_title": "Novas contas", "notifications.policy.filter_new_accounts_title": "Novas contas",
"notifications.policy.filter_not_followers_hint": "Incluindo pessoas que te seguem há menos de {days, plural, one {um dia} other {# dias}}", "notifications.policy.filter_not_followers_hint": "Incluindo pessoas que te seguem há menos de {days, plural, one {um dia} other {# dias}}",
"notifications.policy.filter_not_followers_title": "Pessoas não te seguem", "notifications.policy.filter_not_followers_title": "Pessoas que não te seguem",
"notifications.policy.filter_not_following_hint": "Até que os aproves manualmente", "notifications.policy.filter_not_following_hint": "Até que os aproves manualmente",
"notifications.policy.filter_not_following_title": "Pessoas que não segues", "notifications.policy.filter_not_following_title": "Pessoas que não segues",
"notifications.policy.filter_private_mentions_hint": "Filtrado, a não ser que seja em resposta à tua própria menção ou se seguires o remetente", "notifications.policy.filter_private_mentions_hint": "Filtrado, a não ser que seja em resposta à tua própria menção ou se seguires o remetente",
@ -836,6 +837,7 @@
"status.reblogs.empty": "Ainda ninguém impulsionou esta publicação. Quando alguém o fizer, aparecerá aqui.", "status.reblogs.empty": "Ainda ninguém impulsionou esta publicação. Quando alguém o fizer, aparecerá aqui.",
"status.redraft": "Eliminar e reescrever", "status.redraft": "Eliminar e reescrever",
"status.remove_bookmark": "Retirar dos marcadores", "status.remove_bookmark": "Retirar dos marcadores",
"status.remove_favourite": "Remover dos favoritos",
"status.replied_in_thread": "Responder na conversa", "status.replied_in_thread": "Responder na conversa",
"status.replied_to": "Respondeu a {name}", "status.replied_to": "Respondeu a {name}",
"status.reply": "Responder", "status.reply": "Responder",

View file

@ -141,7 +141,7 @@
"column.bookmarks": "Bokmärken", "column.bookmarks": "Bokmärken",
"column.community": "Lokal tidslinje", "column.community": "Lokal tidslinje",
"column.create_list": "Skapa lista", "column.create_list": "Skapa lista",
"column.direct": "Privata nämningar", "column.direct": "Privata omnämnande",
"column.directory": "Bläddra bland profiler", "column.directory": "Bläddra bland profiler",
"column.domain_blocks": "Blockerade domäner", "column.domain_blocks": "Blockerade domäner",
"column.edit_list": "Redigera lista", "column.edit_list": "Redigera lista",
@ -239,6 +239,10 @@
"disabled_account_banner.text": "Ditt konto {disabledAccount} är för närvarande inaktiverat.", "disabled_account_banner.text": "Ditt konto {disabledAccount} är för närvarande inaktiverat.",
"dismissable_banner.community_timeline": "Dessa är de senaste offentliga inläggen från personer vars konton tillhandahålls av {domain}.", "dismissable_banner.community_timeline": "Dessa är de senaste offentliga inläggen från personer vars konton tillhandahålls av {domain}.",
"dismissable_banner.dismiss": "Avfärda", "dismissable_banner.dismiss": "Avfärda",
"dismissable_banner.explore_links": "Dessa nyhetshistorier delas mest på fediversum idag. Nyare nyhetshistorier som publiceras av fler olika personer rankas högre.",
"dismissable_banner.explore_statuses": "Dessa inlägg från fediversum vinner dragkraft idag. Nyare inlägg som många boostar och favoritmarkerar rankas högre.",
"dismissable_banner.explore_tags": "De här hashtaggarna vinner dragkraft i fediversum idag. Hashtaggar som används av fler olika personer rankas högre.",
"dismissable_banner.public_timeline": "De här är de aktuella publika inlägg från personer i fediversum som personer i {domain} följer.",
"domain_block_modal.block": "Blockera server", "domain_block_modal.block": "Blockera server",
"domain_block_modal.block_account_instead": "Blockera @{name} istället", "domain_block_modal.block_account_instead": "Blockera @{name} istället",
"domain_block_modal.they_can_interact_with_old_posts": "Personer från denna server kan interagera med dina gamla inlägg.", "domain_block_modal.they_can_interact_with_old_posts": "Personer från denna server kan interagera med dina gamla inlägg.",
@ -285,7 +289,7 @@
"empty_column.blocks": "Du har ännu ej blockerat några användare.", "empty_column.blocks": "Du har ännu ej blockerat några användare.",
"empty_column.bookmarked_statuses": "Du har inte bokmärkt några inlägg än. När du bokmärker ett inlägg kommer det synas här.", "empty_column.bookmarked_statuses": "Du har inte bokmärkt några inlägg än. När du bokmärker ett inlägg kommer det synas här.",
"empty_column.community": "Den lokala tidslinjen är tom. Skriv något offentligt för att sätta bollen i rullning!", "empty_column.community": "Den lokala tidslinjen är tom. Skriv något offentligt för att sätta bollen i rullning!",
"empty_column.direct": "Du har inga privata nämningar. När du skickar eller tar emot ett direktmeddelande kommer det att visas här.", "empty_column.direct": "Du har inga privata omnämninande. När du skickar eller tar emot ett direktmeddelande kommer det att visas här.",
"empty_column.domain_blocks": "Det finns ännu inga dolda domäner.", "empty_column.domain_blocks": "Det finns ännu inga dolda domäner.",
"empty_column.explore_statuses": "Ingenting är trendigt just nu. Kom tillbaka senare!", "empty_column.explore_statuses": "Ingenting är trendigt just nu. Kom tillbaka senare!",
"empty_column.favourited_statuses": "Du har inga favoritmarkerade inlägg ännu. När du favoritmärker ett så kommer det att dyka upp här.", "empty_column.favourited_statuses": "Du har inga favoritmarkerade inlägg ännu. När du favoritmärker ett så kommer det att dyka upp här.",
@ -402,7 +406,14 @@
"ignore_notifications_modal.new_accounts_title": "Vill du ignorera aviseringar från nya konton?", "ignore_notifications_modal.new_accounts_title": "Vill du ignorera aviseringar från nya konton?",
"ignore_notifications_modal.not_followers_title": "Vill du ignorera aviseringar från personer som inte följer dig?", "ignore_notifications_modal.not_followers_title": "Vill du ignorera aviseringar från personer som inte följer dig?",
"ignore_notifications_modal.not_following_title": "Vill du blockera aviseringar från personer som du inte följer dig?", "ignore_notifications_modal.not_following_title": "Vill du blockera aviseringar från personer som du inte följer dig?",
"ignore_notifications_modal.private_mentions_title": "Vill du ignorera aviseringar från oönskade privata omnämningar?", "ignore_notifications_modal.private_mentions_title": "Vill du ignorera aviseringar från oombedda privata omnämnanden?",
"interaction_modal.action.favourite": "För att fortsätta, måste du favoritmarkera från ditt konto.",
"interaction_modal.action.follow": "För att fortsätta, måste du följa från ditt konto.",
"interaction_modal.action.reblog": "För att fortsätta, måste du boosta från ditt konto.",
"interaction_modal.action.reply": "För att fortsätta, måste du svara från ditt konto.",
"interaction_modal.action.vote": "För att fortsätta, måste du rösta från ditt konto.",
"interaction_modal.go": "Vidare",
"interaction_modal.no_account_yet": "Har du inget konto än?",
"interaction_modal.on_another_server": "På en annan server", "interaction_modal.on_another_server": "På en annan server",
"interaction_modal.on_this_server": "På denna server", "interaction_modal.on_this_server": "På denna server",
"interaction_modal.title.favourite": "Favoritmarkera {name}s inlägg", "interaction_modal.title.favourite": "Favoritmarkera {name}s inlägg",
@ -410,6 +421,7 @@
"interaction_modal.title.reblog": "Boosta {name}s inlägg", "interaction_modal.title.reblog": "Boosta {name}s inlägg",
"interaction_modal.title.reply": "Svara på {name}s inlägg", "interaction_modal.title.reply": "Svara på {name}s inlägg",
"interaction_modal.title.vote": "Rösta i {name}s enkät", "interaction_modal.title.vote": "Rösta i {name}s enkät",
"interaction_modal.username_prompt": "T.ex. {example}",
"intervals.full.days": "{number, plural, one {# dag} other {# dagar}}", "intervals.full.days": "{number, plural, one {# dag} other {# dagar}}",
"intervals.full.hours": "{number, plural, one {# timme} other {# timmar}}", "intervals.full.hours": "{number, plural, one {# timme} other {# timmar}}",
"intervals.full.minutes": "{number, plural, one {# minut} other {# minuter}}", "intervals.full.minutes": "{number, plural, one {# minut} other {# minuter}}",
@ -419,7 +431,7 @@
"keyboard_shortcuts.column": "Fokusera kolumn", "keyboard_shortcuts.column": "Fokusera kolumn",
"keyboard_shortcuts.compose": "Fokusera skrivfältet", "keyboard_shortcuts.compose": "Fokusera skrivfältet",
"keyboard_shortcuts.description": "Beskrivning", "keyboard_shortcuts.description": "Beskrivning",
"keyboard_shortcuts.direct": "för att öppna privata nämningskolumnen", "keyboard_shortcuts.direct": "för att öppna privata omnämnandekolumnen",
"keyboard_shortcuts.down": "Flytta ner i listan", "keyboard_shortcuts.down": "Flytta ner i listan",
"keyboard_shortcuts.enter": "Öppna inlägg", "keyboard_shortcuts.enter": "Öppna inlägg",
"keyboard_shortcuts.favourite": "Favoritmarkera inlägg", "keyboard_shortcuts.favourite": "Favoritmarkera inlägg",
@ -445,6 +457,7 @@
"keyboard_shortcuts.toggle_hidden": "Visa/gömma text bakom CW", "keyboard_shortcuts.toggle_hidden": "Visa/gömma text bakom CW",
"keyboard_shortcuts.toggle_sensitivity": "Visa/gömma media", "keyboard_shortcuts.toggle_sensitivity": "Visa/gömma media",
"keyboard_shortcuts.toot": "Starta nytt inlägg", "keyboard_shortcuts.toot": "Starta nytt inlägg",
"keyboard_shortcuts.translate": "för att översätta ett inlägg",
"keyboard_shortcuts.unfocus": "Avfokusera skrivfält/sökfält", "keyboard_shortcuts.unfocus": "Avfokusera skrivfält/sökfält",
"keyboard_shortcuts.up": "Flytta uppåt i listan", "keyboard_shortcuts.up": "Flytta uppåt i listan",
"lightbox.close": "Stäng", "lightbox.close": "Stäng",
@ -466,6 +479,7 @@
"lists.delete": "Radera lista", "lists.delete": "Radera lista",
"lists.done": "Klar", "lists.done": "Klar",
"lists.edit": "Redigera lista", "lists.edit": "Redigera lista",
"lists.exclusive": "Dölj medlemmar i Hem flödet",
"lists.exclusive_hint": "Om någon är med på den här listan, göm dem i ditt Hemtidlinje för att undvika att se deras inlägg två gånger.", "lists.exclusive_hint": "Om någon är med på den här listan, göm dem i ditt Hemtidlinje för att undvika att se deras inlägg två gånger.",
"lists.find_users_to_add": "Hitta användare att lägga till", "lists.find_users_to_add": "Hitta användare att lägga till",
"lists.list_members": "Lista medlemmar", "lists.list_members": "Lista medlemmar",
@ -474,12 +488,14 @@
"lists.new_list_name": "Nytt listnamn", "lists.new_list_name": "Nytt listnamn",
"lists.no_lists_yet": "Ännu inga listor.", "lists.no_lists_yet": "Ännu inga listor.",
"lists.no_members_yet": "Inga medlemmar ännu.", "lists.no_members_yet": "Inga medlemmar ännu.",
"lists.no_results_found": "Inga resultat hittades.",
"lists.remove_member": "Ta bort", "lists.remove_member": "Ta bort",
"lists.replies_policy.followed": "Alla användare som följs", "lists.replies_policy.followed": "Alla användare som följs",
"lists.replies_policy.list": "Medlemmar i listan", "lists.replies_policy.list": "Medlemmar i listan",
"lists.replies_policy.none": "Ingen", "lists.replies_policy.none": "Ingen",
"lists.save": "Spara", "lists.save": "Spara",
"lists.search": "Sök", "lists.search": "Sök",
"lists.show_replies_to": "Inkludera svar från listmedlemmar till",
"load_pending": "{count, plural, one {# nytt objekt} other {# nya objekt}}", "load_pending": "{count, plural, one {# nytt objekt} other {# nya objekt}}",
"loading_indicator.label": "Laddar…", "loading_indicator.label": "Laddar…",
"media_gallery.hide": "Dölj", "media_gallery.hide": "Dölj",
@ -500,7 +516,7 @@
"navigation_bar.bookmarks": "Bokmärken", "navigation_bar.bookmarks": "Bokmärken",
"navigation_bar.community_timeline": "Lokal tidslinje", "navigation_bar.community_timeline": "Lokal tidslinje",
"navigation_bar.compose": "Författa nytt inlägg", "navigation_bar.compose": "Författa nytt inlägg",
"navigation_bar.direct": "Privata nämningar", "navigation_bar.direct": "Privata omnämnande",
"navigation_bar.discover": "Upptäck", "navigation_bar.discover": "Upptäck",
"navigation_bar.domain_blocks": "Dolda domäner", "navigation_bar.domain_blocks": "Dolda domäner",
"navigation_bar.explore": "Utforska", "navigation_bar.explore": "Utforska",
@ -532,12 +548,14 @@
"notification.annual_report.view": "Visa #Wrapstodon", "notification.annual_report.view": "Visa #Wrapstodon",
"notification.favourite": "{name} favoritmarkerade ditt inlägg", "notification.favourite": "{name} favoritmarkerade ditt inlägg",
"notification.favourite.name_and_others_with_link": "{name} och <a>{count, plural, one {# annan} other {# andra}}</a> har favoritmarkerat ditt inlägg", "notification.favourite.name_and_others_with_link": "{name} och <a>{count, plural, one {# annan} other {# andra}}</a> har favoritmarkerat ditt inlägg",
"notification.favourite_pm": "{name} favoritmarkerade ditt privata omnämnande",
"notification.favourite_pm.name_and_others_with_link": "{name} och <a>{count, plural, one {# annan} other {# andra}}</a> favoritmarkerade ditt privata omnämnande",
"notification.follow": "{name} följer dig", "notification.follow": "{name} följer dig",
"notification.follow.name_and_others": "{name} och <a>{count, plural, one {# annan} other {# andra}}</a> följer dig", "notification.follow.name_and_others": "{name} och <a>{count, plural, one {# annan} other {# andra}}</a> följer dig",
"notification.follow_request": "{name} har begärt att följa dig", "notification.follow_request": "{name} har begärt att följa dig",
"notification.follow_request.name_and_others": "{name} och {count, plural, one {# en annan} other {# andra}} har bett att följa dig", "notification.follow_request.name_and_others": "{name} och {count, plural, one {# en annan} other {# andra}} har bett att följa dig",
"notification.label.mention": "Nämn", "notification.label.mention": "Nämn",
"notification.label.private_mention": "Privat nämning", "notification.label.private_mention": "Privat omnämnande",
"notification.label.private_reply": "Privata svar", "notification.label.private_reply": "Privata svar",
"notification.label.reply": "Svar", "notification.label.reply": "Svar",
"notification.mention": "Nämn", "notification.mention": "Nämn",
@ -640,6 +658,7 @@
"onboarding.follows.done": "Färdig", "onboarding.follows.done": "Färdig",
"onboarding.follows.empty": "Tyvärr kan inga resultat visas just nu. Du kan prova att använda sökfunktionen eller utforska sidan för att hitta personer att följa, eller försök igen senare.", "onboarding.follows.empty": "Tyvärr kan inga resultat visas just nu. Du kan prova att använda sökfunktionen eller utforska sidan för att hitta personer att följa, eller försök igen senare.",
"onboarding.follows.search": "Sök", "onboarding.follows.search": "Sök",
"onboarding.follows.title": "Följ människor för att komma igång",
"onboarding.profile.discoverable": "Gör min profil upptäckbar", "onboarding.profile.discoverable": "Gör min profil upptäckbar",
"onboarding.profile.discoverable_hint": "När du väljer att vara upptäckbar på Mastodon kan dina inlägg visas i sök- och trendresultat, och din profil kan föreslås för personer med liknande intressen som du.", "onboarding.profile.discoverable_hint": "När du väljer att vara upptäckbar på Mastodon kan dina inlägg visas i sök- och trendresultat, och din profil kan föreslås för personer med liknande intressen som du.",
"onboarding.profile.display_name": "Visningsnamn", "onboarding.profile.display_name": "Visningsnamn",
@ -677,6 +696,8 @@
"privacy_policy.title": "Integritetspolicy", "privacy_policy.title": "Integritetspolicy",
"recommended": "Rekommenderas", "recommended": "Rekommenderas",
"refresh": "Läs om", "refresh": "Läs om",
"regeneration_indicator.please_stand_by": "Vänligen vänta.",
"regeneration_indicator.preparing_your_home_feed": "Förbereder ditt hemflöde…",
"relative_time.days": "{number}d", "relative_time.days": "{number}d",
"relative_time.full.days": "{number, plural, one {# dag} other {# dagar}} sedan", "relative_time.full.days": "{number, plural, one {# dag} other {# dagar}} sedan",
"relative_time.full.hours": "{number, plural, one {# timme} other {# timmar}} sedan", "relative_time.full.hours": "{number, plural, one {# timme} other {# timmar}} sedan",
@ -760,15 +781,18 @@
"search_results.accounts": "Profiler", "search_results.accounts": "Profiler",
"search_results.all": "Alla", "search_results.all": "Alla",
"search_results.hashtags": "Hashtaggar", "search_results.hashtags": "Hashtaggar",
"search_results.no_results": "Inga resultat.",
"search_results.no_search_yet": "Prova att söka efter inlägg, profiler eller hashtags.",
"search_results.see_all": "Visa alla", "search_results.see_all": "Visa alla",
"search_results.statuses": "Inlägg", "search_results.statuses": "Inlägg",
"search_results.title": "Sök efter \"{q}\"",
"server_banner.about_active_users": "Personer som använt denna server de senaste 30 dagarna (månatligt aktiva användare)", "server_banner.about_active_users": "Personer som använt denna server de senaste 30 dagarna (månatligt aktiva användare)",
"server_banner.active_users": "aktiva användare", "server_banner.active_users": "aktiva användare",
"server_banner.administered_by": "Administrerad av:", "server_banner.administered_by": "Administrerad av:",
"server_banner.is_one_of_many": "{domain} är en av de många oberoende Mastodon-servrar som du kan använda för att delta i Fediversen.", "server_banner.is_one_of_many": "{domain} är en av de många oberoende Mastodon-servrar som du kan använda för att delta i Fediversen.",
"server_banner.server_stats": "Serverstatistik:", "server_banner.server_stats": "Serverstatistik:",
"sign_in_banner.create_account": "Skapa konto", "sign_in_banner.create_account": "Skapa konto",
"sign_in_banner.follow_anyone": "Följ vem som helst över Fediverse och se allt i kronologisk ordning. Inga algoritmer, inga annonser och inga klickbeten i sikte.", "sign_in_banner.follow_anyone": "Följ vem som helst över Fediversum och se allt i kronologisk ordning. Inga algoritmer, annonser eller klickbeten i sikte.",
"sign_in_banner.mastodon_is": "Mastodon är det bästa sättet att hänga med i vad som händer.", "sign_in_banner.mastodon_is": "Mastodon är det bästa sättet att hänga med i vad som händer.",
"sign_in_banner.sign_in": "Logga in", "sign_in_banner.sign_in": "Logga in",
"sign_in_banner.sso_redirect": "Logga in eller registrera dig", "sign_in_banner.sso_redirect": "Logga in eller registrera dig",
@ -783,8 +807,8 @@
"status.copy": "Kopiera inläggslänk", "status.copy": "Kopiera inläggslänk",
"status.delete": "Radera", "status.delete": "Radera",
"status.detailed_status": "Detaljerad samtalsvy", "status.detailed_status": "Detaljerad samtalsvy",
"status.direct": "Nämn @{name} privat", "status.direct": "Omnämn @{name} privat",
"status.direct_indicator": "Privat nämning", "status.direct_indicator": "Privat omnämnande",
"status.edit": "Redigera", "status.edit": "Redigera",
"status.edited": "Senast ändrad {date}", "status.edited": "Senast ändrad {date}",
"status.edited_x_times": "Redigerad {count, plural, one {{count} gång} other {{count} gånger}}", "status.edited_x_times": "Redigerad {count, plural, one {{count} gång} other {{count} gånger}}",
@ -813,6 +837,7 @@
"status.reblogs.empty": "Ingen har boostat detta inlägg än. När någon gör det kommer de synas här.", "status.reblogs.empty": "Ingen har boostat detta inlägg än. När någon gör det kommer de synas här.",
"status.redraft": "Radera & gör om", "status.redraft": "Radera & gör om",
"status.remove_bookmark": "Ta bort bokmärke", "status.remove_bookmark": "Ta bort bokmärke",
"status.remove_favourite": "Ta bort från Favoriter",
"status.replied_in_thread": "Svarade i tråden", "status.replied_in_thread": "Svarade i tråden",
"status.replied_to": "Svarade på {name}", "status.replied_to": "Svarade på {name}",
"status.reply": "Svara", "status.reply": "Svara",
@ -834,6 +859,7 @@
"subscribed_languages.target": "Ändra språkprenumerationer för {target}", "subscribed_languages.target": "Ändra språkprenumerationer för {target}",
"tabs_bar.home": "Hem", "tabs_bar.home": "Hem",
"tabs_bar.notifications": "Aviseringar", "tabs_bar.notifications": "Aviseringar",
"terms_of_service.title": "Användarvillkor",
"time_remaining.days": "{number, plural, one {# dag} other {# dagar}} kvar", "time_remaining.days": "{number, plural, one {# dag} other {# dagar}} kvar",
"time_remaining.hours": "{number, plural, one {# timme} other {# timmar}} kvar", "time_remaining.hours": "{number, plural, one {# timme} other {# timmar}} kvar",
"time_remaining.minutes": "{number, plural, one {# minut} other {# minuter}} kvar", "time_remaining.minutes": "{number, plural, one {# minut} other {# minuter}} kvar",

View file

@ -659,6 +659,10 @@ code {
} }
} }
} }
.status-card {
contain: unset;
}
} }
.block-icon { .block-icon {

View file

@ -13,7 +13,7 @@ class ActivityPub::TagManager
}.freeze }.freeze
def public_collection?(uri) def public_collection?(uri)
uri == COLLECTIONS[:public] || uri == 'as:Public' || uri == 'Public' uri == COLLECTIONS[:public] || %w(as:Public Public).include?(uri)
end end
def url_for(target) def url_for(target)

View file

@ -3,14 +3,18 @@
module ApplicationExtension module ApplicationExtension
extend ActiveSupport::Concern extend ActiveSupport::Concern
APP_NAME_LIMIT = 60
APP_REDIRECT_URI_LIMIT = 2_000
APP_WEBSITE_LIMIT = 2_000
included do included do
include Redisable include Redisable
has_many :created_users, class_name: 'User', foreign_key: 'created_by_application_id', inverse_of: :created_by_application has_many :created_users, class_name: 'User', foreign_key: 'created_by_application_id', inverse_of: :created_by_application
validates :name, length: { maximum: 60 } validates :name, length: { maximum: APP_NAME_LIMIT }
validates :website, url: true, length: { maximum: 2_000 }, if: :website? validates :redirect_uri, length: { maximum: APP_REDIRECT_URI_LIMIT }
validates :redirect_uri, length: { maximum: 2_000 } validates :website, url: true, length: { maximum: APP_WEBSITE_LIMIT }, if: :website?
# The relationship used between Applications and AccessTokens is using # The relationship used between Applications and AccessTokens is using
# dependent: delete_all, which means the ActiveRecord callback in # dependent: delete_all, which means the ActiveRecord callback in

View file

@ -111,16 +111,10 @@ class Request
end end
begin begin
# If we are using a persistent connection, we have to
# read every response to be able to move forward at all.
# However, simply calling #to_s or #flush may not be safe,
# as the response body, if malicious, could be too big
# for our memory. So we use the #body_with_limit method
response.body_with_limit if http_client.persistent?
yield response if block_given? yield response if block_given?
ensure ensure
http_client.close unless http_client.persistent? response.truncated_body if http_client.persistent? && !response.connection.finished_request?
http_client.close unless http_client.persistent? && response.connection.finished_request?
end end
end end

View file

@ -107,23 +107,23 @@ class Account < ApplicationRecord
validates_with UniqueUsernameValidator, if: -> { will_save_change_to_username? } validates_with UniqueUsernameValidator, if: -> { will_save_change_to_username? }
# Remote user validations, also applies to internal actors # Remote user validations, also applies to internal actors
validates :username, format: { with: USERNAME_ONLY_RE }, if: -> { (!local? || actor_type == 'Application') && will_save_change_to_username? } validates :username, format: { with: USERNAME_ONLY_RE }, if: -> { (remote? || actor_type_application?) && will_save_change_to_username? }
# Remote user validations # Remote user validations
validates :uri, presence: true, unless: :local?, on: :create validates :uri, presence: true, unless: :local?, on: :create
# Local user validations # Local user validations
validates :username, format: { with: /\A[a-z0-9_]+\z/i }, length: { maximum: USERNAME_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_username? && actor_type != 'Application' } validates :username, format: { with: /\A[a-z0-9_]+\z/i }, length: { maximum: USERNAME_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_username? && !actor_type_application? }
validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? && actor_type != 'Application' } validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? && !actor_type_application? }
validates :display_name, length: { maximum: DISPLAY_NAME_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_display_name? } validates :display_name, length: { maximum: DISPLAY_NAME_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_display_name? }
validates :note, note_length: { maximum: NOTE_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_note? } validates :note, note_length: { maximum: NOTE_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_note? }
validates :fields, length: { maximum: DEFAULT_FIELDS_SIZE }, if: -> { local? && will_save_change_to_fields? } validates :fields, length: { maximum: DEFAULT_FIELDS_SIZE }, if: -> { local? && will_save_change_to_fields? }
validates_with EmptyProfileFieldNamesValidator, if: -> { local? && will_save_change_to_fields? } validates_with EmptyProfileFieldNamesValidator, if: -> { local? && will_save_change_to_fields? }
with_options on: :create do with_options on: :create, if: :local? do
validates :uri, absence: true, if: :local? validates :followers_url, absence: true
validates :inbox_url, absence: true, if: :local? validates :inbox_url, absence: true
validates :shared_inbox_url, absence: true, if: :local? validates :shared_inbox_url, absence: true
validates :followers_url, absence: true, if: :local? validates :uri, absence: true
end end
normalizes :username, with: ->(username) { username.squish } normalizes :username, with: ->(username) { username.squish }
@ -186,6 +186,10 @@ class Account < ApplicationRecord
domain.nil? domain.nil?
end end
def remote?
domain.present?
end
def moved? def moved?
moved_to_account_id.present? moved_to_account_id.present?
end end
@ -204,6 +208,10 @@ class Account < ApplicationRecord
self.actor_type = ActiveModel::Type::Boolean.new.cast(val) ? 'Service' : 'Person' self.actor_type = ActiveModel::Type::Boolean.new.cast(val) ? 'Service' : 'Person'
end end
def actor_type_application?
actor_type == 'Application'
end
def group? def group?
actor_type == 'Group' actor_type == 'Group'
end end

View file

@ -27,6 +27,7 @@ class AccountWarning < ApplicationRecord
suspend: 4_000, suspend: 4_000,
}, suffix: :action }, suffix: :action
APPEAL_WINDOW = 20.days
RECENT_PERIOD = 3.months.freeze RECENT_PERIOD = 3.months.freeze
normalizes :text, with: ->(text) { text.to_s }, apply_to_nil: true normalizes :text, with: ->(text) { text.to_s }, apply_to_nil: true
@ -49,6 +50,10 @@ class AccountWarning < ApplicationRecord
overruled_at.present? overruled_at.present?
end end
def appeal_eligible?
created_at >= APPEAL_WINDOW.ago
end
def to_log_human_identifier def to_log_human_identifier
target_account.acct target_account.acct
end end

View file

@ -16,8 +16,6 @@
# updated_at :datetime not null # updated_at :datetime not null
# #
class Appeal < ApplicationRecord class Appeal < ApplicationRecord
MAX_STRIKE_AGE = 20.days
TEXT_LENGTH_LIMIT = 2_000 TEXT_LENGTH_LIMIT = 2_000
belongs_to :account belongs_to :account
@ -68,6 +66,6 @@ class Appeal < ApplicationRecord
private private
def validate_time_frame def validate_time_frame
errors.add(:base, I18n.t('strikes.errors.too_late')) if strike.created_at < MAX_STRIKE_AGE.ago errors.add(:base, I18n.t('strikes.errors.too_late')) unless strike.appeal_eligible?
end end
end end

View file

@ -84,6 +84,10 @@ class Form::AdminSettings
flavour_and_skin flavour_and_skin
).freeze ).freeze
DIGEST_KEYS = %i(
custom_css
).freeze
OVERRIDEN_SETTINGS = { OVERRIDEN_SETTINGS = {
authorized_fetch: :authorized_fetch_mode?, authorized_fetch: :authorized_fetch_mode?,
}.freeze }.freeze
@ -137,6 +141,8 @@ class Form::AdminSettings
KEYS.each do |key| KEYS.each do |key|
next if PSEUDO_KEYS.include?(key) || !instance_variable_defined?(:"@#{key}") next if PSEUDO_KEYS.include?(key) || !instance_variable_defined?(:"@#{key}")
cache_digest_value(key) if DIGEST_KEYS.include?(key)
if UPLOAD_KEYS.include?(key) if UPLOAD_KEYS.include?(key)
public_send(key).save public_send(key).save
else else
@ -156,6 +162,18 @@ class Form::AdminSettings
private private
def cache_digest_value(key)
Rails.cache.delete(:"setting_digest_#{key}")
key_value = instance_variable_get(:"@#{key}")
if key_value.present?
Rails.cache.write(
:"setting_digest_#{key}",
Digest::SHA256.hexdigest(key_value)
)
end
end
def typecast_value(key, value) def typecast_value(key, value)
if BOOLEAN_KEYS.include?(key) if BOOLEAN_KEYS.include?(key)
value == '1' value == '1'

View file

@ -64,21 +64,31 @@ class NotificationGroup < ActiveModelSerializers::Model
binds = [ binds = [
account_id, account_id,
SAMPLE_ACCOUNTS_SIZE, SAMPLE_ACCOUNTS_SIZE,
pagination_range.begin,
pagination_range.end,
ActiveRecord::Relation::QueryAttribute.new('group_keys', group_keys, ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(ActiveModel::Type::String.new)), ActiveRecord::Relation::QueryAttribute.new('group_keys', group_keys, ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(ActiveModel::Type::String.new)),
pagination_range.begin || 0,
] ]
binds << pagination_range.end unless pagination_range.end.nil?
upper_bound_cond = begin
if pagination_range.end.nil?
''
elsif pagination_range.exclude_end?
'AND id < $5'
else
'AND id <= $5'
end
end
ActiveRecord::Base.connection.select_all(<<~SQL.squish, 'grouped_notifications', binds).cast_values.to_h { |k, *values| [k, values] } ActiveRecord::Base.connection.select_all(<<~SQL.squish, 'grouped_notifications', binds).cast_values.to_h { |k, *values| [k, values] }
SELECT SELECT
groups.group_key, groups.group_key,
(SELECT id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id <= $4 ORDER BY id DESC LIMIT 1), (SELECT id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key #{upper_bound_cond} ORDER BY id DESC LIMIT 1),
array(SELECT from_account_id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id <= $4 ORDER BY id DESC LIMIT $2), array(SELECT from_account_id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key #{upper_bound_cond} ORDER BY id DESC LIMIT $2),
(SELECT count(*) FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id <= $4) AS notifications_count, (SELECT count(*) FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key #{upper_bound_cond}) AS notifications_count,
(SELECT id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id >= $3 ORDER BY id ASC LIMIT 1) AS min_id, (SELECT id FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id >= $4 ORDER BY id ASC LIMIT 1) AS min_id,
(SELECT created_at FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id <= $4 ORDER BY id DESC LIMIT 1) (SELECT created_at FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key #{upper_bound_cond} ORDER BY id DESC LIMIT 1)
FROM FROM
unnest($5::text[]) AS groups(group_key); unnest($3::text[]) AS groups(group_key);
SQL SQL
else else
binds = [ binds = [

View file

@ -61,11 +61,7 @@ class Poll < ApplicationRecord
votes.where(account: account).pluck(:choice) votes.where(account: account).pluck(:choice)
end end
delegate :local?, to: :account delegate :local?, :remote?, to: :account
def remote?
!local?
end
def emojis def emojis
@emojis ||= CustomEmoji.from_text(options.join(' '), account.domain) @emojis ||= CustomEmoji.from_text(options.join(' '), account.domain)

View file

@ -16,7 +16,7 @@ class Trends::Links < Trends::Base
class Query < Trends::Query class Query < Trends::Query
def to_arel def to_arel
scope = PreviewCard.joins(:trend).reorder(score: :desc) scope = PreviewCard.joins(:trend).reorder(score: :desc)
scope = scope.reorder(language_order_clause.desc, score: :desc) if preferred_languages.present? scope = scope.merge(language_order_clause) if preferred_languages.present?
scope = scope.merge(PreviewCardTrend.allowed) if @allowed scope = scope.merge(PreviewCardTrend.allowed) if @allowed
scope = scope.offset(@offset) if @offset.present? scope = scope.offset(@offset) if @offset.present?
scope = scope.limit(@limit) if @limit.present? scope = scope.limit(@limit) if @limit.present?
@ -26,7 +26,7 @@ class Trends::Links < Trends::Base
private private
def language_order_clause def language_order_clause
Arel::Nodes::Case.new.when(PreviewCardTrend.arel_table[:language].in(preferred_languages)).then(1).else(0) language_order_for(PreviewCardTrend)
end end
end end

View file

@ -94,6 +94,13 @@ class Trends::Query
to_arel.to_a to_arel.to_a
end end
def language_order_for(trend_class)
trend_class
.reorder(nil)
.in_order_of(:language, [preferred_languages], filter: false)
.order(score: :desc)
end
def preferred_languages def preferred_languages
if @account&.chosen_languages.present? if @account&.chosen_languages.present?
@account.chosen_languages @account.chosen_languages

View file

@ -15,7 +15,7 @@ class Trends::Statuses < Trends::Base
class Query < Trends::Query class Query < Trends::Query
def to_arel def to_arel
scope = Status.joins(:trend).reorder(score: :desc) scope = Status.joins(:trend).reorder(score: :desc)
scope = scope.reorder(language_order_clause.desc, score: :desc) if preferred_languages.present? scope = scope.merge(language_order_clause) if preferred_languages.present?
scope = scope.merge(StatusTrend.allowed) if @allowed scope = scope.merge(StatusTrend.allowed) if @allowed
scope = scope.not_excluded_by_account(@account).not_domain_blocked_by_account(@account) if @account.present? scope = scope.not_excluded_by_account(@account).not_domain_blocked_by_account(@account) if @account.present?
scope = scope.offset(@offset) if @offset.present? scope = scope.offset(@offset) if @offset.present?
@ -26,7 +26,7 @@ class Trends::Statuses < Trends::Base
private private
def language_order_clause def language_order_clause
Arel::Nodes::Case.new.when(StatusTrend.arel_table[:language].in(preferred_languages)).then(1).else(0) language_order_for(StatusTrend)
end end
end end

View file

@ -15,7 +15,8 @@ class Trends::Tags < Trends::Base
class Query < Trends::Query class Query < Trends::Query
def to_arel def to_arel
scope = Tag.joins(:trend).reorder(language_order_clause.desc, score: :desc) scope = Tag.joins(:trend).reorder(score: :desc)
scope = scope.merge(language_order_clause) if preferred_languages.present?
scope = scope.merge(TagTrend.allowed) if @allowed scope = scope.merge(TagTrend.allowed) if @allowed
scope = scope.offset(@offset) if @offset.present? scope = scope.offset(@offset) if @offset.present?
scope = scope.limit(@limit) if @limit.present? scope = scope.limit(@limit) if @limit.present?
@ -25,7 +26,7 @@ class Trends::Tags < Trends::Base
private private
def language_order_clause def language_order_clause
Arel::Nodes::Case.new.when(TagTrend.arel_table[:language].in(preferred_languages)).then(1).else(0) language_order_for(TagTrend)
end end
end end

View file

@ -42,6 +42,7 @@ class UserRole < ApplicationRecord
NOBODY_POSITION = -1 NOBODY_POSITION = -1
POSITION_LIMIT = (2**31) - 1 POSITION_LIMIT = (2**31) - 1
CSS_COLORS = /\A#?(?:[A-F0-9]{3}){1,2}\z/i # CSS-style hex colors
module Flags module Flags
NONE = 0 NONE = 0
@ -90,7 +91,7 @@ class UserRole < ApplicationRecord
attr_writer :current_account attr_writer :current_account
validates :name, presence: true, unless: :everyone? validates :name, presence: true, unless: :everyone?
validates :color, format: { with: /\A#?(?:[A-F0-9]{3}){1,2}\z/i }, unless: -> { color.blank? } validates :color, format: { with: CSS_COLORS }, if: :color?
validates :position, numericality: { in: (-POSITION_LIMIT..POSITION_LIMIT) } validates :position, numericality: { in: (-POSITION_LIMIT..POSITION_LIMIT) }
validate :validate_permissions_elevation validate :validate_permissions_elevation
@ -101,9 +102,6 @@ class UserRole < ApplicationRecord
before_validation :set_position before_validation :set_position
scope :assignable, -> { where.not(id: EVERYONE_ROLE_ID).order(position: :asc) } scope :assignable, -> { where.not(id: EVERYONE_ROLE_ID).order(position: :asc) }
scope :highlighted, -> { where(highlighted: true) }
scope :with_color, -> { where.not(color: [nil, '']) }
scope :providing_styles, -> { highlighted.with_color }
has_many :users, inverse_of: :role, foreign_key: 'role_id', dependent: :nullify has_many :users, inverse_of: :role, foreign_key: 'role_id', dependent: :nullify

View file

@ -6,7 +6,7 @@ class AccountWarningPolicy < ApplicationPolicy
end end
def appeal? def appeal?
target? && record.created_at >= Appeal::MAX_STRIKE_AGE.ago target? && record.appeal_eligible?
end end
private private

View file

@ -12,7 +12,7 @@ class Admin::StatusPolicy < ApplicationPolicy
end end
def show? def show?
role.can?(:manage_reports, :manage_users) && (record.public_visibility? || record.unlisted_visibility? || record.reported? || viewable_through_normal_policy?) role.can?(:manage_reports, :manage_users) && eligible_to_show?
end end
def destroy? def destroy?
@ -29,6 +29,10 @@ class Admin::StatusPolicy < ApplicationPolicy
private private
def eligible_to_show?
record.distributable? || record.reported? || viewable_through_normal_policy?
end
def viewable_through_normal_policy? def viewable_through_normal_policy?
StatusPolicy.new(current_account, record, @preloaded_relations).show? StatusPolicy.new(current_account, record, @preloaded_relations).show?
end end

View file

@ -10,10 +10,16 @@ class UserRolePolicy < ApplicationPolicy
end end
def update? def update?
role.can?(:manage_roles) && (role.overrides?(record) || role.id == record.id) role.can?(:manage_roles) && (role.overrides?(record) || self_editing?)
end end
def destroy? def destroy?
!record.everyone? && role.can?(:manage_roles) && role.overrides?(record) && role.id != record.id !record.everyone? && role.can?(:manage_roles) && role.overrides?(record) && !self_editing?
end
private
def self_editing?
role.id == record.id
end end
end end

View file

@ -8,8 +8,4 @@ class REST::ScheduledStatusSerializer < ActiveModel::Serializer
def id def id
object.id.to_s object.id.to_s
end end
def params
object.params.without('application_id')
end
end end

View file

@ -4,7 +4,7 @@
.report-notes__item__header .report-notes__item__header
%span.username %span.username
= link_to report_note.account.username, admin_account_path(report_note.account_id) = link_to report_note.account.username, admin_account_path(report_note.account_id)
%time.relative-formatted{ datetime: report_note.created_at.iso8601 } %time.relative-formatted{ datetime: report_note.created_at.iso8601, title: report_note.created_at }
= l report_note.created_at.to_date = l report_note.created_at.to_date
.report-notes__item__content .report-notes__item__content

View file

@ -18,7 +18,7 @@
= link_to report.account.username, admin_account_path(report.account_id) = link_to report.account.username, admin_account_path(report.account_id)
- else - else
= link_to report.account.domain, admin_instance_path(report.account.domain) = link_to report.account.domain, admin_instance_path(report.account.domain)
%time.relative-formatted{ datetime: report.created_at.iso8601 } %time.relative-formatted{ datetime: report.created_at.iso8601, title: report.created_at }
= l report.created_at.to_date = l report.created_at.to_date
.report-notes__item__content .report-notes__item__content
= simple_format(h(report.comment)) = simple_format(h(report.comment))

View file

@ -1,7 +1,7 @@
.announcements-list__item .announcements-list__item
- if can?(:update, role) - if can?(:update, role)
= link_to edit_admin_role_path(role), class: 'announcements-list__item__title' do = link_to edit_admin_role_path(role), class: 'announcements-list__item__title' do
%span.user-role{ class: "user-role-#{role.id}" } %span.user-role
= material_symbol 'group' = material_symbol 'group'
- if role.everyone? - if role.everyone?
@ -10,7 +10,7 @@
= role.name = role.name
- else - else
%span.announcements-list__item__title %span.announcements-list__item__title
%span.user-role{ class: "user-role-#{role.id}" } %span.user-role
= material_symbol 'group' = material_symbol 'group'
- if role.everyone? - if role.everyone?

View file

@ -29,8 +29,8 @@
%div %div
- if defined?(show_apps_buttons) && show_apps_buttons - if defined?(show_apps_buttons) && show_apps_buttons
.email-welcome-apps-btns .email-welcome-apps-btns
= link_to image_tag(frontend_asset_url('images/mailer-new/store-icons/btn-app-store.png'), alt: t('user_mailer.welcome.apps_ios_action'), width: 120, height: 40), 'https://apps.apple.com/app/mastodon-for-iphone-and-ipad/id1571998974' = link_to image_tag(frontend_asset_url('images/mailer-new/store-icons/btn-app-store.png'), alt: t('user_mailer.welcome.apps_ios_action'), width: 120, height: 40), app_store_url_ios
= link_to image_tag(frontend_asset_url('images/mailer-new/store-icons/btn-google-play.png'), alt: t('user_mailer.welcome.apps_android_action'), width: 120, height: 40), 'https://play.google.com/store/apps/details?id=org.joinmastodon.android' = link_to image_tag(frontend_asset_url('images/mailer-new/store-icons/btn-google-play.png'), alt: t('user_mailer.welcome.apps_android_action'), width: 120, height: 40), app_store_url_android
- elsif defined?(button_text) && defined?(button_url) && defined?(checked) && !checked - elsif defined?(button_text) && defined?(button_url) && defined?(checked) && !checked
= render 'application/mailer/button', text: button_text, url: button_url, has_arrow: false = render 'application/mailer/button', text: button_text, url: button_url, has_arrow: false
/[if mso] /[if mso]

View file

@ -2,9 +2,3 @@
<%= raw custom_css_styles %> <%= raw custom_css_styles %>
<%- end %> <%- end %>
<%- @user_roles.each do |role| %>
.user-role-<%= role.id %> {
--user-role-accent: <%= role.color %>;
}
<%- end %>

View file

@ -66,7 +66,7 @@
.report-notes__item__header .report-notes__item__header
%span.username %span.username
= link_to @appeal.account.username, can?(:show, @appeal.account) ? admin_account_path(@appeal.account_id) : short_account_url(@appeal.account) = link_to @appeal.account.username, can?(:show, @appeal.account) ? admin_account_path(@appeal.account_id) : short_account_url(@appeal.account)
%time.relative-formatted{ datetime: @appeal.created_at.iso8601 } %time.relative-formatted{ datetime: @appeal.created_at.iso8601, title: @appeal.created_at }
= l @appeal.created_at.to_date = l @appeal.created_at.to_date
.report-notes__item__content .report-notes__item__content

View file

@ -35,7 +35,7 @@
= csrf_meta_tags unless skip_csrf_meta_tags? = csrf_meta_tags unless skip_csrf_meta_tags?
%meta{ name: 'style-nonce', content: request.content_security_policy_nonce } %meta{ name: 'style-nonce', content: request.content_security_policy_nonce }
= stylesheet_link_tag custom_css_path, skip_pipeline: true, host: root_url, media: 'all' = custom_stylesheet
= yield :header_tags = yield :header_tags

View file

@ -30,8 +30,8 @@
5. <%= t('user_mailer.welcome.apps_title') %> 5. <%= t('user_mailer.welcome.apps_title') %>
<%= t('user_mailer.welcome.apps_step') %> <%= t('user_mailer.welcome.apps_step') %>
* iOS: https://apps.apple.com/app/mastodon-for-iphone-and-ipad/id1571998974 * iOS: <%= app_store_url_ios %>
* Android: https://play.google.com/store/apps/details?id=org.joinmastodon.android * Android: <%= app_store_url_android %>
--- ---

View file

@ -0,0 +1,18 @@
# frozen_string_literal: true
Rails.application.config.to_prepare do
custom_css = begin
Setting.custom_css
rescue ActiveRecord::AdapterError # Running without a database, not migrated, no connection, etc
nil
end
if custom_css.present?
Rails
.cache
.write(
:setting_digest_custom_css,
Digest::SHA256.hexdigest(custom_css)
)
end
end

View file

@ -24,6 +24,8 @@ cy:
models: models:
account: account:
attributes: attributes:
fields:
fields_with_values_missing_labels: yn cynnwys gwerthoedd gyda labeli coll
username: username:
invalid: rhaid iddo gynnwys dim ond llythrennau, rhifau a thanlinellau invalid: rhaid iddo gynnwys dim ond llythrennau, rhifau a thanlinellau
reserved: wedi ei neilltuo reserved: wedi ei neilltuo

View file

@ -24,6 +24,8 @@ gl:
models: models:
account: account:
attributes: attributes:
fields:
fields_with_values_missing_labels: contén valores aos que lle faltan etiquetas
username: username:
invalid: só letras, números e trazo baixo invalid: só letras, números e trazo baixo
reserved: está reservado reserved: está reservado

View file

@ -24,6 +24,8 @@ hu:
models: models:
account: account:
attributes: attributes:
fields:
fields_with_values_missing_labels: hiányzó címkékkel rendelkező értékeket tartalmaz
username: username:
invalid: csak betűket, számokat vagy alávonást tartalmazhat invalid: csak betűket, számokat vagy alávonást tartalmazhat
reserved: foglalt reserved: foglalt

View file

@ -24,6 +24,8 @@ ia:
models: models:
account: account:
attributes: attributes:
fields:
fields_with_values_missing_labels: contine valores con etiquettas perdite
username: username:
invalid: debe continer solmente litteras, numeros e lineettas basse invalid: debe continer solmente litteras, numeros e lineettas basse
reserved: es reservate reserved: es reservate

View file

@ -24,6 +24,8 @@ lv:
models: models:
account: account:
attributes: attributes:
fields:
fields_with_values_missing_labels: satur vērtības ar trūkstošām iezīmēm
username: username:
invalid: drīkst saturēt tikai burtus, ciparus un pasvītras invalid: drīkst saturēt tikai burtus, ciparus un pasvītras
reserved: ir rezervēts reserved: ir rezervēts
@ -39,6 +41,11 @@ lv:
attributes: attributes:
data: data:
malformed: ir nepareizi veidots malformed: ir nepareizi veidots
list_account:
attributes:
account_id:
taken: jau ir sarakstā
must_be_following: jābūt kontam, kuram seko
status: status:
attributes: attributes:
reblog: reblog:

View file

@ -24,6 +24,8 @@ pt-PT:
models: models:
account: account:
attributes: attributes:
fields:
fields_with_values_missing_labels: contém valores com etiquetas em falta
username: username:
invalid: deve conter apenas letras, números e traços inferiores invalid: deve conter apenas letras, números e traços inferiores
reserved: está reservado reserved: está reservado

View file

@ -24,6 +24,8 @@ sv:
models: models:
account: account:
attributes: attributes:
fields:
fields_with_values_missing_labels: innehåller värden med saknade etiketter
username: username:
invalid: endast bokstäver, siffror och understrykning invalid: endast bokstäver, siffror och understrykning
reserved: är reserverat reserved: är reserverat

View file

@ -25,7 +25,7 @@ tr:
account: account:
attributes: attributes:
fields: fields:
fields_with_values_missing_labels: değerleri eksik etiketler içeriyor fields_with_values_missing_labels: etiketleri eksik değerler içeriyor
username: username:
invalid: sadece harfler, sayılar ve alt çizgiler invalid: sadece harfler, sayılar ve alt çizgiler
reserved: kullanılamaz reserved: kullanılamaz

View file

@ -134,6 +134,7 @@ sk:
media: Mediálne prílohy media: Mediálne prílohy
mutes: Stíšenia mutes: Stíšenia
notifications: Upozornenia notifications: Upozornenia
profile: Váš Mastodon profil
push: Upozornenia push push: Upozornenia push
reports: Hlásenia reports: Hlásenia
search: Vyhľadávanie search: Vyhľadávanie

View file

@ -214,6 +214,7 @@ fo:
enable_user: Ger brúkara virknan enable_user: Ger brúkara virknan
memorialize_account: Minnst til Konto memorialize_account: Minnst til Konto
promote_user: Vís fram Brúkara promote_user: Vís fram Brúkara
publish_terms_of_service: Útgev tænastutreytir
reject_appeal: Avvís mótmali reject_appeal: Avvís mótmali
reject_user: Avvís Brúkara reject_user: Avvís Brúkara
remove_avatar_user: Sletta Avatar remove_avatar_user: Sletta Avatar
@ -278,6 +279,7 @@ fo:
enable_user_html: "%{name} gjørdi innritan virkna fyri brúkaran %{target}" enable_user_html: "%{name} gjørdi innritan virkna fyri brúkaran %{target}"
memorialize_account_html: "%{name} broytti kontuna hjá %{target} til eina minnissíðu" memorialize_account_html: "%{name} broytti kontuna hjá %{target} til eina minnissíðu"
promote_user_html: "%{name} flutti brúkaran %{target} fram" promote_user_html: "%{name} flutti brúkaran %{target} fram"
publish_terms_of_service_html: "%{name} útgav dagføringar til tænastutreytirnar"
reject_appeal_html: "%{name} avvísti umsjónaráheitan frá %{target}" reject_appeal_html: "%{name} avvísti umsjónaráheitan frá %{target}"
reject_user_html: "%{name} avvísti skráseting hjá %{target}" reject_user_html: "%{name} avvísti skráseting hjá %{target}"
remove_avatar_user_html: "%{name} strikaði eftirgjørda skapningin hjá %{target}" remove_avatar_user_html: "%{name} strikaði eftirgjørda skapningin hjá %{target}"
@ -925,6 +927,32 @@ fo:
search: Leita search: Leita
title: Frámerki title: Frámerki
updated_msg: Frámerkjastillingar dagførdar updated_msg: Frámerkjastillingar dagførdar
terms_of_service:
back: Aftur til tænastutreytir
changelog: Hvat er broytt
create: Brúka tínar egnu
current: Núverandi
draft: Kladda
generate: Brúka leist
generates:
action: Framleið
chance_to_review_html: "<strong>Framleiddu tænastutreytirnar verða ikki útgivnar av sær sjálvum.</strong> Tú fær møguleika at eftirhyggja úrslitini. Vinarliga útfyll neyðugu smálutirnar fyri at halda fram."
explanation_html: Leisturin við tænastutreytum er einans til kunningar og skal ikki fatast sum løgfrøðislig ráðgeving yvirhøvur. Vinarliga spyr tín egna løgfrøðisliga ráðgeva um tína støðu og ítøkiligu løgfrøðisligu spurningarnar hjá tær.
title: Uppseting av tænastutreytum
history: Søga
live: Beinleiðis
no_history: Enn eru ongar skrásettar broytingar í tænastutreytunum.
no_terms_of_service_html: Í løtuni hevur tú ongar tænastutreytir uppsettar. Hugsanin við tænastutreytum er at veita greidleika og at verja teg ímóti møguligum ábyrgdum í ósemjum við tínar brúkarar.
notified_on_html: Fráboðan latin brúkarum %{date}
notify_users: Gev brúkarum fráboðan
preview:
explanation_html: 'Teldubrævið verður sent til <strong>%{display_count} brúkarar</strong>, sum hava stovna kontu áðrenn %{date}. Fylgjandi tekstur kemur við í teldubrævið:'
send_preview: Send undanvísing til %{email}
title: Undanvís fráboðan um tænastutreytir
publish: Útgev
published_on_html: Útgivið %{date}
save_draft: Goym kladdu
title: Tænastutreytir
title: Umsiting title: Umsiting
trends: trends:
allow: Loyv allow: Loyv
@ -1156,6 +1184,7 @@ fo:
set_new_password: Áset nýtt loyniorð set_new_password: Áset nýtt loyniorð
setup: setup:
email_below_hint_html: Kekka mappuna við ruskposti ella bið um ein annan. Tú kanst rætta teldupostadressuna, um hon er skeiv. email_below_hint_html: Kekka mappuna við ruskposti ella bið um ein annan. Tú kanst rætta teldupostadressuna, um hon er skeiv.
email_settings_hint_html: Trýst á leinkið, sum vit sendu til %{email} fyri at byrja at brúka Mastodon. Vit bíða beint her.
link_not_received: Fekk tú einki leinki? link_not_received: Fekk tú einki leinki?
new_confirmation_instructions_sent: Tú fer at móttaka eitt nýtt teldubræv við váttanarleinkinum um nakrar fáar minuttir! new_confirmation_instructions_sent: Tú fer at móttaka eitt nýtt teldubræv við váttanarleinkinum um nakrar fáar minuttir!
title: Kekka innbakkan hjá tær title: Kekka innbakkan hjá tær
@ -1164,6 +1193,7 @@ fo:
title: Rita inn á %{domain} title: Rita inn á %{domain}
sign_up: sign_up:
manual_review: Tilmeldingar til %{domain} fara ígjøgnum eina manuella eftirkanning av okkara kjakleiðarum. Fyri at hjálpa okkum at skunda undir skrásetingina, skriva eitt sindur um teg sjálva/n og hví tú vil hava eina kontu á %{domain}. manual_review: Tilmeldingar til %{domain} fara ígjøgnum eina manuella eftirkanning av okkara kjakleiðarum. Fyri at hjálpa okkum at skunda undir skrásetingina, skriva eitt sindur um teg sjálva/n og hví tú vil hava eina kontu á %{domain}.
preamble: Við eini kontu á hesum Mastodon ambætaranum ber til hjá tær at fylgja ein og hvønn annan persón á fediversinum, óansæð hvar teirra konta er hýst.
title: Latum okkum fáa teg settan upp á %{domain}. title: Latum okkum fáa teg settan upp á %{domain}.
status: status:
account_status: Kontustøða account_status: Kontustøða
@ -1175,6 +1205,7 @@ fo:
view_strikes: Vís eldri atsóknir móti tíni kontu view_strikes: Vís eldri atsóknir móti tíni kontu
too_fast: Oyðublaðið innsent ov skjótt, royn aftur. too_fast: Oyðublaðið innsent ov skjótt, royn aftur.
use_security_key: Brúka trygdarlykil use_security_key: Brúka trygdarlykil
user_agreement_html: Eg havi lisið og taki undir við <a href="%{terms_of_service_path}" target="_blank">tænastutreytunum</a> og <a href="%{privacy_policy_path}" target="_blank">privatlívspolitikkinum</a>
author_attribution: author_attribution:
example_title: Tekstadømi example_title: Tekstadømi
hint_html: Skrivar tú tíðindi ella greinar til bloggin uttanfyri Mastodon? Her kanst tú stýra, hvussu tú verður tilsipað/ur, tá ið títt tilfar verður deilt á Mastodon. hint_html: Skrivar tú tíðindi ella greinar til bloggin uttanfyri Mastodon? Her kanst tú stýra, hvussu tú verður tilsipað/ur, tá ið títt tilfar verður deilt á Mastodon.
@ -1836,6 +1867,8 @@ fo:
too_late: Tað er ov seint at kæra hesa atsókn too_late: Tað er ov seint at kæra hesa atsókn
tags: tags:
does_not_match_previous_name: samsvarar ikki við undanfarna navnið does_not_match_previous_name: samsvarar ikki við undanfarna navnið
terms_of_service:
title: Tænastutreytir
themes: themes:
contrast: Mastodon (høgur kontrastur) contrast: Mastodon (høgur kontrastur)
default: Mastodon (myrkt) default: Mastodon (myrkt)
@ -1896,6 +1929,15 @@ fo:
further_actions_html: Var hetta ikki tú, so mæla vit til, at tú %{action} beinan vegin og at tú ger váttan í tveimum stigum virkna fyri at konta tín kann vera trygg. further_actions_html: Var hetta ikki tú, so mæla vit til, at tú %{action} beinan vegin og at tú ger váttan í tveimum stigum virkna fyri at konta tín kann vera trygg.
subject: Atgongd er fingin til kontu tína frá eini nýggjari IP adressu subject: Atgongd er fingin til kontu tína frá eini nýggjari IP adressu
title: Ein nýggj innritan title: Ein nýggj innritan
terms_of_service_changed:
agreement: Við framhaldandi at brúka %{domain} góðtekur tú hesar treytir. Tekur tú ikki undir við dagførdu treytunum, so kanst tú til einhvørja tíð uppsiga avtaluna við %{domain} við at strika kontu tína.
changelog: 'Í stuttum merkir henda dagføringin:'
description: 'Tú móttekur hetta teldubrævið, tí at vit gera nakrar broytingar í okkara tænastutreytum á %{domain}. Vit eggja tær til at eftirhyggja dagførdu treytirnar her:'
description_html: Tú móttekur hetta teldubrævið, tí at vit gera nakrar broytingar í okkara tænastutreytum á %{domain}. Vit eggja tær til at eftirhyggja <a href="%{path}" target="_blank">dagførdu og samlaðu treytirnar her</a>.
sign_off: "%{domain} toymið"
subject: Dagføringar til okkara tænastutreytir
subtitle: Tænastutreytirnar hjá %{domain} eru við at verða broyttar
title: Týdningarmikil dagføring
warning: warning:
appeal: Innsend eina kæru appeal: Innsend eina kæru
appeal_description: Trýrt tú, at hetta er ein feilur, so kanst tú senda eina kæru til starvsfólkini á %{instance}. appeal_description: Trýrt tú, at hetta er ein feilur, so kanst tú senda eina kæru til starvsfólkini á %{instance}.

View file

@ -1859,9 +1859,9 @@ gl:
'63113904': 2 anos '63113904': 2 anos
'7889238': 3 meses '7889238': 3 meses
min_age_label: Límite temporal min_age_label: Límite temporal
min_favs: Manter as publicacións favorecidas polo menos min_favs: Manter publicacións favorecidas polo menos
min_favs_hint: Non elimina ningunha das túas publicacións que recibiron alomenos esta cantidade de favorecementos. Deixa en branco para eliminar publicacións independentemente do número de favorecementos min_favs_hint: Non elimina ningunha das túas publicacións que recibiron alomenos esta cantidade de favorecementos. Deixa en branco para eliminar publicacións independentemente do número de favorecementos
min_reblogs: Manter publicacións promovidas máis de min_reblogs: Manter publicacións promovidas polo menos
min_reblogs_hint: Non elimina ningunha das túas publicacións se foron promovidas máis deste número de veces. Deixa en branco para eliminar publicacións independentemente do seu número de promocións min_reblogs_hint: Non elimina ningunha das túas publicacións se foron promovidas máis deste número de veces. Deixa en branco para eliminar publicacións independentemente do seu número de promocións
stream_entries: stream_entries:
sensitive_content: Contido sensible sensitive_content: Contido sensible

View file

@ -217,6 +217,7 @@ lv:
enable_user: Ieslēgt Lietotāju enable_user: Ieslēgt Lietotāju
memorialize_account: Saglabāt Kontu Piemiņai memorialize_account: Saglabāt Kontu Piemiņai
promote_user: Izceltt Lietotāju promote_user: Izceltt Lietotāju
publish_terms_of_service: Publicēt pakalpojuma izmantošanas noteikumus
reject_appeal: Noraidīt Apelāciju reject_appeal: Noraidīt Apelāciju
reject_user: Noraidīt lietotāju reject_user: Noraidīt lietotāju
remove_avatar_user: Noņemt profila attēlu remove_avatar_user: Noņemt profila attēlu
@ -273,6 +274,7 @@ lv:
enable_user_html: "%{name} iespējoja pieteikšanos lietotājam %{target}" enable_user_html: "%{name} iespējoja pieteikšanos lietotājam %{target}"
memorialize_account_html: "%{name} pārvērta %{target} kontu par atmiņas lapu" memorialize_account_html: "%{name} pārvērta %{target} kontu par atmiņas lapu"
promote_user_html: "%{name} paaugstināja lietotāju %{target}" promote_user_html: "%{name} paaugstināja lietotāju %{target}"
publish_terms_of_service_html: "%{name} padarīja pieejamus pakalpojuma izmantošanas noteikumu atjauninājumus"
reject_appeal_html: "%{name} noraidīja satura pārraudzības lēmuma iebildumu no %{target}" reject_appeal_html: "%{name} noraidīja satura pārraudzības lēmuma iebildumu no %{target}"
reject_user_html: "%{name} noraidīja reģistrēšanos no %{target}" reject_user_html: "%{name} noraidīja reģistrēšanos no %{target}"
remove_avatar_user_html: "%{name} noņēma %{target} profila attēlu" remove_avatar_user_html: "%{name} noņēma %{target} profila attēlu"
@ -641,7 +643,7 @@ lv:
create_and_resolve: Atrisināt ar piezīmi create_and_resolve: Atrisināt ar piezīmi
create_and_unresolve: Atvērt atkārtoti ar piezīmi create_and_unresolve: Atvērt atkārtoti ar piezīmi
delete: Dzēst delete: Dzēst
placeholder: Apraksti veiktās darbības vai citus saistītus atjauninājumus... placeholder: Jāapraksta veiktās darbības vai jebkuri citi saistītie atjauninājumi...
title: Piezīmes title: Piezīmes
notes_description_html: Skati un atstāj piezīmes citiem moderatoriem un sev nākotnei notes_description_html: Skati un atstāj piezīmes citiem moderatoriem un sev nākotnei
processed_msg: 'Pārskats #%{id} veiksmīgi apstrādāts' processed_msg: 'Pārskats #%{id} veiksmīgi apstrādāts'
@ -746,13 +748,13 @@ lv:
rules: rules:
add_new: Pievienot noteikumu add_new: Pievienot noteikumu
delete: Dzēst delete: Dzēst
description_html: Lai gan lielākā daļa apgalvo, ka ir izlasījuši pakalpojumu sniegšanas noteikumus un piekrīt tiem, parasti cilvēki to izlasa tikai pēc problēmas rašanās. <strong>Padariet vienkāršāku sava servera noteikumu uztveršanu, veidojot tos vienkāršā sarakstā pa punktiem.</strong> Centieties, lai atsevišķi noteikumi būtu īsi un vienkārši, taču arī nesadaliet tos daudzos atsevišķos vienumos. description_html: Kaut arī lielākā daļa apgalvo, ka ir lasījuši un piekrīt pakalpojuma izmantošanas noteikumiem, parasti cilvēki tos neizlasa, līdz rodas sarežģījumi. <strong>Padari vienkāršāku sava servera noteikumu pārskatīšanu, sniedzot tos vienkāršā uzsvēruma punktu sarakstā!</strong> Jāmēģina atsevišķus noteikumus veidot īsus un vienkāršus, bet jāmēģina arī tos nesadalīt daudzos atsevišķos vienumos.
edit: Labot nosacījumu edit: Labot nosacījumu
empty: Servera noteikumi vēl nav definēti. empty: Vēl nav pievienots neviens servera noteikums.
title: Servera noteikumi title: Servera noteikumi
settings: settings:
about: about:
manage_rules: Pārvaldīt servera nosacījumus manage_rules: Pārvaldīt servera noteikumus
preamble: Sniedz padziļinātu informāciju par to, kā serveris tiek darbināts, moderēts un finansēts. preamble: Sniedz padziļinātu informāciju par to, kā serveris tiek darbināts, moderēts un finansēts.
rules_hint: Noteikumiem, kas taviem lietotājiem ir jāievēro, ir īpaša sadaļa. rules_hint: Noteikumiem, kas taviem lietotājiem ir jāievēro, ir īpaša sadaļa.
title: Par title: Par
@ -821,6 +823,7 @@ lv:
back_to_account: Atpakaļ uz konta lapu back_to_account: Atpakaļ uz konta lapu
back_to_report: Atpakaļ uz paziņojumu lapu back_to_report: Atpakaļ uz paziņojumu lapu
batch: batch:
add_to_report: 'Pievienot atskaitei #%{id}'
remove_from_report: Noņemt no ziņojuma remove_from_report: Noņemt no ziņojuma
report: Ziņojums report: Ziņojums
contents: Saturs contents: Saturs
@ -832,13 +835,17 @@ lv:
media: media:
title: Multivide title: Multivide
metadata: Metadati metadata: Metadati
no_history: Šis ieraksts nav bijis labots
no_status_selected: Neviena ziņa netika mainīta, jo neviena netika atlasīta no_status_selected: Neviena ziņa netika mainīta, jo neviena netika atlasīta
open: Atvērt ziņu open: Atvērt ziņu
original_status: Oriģinālā ziņa original_status: Oriģinālā ziņa
reblogs: Reblogi reblogs: Reblogi
replied_to_html: Atbildēja %{acct_link}
status_changed: Ziņa mainīta status_changed: Ziņa mainīta
status_title: Publicēja @%{name} status_title: Publicēja @%{name}
title: Konta ieraksti - @%{name}
trending: Aktuāli trending: Aktuāli
view_publicly: Skatīt publiski
visibility: Redzamība visibility: Redzamība
with_media: Ar multividi with_media: Ar multividi
strikes: strikes:
@ -876,8 +883,8 @@ lv:
message_html: 'Nesaderīga Elasticsearch versija: %{value}' message_html: 'Nesaderīga Elasticsearch versija: %{value}'
version_comparison: Darbojas Elasticsearch %{running_version}, tomēr ir nepieciešama %{required_version} version_comparison: Darbojas Elasticsearch %{running_version}, tomēr ir nepieciešama %{required_version}
rules_check: rules_check:
action: Pārvaldīt servera nosacījumus action: Pārvaldīt servera noteikumus
message_html: Tu neesi definējis nevienu servera nosacījumu. message_html: Nav pievienots neviens servera noteikums.
sidekiq_process_check: sidekiq_process_check:
message_html: Rindā(s) %{value} nedarbojas neviens Sidekiq process. Lūdzu, pārskati savu Sidekiq konfigurāciju message_html: Rindā(s) %{value} nedarbojas neviens Sidekiq process. Lūdzu, pārskati savu Sidekiq konfigurāciju
software_version_check: software_version_check:
@ -907,16 +914,42 @@ lv:
name: Nosaukums name: Nosaukums
newest: Jaunākie newest: Jaunākie
oldest: Vecākie oldest: Vecākie
open: Apskatīt publiski
reset: Atiestatīt reset: Atiestatīt
review: Pārskatīt stāvokli review: Pārskatīt stāvokli
search: Meklēt search: Meklēt
title: Tēmturi title: Tēmturi
updated_msg: Tēmtura iestatījumi ir veiksmīgi atjaunināti updated_msg: Tēmtura iestatījumi ir veiksmīgi atjaunināti
terms_of_service: terms_of_service:
back: Atpakaļ uz pakalpojuma izmantošanas noteikumiem
changelog: Kas ir mainījies changelog: Kas ir mainījies
create: Izmantot savus
current: Pašreizējie
draft: Melnraksts
generate: Izmantot sagatavi
generates:
action: Izveidot
chance_to_review_html: "<strong>Izveidotie pakalpojuma izmantošanas noteikumi netiks automātiski publicēti.</strong> Būs iespēja izskatīt iznākumu. Lūgums norādīt nepieciešamo informāciju, lai turpinātu."
explanation_html: Pakalpojuma izmantošanas noteikumu sagatave tiek piedāvāta tikai izzināšanas nolūkam, un to nevajadzētu izmantot kā juridisku padomu jebkurā jautājumā. Lūgums sazināties ar savu juridisko padomdevēju par saviem apstākļiem un noteiktiem juridiskiem jautājumiem.
title: Pakalpojuma izmantošānas noteikumu uzstādīšana
history: Vēsture history: Vēsture
live: Darbībā
no_history: Nav ierakstu par pakalpojuma izmantošanas noteikumu izmaiņām.
no_terms_of_service_html: Pašlaik nav uzstādīti pakalpojuma izmantošanas noteikumi. Tie ir paredzēti, lai sniegtu skaidrību un aizsargātu no iespējamas atbildības strīdos ar lietotājiem.
notified_on_html: Lietotājiem paziņots %{date}
notify_users: Paziņot lietotājiem
preview:
explanation_html: 'E-pasta ziņojums tiks nosūtīts <strong>%{display_count} lietotājiem</strong>, kuri ir reģistrējušies pirms %{date}. Šis teksts tiks iekļauts e-pasta ziņojumā:'
send_preview: Nosūtīt priekšskatījumu uz %{email}
send_to_all:
one: Nosūtīt %{display_count} e-pasta ziņojumu
other: Nosūtīt %{display_count} e-pasta ziņojumus
zero: Nosūtīt %{display_count} e-pasta ziņojumu
title: Priekškatīt pakalpojuma izmantošanas noteikumu paziņojumu
publish: Publicēt publish: Publicēt
published_on_html: Publicēts %{date} published_on_html: Publicēti %{date}
save_draft: Saglabāt melnrakstu
title: Pakalpojuma izmantošanas noteikumi
title: Pārvaldība title: Pārvaldība
trends: trends:
allow: Atļaut allow: Atļaut
@ -1157,6 +1190,7 @@ lv:
view_strikes: Skati iepriekšējos brīdinājumus par savu kontu view_strikes: Skati iepriekšējos brīdinājumus par savu kontu
too_fast: Veidlapa ir iesniegta pārāk ātri, mēģini vēlreiz. too_fast: Veidlapa ir iesniegta pārāk ātri, mēģini vēlreiz.
use_security_key: Lietot drošības atslēgu use_security_key: Lietot drošības atslēgu
user_agreement_html: Es esmu izlasījis un piekrītu <a href="%{terms_of_service_path}" target="_blank">pakalpojuma izmantošanas noteikumiem</a> un <a href="%{privacy_policy_path}" target="_blank">privātuma nosacījumiem</a>
author_attribution: author_attribution:
example_title: Parauga teksts example_title: Parauga teksts
more_from_html: Vairāk no %{name} more_from_html: Vairāk no %{name}
@ -1777,6 +1811,8 @@ lv:
too_late: Brīdinājuma apstrīdēšanas laiks ir nokavēts too_late: Brīdinājuma apstrīdēšanas laiks ir nokavēts
tags: tags:
does_not_match_previous_name: nesakrīt ar iepriekšējo nosaukumu does_not_match_previous_name: nesakrīt ar iepriekšējo nosaukumu
terms_of_service:
title: Pakalpojuma izmantošanas noteikumi
themes: themes:
contrast: Mastodon (Augsts kontrasts) contrast: Mastodon (Augsts kontrasts)
default: Mastodon (Tumšs) default: Mastodon (Tumšs)
@ -1826,6 +1862,15 @@ lv:
further_actions_html: Ja tas nebiji tu, iesakām nekavējoties %{action} un iespējot divu faktoru autentifikāciju, lai tavs konts būtu drošībā. further_actions_html: Ja tas nebiji tu, iesakām nekavējoties %{action} un iespējot divu faktoru autentifikāciju, lai tavs konts būtu drošībā.
subject: Tavam kontam ir piekļūts no jaunas IP adreses subject: Tavam kontam ir piekļūts no jaunas IP adreses
title: Jauna pieteikšanās title: Jauna pieteikšanās
terms_of_service_changed:
agreement: Ar %{domain} izmantošanas tuprināšanu tiek piekrists šiem noteikumiem. Ja ir iebildumi pret atjauninātajiem noteikumiem, savu piekrišanu var atcelt jebkurā laikā ar sava konta izdzēšanu.
changelog: 'Šeit īsumā ir aprakstīts, ko šis atjauninājums nozīmē:'
description: 'Šis e-pasta ziņojums tika saņemts, jo mēs veicam dažas izmaiņas savos pakalpojuma izmantošanas noteikumos %{domain}. Mēs aicinām pārskatīt pilnus atjauninātos noteikumus šeit:'
description_html: Šis e-pasta ziņojums tika saņemts, jo mēs veicam dažas izmaiņas savos pakalpojuma izmantošanas noteikumos %{domain}. Mēs aicinām pārskatīt <a href="%{path}" target="_blank">pilnus atjauninātos noteikumus šeit</a>.
sign_off: "%{domain} komanda"
subject: Mūsu pakalpojuma izmantošanas noteikumu atjauninājumi
subtitle: Mainās %{domain} pakalpojuma izmantošanas noteikumi
title: Svarīgs atjauninājums
warning: warning:
appeal: Iesniegt apelāciju appeal: Iesniegt apelāciju
appeal_description: Ja uzskatāt, ka tā ir kļūda, varat iesniegt apelāciju %{instance} darbiniekiem. appeal_description: Ja uzskatāt, ka tā ir kļūda, varat iesniegt apelāciju %{instance} darbiniekiem.

View file

@ -10,7 +10,7 @@ pt-PT:
followers: followers:
one: Seguidor one: Seguidor
other: Seguidores other: Seguidores
following: A seguir following: Seguindo
instance_actor_flash: Esta conta é um ator virtual utilizado para representar o servidor em si e não um utilizador individual. É utilizada para efeitos de federação e não deve ser suspensa. instance_actor_flash: Esta conta é um ator virtual utilizado para representar o servidor em si e não um utilizador individual. É utilizada para efeitos de federação e não deve ser suspensa.
last_active: última atividade last_active: última atividade
link_verified_on: A posse desta hiperligação foi verificada em %{date} link_verified_on: A posse desta hiperligação foi verificada em %{date}

View file

@ -130,6 +130,17 @@ fo:
show_application: Óansæð, so er altíð møguligt hjá tær at síggja, hvør app postaði tín post. show_application: Óansæð, so er altíð møguligt hjá tær at síggja, hvør app postaði tín post.
tag: tag:
name: Tú kanst einans broyta millum stórar og smáar stavir, til dømis fyri at gera tað meira lesiligt name: Tú kanst einans broyta millum stórar og smáar stavir, til dømis fyri at gera tað meira lesiligt
terms_of_service:
changelog: Kunnu vera uppbygdar við Markdown syntaksi.
text: Kunnu vera uppbygdar við Markdown syntaksi.
terms_of_service_generator:
admin_email: Løgfrøðisligar fráboðanir fata um rættarligar mótfráboðanir, úrskurðir, áheitanir um at taka niður og løgfrøðisligar áheitanir.
arbitration_address: Kann vera tann sami sum fysiski bústaðurin omanfyri ella "N/A", um teldupostur verður brúktur
arbitration_website: Kann vera ein vevformularur ella "N/A", um teldupostur verður brúktur
dmca_address: Fyristøðufólk og -feløg í USA brúka bústaðin, ið er skrásettur í DMCA Designated Agent Directory. Ein postsmoguskráseting er tøk, um biðið verður um hana beinleiðis. Brúka DMCA Designated Agent Post Office Box Waiver Request fyri at senda teldubræv til Copyright Office og greið frá, at tú er ein kjakleiðari, sum virkar heimanifrá, og at tú er bangin fyri hevnd ella afturløning fyri tí, tú ger, og at tú hevur tørv á at brúka eina postsmogu fyri at fjala heimabústaðin fyri almenninginum.
dmca_email: Kann vera sami teldupoststaður, sum er brúktur til "Teldupoststaður fyri løgfrøðisligar fráboðanir" omanfyri
domain: Makaleys eyðmerking av nettænastuni, sum tú veitir.
jurisdiction: Lista landið, har sum tann, ið rindar rokningarnar, livir. Er tað eitt felag ella ein onnur eind, lista landið, har tað er skrásett, umframt býin, økið, umveldið ella statin, alt eftir hvat er hóskandi.
user: user:
chosen_languages: Tá hetta er valt, verða einans postar í valdum málum vístir á almennum tíðarlinjum chosen_languages: Tá hetta er valt, verða einans postar í valdum málum vístir á almennum tíðarlinjum
role: Leikluturin stýrir hvørji rættindi, brúkarin hevur. role: Leikluturin stýrir hvørji rættindi, brúkarin hevur.
@ -319,6 +330,17 @@ fo:
name: Tvíkrossur name: Tvíkrossur
trendable: Loyv hesum frámerki at síggjast undir rákum trendable: Loyv hesum frámerki at síggjast undir rákum
usable: Loyv postum at brúka hetta frámerki lokalt usable: Loyv postum at brúka hetta frámerki lokalt
terms_of_service:
changelog: Hvat er broytt?
text: Tænastutreytir
terms_of_service_generator:
admin_email: Teldupoststaður fyri løgfrøðisligar fráboðanir
arbitration_address: Fysisk adressa fyri gerðarrættarfráboðanir
arbitration_website: Heimasíða har gerðarrættarfráboðanir kunnu innlatast
dmca_address: Fysiskur bústaður fyri DMCA/copyright fráboðanir
dmca_email: Teldubústaður fyri DMCA/copyright fráboðanir
domain: Navnaøki
jurisdiction: Løgdømi
user: user:
role: Leiklutur role: Leiklutur
time_zone: Tíðarsona time_zone: Tíðarsona

View file

@ -174,7 +174,7 @@ fr:
admin_account_action: admin_account_action:
include_statuses: Inclure les messages signalés dans le courriel include_statuses: Inclure les messages signalés dans le courriel
send_email_notification: Notifier lutilisateur par courriel send_email_notification: Notifier lutilisateur par courriel
text: Attention personnalisée text: Avertissement personnalisé
type: Action type: Action
types: types:
disable: Désactiver disable: Désactiver

View file

@ -53,7 +53,7 @@ lv:
locale: Lietotāja saskarnes, e-pasta ziņojumu un push paziņojumu valoda locale: Lietotāja saskarnes, e-pasta ziņojumu un push paziņojumu valoda
password: Izmanto vismaz 8 rakstzīmes password: Izmanto vismaz 8 rakstzīmes
phrase: Tiks saskaņots neatkarīgi no ziņas teksta reģistra vai satura brīdinājuma phrase: Tiks saskaņots neatkarīgi no ziņas teksta reģistra vai satura brīdinājuma
scopes: Kuriem API lietojumprogrammai būs atļauta piekļuve. Ja izvēlies augstākā līmeņa tvērumu, tev nav jāatlasa atsevišķi vienumi. scopes: Kuriem API lietotnei būs ļauts piekļūt. Ja atlasa augstākā līmeņa tvērumu, nav nepieciešamas atlasīt atsevišķus.
setting_aggregate_reblogs: Nerādīt jaunus izcēlumus ziņām, kas nesen tika palielinātas (ietekmē tikai nesen saņemtos palielinājumus) setting_aggregate_reblogs: Nerādīt jaunus izcēlumus ziņām, kas nesen tika palielinātas (ietekmē tikai nesen saņemtos palielinājumus)
setting_always_send_emails: Parasti e-pasta paziņojumi netiek sūtīti, kad aktīvi izmantojat Mastodon setting_always_send_emails: Parasti e-pasta paziņojumi netiek sūtīti, kad aktīvi izmantojat Mastodon
setting_default_sensitive: Sensitīva multivide pēc noklusējuma ir paslēpti, un tos var atklāt, noklikšķinot setting_default_sensitive: Sensitīva multivide pēc noklusējuma ir paslēpti, un tos var atklāt, noklikšķinot
@ -119,8 +119,8 @@ lv:
sign_up_requires_approval: Jaunām reģistrācijām būs nepieciešams tavs apstiprinājums sign_up_requires_approval: Jaunām reģistrācijām būs nepieciešams tavs apstiprinājums
severity: Izvēlies, kas notiks ar pieprasījumiem no šīs IP adreses severity: Izvēlies, kas notiks ar pieprasījumiem no šīs IP adreses
rule: rule:
hint: Izvēles. Sniedz vairāk informācijas par nosacījumu hint: Izvēles. Sniedz vairāk informācijas par noteikumu
text: Apraksti nosacījumus vai prasības šī servera lietotājiem. Centies, lai tas būtu īss un vienkāršs text: Jāapraksta nosacījums vai prasība šī servera lietotājiem. Jāmēģina to veidot īsu un vienkāršu
sessions: sessions:
otp: 'Ievadi divfaktoru kodu, ko ģenerējusi tava tālruņa lietotne, vai izmanto kādu no atkopšanas kodiem:' otp: 'Ievadi divfaktoru kodu, ko ģenerējusi tava tālruņa lietotne, vai izmanto kādu no atkopšanas kodiem:'
webauthn: Ja tā ir USB atslēga, noteikti ievieto to un, ja nepieciešams, pieskaries tai. webauthn: Ja tā ir USB atslēga, noteikti ievieto to un, ja nepieciešams, pieskaries tai.
@ -317,6 +317,11 @@ lv:
name: Tēmturis name: Tēmturis
trendable: Atļaut šim tēmturim parādīties zem tendencēm trendable: Atļaut šim tēmturim parādīties zem tendencēm
usable: Ļaut ierakstos vietēji izmantot šo tēmturi usable: Ļaut ierakstos vietēji izmantot šo tēmturi
terms_of_service:
changelog: Kas ir mainījies?
text: Pakalpojuma izmantošanas nosacījumi
terms_of_service_generator:
domain: Domēna vārds
user: user:
role: Loma role: Loma
time_zone: Laika josla time_zone: Laika josla

View file

@ -161,7 +161,7 @@ pt-PT:
fields: fields:
name: Rótulo name: Rótulo
value: Conteúdo value: Conteúdo
indexable: Incluir mensagens públicas nos resultados da pesquisa indexable: Incluir mensagens públicas nos resultados de pesquisas
show_collections: Mostrar quem sigo e os meus seguidores no perfil show_collections: Mostrar quem sigo e os meus seguidores no perfil
unlocked: Aceitar automaticamente novos seguidores unlocked: Aceitar automaticamente novos seguidores
account_alias: account_alias:
@ -205,7 +205,7 @@ pt-PT:
email: Endereço de correio electrónico email: Endereço de correio electrónico
expires_in: Expira em expires_in: Expira em
fields: Metadados de perfil fields: Metadados de perfil
header: Cabeçalho header: Imagem de cabeçalho
honeypot: "%{label} (não preencher)" honeypot: "%{label} (não preencher)"
inbox_url: URL da caixa de entrada do repetidor inbox_url: URL da caixa de entrada do repetidor
irreversible: Expandir em vez de esconder irreversible: Expandir em vez de esconder

View file

@ -130,6 +130,14 @@ sv:
show_application: Du kommer alltid att kunna se vilken app som publicerat ditt inlägg oavsett. show_application: Du kommer alltid att kunna se vilken app som publicerat ditt inlägg oavsett.
tag: tag:
name: Du kan bara ändra skriftläget av bokstäverna, till exempel, för att göra det mer läsbart name: Du kan bara ändra skriftläget av bokstäverna, till exempel, för att göra det mer läsbart
terms_of_service:
changelog: Kan struktureras med Markdown syntax.
text: Kan struktureras med Markdown syntax.
terms_of_service_generator:
arbitration_address: Kan vara samma som fysisk adress ovan, eller “N/A” om du använder e-post
arbitration_website: Kan vara ett webbformulär, eller ”N/A” om du använder e-post
dmca_email: Kan vara samma e-postadress som används för “E-postadress för juridiska meddelanden” ovan
jurisdiction: Lista det land där vem som än betalar räkningarna bor. Om det är ett företag eller annan enhet, lista landet där det är inkorporerat, och staden, regionen, territoriet eller staten på lämpligt sätt.
user: user:
chosen_languages: Vid aktivering visas bara inlägg på dina valda språk i offentliga tidslinjer chosen_languages: Vid aktivering visas bara inlägg på dina valda språk i offentliga tidslinjer
role: Rollen styr vilka behörigheter användaren har. role: Rollen styr vilka behörigheter användaren har.
@ -151,7 +159,7 @@ sv:
name: Etikett name: Etikett
value: Innehåll value: Innehåll
indexable: Inkludera offentliga inlägg i sökresultaten indexable: Inkludera offentliga inlägg i sökresultaten
show_collections: Göm följare och följeslagare på profilen show_collections: Visa följare och följeslagare på profilen
unlocked: Godkänn nya följare automatiskt unlocked: Godkänn nya följare automatiskt
account_alias: account_alias:
acct: Namnet på det gamla kontot acct: Namnet på det gamla kontot
@ -224,6 +232,7 @@ sv:
setting_hide_network: Göm ditt nätverk setting_hide_network: Göm ditt nätverk
setting_reduce_motion: Minska rörelser i animationer setting_reduce_motion: Minska rörelser i animationer
setting_system_font_ui: Använd systemets standardfont setting_system_font_ui: Använd systemets standardfont
setting_system_scrollbars_ui: Använd systemets standardrullningsfält
setting_theme: Sidans tema setting_theme: Sidans tema
setting_trends: Visa dagens trender setting_trends: Visa dagens trender
setting_unfollow_modal: Visa bekräftelse innan du slutar följa någon setting_unfollow_modal: Visa bekräftelse innan du slutar följa någon
@ -318,6 +327,14 @@ sv:
name: Hashtagg name: Hashtagg
trendable: Tillåt denna hashtagg att visas under trender trendable: Tillåt denna hashtagg att visas under trender
usable: Tillåt inlägg att använda denna fyrkantstagg usable: Tillåt inlägg att använda denna fyrkantstagg
terms_of_service:
changelog: Vad har ändrats?
text: Användarvillkor
terms_of_service_generator:
admin_email: E-postadress för juridiska meddelanden
dmca_address: Fysisk adress för meddelanden om DMCA/upphovsrätt
dmca_email: Fysisk adress för meddelanden om DMCA/upphovsrätt
domain: Domän
user: user:
role: Roll role: Roll
time_zone: Tidszon time_zone: Tidszon

View file

@ -2,3 +2,6 @@
shared: shared:
self_destruct_value: <%= ENV.fetch('SELF_DESTRUCT', nil) %> self_destruct_value: <%= ENV.fetch('SELF_DESTRUCT', nil) %>
software_update_url: <%= ENV.fetch('UPDATE_CHECK_URL', 'https://api.joinmastodon.org/update-check') %> software_update_url: <%= ENV.fetch('UPDATE_CHECK_URL', 'https://api.joinmastodon.org/update-check') %>
version:
metadata: <%= ['glitch', ENV.fetch('MASTODON_VERSION_METADATA', nil)].compact_blank.join('.') %>
prerelease: <%= ENV.fetch('MASTODON_VERSION_PRERELEASE', nil) %>

View file

@ -55,7 +55,8 @@ Rails.application.routes.draw do
get 'manifest', to: 'manifests#show', defaults: { format: 'json' } get 'manifest', to: 'manifests#show', defaults: { format: 'json' }
get 'intent', to: 'intents#show' get 'intent', to: 'intents#show'
get 'custom.css', to: 'custom_css#show', as: :custom_css get 'custom.css', to: 'custom_css#show'
resources :custom_css, only: :show, path: :css
get 'remote_interaction_helper', to: 'remote_interaction_helper#index' get 'remote_interaction_helper', to: 'remote_interaction_helper#index'

View file

@ -100,7 +100,8 @@ services:
- redis - redis
sidekiq: sidekiq:
build: . # You can uncomment the following line if you want to not use the prebuilt image, for example if you have local code changes
# build: .
image: ghcr.io/mastodon/mastodon:v4.3.2 image: ghcr.io/mastodon/mastodon:v4.3.2
restart: always restart: always
env_file: .env.production env_file: .env.production

75
docs/DEVELOPMENT.md Normal file
View file

@ -0,0 +1,75 @@
# Development
## Overview
Before starting local development, read the [CONTRIBUTING] guide to understand
what changes are desirable and what general processes to use.
## Environments
### Vagrant
A **Vagrant** configuration is included for development purposes. To use it,
complete the following steps:
- Install Vagrant and Virtualbox
- Install the `vagrant-hostsupdater` plugin:
`vagrant plugin install vagrant-hostsupdater`
- Run `vagrant up`
- Run `vagrant ssh -c "cd /vagrant && bin/dev"`
- Open `http://mastodon.local` in your browser
### macOS
To set up **macOS** for native development, complete the following steps:
- 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 production hosting and deployment with **Docker**, use the `Dockerfile` and
`docker-compose.yml` in the project root directory.
For local development, install and launch [Docker], and run:
```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
[GitHub Codespaces] provides a web-based version of VS Code and a cloud hosted
development environment configured with the software needed for this project.
[![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_
[codespace]: https://codespaces.new/mastodon/mastodon?quickstart=1&devcontainer_path=.devcontainer%2Fcodespaces%2Fdevcontainer.json
[CONTRIBUTING]: CONTRIBUTING.md
[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

View file

@ -192,6 +192,7 @@ module Mastodon::CLI
verify_schema_version! verify_schema_version!
verify_sidekiq_not_active! verify_sidekiq_not_active!
verify_backup_warning! verify_backup_warning!
disable_timeout!
end end
def process_deduplications def process_deduplications
@ -251,6 +252,13 @@ module Mastodon::CLI
fail_with_message 'Maintenance process stopped.' unless yes?('Continue? (Yes/No)') fail_with_message 'Maintenance process stopped.' unless yes?('Continue? (Yes/No)')
end end
def disable_timeout!
# Remove server-configured timeout if present
database_connection.execute(<<~SQL.squish)
SET statement_timeout = 0
SQL
end
def deduplicate_accounts! def deduplicate_accounts!
remove_index_if_exists!(:accounts, 'index_accounts_on_username_and_domain_lower') remove_index_if_exists!(:accounts, 'index_accounts_on_username_and_domain_lower')

View file

@ -21,11 +21,11 @@ module Mastodon
end end
def prerelease def prerelease
ENV['MASTODON_VERSION_PRERELEASE'].presence || default_prerelease version_configuration[:prerelease].presence || default_prerelease
end end
def build_metadata def build_metadata
['glitch', ENV.fetch('MASTODON_VERSION_METADATA', nil)].compact_blank.join('.') version_configuration[:metadata]
end end
def to_a def to_a
@ -77,5 +77,9 @@ module Mastodon
def user_agent def user_agent
@user_agent ||= "Mastodon/#{Version} (#{HTTP::Request::USER_AGENT}; +http#{Rails.configuration.x.use_https ? 's' : ''}://#{Rails.configuration.x.web_domain}/)" @user_agent ||= "Mastodon/#{Version} (#{HTTP::Request::USER_AGENT}; +http#{Rails.configuration.x.use_https ? 's' : ''}://#{Rails.configuration.x.web_domain}/)"
end end
def version_configuration
Rails.configuration.x.mastodon.version
end
end end
end end

View file

@ -102,7 +102,7 @@
"react-hotkeys": "^1.1.4", "react-hotkeys": "^1.1.4",
"react-immutable-proptypes": "^2.2.0", "react-immutable-proptypes": "^2.2.0",
"react-immutable-pure-component": "^2.2.2", "react-immutable-pure-component": "^2.2.2",
"react-intl": "^6.4.2", "react-intl": "^7.0.0",
"react-motion": "^0.5.2", "react-motion": "^0.5.2",
"react-notification": "^6.8.5", "react-notification": "^6.8.5",
"react-overlays": "^5.2.1", "react-overlays": "^5.2.1",
@ -201,6 +201,8 @@
"webpack-dev-server": "^3.11.3" "webpack-dev-server": "^3.11.3"
}, },
"resolutions": { "resolutions": {
"@types/react": "^18.2.7",
"@types/react-dom": "^18.2.4",
"kind-of": "^6.0.3", "kind-of": "^6.0.3",
"webpack/terser-webpack-plugin": "^4.2.3" "webpack/terser-webpack-plugin": "^4.2.3"
}, },

View file

@ -1,21 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Admin::FollowRecommendationsController do
render_views
let(:user) { Fabricate(:admin_user) }
before do
sign_in user, scope: :user
end
describe 'GET #show' do
it 'returns http success' do
get :show
expect(response).to have_http_status(:success)
end
end
end

View file

@ -1,21 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Admin::TermsOfService::HistoriesController do
render_views
let(:user) { Fabricate(:admin_user) }
before do
sign_in user, scope: :user
end
describe 'GET #show' do
it 'returns http success' do
get :show
expect(response).to have_http_status(:success)
end
end
end

View file

@ -3,6 +3,8 @@
require 'rails_helper' require 'rails_helper'
RSpec.describe ApplicationController do RSpec.describe ApplicationController do
render_views
controller do controller do
def success def success
head 200 head 200
@ -23,9 +25,22 @@ RSpec.describe ApplicationController do
shared_examples 'respond_with_error' do |code| shared_examples 'respond_with_error' do |code|
it "returns http #{code} for http and renders template" do it "returns http #{code} for http and renders template" do
expect(subject).to render_template("errors/#{code}", layout: 'error') subject
expect(response).to have_http_status(code) expect(response)
.to have_http_status(code)
expect(response.parsed_body)
.to have_css('body[class=error]')
expect(response.parsed_body.css('h1').to_s)
.to include(error_content(code))
end
def error_content(code)
if code == 422
I18n.t('errors.422.content')
else
I18n.t("errors.#{code}")
end
end end
end end

View file

@ -1,104 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Settings::MigrationsController do
render_views
describe 'GET #show' do
context 'when user is not sign in' do
subject { get :show }
it { is_expected.to redirect_to new_user_session_path }
end
context 'when user is sign in' do
subject { get :show }
let(:user) { Fabricate(:account, moved_to_account: moved_to_account).user }
before { sign_in user, scope: :user }
context 'when user does not have moved to account' do
let(:moved_to_account) { nil }
it 'renders show page' do
expect(subject).to have_http_status 200
expect(subject).to render_template :show
end
end
context 'when user has a moved to account' do
let(:moved_to_account) { Fabricate(:account) }
it 'renders show page' do
expect(subject).to have_http_status 200
expect(subject).to render_template :show
end
end
end
end
describe 'POST #create' do
context 'when user is not sign in' do
subject { post :create }
it { is_expected.to redirect_to new_user_session_path }
end
context 'when user is signed in' do
subject { post :create, params: { account_migration: { acct: acct, current_password: '12345678' } } }
let(:user) { Fabricate(:user, password: '12345678') }
before { sign_in user, scope: :user }
context 'when migration account is changed' do
let(:acct) { Fabricate(:account, also_known_as: [ActivityPub::TagManager.instance.uri_for(user.account)]) }
it 'updates moved to account' do
expect(subject).to redirect_to settings_migration_path
expect(user.account.reload.moved_to_account_id).to eq acct.id
end
end
context 'when acct is the current account' do
let(:acct) { user.account }
it 'does not update the moved account', :aggregate_failures do
subject
expect(user.account.reload.moved_to_account_id).to be_nil
expect(response).to render_template :show
end
end
context 'when target account does not reference the account being moved from' do
let(:acct) { Fabricate(:account, also_known_as: []) }
it 'does not update the moved account', :aggregate_failures do
subject
expect(user.account.reload.moved_to_account_id).to be_nil
expect(response).to render_template :show
end
end
context 'when a recent migration already exists' do
let(:acct) { Fabricate(:account, also_known_as: [ActivityPub::TagManager.instance.uri_for(user.account)]) }
before do
moved_to = Fabricate(:account, also_known_as: [ActivityPub::TagManager.instance.uri_for(user.account)])
user.account.migrations.create!(acct: moved_to.acct)
end
it 'does not update the moved account', :aggregate_failures do
subject
expect(user.account.reload.moved_to_account_id).to be_nil
expect(response).to render_template :show
end
end
end
end
end

View file

@ -79,6 +79,26 @@ RSpec.describe ThemeHelper do
end end
end end
describe '#custom_stylesheet' do
context 'when custom css setting value digest is present' do
before { Rails.cache.write(:setting_digest_custom_css, '1a2s3d4f1a2s3d4f') }
it 'returns value from settings' do
expect(custom_stylesheet)
.to match('/css/custom-1a2s3d4f.css')
end
end
context 'when custom css setting value digest is not present' do
before { Rails.cache.delete(:setting_digest_custom_css) }
it 'returns default value' do
expect(custom_stylesheet)
.to be_blank
end
end
end
private private
def html_links def html_links

View file

@ -162,12 +162,9 @@ RSpec.describe ActivityPub::Activity::Create do
context 'when object publication date is below ISO8601 range' do context 'when object publication date is below ISO8601 range' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, published: '-0977-11-03T08:31:22Z'
type: 'Note', )
content: 'Lorem ipsum',
published: '-0977-11-03T08:31:22Z',
}
end end
it 'creates status with a valid creation date', :aggregate_failures do it 'creates status with a valid creation date', :aggregate_failures do
@ -184,12 +181,9 @@ RSpec.describe ActivityPub::Activity::Create do
context 'when object publication date is above ISO8601 range' do context 'when object publication date is above ISO8601 range' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, published: '10000-11-03T08:31:22Z'
type: 'Note', )
content: 'Lorem ipsum',
published: '10000-11-03T08:31:22Z',
}
end end
it 'creates status with a valid creation date', :aggregate_failures do it 'creates status with a valid creation date', :aggregate_failures do
@ -206,13 +200,10 @@ RSpec.describe ActivityPub::Activity::Create do
context 'when object has been edited' do context 'when object has been edited' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
published: '2022-01-22T15:00:00Z', published: '2022-01-22T15:00:00Z',
updated: '2022-01-22T16:00:00Z', updated: '2022-01-22T16:00:00Z'
} )
end end
it 'creates status with appropriate creation and edition dates', :aggregate_failures do it 'creates status with appropriate creation and edition dates', :aggregate_failures do
@ -232,13 +223,10 @@ RSpec.describe ActivityPub::Activity::Create do
context 'when object has update date equal to creation date' do context 'when object has update date equal to creation date' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
published: '2022-01-22T15:00:00Z', published: '2022-01-22T15:00:00Z',
updated: '2022-01-22T15:00:00Z', updated: '2022-01-22T15:00:00Z'
} )
end end
it 'creates status and does not mark it as edited' do it 'creates status and does not mark it as edited' do
@ -254,11 +242,9 @@ RSpec.describe ActivityPub::Activity::Create do
context 'with an unknown object type' do context 'with an unknown object type' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, type: 'Banana'
type: 'Banana', )
content: 'Lorem ipsum',
}
end end
it 'does not create a status' do it 'does not create a status' do
@ -267,13 +253,7 @@ RSpec.describe ActivityPub::Activity::Create do
end end
context 'with a standalone' do context 'with a standalone' do
let(:object_json) do let(:object_json) { build_object }
{
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
}
end
it 'creates status' do it 'creates status' do
expect { subject.perform }.to change(sender.statuses, :count).by(1) expect { subject.perform }.to change(sender.statuses, :count).by(1)
@ -296,12 +276,9 @@ RSpec.describe ActivityPub::Activity::Create do
context 'when public with explicit public address' do context 'when public with explicit public address' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, to: 'https://www.w3.org/ns/activitystreams#Public'
type: 'Note', )
content: 'Lorem ipsum',
to: 'https://www.w3.org/ns/activitystreams#Public',
}
end end
it 'creates status' do it 'creates status' do
@ -316,12 +293,9 @@ RSpec.describe ActivityPub::Activity::Create do
context 'when public with as:Public' do context 'when public with as:Public' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, to: 'as:Public'
type: 'Note', )
content: 'Lorem ipsum',
to: 'as:Public',
}
end end
it 'creates status' do it 'creates status' do
@ -336,12 +310,9 @@ RSpec.describe ActivityPub::Activity::Create do
context 'when public with Public' do context 'when public with Public' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, to: 'Public'
type: 'Note', )
content: 'Lorem ipsum',
to: 'Public',
}
end end
it 'creates status' do it 'creates status' do
@ -356,12 +327,9 @@ RSpec.describe ActivityPub::Activity::Create do
context 'when unlisted with explicit public address' do context 'when unlisted with explicit public address' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, cc: 'https://www.w3.org/ns/activitystreams#Public'
type: 'Note', )
content: 'Lorem ipsum',
cc: 'https://www.w3.org/ns/activitystreams#Public',
}
end end
it 'creates status' do it 'creates status' do
@ -376,12 +344,9 @@ RSpec.describe ActivityPub::Activity::Create do
context 'when unlisted with as:Public' do context 'when unlisted with as:Public' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, cc: 'as:Public'
type: 'Note', )
content: 'Lorem ipsum',
cc: 'as:Public',
}
end end
it 'creates status' do it 'creates status' do
@ -396,12 +361,9 @@ RSpec.describe ActivityPub::Activity::Create do
context 'when unlisted with Public' do context 'when unlisted with Public' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, cc: 'Public'
type: 'Note', )
content: 'Lorem ipsum',
cc: 'Public',
}
end end
it 'creates status' do it 'creates status' do
@ -416,12 +378,9 @@ RSpec.describe ActivityPub::Activity::Create do
context 'when private' do context 'when private' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, to: 'http://example.com/followers'
type: 'Note', )
content: 'Lorem ipsum',
to: 'http://example.com/followers',
}
end end
it 'creates status' do it 'creates status' do
@ -436,16 +395,13 @@ RSpec.describe ActivityPub::Activity::Create do
context 'when private with inlined Collection in audience' do context 'when private with inlined Collection in audience' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
to: { to: {
type: 'OrderedCollection', type: 'OrderedCollection',
id: 'http://example.com/followers', id: 'http://example.com/followers',
first: 'http://example.com/followers?page=true', first: 'http://example.com/followers?page=true',
}, }
} )
end end
it 'creates status' do it 'creates status' do
@ -462,12 +418,9 @@ RSpec.describe ActivityPub::Activity::Create do
let(:recipient) { Fabricate(:account) } let(:recipient) { Fabricate(:account) }
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, to: ActivityPub::TagManager.instance.uri_for(recipient)
type: 'Note', )
content: 'Lorem ipsum',
to: ActivityPub::TagManager.instance.uri_for(recipient),
}
end end
it 'creates status with a silent mention' do it 'creates status with a silent mention' do
@ -512,16 +465,13 @@ RSpec.describe ActivityPub::Activity::Create do
let(:recipient) { Fabricate(:account) } let(:recipient) { Fabricate(:account) }
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
to: ActivityPub::TagManager.instance.uri_for(recipient), to: ActivityPub::TagManager.instance.uri_for(recipient),
tag: { tag: {
type: 'Mention', type: 'Mention',
href: ActivityPub::TagManager.instance.uri_for(recipient), href: ActivityPub::TagManager.instance.uri_for(recipient),
}, }
} )
end end
it 'creates status with direct visibility' do it 'creates status with direct visibility' do
@ -561,12 +511,9 @@ RSpec.describe ActivityPub::Activity::Create do
let(:original_status) { Fabricate(:status) } let(:original_status) { Fabricate(:status) }
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, inReplyTo: ActivityPub::TagManager.instance.uri_for(original_status)
type: 'Note', )
content: 'Lorem ipsum',
inReplyTo: ActivityPub::TagManager.instance.uri_for(original_status),
}
end end
it 'creates status' do it 'creates status' do
@ -586,17 +533,14 @@ RSpec.describe ActivityPub::Activity::Create do
let(:recipient) { Fabricate(:account) } let(:recipient) { Fabricate(:account) }
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
tag: [ tag: [
{ {
type: 'Mention', type: 'Mention',
href: ActivityPub::TagManager.instance.uri_for(recipient), href: ActivityPub::TagManager.instance.uri_for(recipient),
}, },
], ]
} )
end end
it 'creates status' do it 'creates status' do
@ -611,16 +555,13 @@ RSpec.describe ActivityPub::Activity::Create do
context 'with mentions missing href' do context 'with mentions missing href' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
tag: [ tag: [
{ {
type: 'Mention', type: 'Mention',
}, },
], ]
} )
end end
it 'creates status' do it 'creates status' do
@ -633,10 +574,7 @@ RSpec.describe ActivityPub::Activity::Create do
context 'with media attachments' do context 'with media attachments' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
attachment: [ attachment: [
{ {
type: 'Document', type: 'Document',
@ -648,8 +586,8 @@ RSpec.describe ActivityPub::Activity::Create do
mediaType: 'image/png', mediaType: 'image/png',
url: 'http://example.com/emoji.png', url: 'http://example.com/emoji.png',
}, },
], ]
} )
end end
it 'creates status with correctly-ordered media attachments' do it 'creates status with correctly-ordered media attachments' do
@ -665,19 +603,16 @@ RSpec.describe ActivityPub::Activity::Create do
context 'with media attachments with long description' do context 'with media attachments with long description' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
attachment: [ attachment: [
{ {
type: 'Document', type: 'Document',
mediaType: 'image/png', mediaType: 'image/png',
url: 'http://example.com/attachment.png', url: 'http://example.com/attachment.png',
name: '*' * 1500, name: '*' * MediaAttachment::MAX_DESCRIPTION_LENGTH,
}, },
], ]
} )
end end
it 'creates status' do it 'creates status' do
@ -686,25 +621,22 @@ RSpec.describe ActivityPub::Activity::Create do
status = sender.statuses.first status = sender.statuses.first
expect(status).to_not be_nil expect(status).to_not be_nil
expect(status.media_attachments.map(&:description)).to include('*' * 1500) expect(status.media_attachments.map(&:description)).to include('*' * MediaAttachment::MAX_DESCRIPTION_LENGTH)
end end
end end
context 'with media attachments with long description as summary' do context 'with media attachments with long description as summary' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
attachment: [ attachment: [
{ {
type: 'Document', type: 'Document',
mediaType: 'image/png', mediaType: 'image/png',
url: 'http://example.com/attachment.png', url: 'http://example.com/attachment.png',
summary: '*' * 1500, summary: '*' * MediaAttachment::MAX_DESCRIPTION_LENGTH,
}, },
], ]
} )
end end
it 'creates status' do it 'creates status' do
@ -713,16 +645,13 @@ RSpec.describe ActivityPub::Activity::Create do
status = sender.statuses.first status = sender.statuses.first
expect(status).to_not be_nil expect(status).to_not be_nil
expect(status.media_attachments.map(&:description)).to include('*' * 1500) expect(status.media_attachments.map(&:description)).to include('*' * MediaAttachment::MAX_DESCRIPTION_LENGTH)
end end
end end
context 'with media attachments with focal points' do context 'with media attachments with focal points' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
attachment: [ attachment: [
{ {
type: 'Document', type: 'Document',
@ -730,8 +659,8 @@ RSpec.describe ActivityPub::Activity::Create do
url: 'http://example.com/attachment.png', url: 'http://example.com/attachment.png',
focalPoint: [0.5, -0.7], focalPoint: [0.5, -0.7],
}, },
], ]
} )
end end
it 'creates status' do it 'creates status' do
@ -746,17 +675,14 @@ RSpec.describe ActivityPub::Activity::Create do
context 'with media attachments missing url' do context 'with media attachments missing url' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
attachment: [ attachment: [
{ {
type: 'Document', type: 'Document',
mediaType: 'image/png', mediaType: 'image/png',
}, },
], ]
} )
end end
it 'creates status' do it 'creates status' do
@ -769,18 +695,15 @@ RSpec.describe ActivityPub::Activity::Create do
context 'with hashtags' do context 'with hashtags' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
tag: [ tag: [
{ {
type: 'Hashtag', type: 'Hashtag',
href: 'http://example.com/blah', href: 'http://example.com/blah',
name: '#test', name: '#test',
}, },
], ]
} )
end end
it 'creates status' do it 'creates status' do
@ -795,10 +718,7 @@ RSpec.describe ActivityPub::Activity::Create do
context 'with featured hashtags' do context 'with featured hashtags' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
to: 'https://www.w3.org/ns/activitystreams#Public', to: 'https://www.w3.org/ns/activitystreams#Public',
tag: [ tag: [
{ {
@ -806,8 +726,8 @@ RSpec.describe ActivityPub::Activity::Create do
href: 'http://example.com/blah', href: 'http://example.com/blah',
name: '#test', name: '#test',
}, },
], ]
} )
end end
before do before do
@ -829,17 +749,14 @@ RSpec.describe ActivityPub::Activity::Create do
context 'with hashtags missing name' do context 'with hashtags missing name' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
tag: [ tag: [
{ {
type: 'Hashtag', type: 'Hashtag',
href: 'http://example.com/blah', href: 'http://example.com/blah',
}, },
], ]
} )
end end
it 'creates status' do it 'creates status' do
@ -852,18 +769,15 @@ RSpec.describe ActivityPub::Activity::Create do
context 'with hashtags invalid name' do context 'with hashtags invalid name' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
tag: [ tag: [
{ {
type: 'Hashtag', type: 'Hashtag',
href: 'http://example.com/blah', href: 'http://example.com/blah',
name: 'foo, #eh !', name: 'foo, #eh !',
}, },
], ]
} )
end end
it 'creates status' do it 'creates status' do
@ -876,9 +790,7 @@ RSpec.describe ActivityPub::Activity::Create do
context 'with emojis' do context 'with emojis' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum :tinking:', content: 'Lorem ipsum :tinking:',
tag: [ tag: [
{ {
@ -888,8 +800,8 @@ RSpec.describe ActivityPub::Activity::Create do
}, },
name: 'tinking', name: 'tinking',
}, },
], ]
} )
end end
it 'creates status' do it 'creates status' do
@ -904,9 +816,7 @@ RSpec.describe ActivityPub::Activity::Create do
context 'with emojis served with invalid content-type' do context 'with emojis served with invalid content-type' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum :tinkong:', content: 'Lorem ipsum :tinkong:',
tag: [ tag: [
{ {
@ -916,8 +826,8 @@ RSpec.describe ActivityPub::Activity::Create do
}, },
name: 'tinkong', name: 'tinkong',
}, },
], ]
} )
end end
it 'creates status' do it 'creates status' do
@ -932,9 +842,7 @@ RSpec.describe ActivityPub::Activity::Create do
context 'with emojis missing name' do context 'with emojis missing name' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum :tinking:', content: 'Lorem ipsum :tinking:',
tag: [ tag: [
{ {
@ -943,8 +851,8 @@ RSpec.describe ActivityPub::Activity::Create do
url: 'http://example.com/emoji.png', url: 'http://example.com/emoji.png',
}, },
}, },
], ]
} )
end end
it 'creates status' do it 'creates status' do
@ -957,17 +865,15 @@ RSpec.describe ActivityPub::Activity::Create do
context 'with emojis missing icon' do context 'with emojis missing icon' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum :tinking:', content: 'Lorem ipsum :tinking:',
tag: [ tag: [
{ {
type: 'Emoji', type: 'Emoji',
name: 'tinking', name: 'tinking',
}, },
], ]
} )
end end
it 'creates status' do it 'creates status' do
@ -980,8 +886,7 @@ RSpec.describe ActivityPub::Activity::Create do
context 'with poll' do context 'with poll' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Question', type: 'Question',
content: 'Which color was the submarine?', content: 'Which color was the submarine?',
oneOf: [ oneOf: [
@ -999,8 +904,8 @@ RSpec.describe ActivityPub::Activity::Create do
totalItems: 3, totalItems: 3,
}, },
}, },
], ]
} )
end end
it 'creates status with a poll' do it 'creates status with a poll' do
@ -1023,12 +928,10 @@ RSpec.describe ActivityPub::Activity::Create do
let!(:local_status) { Fabricate(:status, poll: poll) } let!(:local_status) { Fabricate(:status, poll: poll) }
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
name: 'Yellow', name: 'Yellow',
inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status), inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status)
} ).except(:content)
end end
it 'adds a vote to the poll with correct uri' do it 'adds a vote to the poll with correct uri' do
@ -1050,12 +953,10 @@ RSpec.describe ActivityPub::Activity::Create do
let!(:local_status) { Fabricate(:status, poll: poll) } let!(:local_status) { Fabricate(:status, poll: poll) }
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
name: 'Yellow', name: 'Yellow',
inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status), inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status)
} ).except(:content)
end end
it 'does not add a vote to the poll' do it 'does not add a vote to the poll' do
@ -1067,10 +968,7 @@ RSpec.describe ActivityPub::Activity::Create do
context 'with counts' do context 'with counts' do
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
likes: { likes: {
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar', '/likes'].join, id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar', '/likes'].join,
type: 'Collection', type: 'Collection',
@ -1080,8 +978,8 @@ RSpec.describe ActivityPub::Activity::Create do
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar', '/shares'].join, id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar', '/shares'].join,
type: 'Collection', type: 'Collection',
totalItems: 100, totalItems: 100,
}, }
} )
end end
it 'uses the counts from the created object' do it 'uses the counts from the created object' do
@ -1110,12 +1008,9 @@ RSpec.describe ActivityPub::Activity::Create do
end end
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, to: 'https://www.w3.org/ns/activitystreams#Public'
type: 'Note', )
content: 'Lorem ipsum',
to: 'https://www.w3.org/ns/activitystreams#Public',
}
end end
before do before do
@ -1145,13 +1040,7 @@ RSpec.describe ActivityPub::Activity::Create do
subject.perform subject.perform
end end
let(:object_json) do let(:object_json) { build_object }
{
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
}
end
it 'creates status' do it 'creates status' do
status = sender.statuses.first status = sender.statuses.first
@ -1166,12 +1055,9 @@ RSpec.describe ActivityPub::Activity::Create do
let!(:local_status) { Fabricate(:status) } let!(:local_status) { Fabricate(:status) }
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status)
type: 'Note', )
content: 'Lorem ipsum',
inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status),
}
end end
before do before do
@ -1190,13 +1076,11 @@ RSpec.describe ActivityPub::Activity::Create do
subject { described_class.new(json, sender, delivery: true) } subject { described_class.new(json, sender, delivery: true) }
let!(:local_account) { Fabricate(:account) } let!(:local_account) { Fabricate(:account) }
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, to: ActivityPub::TagManager.instance.uri_for(local_account)
type: 'Note', )
content: 'Lorem ipsum',
to: ActivityPub::TagManager.instance.uri_for(local_account),
}
end end
before do before do
@ -1216,12 +1100,9 @@ RSpec.describe ActivityPub::Activity::Create do
let!(:local_account) { Fabricate(:account) } let!(:local_account) { Fabricate(:account) }
let(:object_json) do let(:object_json) do
{ build_object(
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, cc: ActivityPub::TagManager.instance.uri_for(local_account)
type: 'Note', )
content: 'Lorem ipsum',
cc: ActivityPub::TagManager.instance.uri_for(local_account),
}
end end
before do before do
@ -1243,17 +1124,19 @@ RSpec.describe ActivityPub::Activity::Create do
subject.perform subject.perform
end end
let(:object_json) do let(:object_json) { build_object }
{
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
}
end
it 'does not create anything' do it 'does not create anything' do
expect(sender.statuses.count).to eq 0 expect(sender.statuses.count).to eq 0
end end
end end
def build_object(options = {})
{
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
}.merge(options)
end
end end
end end

View file

@ -89,10 +89,8 @@ RSpec.describe Mastodon::CLI::Maintenance do
def prepare_duplicate_data def prepare_duplicate_data
ActiveRecord::Base.connection.remove_index :accounts, name: :index_accounts_on_username_and_domain_lower ActiveRecord::Base.connection.remove_index :accounts, name: :index_accounts_on_username_and_domain_lower
_remote_account = Fabricate(:account, username: duplicate_account_username, domain: duplicate_account_domain) duplicate_record(:account, username: duplicate_account_username, domain: duplicate_account_domain)
_remote_account_dupe = Fabricate.build(:account, username: duplicate_account_username, domain: duplicate_account_domain).save(validate: false) duplicate_record(:account, username: duplicate_account_username, domain: nil)
_local_account = Fabricate(:account, username: duplicate_account_username, domain: nil)
_local_account_dupe = Fabricate.build(:account, username: duplicate_account_username, domain: nil).save(validate: false)
end end
def choose_local_account_to_keep def choose_local_account_to_keep
@ -127,8 +125,7 @@ RSpec.describe Mastodon::CLI::Maintenance do
def prepare_duplicate_data def prepare_duplicate_data
ActiveRecord::Base.connection.remove_index :users, :email ActiveRecord::Base.connection.remove_index :users, :email
Fabricate(:user, email: duplicate_email) duplicate_record(:user, email: duplicate_email)
Fabricate.build(:user, email: duplicate_email).save(validate: false)
end end
end end
@ -156,8 +153,7 @@ RSpec.describe Mastodon::CLI::Maintenance do
def prepare_duplicate_data def prepare_duplicate_data
ActiveRecord::Base.connection.remove_index :users, :confirmation_token ActiveRecord::Base.connection.remove_index :users, :confirmation_token
Fabricate(:user, confirmation_token: duplicate_confirmation_token) duplicate_record(:user, confirmation_token: duplicate_confirmation_token)
Fabricate.build(:user, confirmation_token: duplicate_confirmation_token).save(validate: false)
end end
end end
@ -185,8 +181,7 @@ RSpec.describe Mastodon::CLI::Maintenance do
def prepare_duplicate_data def prepare_duplicate_data
ActiveRecord::Base.connection.remove_index :users, :reset_password_token ActiveRecord::Base.connection.remove_index :users, :reset_password_token
Fabricate(:user, reset_password_token: duplicate_reset_password_token) duplicate_record(:user, reset_password_token: duplicate_reset_password_token)
Fabricate.build(:user, reset_password_token: duplicate_reset_password_token).save(validate: false)
end end
end end
@ -214,8 +209,7 @@ RSpec.describe Mastodon::CLI::Maintenance do
def prepare_duplicate_data def prepare_duplicate_data
ActiveRecord::Base.connection.remove_index :account_domain_blocks, [:account_id, :domain] ActiveRecord::Base.connection.remove_index :account_domain_blocks, [:account_id, :domain]
Fabricate(:account_domain_block, account: account, domain: duplicate_domain) duplicate_record(:account_domain_block, account: account, domain: duplicate_domain)
Fabricate.build(:account_domain_block, account: account, domain: duplicate_domain).save(validate: false)
end end
end end
@ -244,8 +238,7 @@ RSpec.describe Mastodon::CLI::Maintenance do
def prepare_duplicate_data def prepare_duplicate_data
ActiveRecord::Base.connection.remove_index :announcement_reactions, [:account_id, :announcement_id, :name] ActiveRecord::Base.connection.remove_index :announcement_reactions, [:account_id, :announcement_id, :name]
Fabricate(:announcement_reaction, account: account, announcement: announcement, name: name) duplicate_record(:announcement_reaction, account: account, announcement: announcement, name: name)
Fabricate.build(:announcement_reaction, account: account, announcement: announcement, name: name).save(validate: false)
end end
end end
@ -272,8 +265,7 @@ RSpec.describe Mastodon::CLI::Maintenance do
def prepare_duplicate_data def prepare_duplicate_data
ActiveRecord::Base.connection.remove_index :conversations, :uri ActiveRecord::Base.connection.remove_index :conversations, :uri
Fabricate(:conversation, uri: uri) duplicate_record(:conversation, uri: uri)
Fabricate.build(:conversation, uri: uri).save(validate: false)
end end
end end
@ -301,8 +293,7 @@ RSpec.describe Mastodon::CLI::Maintenance do
def prepare_duplicate_data def prepare_duplicate_data
ActiveRecord::Base.connection.remove_index :custom_emojis, [:shortcode, :domain] ActiveRecord::Base.connection.remove_index :custom_emojis, [:shortcode, :domain]
Fabricate(:custom_emoji, shortcode: duplicate_shortcode, domain: duplicate_domain) duplicate_record(:custom_emoji, shortcode: duplicate_shortcode, domain: duplicate_domain)
Fabricate.build(:custom_emoji, shortcode: duplicate_shortcode, domain: duplicate_domain).save(validate: false)
end end
end end
@ -329,8 +320,7 @@ RSpec.describe Mastodon::CLI::Maintenance do
def prepare_duplicate_data def prepare_duplicate_data
ActiveRecord::Base.connection.remove_index :custom_emoji_categories, :name ActiveRecord::Base.connection.remove_index :custom_emoji_categories, :name
Fabricate(:custom_emoji_category, name: duplicate_name) duplicate_record(:custom_emoji_category, name: duplicate_name)
Fabricate.build(:custom_emoji_category, name: duplicate_name).save(validate: false)
end end
end end
@ -357,8 +347,7 @@ RSpec.describe Mastodon::CLI::Maintenance do
def prepare_duplicate_data def prepare_duplicate_data
ActiveRecord::Base.connection.remove_index :domain_allows, :domain ActiveRecord::Base.connection.remove_index :domain_allows, :domain
Fabricate(:domain_allow, domain: domain) duplicate_record(:domain_allow, domain: domain)
Fabricate.build(:domain_allow, domain: domain).save(validate: false)
end end
end end
@ -385,8 +374,7 @@ RSpec.describe Mastodon::CLI::Maintenance do
def prepare_duplicate_data def prepare_duplicate_data
ActiveRecord::Base.connection.remove_index :domain_blocks, :domain ActiveRecord::Base.connection.remove_index :domain_blocks, :domain
Fabricate(:domain_block, domain: domain) duplicate_record(:domain_block, domain: domain)
Fabricate.build(:domain_block, domain: domain).save(validate: false)
end end
end end
@ -413,8 +401,7 @@ RSpec.describe Mastodon::CLI::Maintenance do
def prepare_duplicate_data def prepare_duplicate_data
ActiveRecord::Base.connection.remove_index :email_domain_blocks, :domain ActiveRecord::Base.connection.remove_index :email_domain_blocks, :domain
Fabricate(:email_domain_block, domain: domain) duplicate_record(:email_domain_block, domain: domain)
Fabricate.build(:email_domain_block, domain: domain).save(validate: false)
end end
end end
@ -441,8 +428,7 @@ RSpec.describe Mastodon::CLI::Maintenance do
def prepare_duplicate_data def prepare_duplicate_data
ActiveRecord::Base.connection.remove_index :media_attachments, :shortcode ActiveRecord::Base.connection.remove_index :media_attachments, :shortcode
Fabricate(:media_attachment, shortcode: shortcode) duplicate_record(:media_attachment, shortcode: shortcode)
Fabricate.build(:media_attachment, shortcode: shortcode).save(validate: false)
end end
end end
@ -469,8 +455,7 @@ RSpec.describe Mastodon::CLI::Maintenance do
def prepare_duplicate_data def prepare_duplicate_data
ActiveRecord::Base.connection.remove_index :preview_cards, :url ActiveRecord::Base.connection.remove_index :preview_cards, :url
Fabricate(:preview_card, url: url) duplicate_record(:preview_card, url: url)
Fabricate.build(:preview_card, url: url).save(validate: false)
end end
end end
@ -530,8 +515,7 @@ RSpec.describe Mastodon::CLI::Maintenance do
def prepare_duplicate_data def prepare_duplicate_data
ActiveRecord::Base.connection.remove_index :tags, name: 'index_tags_on_name_lower_btree' ActiveRecord::Base.connection.remove_index :tags, name: 'index_tags_on_name_lower_btree'
Fabricate(:tag, name: name) duplicate_record(:tag, name: name)
Fabricate.build(:tag, name: name).save(validate: false)
end end
end end
@ -558,8 +542,7 @@ RSpec.describe Mastodon::CLI::Maintenance do
def prepare_duplicate_data def prepare_duplicate_data
ActiveRecord::Base.connection.remove_index :webauthn_credentials, :external_id ActiveRecord::Base.connection.remove_index :webauthn_credentials, :external_id
Fabricate(:webauthn_credential, external_id: external_id) duplicate_record(:webauthn_credential, external_id: external_id)
Fabricate.build(:webauthn_credential, external_id: external_id).save(validate: false)
end end
end end
@ -586,11 +569,15 @@ RSpec.describe Mastodon::CLI::Maintenance do
def prepare_duplicate_data def prepare_duplicate_data
ActiveRecord::Base.connection.remove_index :webhooks, :url ActiveRecord::Base.connection.remove_index :webhooks, :url
Fabricate(:webhook, url: url) duplicate_record(:webhook, url: url)
Fabricate.build(:webhook, url: url).save(validate: false)
end end
end end
def duplicate_record(fabricator, options = {})
Fabricate(fabricator, options)
Fabricate.build(fabricator, options).save(validate: false)
end
def agree_to_backup_warning def agree_to_backup_warning
allow(cli.shell) allow(cli.shell)
.to receive(:yes?) .to receive(:yes?)

View file

@ -4,7 +4,9 @@ require 'rails_helper'
require 'securerandom' require 'securerandom'
RSpec.describe Request do RSpec.describe Request do
subject { described_class.new(:get, 'http://example.com') } subject { described_class.new(:get, 'http://example.com', **options) }
let(:options) { {} }
describe '#headers' do describe '#headers' do
it 'returns user agent' do it 'returns user agent' do
@ -39,8 +41,8 @@ RSpec.describe Request do
end end
describe '#perform' do describe '#perform' do
context 'with valid host' do context 'with valid host and non-persistent connection' do
before { stub_request(:get, 'http://example.com') } before { stub_request(:get, 'http://example.com').to_return(body: 'lorem ipsum') }
it 'executes a HTTP request' do it 'executes a HTTP request' do
expect { |block| subject.perform(&block) }.to yield_control expect { |block| subject.perform(&block) }.to yield_control
@ -71,9 +73,9 @@ RSpec.describe Request do
expect(subject.send(:http_client)).to have_received(:close) expect(subject.send(:http_client)).to have_received(:close)
end end
it 'returns response which implements body_with_limit' do it 'yields response' do
subject.perform do |response| subject.perform do |response|
expect(response).to respond_to :body_with_limit expect(response.body_with_limit).to eq 'lorem ipsum'
end end
end end
end end
@ -95,6 +97,43 @@ RSpec.describe Request do
expect { subject.perform }.to raise_error Mastodon::ValidationError expect { subject.perform }.to raise_error Mastodon::ValidationError
end end
end end
context 'with persistent connection' do
before { stub_request(:get, 'http://example.com').to_return(body: SecureRandom.random_bytes(2.megabytes)) }
let(:http_client) { described_class.http_client.persistent('http://example.com') }
let(:options) { { http_client: http_client } }
it 'leaves connection open after completely consumed response' do
allow(http_client).to receive(:close)
subject.perform { |response| response.truncated_body(3.megabytes) }
expect(http_client).to_not have_received(:close)
end
it 'leaves connection open after nearly consumed response' do
allow(http_client).to receive(:close)
subject.perform { |response| response.truncated_body(1.8.megabytes) }
expect(http_client).to_not have_received(:close)
end
it 'closes connection after unconsumed response' do
allow(http_client).to receive(:close)
subject.perform
expect(http_client).to have_received(:close)
end
it 'yields response' do
subject.perform do |response|
expect(response.body_with_limit(2.megabytes).size).to eq 2.megabytes
end
end
end
end end
describe "response's body_with_limit method" do describe "response's body_with_limit method" do

View file

@ -3,6 +3,7 @@
require 'rails_helper' require 'rails_helper'
RSpec.describe Account do RSpec.describe Account do
include_examples 'Account::Search'
include_examples 'Reviewable' include_examples 'Reviewable'
context 'with an account record' do context 'with an account record' do
@ -48,14 +49,48 @@ RSpec.describe Account do
end end
describe '#local?' do describe '#local?' do
it 'returns true when the account is local' do it 'returns true when domain is null' do
account = Fabricate(:account, domain: nil) account = Fabricate(:account, domain: nil)
expect(account.local?).to be true expect(account).to be_local
end end
it 'returns false when the account is on a different domain' do it 'returns false when domain is present' do
account = Fabricate(:account, domain: 'foreign.tld') account = Fabricate(:account, domain: 'foreign.tld')
expect(account.local?).to be false expect(account).to_not be_local
end
end
describe '#remote?' do
context 'when the domain is null' do
subject { Fabricate.build :account, domain: nil }
it { is_expected.to_not be_remote }
end
context 'when the domain is blank' do
subject { Fabricate.build :account, domain: '' }
it { is_expected.to_not be_remote }
end
context 'when the domain is present' do
subject { Fabricate.build :account, domain: 'host.example' }
it { is_expected.to be_remote }
end
end
describe '#actor_type_application?' do
context 'when the actor is not of type application' do
subject { Fabricate.build :account, actor_type: 'Person' }
it { is_expected.to_not be_actor_type_application }
end
context 'when the actor is of type application' do
subject { Fabricate.build :account, actor_type: 'Application' }
it { is_expected.to be_actor_type_application }
end end
end end
@ -324,271 +359,6 @@ RSpec.describe Account do
end end
end end
describe '.search_for' do
before do
_missing = Fabricate(
:account,
display_name: 'Missing',
username: 'missing',
domain: 'missing.com'
)
end
it 'does not return suspended users' do
Fabricate(
:account,
display_name: 'Display Name',
username: 'username',
domain: 'example.com',
suspended: true
)
results = described_class.search_for('username')
expect(results).to eq []
end
it 'does not return unapproved users' do
match = Fabricate(
:account,
display_name: 'Display Name',
username: 'username'
)
match.user.update(approved: false)
results = described_class.search_for('username')
expect(results).to eq []
end
it 'does not return unconfirmed users' do
match = Fabricate(
:account,
display_name: 'Display Name',
username: 'username'
)
match.user.update(confirmed_at: nil)
results = described_class.search_for('username')
expect(results).to eq []
end
it 'accepts ?, \, : and space as delimiter' do
match = Fabricate(
:account,
display_name: 'A & l & i & c & e',
username: 'username',
domain: 'example.com'
)
results = described_class.search_for('A?l\i:c e')
expect(results).to eq [match]
end
it 'finds accounts with matching display_name' do
match = Fabricate(
:account,
display_name: 'Display Name',
username: 'username',
domain: 'example.com'
)
results = described_class.search_for('display')
expect(results).to eq [match]
end
it 'finds accounts with matching username' do
match = Fabricate(
:account,
display_name: 'Display Name',
username: 'username',
domain: 'example.com'
)
results = described_class.search_for('username')
expect(results).to eq [match]
end
it 'finds accounts with matching domain' do
match = Fabricate(
:account,
display_name: 'Display Name',
username: 'username',
domain: 'example.com'
)
results = described_class.search_for('example')
expect(results).to eq [match]
end
it 'limits via constant by default' do
stub_const('Account::Search::DEFAULT_LIMIT', 1)
2.times.each { Fabricate(:account, display_name: 'Display Name') }
results = described_class.search_for('display')
expect(results.size).to eq 1
end
it 'accepts arbitrary limits' do
2.times.each { Fabricate(:account, display_name: 'Display Name') }
results = described_class.search_for('display', limit: 1)
expect(results.size).to eq 1
end
it 'ranks multiple matches higher' do
matches = [
{ username: 'username', display_name: 'username' },
{ display_name: 'Display Name', username: 'username', domain: 'example.com' },
].map(&method(:Fabricate).curry(2).call(:account))
results = described_class.search_for('username')
expect(results).to eq matches
end
end
describe '.advanced_search_for' do
let(:account) { Fabricate(:account) }
context 'when limiting search to followed accounts' do
it 'accepts ?, \, : and space as delimiter' do
match = Fabricate(
:account,
display_name: 'A & l & i & c & e',
username: 'username',
domain: 'example.com'
)
account.follow!(match)
results = described_class.advanced_search_for('A?l\i:c e', account, limit: 10, following: true)
expect(results).to eq [match]
end
it 'does not return non-followed accounts' do
Fabricate(
:account,
display_name: 'A & l & i & c & e',
username: 'username',
domain: 'example.com'
)
results = described_class.advanced_search_for('A?l\i:c e', account, limit: 10, following: true)
expect(results).to eq []
end
it 'does not return suspended users' do
Fabricate(
:account,
display_name: 'Display Name',
username: 'username',
domain: 'example.com',
suspended: true
)
results = described_class.advanced_search_for('username', account, limit: 10, following: true)
expect(results).to eq []
end
it 'does not return unapproved users' do
match = Fabricate(
:account,
display_name: 'Display Name',
username: 'username'
)
match.user.update(approved: false)
results = described_class.advanced_search_for('username', account, limit: 10, following: true)
expect(results).to eq []
end
it 'does not return unconfirmed users' do
match = Fabricate(
:account,
display_name: 'Display Name',
username: 'username'
)
match.user.update(confirmed_at: nil)
results = described_class.advanced_search_for('username', account, limit: 10, following: true)
expect(results).to eq []
end
end
it 'does not return suspended users' do
Fabricate(
:account,
display_name: 'Display Name',
username: 'username',
domain: 'example.com',
suspended: true
)
results = described_class.advanced_search_for('username', account)
expect(results).to eq []
end
it 'does not return unapproved users' do
match = Fabricate(
:account,
display_name: 'Display Name',
username: 'username'
)
match.user.update(approved: false)
results = described_class.advanced_search_for('username', account)
expect(results).to eq []
end
it 'does not return unconfirmed users' do
match = Fabricate(
:account,
display_name: 'Display Name',
username: 'username'
)
match.user.update(confirmed_at: nil)
results = described_class.advanced_search_for('username', account)
expect(results).to eq []
end
it 'accepts ?, \, : and space as delimiter' do
match = Fabricate(
:account,
display_name: 'A & l & i & c & e',
username: 'username',
domain: 'example.com'
)
results = described_class.advanced_search_for('A?l\i:c e', account)
expect(results).to eq [match]
end
it 'limits result count by default value' do
stub_const('Account::Search::DEFAULT_LIMIT', 1)
2.times { Fabricate(:account, display_name: 'Display Name') }
results = described_class.advanced_search_for('display', account)
expect(results.size).to eq 1
end
it 'accepts arbitrary limits' do
2.times { Fabricate(:account, display_name: 'Display Name') }
results = described_class.advanced_search_for('display', account, limit: 1)
expect(results.size).to eq 1
end
it 'ranks followed accounts higher' do
match = Fabricate(:account, username: 'Matching')
followed_match = Fabricate(:account, username: 'Matcher')
Fabricate(:follow, account: account, target_account: followed_match)
results = described_class.advanced_search_for('match', account)
expect(results).to eq [followed_match, match]
expect(results.first.rank).to be > results.last.rank
end
end
describe '#statuses_count' do describe '#statuses_count' do
subject { Fabricate(:account) } subject { Fabricate(:account) }
@ -824,8 +594,12 @@ RSpec.describe Account do
it { is_expected.to_not allow_values(account_note_over_limit).for(:note) } it { is_expected.to_not allow_values(account_note_over_limit).for(:note) }
it { is_expected.to allow_value(fields_empty_name_value).for(:fields) } it { is_expected.to allow_value(fields_empty_name_value).for(:fields) }
it { is_expected.to_not allow_value(fields_over_limit).for(:fields) } it { is_expected.to_not allow_values(fields_over_limit, fields_empty_name).for(:fields) }
it { is_expected.to_not allow_value(fields_empty_name).for(:fields) }
it { is_expected.to validate_absence_of(:followers_url).on(:create) }
it { is_expected.to validate_absence_of(:inbox_url).on(:create) }
it { is_expected.to validate_absence_of(:shared_inbox_url).on(:create) }
it { is_expected.to validate_absence_of(:uri).on(:create) }
end end
context 'when account is remote' do context 'when account is remote' do

View file

@ -8,4 +8,18 @@ RSpec.describe AccountWarning do
it { is_expected.to normalize(:text).from(nil).to('') } it { is_expected.to normalize(:text).from(nil).to('') }
end end
end end
describe '#appeal_eligible?' do
context 'when created too long ago' do
subject { Fabricate.build :account_warning, created_at: (described_class::APPEAL_WINDOW * 2).ago }
it { is_expected.to_not be_appeal_eligible }
end
context 'when created recently' do
subject { Fabricate.build :account_warning, created_at: (described_class::APPEAL_WINDOW - 2.days).ago }
it { is_expected.to be_appeal_eligible }
end
end
end end

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