Merge remote-tracking branch 'upstream/main'

This commit is contained in:
Dalite 2023-11-30 12:12:59 +01:00
commit 91a78e0652
144 changed files with 1634 additions and 815 deletions

View file

@ -24,4 +24,4 @@ RAILS_ENV=development ./bin/rails db:setup
RAILS_ENV=development ./bin/rails assets:precompile
# Precompile assets for test
RAILS_ENV=test NODE_ENV=tests ./bin/rails assets:precompile
RAILS_ENV=test ./bin/rails assets:precompile

View file

@ -1,4 +1,7 @@
module.exports = {
// @ts-check
const { defineConfig } = require('eslint-define-config');
module.exports = defineConfig({
root: true,
extends: [
@ -193,6 +196,7 @@ module.exports = {
'error',
{
devDependencies: [
'.eslintrc.js',
'config/webpack/**',
'app/javascript/mastodon/performance.js',
'app/javascript/mastodon/test_setup.js',
@ -280,7 +284,6 @@ module.exports = {
'formatjs/no-id': 'off', // IDs are used for translation keys
'formatjs/no-invalid-icu': 'error',
'formatjs/no-literal-string-in-jsx': 'off', // Should be looked at, but mainly flagging punctuation outside of strings
'formatjs/no-multiple-plurals': 'off', // Only used by hashtag.jsx
'formatjs/no-multiple-whitespaces': 'error',
'formatjs/no-offset': 'error',
'formatjs/no-useless-message': 'error',
@ -299,6 +302,7 @@ module.exports = {
overrides: [
{
files: [
'.eslintrc.js',
'*.config.js',
'.*rc.js',
'ide-helper.js',
@ -349,7 +353,7 @@ module.exports = {
'@typescript-eslint/consistent-type-definitions': ['warn', 'interface'],
'@typescript-eslint/consistent-type-exports': 'error',
'@typescript-eslint/consistent-type-imports': 'error',
"@typescript-eslint/prefer-nullish-coalescing": ['error', {ignorePrimitives: {boolean: true}}],
"@typescript-eslint/prefer-nullish-coalescing": ['error', { ignorePrimitives: { boolean: true } }],
'jsdoc/require-jsdoc': 'off',
@ -372,14 +376,6 @@ module.exports = {
env: {
jest: true,
},
},
{
files: [
'streaming/**/*',
],
rules: {
'import/no-commonjs': 'off',
},
},
}
],
};
});

View file

@ -22,6 +22,7 @@
'react-hotkeys', // Requires code changes
// Requires Webpacker upgrade or replacement
'@svgr/webpack',
'@types/webpack',
'babel-loader',
'compression-webpack-plugin',

View file

@ -21,6 +21,8 @@ on:
type: string
labels:
type: string
file_to_build:
type: string
jobs:
build-image:
@ -86,6 +88,7 @@ jobs:
- uses: docker/build-push-action@v5
with:
context: .
file: ${{ inputs.file_to_build }}
build-args: |
MASTODON_VERSION_PRERELEASE=${{ inputs.version_prerelease }}
MASTODON_VERSION_METADATA=${{ inputs.version_metadata }}

View file

@ -25,6 +25,7 @@ jobs:
needs: compute-suffix
uses: ./.github/workflows/build-container-image.yml
with:
file_to_build: Dockerfile
platforms: linux/amd64,linux/arm64
use_native_arm64_builder: true
cache: false
@ -41,3 +42,25 @@ jobs:
type=raw,value=nightly
type=schedule,pattern=${{ needs.compute-suffix.outputs.prerelease }}
secrets: inherit
build-image-streaming:
needs: compute-suffix
uses: ./.github/workflows/build-container-image.yml
with:
file_to_build: streaming/Dockerfile
platforms: linux/amd64,linux/arm64
use_native_arm64_builder: true
cache: false
push_to_images: |
tootsuite/mastodon-streaming
ghcr.io/mastodon/mastodon-streaming
version_prerelease: ${{ needs.compute-suffix.outputs.prerelease }}
labels: |
org.opencontainers.image.description=Nightly build image used for testing purposes
flavor: |
latest=auto
tags: |
type=raw,value=edge
type=raw,value=nightly
type=schedule,pattern=${{ needs.compute-suffix.outputs.prerelease }}
secrets: inherit

View file

@ -29,6 +29,7 @@ jobs:
needs: compute-suffix
uses: ./.github/workflows/build-container-image.yml
with:
file_to_build: Dockerfile
platforms: linux/amd64,linux/arm64
use_native_arm64_builder: true
push_to_images: |
@ -39,3 +40,19 @@ jobs:
tags: |
type=ref,event=pr
secrets: inherit
build-image-streaming:
needs: compute-suffix
uses: ./.github/workflows/build-container-image.yml
with:
file_to_build: streaming/Dockerfile
platforms: linux/amd64,linux/arm64
use_native_arm64_builder: true
push_to_images: |
ghcr.io/mastodon/mastodon-streaming
version_metadata: ${{ needs.compute-suffix.outputs.metadata }}
flavor: |
latest=auto
tags: |
type=ref,event=pr
secrets: inherit

View file

@ -12,6 +12,7 @@ jobs:
build-image:
uses: ./.github/workflows/build-container-image.yml
with:
file_to_build: Dockerfile
platforms: linux/amd64,linux/arm64
use_native_arm64_builder: true
push_to_images: |
@ -27,3 +28,24 @@ jobs:
type=pep440,pattern={{raw}}
type=pep440,pattern=v{{major}}.{{minor}}
secrets: inherit
build-image-streaming:
if: startsWith(github.ref, 'refs/tags/v4.3.')
uses: ./.github/workflows/build-container-image.yml
with:
file_to_build: streaming/Dockerfile
platforms: linux/amd64,linux/arm64
use_native_arm64_builder: true
push_to_images: |
tootsuite/mastodon-streaming
ghcr.io/mastodon/mastodon-streaming
# Do not use cache when building releases, so apt update is always ran and the release always contain the latest packages
cache: false
# Only tag with latest when ran against the latest stable branch
# This needs to be updated after each minor version release
flavor: |
latest=${{ startsWith(github.ref, 'refs/tags/v4.3.') }}
tags: |
type=pep440,pattern={{raw}}
type=pep440,pattern=v{{major}}.{{minor}}
secrets: inherit

View file

@ -7,6 +7,7 @@ on:
- .github/workflows/build-releases.yml
- .github/workflows/test-image-build.yml
- Dockerfile
- streaming/Dockerfile
permissions:
contents: read
@ -18,4 +19,17 @@ jobs:
uses: ./.github/workflows/build-container-image.yml
with:
file_to_build: Dockerfile
platforms: linux/amd64 # Testing only on native platform so it is performant
cache: true
build-image-streaming:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-streaming
cancel-in-progress: true
uses: ./.github/workflows/build-container-image.yml
with:
file_to_build: streaming/Dockerfile
platforms: linux/amd64 # Testing only on native platform so it is performant
cache: true

View file

@ -307,7 +307,7 @@ Style/FetchEnvVar:
- 'config/initializers/devise.rb'
- 'config/initializers/paperclip.rb'
- 'config/initializers/vapid.rb'
- 'lib/mastodon/premailer_webpack_strategy.rb'
- 'lib/premailer_webpack_strategy.rb'
- 'lib/mastodon/redis_config.rb'
- 'lib/tasks/repo.rake'
- 'spec/features/profile_spec.rb'
@ -357,8 +357,8 @@ Style/GuardClause:
- 'config/initializers/devise.rb'
- 'db/migrate/20170901141119_truncate_preview_cards.rb'
- 'db/post_migrate/20220704024901_migrate_settings_to_user_roles.rb'
- 'lib/devise/two_factor_ldap_authenticatable.rb'
- 'lib/devise/two_factor_pam_authenticatable.rb'
- 'lib/devise/strategies/two_factor_ldap_authenticatable.rb'
- 'lib/devise/strategies/two_factor_pam_authenticatable.rb'
- 'lib/mastodon/cli/accounts.rb'
- 'lib/mastodon/cli/maintenance.rb'
- 'lib/mastodon/cli/media.rb'
@ -493,8 +493,8 @@ Style/SafeNavigation:
# SupportedStyles: only_raise, only_fail, semantic
Style/SignalException:
Exclude:
- 'lib/devise/two_factor_ldap_authenticatable.rb'
- 'lib/devise/two_factor_pam_authenticatable.rb'
- 'lib/devise/strategies/two_factor_ldap_authenticatable.rb'
- 'lib/devise/strategies/two_factor_pam_authenticatable.rb'
# This cop supports unsafe autocorrection (--autocorrect-all).
Style/SingleArgumentDig:

View file

@ -1,112 +1,257 @@
# syntax=docker/dockerfile:1.4
# This needs to be bookworm-slim because the Ruby image is built on bookworm-slim
ARG NODE_VERSION="20.9-bookworm-slim"
FROM ghcr.io/moritzheiber/ruby-jemalloc:3.2.2-slim as ruby
FROM node:${NODE_VERSION} as build
# Please see https://docs.docker.com/engine/reference/builder for information about
# the extended buildx capabilities used in this file.
# Make sure multiarch TARGETPLATFORM is available for interpolation
# See: https://docs.docker.com/build/building/multi-platform/
ARG TARGETPLATFORM=${TARGETPLATFORM}
ARG BUILDPLATFORM=${BUILDPLATFORM}
COPY --link --from=ruby /opt/ruby /opt/ruby
# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.2.2"]
ARG RUBY_VERSION="3.2.2"
# # Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"]
ARG NODE_MAJOR_VERSION="20"
# Debian image to use for base image, change with [--build-arg DEBIAN_VERSION="bookworm"]
ARG DEBIAN_VERSION="bookworm"
# Node image to use for base image based on combined variables (ex: 20-bookworm-slim)
FROM docker.io/node:${NODE_MAJOR_VERSION}-${DEBIAN_VERSION}-slim as node
# Ruby image to use for base image based on combined variables (ex: 3.2.2-slim-bookworm)
FROM docker.io/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} as ruby
ENV DEBIAN_FRONTEND="noninteractive" \
PATH="${PATH}:/opt/ruby/bin"
# Resulting version string is vX.X.X-MASTODON_VERSION_PRERELEASE+MASTODON_VERSION_METADATA
# Example: v4.2.0-nightly.2023.11.09+something
# Overwrite existance of 'alpha.0' in version.rb [--build-arg MASTODON_VERSION_PRERELEASE="nightly.2023.11.09"]
ARG MASTODON_VERSION_PRERELEASE="bark"
# Append build metadata or fork information to version.rb [--build-arg MASTODON_VERSION_METADATA="something"]
ARG MASTODON_VERSION_METADATA="dev"
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Allow Ruby on Rails to serve static files
# See: https://docs.joinmastodon.org/admin/config/#rails_serve_static_files
ARG RAILS_SERVE_STATIC_FILES="true"
# Allow to use YJIT compiler
# See: https://github.com/ruby/ruby/blob/master/doc/yjit/yjit.md
ARG RUBY_YJIT_ENABLE="1"
# Timezone used by the Docker container and runtime, change with [--build-arg TZ=Europe/Berlin]
ARG TZ="Etc/UTC"
# Linux UID (user id) for the mastodon user, change with [--build-arg UID=1234]
ARG UID="991"
# Linux GID (group id) for the mastodon user, change with [--build-arg GID=1234]
ARG GID="991"
# Apply Mastodon build options based on options above
ENV \
# Apply Mastodon version information
MASTODON_VERSION_PRERELEASE="${MASTODON_VERSION_PRERELEASE}" \
MASTODON_VERSION_METADATA="${MASTODON_VERSION_METADATA}" \
# Apply Mastodon static files and YJIT options
RAILS_SERVE_STATIC_FILES=${RAILS_SERVE_STATIC_FILES} \
RUBY_YJIT_ENABLE=${RUBY_YJIT_ENABLE} \
# Apply timezone
TZ=${TZ}
ENV \
# Configure the IP to bind Mastodon to when serving traffic
BIND="0.0.0.0" \
# Use production settings for Yarn, Node and related nodejs based tools
NODE_ENV="production" \
# Use production settings for Ruby on Rails
RAILS_ENV="production" \
# Add Ruby and Mastodon installation to the PATH
DEBIAN_FRONTEND="noninteractive" \
PATH="${PATH}:/opt/ruby/bin:/opt/mastodon/bin" \
# Optimize jemalloc 5.x performance
MALLOC_CONF="narenas:2,background_thread:true,thp:never,dirty_decay_ms:1000,muzzy_decay_ms:0"
# Set default shell used for running commands
SHELL ["/bin/bash", "-o", "pipefail", "-o", "errexit", "-c"]
ARG TARGETPLATFORM
RUN echo "Target platform is $TARGETPLATFORM"
RUN \
# Remove automatic apt cache Docker cleanup scripts
rm -f /etc/apt/apt.conf.d/docker-clean; \
# Sets timezone
echo "${TZ}" > /etc/localtime; \
# Creates mastodon user/group and sets home directory
groupadd -g "${GID}" mastodon; \
useradd -l -u "${UID}" -g "${GID}" -m -d /opt/mastodon mastodon; \
# Creates /mastodon symlink to /opt/mastodon
ln -s /opt/mastodon /mastodon;
# Set /opt/mastodon as working directory
WORKDIR /opt/mastodon
# hadolint ignore=DL3008
RUN apt-get update && \
apt-get -yq dist-upgrade && \
apt-get install -y --no-install-recommends build-essential \
git \
libicu-dev \
libidn-dev \
libpq-dev \
libjemalloc-dev \
zlib1g-dev \
libgdbm-dev \
libgmp-dev \
libssl-dev \
libyaml-dev \
ca-certificates \
libreadline8 \
python3 \
shared-mime-info && \
bundle config set --local deployment 'true' && \
bundle config set --local without 'development test' && \
bundle config set silence_root_warning true && \
corepack enable
# hadolint ignore=DL3008,DL3005
RUN \
# Mount Apt cache and lib directories from Docker buildx caches
--mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \
--mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
# Apt update & upgrade to check for security updates to Debian image
apt-get update; \
apt-get dist-upgrade -yq; \
# Install jemalloc, curl and other necessary components
apt-get install -y --no-install-recommends \
ca-certificates \
curl \
ffmpeg \
file \
imagemagick \
libjemalloc2 \
patchelf \
procps \
tini \
tzdata \
; \
# Patch Ruby to use jemalloc
patchelf --add-needed libjemalloc.so.2 /usr/local/bin/ruby; \
# Discard patchelf after use
apt-get purge -y \
patchelf \
;
COPY Gemfile* package.json yarn.lock .yarnrc.yml /opt/mastodon/
# Create temporary build layer from base image
FROM ruby as build
# Copy Node package configuration files into working directory
COPY package.json yarn.lock .yarnrc.yml /opt/mastodon/
COPY .yarn /opt/mastodon/.yarn
COPY --from=node /usr/local/bin /usr/local/bin
COPY --from=node /usr/local/lib /usr/local/lib
ARG TARGETPLATFORM
# hadolint ignore=DL3008
RUN \
# Mount Apt cache and lib directories from Docker buildx caches
--mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \
--mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
# Install build tools and bundler dependencies from APT
apt-get install -y --no-install-recommends \
g++ \
gcc \
git \
libgdbm-dev \
libgmp-dev \
libicu-dev \
libidn-dev \
libpq-dev \
libssl-dev \
make \
shared-mime-info \
zlib1g-dev \
;
RUN \
# Configure Corepack
rm /usr/local/bin/yarn*; \
corepack enable; \
corepack prepare --activate;
# Create temporary bundler specific build layer from build layer
FROM build as bundler
ARG TARGETPLATFORM
# Copy Gemfile config into working directory
COPY Gemfile* /opt/mastodon/
RUN \
# Mount Ruby Gem caches
--mount=type=cache,id=gem-cache-${TARGETPLATFORM},target=/usr/local/bundle/cache/,sharing=locked \
# Configure bundle to prevent changes to Gemfile and Gemfile.lock
bundle config set --global frozen "true"; \
# Configure bundle to not cache downloaded Gems
bundle config set --global cache_all "false"; \
# Configure bundle to only process production Gems
bundle config set --local without "development test"; \
# Configure bundle to not warn about root user
bundle config set silence_root_warning "true"; \
# Download and install required Gems
bundle install -j"$(nproc)";
# Create temporary node specific build layer from build layer
FROM build as yarn
ARG TARGETPLATFORM
# Copy Node package configuration files into working directory
COPY package.json yarn.lock .yarnrc.yml /opt/mastodon/
COPY streaming/package.json /opt/mastodon/streaming/
COPY .yarn /opt/mastodon/.yarn
RUN bundle install -j"$(nproc)"
# hadolint ignore=DL3008
RUN \
--mount=type=cache,id=corepack-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/corepack,sharing=locked \
--mount=type=cache,id=yarn-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/yarn,sharing=locked \
# Install Node packages
yarn workspaces focus --production @mastodon/mastodon;
RUN yarn workspaces focus --all --production && \
yarn cache clean
# Create temporary assets build layer from build layer
FROM build as precompiler
FROM node:${NODE_VERSION}
# Copy Mastodon sources into precompiler layer
COPY . /opt/mastodon/
# Use those args to specify your own version flags & suffixes
ARG MASTODON_VERSION_PRERELEASE="bark"
ARG MASTODON_VERSION_METADATA="dev"
# Copy bundler and node packages from build layer to container
COPY --from=yarn /opt/mastodon /opt/mastodon/
COPY --from=bundler /opt/mastodon /opt/mastodon/
COPY --from=bundler /usr/local/bundle/ /usr/local/bundle/
ARG UID="991"
ARG GID="991"
ARG TARGETPLATFORM
COPY --link --from=ruby /opt/ruby /opt/ruby
RUN \
# Use Ruby on Rails to create Mastodon assets
OTP_SECRET=precompile_placeholder SECRET_KEY_BASE=precompile_placeholder bundle exec rails assets:precompile; \
# Cleanup temporary files
rm -fr /opt/mastodon/tmp;
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Prep final Mastodon Ruby layer
FROM ruby as mastodon
ENV DEBIAN_FRONTEND="noninteractive" \
PATH="${PATH}:/opt/ruby/bin:/opt/mastodon/bin"
ARG TARGETPLATFORM
# Ignoring these here since we don't want to pin any versions and the Debian image removes apt-get content after use
# hadolint ignore=DL3008,DL3009
RUN apt-get update && \
echo "Etc/UTC" > /etc/localtime && \
groupadd -g "${GID}" mastodon && \
useradd -l -u "$UID" -g "${GID}" -m -d /opt/mastodon mastodon && \
apt-get -y --no-install-recommends install whois \
wget \
procps \
libssl3 \
libpq5 \
imagemagick \
ffmpeg \
libjemalloc2 \
libicu72 \
libidn12 \
libyaml-0-2 \
file \
ca-certificates \
tzdata \
libreadline8 \
tini && \
ln -s /opt/mastodon /mastodon && \
corepack enable
# hadolint ignore=DL3008
RUN \
# Mount Apt cache and lib directories from Docker buildx caches
--mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \
--mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
# Mount Corepack and Yarn caches from Docker buildx caches
--mount=type=cache,id=corepack-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/corepack,sharing=locked \
--mount=type=cache,id=yarn-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/yarn,sharing=locked \
# Apt update install non-dev versions of necessary components
apt-get install -y --no-install-recommends \
libssl3 \
libpq5 \
libicu72 \
libidn12 \
libreadline8 \
libyaml-0-2 \
;
# Note: no, cleaning here since Debian does this automatically
# See the file /etc/apt/apt.conf.d/docker-clean within the Docker image's filesystem
# Copy Mastodon sources into final layer
COPY . /opt/mastodon/
COPY --chown=mastodon:mastodon . /opt/mastodon
COPY --chown=mastodon:mastodon --from=build /opt/mastodon /opt/mastodon
# Copy compiled assets to layer
COPY --from=precompiler /opt/mastodon/public/packs /opt/mastodon/public/packs
COPY --from=precompiler /opt/mastodon/public/assets /opt/mastodon/public/assets
# Copy bundler components to layer
COPY --from=bundler /usr/local/bundle/ /usr/local/bundle/
ENV RAILS_ENV="production" \
NODE_ENV="production" \
RAILS_SERVE_STATIC_FILES="true" \
BIND="0.0.0.0" \
MASTODON_VERSION_PRERELEASE="${MASTODON_VERSION_PRERELEASE}" \
MASTODON_VERSION_METADATA="${MASTODON_VERSION_METADATA}"
RUN \
# Precompile bootsnap code for faster Rails startup
bundle exec bootsnap precompile --gemfile app/ lib/;
# Set the run user
RUN \
# Pre-create and chown system volume to Mastodon user
mkdir -p /opt/mastodon/public/system; \
chown mastodon:mastodon /opt/mastodon/public/system;
# Set the running user for resulting container
USER mastodon
WORKDIR /opt/mastodon
# Precompile assets
RUN OTP_SECRET=precompile_placeholder SECRET_KEY_BASE=precompile_placeholder rails assets:precompile
# Set the work dir and the container entry point
# Expose default Puma ports
EXPOSE 3000
# Set container tini as default entry point
ENTRYPOINT ["/usr/bin/tini", "--"]
EXPOSE 3000 4000

View file

@ -131,7 +131,7 @@ GEM
attr_required (1.0.1)
awrence (1.2.1)
aws-eventstream (1.3.0)
aws-partitions (1.855.0)
aws-partitions (1.857.0)
aws-sdk-core (3.188.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.651.0)
@ -140,7 +140,7 @@ GEM
aws-sdk-kms (1.73.0)
aws-sdk-core (~> 3, >= 3.188.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.139.0)
aws-sdk-s3 (1.140.0)
aws-sdk-core (~> 3, >= 3.188.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.6)
@ -156,7 +156,7 @@ GEM
nokogiri (~> 1, >= 1.10.8)
base64 (0.2.0)
bcp47_spec (0.2.1)
bcrypt (3.1.19)
bcrypt (3.1.20)
better_errors (2.10.1)
erubi (>= 1.0.0)
rack (>= 0.9.0)
@ -272,7 +272,7 @@ GEM
et-orbi (1.2.7)
tzinfo
excon (0.104.0)
fabrication (2.30.0)
fabrication (2.31.0)
faker (3.2.2)
i18n (>= 1.8.11, < 2)
faraday (1.10.3)
@ -522,7 +522,7 @@ GEM
pastel (0.8.0)
tty-color (~> 0.5)
pg (1.5.4)
pghero (3.3.4)
pghero (3.4.0)
activerecord (>= 6)
posix-spawn (0.3.15)
premailer (1.21.0)
@ -755,7 +755,7 @@ GEM
attr_required (>= 0.0.5)
httpclient (>= 2.4)
sysexits (1.2.0)
temple (0.10.2)
temple (0.10.3)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
terrapin (0.6.0)

6
Vagrantfile vendored
View file

@ -10,7 +10,11 @@ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
sudo apt-add-repository 'deb https://dl.yarnpkg.com/debian/ stable main'
# Add repo for NodeJS
curl -sL https://deb.nodesource.com/setup_16.x | sudo bash -
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
NODE_MAJOR=20
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
sudo apt-get update
# Add firewall rule to redirect 80 to PORT and save
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port #{ENV["PORT"]}

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
class AccountsIndex < Chewy::Index
include DatetimeClampingConcern
settings index: index_preset(refresh_interval: '30s'), analysis: {
filter: {
english_stop: {
@ -60,7 +62,7 @@ class AccountsIndex < Chewy::Index
field(:following_count, type: 'long')
field(:followers_count, type: 'long')
field(:properties, type: 'keyword', value: ->(account) { account.searchable_properties })
field(:last_status_at, type: 'date', value: ->(account) { account.last_status_at || account.created_at })
field(:last_status_at, type: 'date', value: ->(account) { clamp_date(account.last_status_at || account.created_at) })
field(:display_name, type: 'text', analyzer: 'verbatim') { field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'verbatim' }
field(:username, type: 'text', analyzer: 'verbatim', value: ->(account) { [account.username, account.domain].compact.join('@') }) { field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'verbatim' }
field(:text, type: 'text', analyzer: 'verbatim', value: ->(account) { account.searchable_text }) { field :stemmed, type: 'text', analyzer: 'natural' }

View file

@ -0,0 +1,14 @@
# frozen_string_literal: true
module DatetimeClampingConcern
extend ActiveSupport::Concern
MIN_ISO8601_DATETIME = '0000-01-01T00:00:00Z'.to_datetime.freeze
MAX_ISO8601_DATETIME = '9999-12-31T23:59:59Z'.to_datetime.freeze
class_methods do
def clamp_date(datetime)
datetime.clamp(MIN_ISO8601_DATETIME, MAX_ISO8601_DATETIME)
end
end
end

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
class PublicStatusesIndex < Chewy::Index
include DatetimeClampingConcern
settings index: index_preset(refresh_interval: '30s', number_of_shards: 5), analysis: {
filter: {
english_stop: {
@ -62,6 +64,6 @@ class PublicStatusesIndex < Chewy::Index
field(:tags, type: 'text', analyzer: 'hashtag', value: ->(status) { status.tags.map(&:display_name) })
field(:language, type: 'keyword')
field(:properties, type: 'keyword', value: ->(status) { status.searchable_properties })
field(:created_at, type: 'date')
field(:created_at, type: 'date', value: ->(status) { clamp_date(status.created_at) })
end
end

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
class StatusesIndex < Chewy::Index
include DatetimeClampingConcern
settings index: index_preset(refresh_interval: '30s', number_of_shards: 5), analysis: {
filter: {
english_stop: {
@ -60,6 +62,6 @@ class StatusesIndex < Chewy::Index
field(:searchable_by, type: 'long', value: ->(status) { status.searchable_by })
field(:language, type: 'keyword')
field(:properties, type: 'keyword', value: ->(status) { status.searchable_properties })
field(:created_at, type: 'date')
field(:created_at, type: 'date', value: ->(status) { clamp_date(status.created_at) })
end
end

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
class TagsIndex < Chewy::Index
include DatetimeClampingConcern
settings index: index_preset(refresh_interval: '30s'), analysis: {
analyzer: {
content: {
@ -42,6 +44,6 @@ class TagsIndex < Chewy::Index
field(:name, type: 'text', analyzer: 'content', value: :display_name) { field(:edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content') }
field(:reviewed, type: 'boolean', value: ->(tag) { tag.reviewed? })
field(:usage, type: 'long', value: ->(tag, crutches) { tag.history.aggregate(crutches.time_period).accounts })
field(:last_status_at, type: 'date', value: ->(tag) { tag.last_status_at || tag.created_at })
field(:last_status_at, type: 'date', value: ->(tag) { clamp_date(tag.last_status_at || tag.created_at) })
end
end

View file

@ -18,8 +18,6 @@ class AccountsController < ApplicationController
respond_to do |format|
format.html do
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.hour) unless user_signed_in?
@rss_url = rss_url
end
format.rss do
@ -84,29 +82,21 @@ class AccountsController < ApplicationController
short_account_url(@account, format: 'rss')
end
end
helper_method :rss_url
def media_requested?
request.path.split('.').first.end_with?('/media') && !tag_requested?
path_without_format.end_with?('/media') && !tag_requested?
end
def replies_requested?
request.path.split('.').first.end_with?('/with_replies') && !tag_requested?
path_without_format.end_with?('/with_replies') && !tag_requested?
end
def tag_requested?
request.path.split('.').first.end_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
path_without_format.end_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
end
def cached_filtered_status_page
cache_collection_paginated_by_id(
filtered_statuses,
Status,
PAGE_SIZE,
params_slice(:max_id, :min_id, :since_id)
)
end
def params_slice(*keys)
params.slice(*keys).permit(*keys)
def path_without_format
request.path.split('.').first
end
end

View file

@ -16,7 +16,7 @@ module Admin
@moderation_notes = @account.targeted_moderation_notes.latest
@warnings = @account.strikes.custom.latest
render template: 'admin/accounts/show'
render 'admin/accounts/show'
end
end

View file

@ -6,7 +6,7 @@ module Admin
def index
authorize :audit_log, :index?
@auditable_accounts = Account.where(id: Admin::ActionLog.reorder(nil).select('distinct account_id')).select(:id, :username)
@auditable_accounts = Account.where(id: Admin::ActionLog.select('distinct account_id')).select(:id, :username)
end
private

View file

@ -24,7 +24,7 @@ module Admin
@relay.enable!
redirect_to admin_relays_path
else
render action: :new
render :new
end
end

View file

@ -26,7 +26,7 @@ module Admin
@form = Admin::StatusBatchAction.new
@statuses = @report.statuses.with_includes
render template: 'admin/reports/show'
render 'admin/reports/show'
end
end

View file

@ -43,7 +43,7 @@ module ChallengableConcern
def render_challenge
@body_classes = 'lighter'
render template: 'auth/challenges/new', layout: 'auth'
render 'auth/challenges/new', layout: 'auth'
end
def challenge_passed?

View file

@ -11,7 +11,7 @@ class Disputes::AppealsController < Disputes::BaseController
redirect_to disputes_strike_path(@strike), notice: I18n.t('disputes.strikes.appealed_msg')
rescue ActiveRecord::RecordInvalid => e
@appeal = e.record
render template: 'disputes/strikes/show'
render 'disputes/strikes/show'
end
private

View file

@ -25,7 +25,7 @@ class FiltersController < ApplicationController
if @filter.save
redirect_to filters_path
else
render action: :new
render :new
end
end
@ -33,7 +33,7 @@ class FiltersController < ApplicationController
if @filter.update(resource_params)
redirect_to filters_path
else
render action: :edit
render :edit
end
end

View file

@ -14,7 +14,7 @@ class StatusesCleanupController < ApplicationController
if @policy.update(resource_params)
redirect_to statuses_cleanup_path, notice: I18n.t('generic.changes_saved_msg')
else
render action: :show
render :show
end
rescue ActionController::ParameterMissing
# Do nothing

View file

@ -0,0 +1,12 @@
# frozen_string_literal: true
module Admin::AccountActionsHelper
def account_action_type_label(type)
safe_join(
[
I18n.t("simple_form.labels.admin_account_action.types.#{type}"),
content_tag(:span, I18n.t("simple_form.hints.admin_account_action.types.#{type}"), class: 'hint'),
]
)
end
end

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
module Admin::AccountsHelper
def admin_accounts_moderation_options
[
[t('admin.accounts.moderation.active'), 'active'],
[t('admin.accounts.moderation.silenced'), 'silenced'],
[t('admin.accounts.moderation.disabled'), 'disabled'],
[t('admin.accounts.moderation.suspended'), 'suspended'],
[safe_join([t('admin.accounts.moderation.pending'), "(#{pending_user_count_label})"], ' '), 'pending'],
]
end
private
def pending_user_count_label
number_with_delimiter User.pending.count
end
end

View file

@ -0,0 +1,12 @@
# frozen_string_literal: true
module Admin::IpBlocksHelper
def ip_blocks_severity_label(severity)
safe_join(
[
I18n.t("simple_form.labels.ip_block.severities.#{severity}"),
content_tag(:span, I18n.t("simple_form.hints.ip_block.severities.#{severity}"), class: 'hint'),
]
)
end
end

View file

@ -0,0 +1,24 @@
# frozen_string_literal: true
module Admin
module RolesHelper
def privilege_label(privilege)
safe_join(
[
t("admin.roles.privileges.#{privilege}"),
content_tag(:span, t("admin.roles.privileges.#{privilege}_description"), class: 'hint'),
]
)
end
def disable_permissions?(permissions)
permissions.filter { |privilege| role_flag_value(privilege).zero? }
end
private
def role_flag_value(privilege)
UserRole::FLAGS[privilege] & current_user.role.computed_permissions
end
end
end

View file

@ -0,0 +1,15 @@
# frozen_string_literal: true
module Admin::Settings::DiscoveryHelper
def discovery_warning_hint_text
authorized_fetch_overridden? ? t('admin.settings.security.authorized_fetch_overridden_hint') : nil
end
def discovery_hint_text
t('admin.settings.security.authorized_fetch_hint')
end
def discovery_recommended_value
authorized_fetch_overridden? ? :overridden : nil
end
end

View file

@ -0,0 +1,12 @@
# frozen_string_literal: true
module FiltersHelper
def filter_action_label(action)
safe_join(
[
t("simple_form.labels.filters.actions.#{action}"),
content_tag(:span, t("simple_form.hints.filters.actions.#{action}"), class: 'hint'),
]
)
end
end

View file

@ -6,7 +6,7 @@ import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import api from 'mastodon/api';
import Hashtag from 'mastodon/components/hashtag';
import { Hashtag } from 'mastodon/components/hashtag';
export default class Trends extends PureComponent {

View file

@ -1,120 +0,0 @@
// @ts-check
import PropTypes from 'prop-types';
import { Component } from 'react';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { Link } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { Sparklines, SparklinesCurve } from 'react-sparklines';
import { ShortNumber } from 'mastodon/components/short_number';
import { Skeleton } from 'mastodon/components/skeleton';
class SilentErrorBoundary extends Component {
static propTypes = {
children: PropTypes.node,
};
state = {
error: false,
};
componentDidCatch() {
this.setState({ error: true });
}
render() {
if (this.state.error) {
return null;
}
return this.props.children;
}
}
/**
* Used to render counter of how much people are talking about hashtag
* @type {(displayNumber: JSX.Element, pluralReady: number) => JSX.Element}
*/
export const accountsCountRenderer = (displayNumber, pluralReady) => (
<FormattedMessage
id='trends.counter_by_accounts'
defaultMessage='{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}'
values={{
count: pluralReady,
counter: <strong>{displayNumber}</strong>,
days: 2,
}}
/>
);
// @ts-expect-error
export const ImmutableHashtag = ({ hashtag }) => (
<Hashtag
name={hashtag.get('name')}
to={`/tags/${hashtag.get('name')}`}
people={hashtag.getIn(['history', 0, 'accounts']) * 1 + hashtag.getIn(['history', 1, 'accounts']) * 1}
// @ts-expect-error
history={hashtag.get('history').reverse().map((day) => day.get('uses')).toArray()}
/>
);
ImmutableHashtag.propTypes = {
hashtag: ImmutablePropTypes.map.isRequired,
};
// @ts-expect-error
const Hashtag = ({ name, to, people, uses, history, className, description, withGraph }) => (
<div className={classNames('trends__item', className)}>
<div className='trends__item__name'>
<Link to={to}>
{name ? <>#<span>{name}</span></> : <Skeleton width={50} />}
</Link>
{description ? (
<span>{description}</span>
) : (
typeof people !== 'undefined' ? <ShortNumber value={people} renderer={accountsCountRenderer} /> : <Skeleton width={100} />
)}
</div>
{typeof uses !== 'undefined' && (
<div className='trends__item__current'>
<ShortNumber value={uses} />
</div>
)}
{withGraph && (
<div className='trends__item__sparkline'>
<SilentErrorBoundary>
<Sparklines width={50} height={28} data={history ? history : Array.from(Array(7)).map(() => 0)}>
<SparklinesCurve style={{ fill: 'none' }} />
</Sparklines>
</SilentErrorBoundary>
</div>
)}
</div>
);
Hashtag.propTypes = {
name: PropTypes.string,
to: PropTypes.string,
people: PropTypes.number,
description: PropTypes.node,
uses: PropTypes.number,
history: PropTypes.arrayOf(PropTypes.number),
className: PropTypes.string,
withGraph: PropTypes.bool,
};
Hashtag.defaultProps = {
withGraph: true,
};
export default Hashtag;

View file

@ -0,0 +1,145 @@
import type { JSX } from 'react';
import { Component } from 'react';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { Link } from 'react-router-dom';
import type Immutable from 'immutable';
import { Sparklines, SparklinesCurve } from 'react-sparklines';
import { ShortNumber } from 'mastodon/components/short_number';
import { Skeleton } from 'mastodon/components/skeleton';
interface SilentErrorBoundaryProps {
children: React.ReactNode;
}
class SilentErrorBoundary extends Component<SilentErrorBoundaryProps> {
state = {
error: false,
};
componentDidCatch() {
this.setState({ error: true });
}
render() {
if (this.state.error) {
return null;
}
return this.props.children;
}
}
/**
* Used to render counter of how much people are talking about hashtag
* @param displayNumber Counter number to display
* @param pluralReady Whether the count is plural
* @returns Formatted counter of how much people are talking about hashtag
*/
export const accountsCountRenderer = (
displayNumber: JSX.Element,
pluralReady: number,
) => (
<FormattedMessage
id='trends.counter_by_accounts'
defaultMessage='{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}'
values={{
count: pluralReady,
counter: <strong>{displayNumber}</strong>,
days: 2,
}}
/>
);
interface ImmutableHashtagProps {
hashtag: Immutable.Map<string, unknown>;
}
export const ImmutableHashtag = ({ hashtag }: ImmutableHashtagProps) => (
<Hashtag
name={hashtag.get('name') as string}
to={`/tags/${hashtag.get('name') as string}`}
people={
(hashtag.getIn(['history', 0, 'accounts']) as number) * 1 +
(hashtag.getIn(['history', 1, 'accounts']) as number) * 1
}
history={(
hashtag.get('history') as Immutable.Collection.Indexed<
Immutable.Map<string, number>
>
)
.reverse()
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
.map((day) => day.get('uses')!)
.toArray()}
/>
);
export interface HashtagProps {
className?: string;
description?: React.ReactNode;
history?: number[];
name: string;
people: number;
to: string;
uses?: number;
withGraph?: boolean;
}
export const Hashtag: React.FC<HashtagProps> = ({
name,
to,
people,
uses,
history,
className,
description,
withGraph = true,
}) => (
<div className={classNames('trends__item', className)}>
<div className='trends__item__name'>
<Link to={to}>
{name ? (
<>
#<span>{name}</span>
</>
) : (
<Skeleton width={50} />
)}
</Link>
{description ? (
<span>{description}</span>
) : typeof people !== 'undefined' ? (
<ShortNumber value={people} renderer={accountsCountRenderer} />
) : (
<Skeleton width={100} />
)}
</div>
{typeof uses !== 'undefined' && (
<div className='trends__item__current'>
<ShortNumber value={uses} />
</div>
)}
{withGraph && (
<div className='trends__item__sparkline'>
<SilentErrorBoundary>
<Sparklines
width={50}
height={28}
data={history ? history : Array.from(Array(7)).map(() => 0)}
>
<SparklinesCurve style={{ fill: 'none' }} />
</Sparklines>
</SilentErrorBoundary>
</div>
)}
</div>
);

View file

@ -5,7 +5,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Hashtag from 'mastodon/components/hashtag';
import { Hashtag } from 'mastodon/components/hashtag';
const messages = defineMessages({
lastStatusAt: { id: 'account.featured_tags.last_status_at', defaultMessage: 'Last post on {date}' },

View file

@ -13,7 +13,7 @@ import { debounce } from 'lodash';
import { expandFollowedHashtags, fetchFollowedHashtags } from 'mastodon/actions/tags';
import ColumnHeader from 'mastodon/components/column_header';
import Hashtag from 'mastodon/components/hashtag';
import { Hashtag } from 'mastodon/components/hashtag';
import ScrollableList from 'mastodon/components/scrollable_list';
import Column from 'mastodon/features/ui/components/column';

View file

@ -250,6 +250,9 @@
"notifications.column_settings.unread_notifications.highlight": "Lig ongelese kennisgewings uit",
"notifications.filter.boosts": "Aangestuurde plasings",
"notifications.group": "{count} kennisgewings",
"notifications.permission_denied_alert": "Lessenaarkennisgewings kan nie geaktiveer word nie omdat 'n webblaaier toegewing voorheen geweier was",
"notifications_permission_banner.enable": "Aktiveer lessenaarkennissgewings",
"notifications_permission_banner.how_to_control": "Om kennisgewings te ontvang wanner Mastodon nie oop is nie, aktiveer lessenaarkennisgewings. Jy kan beheer watter spesifieke tipe interaksies lessenaarkennisgewings genereer deur die {icon} knoppie hier bo sodra hulle geaktiveer is.",
"onboarding.actions.go_to_explore": "See what's trending",
"onboarding.actions.go_to_home": "Go to your home feed",
"onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Супадзенне паведамленняў {x}",
"search.search_or_paste": "Пошук",
"search_popout.full_text_search_disabled_message": "Недаступна на {domain}.",
"search_popout.full_text_search_logged_out_message": "Даступна толькі пры ўваходзе ў сістэму.",
"search_popout.language_code": "ISO код мовы",
"search_popout.options": "Параметры пошуку",
"search_popout.quick_actions": "Хуткія дзеянні",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Съвпадение на публикации {x}",
"search.search_or_paste": "Търсене или поставяне на URL адрес",
"search_popout.full_text_search_disabled_message": "Не е достъпно на {domain}.",
"search_popout.full_text_search_logged_out_message": "Достъпно само при влизане в системата.",
"search_popout.language_code": "Код на езика по ISO",
"search_popout.options": "Възможности при търсене",
"search_popout.quick_actions": "Бързи действия",

View file

@ -605,6 +605,7 @@
"search.quick_action.status_search": "Tuts coincidint amb {x}",
"search.search_or_paste": "Cerca o escriu l'URL",
"search_popout.full_text_search_disabled_message": "No disponible a {domain}.",
"search_popout.full_text_search_logged_out_message": "Només disponible en iniciar la sessió.",
"search_popout.language_code": "Codi de llengua ISO",
"search_popout.options": "Opcions de cerca",
"search_popout.quick_actions": "Accions ràpides",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Indlæg matchende {x}",
"search.search_or_paste": "Søg efter eller angiv URL",
"search_popout.full_text_search_disabled_message": "Utilgængelig på {domain}.",
"search_popout.full_text_search_logged_out_message": "Kun tilgængelig, når logget ind.",
"search_popout.language_code": "ISO-sprogkode",
"search_popout.options": "Søgevalg",
"search_popout.quick_actions": "Hurtige handlinger",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Beiträge passend zu {x}",
"search.search_or_paste": "Suchen oder URL einfügen",
"search_popout.full_text_search_disabled_message": "Auf {domain} nicht verfügbar.",
"search_popout.full_text_search_logged_out_message": "Nur verfügbar, wenn angemeldet.",
"search_popout.language_code": "ISO-Sprachcode",
"search_popout.options": "Suchoptionen",
"search_popout.quick_actions": "Schnellaktionen",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Mensajes que coinciden con {x}",
"search.search_or_paste": "Buscar o pegar dirección web",
"search_popout.full_text_search_disabled_message": "No disponible en {domain}.",
"search_popout.full_text_search_logged_out_message": "Solo disponible al iniciar sesión.",
"search_popout.language_code": "Código ISO de idioma",
"search_popout.options": "Opciones de búsqueda",
"search_popout.quick_actions": "Acciones rápidas",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Publicaciones que coinciden con {x}",
"search.search_or_paste": "Buscar o pegar URL",
"search_popout.full_text_search_disabled_message": "No disponible en {domain}.",
"search_popout.full_text_search_logged_out_message": "Solo disponible si inicias sesión.",
"search_popout.language_code": "Código de idioma ISO",
"search_popout.options": "Opciones de búsqueda",
"search_popout.quick_actions": "Acciones rápidas",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Publicaciones que coinciden con {x}",
"search.search_or_paste": "Buscar o pegar URL",
"search_popout.full_text_search_disabled_message": "No disponible en {domain}.",
"search_popout.full_text_search_logged_out_message": "Solo disponible si inicias sesión.",
"search_popout.language_code": "Código de idioma ISO",
"search_popout.options": "Opciones de búsqueda",
"search_popout.quick_actions": "Acciones rápidas",

View file

@ -21,6 +21,7 @@
"account.blocked": "Blokeeritud",
"account.browse_more_on_origin_server": "Vaata rohkem algsel profiilil",
"account.cancel_follow_request": "Võta jälgimistaotlus tagasi",
"account.copy": "Kopeeri link profiili",
"account.direct": "Maini privaatselt @{name}",
"account.disable_notifications": "Peata teavitused @{name} postitustest",
"account.domain_blocked": "Domeen peidetud",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Märgi loetuks",
"conversation.open": "Vaata vestlust",
"conversation.with": "Koos {names}",
"copy_icon_button.copied": "Kopeeritud vahemällu",
"copypaste.copied": "Kopeeritud",
"copypaste.copy_to_clipboard": "Kopeeri vahemällu",
"directory.federated": "Tuntud födiversumist",
@ -390,6 +392,7 @@
"lists.search": "Otsi enda jälgitavate inimeste hulgast",
"lists.subheading": "Sinu nimekirjad",
"load_pending": "{count, plural, one {# uus kirje} other {# uut kirjet}}",
"loading_indicator.label": "Laadimine…",
"media_gallery.toggle_visible": "{number, plural, one {Varja pilt} other {Varja pildid}}",
"moved_to_account_banner.text": "Kontot {disabledAccount} ei ole praegu võimalik kasutada, sest kolisid kontole {movedToAccount}.",
"mute_modal.duration": "Kestus",
@ -478,6 +481,17 @@
"onboarding.follows.empty": "Kahjuks ei saa hetkel tulemusi näidata. Proovi kasutada otsingut või lehitse uurimise lehte, et leida inimesi, keda jälgida, või proovi hiljem uuesti.",
"onboarding.follows.lead": "Haldad ise oma koduvoogu. Mida rohkemaid inimesi jälgid, seda aktiivsem ja huvitavam see on. Need profiilid võiksid olla head alustamiskohad — saad nende jälgimise alati lõpetada!",
"onboarding.follows.title": "Populaarne Mastodonis",
"onboarding.profile.discoverable": "Muuda mu profiil avastatavaks",
"onboarding.profile.discoverable_hint": "Kui nõustud enda avastamisega Mastodonis, võivad sinu postitused ilmuda otsingutulemustes ja trendides ning sinu profiili võidakse soovitada sinuga sarnaste huvidega inimestele.",
"onboarding.profile.display_name": "Näidatav nimi",
"onboarding.profile.display_name_hint": "Su täisnimi või naljanimi…",
"onboarding.profile.lead": "Saad selle alati hiljem seadetes lõpuni viia, kus on saadaval veel rohkem kohandamisvalikuid.",
"onboarding.profile.note": "Elulugu",
"onboarding.profile.note_hint": "Saad @mainida teisi kasutajaid või #sildistada…",
"onboarding.profile.save_and_continue": "Salvesta ja jätka",
"onboarding.profile.title": "Profiili seadistamine",
"onboarding.profile.upload_avatar": "Laadi üles profiilipilt",
"onboarding.profile.upload_header": "Laadi üles profiili päis",
"onboarding.share.lead": "Anna inimestele teada, kuidas sind Mastodonist üles leida!",
"onboarding.share.message": "Ma olen #Mastodon võrgustikus {username}! tule ja jälgi mind aadressil {url}",
"onboarding.share.next_steps": "Võimalikud järgmised sammud:",
@ -521,6 +535,7 @@
"privacy.unlisted.short": "Määramata",
"privacy_policy.last_updated": "Viimati uuendatud {date}",
"privacy_policy.title": "Isikuandmete kaitse",
"recommended": "Soovitatud",
"refresh": "Värskenda",
"regeneration_indicator.label": "Laeb…",
"regeneration_indicator.sublabel": "Su koduvoog on ettevalmistamisel!",

View file

@ -16,17 +16,17 @@
"account.badges.bot": "Bot-a",
"account.badges.group": "Taldea",
"account.block": "Blokeatu @{name}",
"account.block_domain": "Ezkutatu {domain} domeinuko guztia",
"account.block_domain": "Blokeatu {domain} domeinua",
"account.block_short": "Blokeatu",
"account.blocked": "Blokeatuta",
"account.browse_more_on_origin_server": "Arakatu gehiago jatorrizko profilean",
"account.cancel_follow_request": "Baztertu jarraitzeko eskaera",
"account.copy": "Kopiatu profilerako esteka",
"account.direct": "Aipatu pribatuki @{name}",
"account.disable_notifications": "Utzi jakinarazteari @{name} erabiltzailearen bidalketetan",
"account.disable_notifications": "Utzi jakinarazteari @{name} erabiltzaileak argitaratzean",
"account.domain_blocked": "Ezkutatutako domeinua",
"account.edit_profile": "Aldatu profila",
"account.enable_notifications": "Jakinarazi @{name} erabiltzaileak bidalketak egitean",
"account.enable_notifications": "Jakinarazi @{name} erabiltzaileak argitaratzean",
"account.endorse": "Nabarmendu profilean",
"account.featured_tags.last_status_at": "Azken bidalketa {date} datan",
"account.featured_tags.last_status_never": "Bidalketarik ez",
@ -40,7 +40,7 @@
"account.follows.empty": "Erabiltzaile honek ez du inor jarraitzen oraindik.",
"account.follows_you": "Jarraitzen dizu",
"account.go_to_profile": "Joan profilera",
"account.hide_reblogs": "Ezkutatu @{name}(r)en bultzadak",
"account.hide_reblogs": "Ezkutatu @{name} erabiltzailearen bultzadak",
"account.in_memoriam": "Oroimenezkoa.",
"account.joined_short": "Elkartuta",
"account.languages": "Aldatu harpidetutako hizkuntzak",
@ -60,8 +60,8 @@
"account.report": "Salatu @{name}",
"account.requested": "Onarpenaren zain. Egin klik jarraipen-eskaera ezeztatzeko",
"account.requested_follow": "{name}-(e)k zu jarraitzeko eskaera egin du",
"account.share": "@{name}(e)ren profila elkarbanatu",
"account.show_reblogs": "Erakutsi @{name}(r)en bultzadak",
"account.share": "Partekatu @{name} erabiltzailearen profila",
"account.show_reblogs": "Erakutsi @{name} erabiltzailearen bultzadak",
"account.statuses_counter": "{count, plural, one {Bidalketa {counter}} other {{counter} bidalketa}}",
"account.unblock": "Desblokeatu @{name}",
"account.unblock_domain": "Berriz erakutsi {domain}",
@ -606,6 +606,7 @@
"search.quick_action.status_search": "{x}-(r)ekin bat datozen argitalpenak",
"search.search_or_paste": "Bilatu edo itsatsi URLa",
"search_popout.full_text_search_disabled_message": "{domain}-en ez dago eskuragarri.",
"search_popout.full_text_search_logged_out_message": "Soilik erabilgarri saioa hastean.",
"search_popout.language_code": "ISO hizkuntza-kodea",
"search_popout.options": "Bilaketaren aukerak",
"search_popout.quick_actions": "Ekintza azkarrak",

View file

@ -482,7 +482,7 @@
"onboarding.follows.lead": "Kokoat oman kotisyötteesi itse. Mitä enemmän ihmisiä seuraat, sitä aktiivisempi ja kiinnostavampi syöte on. Nämä profiilit voivat olla alkuun hyvä lähtökohta — voit aina lopettaa niiden seuraamisen myöhemmin!",
"onboarding.follows.title": "Mukauta kotisyötettäsi",
"onboarding.profile.discoverable": "Aseta profiilini löydettäväksi",
"onboarding.profile.discoverable_hint": "Kun olet määrittänyt itsesi löydettäväksi Mastodonista, voivat julkaisusi näkyä hakutuloksissa ja suosituissa kohteissa, ja profiiliasi voidaan ehdottaa käyttäjille, jotka ovat kiinnostuneet samoista aiheista kuin sinä.",
"onboarding.profile.discoverable_hint": "Kun olet määrittänyt itsesi löydettäväksi Mastodonista, julkaisusi voivat näkyä hakutuloksissa ja suosituissa kohteissa ja profiiliasi voidaan ehdottaa käyttäjille, jotka ovat kiinnostuneet samoista aiheista kuin sinä.",
"onboarding.profile.display_name": "Näyttönimi",
"onboarding.profile.display_name_hint": "Koko nimesi tai lempinimesi…",
"onboarding.profile.lead": "Voit viimeistellä tämän milloin tahansa asetuksista, jotka tarjoavat vielä enemmän mukautusvalintoja.",
@ -606,6 +606,7 @@
"search.quick_action.status_search": "Julkaisut haulla {x}",
"search.search_or_paste": "Hae tai liitä URL-osoite",
"search_popout.full_text_search_disabled_message": "Ei saatavilla palvelimella {domain}.",
"search_popout.full_text_search_logged_out_message": "Saatavilla vain sisäänkirjautuneena.",
"search_popout.language_code": "ISO-kielikoodi",
"search_popout.options": "Hakuvalinnat",
"search_popout.quick_actions": "Pikatoiminnot",

View file

@ -596,6 +596,7 @@
"search.quick_action.status_search": "Postar, ið samsvara {x}",
"search.search_or_paste": "Leita ella set URL inn",
"search_popout.full_text_search_disabled_message": "Ikki tøkt á {domain}.",
"search_popout.full_text_search_logged_out_message": "Einans tøkt um innritað er.",
"search_popout.language_code": "ISO málkoda",
"search_popout.options": "Leitimøguleikar",
"search_popout.quick_actions": "Skjótar atgerðir",

View file

@ -21,6 +21,7 @@
"account.blocked": "Bloqué·e",
"account.browse_more_on_origin_server": "Parcourir davantage sur le profil original",
"account.cancel_follow_request": "Retirer cette demande d'abonnement",
"account.copy": "Copier le lien vers le profil",
"account.direct": "Mention privée @{name}",
"account.disable_notifications": "Ne plus me notifier quand @{name} publie",
"account.domain_blocked": "Domaine bloqué",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Marquer comme lu",
"conversation.open": "Afficher cette conversation",
"conversation.with": "Avec {names}",
"copy_icon_button.copied": "Copié dans le presse-papier",
"copypaste.copied": "Copié",
"copypaste.copy_to_clipboard": "Copier dans le presse-papiers",
"directory.federated": "D'un fediverse connu",
@ -479,12 +481,17 @@
"onboarding.follows.empty": "Malheureusement, aucun résultat ne peut être affiché pour le moment. Vous pouvez essayer de rechercher ou de parcourir la page \"Explorer\" pour trouver des personnes à suivre, ou réessayer plus tard.",
"onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
"onboarding.follows.title": "Popular on Mastodon",
"onboarding.profile.discoverable": "Rendre mon profil découvrable",
"onboarding.profile.discoverable_hint": "Lorsque vous acceptez d'être découvert sur Mastodon, vos messages peuvent apparaître dans les résultats de recherche et les tendances, et votre profil peut être suggéré à des personnes ayant des intérêts similaires aux vôtres.",
"onboarding.profile.display_name": "Nom affiché",
"onboarding.profile.display_name_hint": "Votre nom complet ou votre nom rigolo…",
"onboarding.profile.lead": "Vous pouvez toujours compléter cela plus tard dans les paramètres. Vous y trouverez encore plus d'options de personnalisation.",
"onboarding.profile.note": "Biographie",
"onboarding.profile.note_hint": "Vous pouvez @mentionner d'autres personnes ou #hashtags…",
"onboarding.profile.save_and_continue": "Enregistrer et continuer",
"onboarding.profile.title": "Configuration du profil",
"onboarding.profile.upload_avatar": "Importer une photo de profil",
"onboarding.profile.upload_header": "Envoyer une image de profil",
"onboarding.profile.upload_header": "Importer un entête de profil",
"onboarding.share.lead": "Faites savoir aux gens comment vous trouver sur Mastodon!",
"onboarding.share.message": "Je suis {username} sur #Mastodon! Suivez-moi sur {url}",
"onboarding.share.next_steps": "Étapes suivantes possibles:",
@ -528,6 +535,7 @@
"privacy.unlisted.short": "Non listé",
"privacy_policy.last_updated": "Dernière mise à jour {date}",
"privacy_policy.title": "Politique de confidentialité",
"recommended": "Recommandé",
"refresh": "Actualiser",
"regeneration_indicator.label": "Chargement…",
"regeneration_indicator.sublabel": "Votre fil d'accueil est en cours de préparation!",
@ -598,6 +606,7 @@
"search.quick_action.status_search": "Publications correspondant à {x}",
"search.search_or_paste": "Rechercher ou saisir un URL",
"search_popout.full_text_search_disabled_message": "Non disponible sur {domain}.",
"search_popout.full_text_search_logged_out_message": "Disponible uniquement lorsque vous êtes connecté.",
"search_popout.language_code": "code de langue ISO",
"search_popout.options": "Options de recherche",
"search_popout.quick_actions": "Actions rapides",

View file

@ -21,6 +21,7 @@
"account.blocked": "Bloqué·e",
"account.browse_more_on_origin_server": "Parcourir davantage sur le profil original",
"account.cancel_follow_request": "Annuler le suivi",
"account.copy": "Copier le lien vers le profil",
"account.direct": "Mention privée @{name}",
"account.disable_notifications": "Ne plus me notifier quand @{name} publie quelque chose",
"account.domain_blocked": "Domaine bloqué",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Marquer comme lu",
"conversation.open": "Afficher la conversation",
"conversation.with": "Avec {names}",
"copy_icon_button.copied": "Copié dans le presse-papier",
"copypaste.copied": "Copié",
"copypaste.copy_to_clipboard": "Copier dans le presse-papiers",
"directory.federated": "Du fédiverse connu",
@ -479,12 +481,17 @@
"onboarding.follows.empty": "Malheureusement, aucun résultat ne peut être affiché pour le moment. Vous pouvez essayer d'utiliser la recherche ou parcourir la page de découverte pour trouver des personnes à suivre, ou réessayez plus tard.",
"onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!",
"onboarding.follows.title": "Personnaliser votre flux principal",
"onboarding.profile.discoverable": "Rendre mon profil découvrable",
"onboarding.profile.discoverable_hint": "Lorsque vous acceptez d'être découvert sur Mastodon, vos messages peuvent apparaître dans les résultats de recherche et les tendances, et votre profil peut être suggéré à des personnes ayant des intérêts similaires aux vôtres.",
"onboarding.profile.display_name": "Nom affiché",
"onboarding.profile.display_name_hint": "Votre nom complet ou votre nom rigolo…",
"onboarding.profile.lead": "Vous pouvez toujours compléter cela plus tard dans les paramètres. Vous y trouverez encore plus d'options de personnalisation.",
"onboarding.profile.note": "Biographie",
"onboarding.profile.note_hint": "Vous pouvez @mentionner d'autres personnes ou #hashtags…",
"onboarding.profile.save_and_continue": "Enregistrer et continuer",
"onboarding.profile.title": "Configuration du profil",
"onboarding.profile.upload_avatar": "Importer une photo de profil",
"onboarding.profile.upload_header": "Envoyer une image de profil",
"onboarding.profile.upload_header": "Importer un entête de profil",
"onboarding.share.lead": "Faites savoir aux gens comment ils peuvent vous trouver sur Mastodon!",
"onboarding.share.message": "Je suis {username} sur #Mastodon! Suivez-moi sur {url}",
"onboarding.share.next_steps": "Étapes suivantes possibles :",
@ -528,6 +535,7 @@
"privacy.unlisted.short": "Non listé",
"privacy_policy.last_updated": "Dernière mise à jour {date}",
"privacy_policy.title": "Politique de confidentialité",
"recommended": "Recommandé",
"refresh": "Actualiser",
"regeneration_indicator.label": "Chargement…",
"regeneration_indicator.sublabel": "Votre fil principal est en cours de préparation!",
@ -598,6 +606,7 @@
"search.quick_action.status_search": "Publications correspondant à {x}",
"search.search_or_paste": "Rechercher ou saisir une URL",
"search_popout.full_text_search_disabled_message": "Non disponible sur {domain}.",
"search_popout.full_text_search_logged_out_message": "Disponible uniquement lorsque vous êtes connecté.",
"search_popout.language_code": "code de langue ISO",
"search_popout.options": "Options de recherche",
"search_popout.quick_actions": "Actions rapides",

View file

@ -21,6 +21,7 @@
"account.blocked": "Blokkearre",
"account.browse_more_on_origin_server": "Mear op it orizjinele profyl besjen",
"account.cancel_follow_request": "Folchfersyk annulearje",
"account.copy": "Keppeling nei profyl kopiearje",
"account.direct": "Privee fermelde @{name}",
"account.disable_notifications": "Jou gjin melding mear wannear @{name} in berjocht pleatst",
"account.domain_blocked": "Domein blokkearre",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "As lêzen markearje",
"conversation.open": "Petear toane",
"conversation.with": "Mei {names}",
"copy_icon_button.copied": "Nei klamboerd kopiearre",
"copypaste.copied": "Kopiearre",
"copypaste.copy_to_clipboard": "Nei klamboerd kopiearje",
"directory.federated": "Fediverse (wat bekend is)",
@ -390,6 +392,7 @@
"lists.search": "Sykje nei minsken dyt jo folgje",
"lists.subheading": "Jo listen",
"load_pending": "{count, plural, one {# nij item} other {# nije items}}",
"loading_indicator.label": "Lade…",
"media_gallery.toggle_visible": "{number, plural, one {ôfbylding ferstopje} other {ôfbyldingen ferstopje}}",
"moved_to_account_banner.text": "Omdat jo nei {movedToAccount} ferhuze binne is jo account {disabledAccount} op dit stuit útskeakele.",
"mute_modal.duration": "Doer",
@ -478,6 +481,17 @@
"onboarding.follows.empty": "Spitigernôch kinne op dit stuit gjin resultaten toand wurde. Jo kinne probearje te sykjen of te blêdzjen troch de ferkenningsside om minsken te finen dyt jo folgje kinne, of probearje it letter opnij.",
"onboarding.follows.lead": "Jo beheare jo eigen startside. Hoe mear minsken jo folgje, hoe aktiver en ynteressanter it wêze sil. Dizze profilen kinne in goed startpunt wêze, jo kinne se letter altyd ûntfolgje!",
"onboarding.follows.title": "Populêr op Mastodon",
"onboarding.profile.discoverable": "Meitsje myn profyl te finen",
"onboarding.profile.discoverable_hint": "Wanneart jo akkoard gean mei it te finen wêzen op Mastodon, ferskine jo berjochten yn sykresultaten en kinne se trending wurde, en jo profyl kin oan oare minsken oanrekommandearre wurde wanneart se fergelykbere ynteressen hawwe.",
"onboarding.profile.display_name": "Werjeftenamme",
"onboarding.profile.display_name_hint": "Jo folsleine namme of in aardige bynamme…",
"onboarding.profile.lead": "Jo kinne dit letter altyd oanfolje yn de ynstellingen, wêrt noch mear oanpassingsopsjes beskikber binne.",
"onboarding.profile.note": "Biografy",
"onboarding.profile.note_hint": "Jo kinne oare minsken @fermelde of #hashtags brûke…",
"onboarding.profile.save_and_continue": "Bewarje en trochgean",
"onboarding.profile.title": "Profyl ynstelle",
"onboarding.profile.upload_avatar": "Profylfoto oplade",
"onboarding.profile.upload_header": "Omslachfoto foar profyl oplade",
"onboarding.share.lead": "Lit minsken witte hoet se jo fine kinne op Mastodon!",
"onboarding.share.message": "Ik bin {username} op #Mastodon! Folgje my op {url}",
"onboarding.share.next_steps": "Mooglike folgjende stappen:",
@ -521,6 +535,7 @@
"privacy.unlisted.short": "Minder iepenbier",
"privacy_policy.last_updated": "Lêst bywurke op {date}",
"privacy_policy.title": "Privacybelied",
"recommended": "Oanrekommandearre",
"refresh": "Ferfarskje",
"regeneration_indicator.label": "Lade…",
"regeneration_indicator.sublabel": "Jo starttiidline wurdt oanmakke!",
@ -591,6 +606,7 @@
"search.quick_action.status_search": "Berjochten dyt oerienkomme mei {x}",
"search.search_or_paste": "Sykje of fier URL yn",
"search_popout.full_text_search_disabled_message": "Net beskikber op {domain}.",
"search_popout.full_text_search_logged_out_message": "Allinnich beskikber as jo oanmeld binne.",
"search_popout.language_code": "ISO-taalkoade",
"search_popout.options": "Sykopsjes",
"search_popout.quick_actions": "Flugge aksjes",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Publicacións coincidentes {x}",
"search.search_or_paste": "Busca ou insire URL",
"search_popout.full_text_search_disabled_message": "Non está dispoñible en {domain}.",
"search_popout.full_text_search_logged_out_message": "Só dispoñible ao iniciar sesión.",
"search_popout.language_code": "Código ISO do idioma",
"search_popout.options": "Opcións de busca",
"search_popout.quick_actions": "Accións rápidas",
@ -686,7 +687,7 @@
"status.translated_from_with": "Traducido do {lang} usando {provider}",
"status.uncached_media_warning": "A vista previa non está dispoñíble",
"status.unmute_conversation": "Deixar de silenciar conversa",
"status.unpin": "Desafixar do perfil",
"status.unpin": "Non fixar no perfil",
"subscribed_languages.lead": "Ao facer cambios só as publicacións nos idiomas seleccionados aparecerán nas túas cronoloxías. Non elixas ningún para poder ver publicacións en tódolos idiomas.",
"subscribed_languages.save": "Gardar cambios",
"subscribed_languages.target": "Cambiar a subscrición a idiomas para {target}",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "הודעות המכילות {x}",
"search.search_or_paste": "חפש או הזן קישור",
"search_popout.full_text_search_disabled_message": "בלתי זמין על {domain}.",
"search_popout.full_text_search_logged_out_message": "זמין רק לאחר כניסה לאתר.",
"search_popout.language_code": "קוד ISO לשפה",
"search_popout.options": "אפשרויות חיפוש",
"search_popout.quick_actions": "פעולות זריזות",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Bejegyzések a következő keresésre: {x}",
"search.search_or_paste": "Keresés vagy URL beillesztése",
"search_popout.full_text_search_disabled_message": "Nem érhető el ezen: {domain}.",
"search_popout.full_text_search_logged_out_message": "Csak bejelentkezve érhető el.",
"search_popout.language_code": "ISO nyelvkód",
"search_popout.options": "Keresési beállítások",
"search_popout.quick_actions": "Gyors műveletek",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Færslur sem samsvara {x}",
"search.search_or_paste": "Leita eða líma slóð",
"search_popout.full_text_search_disabled_message": "Ekki tiltækt á {domain}.",
"search_popout.full_text_search_logged_out_message": "Aðeins tiltækt eftir innskráningu.",
"search_popout.language_code": "ISO-kóði tungumáls",
"search_popout.options": "Leitarvalkostir",
"search_popout.quick_actions": "Flýtiaðgerðir",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Post corrispondenti a {x}",
"search.search_or_paste": "Cerca o incolla URL",
"search_popout.full_text_search_disabled_message": "Non disponibile in {domain}.",
"search_popout.full_text_search_logged_out_message": "Disponibile solo dopo aver effettuato l'accesso.",
"search_popout.language_code": "Codice ISO lingua",
"search_popout.options": "Opzioni di ricerca",
"search_popout.quick_actions": "Azioni rapide",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "{x}에 맞는 게시물",
"search.search_or_paste": "검색하거나 URL 붙여넣기",
"search_popout.full_text_search_disabled_message": "{domain}에서는 이용할 수 없습니다.",
"search_popout.full_text_search_logged_out_message": "로그인되어 있을 때만 할 수 있습니다.",
"search_popout.language_code": "ISO 언어코드",
"search_popout.options": "검색 옵션",
"search_popout.quick_actions": "빠른 작업",

View file

@ -445,6 +445,7 @@
"search.placeholder": "Paieška",
"search.search_or_paste": "Ieškok arba įklijuok URL",
"search_popout.full_text_search_disabled_message": "Nepasiekima {domain}.",
"search_popout.full_text_search_logged_out_message": "Pasiekiama tik prisijungus.",
"search_popout.language_code": "ISO kalbos kodas",
"search_popout.specific_date": "konkreti data",
"search_popout.user": "naudotojas",

View file

@ -143,7 +143,7 @@
"compose_form.encryption_warning": "Berichten op Mastodon worden, net zoals op andere social media, niet end-to-end versleuteld. Deel daarom geen gevoelige informatie via Mastodon.",
"compose_form.hashtag_warning": "Dit bericht valt niet onder een hashtag te bekijken, omdat deze niet openbaar is. Alleen openbare berichten kunnen via hashtags gevonden worden.",
"compose_form.lock_disclaimer": "Jouw account is niet {locked}. Iedereen kan jou volgen en kan de berichten zien die je alleen aan jouw volgers hebt gericht.",
"compose_form.lock_disclaimer.lock": "besloten",
"compose_form.lock_disclaimer.lock": "vergrendeld",
"compose_form.placeholder": "Wat wil je kwijt?",
"compose_form.poll.add_option": "Keuze toevoegen",
"compose_form.poll.duration": "Duur van de peiling",
@ -382,7 +382,7 @@
"lists.delete": "Lijst verwijderen",
"lists.edit": "Lijst bewerken",
"lists.edit.submit": "Titel veranderen",
"lists.exclusive": "Verberg deze berichten op je starttijdlijn",
"lists.exclusive": "Verberg lijstleden op je starttijdlijn",
"lists.new.create": "Lijst toevoegen",
"lists.new.title_placeholder": "Naam nieuwe lijst",
"lists.replies_policy.followed": "Elke gevolgde gebruiker",
@ -395,7 +395,7 @@
"loading_indicator.label": "Laden…",
"media_gallery.toggle_visible": "{number, plural, one {afbeelding verbergen} other {afbeeldingen verbergen}}",
"moved_to_account_banner.text": "Omdat je naar {movedToAccount} bent verhuisd is jouw account {disabledAccount} momenteel uitgeschakeld.",
"mute_modal.duration": "Duur",
"mute_modal.duration": "Tijdsduur",
"mute_modal.hide_notifications": "Verberg meldingen van deze persoon?",
"mute_modal.indefinite": "Voor onbepaalde tijd",
"navigation_bar.about": "Over",
@ -481,8 +481,8 @@
"onboarding.follows.empty": "Helaas kunnen op dit moment geen resultaten worden getoond. Je kunt proberen te zoeken of op de verkenningspagina te bladeren om mensen te vinden die je kunt volgen, of probeer het later opnieuw.",
"onboarding.follows.lead": "Jouw starttijdlijn is de belangrijkste manier om Mastodon te ervaren. Hoe meer mensen je volgt, hoe actiever en interessanter het zal zijn. Om te beginnen, zijn hier enkele suggesties:",
"onboarding.follows.title": "Je starttijdlijn aan jouw wensen aanpassen",
"onboarding.profile.discoverable": "Maak mij profiel ontdekbaar",
"onboarding.profile.discoverable_hint": "Wanneer je kiest voor Mastodon kun je berichten weergeven in zoekresultaten en trending, en je profiel kan worden voorgesteld aan mensen met vergelijkbare interesses.",
"onboarding.profile.discoverable": "Maak mijn profiel vindbaar",
"onboarding.profile.discoverable_hint": "Wanneer je akkoord gaat met het vindbaar zijn op Mastodon, verschijnen je berichten in zoekresultaten en kunnen ze trending worden, en je profiel kan aan andere mensen worden aanbevolen wanneer ze vergelijkbare interesses hebben.",
"onboarding.profile.display_name": "Weergavenaam",
"onboarding.profile.display_name_hint": "Jouw volledige naam of een leuke bijnaam…",
"onboarding.profile.lead": "Je kunt dit later altijd aanvullen in de instellingen, waar nog meer aanpassingsopties beschikbaar zijn.",
@ -491,7 +491,7 @@
"onboarding.profile.save_and_continue": "Opslaan en doorgaan",
"onboarding.profile.title": "Profiel instellen",
"onboarding.profile.upload_avatar": "Profielfoto uploaden",
"onboarding.profile.upload_header": "Kop voor het profiel uploaden",
"onboarding.profile.upload_header": "Omslagfoto voor het profiel uploaden",
"onboarding.share.lead": "Laat mensen weten hoe ze je kunnen vinden op Mastodon!",
"onboarding.share.message": "Ik ben {username} op #Mastodon! Volg mij op {url}",
"onboarding.share.next_steps": "Mogelijke volgende stappen:",
@ -504,7 +504,7 @@
"onboarding.steps.publish_status.body": "Zeg hallo tegen de wereld met tekst, foto's, video's of peilingen {emoji}",
"onboarding.steps.publish_status.title": "Maak je eerste bericht",
"onboarding.steps.setup_profile.body": "Anderen zullen eerder met je in contact treden als je wat over jezelf vertelt.",
"onboarding.steps.setup_profile.title": "Pas je profiel aan",
"onboarding.steps.setup_profile.title": "Je profiel aanpassen",
"onboarding.steps.share_profile.body": "Laat je vrienden weten waar je te vinden bent op Mastodon",
"onboarding.steps.share_profile.title": "Deel je Mastodonprofiel",
"onboarding.tips.2fa": "<strong>Wist je dat?</strong> Je kunt je account beveiligen door tweestapsverificatie in te stellen in je accountinstellingen. Het werkt met elke TOTP-app naar keuze, geen telefoonnummer nodig!",
@ -606,6 +606,7 @@
"search.quick_action.status_search": "Berichten die overeenkomen met {x}",
"search.search_or_paste": "Zoek of voer een URL in",
"search_popout.full_text_search_disabled_message": "Niet beschikbaar op {domain}.",
"search_popout.full_text_search_logged_out_message": "Alleen beschikbaar als je bent ingelogd.",
"search_popout.language_code": "ISO-taalcode",
"search_popout.options": "Zoekopties",
"search_popout.quick_actions": "Snelle acties",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Wpisy pasujące do {x}",
"search.search_or_paste": "Wyszukaj lub wklej adres",
"search_popout.full_text_search_disabled_message": "Niedostępne na {domain}.",
"search_popout.full_text_search_logged_out_message": "Dostępne tylko po zalogowaniu.",
"search_popout.language_code": "Kod języka wg ISO",
"search_popout.options": "Opcje wyszukiwania",
"search_popout.quick_actions": "Szybkie akcje",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Publicações correspondentes a {x}",
"search.search_or_paste": "Buscar ou colar URL",
"search_popout.full_text_search_disabled_message": "Não disponível em {domain}.",
"search_popout.full_text_search_logged_out_message": "Disponível apenas quando conectado.",
"search_popout.language_code": "Código ISO do idioma",
"search_popout.options": "Opções de pesquisa",
"search_popout.quick_actions": "Ações rápidas",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Publicações com correspondência a {x}",
"search.search_or_paste": "Pesquisar ou introduzir URL",
"search_popout.full_text_search_disabled_message": "Não disponível em {domain}.",
"search_popout.full_text_search_logged_out_message": "Apenas disponível quando tem sessão iniciada.",
"search_popout.language_code": "Código ISO do idioma",
"search_popout.options": "Opções de pesquisa",
"search_popout.quick_actions": "Ações rápidas",

View file

@ -21,6 +21,7 @@
"account.blocked": "Заблокировано",
"account.browse_more_on_origin_server": "Посмотреть в оригинальном профиле",
"account.cancel_follow_request": "Отозвать запрос на подписку",
"account.copy": "Скопировать ссылку на профиль",
"account.direct": "Лично упоминать @{name}",
"account.disable_notifications": "Не уведомлять о постах от @{name}",
"account.domain_blocked": "Домен заблокирован",
@ -191,6 +192,7 @@
"conversation.mark_as_read": "Отметить как прочитанное",
"conversation.open": "Просмотр беседы",
"conversation.with": "С {names}",
"copy_icon_button.copied": "Скопировано в буфер обмена",
"copypaste.copied": "Скопировано",
"copypaste.copy_to_clipboard": "Копировать в буфер обмена",
"directory.federated": "Со всей федерации",
@ -222,6 +224,7 @@
"emoji_button.search_results": "Результаты поиска",
"emoji_button.symbols": "Символы",
"emoji_button.travel": "Путешествия и места",
"empty_column.account_hides_collections": "Данный пользователь решил не предоставлять эту информацию",
"empty_column.account_suspended": "Учетная запись заблокирована",
"empty_column.account_timeline": "Здесь нет постов!",
"empty_column.account_unavailable": "Профиль недоступен",
@ -389,6 +392,7 @@
"lists.search": "Искать среди подписок",
"lists.subheading": "Ваши списки",
"load_pending": "{count, plural, one {# новый элемент} few {# новых элемента} other {# новых элементов}}",
"loading_indicator.label": "Загрузка…",
"media_gallery.toggle_visible": "Показать/скрыть {number, plural, =1 {изображение} other {изображения}}",
"moved_to_account_banner.text": "Ваша учетная запись {disabledAccount} в настоящее время заморожена, потому что вы переехали на {movedToAccount}.",
"mute_modal.duration": "Продолжительность",
@ -477,6 +481,17 @@
"onboarding.follows.empty": "К сожалению, сейчас нет результатов. Вы можете попробовать использовать поиск или просмотреть страницу \"Исследования\", чтобы найти людей, за которыми можно следить, или повторить попытку позже.",
"onboarding.follows.lead": "Вы сами формируете свою домашнюю ленту. Чем больше людей, за которыми вы следите, тем активнее и интереснее она будет. Эти профили могут быть хорошей отправной точкой - вы всегда можете от них отказаться!",
"onboarding.follows.title": "Популярно на Mastodon",
"onboarding.profile.discoverable": "Сделать мой профиль открытым",
"onboarding.profile.discoverable_hint": "Если вы соглашаетесь на открытость на Mastodon, ваши сообщения могут появляться в результатах поиска и трендах, а ваш профиль может быть предложен людям со схожими с вами интересами.",
"onboarding.profile.display_name": "Отображаемое имя",
"onboarding.profile.display_name_hint": "Ваше полное имя или псевдоним…",
"onboarding.profile.lead": "Вы всегда можете завершить это позже в настройках, где доступны еще более широкие возможности настройки.",
"onboarding.profile.note": "О себе",
"onboarding.profile.note_hint": "Вы можете @упоминать других людей или использовать #хэштеги…",
"onboarding.profile.save_and_continue": "Сохранить и продолжить",
"onboarding.profile.title": "Настройка профиля",
"onboarding.profile.upload_avatar": "Загрузить фотографию профиля",
"onboarding.profile.upload_header": "Загрузить заголовок профиля",
"onboarding.share.lead": "Расскажите людям, как они могут найти вас на Mastodon!",
"onboarding.share.message": "Я {username} на #Mastodon! Следуйте за мной по адресу {url}",
"onboarding.share.next_steps": "Возможные дальнейшие шаги:",
@ -520,6 +535,7 @@
"privacy.unlisted.short": "Скрытый",
"privacy_policy.last_updated": "Последнее обновление {date}",
"privacy_policy.title": "Политика конфиденциальности",
"recommended": "Рекомендуется",
"refresh": "Обновить",
"regeneration_indicator.label": "Загрузка…",
"regeneration_indicator.sublabel": "Один момент, мы подготавливаем вашу ленту!",
@ -590,6 +606,7 @@
"search.quick_action.status_search": "Посты, соответствующие {x}",
"search.search_or_paste": "Поиск (или вставьте URL)",
"search_popout.full_text_search_disabled_message": "Недоступно на {domain}.",
"search_popout.full_text_search_logged_out_message": "Доступно только при авторизации.",
"search_popout.language_code": "Код языка по стандарту ISO",
"search_popout.options": "Параметры поиска",
"search_popout.quick_actions": "Быстрые действия",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Objave, ki se ujemajo z {x}",
"search.search_or_paste": "Iščite ali prilepite URL",
"search_popout.full_text_search_disabled_message": "Ni dostopno na {domain}.",
"search_popout.full_text_search_logged_out_message": "Na voljo le, če ste prijavljeni.",
"search_popout.language_code": "Koda ISO jezika",
"search_popout.options": "Možnosti iskanja",
"search_popout.quick_actions": "Hitra dejanja",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Podudaranje objava {x}",
"search.search_or_paste": "Pretražite ili unesite adresu",
"search_popout.full_text_search_disabled_message": "Nije dostupno na {domain}.",
"search_popout.full_text_search_logged_out_message": "Dostupno samo kada ste prijavljeni.",
"search_popout.language_code": "ISO kod jezika",
"search_popout.options": "Opcije pretrage",
"search_popout.quick_actions": "Brze radnje",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Подударање објава {x}",
"search.search_or_paste": "Претражите или унесите адресу",
"search_popout.full_text_search_disabled_message": "Није доступно на {domain}.",
"search_popout.full_text_search_logged_out_message": "Доступно само када сте пријављени.",
"search_popout.language_code": "ISO код језика",
"search_popout.options": "Опције претраге",
"search_popout.quick_actions": "Брзе радње",

View file

@ -604,6 +604,7 @@
"search.quick_action.status_search": "Inlägg som matchar {x}",
"search.search_or_paste": "Sök eller klistra in URL",
"search_popout.full_text_search_disabled_message": "Inte tillgänglig på {domain}.",
"search_popout.full_text_search_logged_out_message": "Endast tillgängligt när du är inloggad.",
"search_popout.language_code": "ISO språkkod",
"search_popout.options": "Sökalternativ",
"search_popout.quick_actions": "Snabbåtgärder",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "โพสต์ที่ตรงกับ {x}",
"search.search_or_paste": "ค้นหาหรือวาง URL",
"search_popout.full_text_search_disabled_message": "ไม่พร้อมใช้งานใน {domain}",
"search_popout.full_text_search_logged_out_message": "พร้อมใช้งานเฉพาะเมื่อเข้าสู่ระบบแล้วเท่านั้น",
"search_popout.language_code": "รหัสภาษา ISO",
"search_popout.options": "ตัวเลือกการค้นหา",
"search_popout.quick_actions": "การกระทำด่วน",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Eşleşen gönderiler {x}",
"search.search_or_paste": "Ara veya bağlantıyı yapıştır",
"search_popout.full_text_search_disabled_message": "{domain} sunucusunda mevcut değil.",
"search_popout.full_text_search_logged_out_message": "Sadece oturum açıldığında mevcuttur.",
"search_popout.language_code": "ISO dil kodu",
"search_popout.options": "Arama seçenekleri",
"search_popout.quick_actions": "Hızlı eylemler",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Збіг дописів {x}",
"search.search_or_paste": "Введіть адресу або пошуковий запит",
"search_popout.full_text_search_disabled_message": "Недоступно на {domain}.",
"search_popout.full_text_search_logged_out_message": "Доступно лише після входу.",
"search_popout.language_code": "Код мови ISO",
"search_popout.options": "Опції пошуку",
"search_popout.quick_actions": "Швидкі дії",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "Tút nhắc đến {x}",
"search.search_or_paste": "Tìm kiếm hoặc nhập URL",
"search_popout.full_text_search_disabled_message": "Không khả dụng trên {domain}.",
"search_popout.full_text_search_logged_out_message": "Cần đăng nhập trước.",
"search_popout.language_code": "Mã ngôn ngữ ISO",
"search_popout.options": "Tùy chọn tìm kiếm",
"search_popout.quick_actions": "Thao tác nhanh",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "匹配 {x} 的嘟文",
"search.search_or_paste": "搜索或输入网址",
"search_popout.full_text_search_disabled_message": "在 {domain} 不可用",
"search_popout.full_text_search_logged_out_message": "只有登录后才可用。",
"search_popout.language_code": "ISO语言代码",
"search_popout.options": "搜索选项",
"search_popout.quick_actions": "快捷操作",

View file

@ -606,6 +606,7 @@
"search.quick_action.status_search": "符合的帖文 {x}",
"search.search_or_paste": "搜尋或貼上網址",
"search_popout.full_text_search_disabled_message": "在 {domain} 上無法使用。",
"search_popout.full_text_search_logged_out_message": "登入後才可使用。",
"search_popout.language_code": "ISO 語言代碼",
"search_popout.options": "搜尋選項",
"search_popout.quick_actions": "快速動作",

View file

@ -176,7 +176,7 @@
"confirmations.domain_block.confirm": "封鎖整個網域",
"confirmations.domain_block.message": "您真的非常確定要封鎖整個 {domain} 網域嗎?大部分情況下,封鎖或靜音少數特定的帳號就能滿足需求了。您將不能在任何公開的時間軸及通知中看到來自此網域的內容。您來自該網域的跟隨者也將被移除。",
"confirmations.edit.confirm": "編輯",
"confirmations.edit.message": "編輯嘟文將覆蓋掉您目前正在撰寫的訊息。是否仍要繼續?",
"confirmations.edit.message": "編輯嘟文將覆蓋掉您目前正在撰寫之嘟文內容。您是否仍要繼續?",
"confirmations.logout.confirm": "登出",
"confirmations.logout.message": "您確定要登出嗎?",
"confirmations.mute.confirm": "靜音",
@ -606,6 +606,7 @@
"search.quick_action.status_search": "符合的嘟文 {x}",
"search.search_or_paste": "搜尋或輸入網址",
"search_popout.full_text_search_disabled_message": "{domain} 上無法使用。",
"search_popout.full_text_search_logged_out_message": "僅於登入時能使用。",
"search_popout.language_code": "ISO 語言代碼 (ISO language code)",
"search_popout.options": "搜尋選項",
"search_popout.quick_actions": "快捷操作",

View file

@ -1,10 +0,0 @@
import ready from '../ready';
export let assetHost = '';
ready(() => {
const cdnHost = document.querySelector('meta[name=cdn-host]');
if (cdnHost) {
assetHost = cdnHost.content || '';
}
});

View file

@ -0,0 +1,13 @@
import ready from '../ready';
export let assetHost = '';
// eslint-disable-next-line @typescript-eslint/no-floating-promises
ready(() => {
const cdnHost = document.querySelector<HTMLMetaElement>(
'meta[name=cdn-host]',
);
if (cdnHost) {
assetHost = cdnHost.content || '';
}
});

View file

@ -1,6 +0,0 @@
// NB: This function can still return unsafe HTML
export const unescapeHTML = (html) => {
const wrapper = document.createElement('div');
wrapper.innerHTML = html.replace(/<br\s*\/?>/g, '\n').replace(/<\/p><p>/g, '\n\n').replace(/<[^>]*>/g, '');
return wrapper.textContent;
};

View file

@ -0,0 +1,9 @@
// NB: This function can still return unsafe HTML
export const unescapeHTML = (html: string) => {
const wrapper = document.createElement('div');
wrapper.innerHTML = html
.replace(/<br\s*\/?>/g, '\n')
.replace(/<\/p><p>/g, '\n\n')
.replace(/<[^>]*>/g, '');
return wrapper.textContent;
};

View file

@ -1,13 +1,23 @@
// Copied from emoji-mart for consistency with emoji picker and since
// they don't export the icons in the package
export const loupeIcon = (
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' width='13' height='13'>
<svg
xmlns='http://www.w3.org/2000/svg'
viewBox='0 0 20 20'
width='13'
height='13'
>
<path d='M12.9 14.32a8 8 0 1 1 1.41-1.41l5.35 5.33-1.42 1.42-5.33-5.34zM8 14A6 6 0 1 0 8 2a6 6 0 0 0 0 12z' />
</svg>
);
export const deleteIcon = (
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' width='13' height='13'>
<svg
xmlns='http://www.w3.org/2000/svg'
viewBox='0 0 20 20'
width='13'
height='13'
>
<path d='M10 8.586L2.929 1.515 1.515 2.929 8.586 10l-7.071 7.071 1.414 1.414L10 11.414l7.071 7.071 1.414-1.414L11.414 10l7.071-7.071-1.414-1.414L10 8.586z' />
</svg>
);

View file

@ -1,30 +0,0 @@
// Handles browser quirks, based on
// https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API/Using_the_Notifications_API
const checkNotificationPromise = () => {
try {
// eslint-disable-next-line promise/valid-params, promise/catch-or-return
Notification.requestPermission().then();
} catch(e) {
return false;
}
return true;
};
const handlePermission = (permission, callback) => {
// Whatever the user answers, we make sure Chrome stores the information
if(!('permission' in Notification)) {
Notification.permission = permission;
}
callback(Notification.permission);
};
export const requestNotificationPermission = (callback) => {
if (checkNotificationPromise()) {
Notification.requestPermission().then((permission) => handlePermission(permission, callback)).catch(console.warn);
} else {
Notification.requestPermission((permission) => handlePermission(permission, callback));
}
};

View file

@ -0,0 +1,13 @@
/**
* Tries Notification.requestPermission, console warning instead of rejecting on error.
* @param callback Runs with the permission result on completion.
*/
export const requestNotificationPermission = async (
callback: NotificationPermissionCallback,
) => {
try {
callback(await Notification.requestPermission());
} catch (error) {
console.warn(error);
}
};

View file

@ -1,8 +1,8 @@
import PropTypes from "prop-types";
import PropTypes from 'prop-types';
import { __RouterContext } from "react-router";
import { __RouterContext } from 'react-router';
import hoistStatics from "hoist-non-react-statics";
import hoistStatics from 'hoist-non-react-statics';
export const WithRouterPropTypes = {
match: PropTypes.object.isRequired,
@ -16,31 +16,37 @@ export const WithOptionalRouterPropTypes = {
history: PropTypes.object,
};
export interface OptionalRouterProps {
ref: unknown;
wrappedComponentRef: unknown;
}
// This is copied from https://github.com/remix-run/react-router/blob/v5.3.4/packages/react-router/modules/withRouter.js
// but does not fail if called outside of a React Router context
export function withOptionalRouter(Component) {
const displayName = `withRouter(${Component.displayName || Component.name})`;
const C = props => {
export function withOptionalRouter<
ComponentType extends React.ComponentType<OptionalRouterProps>,
>(Component: ComponentType) {
const displayName = `withRouter(${Component.displayName ?? Component.name})`;
const C = (props: React.ComponentProps<ComponentType>) => {
const { wrappedComponentRef, ...remainingProps } = props;
return (
<__RouterContext.Consumer>
{context => {
if(context)
{(context) => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (context) {
return (
// @ts-expect-error - Dynamic covariant generic components are tough to type.
<Component
{...remainingProps}
{...context}
ref={wrappedComponentRef}
/>
);
else
return (
<Component
{...remainingProps}
ref={wrappedComponentRef}
/>
);
} else {
// @ts-expect-error - Dynamic covariant generic components are tough to type.
return <Component {...remainingProps} ref={wrappedComponentRef} />;
}
}}
</__RouterContext.Consumer>
);
@ -53,8 +59,8 @@ export function withOptionalRouter(Component) {
wrappedComponentRef: PropTypes.oneOfType([
PropTypes.string,
PropTypes.func,
PropTypes.object
])
PropTypes.object,
]),
};
return hoistStatics(C, Component);

View file

@ -1,11 +1,7 @@
import { isMobile } from '../is_mobile';
/** @type {number | null} */
let cachedScrollbarWidth = null;
let cachedScrollbarWidth: number | null = null;
/**
* @returns {number}
*/
const getActualScrollbarWidth = () => {
const outer = document.createElement('div');
outer.style.visibility = 'hidden';
@ -16,20 +12,19 @@ const getActualScrollbarWidth = () => {
outer.appendChild(inner);
const scrollbarWidth = outer.offsetWidth - inner.offsetWidth;
outer.parentNode.removeChild(outer);
outer.remove();
return scrollbarWidth;
};
/**
* @returns {number}
*/
export const getScrollbarWidth = () => {
if (cachedScrollbarWidth !== null) {
return cachedScrollbarWidth;
}
const scrollbarWidth = isMobile(window.innerWidth) ? 0 : getActualScrollbarWidth();
const scrollbarWidth = isMobile(window.innerWidth)
? 0
: getActualScrollbarWidth();
cachedScrollbarWidth = scrollbarWidth;
return scrollbarWidth;

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
class AdminMailer < ApplicationMailer
layout 'plain_mailer'
layout 'admin_mailer'
helper :accounts
helper :languages

View file

@ -24,12 +24,12 @@ class Admin::ActionLog < ApplicationRecord
belongs_to :account
belongs_to :target, polymorphic: true, optional: true
default_scope -> { order('id desc') }
before_validation :set_human_identifier
before_validation :set_route_param
before_validation :set_permalink
scope :latest, -> { order(id: :desc) }
def action
super.to_sym
end

View file

@ -72,7 +72,7 @@ class Admin::ActionLogFilter
end
def results
scope = Admin::ActionLog.includes(:target)
scope = latest_action_logs.includes(:target)
params.each do |key, value|
next if key.to_s == 'page'
@ -88,14 +88,18 @@ class Admin::ActionLogFilter
def scope_for(key, value)
case key
when 'action_type'
Admin::ActionLog.where(ACTION_TYPE_MAP[value.to_sym])
latest_action_logs.where(ACTION_TYPE_MAP[value.to_sym])
when 'account_id'
Admin::ActionLog.where(account_id: value)
latest_action_logs.where(account_id: value)
when 'target_account_id'
account = Account.find_or_initialize_by(id: value)
Admin::ActionLog.where(target: [account, account.user].compact)
latest_action_logs.where(target: [account, account.user].compact)
else
raise Mastodon::InvalidParameterError, "Unknown filter: #{key}"
end
end
def latest_action_logs
Admin::ActionLog.latest
end
end

View file

@ -131,25 +131,25 @@ class Report < ApplicationRecord
Admin::ActionLog.where(
target_type: 'Report',
target_id: id
).unscope(:order).arel,
).arel,
Admin::ActionLog.where(
target_type: 'Account',
target_id: target_account_id
).unscope(:order).arel,
).arel,
Admin::ActionLog.where(
target_type: 'Status',
target_id: status_ids
).unscope(:order).arel,
).arel,
Admin::ActionLog.where(
target_type: 'AccountWarning',
target_id: AccountWarning.where(report_id: id).select(:id)
).unscope(:order).arel,
).arel,
].reduce { |union, query| Arel::Nodes::UnionAll.new(union, query) }
Admin::ActionLog.from(Arel::Nodes::As.new(subquery, Admin::ActionLog.arel_table))
Admin::ActionLog.latest.from(Arel::Nodes::As.new(subquery, Admin::ActionLog.arel_table))
end
private

View file

@ -49,7 +49,7 @@ class UserRole < ApplicationRecord
invite_users
).freeze,
moderation: %w(
moderation: %i(
view_dashboard
view_audit_log
manage_users
@ -63,7 +63,7 @@ class UserRole < ApplicationRecord
manage_invites
).freeze,
administration: %w(
administration: %i(
manage_settings
manage_rules
manage_roles
@ -72,7 +72,7 @@ class UserRole < ApplicationRecord
manage_announcements
).freeze,
devops: %w(
devops: %i(
view_devops
).freeze,

View file

@ -38,7 +38,10 @@ class NodeInfo::Serializer < ActiveModel::Serializer
end
def metadata
{}
{
nodeName: Setting.site_title,
nodeDescription: Setting.site_short_description,
}
end
private

View file

@ -2,7 +2,10 @@
class REST::ApplicationSerializer < ActiveModel::Serializer
attributes :id, :name, :website, :scopes, :redirect_uri,
:client_id, :client_secret, :vapid_key
:client_id, :client_secret
# NOTE: Deprecated in 4.3.0, needs to be removed in 5.0.0
attribute :vapid_key
def id
object.id.to_s

View file

@ -48,6 +48,10 @@ class REST::InstanceSerializer < ActiveModel::Serializer
status: object.status_page_url,
},
vapid: {
public_key: Rails.configuration.x.vapid_public_key,
},
accounts: {
max_featured_tags: FeaturedTag::LIMIT,
},

View file

@ -100,7 +100,9 @@ class ResolveAccountService < BaseService
end
def split_acct(acct)
acct.delete_prefix('acct:').split('@')
acct.delete_prefix('acct:').split('@').tap do |parts|
raise Webfinger::Error, 'Webfinger response is missing user or host value' unless parts.size == 2
end
end
def fetch_account!

View file

@ -5,7 +5,7 @@
- if @account.user_prefers_noindex?
%meta{ name: 'robots', content: 'noindex, noarchive' }/
%link{ rel: 'alternate', type: 'application/rss+xml', href: @rss_url }/
%link{ rel: 'alternate', type: 'application/rss+xml', href: rss_url }/
%link{ rel: 'alternate', type: 'application/activity+json', href: ActivityPub::TagManager.instance.uri_for(@account) }/
- @account.fields.select(&:verifiable?).each do |field|

View file

@ -5,7 +5,7 @@
= f.input :report_id, as: :hidden
.fields-group
= f.input :type, as: :radio_buttons, collection: Admin::AccountAction.types_for_account(@account), include_blank: false, wrapper: :with_block_label, label_method: ->(type) { safe_join([I18n.t("simple_form.labels.admin_account_action.types.#{type}"), content_tag(:span, I18n.t("simple_form.hints.admin_account_action.types.#{type}"), class: 'hint')]) }, hint: t('simple_form.hints.admin_account_action.type_html', acct: @account.pretty_acct)
= f.input :type, as: :radio_buttons, collection: Admin::AccountAction.types_for_account(@account), include_blank: false, wrapper: :with_block_label, label_method: ->(type) { account_action_type_label(type) }, hint: t('simple_form.hints.admin_account_action.type_html', acct: @account.pretty_acct)
- if @account.local?
%hr.spacer/

View file

@ -10,7 +10,7 @@
.filter-subset.filter-subset--with-select
%strong= t('admin.accounts.moderation.title')
.input.select.optional
= select_tag :status, options_for_select([[t('admin.accounts.moderation.active'), 'active'], [t('admin.accounts.moderation.silenced'), 'silenced'], [t('admin.accounts.moderation.disabled'), 'disabled'], [t('admin.accounts.moderation.suspended'), 'suspended'], [safe_join([t('admin.accounts.moderation.pending'), "(#{number_with_delimiter(User.pending.count)})"], ' '), 'pending']], params[:status]), prompt: I18n.t('generic.all')
= select_tag :status, options_for_select(admin_accounts_moderation_options, params[:status]), prompt: I18n.t('generic.all')
.filter-subset.filter-subset--with-select
%strong= t('admin.accounts.role')
.input.select.optional

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