Merge pull request #2937 from ClearlyClaire/glitch-soc/merge-upstream

Merge upstream changes up to 34cd7d6585
This commit is contained in:
Claire 2025-01-11 10:03:15 +01:00 committed by GitHub
commit f950b97024
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
131 changed files with 1785 additions and 1229 deletions

View file

@ -1,10 +1,6 @@
[production]
defaults
> 0.2%
firefox >= 78
ios >= 15.6
not dead
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
value: |
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)
validations:
required: false

View file

@ -60,7 +60,7 @@ body:
value: |
Please at least include those informations:
- 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)
validations:
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
// 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).',
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'],
packageRules: [
{

View file

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

View file

@ -125,6 +125,7 @@ jobs:
matrix:
ruby-version:
- '3.2'
- '3.3'
- '.ruby-version'
steps:
- uses: actions/checkout@v4
@ -226,6 +227,7 @@ jobs:
matrix:
ruby-version:
- '3.2'
- '3.3'
- '.ruby-version'
steps:
- uses: actions/checkout@v4
@ -304,6 +306,7 @@ jobs:
matrix:
ruby-version:
- '3.2'
- '3.3'
- '.ruby-version'
steps:
@ -420,6 +423,7 @@ jobs:
matrix:
ruby-version:
- '3.2'
- '3.3'
- '.ruby-version'
search-image:
- 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:
Enabled: false
Style/MultipleComparison:
Enabled: false
Style/NumericLiterals:
AllowedPatterns:
- \d{4}_\d{2}_\d{2}_\d{6}

View file

@ -1,6 +1,6 @@
# This configuration was generated by
# `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
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
@ -8,7 +8,7 @@
Lint/NonLocalExitFromIterator:
Exclude:
- 'app/helpers/jsonld_helper.rb'
- 'app/helpers/json_ld_helper.rb'
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
Metrics/AbcSize:
@ -82,7 +82,7 @@ Style/MutableConstant:
# AllowedMethods: respond_to_missing?
Style/OptionalBooleanParameter:
Exclude:
- 'app/helpers/jsonld_helper.rb'
- 'app/helpers/json_ld_helper.rb'
- 'app/lib/admin/system_check/message.rb'
- 'app/lib/request.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).
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
@ -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).
</blockquote>
[DEVELOPMENT]: docs/DEVELOPMENT.md

View file

@ -10,9 +10,9 @@
ARG TARGETPLATFORM=${TARGETPLATFORM}
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
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"]
# renovate: datasource=node-version depName=node
ARG NODE_MAJOR_VERSION="22"
@ -20,7 +20,7 @@ ARG NODE_MAJOR_VERSION="22"
ARG DEBIAN_VERSION="bookworm"
# Node image to use for base image based on combined variables (ex: 20-bookworm-slim)
FROM docker.io/node:${NODE_MAJOR_VERSION}-${DEBIAN_VERSION}-slim AS node
# Ruby image to use for base image based on combined variables (ex: 3.3.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
# Resulting version string is vX.X.X-MASTODON_VERSION_PRERELEASE+MASTODON_VERSION_METADATA

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
source 'https://rubygems.org'
ruby '>= 3.2.0', '< 3.5'
ruby '>= 3.2.0', '< 3.5.0'
gem 'propshaft'
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-concurrent_ruby', '~> 0.21.2', 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_client', '~> 0.22.3', require: false
gem 'opentelemetry-instrumentation-net_http', '~> 0.22.4', require: false

View file

@ -94,7 +94,7 @@ GEM
ast (2.4.2)
attr_required (1.0.2)
aws-eventstream (1.3.0)
aws-partitions (1.1029.0)
aws-partitions (1.1032.0)
aws-sdk-core (3.214.1)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
@ -103,7 +103,7 @@ GEM
aws-sdk-kms (1.96.0)
aws-sdk-core (~> 3, >= 3.210.0)
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-kms (~> 1)
aws-sigv4 (~> 1.5)
@ -160,7 +160,7 @@ GEM
cocoon (1.2.15)
color_diff (0.1)
concurrent-ruby (1.3.4)
connection_pool (2.4.1)
connection_pool (2.5.0)
cose (1.3.1)
cbor (~> 0.5.9)
openssl-signature_algorithm (~> 1.0)
@ -233,16 +233,16 @@ GEM
faraday-net_http (3.4.0)
net-http (>= 0.5.0)
fast_blank (1.0.1)
fastimage (2.3.1)
fastimage (2.4.0)
ffi (1.17.1)
ffi-compiler (1.3.2)
ffi (>= 1.15.5)
rake
flatware (2.3.3)
flatware (2.3.4)
drb
thor (< 2.0)
flatware-rspec (2.3.3)
flatware (= 2.3.3)
flatware-rspec (2.3.4)
flatware (= 2.3.4)
rspec (>= 3.6)
fog-core (2.5.0)
builder
@ -490,7 +490,7 @@ GEM
opentelemetry-instrumentation-active_job (0.7.8)
opentelemetry-api (~> 1.0)
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-instrumentation-active_support (>= 0.7.0)
opentelemetry-instrumentation-base (~> 0.22.1)
@ -510,7 +510,7 @@ GEM
opentelemetry-instrumentation-excon (0.22.5)
opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-base (~> 0.22.1)
opentelemetry-instrumentation-faraday (0.24.8)
opentelemetry-instrumentation-faraday (0.25.0)
opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-base (~> 0.22.1)
opentelemetry-instrumentation-http (0.23.5)
@ -522,7 +522,7 @@ GEM
opentelemetry-instrumentation-net_http (0.22.8)
opentelemetry-api (~> 1.0)
opentelemetry-instrumentation-base (~> 0.22.1)
opentelemetry-instrumentation-pg (0.29.1)
opentelemetry-instrumentation-pg (0.29.2)
opentelemetry-api (~> 1.0)
opentelemetry-helpers-sql-obfuscation
opentelemetry-instrumentation-base (~> 0.22.1)
@ -709,7 +709,7 @@ GEM
rspec-mocks (~> 3.0)
sidekiq (>= 5, < 8)
rspec-support (3.13.2)
rubocop (1.69.2)
rubocop (1.70.0)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
@ -723,7 +723,7 @@ GEM
parser (>= 3.3.1.0)
rubocop-capybara (2.21.0)
rubocop (~> 1.41)
rubocop-performance (1.23.0)
rubocop-performance (1.23.1)
rubocop (>= 1.48.1, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rails (2.28.0)
@ -744,7 +744,7 @@ GEM
ruby-vips (2.2.2)
ffi (~> 1.12)
logger
rubyzip (2.3.2)
rubyzip (2.4.1)
rufus-scheduler (3.9.2)
fugit (~> 1.1, >= 1.11.1)
safety_net_attestation (0.4.0)
@ -809,7 +809,7 @@ GEM
unicode-display_width (>= 1.1.1, < 3)
terrapin (1.0.1)
climate_control
test-prof (1.4.3)
test-prof (1.4.4)
thor (1.3.2)
tilt (2.5.0)
timeout (0.4.3)
@ -963,7 +963,7 @@ DEPENDENCIES
opentelemetry-instrumentation-active_model_serializers (~> 0.21.0)
opentelemetry-instrumentation-concurrent_ruby (~> 0.21.2)
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_client (~> 0.22.3)
opentelemetry-instrumentation-net_http (~> 0.22.4)
@ -1034,7 +1034,7 @@ DEPENDENCIES
xorcist (~> 1.1)
RUBY VERSION
ruby 3.3.6p108
ruby 3.4.1p0
BUNDLED WITH
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>
<source media="(prefers-color-scheme: dark)" srcset="./lib/assets/wordmark.dark.png?raw=true">
<source media="(prefers-color-scheme: light)" srcset="./lib/assets/wordmark.light.png?raw=true">
<img alt="Mastodon" src="./lib/assets/wordmark.light.png?raw=true" height="34">
</picture></h1>
> [!NOTE]
> Want to learn more about Mastodon?
> Click below to find out more in a video.
[![GitHub release](https://img.shields.io/github/release/mastodon/mastodon.svg)][releases]
[![Ruby Testing](https://github.com/mastodon/mastodon/actions/workflows/test-ruby.yml/badge.svg)](https://github.com/mastodon/mastodon/actions/workflows/test-ruby.yml)
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/mastodon/localized.svg)][crowdin]
<p align="center">
<a style="text-decoration:none" href="https://www.youtube.com/watch?v=IPSbNdBmWKE">
<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
[crowdin]: https://crowdin.com/project/mastodon
<p align="center">
<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!)
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
- [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%" />
### 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
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!
**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
@ -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.
## 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
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
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
[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
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
details.
You should have received a copy of the GNU Affero General Public License along
with this program. If not, see https://www.gnu.org/licenses/
```
[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?
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
def incomplete_page?
@notifications.size < limit_param(DEFAULT_NOTIFICATIONS_LIMIT)
end
def paginating_up?
params[:min_id].present?
end
def browserable_account_notifications
current_account.notifications.without_suspended.browserable(
types: Array(browserable_params[:types]),

View file

@ -1,10 +1,8 @@
# frozen_string_literal: true
class CustomCssController < ActionController::Base # rubocop:disable Rails/ApplicationController
before_action :set_user_roles
def show
expires_in 3.minutes, public: true
expires_in 1.month, public: true
render content_type: 'text/css'
end
@ -14,8 +12,4 @@ class CustomCssController < ActionController::Base # rubocop:disable Rails/Appli
Setting.custom_css
end
helper_method :custom_css_styles
def set_user_roles
@user_roles = UserRole.providing_styles
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
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
def preload_locale_pack
supported_locales = Themes.instance.flavour(current_flavour)['locales']

View file

@ -27,8 +27,31 @@ module ThemeHelper
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
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)
theme == 'mastodon-light' ? Themes::THEME_COLORS[:light] : Themes::THEME_COLORS[:dark]
end

View file

@ -119,7 +119,11 @@ function loaded() {
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;
});

View file

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

View file

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

View file

@ -119,7 +119,11 @@ function loaded() {
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;
});

View file

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

View file

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

View file

@ -1,6 +1,6 @@
import { Component } from 'react';
import type { IntlShape } from 'react-intl';
import type { MessageDescriptor, PrimitiveType, IntlShape } from 'react-intl';
import { injectIntl, defineMessages } from 'react-intl';
const messages = defineMessages({
@ -102,7 +102,13 @@ const getUnitDelay = (units: string) => {
};
export const timeAgoString = (
intl: Pick<IntlShape, 'formatDate' | 'formatMessage'>,
intl: {
formatDate: IntlShape['formatDate'];
formatMessage: (
{ id, defaultMessage }: MessageDescriptor,
values?: Record<string, PrimitiveType>,
) => string;
},
date: Date,
now: 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 now = new Date();
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');
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 (
<div className='announcements__item'>
<strong className='announcements__item__range'>
<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>
<Content announcement={announcement} />

View file

@ -457,6 +457,7 @@
"keyboard_shortcuts.toggle_hidden": "Dangos/cuddio testun tu ôl i CW",
"keyboard_shortcuts.toggle_sensitivity": "Dangos/cuddio cyfryngau",
"keyboard_shortcuts.toot": "Dechrau post newydd",
"keyboard_shortcuts.translate": "i gyfieithu postiad",
"keyboard_shortcuts.unfocus": "Dad-ffocysu ardal cyfansoddi testun/chwilio",
"keyboard_shortcuts.up": "Symud yn uwch yn y rhestr",
"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.redraft": "Dileu ac ailddrafftio",
"status.remove_bookmark": "Tynnu nod tudalen",
"status.remove_favourite": "Tynnu o'r ffefrynnau",
"status.replied_in_thread": "Atebodd mewn edefyn",
"status.replied_to": "Wedi ateb {name}",
"status.reply": "Ateb",

View file

@ -482,6 +482,7 @@
"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.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_name": "Listetitel",
"lists.new_list_name": "Ny listetitel",

View file

@ -362,6 +362,7 @@
"footer.privacy_policy": "Privatlívspolitikkur",
"footer.source_code": "Vís keldukotuna",
"footer.status": "Støða",
"footer.terms_of_service": "Tænastutreytir",
"generic.saved": "Goymt",
"getting_started.heading": "At byrja",
"hashtag.admin_moderation": "Lat umsjónarmarkamót upp fyri #{name}",
@ -858,6 +859,7 @@
"subscribed_languages.target": "Broyt haldaramál fyri {target}",
"tabs_bar.home": "Heim",
"tabs_bar.notifications": "Fráboðanir",
"terms_of_service.title": "Tænastutreytir",
"time_remaining.days": "{number, plural, one {# dagur} other {# dagar}} eftir",
"time_remaining.hours": "{number, plural, one {# tími} other {# tímar}} 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.search": "Fókuszálás a keresősávra",
"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_sensitivity": "Média megjelenítése/elrejtése",
"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.up": "Mozgás felfelé a listában",
"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_sensitivity": "Monstrar/celar multimedia",
"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.up": "Displaciar in alto in le lista",
"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.redraft": "Deler e reconciper",
"status.remove_bookmark": "Remover marcapagina",
"status.remove_favourite": "Remover del favoritos",
"status.replied_in_thread": "Respondite in le discussion",
"status.replied_to": "Respondite a {name}",
"status.reply": "Responder",

View file

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

View file

@ -52,7 +52,7 @@
"account.mute_notifications_short": "Izslēgt paziņojumu skaņu",
"account.mute_short": "Apklusināt",
"account.muted": "Apklusināts",
"account.mutual": "Savstarpējs",
"account.mutual": "Abpusēji",
"account.no_bio": "Apraksts nav sniegts.",
"account.open_original_page": "Atvērt oriģinālo lapu",
"account.posts": "Ieraksti",
@ -85,6 +85,7 @@
"alert.rate_limited.title": "Biežums ierobežots",
"alert.unexpected.message": "Radās negaidīta kļūda.",
"alert.unexpected.title": "Ups!",
"alt_text_badge.title": "Alt teksts",
"announcement.announcement": "Paziņojums",
"annual_report.summary.archetype.oracle": "Orākuls",
"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.redraft": "Slett & skriv på nytt",
"status.remove_bookmark": "Fjern bokmerke",
"status.remove_favourite": "Fjern frå favorittar",
"status.replied_in_thread": "Svara i tråden",
"status.replied_to": "Svarte {name}",
"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_following_title": "Ignoruj powiadomienia od użytkowników których nie obserwujesz?",
"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_this_server": "Na tym serwerze",
"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_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.",
"domain_block_modal.block": "Servidor de blocos.",
"domain_block_modal.block_account_instead": "Bloco @(nome)",
"domain_block_modal.block": "Bloquear servidor",
"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_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.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_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.",

View file

@ -85,7 +85,7 @@
"alert.rate_limited.title": "Limite de tentativas",
"alert.unexpected.message": "Ocorreu um erro inesperado.",
"alert.unexpected.title": "Bolas!",
"alt_text_badge.title": "Texto alternativo",
"alt_text_badge.title": "Texto descritivo",
"announcement.announcement": "Mensagem de manutenção",
"annual_report.summary.archetype.booster": "O caçador de frescura",
"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.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_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_cant_follow": "Ninguém deste servidor pode seguir-te.",
"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.username": "Nome de utilizador",
"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.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.",
@ -457,6 +457,7 @@
"keyboard_shortcuts.toggle_hidden": "mostrar / esconder texto atrás do aviso de conteúdo",
"keyboard_shortcuts.toggle_sensitivity": "mostrar / ocultar multimédia",
"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.up": "mover para cima na lista",
"lightbox.close": "Fechar",
@ -641,10 +642,10 @@
"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_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_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_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",
@ -836,6 +837,7 @@
"status.reblogs.empty": "Ainda ninguém impulsionou esta publicação. Quando alguém o fizer, aparecerá aqui.",
"status.redraft": "Eliminar e reescrever",
"status.remove_bookmark": "Retirar dos marcadores",
"status.remove_favourite": "Remover dos favoritos",
"status.replied_in_thread": "Responder na conversa",
"status.replied_to": "Respondeu a {name}",
"status.reply": "Responder",

View file

@ -141,7 +141,7 @@
"column.bookmarks": "Bokmärken",
"column.community": "Lokal tidslinje",
"column.create_list": "Skapa lista",
"column.direct": "Privata nämningar",
"column.direct": "Privata omnämnande",
"column.directory": "Bläddra bland profiler",
"column.domain_blocks": "Blockerade domäner",
"column.edit_list": "Redigera lista",
@ -239,6 +239,10 @@
"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.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_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.",
@ -285,7 +289,7 @@
"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.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.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.",
@ -402,7 +406,14 @@
"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_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_this_server": "På denna server",
"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.reply": "Svara på {name}s inlägg",
"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.hours": "{number, plural, one {# timme} other {# timmar}}",
"intervals.full.minutes": "{number, plural, one {# minut} other {# minuter}}",
@ -419,7 +431,7 @@
"keyboard_shortcuts.column": "Fokusera kolumn",
"keyboard_shortcuts.compose": "Fokusera skrivfältet",
"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.enter": "Öppna 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_sensitivity": "Visa/gömma media",
"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.up": "Flytta uppåt i listan",
"lightbox.close": "Stäng",
@ -466,6 +479,7 @@
"lists.delete": "Radera lista",
"lists.done": "Klar",
"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.find_users_to_add": "Hitta användare att lägga till",
"lists.list_members": "Lista medlemmar",
@ -474,12 +488,14 @@
"lists.new_list_name": "Nytt listnamn",
"lists.no_lists_yet": "Ännu inga listor.",
"lists.no_members_yet": "Inga medlemmar ännu.",
"lists.no_results_found": "Inga resultat hittades.",
"lists.remove_member": "Ta bort",
"lists.replies_policy.followed": "Alla användare som följs",
"lists.replies_policy.list": "Medlemmar i listan",
"lists.replies_policy.none": "Ingen",
"lists.save": "Spara",
"lists.search": "Sök",
"lists.show_replies_to": "Inkludera svar från listmedlemmar till",
"load_pending": "{count, plural, one {# nytt objekt} other {# nya objekt}}",
"loading_indicator.label": "Laddar…",
"media_gallery.hide": "Dölj",
@ -500,7 +516,7 @@
"navigation_bar.bookmarks": "Bokmärken",
"navigation_bar.community_timeline": "Lokal tidslinje",
"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.domain_blocks": "Dolda domäner",
"navigation_bar.explore": "Utforska",
@ -532,12 +548,14 @@
"notification.annual_report.view": "Visa #Wrapstodon",
"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_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_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_and_others": "{name} och {count, plural, one {# en annan} other {# andra}} har bett att följa dig",
"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.reply": "Svar",
"notification.mention": "Nämn",
@ -640,6 +658,7 @@
"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.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_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",
@ -677,6 +696,8 @@
"privacy_policy.title": "Integritetspolicy",
"recommended": "Rekommenderas",
"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.full.days": "{number, plural, one {# dag} other {# dagar}} sedan",
"relative_time.full.hours": "{number, plural, one {# timme} other {# timmar}} sedan",
@ -760,15 +781,18 @@
"search_results.accounts": "Profiler",
"search_results.all": "Alla",
"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.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.active_users": "aktiva användare",
"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.server_stats": "Serverstatistik:",
"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.sign_in": "Logga in",
"sign_in_banner.sso_redirect": "Logga in eller registrera dig",
@ -783,8 +807,8 @@
"status.copy": "Kopiera inläggslänk",
"status.delete": "Radera",
"status.detailed_status": "Detaljerad samtalsvy",
"status.direct": "Nämn @{name} privat",
"status.direct_indicator": "Privat nämning",
"status.direct": "Omnämn @{name} privat",
"status.direct_indicator": "Privat omnämnande",
"status.edit": "Redigera",
"status.edited": "Senast ändrad {date}",
"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.redraft": "Radera & gör om",
"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_to": "Svarade på {name}",
"status.reply": "Svara",
@ -834,6 +859,7 @@
"subscribed_languages.target": "Ändra språkprenumerationer för {target}",
"tabs_bar.home": "Hem",
"tabs_bar.notifications": "Aviseringar",
"terms_of_service.title": "Användarvillkor",
"time_remaining.days": "{number, plural, one {# dag} other {# dagar}} kvar",
"time_remaining.hours": "{number, plural, one {# timme} other {# timmar}} kvar",
"time_remaining.minutes": "{number, plural, one {# minut} other {# minuter}} kvar",

View file

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

View file

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

View file

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

View file

@ -111,16 +111,10 @@ class Request
end
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?
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

View file

@ -107,23 +107,23 @@ class Account < ApplicationRecord
validates_with UniqueUsernameValidator, if: -> { will_save_change_to_username? }
# 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
validates :uri, presence: true, unless: :local?, on: :create
# 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_with UnreservedUsernameValidator, 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 :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 :fields, length: { maximum: DEFAULT_FIELDS_SIZE }, if: -> { local? && will_save_change_to_fields? }
validates_with EmptyProfileFieldNamesValidator, if: -> { local? && will_save_change_to_fields? }
with_options on: :create do
validates :uri, absence: true, if: :local?
validates :inbox_url, absence: true, if: :local?
validates :shared_inbox_url, absence: true, if: :local?
validates :followers_url, absence: true, if: :local?
with_options on: :create, if: :local? do
validates :followers_url, absence: true
validates :inbox_url, absence: true
validates :shared_inbox_url, absence: true
validates :uri, absence: true
end
normalizes :username, with: ->(username) { username.squish }
@ -186,6 +186,10 @@ class Account < ApplicationRecord
domain.nil?
end
def remote?
domain.present?
end
def moved?
moved_to_account_id.present?
end
@ -204,6 +208,10 @@ class Account < ApplicationRecord
self.actor_type = ActiveModel::Type::Boolean.new.cast(val) ? 'Service' : 'Person'
end
def actor_type_application?
actor_type == 'Application'
end
def group?
actor_type == 'Group'
end

View file

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

View file

@ -16,8 +16,6 @@
# updated_at :datetime not null
#
class Appeal < ApplicationRecord
MAX_STRIKE_AGE = 20.days
TEXT_LENGTH_LIMIT = 2_000
belongs_to :account
@ -68,6 +66,6 @@ class Appeal < ApplicationRecord
private
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

View file

@ -84,6 +84,10 @@ class Form::AdminSettings
flavour_and_skin
).freeze
DIGEST_KEYS = %i(
custom_css
).freeze
OVERRIDEN_SETTINGS = {
authorized_fetch: :authorized_fetch_mode?,
}.freeze
@ -137,6 +141,8 @@ class Form::AdminSettings
KEYS.each do |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)
public_send(key).save
else
@ -156,6 +162,18 @@ class Form::AdminSettings
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)
if BOOLEAN_KEYS.include?(key)
value == '1'

View file

@ -64,21 +64,31 @@ class NotificationGroup < ActiveModelSerializers::Model
binds = [
account_id,
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)),
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] }
SELECT
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),
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),
(SELECT count(*) FROM notifications WHERE notifications.account_id = $1 AND notifications.group_key = groups.group_key AND id <= $4) 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 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 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 #{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 #{upper_bound_cond}) AS notifications_count,
(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 #{upper_bound_cond} ORDER BY id DESC LIMIT 1)
FROM
unnest($5::text[]) AS groups(group_key);
unnest($3::text[]) AS groups(group_key);
SQL
else
binds = [

View file

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

View file

@ -16,7 +16,7 @@ class Trends::Links < Trends::Base
class Query < Trends::Query
def to_arel
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.offset(@offset) if @offset.present?
scope = scope.limit(@limit) if @limit.present?
@ -26,7 +26,7 @@ class Trends::Links < Trends::Base
private
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

View file

@ -94,6 +94,13 @@ class Trends::Query
to_arel.to_a
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
if @account&.chosen_languages.present?
@account.chosen_languages

View file

@ -15,7 +15,7 @@ class Trends::Statuses < Trends::Base
class Query < Trends::Query
def to_arel
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.not_excluded_by_account(@account).not_domain_blocked_by_account(@account) if @account.present?
scope = scope.offset(@offset) if @offset.present?
@ -26,7 +26,7 @@ class Trends::Statuses < Trends::Base
private
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

View file

@ -15,7 +15,8 @@ class Trends::Tags < Trends::Base
class Query < Trends::Query
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.offset(@offset) if @offset.present?
scope = scope.limit(@limit) if @limit.present?
@ -25,7 +26,7 @@ class Trends::Tags < Trends::Base
private
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

View file

@ -42,6 +42,7 @@ class UserRole < ApplicationRecord
NOBODY_POSITION = -1
POSITION_LIMIT = (2**31) - 1
CSS_COLORS = /\A#?(?:[A-F0-9]{3}){1,2}\z/i # CSS-style hex colors
module Flags
NONE = 0
@ -90,7 +91,7 @@ class UserRole < ApplicationRecord
attr_writer :current_account
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) }
validate :validate_permissions_elevation
@ -101,9 +102,6 @@ class UserRole < ApplicationRecord
before_validation :set_position
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

View file

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

View file

@ -12,7 +12,7 @@ class Admin::StatusPolicy < ApplicationPolicy
end
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
def destroy?
@ -29,6 +29,10 @@ class Admin::StatusPolicy < ApplicationPolicy
private
def eligible_to_show?
record.distributable? || record.reported? || viewable_through_normal_policy?
end
def viewable_through_normal_policy?
StatusPolicy.new(current_account, record, @preloaded_relations).show?
end

View file

@ -10,10 +10,16 @@ class UserRolePolicy < ApplicationPolicy
end
def update?
role.can?(:manage_roles) && (role.overrides?(record) || role.id == record.id)
role.can?(:manage_roles) && (role.overrides?(record) || self_editing?)
end
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

View file

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

View file

@ -4,7 +4,7 @@
.report-notes__item__header
%span.username
= 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
.report-notes__item__content

View file

@ -18,7 +18,7 @@
= link_to report.account.username, admin_account_path(report.account_id)
- else
= 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
.report-notes__item__content
= simple_format(h(report.comment))

View file

@ -1,7 +1,7 @@
.announcements-list__item
- if can?(:update, role)
= 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'
- if role.everyone?
@ -10,7 +10,7 @@
= role.name
- else
%span.announcements-list__item__title
%span.user-role{ class: "user-role-#{role.id}" }
%span.user-role
= material_symbol 'group'
- if role.everyone?

View file

@ -29,8 +29,8 @@
%div
- if defined?(show_apps_buttons) && show_apps_buttons
.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-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-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), app_store_url_android
- elsif defined?(button_text) && defined?(button_url) && defined?(checked) && !checked
= render 'application/mailer/button', text: button_text, url: button_url, has_arrow: false
/[if mso]

View file

@ -2,9 +2,3 @@
<%= raw custom_css_styles %>
<%- 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
%span.username
= 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
.report-notes__item__content

View file

@ -35,7 +35,7 @@
= csrf_meta_tags unless skip_csrf_meta_tags?
%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

View file

@ -30,8 +30,8 @@
5. <%= t('user_mailer.welcome.apps_title') %>
<%= t('user_mailer.welcome.apps_step') %>
* iOS: https://apps.apple.com/app/mastodon-for-iphone-and-ipad/id1571998974
* Android: https://play.google.com/store/apps/details?id=org.joinmastodon.android
* iOS: <%= app_store_url_ios %>
* 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:
account:
attributes:
fields:
fields_with_values_missing_labels: yn cynnwys gwerthoedd gyda labeli coll
username:
invalid: rhaid iddo gynnwys dim ond llythrennau, rhifau a thanlinellau
reserved: wedi ei neilltuo

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -214,6 +214,7 @@ fo:
enable_user: Ger brúkara virknan
memorialize_account: Minnst til Konto
promote_user: Vís fram Brúkara
publish_terms_of_service: Útgev tænastutreytir
reject_appeal: Avvís mótmali
reject_user: Avvís Brúkara
remove_avatar_user: Sletta Avatar
@ -278,6 +279,7 @@ fo:
enable_user_html: "%{name} gjørdi innritan virkna fyri brúkaran %{target}"
memorialize_account_html: "%{name} broytti kontuna hjá %{target} til eina minnissíðu"
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_user_html: "%{name} avvísti skráseting hjá %{target}"
remove_avatar_user_html: "%{name} strikaði eftirgjørda skapningin hjá %{target}"
@ -925,6 +927,32 @@ fo:
search: Leita
title: Frámerki
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
trends:
allow: Loyv
@ -1156,6 +1184,7 @@ fo:
set_new_password: Áset nýtt loyniorð
setup:
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?
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
@ -1164,6 +1193,7 @@ fo:
title: Rita inn á %{domain}
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}.
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}.
status:
account_status: Kontustøða
@ -1175,6 +1205,7 @@ fo:
view_strikes: Vís eldri atsóknir móti tíni kontu
too_fast: Oyðublaðið innsent ov skjótt, royn aftur.
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:
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.
@ -1836,6 +1867,8 @@ fo:
too_late: Tað er ov seint at kæra hesa atsókn
tags:
does_not_match_previous_name: samsvarar ikki við undanfarna navnið
terms_of_service:
title: Tænastutreytir
themes:
contrast: Mastodon (høgur kontrastur)
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.
subject: Atgongd er fingin til kontu tína frá eini nýggjari IP adressu
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:
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}.

View file

@ -1859,9 +1859,9 @@ gl:
'63113904': 2 anos
'7889238': 3 meses
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_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
stream_entries:
sensitive_content: Contido sensible

View file

@ -217,6 +217,7 @@ lv:
enable_user: Ieslēgt Lietotāju
memorialize_account: Saglabāt Kontu Piemiņai
promote_user: Izceltt Lietotāju
publish_terms_of_service: Publicēt pakalpojuma izmantošanas noteikumus
reject_appeal: Noraidīt Apelāciju
reject_user: Noraidīt lietotāju
remove_avatar_user: Noņemt profila attēlu
@ -273,6 +274,7 @@ lv:
enable_user_html: "%{name} iespējoja pieteikšanos lietotājam %{target}"
memorialize_account_html: "%{name} pārvērta %{target} kontu par atmiņas lapu"
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_user_html: "%{name} noraidīja reģistrēšanos no %{target}"
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_unresolve: Atvērt atkārtoti ar piezīmi
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
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'
@ -746,13 +748,13 @@ lv:
rules:
add_new: Pievienot noteikumu
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
empty: Servera noteikumi vēl nav definēti.
empty: Vēl nav pievienots neviens servera noteikums.
title: Servera noteikumi
settings:
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.
rules_hint: Noteikumiem, kas taviem lietotājiem ir jāievēro, ir īpaša sadaļa.
title: Par
@ -821,6 +823,7 @@ lv:
back_to_account: Atpakaļ uz konta lapu
back_to_report: Atpakaļ uz paziņojumu lapu
batch:
add_to_report: 'Pievienot atskaitei #%{id}'
remove_from_report: Noņemt no ziņojuma
report: Ziņojums
contents: Saturs
@ -832,13 +835,17 @@ lv:
media:
title: Multivide
metadata: Metadati
no_history: Šis ieraksts nav bijis labots
no_status_selected: Neviena ziņa netika mainīta, jo neviena netika atlasīta
open: Atvērt ziņu
original_status: Oriģinālā ziņa
reblogs: Reblogi
replied_to_html: Atbildēja %{acct_link}
status_changed: Ziņa mainīta
status_title: Publicēja @%{name}
title: Konta ieraksti - @%{name}
trending: Aktuāli
view_publicly: Skatīt publiski
visibility: Redzamība
with_media: Ar multividi
strikes:
@ -876,8 +883,8 @@ lv:
message_html: 'Nesaderīga Elasticsearch versija: %{value}'
version_comparison: Darbojas Elasticsearch %{running_version}, tomēr ir nepieciešama %{required_version}
rules_check:
action: Pārvaldīt servera nosacījumus
message_html: Tu neesi definējis nevienu servera nosacījumu.
action: Pārvaldīt servera noteikumus
message_html: Nav pievienots neviens servera noteikums.
sidekiq_process_check:
message_html: Rindā(s) %{value} nedarbojas neviens Sidekiq process. Lūdzu, pārskati savu Sidekiq konfigurāciju
software_version_check:
@ -907,16 +914,42 @@ lv:
name: Nosaukums
newest: Jaunākie
oldest: Vecākie
open: Apskatīt publiski
reset: Atiestatīt
review: Pārskatīt stāvokli
search: Meklēt
title: Tēmturi
updated_msg: Tēmtura iestatījumi ir veiksmīgi atjaunināti
terms_of_service:
back: Atpakaļ uz pakalpojuma izmantošanas noteikumiem
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
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
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
trends:
allow: Atļaut
@ -1157,6 +1190,7 @@ lv:
view_strikes: Skati iepriekšējos brīdinājumus par savu kontu
too_fast: Veidlapa ir iesniegta pārāk ātri, mēģini vēlreiz.
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:
example_title: Parauga teksts
more_from_html: Vairāk no %{name}
@ -1777,6 +1811,8 @@ lv:
too_late: Brīdinājuma apstrīdēšanas laiks ir nokavēts
tags:
does_not_match_previous_name: nesakrīt ar iepriekšējo nosaukumu
terms_of_service:
title: Pakalpojuma izmantošanas noteikumi
themes:
contrast: Mastodon (Augsts kontrasts)
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ā.
subject: Tavam kontam ir piekļūts no jaunas IP adreses
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:
appeal: Iesniegt apelāciju
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:
one: Seguidor
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.
last_active: última atividade
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.
tag:
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:
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.
@ -319,6 +330,17 @@ fo:
name: Tvíkrossur
trendable: Loyv hesum frámerki at síggjast undir rákum
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:
role: Leiklutur
time_zone: Tíðarsona

View file

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

View file

@ -53,7 +53,7 @@ lv:
locale: Lietotāja saskarnes, e-pasta ziņojumu un push paziņojumu valoda
password: Izmanto vismaz 8 rakstzīmes
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_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
@ -119,8 +119,8 @@ lv:
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
rule:
hint: Izvēles. Sniedz vairāk informācijas par nosacījumu
text: Apraksti nosacījumus vai prasības šī servera lietotājiem. Centies, lai tas būtu īss un vienkāršs
hint: Izvēles. Sniedz vairāk informācijas par noteikumu
text: Jāapraksta nosacījums vai prasība šī servera lietotājiem. Jāmēģina to veidot īsu un vienkāršu
sessions:
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.
@ -317,6 +317,11 @@ lv:
name: Tēmturis
trendable: Atļaut šim tēmturim parādīties zem tendencēm
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:
role: Loma
time_zone: Laika josla

View file

@ -161,7 +161,7 @@ pt-PT:
fields:
name: Rótulo
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
unlocked: Aceitar automaticamente novos seguidores
account_alias:
@ -205,7 +205,7 @@ pt-PT:
email: Endereço de correio electrónico
expires_in: Expira em
fields: Metadados de perfil
header: Cabeçalho
header: Imagem de cabeçalho
honeypot: "%{label} (não preencher)"
inbox_url: URL da caixa de entrada do repetidor
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.
tag:
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:
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.
@ -151,7 +159,7 @@ sv:
name: Etikett
value: Innehåll
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
account_alias:
acct: Namnet på det gamla kontot
@ -224,6 +232,7 @@ sv:
setting_hide_network: Göm ditt nätverk
setting_reduce_motion: Minska rörelser i animationer
setting_system_font_ui: Använd systemets standardfont
setting_system_scrollbars_ui: Använd systemets standardrullningsfält
setting_theme: Sidans tema
setting_trends: Visa dagens trender
setting_unfollow_modal: Visa bekräftelse innan du slutar följa någon
@ -318,6 +327,14 @@ sv:
name: Hashtagg
trendable: Tillåt denna hashtagg att visas under trender
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:
role: Roll
time_zone: Tidszon

View file

@ -2,3 +2,6 @@
shared:
self_destruct_value: <%= ENV.fetch('SELF_DESTRUCT', nil) %>
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 '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'

View file

@ -100,7 +100,8 @@ services:
- redis
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
restart: always
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_sidekiq_not_active!
verify_backup_warning!
disable_timeout!
end
def process_deduplications
@ -251,6 +252,13 @@ module Mastodon::CLI
fail_with_message 'Maintenance process stopped.' unless yes?('Continue? (Yes/No)')
end
def disable_timeout!
# Remove server-configured timeout if present
database_connection.execute(<<~SQL.squish)
SET statement_timeout = 0
SQL
end
def deduplicate_accounts!
remove_index_if_exists!(:accounts, 'index_accounts_on_username_and_domain_lower')

View file

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

View file

@ -102,7 +102,7 @@
"react-hotkeys": "^1.1.4",
"react-immutable-proptypes": "^2.2.0",
"react-immutable-pure-component": "^2.2.2",
"react-intl": "^6.4.2",
"react-intl": "^7.0.0",
"react-motion": "^0.5.2",
"react-notification": "^6.8.5",
"react-overlays": "^5.2.1",
@ -201,6 +201,8 @@
"webpack-dev-server": "^3.11.3"
},
"resolutions": {
"@types/react": "^18.2.7",
"@types/react-dom": "^18.2.4",
"kind-of": "^6.0.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'
RSpec.describe ApplicationController do
render_views
controller do
def success
head 200
@ -23,9 +25,22 @@ RSpec.describe ApplicationController do
shared_examples 'respond_with_error' do |code|
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

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

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