Merge remote-tracking branch 'glitch/main'
This commit is contained in:
commit
e0f9ac242b
636 changed files with 13676 additions and 4983 deletions
|
@ -1,128 +0,0 @@
|
||||||
version: 2.1
|
|
||||||
|
|
||||||
orbs:
|
|
||||||
ruby: circleci/ruby@2.0.0
|
|
||||||
node: circleci/node@5.0.3
|
|
||||||
|
|
||||||
executors:
|
|
||||||
default:
|
|
||||||
parameters:
|
|
||||||
ruby-version:
|
|
||||||
type: string
|
|
||||||
docker:
|
|
||||||
- image: cimg/ruby:<< parameters.ruby-version >>
|
|
||||||
environment:
|
|
||||||
BUNDLE_JOBS: 3
|
|
||||||
BUNDLE_RETRY: 3
|
|
||||||
CONTINUOUS_INTEGRATION: true
|
|
||||||
DB_HOST: localhost
|
|
||||||
DB_USER: root
|
|
||||||
DISABLE_SIMPLECOV: true
|
|
||||||
RAILS_ENV: test
|
|
||||||
- image: cimg/postgres:14.5
|
|
||||||
environment:
|
|
||||||
POSTGRES_USER: root
|
|
||||||
POSTGRES_HOST_AUTH_METHOD: trust
|
|
||||||
- image: cimg/redis:7.0
|
|
||||||
|
|
||||||
commands:
|
|
||||||
install-system-dependencies:
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
name: Install system dependencies
|
|
||||||
command: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y libicu-dev libidn11-dev
|
|
||||||
install-ruby-dependencies:
|
|
||||||
parameters:
|
|
||||||
ruby-version:
|
|
||||||
type: string
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
command: |
|
|
||||||
bundle config clean 'true'
|
|
||||||
bundle config frozen 'true'
|
|
||||||
bundle config without 'development production'
|
|
||||||
name: Set bundler settings
|
|
||||||
- ruby/install-deps:
|
|
||||||
bundler-version: '2.3.26'
|
|
||||||
key: ruby<< parameters.ruby-version >>-gems-v2
|
|
||||||
wait-db:
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
command: dockerize -wait tcp://localhost:5432 -wait tcp://localhost:6379 -timeout 1m
|
|
||||||
name: Wait for PostgreSQL and Redis
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
docker:
|
|
||||||
- image: cimg/ruby:3.2-node
|
|
||||||
environment:
|
|
||||||
RAILS_ENV: test
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- install-system-dependencies
|
|
||||||
- install-ruby-dependencies:
|
|
||||||
ruby-version: '3.2'
|
|
||||||
- node/install-packages:
|
|
||||||
cache-version: v1
|
|
||||||
pkg-manager: yarn
|
|
||||||
- run:
|
|
||||||
command: |
|
|
||||||
export NODE_OPTIONS=--openssl-legacy-provider
|
|
||||||
./bin/rails assets:precompile
|
|
||||||
name: Precompile assets
|
|
||||||
- persist_to_workspace:
|
|
||||||
paths:
|
|
||||||
- public/assets
|
|
||||||
- public/packs-test
|
|
||||||
root: .
|
|
||||||
|
|
||||||
test:
|
|
||||||
parameters:
|
|
||||||
ruby-version:
|
|
||||||
type: string
|
|
||||||
executor:
|
|
||||||
name: default
|
|
||||||
ruby-version: << parameters.ruby-version >>
|
|
||||||
environment:
|
|
||||||
ALLOW_NOPAM: true
|
|
||||||
PAM_ENABLED: true
|
|
||||||
PAM_DEFAULT_SERVICE: pam_test
|
|
||||||
PAM_CONTROLLED_SERVICE: pam_test_controlled
|
|
||||||
parallelism: 4
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- install-system-dependencies
|
|
||||||
- run:
|
|
||||||
command: sudo apt-get install -y ffmpeg imagemagick libmagickcore-dev libmagickwand-dev libjpeg-dev libpng-dev libtiff-dev libwebp-dev libpam-dev
|
|
||||||
name: Install additional system dependencies
|
|
||||||
- run:
|
|
||||||
command: bundle config with 'pam_authentication'
|
|
||||||
name: Enable PAM authentication
|
|
||||||
- install-ruby-dependencies:
|
|
||||||
ruby-version: << parameters.ruby-version >>
|
|
||||||
- attach_workspace:
|
|
||||||
at: .
|
|
||||||
- wait-db
|
|
||||||
- run:
|
|
||||||
command: ./bin/rails db:create db:schema:load db:seed
|
|
||||||
name: Load database schema
|
|
||||||
- ruby/rspec-test
|
|
||||||
|
|
||||||
workflows:
|
|
||||||
version: 2
|
|
||||||
build-and-test:
|
|
||||||
jobs:
|
|
||||||
- build
|
|
||||||
- test:
|
|
||||||
matrix:
|
|
||||||
parameters:
|
|
||||||
ruby-version:
|
|
||||||
- '2.7'
|
|
||||||
- '3.0'
|
|
||||||
- '3.1'
|
|
||||||
- '3.2'
|
|
||||||
name: test-ruby<< matrix.ruby-version >>
|
|
||||||
requires:
|
|
||||||
- build
|
|
|
@ -1,16 +1,14 @@
|
||||||
# [Choice] Ruby version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.1, 3.0, 2, 2.7, 2.6, 3-bullseye, 3.1-bullseye, 3.0-bullseye, 2-bullseye, 2.7-bullseye, 2.6-bullseye, 3-buster, 3.1-buster, 3.0-buster, 2-buster, 2.7-buster, 2.6-buster
|
# For details, see https://github.com/devcontainers/images/tree/main/src/ruby
|
||||||
ARG VARIANT=3.1-bullseye
|
FROM mcr.microsoft.com/devcontainers/ruby:0-3.2-bullseye
|
||||||
FROM mcr.microsoft.com/vscode/devcontainers/ruby:${VARIANT}
|
|
||||||
|
|
||||||
# Install Rails
|
# Install Rails
|
||||||
# RUN gem install rails webdrivers
|
# RUN gem install rails webdrivers
|
||||||
|
|
||||||
# Default value to allow debug server to serve content over GitHub Codespace's port forwarding service
|
# Default value to allow debug server to serve content over GitHub Codespace's port forwarding service
|
||||||
# The value is a comma-separated list of allowed domains
|
# The value is a comma-separated list of allowed domains
|
||||||
ENV RAILS_DEVELOPMENT_HOSTS=".githubpreview.dev"
|
ENV RAILS_DEVELOPMENT_HOSTS=".githubpreview.dev,.preview.app.github.dev,.app.github.dev"
|
||||||
|
|
||||||
# [Choice] Node.js version: lts/*, 18, 16, 14
|
ARG NODE_VERSION="16"
|
||||||
ARG NODE_VERSION="lts/*"
|
|
||||||
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"
|
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"
|
||||||
|
|
||||||
# [Optional] Uncomment this section to install additional OS packages.
|
# [Optional] Uncomment this section to install additional OS packages.
|
||||||
|
@ -22,3 +20,5 @@ RUN gem install foreman
|
||||||
|
|
||||||
# [Optional] Uncomment this line to install global node packages.
|
# [Optional] Uncomment this line to install global node packages.
|
||||||
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g yarn" 2>&1
|
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g yarn" 2>&1
|
||||||
|
|
||||||
|
COPY welcome-message.txt /usr/local/etc/vscode-dev-containers/first-run-notice.txt
|
||||||
|
|
|
@ -1,30 +1,13 @@
|
||||||
|
// For more details, see https://aka.ms/devcontainer.json.
|
||||||
{
|
{
|
||||||
"name": "Mastodon",
|
"name": "Mastodon",
|
||||||
"dockerComposeFile": "docker-compose.yml",
|
"dockerComposeFile": "docker-compose.yml",
|
||||||
"service": "app",
|
"service": "app",
|
||||||
"workspaceFolder": "/mastodon",
|
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
|
||||||
|
|
||||||
// Configure tool-specific properties.
|
|
||||||
"customizations": {
|
|
||||||
// Configure properties specific to VS Code.
|
|
||||||
"vscode": {
|
|
||||||
// Set *default* container specific settings.json values on container create.
|
|
||||||
"settings": {},
|
|
||||||
|
|
||||||
// Add the IDs of extensions you want installed when the container is created.
|
|
||||||
"extensions": [
|
|
||||||
"EditorConfig.EditorConfig",
|
|
||||||
"dbaeumer.vscode-eslint",
|
|
||||||
"rebornix.Ruby",
|
|
||||||
"webben.browserslist"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
|
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||||
"features": {
|
"features": {
|
||||||
"ghcr.io/devcontainers/features/sshd:1": {
|
"ghcr.io/devcontainers/features/sshd:1": {}
|
||||||
"version": "latest"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||||
|
@ -33,7 +16,16 @@
|
||||||
|
|
||||||
// Use 'postCreateCommand' to run commands after the container is created.
|
// Use 'postCreateCommand' to run commands after the container is created.
|
||||||
"postCreateCommand": ".devcontainer/post-create.sh",
|
"postCreateCommand": ".devcontainer/post-create.sh",
|
||||||
|
"waitFor": "postCreateCommand",
|
||||||
|
|
||||||
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
// Configure tool-specific properties.
|
||||||
"remoteUser": "vscode"
|
"customizations": {
|
||||||
|
// Configure properties specific to VS Code.
|
||||||
|
"vscode": {
|
||||||
|
// Set *default* container specific settings.json values on container create.
|
||||||
|
"settings": {},
|
||||||
|
// Add the IDs of extensions you want installed when the container is created.
|
||||||
|
"extensions": ["EditorConfig.EditorConfig", "webben.browserslist"]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,15 +5,8 @@ services:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
args:
|
|
||||||
# Update 'VARIANT' to pick a version of Ruby: 3, 3.1, 3.0, 2, 2.7, 2.6
|
|
||||||
# Append -bullseye or -buster to pin to an OS version.
|
|
||||||
# Use -bullseye variants on local arm64/Apple Silicon.
|
|
||||||
VARIANT: '3.0-bullseye'
|
|
||||||
# Optional Node.js version to install
|
|
||||||
NODE_VERSION: '16'
|
|
||||||
volumes:
|
volumes:
|
||||||
- ..:/mastodon:cached
|
- ../..:/workspaces:cached
|
||||||
environment:
|
environment:
|
||||||
RAILS_ENV: development
|
RAILS_ENV: development
|
||||||
NODE_ENV: development
|
NODE_ENV: development
|
||||||
|
@ -33,7 +26,6 @@ services:
|
||||||
networks:
|
networks:
|
||||||
- external_network
|
- external_network
|
||||||
- internal_network
|
- internal_network
|
||||||
user: vscode
|
|
||||||
|
|
||||||
db:
|
db:
|
||||||
image: postgres:14-alpine
|
image: postgres:14-alpine
|
||||||
|
@ -49,7 +41,7 @@ services:
|
||||||
- internal_network
|
- internal_network
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: redis:6-alpine
|
image: redis:7-alpine
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- redis-data:/data
|
- redis-data:/data
|
||||||
|
|
8
.devcontainer/welcome-message.txt
Normal file
8
.devcontainer/welcome-message.txt
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
👋 Welcome to "Mastodon" in GitHub Codespaces!
|
||||||
|
|
||||||
|
🛠️ Your environment is fully setup with all the required software.
|
||||||
|
|
||||||
|
🔍 To explore VS Code to its fullest, search using the Command Palette (Cmd/Ctrl + Shift + P or F1).
|
||||||
|
|
||||||
|
📝 Edit away, run your app as usual, and we'll automatically make it available for you to access.
|
||||||
|
|
9
.github/workflows/build-image.yml
vendored
9
.github/workflows/build-image.yml
vendored
|
@ -25,12 +25,15 @@ jobs:
|
||||||
- uses: hadolint/hadolint-action@v3.1.0
|
- uses: hadolint/hadolint-action@v3.1.0
|
||||||
- uses: docker/setup-qemu-action@v2
|
- uses: docker/setup-qemu-action@v2
|
||||||
- uses: docker/setup-buildx-action@v2
|
- uses: docker/setup-buildx-action@v2
|
||||||
- uses: docker/login-action@v2
|
|
||||||
|
- name: Log in to the Github Container registry
|
||||||
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
|
|
||||||
- uses: docker/metadata-action@v4
|
- uses: docker/metadata-action@v4
|
||||||
id: meta
|
id: meta
|
||||||
with:
|
with:
|
||||||
|
@ -39,6 +42,7 @@ jobs:
|
||||||
type=raw,value=latest,enable={{is_default_branch}}
|
type=raw,value=latest,enable={{is_default_branch}}
|
||||||
type=edge,branch=main
|
type=edge,branch=main
|
||||||
type=sha,prefix=,format=long
|
type=sha,prefix=,format=long
|
||||||
|
|
||||||
- uses: docker/build-push-action@v4
|
- uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
|
@ -47,5 +51,6 @@ jobs:
|
||||||
builder: ${{ steps.buildx.outputs.name }}
|
builder: ${{ steps.buildx.outputs.name }}
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
|
|
17
.github/workflows/haml-lint-problem-matcher.json
vendored
Normal file
17
.github/workflows/haml-lint-problem-matcher.json
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"problemMatcher": [
|
||||||
|
{
|
||||||
|
"owner": "haml-lint",
|
||||||
|
"severity": "warning",
|
||||||
|
"pattern": [
|
||||||
|
{
|
||||||
|
"regexp": "^(.*):(\\d+)\\s\\[W]\\s(.*):\\s(.*)$",
|
||||||
|
"file": 1,
|
||||||
|
"line": 2,
|
||||||
|
"code": 3,
|
||||||
|
"message": 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
44
.github/workflows/lint-haml.yml
vendored
Normal file
44
.github/workflows/lint-haml.yml
vendored
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
name: Haml Linting
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches-ignore:
|
||||||
|
- 'dependabot/**'
|
||||||
|
paths:
|
||||||
|
- '.github/workflows/haml-lint-problem-matcher.json'
|
||||||
|
- '.github/workflows/lint-haml.yml'
|
||||||
|
- '.haml-lint*.yml'
|
||||||
|
- '.rubocop*.yml'
|
||||||
|
- '.ruby-version'
|
||||||
|
- '**/*.haml'
|
||||||
|
- 'Gemfile*'
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- '.github/workflows/haml-lint-problem-matcher.json'
|
||||||
|
- '.github/workflows/lint-haml.yml'
|
||||||
|
- '.haml-lint*.yml'
|
||||||
|
- '.rubocop*.yml'
|
||||||
|
- '.ruby-version'
|
||||||
|
- '**/*.haml'
|
||||||
|
- 'Gemfile*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Clone repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install native Ruby dependencies
|
||||||
|
run: sudo apt-get install -y libicu-dev libidn11-dev
|
||||||
|
|
||||||
|
- name: Set up Ruby
|
||||||
|
uses: ruby/setup-ruby@v1
|
||||||
|
with:
|
||||||
|
ruby-version: .ruby-version
|
||||||
|
bundler-cache: true
|
||||||
|
|
||||||
|
- name: Run haml-lint
|
||||||
|
run: |
|
||||||
|
echo "::add-matcher::.github/workflows/haml-lint-problem-matcher.json"
|
||||||
|
bundle exec haml-lint
|
6
.github/workflows/rebase-needed.yml
vendored
6
.github/workflows/rebase-needed.yml
vendored
|
@ -2,7 +2,13 @@ name: PR Needs Rebase
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches-ignore:
|
||||||
|
- 'dependabot/**'
|
||||||
|
- 'l10n_main'
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
|
branches-ignore:
|
||||||
|
- 'dependabot/**'
|
||||||
|
- 'l10n_main'
|
||||||
types: [synchronize]
|
types: [synchronize]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
|
|
@ -25,7 +25,7 @@ jobs:
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:14.5
|
image: postgres:14-alpine
|
||||||
env:
|
env:
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
|
@ -38,7 +38,7 @@ jobs:
|
||||||
- 5432:5432
|
- 5432:5432
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: redis:7.0
|
image: redis:7-alpine
|
||||||
options: >-
|
options: >-
|
||||||
--health-cmd "redis-cli ping"
|
--health-cmd "redis-cli ping"
|
||||||
--health-interval 10s
|
--health-interval 10s
|
||||||
|
|
|
@ -25,7 +25,7 @@ jobs:
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:14.5
|
image: postgres:14-alpine
|
||||||
env:
|
env:
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
|
@ -37,7 +37,7 @@ jobs:
|
||||||
ports:
|
ports:
|
||||||
- 5432:5432
|
- 5432:5432
|
||||||
redis:
|
redis:
|
||||||
image: redis:7.0
|
image: redis:7-alpine
|
||||||
options: >-
|
options: >-
|
||||||
--health-cmd "redis-cli ping"
|
--health-cmd "redis-cli ping"
|
||||||
--health-interval 10s
|
--health-interval 10s
|
||||||
|
|
141
.github/workflows/test-ruby.yml
vendored
Normal file
141
.github/workflows/test-ruby.yml
vendored
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
name: Ruby Testing
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches-ignore:
|
||||||
|
- 'dependabot/**'
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
env:
|
||||||
|
BUNDLE_CLEAN: true
|
||||||
|
BUNDLE_FROZEN: true
|
||||||
|
BUNDLE_WITHOUT: 'development production'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
env:
|
||||||
|
RAILS_ENV: test
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
cache: yarn
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
|
|
||||||
|
- name: Install native Ruby dependencies
|
||||||
|
run: sudo apt-get install -y libicu-dev libidn11-dev
|
||||||
|
|
||||||
|
- name: Set up bundler cache
|
||||||
|
uses: ruby/setup-ruby@v1
|
||||||
|
with:
|
||||||
|
ruby-version: .ruby-version
|
||||||
|
bundler-cache: true
|
||||||
|
|
||||||
|
- run: yarn install --frozen-lockfile
|
||||||
|
- name: Precompile assets
|
||||||
|
# Previously had set this, but it's not supported
|
||||||
|
# export NODE_OPTIONS=--openssl-legacy-provider
|
||||||
|
run: |-
|
||||||
|
./bin/rails assets:precompile
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
path: |-
|
||||||
|
./public/assets
|
||||||
|
./public/packs-test
|
||||||
|
name: ${{ github.sha }}
|
||||||
|
retention-days: 0
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
needs:
|
||||||
|
- build
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:14-alpine
|
||||||
|
env:
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
options: >-
|
||||||
|
--health-cmd pg_isready
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
options: >-
|
||||||
|
--health-cmd "redis-cli ping"
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
ports:
|
||||||
|
- 6379:6379
|
||||||
|
|
||||||
|
env:
|
||||||
|
DB_HOST: localhost
|
||||||
|
DB_USER: postgres
|
||||||
|
DB_PASS: postgres
|
||||||
|
DISABLE_SIMPLECOV: true
|
||||||
|
RAILS_ENV: test
|
||||||
|
ALLOW_NOPAM: true
|
||||||
|
PAM_ENABLED: true
|
||||||
|
PAM_DEFAULT_SERVICE: pam_test
|
||||||
|
PAM_CONTROLLED_SERVICE: pam_test_controlled
|
||||||
|
BUNDLE_WITH: 'pam_authentication'
|
||||||
|
CI_JOBS: ${{ matrix.ci_job }}/4
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
ruby-version:
|
||||||
|
- '2.7'
|
||||||
|
- '3.0'
|
||||||
|
- '3.1'
|
||||||
|
- '.ruby-version'
|
||||||
|
ci_job:
|
||||||
|
- 1
|
||||||
|
- 2
|
||||||
|
- 3
|
||||||
|
- 4
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
path: './public'
|
||||||
|
name: ${{ github.sha }}
|
||||||
|
|
||||||
|
- name: Install native Ruby dependencies
|
||||||
|
run: sudo apt-get install -y libicu-dev libidn11-dev
|
||||||
|
|
||||||
|
- name: Install additional system dependencies
|
||||||
|
run: sudo apt-get install -y ffmpeg imagemagick libpam-dev
|
||||||
|
|
||||||
|
- name: Set up bundler cache
|
||||||
|
uses: ruby/setup-ruby@v1
|
||||||
|
with:
|
||||||
|
ruby-version: ${{ matrix.ruby-version}}
|
||||||
|
bundler-cache: true
|
||||||
|
|
||||||
|
- name: Update system gems
|
||||||
|
if: matrix.ruby-version == '2.7'
|
||||||
|
run: gem update --system
|
||||||
|
|
||||||
|
- name: Load database schema
|
||||||
|
run: './bin/rails db:create db:schema:load db:seed'
|
||||||
|
|
||||||
|
- run: bundle exec rake rspec_chunked
|
103
.haml-lint.yml
103
.haml-lint.yml
|
@ -1,108 +1,9 @@
|
||||||
# Whether to ignore frontmatter at the beginning of HAML documents for
|
inherits_from: .haml-lint_todo.yml
|
||||||
# frameworks such as Jekyll/Middleman
|
|
||||||
skip_frontmatter: false
|
|
||||||
|
|
||||||
exclude:
|
exclude:
|
||||||
- 'vendor/**/*'
|
- 'vendor/**/*'
|
||||||
- 'spec/**/*'
|
- lib/templates/haml/scaffold/_form.html.haml
|
||||||
- 'lib/templates/**/*'
|
|
||||||
- 'app/views/kaminari/**/*'
|
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
AltText:
|
AltText:
|
||||||
enabled: false
|
|
||||||
|
|
||||||
ClassAttributeWithStaticValue:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
ClassesBeforeIds:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
ConsecutiveComments:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
ConsecutiveSilentScripts:
|
|
||||||
enabled: true
|
|
||||||
max_consecutive: 2
|
|
||||||
|
|
||||||
EmptyObjectReference:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
EmptyScript:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
FinalNewline:
|
|
||||||
enabled: true
|
|
||||||
present: true
|
|
||||||
|
|
||||||
HtmlAttributes:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
ImplicitDiv:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
LeadingCommentSpace:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
LineLength:
|
|
||||||
enabled: false
|
|
||||||
max: 80
|
|
||||||
|
|
||||||
MultilinePipe:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
MultilineScript:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
ObjectReferenceAttributes:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
RuboCop:
|
|
||||||
enabled: true
|
|
||||||
# These cops are incredibly noisy when it comes to HAML templates, so we
|
|
||||||
# ignore them.
|
|
||||||
ignored_cops:
|
|
||||||
- Lint/BlockAlignment
|
|
||||||
- Lint/EndAlignment
|
|
||||||
- Lint/Void
|
|
||||||
- Metrics/BlockLength
|
|
||||||
- Metrics/LineLength
|
|
||||||
- Style/AlignParameters
|
|
||||||
- Style/BlockNesting
|
|
||||||
- Style/ElseAlignment
|
|
||||||
- Style/EndOfLine
|
|
||||||
- Style/FileName
|
|
||||||
- Style/FinalNewline
|
|
||||||
- Style/FrozenStringLiteralComment
|
|
||||||
- Style/IfUnlessModifier
|
|
||||||
- Style/IndentationWidth
|
|
||||||
- Style/Next
|
|
||||||
- Style/TrailingBlankLines
|
|
||||||
- Style/TrailingWhitespace
|
|
||||||
- Style/WhileUntilModifier
|
|
||||||
|
|
||||||
RubyComments:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
SpaceBeforeScript:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
SpaceInsideHashAttributes:
|
|
||||||
enabled: true
|
|
||||||
style: space
|
|
||||||
|
|
||||||
Indentation:
|
|
||||||
enabled: true
|
|
||||||
character: space # or tab
|
|
||||||
|
|
||||||
TagName:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
TrailingWhitespace:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
UnnecessaryInterpolation:
|
|
||||||
enabled: true
|
|
||||||
|
|
||||||
UnnecessaryStringOutput:
|
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
106
.haml-lint_todo.yml
Normal file
106
.haml-lint_todo.yml
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
# This configuration was generated by
|
||||||
|
# `haml-lint --auto-gen-config`
|
||||||
|
# on 2023-03-15 00:55:01 -0400 using Haml-Lint version 0.45.0.
|
||||||
|
# The point is for the user to remove these configuration records
|
||||||
|
# one by one as the lints are removed from the code base.
|
||||||
|
# Note that changes in the inspected code, or installation of new
|
||||||
|
# versions of Haml-Lint, may require this file to be generated again.
|
||||||
|
|
||||||
|
linters:
|
||||||
|
# Offense count: 63
|
||||||
|
RuboCop:
|
||||||
|
exclude:
|
||||||
|
- 'app/views/accounts/_og.html.haml'
|
||||||
|
- 'app/views/admin/account_warnings/_account_warning.html.haml'
|
||||||
|
- 'app/views/admin/accounts/index.html.haml'
|
||||||
|
- 'app/views/admin/accounts/show.html.haml'
|
||||||
|
- 'app/views/admin/announcements/edit.html.haml'
|
||||||
|
- 'app/views/admin/announcements/new.html.haml'
|
||||||
|
- 'app/views/admin/disputes/appeals/_appeal.html.haml'
|
||||||
|
- 'app/views/admin/domain_blocks/edit.html.haml'
|
||||||
|
- 'app/views/admin/domain_blocks/new.html.haml'
|
||||||
|
- 'app/views/admin/ip_blocks/new.html.haml'
|
||||||
|
- 'app/views/admin/reports/actions/preview.html.haml'
|
||||||
|
- 'app/views/admin/reports/index.html.haml'
|
||||||
|
- 'app/views/admin/reports/show.html.haml'
|
||||||
|
- 'app/views/admin/roles/_form.html.haml'
|
||||||
|
- 'app/views/admin/settings/about/show.html.haml'
|
||||||
|
- 'app/views/admin/settings/appearance/show.html.haml'
|
||||||
|
- 'app/views/admin/settings/registrations/show.html.haml'
|
||||||
|
- 'app/views/admin/statuses/show.html.haml'
|
||||||
|
- 'app/views/auth/registrations/new.html.haml'
|
||||||
|
- 'app/views/disputes/strikes/show.html.haml'
|
||||||
|
- 'app/views/filters/_filter_fields.html.haml'
|
||||||
|
- 'app/views/invites/_form.html.haml'
|
||||||
|
- 'app/views/layouts/application.html.haml'
|
||||||
|
- 'app/views/layouts/error.html.haml'
|
||||||
|
- 'app/views/notification_mailer/_status.html.haml'
|
||||||
|
- 'app/views/settings/applications/_fields.html.haml'
|
||||||
|
- 'app/views/settings/imports/show.html.haml'
|
||||||
|
- 'app/views/settings/preferences/appearance/show.html.haml'
|
||||||
|
- 'app/views/settings/preferences/other/show.html.haml'
|
||||||
|
- 'app/views/statuses/_detailed_status.html.haml'
|
||||||
|
- 'app/views/statuses/_poll.html.haml'
|
||||||
|
- 'app/views/statuses/show.html.haml'
|
||||||
|
- 'app/views/statuses_cleanup/show.html.haml'
|
||||||
|
- 'app/views/user_mailer/warning.html.haml'
|
||||||
|
|
||||||
|
# Offense count: 913
|
||||||
|
LineLength:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
# Offense count: 22
|
||||||
|
UnnecessaryStringOutput:
|
||||||
|
exclude:
|
||||||
|
- 'app/views/accounts/show.html.haml'
|
||||||
|
- 'app/views/admin/custom_emojis/_custom_emoji.html.haml'
|
||||||
|
- 'app/views/admin/relays/_relay.html.haml'
|
||||||
|
- 'app/views/admin/rules/_rule.html.haml'
|
||||||
|
- 'app/views/admin/statuses/index.html.haml'
|
||||||
|
- 'app/views/auth/registrations/_sessions.html.haml'
|
||||||
|
- 'app/views/disputes/strikes/show.html.haml'
|
||||||
|
- 'app/views/notification_mailer/_status.html.haml'
|
||||||
|
- 'app/views/settings/two_factor_authentication_methods/index.html.haml'
|
||||||
|
- 'app/views/statuses/_detailed_status.html.haml'
|
||||||
|
- 'app/views/statuses/_poll.html.haml'
|
||||||
|
- 'app/views/statuses/_simple_status.html.haml'
|
||||||
|
- 'app/views/user_mailer/suspicious_sign_in.html.haml'
|
||||||
|
- 'app/views/user_mailer/webauthn_credential_added.html.haml'
|
||||||
|
- 'app/views/user_mailer/webauthn_credential_deleted.html.haml'
|
||||||
|
- 'app/views/user_mailer/welcome.html.haml'
|
||||||
|
|
||||||
|
# Offense count: 3
|
||||||
|
ViewLength:
|
||||||
|
exclude:
|
||||||
|
- 'app/views/admin/accounts/show.html.haml'
|
||||||
|
- 'app/views/admin/reports/show.html.haml'
|
||||||
|
- 'app/views/disputes/strikes/show.html.haml'
|
||||||
|
|
||||||
|
# Offense count: 41
|
||||||
|
InstanceVariables:
|
||||||
|
exclude:
|
||||||
|
- 'app/views/admin/reports/_actions.html.haml'
|
||||||
|
- 'app/views/admin/roles/_form.html.haml'
|
||||||
|
- 'app/views/admin/webhooks/_form.html.haml'
|
||||||
|
- 'app/views/auth/registrations/_sessions.html.haml'
|
||||||
|
- 'app/views/auth/registrations/_status.html.haml'
|
||||||
|
- 'app/views/auth/sessions/two_factor/_otp_authentication_form.html.haml'
|
||||||
|
- 'app/views/authorize_interactions/_post_follow_actions.html.haml'
|
||||||
|
- 'app/views/invites/_form.html.haml'
|
||||||
|
- 'app/views/relationships/_account.html.haml'
|
||||||
|
- 'app/views/shared/_og.html.haml'
|
||||||
|
- 'app/views/statuses/_status.html.haml'
|
||||||
|
|
||||||
|
# Offense count: 6
|
||||||
|
ConsecutiveSilentScripts:
|
||||||
|
exclude:
|
||||||
|
- 'app/views/admin/settings/shared/_links.html.haml'
|
||||||
|
- 'app/views/settings/login_activities/_login_activity.html.haml'
|
||||||
|
- 'app/views/statuses/_poll.html.haml'
|
||||||
|
|
||||||
|
# Offense count: 3
|
||||||
|
IdNames:
|
||||||
|
exclude:
|
||||||
|
- 'app/views/authorize_interactions/error.html.haml'
|
||||||
|
- 'app/views/oauth/authorizations/error.html.haml'
|
||||||
|
- 'app/views/shared/_error_messages.html.haml'
|
47
.rubocop.yml
47
.rubocop.yml
|
@ -8,6 +8,7 @@ require:
|
||||||
- rubocop-rails
|
- rubocop-rails
|
||||||
- rubocop-rspec
|
- rubocop-rspec
|
||||||
- rubocop-performance
|
- rubocop-performance
|
||||||
|
- rubocop-capybara
|
||||||
|
|
||||||
AllCops:
|
AllCops:
|
||||||
TargetRubyVersion: 2.7
|
TargetRubyVersion: 2.7
|
||||||
|
@ -19,8 +20,6 @@ AllCops:
|
||||||
NewCops: enable
|
NewCops: enable
|
||||||
Exclude:
|
Exclude:
|
||||||
- db/schema.rb
|
- db/schema.rb
|
||||||
- 'app/views/**/*'
|
|
||||||
- 'config/**/*'
|
|
||||||
- 'bin/*'
|
- 'bin/*'
|
||||||
- 'Rakefile'
|
- 'Rakefile'
|
||||||
- 'node_modules/**/*'
|
- 'node_modules/**/*'
|
||||||
|
@ -33,7 +32,6 @@ Layout/FirstHashElementIndentation:
|
||||||
EnforcedStyle: consistent
|
EnforcedStyle: consistent
|
||||||
|
|
||||||
Layout/LineLength:
|
Layout/LineLength:
|
||||||
Max: 140 # RuboCop default 120
|
|
||||||
AllowedPatterns:
|
AllowedPatterns:
|
||||||
# Allow comments to be long lines
|
# Allow comments to be long lines
|
||||||
- !ruby/regexp / \# .*$/
|
- !ruby/regexp / \# .*$/
|
||||||
|
@ -48,13 +46,11 @@ Lint/UselessAccessModifier:
|
||||||
- class_methods
|
- class_methods
|
||||||
|
|
||||||
Metrics/AbcSize:
|
Metrics/AbcSize:
|
||||||
Max: 34 # RuboCop default 17
|
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'lib/**/*cli*.rb'
|
- 'lib/**/*cli*.rb'
|
||||||
- db/*migrate/**/*
|
- db/*migrate/**/*
|
||||||
|
|
||||||
Metrics/BlockLength:
|
Metrics/BlockLength:
|
||||||
Max: 55 # Default 25
|
|
||||||
CountAsOne: [array, heredoc]
|
CountAsOne: [array, heredoc]
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'lib/mastodon/*_cli.rb'
|
- 'lib/mastodon/*_cli.rb'
|
||||||
|
@ -64,30 +60,23 @@ Metrics/BlockNesting:
|
||||||
- 'lib/mastodon/*_cli.rb'
|
- 'lib/mastodon/*_cli.rb'
|
||||||
|
|
||||||
Metrics/ClassLength:
|
Metrics/ClassLength:
|
||||||
Max: 500 # Default 100
|
|
||||||
CountAsOne: [array, heredoc]
|
CountAsOne: [array, heredoc]
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'lib/mastodon/*_cli.rb'
|
- 'lib/mastodon/*_cli.rb'
|
||||||
|
|
||||||
Metrics/CyclomaticComplexity:
|
Metrics/CyclomaticComplexity:
|
||||||
Max: 12 # Default 7
|
|
||||||
Exclude:
|
Exclude:
|
||||||
- lib/mastodon/*cli*.rb
|
- lib/mastodon/*cli*.rb
|
||||||
- db/*migrate/**/*
|
- db/*migrate/**/*
|
||||||
|
|
||||||
Metrics/MethodLength:
|
Metrics/MethodLength:
|
||||||
Max: 25 # RuboCop default 10
|
|
||||||
CountAsOne: [array, heredoc]
|
CountAsOne: [array, heredoc]
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'lib/mastodon/*_cli.rb'
|
- 'lib/mastodon/*_cli.rb'
|
||||||
|
|
||||||
Metrics/ModuleLength:
|
Metrics/ModuleLength:
|
||||||
Max: 200 # Default 100
|
|
||||||
CountAsOne: [array, heredoc]
|
CountAsOne: [array, heredoc]
|
||||||
|
|
||||||
Metrics/PerceivedComplexity:
|
|
||||||
Max: 16 # RuboCop default 8
|
|
||||||
|
|
||||||
Rails/HttpStatus:
|
Rails/HttpStatus:
|
||||||
EnforcedStyle: numeric
|
EnforcedStyle: numeric
|
||||||
|
|
||||||
|
@ -97,12 +86,43 @@ Rails/Exit:
|
||||||
- 'lib/mastodon/cli_helper.rb'
|
- 'lib/mastodon/cli_helper.rb'
|
||||||
- 'lib/cli.rb'
|
- 'lib/cli.rb'
|
||||||
|
|
||||||
|
# Reason: Some single letter camel case files shouldn't be split
|
||||||
|
# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecfilepath
|
||||||
|
RSpec/FilePath:
|
||||||
|
CustomTransform:
|
||||||
|
ActivityPub: activitypub # Ignore the snake_case due to the amount of files to rename
|
||||||
|
DeepL: deepl
|
||||||
|
FetchOEmbedService: fetch_oembed_service
|
||||||
|
JsonLdHelper: jsonld_helper
|
||||||
|
OEmbedController: oembed_controller
|
||||||
|
OStatus: ostatus
|
||||||
|
NodeInfoController: nodeinfo_controller # NodeInfo isn't snake_cased for any of the instances
|
||||||
|
Exclude:
|
||||||
|
- 'spec/config/initializers/rack_attack_spec.rb' # namespaces usually have separate folder
|
||||||
|
- 'spec/lib/sanitize_config_spec.rb' # namespaces usually have separate folder
|
||||||
|
- 'spec/controllers/concerns/account_controller_concern_spec.rb' # Concerns describe ApplicationController and don't fit naming
|
||||||
|
- 'spec/controllers/concerns/export_controller_concern_spec.rb'
|
||||||
|
- 'spec/controllers/concerns/localized_spec.rb'
|
||||||
|
- 'spec/controllers/concerns/rate_limit_headers_spec.rb'
|
||||||
|
- 'spec/controllers/concerns/signature_verification_spec.rb'
|
||||||
|
- 'spec/controllers/concerns/user_tracking_concern_spec.rb'
|
||||||
|
|
||||||
RSpec/NotToNot:
|
RSpec/NotToNot:
|
||||||
EnforcedStyle: to_not
|
EnforcedStyle: to_not
|
||||||
|
|
||||||
RSpec/Rails/HttpStatus:
|
RSpec/Rails/HttpStatus:
|
||||||
EnforcedStyle: numeric
|
EnforcedStyle: numeric
|
||||||
|
|
||||||
|
# Reason:
|
||||||
|
# https://docs.rubocop.org/rubocop/cops_style.html#styleclassandmodulechildren
|
||||||
|
Style/ClassAndModuleChildren:
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
|
# Reason: Classes mostly self-document with their names
|
||||||
|
# https://docs.rubocop.org/rubocop/cops_style.html#styledocumentation
|
||||||
|
Style/Documentation:
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
Style/HashSyntax:
|
Style/HashSyntax:
|
||||||
EnforcedStyle: ruby19_no_mixed_keys
|
EnforcedStyle: ruby19_no_mixed_keys
|
||||||
|
|
||||||
|
@ -123,3 +143,6 @@ Style/TrailingCommaInArrayLiteral:
|
||||||
|
|
||||||
Style/TrailingCommaInHashLiteral:
|
Style/TrailingCommaInHashLiteral:
|
||||||
EnforcedStyleForMultiline: 'comma'
|
EnforcedStyleForMultiline: 'comma'
|
||||||
|
|
||||||
|
Style/SymbolArray:
|
||||||
|
Enabled: false
|
||||||
|
|
1179
.rubocop_todo.yml
1179
.rubocop_todo.yml
File diff suppressed because it is too large
Load diff
51
CHANGELOG.md
51
CHANGELOG.md
|
@ -2,6 +2,57 @@
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [4.1.1] - 2023-03-16
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add redirection from paths with url-encoded `@` to their decoded form ([thijskh](https://github.com/mastodon/mastodon/pull/23593))
|
||||||
|
- Add `lang` attribute to native language names in language picker in Web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23749))
|
||||||
|
- Add headers to outgoing mails to avoid auto-replies ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23597))
|
||||||
|
- Add support for refreshing many accounts at once with `tootctl accounts refresh` ([9p4](https://github.com/mastodon/mastodon/pull/23304))
|
||||||
|
- Add confirmation modal when clicking to edit a post with a non-empty compose form ([PauloVilarinho](https://github.com/mastodon/mastodon/pull/23936))
|
||||||
|
- Add support for the HAproxy PROXY protocol through the `PROXY_PROTO_V1` environment variable ([CSDUMMI](https://github.com/mastodon/mastodon/pull/24064))
|
||||||
|
- Add `SENDFILE_HEADER` environment variable ([Gargron](https://github.com/mastodon/mastodon/pull/24123))
|
||||||
|
- Add cache headers to static files served through Rails ([Gargron](https://github.com/mastodon/mastodon/pull/24120))
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Increase contrast of upload progress bar background ([toolmantim](https://github.com/mastodon/mastodon/pull/23836))
|
||||||
|
- Change post auto-deletion throttling constants to better scale with server size ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23320))
|
||||||
|
- Change order of bookmark and favourite sidebar entries in single-column UI for consistency ([TerryGarcia](https://github.com/mastodon/mastodon/pull/23701))
|
||||||
|
- Change `ActivityPub::DeliveryWorker` retries to be spread out more ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21956))
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix “Remove all followers from the selected domains” also removing follows and notifications ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23805))
|
||||||
|
- Fix streaming metrics format ([emilweth](https://github.com/mastodon/mastodon/pull/23519), [emilweth](https://github.com/mastodon/mastodon/pull/23520))
|
||||||
|
- Fix case-sensitive check for previously used hashtags in hashtag autocompletion ([deanveloper](https://github.com/mastodon/mastodon/pull/23526))
|
||||||
|
- Fix focus point of already-attached media not saving after edit ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23566))
|
||||||
|
- Fix sidebar behavior in settings/admin UI on mobile ([wxt2005](https://github.com/mastodon/mastodon/pull/23764))
|
||||||
|
- Fix inefficiency when searching accounts per username in admin interface ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23801))
|
||||||
|
- Fix duplicate “Publish” button on mobile ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23804))
|
||||||
|
- Fix server error when failing to follow back followers from `/relationships` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23787))
|
||||||
|
- Fix server error when attempting to display the edit history of a trendable post in the admin interface ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23574))
|
||||||
|
- Fix `tootctl accounts migrate` crashing because of a typo ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23567))
|
||||||
|
- Fix original account being unfollowed on migration before the follow request to the new account could be sent ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21957))
|
||||||
|
- Fix the “Back” button in column headers sometimes leaving Mastodon ([c960657](https://github.com/mastodon/mastodon/pull/23953))
|
||||||
|
- Fix pgBouncer resetting application name on every transaction ([Gargron](https://github.com/mastodon/mastodon/pull/23958))
|
||||||
|
- Fix unconfirmed accounts being counted as active users ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23803))
|
||||||
|
- Fix `/api/v1/streaming` sub-paths not being redirected ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23988))
|
||||||
|
- Fix drag'n'drop upload area text that spans multiple lines not being centered ([vintprox](https://github.com/mastodon/mastodon/pull/24029))
|
||||||
|
- Fix sidekiq jobs not triggering Elasticsearch index updates ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24046))
|
||||||
|
- Fix tags being unnecessarily stripped from plain-text short site description ([c960657](https://github.com/mastodon/mastodon/pull/23975))
|
||||||
|
- Fix HTML entities not being un-escaped in extracted plain-text from remote posts ([c960657](https://github.com/mastodon/mastodon/pull/24019))
|
||||||
|
- Fix dashboard crash on ElasticSearch server error ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23751))
|
||||||
|
- Fix incorrect post links in strikes when the account is remote ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23611))
|
||||||
|
- Fix misleading error code when receiving invalid WebAuthn credentials ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23568))
|
||||||
|
- Fix duplicate mails being sent when the SMTP server is too slow to close the connection ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23750))
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Change user backups to use expiring URLs for download when possible ([Gargron](https://github.com/mastodon/mastodon/pull/24136))
|
||||||
|
- Add warning for object storage misconfiguration ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24137))
|
||||||
|
|
||||||
## [4.1.0] - 2023-02-10
|
## [4.1.0] - 2023-02-10
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -80,8 +80,6 @@ It is not always possible to phrase every change in such a manner, but it is des
|
||||||
- Code style rules (rubocop, eslint)
|
- Code style rules (rubocop, eslint)
|
||||||
- Normalization of locale files (i18n-tasks)
|
- Normalization of locale files (i18n-tasks)
|
||||||
|
|
||||||
**Note**: You may need to log in and authorise the GitHub account your fork of this repository belongs to with CircleCI to enable some of the automated checks to run.
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
The [Mastodon documentation](https://docs.joinmastodon.org) is a statically generated site. You can [submit merge requests to mastodon/documentation](https://github.com/mastodon/documentation).
|
The [Mastodon documentation](https://docs.joinmastodon.org) is a statically generated site. You can [submit merge requests to mastodon/documentation](https://github.com/mastodon/documentation).
|
||||||
|
|
21
Gemfile
21
Gemfile
|
@ -4,9 +4,8 @@ source 'https://rubygems.org'
|
||||||
ruby '>= 2.7.0', '< 3.3.0'
|
ruby '>= 2.7.0', '< 3.3.0'
|
||||||
|
|
||||||
gem 'pkg-config', '~> 1.5'
|
gem 'pkg-config', '~> 1.5'
|
||||||
gem 'rexml', '~> 3.2'
|
|
||||||
|
|
||||||
gem 'puma', '~> 5.6'
|
gem 'puma', '~> 6.1'
|
||||||
gem 'rails', '~> 6.1.7'
|
gem 'rails', '~> 6.1.7'
|
||||||
gem 'sprockets', '~> 3.7.2'
|
gem 'sprockets', '~> 3.7.2'
|
||||||
gem 'thor', '~> 1.2'
|
gem 'thor', '~> 1.2'
|
||||||
|
@ -40,7 +39,7 @@ end
|
||||||
gem 'net-ldap', '~> 0.17'
|
gem 'net-ldap', '~> 0.17'
|
||||||
gem 'omniauth-cas', '~> 2.0'
|
gem 'omniauth-cas', '~> 2.0'
|
||||||
gem 'omniauth-saml', '~> 1.10'
|
gem 'omniauth-saml', '~> 1.10'
|
||||||
gem 'omniauth_openid_connect', '~> 0.6.0'
|
gem 'omniauth_openid_connect', '~> 0.6.1'
|
||||||
gem 'omniauth', '~> 1.9'
|
gem 'omniauth', '~> 1.9'
|
||||||
gem 'omniauth-rails_csrf_protection', '~> 0.1'
|
gem 'omniauth-rails_csrf_protection', '~> 0.1'
|
||||||
|
|
||||||
|
@ -70,7 +69,7 @@ gem 'public_suffix', '~> 5.0'
|
||||||
gem 'pundit', '~> 2.3'
|
gem 'pundit', '~> 2.3'
|
||||||
gem 'premailer-rails'
|
gem 'premailer-rails'
|
||||||
gem 'rack-attack', '~> 6.6'
|
gem 'rack-attack', '~> 6.6'
|
||||||
gem 'rack-cors', '~> 1.1', require: 'rack/cors'
|
gem 'rack-cors', '~> 2.0', require: 'rack/cors'
|
||||||
gem 'rails-i18n', '~> 6.0'
|
gem 'rails-i18n', '~> 6.0'
|
||||||
gem 'rails-settings-cached', '~> 0.6', git: 'https://github.com/mastodon/rails-settings-cached.git', branch: 'v0.6.6-aliases-true'
|
gem 'rails-settings-cached', '~> 0.6', git: 'https://github.com/mastodon/rails-settings-cached.git', branch: 'v0.6.6-aliases-true'
|
||||||
gem 'redcarpet', '~> 3.6'
|
gem 'redcarpet', '~> 3.6'
|
||||||
|
@ -104,9 +103,10 @@ group :development, :test do
|
||||||
gem 'fabrication', '~> 2.30'
|
gem 'fabrication', '~> 2.30'
|
||||||
gem 'fuubar', '~> 2.5'
|
gem 'fuubar', '~> 2.5'
|
||||||
gem 'i18n-tasks', '~> 1.0', require: false
|
gem 'i18n-tasks', '~> 1.0', require: false
|
||||||
gem 'pry-byebug', '~> 3.10'
|
gem 'rspec-rails', '~> 6.0'
|
||||||
gem 'pry-rails', '~> 0.3'
|
gem 'rspec_chunked', '~> 0.6'
|
||||||
gem 'rspec-rails', '~> 5.1'
|
|
||||||
|
gem 'rubocop-capybara', require: false
|
||||||
gem 'rubocop-performance', require: false
|
gem 'rubocop-performance', require: false
|
||||||
gem 'rubocop-rails', require: false
|
gem 'rubocop-rails', require: false
|
||||||
gem 'rubocop-rspec', require: false
|
gem 'rubocop-rspec', require: false
|
||||||
|
@ -119,10 +119,10 @@ end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
gem 'capybara', '~> 3.38'
|
gem 'capybara', '~> 3.38'
|
||||||
gem 'climate_control', '~> 0.2'
|
gem 'climate_control'
|
||||||
gem 'faker', '~> 3.1'
|
gem 'faker', '~> 3.1'
|
||||||
gem 'json-schema', '~> 3.0'
|
gem 'json-schema', '~> 3.0'
|
||||||
gem 'rack-test', '~> 2.0'
|
gem 'rack-test', '~> 2.1'
|
||||||
gem 'rails-controller-testing', '~> 1.0'
|
gem 'rails-controller-testing', '~> 1.0'
|
||||||
gem 'rspec_junit_formatter', '~> 0.6'
|
gem 'rspec_junit_formatter', '~> 0.6'
|
||||||
gem 'rspec-sidekiq', '~> 3.1'
|
gem 'rspec-sidekiq', '~> 3.1'
|
||||||
|
@ -131,16 +131,15 @@ group :test do
|
||||||
end
|
end
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
gem 'active_record_query_trace', '~> 1.8'
|
|
||||||
gem 'annotate', '~> 3.2'
|
gem 'annotate', '~> 3.2'
|
||||||
gem 'better_errors', '~> 2.9'
|
gem 'better_errors', '~> 2.9'
|
||||||
gem 'binding_of_caller', '~> 1.0'
|
gem 'binding_of_caller', '~> 1.0'
|
||||||
gem 'bullet', '~> 7.0'
|
|
||||||
gem 'letter_opener', '~> 1.8'
|
gem 'letter_opener', '~> 1.8'
|
||||||
gem 'letter_opener_web', '~> 2.0'
|
gem 'letter_opener_web', '~> 2.0'
|
||||||
gem 'memory_profiler'
|
gem 'memory_profiler'
|
||||||
gem 'brakeman', '~> 5.4', require: false
|
gem 'brakeman', '~> 5.4', require: false
|
||||||
gem 'bundler-audit', '~> 0.9', require: false
|
gem 'bundler-audit', '~> 0.9', require: false
|
||||||
|
gem 'haml_lint', require: false
|
||||||
|
|
||||||
gem 'capistrano', '~> 3.17'
|
gem 'capistrano', '~> 3.17'
|
||||||
gem 'capistrano-rails', '~> 1.6'
|
gem 'capistrano-rails', '~> 1.6'
|
||||||
|
|
232
Gemfile.lock
232
Gemfile.lock
|
@ -30,40 +30,40 @@ GIT
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actioncable (6.1.7.2)
|
actioncable (6.1.7.3)
|
||||||
actionpack (= 6.1.7.2)
|
actionpack (= 6.1.7.3)
|
||||||
activesupport (= 6.1.7.2)
|
activesupport (= 6.1.7.3)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
actionmailbox (6.1.7.2)
|
actionmailbox (6.1.7.3)
|
||||||
actionpack (= 6.1.7.2)
|
actionpack (= 6.1.7.3)
|
||||||
activejob (= 6.1.7.2)
|
activejob (= 6.1.7.3)
|
||||||
activerecord (= 6.1.7.2)
|
activerecord (= 6.1.7.3)
|
||||||
activestorage (= 6.1.7.2)
|
activestorage (= 6.1.7.3)
|
||||||
activesupport (= 6.1.7.2)
|
activesupport (= 6.1.7.3)
|
||||||
mail (>= 2.7.1)
|
mail (>= 2.7.1)
|
||||||
actionmailer (6.1.7.2)
|
actionmailer (6.1.7.3)
|
||||||
actionpack (= 6.1.7.2)
|
actionpack (= 6.1.7.3)
|
||||||
actionview (= 6.1.7.2)
|
actionview (= 6.1.7.3)
|
||||||
activejob (= 6.1.7.2)
|
activejob (= 6.1.7.3)
|
||||||
activesupport (= 6.1.7.2)
|
activesupport (= 6.1.7.3)
|
||||||
mail (~> 2.5, >= 2.5.4)
|
mail (~> 2.5, >= 2.5.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
actionpack (6.1.7.2)
|
actionpack (6.1.7.3)
|
||||||
actionview (= 6.1.7.2)
|
actionview (= 6.1.7.3)
|
||||||
activesupport (= 6.1.7.2)
|
activesupport (= 6.1.7.3)
|
||||||
rack (~> 2.0, >= 2.0.9)
|
rack (~> 2.0, >= 2.0.9)
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||||
actiontext (6.1.7.2)
|
actiontext (6.1.7.3)
|
||||||
actionpack (= 6.1.7.2)
|
actionpack (= 6.1.7.3)
|
||||||
activerecord (= 6.1.7.2)
|
activerecord (= 6.1.7.3)
|
||||||
activestorage (= 6.1.7.2)
|
activestorage (= 6.1.7.3)
|
||||||
activesupport (= 6.1.7.2)
|
activesupport (= 6.1.7.3)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (6.1.7.2)
|
actionview (6.1.7.3)
|
||||||
activesupport (= 6.1.7.2)
|
activesupport (= 6.1.7.3)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.4)
|
erubi (~> 1.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
|
@ -73,23 +73,22 @@ GEM
|
||||||
activemodel (>= 4.1, < 7.1)
|
activemodel (>= 4.1, < 7.1)
|
||||||
case_transform (>= 0.2)
|
case_transform (>= 0.2)
|
||||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||||
active_record_query_trace (1.8)
|
activejob (6.1.7.3)
|
||||||
activejob (6.1.7.2)
|
activesupport (= 6.1.7.3)
|
||||||
activesupport (= 6.1.7.2)
|
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (6.1.7.2)
|
activemodel (6.1.7.3)
|
||||||
activesupport (= 6.1.7.2)
|
activesupport (= 6.1.7.3)
|
||||||
activerecord (6.1.7.2)
|
activerecord (6.1.7.3)
|
||||||
activemodel (= 6.1.7.2)
|
activemodel (= 6.1.7.3)
|
||||||
activesupport (= 6.1.7.2)
|
activesupport (= 6.1.7.3)
|
||||||
activestorage (6.1.7.2)
|
activestorage (6.1.7.3)
|
||||||
actionpack (= 6.1.7.2)
|
actionpack (= 6.1.7.3)
|
||||||
activejob (= 6.1.7.2)
|
activejob (= 6.1.7.3)
|
||||||
activerecord (= 6.1.7.2)
|
activerecord (= 6.1.7.3)
|
||||||
activesupport (= 6.1.7.2)
|
activesupport (= 6.1.7.3)
|
||||||
marcel (~> 1.0)
|
marcel (~> 1.0)
|
||||||
mini_mime (>= 1.1.0)
|
mini_mime (>= 1.1.0)
|
||||||
activesupport (6.1.7.2)
|
activesupport (6.1.7.3)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
i18n (>= 1.6, < 2)
|
i18n (>= 1.6, < 2)
|
||||||
minitest (>= 5.1)
|
minitest (>= 5.1)
|
||||||
|
@ -144,18 +143,14 @@ GEM
|
||||||
bootsnap (1.16.0)
|
bootsnap (1.16.0)
|
||||||
msgpack (~> 1.2)
|
msgpack (~> 1.2)
|
||||||
brakeman (5.4.0)
|
brakeman (5.4.0)
|
||||||
browser (4.2.0)
|
browser (5.3.1)
|
||||||
brpoplpush-redis_script (0.1.3)
|
brpoplpush-redis_script (0.1.3)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.5)
|
concurrent-ruby (~> 1.0, >= 1.0.5)
|
||||||
redis (>= 1.0, < 6)
|
redis (>= 1.0, < 6)
|
||||||
builder (3.2.4)
|
builder (3.2.4)
|
||||||
bullet (7.0.7)
|
|
||||||
activesupport (>= 3.0.0)
|
|
||||||
uniform_notifier (~> 1.11)
|
|
||||||
bundler-audit (0.9.1)
|
bundler-audit (0.9.1)
|
||||||
bundler (>= 1.2.0, < 3)
|
bundler (>= 1.2.0, < 3)
|
||||||
thor (~> 1.0)
|
thor (~> 1.0)
|
||||||
byebug (11.1.3)
|
|
||||||
capistrano (3.17.2)
|
capistrano (3.17.2)
|
||||||
airbrussh (>= 1.0.0)
|
airbrussh (>= 1.0.0)
|
||||||
i18n
|
i18n
|
||||||
|
@ -193,7 +188,7 @@ GEM
|
||||||
cocoon (1.2.15)
|
cocoon (1.2.15)
|
||||||
coderay (1.1.3)
|
coderay (1.1.3)
|
||||||
color_diff (0.1)
|
color_diff (0.1)
|
||||||
concurrent-ruby (1.2.0)
|
concurrent-ruby (1.2.2)
|
||||||
connection_pool (2.3.0)
|
connection_pool (2.3.0)
|
||||||
cose (1.3.0)
|
cose (1.3.0)
|
||||||
cbor (~> 0.5.9)
|
cbor (~> 0.5.9)
|
||||||
|
@ -226,7 +221,7 @@ GEM
|
||||||
docile (1.4.0)
|
docile (1.4.0)
|
||||||
domain_name (0.5.20190701)
|
domain_name (0.5.20190701)
|
||||||
unf (>= 0.0.5, < 1.0.0)
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
doorkeeper (5.6.4)
|
doorkeeper (5.6.5)
|
||||||
railties (>= 5)
|
railties (>= 5)
|
||||||
dotenv (2.8.1)
|
dotenv (2.8.1)
|
||||||
dotenv-rails (2.8.1)
|
dotenv-rails (2.8.1)
|
||||||
|
@ -309,6 +304,12 @@ GEM
|
||||||
activesupport (>= 5.1)
|
activesupport (>= 5.1)
|
||||||
haml (>= 4.0.6)
|
haml (>= 4.0.6)
|
||||||
railties (>= 5.1)
|
railties (>= 5.1)
|
||||||
|
haml_lint (0.45.0)
|
||||||
|
haml (>= 4.0, < 6.2)
|
||||||
|
parallel (~> 1.10)
|
||||||
|
rainbow
|
||||||
|
rubocop (>= 0.50.0)
|
||||||
|
sysexits (~> 1.1)
|
||||||
hashdiff (1.0.1)
|
hashdiff (1.0.1)
|
||||||
hashie (5.0.0)
|
hashie (5.0.0)
|
||||||
hcaptcha (7.1.0)
|
hcaptcha (7.1.0)
|
||||||
|
@ -400,7 +401,7 @@ GEM
|
||||||
loofah (2.19.1)
|
loofah (2.19.1)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.5.9)
|
nokogiri (>= 1.5.9)
|
||||||
mail (2.8.0.1)
|
mail (2.8.1)
|
||||||
mini_mime (>= 0.1.1)
|
mini_mime (>= 0.1.1)
|
||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
|
@ -418,7 +419,7 @@ GEM
|
||||||
mime-types-data (3.2022.0105)
|
mime-types-data (3.2022.0105)
|
||||||
mini_mime (1.1.2)
|
mini_mime (1.1.2)
|
||||||
mini_portile2 (2.8.1)
|
mini_portile2 (2.8.1)
|
||||||
minitest (5.17.0)
|
minitest (5.18.0)
|
||||||
msgpack (1.6.0)
|
msgpack (1.6.0)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
multipart-post (2.3.0)
|
multipart-post (2.3.0)
|
||||||
|
@ -460,7 +461,7 @@ GEM
|
||||||
omniauth-saml (1.10.3)
|
omniauth-saml (1.10.3)
|
||||||
omniauth (~> 1.3, >= 1.3.2)
|
omniauth (~> 1.3, >= 1.3.2)
|
||||||
ruby-saml (~> 1.9)
|
ruby-saml (~> 1.9)
|
||||||
omniauth_openid_connect (0.6.0)
|
omniauth_openid_connect (0.6.1)
|
||||||
omniauth (>= 1.9, < 3)
|
omniauth (>= 1.9, < 3)
|
||||||
openid_connect (~> 1.1)
|
openid_connect (~> 1.1)
|
||||||
openid_connect (1.4.2)
|
openid_connect (1.4.2)
|
||||||
|
@ -480,13 +481,13 @@ GEM
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
ox (2.14.14)
|
ox (2.14.14)
|
||||||
parallel (1.22.1)
|
parallel (1.22.1)
|
||||||
parser (3.2.1.0)
|
parser (3.2.1.1)
|
||||||
ast (~> 2.4.1)
|
ast (~> 2.4.1)
|
||||||
parslet (2.0.0)
|
parslet (2.0.0)
|
||||||
pastel (0.8.0)
|
pastel (0.8.0)
|
||||||
tty-color (~> 0.5)
|
tty-color (~> 0.5)
|
||||||
pg (1.4.5)
|
pg (1.4.6)
|
||||||
pghero (3.1.0)
|
pghero (3.3.1)
|
||||||
activerecord (>= 6)
|
activerecord (>= 6)
|
||||||
pkg-config (1.5.1)
|
pkg-config (1.5.1)
|
||||||
posix-spawn (0.3.15)
|
posix-spawn (0.3.15)
|
||||||
|
@ -499,25 +500,17 @@ GEM
|
||||||
net-smtp
|
net-smtp
|
||||||
premailer (~> 1.7, >= 1.7.9)
|
premailer (~> 1.7, >= 1.7.9)
|
||||||
private_address_check (0.5.0)
|
private_address_check (0.5.0)
|
||||||
pry (0.14.1)
|
|
||||||
coderay (~> 1.1)
|
|
||||||
method_source (~> 1.0)
|
|
||||||
pry-byebug (3.10.1)
|
|
||||||
byebug (~> 11.0)
|
|
||||||
pry (>= 0.13, < 0.15)
|
|
||||||
pry-rails (0.3.9)
|
|
||||||
pry (>= 0.10.4)
|
|
||||||
public_suffix (5.0.1)
|
public_suffix (5.0.1)
|
||||||
puma (5.6.5)
|
puma (6.1.1)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
pundit (2.3.0)
|
pundit (2.3.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
raabro (1.4.0)
|
raabro (1.4.0)
|
||||||
racc (1.6.2)
|
racc (1.6.2)
|
||||||
rack (2.2.6.2)
|
rack (2.2.6.4)
|
||||||
rack-attack (6.6.1)
|
rack-attack (6.6.1)
|
||||||
rack (>= 1.0, < 3)
|
rack (>= 1.0, < 3)
|
||||||
rack-cors (1.1.1)
|
rack-cors (2.0.1)
|
||||||
rack (>= 2.0.0)
|
rack (>= 2.0.0)
|
||||||
rack-oauth2 (1.21.3)
|
rack-oauth2 (1.21.3)
|
||||||
activesupport
|
activesupport
|
||||||
|
@ -527,22 +520,22 @@ GEM
|
||||||
rack (>= 2.1.0)
|
rack (>= 2.1.0)
|
||||||
rack-proxy (0.7.6)
|
rack-proxy (0.7.6)
|
||||||
rack
|
rack
|
||||||
rack-test (2.0.2)
|
rack-test (2.1.0)
|
||||||
rack (>= 1.3)
|
rack (>= 1.3)
|
||||||
rails (6.1.7.2)
|
rails (6.1.7.3)
|
||||||
actioncable (= 6.1.7.2)
|
actioncable (= 6.1.7.3)
|
||||||
actionmailbox (= 6.1.7.2)
|
actionmailbox (= 6.1.7.3)
|
||||||
actionmailer (= 6.1.7.2)
|
actionmailer (= 6.1.7.3)
|
||||||
actionpack (= 6.1.7.2)
|
actionpack (= 6.1.7.3)
|
||||||
actiontext (= 6.1.7.2)
|
actiontext (= 6.1.7.3)
|
||||||
actionview (= 6.1.7.2)
|
actionview (= 6.1.7.3)
|
||||||
activejob (= 6.1.7.2)
|
activejob (= 6.1.7.3)
|
||||||
activemodel (= 6.1.7.2)
|
activemodel (= 6.1.7.3)
|
||||||
activerecord (= 6.1.7.2)
|
activerecord (= 6.1.7.3)
|
||||||
activestorage (= 6.1.7.2)
|
activestorage (= 6.1.7.3)
|
||||||
activesupport (= 6.1.7.2)
|
activesupport (= 6.1.7.3)
|
||||||
bundler (>= 1.15.0)
|
bundler (>= 1.15.0)
|
||||||
railties (= 6.1.7.2)
|
railties (= 6.1.7.3)
|
||||||
sprockets-rails (>= 2.0.0)
|
sprockets-rails (>= 2.0.0)
|
||||||
rails-controller-testing (1.0.5)
|
rails-controller-testing (1.0.5)
|
||||||
actionpack (>= 5.0.1.rc1)
|
actionpack (>= 5.0.1.rc1)
|
||||||
|
@ -556,9 +549,9 @@ GEM
|
||||||
rails-i18n (6.0.0)
|
rails-i18n (6.0.0)
|
||||||
i18n (>= 0.7, < 2)
|
i18n (>= 0.7, < 2)
|
||||||
railties (>= 6.0.0, < 7)
|
railties (>= 6.0.0, < 7)
|
||||||
railties (6.1.7.2)
|
railties (6.1.7.3)
|
||||||
actionpack (= 6.1.7.2)
|
actionpack (= 6.1.7.3)
|
||||||
activesupport (= 6.1.7.2)
|
activesupport (= 6.1.7.3)
|
||||||
method_source
|
method_source
|
||||||
rake (>= 12.2)
|
rake (>= 12.2)
|
||||||
thor (~> 1.0)
|
thor (~> 1.0)
|
||||||
|
@ -569,7 +562,7 @@ GEM
|
||||||
rdf-normalize (0.5.1)
|
rdf-normalize (0.5.1)
|
||||||
rdf (~> 3.2)
|
rdf (~> 3.2)
|
||||||
redcarpet (3.6.0)
|
redcarpet (3.6.0)
|
||||||
redis (4.5.1)
|
redis (4.8.1)
|
||||||
redis-namespace (1.10.0)
|
redis-namespace (1.10.0)
|
||||||
redis (>= 4)
|
redis (>= 4)
|
||||||
redlock (1.3.2)
|
redlock (1.3.2)
|
||||||
|
@ -587,53 +580,54 @@ GEM
|
||||||
chunky_png (~> 1.0)
|
chunky_png (~> 1.0)
|
||||||
rqrcode_core (~> 1.0)
|
rqrcode_core (~> 1.0)
|
||||||
rqrcode_core (1.2.0)
|
rqrcode_core (1.2.0)
|
||||||
rspec-core (3.11.0)
|
rspec-core (3.12.1)
|
||||||
rspec-support (~> 3.11.0)
|
rspec-support (~> 3.12.0)
|
||||||
rspec-expectations (3.11.0)
|
rspec-expectations (3.12.2)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.11.0)
|
rspec-support (~> 3.12.0)
|
||||||
rspec-mocks (3.11.1)
|
rspec-mocks (3.12.3)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.11.0)
|
rspec-support (~> 3.12.0)
|
||||||
rspec-rails (5.1.2)
|
rspec-rails (6.0.1)
|
||||||
actionpack (>= 5.2)
|
actionpack (>= 6.1)
|
||||||
activesupport (>= 5.2)
|
activesupport (>= 6.1)
|
||||||
railties (>= 5.2)
|
railties (>= 6.1)
|
||||||
rspec-core (~> 3.10)
|
rspec-core (~> 3.11)
|
||||||
rspec-expectations (~> 3.10)
|
rspec-expectations (~> 3.11)
|
||||||
rspec-mocks (~> 3.10)
|
rspec-mocks (~> 3.11)
|
||||||
rspec-support (~> 3.10)
|
rspec-support (~> 3.11)
|
||||||
rspec-sidekiq (3.1.0)
|
rspec-sidekiq (3.1.0)
|
||||||
rspec-core (~> 3.0, >= 3.0.0)
|
rspec-core (~> 3.0, >= 3.0.0)
|
||||||
sidekiq (>= 2.4.0)
|
sidekiq (>= 2.4.0)
|
||||||
rspec-support (3.11.1)
|
rspec-support (3.12.0)
|
||||||
|
rspec_chunked (0.6)
|
||||||
rspec_junit_formatter (0.6.0)
|
rspec_junit_formatter (0.6.0)
|
||||||
rspec-core (>= 2, < 4, != 2.12.0)
|
rspec-core (>= 2, < 4, != 2.12.0)
|
||||||
rubocop (1.45.1)
|
rubocop (1.48.1)
|
||||||
json (~> 2.3)
|
json (~> 2.3)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
parser (>= 3.2.0.0)
|
parser (>= 3.2.0.0)
|
||||||
rainbow (>= 2.2.2, < 4.0)
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
regexp_parser (>= 1.8, < 3.0)
|
regexp_parser (>= 1.8, < 3.0)
|
||||||
rexml (>= 3.2.5, < 4.0)
|
rexml (>= 3.2.5, < 4.0)
|
||||||
rubocop-ast (>= 1.24.1, < 2.0)
|
rubocop-ast (>= 1.26.0, < 2.0)
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (>= 2.4.0, < 3.0)
|
unicode-display_width (>= 2.4.0, < 3.0)
|
||||||
rubocop-ast (1.24.1)
|
rubocop-ast (1.27.0)
|
||||||
parser (>= 3.1.1.0)
|
parser (>= 3.2.1.0)
|
||||||
rubocop-capybara (2.17.0)
|
rubocop-capybara (2.17.1)
|
||||||
rubocop (~> 1.41)
|
rubocop (~> 1.41)
|
||||||
rubocop-performance (1.16.0)
|
rubocop-performance (1.16.0)
|
||||||
rubocop (>= 1.7.0, < 2.0)
|
rubocop (>= 1.7.0, < 2.0)
|
||||||
rubocop-ast (>= 0.4.0)
|
rubocop-ast (>= 0.4.0)
|
||||||
rubocop-rails (2.17.4)
|
rubocop-rails (2.18.0)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
rack (>= 1.1)
|
rack (>= 1.1)
|
||||||
rubocop (>= 1.33.0, < 2.0)
|
rubocop (>= 1.33.0, < 2.0)
|
||||||
rubocop-rspec (2.18.1)
|
rubocop-rspec (2.19.0)
|
||||||
rubocop (~> 1.33)
|
rubocop (~> 1.33)
|
||||||
rubocop-capybara (~> 2.17)
|
rubocop-capybara (~> 2.17)
|
||||||
ruby-progressbar (1.11.0)
|
ruby-progressbar (1.13.0)
|
||||||
ruby-saml (1.13.0)
|
ruby-saml (1.13.0)
|
||||||
nokogiri (>= 1.10.5)
|
nokogiri (>= 1.10.5)
|
||||||
rexml
|
rexml
|
||||||
|
@ -655,9 +649,9 @@ GEM
|
||||||
redis (>= 4.5.0, < 5)
|
redis (>= 4.5.0, < 5)
|
||||||
sidekiq-bulk (0.2.0)
|
sidekiq-bulk (0.2.0)
|
||||||
sidekiq
|
sidekiq
|
||||||
sidekiq-scheduler (5.0.1)
|
sidekiq-scheduler (5.0.2)
|
||||||
rufus-scheduler (~> 3.2)
|
rufus-scheduler (~> 3.2)
|
||||||
sidekiq (>= 4, < 8)
|
sidekiq (>= 6, < 8)
|
||||||
tilt (>= 1.4.0)
|
tilt (>= 1.4.0)
|
||||||
sidekiq-unique-jobs (7.1.29)
|
sidekiq-unique-jobs (7.1.29)
|
||||||
brpoplpush-redis_script (> 0.1.1, <= 2.0.0)
|
brpoplpush-redis_script (> 0.1.1, <= 2.0.0)
|
||||||
|
@ -687,7 +681,7 @@ GEM
|
||||||
sshkit (1.21.4)
|
sshkit (1.21.4)
|
||||||
net-scp (>= 1.1.2)
|
net-scp (>= 1.1.2)
|
||||||
net-ssh (>= 2.8.0)
|
net-ssh (>= 2.8.0)
|
||||||
stackprof (0.2.23)
|
stackprof (0.2.24)
|
||||||
statsd-ruby (1.5.0)
|
statsd-ruby (1.5.0)
|
||||||
stoplight (3.0.1)
|
stoplight (3.0.1)
|
||||||
redlock (~> 1.0)
|
redlock (~> 1.0)
|
||||||
|
@ -697,14 +691,15 @@ GEM
|
||||||
activesupport (>= 3)
|
activesupport (>= 3)
|
||||||
attr_required (>= 0.0.5)
|
attr_required (>= 0.0.5)
|
||||||
httpclient (>= 2.4)
|
httpclient (>= 2.4)
|
||||||
|
sysexits (1.2.0)
|
||||||
temple (0.10.0)
|
temple (0.10.0)
|
||||||
terminal-table (3.0.2)
|
terminal-table (3.0.2)
|
||||||
unicode-display_width (>= 1.1.1, < 3)
|
unicode-display_width (>= 1.1.1, < 3)
|
||||||
terrapin (0.6.0)
|
terrapin (0.6.0)
|
||||||
climate_control (>= 0.0.3, < 1.0)
|
climate_control (>= 0.0.3, < 1.0)
|
||||||
thor (1.2.1)
|
thor (1.2.1)
|
||||||
tilt (2.0.11)
|
tilt (2.1.0)
|
||||||
timeout (0.3.1)
|
timeout (0.3.2)
|
||||||
tpm-key_attestation (0.12.0)
|
tpm-key_attestation (0.12.0)
|
||||||
bindata (~> 2.4)
|
bindata (~> 2.4)
|
||||||
openssl (> 2.0)
|
openssl (> 2.0)
|
||||||
|
@ -730,7 +725,6 @@ GEM
|
||||||
unf_ext
|
unf_ext
|
||||||
unf_ext (0.0.8.2)
|
unf_ext (0.0.8.2)
|
||||||
unicode-display_width (2.4.2)
|
unicode-display_width (2.4.2)
|
||||||
uniform_notifier (1.16.0)
|
|
||||||
uri (0.12.0)
|
uri (0.12.0)
|
||||||
validate_email (0.1.6)
|
validate_email (0.1.6)
|
||||||
activemodel (>= 3.0)
|
activemodel (>= 3.0)
|
||||||
|
@ -775,7 +769,6 @@ PLATFORMS
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
active_model_serializers (~> 0.10)
|
active_model_serializers (~> 0.10)
|
||||||
active_record_query_trace (~> 1.8)
|
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
annotate (~> 3.2)
|
annotate (~> 3.2)
|
||||||
aws-sdk-s3 (~> 1.119)
|
aws-sdk-s3 (~> 1.119)
|
||||||
|
@ -785,7 +778,6 @@ DEPENDENCIES
|
||||||
bootsnap (~> 1.16.0)
|
bootsnap (~> 1.16.0)
|
||||||
brakeman (~> 5.4)
|
brakeman (~> 5.4)
|
||||||
browser
|
browser
|
||||||
bullet (~> 7.0)
|
|
||||||
bundler-audit (~> 0.9)
|
bundler-audit (~> 0.9)
|
||||||
capistrano (~> 3.17)
|
capistrano (~> 3.17)
|
||||||
capistrano-rails (~> 1.6)
|
capistrano-rails (~> 1.6)
|
||||||
|
@ -794,7 +786,7 @@ DEPENDENCIES
|
||||||
capybara (~> 3.38)
|
capybara (~> 3.38)
|
||||||
charlock_holmes (~> 0.7.7)
|
charlock_holmes (~> 0.7.7)
|
||||||
chewy (~> 7.2)
|
chewy (~> 7.2)
|
||||||
climate_control (~> 0.2)
|
climate_control
|
||||||
cocoon (~> 1.2)
|
cocoon (~> 1.2)
|
||||||
color_diff (~> 0.1)
|
color_diff (~> 0.1)
|
||||||
concurrent-ruby
|
concurrent-ruby
|
||||||
|
@ -814,6 +806,7 @@ DEPENDENCIES
|
||||||
fog-openstack (~> 0.3)
|
fog-openstack (~> 0.3)
|
||||||
fuubar (~> 2.5)
|
fuubar (~> 2.5)
|
||||||
haml-rails (~> 2.0)
|
haml-rails (~> 2.0)
|
||||||
|
haml_lint
|
||||||
hcaptcha (~> 7.1)
|
hcaptcha (~> 7.1)
|
||||||
hiredis (~> 0.6)
|
hiredis (~> 0.6)
|
||||||
htmlentities (~> 4.3)
|
htmlentities (~> 4.3)
|
||||||
|
@ -844,7 +837,7 @@ DEPENDENCIES
|
||||||
omniauth-cas (~> 2.0)
|
omniauth-cas (~> 2.0)
|
||||||
omniauth-rails_csrf_protection (~> 0.1)
|
omniauth-rails_csrf_protection (~> 0.1)
|
||||||
omniauth-saml (~> 1.10)
|
omniauth-saml (~> 1.10)
|
||||||
omniauth_openid_connect (~> 0.6.0)
|
omniauth_openid_connect (~> 0.6.1)
|
||||||
ox (~> 2.14)
|
ox (~> 2.14)
|
||||||
parslet
|
parslet
|
||||||
pg (~> 1.4)
|
pg (~> 1.4)
|
||||||
|
@ -853,15 +846,13 @@ DEPENDENCIES
|
||||||
posix-spawn
|
posix-spawn
|
||||||
premailer-rails
|
premailer-rails
|
||||||
private_address_check (~> 0.5)
|
private_address_check (~> 0.5)
|
||||||
pry-byebug (~> 3.10)
|
|
||||||
pry-rails (~> 0.3)
|
|
||||||
public_suffix (~> 5.0)
|
public_suffix (~> 5.0)
|
||||||
puma (~> 5.6)
|
puma (~> 6.1)
|
||||||
pundit (~> 2.3)
|
pundit (~> 2.3)
|
||||||
rack (~> 2.2.6)
|
rack (~> 2.2.6)
|
||||||
rack-attack (~> 6.6)
|
rack-attack (~> 6.6)
|
||||||
rack-cors (~> 1.1)
|
rack-cors (~> 2.0)
|
||||||
rack-test (~> 2.0)
|
rack-test (~> 2.1)
|
||||||
rails (~> 6.1.7)
|
rails (~> 6.1.7)
|
||||||
rails-controller-testing (~> 1.0)
|
rails-controller-testing (~> 1.0)
|
||||||
rails-i18n (~> 6.0)
|
rails-i18n (~> 6.0)
|
||||||
|
@ -870,12 +861,13 @@ DEPENDENCIES
|
||||||
redcarpet (~> 3.6)
|
redcarpet (~> 3.6)
|
||||||
redis (~> 4.5)
|
redis (~> 4.5)
|
||||||
redis-namespace (~> 1.10)
|
redis-namespace (~> 1.10)
|
||||||
rexml (~> 3.2)
|
|
||||||
rqrcode (~> 2.1)
|
rqrcode (~> 2.1)
|
||||||
rspec-rails (~> 5.1)
|
rspec-rails (~> 6.0)
|
||||||
rspec-sidekiq (~> 3.1)
|
rspec-sidekiq (~> 3.1)
|
||||||
|
rspec_chunked (~> 0.6)
|
||||||
rspec_junit_formatter (~> 0.6)
|
rspec_junit_formatter (~> 0.6)
|
||||||
rubocop
|
rubocop
|
||||||
|
rubocop-capybara
|
||||||
rubocop-performance
|
rubocop-performance
|
||||||
rubocop-rails
|
rubocop-rails
|
||||||
rubocop-rspec
|
rubocop-rspec
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
module Admin
|
module Admin
|
||||||
class DomainBlocksController < BaseController
|
class DomainBlocksController < BaseController
|
||||||
before_action :set_domain_block, only: [:show, :destroy, :edit, :update]
|
before_action :set_domain_block, only: [:destroy, :edit, :update]
|
||||||
|
|
||||||
def batch
|
def batch
|
||||||
authorize :domain_block, :create?
|
authorize :domain_block, :create?
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
module Admin
|
module Admin
|
||||||
class EmailDomainBlocksController < BaseController
|
class EmailDomainBlocksController < BaseController
|
||||||
before_action :set_email_domain_block, only: [:show, :destroy]
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
authorize :email_domain_block, :index?
|
authorize :email_domain_block, :index?
|
||||||
|
|
||||||
|
@ -59,10 +57,6 @@ module Admin
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_email_domain_block
|
|
||||||
@email_domain_block = EmailDomainBlock.find(params[:id])
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_resolved_records
|
def set_resolved_records
|
||||||
Resolv::DNS.open do |dns|
|
Resolv::DNS.open do |dns|
|
||||||
dns.timeouts = 5
|
dns.timeouts = 5
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Instances::TranslationLanguagesController < Api::BaseController
|
||||||
|
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
||||||
|
|
||||||
|
before_action :set_languages
|
||||||
|
|
||||||
|
def show
|
||||||
|
expires_in 1.day, public: true
|
||||||
|
render json: @languages
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_languages
|
||||||
|
if TranslationService.configured?
|
||||||
|
@languages = Rails.cache.fetch('translation_service/languages', expires_in: 7.days, race_condition_ttl: 1.hour) { TranslationService.configured.languages }
|
||||||
|
@languages['und'] = @languages.delete(nil) if @languages.key?(nil)
|
||||||
|
else
|
||||||
|
@languages = {}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -18,6 +18,8 @@ class ApplicationController < ActionController::Base
|
||||||
helper_method :current_skin
|
helper_method :current_skin
|
||||||
helper_method :single_user_mode?
|
helper_method :single_user_mode?
|
||||||
helper_method :use_seamless_external_login?
|
helper_method :use_seamless_external_login?
|
||||||
|
helper_method :omniauth_only?
|
||||||
|
helper_method :sso_account_settings
|
||||||
helper_method :whitelist_mode?
|
helper_method :whitelist_mode?
|
||||||
|
|
||||||
rescue_from ActionController::ParameterMissing, Paperclip::AdapterRegistry::NoHandlerError, with: :bad_request
|
rescue_from ActionController::ParameterMissing, Paperclip::AdapterRegistry::NoHandlerError, with: :bad_request
|
||||||
|
@ -63,8 +65,12 @@ class ApplicationController < ActionController::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def after_sign_out_path_for(_resource_or_scope)
|
def after_sign_out_path_for(_resource_or_scope)
|
||||||
|
if ENV['OMNIAUTH_ONLY'] == 'true' && ENV['OIDC_ENABLED'] == 'true'
|
||||||
|
'/auth/auth/openid_connect/logout'
|
||||||
|
else
|
||||||
new_user_session_path
|
new_user_session_path
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
|
@ -116,6 +122,14 @@ class ApplicationController < ActionController::Base
|
||||||
Devise.pam_authentication || Devise.ldap_authentication
|
Devise.pam_authentication || Devise.ldap_authentication
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def omniauth_only?
|
||||||
|
ENV['OMNIAUTH_ONLY'] == 'true'
|
||||||
|
end
|
||||||
|
|
||||||
|
def sso_account_settings
|
||||||
|
ENV.fetch('SSO_ACCOUNT_SETTINGS')
|
||||||
|
end
|
||||||
|
|
||||||
def current_account
|
def current_account
|
||||||
return @current_account if defined?(@current_account)
|
return @current_account if defined?(@current_account)
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||||
|
|
||||||
def after_sign_in_path_for(resource)
|
def after_sign_in_path_for(resource)
|
||||||
if resource.email_present?
|
if resource.email_present?
|
||||||
root_path
|
stored_location_for(resource) || root_path
|
||||||
else
|
else
|
||||||
auth_setup_path(missing_email: '1')
|
auth_setup_path(missing_email: '1')
|
||||||
end
|
end
|
||||||
|
|
27
app/controllers/backups_controller.rb
Normal file
27
app/controllers/backups_controller.rb
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class BackupsController < ApplicationController
|
||||||
|
include RoutingHelper
|
||||||
|
|
||||||
|
skip_before_action :require_functional!
|
||||||
|
|
||||||
|
before_action :authenticate_user!
|
||||||
|
before_action :set_backup
|
||||||
|
|
||||||
|
def download
|
||||||
|
case Paperclip::Attachment.default_options[:storage]
|
||||||
|
when :s3
|
||||||
|
redirect_to @backup.dump.expiring_url(10)
|
||||||
|
when :fog
|
||||||
|
redirect_to @backup.dump.expiring_url(Time.now.utc + 10)
|
||||||
|
when :filesystem
|
||||||
|
redirect_to full_asset_url(@backup.dump.url)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_backup
|
||||||
|
@backup = current_user.backups.find(params[:id])
|
||||||
|
end
|
||||||
|
end
|
|
@ -3,6 +3,158 @@
|
||||||
module CacheConcern
|
module CacheConcern
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
module ActiveRecordCoder
|
||||||
|
EMPTY_HASH = {}.freeze
|
||||||
|
|
||||||
|
class << self
|
||||||
|
def dump(record)
|
||||||
|
instances = InstanceTracker.new
|
||||||
|
serialized_associations = serialize_associations(record, instances)
|
||||||
|
serialized_records = instances.map { |r| serialize_record(r) }
|
||||||
|
[serialized_associations, *serialized_records]
|
||||||
|
end
|
||||||
|
|
||||||
|
def load(payload)
|
||||||
|
instances = InstanceTracker.new
|
||||||
|
serialized_associations, *serialized_records = payload
|
||||||
|
serialized_records.each { |attrs| instances.push(deserialize_record(*attrs)) }
|
||||||
|
deserialize_associations(serialized_associations, instances)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# Records without associations, or which have already been visited before,
|
||||||
|
# are serialized by their id alone.
|
||||||
|
#
|
||||||
|
# Records with associations are serialized as a two-element array including
|
||||||
|
# their id and the record's association cache.
|
||||||
|
#
|
||||||
|
def serialize_associations(record, instances)
|
||||||
|
return unless record
|
||||||
|
|
||||||
|
if (id = instances.lookup(record))
|
||||||
|
payload = id
|
||||||
|
else
|
||||||
|
payload = instances.push(record)
|
||||||
|
|
||||||
|
cached_associations = record.class.reflect_on_all_associations.select do |reflection|
|
||||||
|
record.association_cached?(reflection.name)
|
||||||
|
end
|
||||||
|
|
||||||
|
unless cached_associations.empty?
|
||||||
|
serialized_associations = cached_associations.map do |reflection|
|
||||||
|
association = record.association(reflection.name)
|
||||||
|
|
||||||
|
serialized_target = if reflection.collection?
|
||||||
|
association.target.map { |target_record| serialize_associations(target_record, instances) }
|
||||||
|
else
|
||||||
|
serialize_associations(association.target, instances)
|
||||||
|
end
|
||||||
|
|
||||||
|
[reflection.name, serialized_target]
|
||||||
|
end
|
||||||
|
|
||||||
|
payload = [payload, serialized_associations]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
payload
|
||||||
|
end
|
||||||
|
|
||||||
|
def deserialize_associations(payload, instances)
|
||||||
|
return unless payload
|
||||||
|
|
||||||
|
id, associations = payload
|
||||||
|
record = instances.fetch(id)
|
||||||
|
|
||||||
|
associations&.each do |name, serialized_target|
|
||||||
|
begin
|
||||||
|
association = record.association(name)
|
||||||
|
rescue ActiveRecord::AssociationNotFoundError
|
||||||
|
raise AssociationMissingError, "undefined association: #{name}"
|
||||||
|
end
|
||||||
|
|
||||||
|
target = if association.reflection.collection?
|
||||||
|
serialized_target.map! { |serialized_record| deserialize_associations(serialized_record, instances) }
|
||||||
|
else
|
||||||
|
deserialize_associations(serialized_target, instances)
|
||||||
|
end
|
||||||
|
|
||||||
|
association.target = target
|
||||||
|
end
|
||||||
|
|
||||||
|
record
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize_record(record)
|
||||||
|
arguments = [record.class.name, attributes_for_database(record)]
|
||||||
|
arguments << true if record.new_record?
|
||||||
|
arguments
|
||||||
|
end
|
||||||
|
|
||||||
|
if Rails.gem_version >= Gem::Version.new('7.0')
|
||||||
|
def attributes_for_database(record)
|
||||||
|
attributes = record.attributes_for_database
|
||||||
|
attributes.transform_values! { |attr| attr.is_a?(::ActiveModel::Type::Binary::Data) ? attr.to_s : attr }
|
||||||
|
attributes
|
||||||
|
end
|
||||||
|
else
|
||||||
|
def attributes_for_database(record)
|
||||||
|
attributes = record.instance_variable_get(:@attributes).send(:attributes).transform_values(&:value_for_database)
|
||||||
|
attributes.transform_values! { |attr| attr.is_a?(::ActiveModel::Type::Binary::Data) ? attr.to_s : attr }
|
||||||
|
attributes
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def deserialize_record(class_name, attributes_from_database, new_record = false) # rubocop:disable Style/OptionalBooleanParameter
|
||||||
|
begin
|
||||||
|
klass = Object.const_get(class_name)
|
||||||
|
rescue NameError
|
||||||
|
raise ClassMissingError, "undefined class: #{class_name}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Ideally we'd like to call `klass.instantiate`, however it doesn't allow to pass
|
||||||
|
# wether the record was persisted or not.
|
||||||
|
attributes = klass.attributes_builder.build_from_database(attributes_from_database, EMPTY_HASH)
|
||||||
|
klass.allocate.init_with_attributes(attributes, new_record)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Error < StandardError
|
||||||
|
end
|
||||||
|
|
||||||
|
class ClassMissingError < Error
|
||||||
|
end
|
||||||
|
|
||||||
|
class AssociationMissingError < Error
|
||||||
|
end
|
||||||
|
|
||||||
|
class InstanceTracker
|
||||||
|
def initialize
|
||||||
|
@instances = []
|
||||||
|
@ids = {}.compare_by_identity
|
||||||
|
end
|
||||||
|
|
||||||
|
def map(&block)
|
||||||
|
@instances.map(&block)
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch(...)
|
||||||
|
@instances.fetch(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
def push(instance)
|
||||||
|
id = @ids[instance] = @instances.size
|
||||||
|
@instances << instance
|
||||||
|
id
|
||||||
|
end
|
||||||
|
|
||||||
|
def lookup(instance)
|
||||||
|
@ids[instance]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def render_with_cache(**options)
|
def render_with_cache(**options)
|
||||||
raise ArgumentError, 'only JSON render calls are supported' unless options.key?(:json) || block_given?
|
raise ArgumentError, 'only JSON render calls are supported' unless options.key?(:json) || block_given?
|
||||||
|
|
||||||
|
@ -34,7 +186,12 @@ module CacheConcern
|
||||||
raw = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation)
|
raw = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation)
|
||||||
return [] if raw.empty?
|
return [] if raw.empty?
|
||||||
|
|
||||||
cached_keys_with_value = Rails.cache.read_multi(*raw).transform_keys(&:id)
|
cached_keys_with_value = begin
|
||||||
|
Rails.cache.read_multi(*raw).transform_keys(&:id).transform_values { |r| ActiveRecordCoder.load(r) }
|
||||||
|
rescue ActiveRecordCoder::Error
|
||||||
|
{} # The serialization format may have changed, let's pretend it's a cache miss.
|
||||||
|
end
|
||||||
|
|
||||||
uncached_ids = raw.map(&:id) - cached_keys_with_value.keys
|
uncached_ids = raw.map(&:id) - cached_keys_with_value.keys
|
||||||
|
|
||||||
klass.reload_stale_associations!(cached_keys_with_value.values) if klass.respond_to?(:reload_stale_associations!)
|
klass.reload_stale_associations!(cached_keys_with_value.values) if klass.respond_to?(:reload_stale_associations!)
|
||||||
|
@ -43,7 +200,7 @@ module CacheConcern
|
||||||
uncached = klass.where(id: uncached_ids).with_includes.index_by(&:id)
|
uncached = klass.where(id: uncached_ids).with_includes.index_by(&:id)
|
||||||
|
|
||||||
uncached.each_value do |item|
|
uncached.each_value do |item|
|
||||||
Rails.cache.write(item, item)
|
Rails.cache.write(item, ActiveRecordCoder.dump(item))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -138,7 +138,7 @@ module SignatureVerification
|
||||||
end
|
end
|
||||||
|
|
||||||
def signed_headers
|
def signed_headers
|
||||||
signature_params.fetch('headers', signature_algorithm == 'hs2019' ? '(created)' : 'date').downcase.split(' ')
|
signature_params.fetch('headers', signature_algorithm == 'hs2019' ? '(created)' : 'date').downcase.split
|
||||||
end
|
end
|
||||||
|
|
||||||
def verify_signature_strength!
|
def verify_signature_strength!
|
||||||
|
|
|
@ -20,6 +20,8 @@ class RelationshipsController < ApplicationController
|
||||||
@form.save
|
@form.save
|
||||||
rescue ActionController::ParameterMissing
|
rescue ActionController::ParameterMissing
|
||||||
# Do nothing
|
# Do nothing
|
||||||
|
rescue Mastodon::NotPermittedError, ActiveRecord::RecordNotFound
|
||||||
|
flash[:alert] = I18n.t('relationships.follow_failure') if action_from_button == 'follow'
|
||||||
ensure
|
ensure
|
||||||
redirect_to relationships_path(filter_params)
|
redirect_to relationships_path(filter_params)
|
||||||
end
|
end
|
||||||
|
@ -61,8 +63,8 @@ class RelationshipsController < ApplicationController
|
||||||
'unfollow'
|
'unfollow'
|
||||||
elsif params[:remove_from_followers]
|
elsif params[:remove_from_followers]
|
||||||
'remove_from_followers'
|
'remove_from_followers'
|
||||||
elsif params[:block_domains]
|
elsif params[:block_domains] || params[:remove_domains_from_followers]
|
||||||
'block_domains'
|
'remove_domains_from_followers'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ module Settings
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
flash[:error] = I18n.t('webauthn_credentials.create.error')
|
flash[:error] = I18n.t('webauthn_credentials.create.error')
|
||||||
status = :internal_server_error
|
status = :unprocessable_entity
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
flash[:error] = t('webauthn_credentials.create.error')
|
flash[:error] = t('webauthn_credentials.create.error')
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module Admin::AnnouncementsHelper
|
|
||||||
def time_range(announcement)
|
|
||||||
if announcement.all_day?
|
|
||||||
safe_join([l(announcement.starts_at.to_date), ' - ', l(announcement.ends_at.to_date)])
|
|
||||||
else
|
|
||||||
safe_join([l(announcement.starts_at), ' - ', l(announcement.ends_at)])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -112,7 +112,7 @@ module ApplicationHelper
|
||||||
def fa_icon(icon, attributes = {})
|
def fa_icon(icon, attributes = {})
|
||||||
class_names = attributes[:class]&.split(' ') || []
|
class_names = attributes[:class]&.split(' ') || []
|
||||||
class_names << 'fa'
|
class_names << 'fa'
|
||||||
class_names += icon.split(' ').map { |cl| "fa-#{cl}" }
|
class_names += icon.split.map { |cl| "fa-#{cl}" }
|
||||||
|
|
||||||
content_tag(:i, nil, attributes.merge(class: class_names.join(' ')))
|
content_tag(:i, nil, attributes.merge(class: class_names.join(' ')))
|
||||||
end
|
end
|
||||||
|
@ -164,7 +164,7 @@ module ApplicationHelper
|
||||||
end
|
end
|
||||||
|
|
||||||
def body_classes
|
def body_classes
|
||||||
output = (@body_classes || '').split(' ')
|
output = (@body_classes || '').split
|
||||||
output << "flavour-#{current_flavour.parameterize}"
|
output << "flavour-#{current_flavour.parameterize}"
|
||||||
output << "skin-#{current_skin.parameterize}"
|
output << "skin-#{current_skin.parameterize}"
|
||||||
output << 'system-font' if current_account&.user&.setting_system_font_ui
|
output << 'system-font' if current_account&.user&.setting_system_font_ui
|
||||||
|
|
|
@ -41,9 +41,9 @@ module HomeHelper
|
||||||
|
|
||||||
def obscured_counter(count)
|
def obscured_counter(count)
|
||||||
if count <= 0
|
if count <= 0
|
||||||
0
|
'0'
|
||||||
elsif count == 1
|
elsif count == 1
|
||||||
1
|
'1'
|
||||||
else
|
else
|
||||||
'1+'
|
'1+'
|
||||||
end
|
end
|
||||||
|
@ -57,14 +57,6 @@ module HomeHelper
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def optional_link_to(condition, path, options = {}, &block)
|
|
||||||
if condition
|
|
||||||
link_to(path, options, &block)
|
|
||||||
else
|
|
||||||
content_tag(:div, &block)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def sign_up_message
|
def sign_up_message
|
||||||
if closed_registrations?
|
if closed_registrations?
|
||||||
t('auth.registration_closed', instance: site_hostname)
|
t('auth.registration_closed', instance: site_hostname)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# rubocop:disable Metrics/ModuleLength, Style/WordArray
|
# rubocop:disable Metrics/ModuleLength
|
||||||
|
|
||||||
module LanguagesHelper
|
module LanguagesHelper
|
||||||
ISO_639_1 = {
|
ISO_639_1 = {
|
||||||
|
@ -275,4 +275,4 @@ module LanguagesHelper
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# rubocop:enable Metrics/ModuleLength, Style/WordArray
|
# rubocop:enable Metrics/ModuleLength
|
||||||
|
|
|
@ -5,6 +5,10 @@ export const SERVER_FETCH_REQUEST = 'Server_FETCH_REQUEST';
|
||||||
export const SERVER_FETCH_SUCCESS = 'Server_FETCH_SUCCESS';
|
export const SERVER_FETCH_SUCCESS = 'Server_FETCH_SUCCESS';
|
||||||
export const SERVER_FETCH_FAIL = 'Server_FETCH_FAIL';
|
export const SERVER_FETCH_FAIL = 'Server_FETCH_FAIL';
|
||||||
|
|
||||||
|
export const SERVER_TRANSLATION_LANGUAGES_FETCH_REQUEST = 'SERVER_TRANSLATION_LANGUAGES_FETCH_REQUEST';
|
||||||
|
export const SERVER_TRANSLATION_LANGUAGES_FETCH_SUCCESS = 'SERVER_TRANSLATION_LANGUAGES_FETCH_SUCCESS';
|
||||||
|
export const SERVER_TRANSLATION_LANGUAGES_FETCH_FAIL = 'SERVER_TRANSLATION_LANGUAGES_FETCH_FAIL';
|
||||||
|
|
||||||
export const EXTENDED_DESCRIPTION_REQUEST = 'EXTENDED_DESCRIPTION_REQUEST';
|
export const EXTENDED_DESCRIPTION_REQUEST = 'EXTENDED_DESCRIPTION_REQUEST';
|
||||||
export const EXTENDED_DESCRIPTION_SUCCESS = 'EXTENDED_DESCRIPTION_SUCCESS';
|
export const EXTENDED_DESCRIPTION_SUCCESS = 'EXTENDED_DESCRIPTION_SUCCESS';
|
||||||
export const EXTENDED_DESCRIPTION_FAIL = 'EXTENDED_DESCRIPTION_FAIL';
|
export const EXTENDED_DESCRIPTION_FAIL = 'EXTENDED_DESCRIPTION_FAIL';
|
||||||
|
@ -37,6 +41,29 @@ const fetchServerFail = error => ({
|
||||||
error,
|
error,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const fetchServerTranslationLanguages = () => (dispatch, getState) => {
|
||||||
|
dispatch(fetchServerTranslationLanguagesRequest());
|
||||||
|
|
||||||
|
api(getState)
|
||||||
|
.get('/api/v1/instance/translation_languages').then(({ data }) => {
|
||||||
|
dispatch(fetchServerTranslationLanguagesSuccess(data));
|
||||||
|
}).catch(err => dispatch(fetchServerTranslationLanguagesFail(err)));
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchServerTranslationLanguagesRequest = () => ({
|
||||||
|
type: SERVER_TRANSLATION_LANGUAGES_FETCH_REQUEST,
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchServerTranslationLanguagesSuccess = translationLanguages => ({
|
||||||
|
type: SERVER_TRANSLATION_LANGUAGES_FETCH_SUCCESS,
|
||||||
|
translationLanguages,
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchServerTranslationLanguagesFail = error => ({
|
||||||
|
type: SERVER_TRANSLATION_LANGUAGES_FETCH_FAIL,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
|
||||||
export const fetchExtendedDescription = () => (dispatch, getState) => {
|
export const fetchExtendedDescription = () => (dispatch, getState) => {
|
||||||
dispatch(fetchExtendedDescriptionRequest());
|
dispatch(fetchExtendedDescriptionRequest());
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ export default class GIFV extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
src: PropTypes.string.isRequired,
|
src: PropTypes.string.isRequired,
|
||||||
alt: PropTypes.string,
|
alt: PropTypes.string,
|
||||||
|
lang: PropTypes.string,
|
||||||
width: PropTypes.number,
|
width: PropTypes.number,
|
||||||
height: PropTypes.number,
|
height: PropTypes.number,
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
|
@ -35,7 +36,7 @@ export default class GIFV extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { src, width, height, alt } = this.props;
|
const { src, width, height, alt, lang } = this.props;
|
||||||
const { loading } = this.state;
|
const { loading } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -48,6 +49,7 @@ export default class GIFV extends React.PureComponent {
|
||||||
tabIndex='0'
|
tabIndex='0'
|
||||||
aria-label={alt}
|
aria-label={alt}
|
||||||
title={alt}
|
title={alt}
|
||||||
|
lang={lang}
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -58,6 +60,7 @@ export default class GIFV extends React.PureComponent {
|
||||||
tabIndex='0'
|
tabIndex='0'
|
||||||
aria-label={alt}
|
aria-label={alt}
|
||||||
title={alt}
|
title={alt}
|
||||||
|
lang={lang}
|
||||||
muted
|
muted
|
||||||
loop
|
loop
|
||||||
autoPlay
|
autoPlay
|
||||||
|
|
|
@ -10,6 +10,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
status: ImmutablePropTypes.map.isRequired,
|
status: ImmutablePropTypes.map.isRequired,
|
||||||
|
lang: PropTypes.string,
|
||||||
height: PropTypes.number,
|
height: PropTypes.number,
|
||||||
width: PropTypes.number,
|
width: PropTypes.number,
|
||||||
revealed: PropTypes.bool,
|
revealed: PropTypes.bool,
|
||||||
|
@ -49,7 +50,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { status, width, height, revealed } = this.props;
|
const { status, lang, width, height, revealed } = this.props;
|
||||||
const mediaAttachments = status.get('media_attachments');
|
const mediaAttachments = status.get('media_attachments');
|
||||||
|
|
||||||
if (mediaAttachments.size === 0) {
|
if (mediaAttachments.size === 0) {
|
||||||
|
@ -65,6 +66,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
|
||||||
<Component
|
<Component
|
||||||
src={audio.get('url')}
|
src={audio.get('url')}
|
||||||
alt={audio.get('description')}
|
alt={audio.get('description')}
|
||||||
|
lang={lang || status.get('language')}
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
poster={audio.get('preview_url') || status.getIn(['account', 'avatar_static'])}
|
poster={audio.get('preview_url') || status.getIn(['account', 'avatar_static'])}
|
||||||
|
@ -88,6 +90,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
|
||||||
blurhash={video.get('blurhash')}
|
blurhash={video.get('blurhash')}
|
||||||
src={video.get('url')}
|
src={video.get('url')}
|
||||||
alt={video.get('description')}
|
alt={video.get('description')}
|
||||||
|
lang={lang || status.get('language')}
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
inline
|
inline
|
||||||
|
@ -104,6 +107,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
|
||||||
{Component => (
|
{Component => (
|
||||||
<Component
|
<Component
|
||||||
media={mediaAttachments}
|
media={mediaAttachments}
|
||||||
|
lang={lang || status.get('language')}
|
||||||
sensitive={status.get('sensitive')}
|
sensitive={status.get('sensitive')}
|
||||||
defaultWidth={width}
|
defaultWidth={width}
|
||||||
revealed={revealed}
|
revealed={revealed}
|
||||||
|
|
|
@ -36,6 +36,7 @@ class Item extends React.PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
attachment: ImmutablePropTypes.map.isRequired,
|
attachment: ImmutablePropTypes.map.isRequired,
|
||||||
|
lang: PropTypes.string,
|
||||||
standalone: PropTypes.bool,
|
standalone: PropTypes.bool,
|
||||||
index: PropTypes.number.isRequired,
|
index: PropTypes.number.isRequired,
|
||||||
size: PropTypes.number.isRequired,
|
size: PropTypes.number.isRequired,
|
||||||
|
@ -98,7 +99,7 @@ class Item extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { attachment, index, size, standalone, letterbox, displayWidth, visible } = this.props;
|
const { attachment, lang, index, size, standalone, letterbox, displayWidth, visible } = this.props;
|
||||||
|
|
||||||
let width = 50;
|
let width = 50;
|
||||||
let height = 100;
|
let height = 100;
|
||||||
|
@ -154,7 +155,7 @@ class Item extends React.PureComponent {
|
||||||
if (attachment.get('type') === 'unknown') {
|
if (attachment.get('type') === 'unknown') {
|
||||||
return (
|
return (
|
||||||
<div className={classNames('media-gallery__item', { standalone })} key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}>
|
<div className={classNames('media-gallery__item', { standalone })} key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}>
|
||||||
<a className='media-gallery__item-thumbnail' href={attachment.get('remote_url') || attachment.get('url')} style={{ cursor: 'pointer' }} title={attachment.get('description')} target='_blank' rel='noopener noreferrer'>
|
<a className='media-gallery__item-thumbnail' href={attachment.get('remote_url') || attachment.get('url')} style={{ cursor: 'pointer' }} title={attachment.get('description')} lang={lang} target='_blank' rel='noopener noreferrer'>
|
||||||
<Blurhash
|
<Blurhash
|
||||||
hash={attachment.get('blurhash')}
|
hash={attachment.get('blurhash')}
|
||||||
className='media-gallery__preview'
|
className='media-gallery__preview'
|
||||||
|
@ -195,6 +196,7 @@ class Item extends React.PureComponent {
|
||||||
sizes={sizes}
|
sizes={sizes}
|
||||||
alt={attachment.get('description')}
|
alt={attachment.get('description')}
|
||||||
title={attachment.get('description')}
|
title={attachment.get('description')}
|
||||||
|
lang={lang}
|
||||||
style={{ objectPosition: letterbox ? null : `${x}% ${y}%` }}
|
style={{ objectPosition: letterbox ? null : `${x}% ${y}%` }}
|
||||||
onLoad={this.handleImageLoad}
|
onLoad={this.handleImageLoad}
|
||||||
/>
|
/>
|
||||||
|
@ -209,6 +211,7 @@ class Item extends React.PureComponent {
|
||||||
className={`media-gallery__item-gifv-thumbnail${letterbox ? ' letterbox' : ''}`}
|
className={`media-gallery__item-gifv-thumbnail${letterbox ? ' letterbox' : ''}`}
|
||||||
aria-label={attachment.get('description')}
|
aria-label={attachment.get('description')}
|
||||||
title={attachment.get('description')}
|
title={attachment.get('description')}
|
||||||
|
lang={lang}
|
||||||
role='application'
|
role='application'
|
||||||
src={attachment.get('url')}
|
src={attachment.get('url')}
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
|
@ -251,6 +254,7 @@ class MediaGallery extends React.PureComponent {
|
||||||
fullwidth: PropTypes.bool,
|
fullwidth: PropTypes.bool,
|
||||||
hidden: PropTypes.bool,
|
hidden: PropTypes.bool,
|
||||||
media: ImmutablePropTypes.list.isRequired,
|
media: ImmutablePropTypes.list.isRequired,
|
||||||
|
lang: PropTypes.string,
|
||||||
size: PropTypes.object,
|
size: PropTypes.object,
|
||||||
onOpenMedia: PropTypes.func.isRequired,
|
onOpenMedia: PropTypes.func.isRequired,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
|
@ -342,7 +346,7 @@ class MediaGallery extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { media, intl, sensitive, letterbox, fullwidth, defaultWidth, autoplay } = this.props;
|
const { media, lang, intl, sensitive, letterbox, fullwidth, defaultWidth, autoplay } = this.props;
|
||||||
const { visible } = this.state;
|
const { visible } = this.state;
|
||||||
const size = media.take(4).size;
|
const size = media.take(4).size;
|
||||||
const uncached = media.every(attachment => attachment.get('type') === 'unknown');
|
const uncached = media.every(attachment => attachment.get('type') === 'unknown');
|
||||||
|
@ -364,9 +368,9 @@ class MediaGallery extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isStandaloneEligible()) {
|
if (this.isStandaloneEligible()) {
|
||||||
children = <Item standalone autoplay={autoplay} onClick={this.handleClick} attachment={media.get(0)} displayWidth={width} visible={visible} />;
|
children = <Item standalone autoplay={autoplay} onClick={this.handleClick} attachment={media.get(0)} lang={lang} displayWidth={width} visible={visible} />;
|
||||||
} else {
|
} else {
|
||||||
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} autoplay={autoplay} onClick={this.handleClick} attachment={attachment} index={i} size={size} letterbox={letterbox} displayWidth={width} visible={visible || uncached} />);
|
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} autoplay={autoplay} onClick={this.handleClick} attachment={attachment} index={i} lang={lang} size={size} letterbox={letterbox} displayWidth={width} visible={visible || uncached} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uncached) {
|
if (uncached) {
|
||||||
|
|
|
@ -40,6 +40,7 @@ class Poll extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
poll: ImmutablePropTypes.map,
|
poll: ImmutablePropTypes.map,
|
||||||
|
lang: PropTypes.string,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
refresh: PropTypes.func,
|
refresh: PropTypes.func,
|
||||||
|
@ -126,7 +127,7 @@ class Poll extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
renderOption (option, optionIndex, showResults) {
|
renderOption (option, optionIndex, showResults) {
|
||||||
const { poll, disabled, intl } = this.props;
|
const { poll, lang, disabled, intl } = this.props;
|
||||||
const pollVotesCount = poll.get('voters_count') || poll.get('votes_count');
|
const pollVotesCount = poll.get('voters_count') || poll.get('votes_count');
|
||||||
const percent = pollVotesCount === 0 ? 0 : (option.get('votes_count') / pollVotesCount) * 100;
|
const percent = pollVotesCount === 0 ? 0 : (option.get('votes_count') / pollVotesCount) * 100;
|
||||||
const leading = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') >= other.get('votes_count'));
|
const leading = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') >= other.get('votes_count'));
|
||||||
|
@ -159,6 +160,7 @@ class Poll extends ImmutablePureComponent {
|
||||||
onKeyPress={this.handleOptionKeyPress}
|
onKeyPress={this.handleOptionKeyPress}
|
||||||
aria-checked={active}
|
aria-checked={active}
|
||||||
aria-label={option.get('title')}
|
aria-label={option.get('title')}
|
||||||
|
lang={lang}
|
||||||
data-index={optionIndex}
|
data-index={optionIndex}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -175,6 +177,7 @@ class Poll extends ImmutablePureComponent {
|
||||||
|
|
||||||
<span
|
<span
|
||||||
className='poll__option__text translate'
|
className='poll__option__text translate'
|
||||||
|
lang={lang}
|
||||||
dangerouslySetInnerHTML={{ __html: titleEmojified }}
|
dangerouslySetInnerHTML={{ __html: titleEmojified }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -635,6 +635,7 @@ class Status extends ImmutablePureComponent {
|
||||||
<Component
|
<Component
|
||||||
src={attachment.get('url')}
|
src={attachment.get('url')}
|
||||||
alt={attachment.get('description')}
|
alt={attachment.get('description')}
|
||||||
|
lang={status.get('language')}
|
||||||
poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
|
poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
|
||||||
backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
|
backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
|
||||||
foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])}
|
foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])}
|
||||||
|
@ -664,6 +665,7 @@ class Status extends ImmutablePureComponent {
|
||||||
blurhash={attachment.get('blurhash')}
|
blurhash={attachment.get('blurhash')}
|
||||||
src={attachment.get('url')}
|
src={attachment.get('url')}
|
||||||
alt={attachment.get('description')}
|
alt={attachment.get('description')}
|
||||||
|
lang={status.get('language')}
|
||||||
inline
|
inline
|
||||||
sensitive={status.get('sensitive')}
|
sensitive={status.get('sensitive')}
|
||||||
letterbox={settings.getIn(['media', 'letterbox'])}
|
letterbox={settings.getIn(['media', 'letterbox'])}
|
||||||
|
@ -685,6 +687,7 @@ class Status extends ImmutablePureComponent {
|
||||||
{Component => (
|
{Component => (
|
||||||
<Component
|
<Component
|
||||||
media={attachments}
|
media={attachments}
|
||||||
|
lang={status.get('language')}
|
||||||
sensitive={status.get('sensitive')}
|
sensitive={status.get('sensitive')}
|
||||||
letterbox={settings.getIn(['media', 'letterbox'])}
|
letterbox={settings.getIn(['media', 'letterbox'])}
|
||||||
fullwidth={settings.getIn(['media', 'fullwidth'])}
|
fullwidth={settings.getIn(['media', 'fullwidth'])}
|
||||||
|
@ -719,7 +722,7 @@ class Status extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.get('poll')) {
|
if (status.get('poll')) {
|
||||||
contentMedia.push(<PollContainer pollId={status.get('poll')} />);
|
contentMedia.push(<PollContainer pollId={status.get('poll')} lang={status.get('language')} />);
|
||||||
contentMediaIcons.push('tasks');
|
contentMediaIcons.push('tasks');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||||
import Permalink from './permalink';
|
import Permalink from './permalink';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import Icon from 'flavours/glitch/components/icon';
|
import Icon from 'flavours/glitch/components/icon';
|
||||||
import { autoPlayGif, languages as preloadedLanguages, translationEnabled } from 'flavours/glitch/initial_state';
|
import { autoPlayGif, languages as preloadedLanguages } from 'flavours/glitch/initial_state';
|
||||||
import { decode as decodeIDNA } from 'flavours/glitch/utils/idna';
|
import { decode as decodeIDNA } from 'flavours/glitch/utils/idna';
|
||||||
|
|
||||||
const textMatchesTarget = (text, origin, host) => {
|
const textMatchesTarget = (text, origin, host) => {
|
||||||
|
@ -99,7 +100,12 @@ class TranslateButton extends React.PureComponent {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default @injectIntl
|
const mapStateToProps = state => ({
|
||||||
|
languages: state.getIn(['server', 'translationLanguages', 'items']),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default @connect(mapStateToProps)
|
||||||
|
@injectIntl
|
||||||
class StatusContent extends React.PureComponent {
|
class StatusContent extends React.PureComponent {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
|
@ -120,6 +126,7 @@ class StatusContent extends React.PureComponent {
|
||||||
onUpdate: PropTypes.func,
|
onUpdate: PropTypes.func,
|
||||||
tagLinks: PropTypes.bool,
|
tagLinks: PropTypes.bool,
|
||||||
rewriteMentions: PropTypes.string,
|
rewriteMentions: PropTypes.string,
|
||||||
|
languages: ImmutablePropTypes.map,
|
||||||
intl: PropTypes.object,
|
intl: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -315,7 +322,9 @@ class StatusContent extends React.PureComponent {
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
|
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
|
||||||
const renderTranslate = translationEnabled && this.context.identity.signedIn && this.props.onTranslate && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('contentHtml').length > 0 && status.get('language') !== null && intl.locale !== status.get('language');
|
const contentLocale = intl.locale.replace(/[_-].*/, '');
|
||||||
|
const targetLanguages = this.props.languages?.get(status.get('language') || 'und');
|
||||||
|
const renderTranslate = this.props.onTranslate && this.context.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('contentHtml').length > 0 && targetLanguages?.includes(contentLocale);
|
||||||
|
|
||||||
const content = { __html: status.get('translation') ? status.getIn(['translation', 'content']) : status.get('contentHtml') };
|
const content = { __html: status.get('translation') ? status.getIn(['translation', 'content']) : status.get('contentHtml') };
|
||||||
const spoilerContent = { __html: status.get('spoilerHtml') };
|
const spoilerContent = { __html: status.get('spoilerHtml') };
|
||||||
|
|
|
@ -56,6 +56,8 @@ const messages = defineMessages({
|
||||||
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
||||||
quoteConfirm: { id: 'confirmations.quote.confirm', defaultMessage: 'Quote' },
|
quoteConfirm: { id: 'confirmations.quote.confirm', defaultMessage: 'Quote' },
|
||||||
quoteMessage: { id: 'confirmations.quote.message', defaultMessage: 'Quoting now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
quoteMessage: { id: 'confirmations.quote.message', defaultMessage: 'Quoting now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
||||||
|
editConfirm: { id: 'confirmations.edit.confirm', defaultMessage: 'Edit' },
|
||||||
|
editMessage: { id: 'confirmations.edit.message', defaultMessage: 'Editing now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
||||||
unfilterConfirm: { id: 'confirmations.unfilter.confirm', defaultMessage: 'Show' },
|
unfilterConfirm: { id: 'confirmations.unfilter.confirm', defaultMessage: 'Show' },
|
||||||
author: { id: 'confirmations.unfilter.author', defaultMessage: 'Author' },
|
author: { id: 'confirmations.unfilter.author', defaultMessage: 'Author' },
|
||||||
matchingFilters: { id: 'confirmations.unfilter.filters', defaultMessage: 'Matching {count, plural, one {filter} other {filters}}' },
|
matchingFilters: { id: 'confirmations.unfilter.filters', defaultMessage: 'Matching {count, plural, one {filter} other {filters}}' },
|
||||||
|
@ -212,7 +214,18 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
|
||||||
},
|
},
|
||||||
|
|
||||||
onEdit (status, history) {
|
onEdit (status, history) {
|
||||||
|
dispatch((_, getState) => {
|
||||||
|
let state = getState();
|
||||||
|
if (state.getIn(['compose', 'text']).trim().length !== 0) {
|
||||||
|
dispatch(openModal('CONFIRM', {
|
||||||
|
message: intl.formatMessage(messages.editMessage),
|
||||||
|
confirm: intl.formatMessage(messages.editConfirm),
|
||||||
|
onConfirm: () => dispatch(editStatus(status.get('id'), history)),
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
dispatch(editStatus(status.get('id'), history));
|
dispatch(editStatus(status.get('id'), history));
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onTranslate (status) {
|
onTranslate (status) {
|
||||||
|
|
|
@ -76,6 +76,7 @@ export default class MediaItem extends ImmutablePureComponent {
|
||||||
<img
|
<img
|
||||||
src={attachment.get('preview_url') || attachment.getIn(['account', 'avatar_static'])}
|
src={attachment.get('preview_url') || attachment.getIn(['account', 'avatar_static'])}
|
||||||
alt={attachment.get('description')}
|
alt={attachment.get('description')}
|
||||||
|
lang={status.get('language')}
|
||||||
onLoad={this.handleImageLoad}
|
onLoad={this.handleImageLoad}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -95,6 +96,7 @@ export default class MediaItem extends ImmutablePureComponent {
|
||||||
<img
|
<img
|
||||||
src={attachment.get('preview_url')}
|
src={attachment.get('preview_url')}
|
||||||
alt={attachment.get('description')}
|
alt={attachment.get('description')}
|
||||||
|
lang={status.get('language')}
|
||||||
style={{ objectPosition: `${x}% ${y}%` }}
|
style={{ objectPosition: `${x}% ${y}%` }}
|
||||||
onLoad={this.handleImageLoad}
|
onLoad={this.handleImageLoad}
|
||||||
/>
|
/>
|
||||||
|
@ -105,6 +107,7 @@ export default class MediaItem extends ImmutablePureComponent {
|
||||||
className='media-gallery__item-gifv-thumbnail'
|
className='media-gallery__item-gifv-thumbnail'
|
||||||
aria-label={attachment.get('description')}
|
aria-label={attachment.get('description')}
|
||||||
title={attachment.get('description')}
|
title={attachment.get('description')}
|
||||||
|
lang={status.get('language')}
|
||||||
role='application'
|
role='application'
|
||||||
src={attachment.get('url')}
|
src={attachment.get('url')}
|
||||||
onMouseEnter={this.handleMouseEnter}
|
onMouseEnter={this.handleMouseEnter}
|
||||||
|
|
|
@ -28,6 +28,7 @@ class Audio extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
src: PropTypes.string.isRequired,
|
src: PropTypes.string.isRequired,
|
||||||
alt: PropTypes.string,
|
alt: PropTypes.string,
|
||||||
|
lang: PropTypes.string,
|
||||||
poster: PropTypes.string,
|
poster: PropTypes.string,
|
||||||
duration: PropTypes.number,
|
duration: PropTypes.number,
|
||||||
width: PropTypes.number,
|
width: PropTypes.number,
|
||||||
|
@ -464,7 +465,7 @@ class Audio extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { src, intl, alt, editable, autoPlay, sensitive, blurhash } = this.props;
|
const { src, intl, alt, lang, editable, autoPlay, sensitive, blurhash } = this.props;
|
||||||
const { paused, muted, volume, currentTime, duration, buffer, dragging, revealed } = this.state;
|
const { paused, muted, volume, currentTime, duration, buffer, dragging, revealed } = this.state;
|
||||||
const progress = Math.min((currentTime / duration) * 100, 100);
|
const progress = Math.min((currentTime / duration) * 100, 100);
|
||||||
|
|
||||||
|
@ -509,6 +510,7 @@ class Audio extends React.PureComponent {
|
||||||
onKeyDown={this.handleAudioKeyDown}
|
onKeyDown={this.handleAudioKeyDown}
|
||||||
title={alt}
|
title={alt}
|
||||||
aria-label={alt}
|
aria-label={alt}
|
||||||
|
lang={lang}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className={classNames('spoiler-button', { 'spoiler-button--hidden': revealed || editable })}>
|
<div className={classNames('spoiler-button', { 'spoiler-button--hidden': revealed || editable })}>
|
||||||
|
|
|
@ -16,10 +16,8 @@ const messages = defineMessages({
|
||||||
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
|
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
|
||||||
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
|
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
|
||||||
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
|
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
|
||||||
info: { id: 'navigation_bar.info', defaultMessage: 'Extended information' },
|
|
||||||
show_me_around: { id: 'getting_started.onboarding', defaultMessage: 'Show me around' },
|
show_me_around: { id: 'getting_started.onboarding', defaultMessage: 'Show me around' },
|
||||||
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' },
|
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' },
|
||||||
info: { id: 'navigation_bar.info', defaultMessage: 'Extended information' },
|
|
||||||
keyboard_shortcuts: { id: 'navigation_bar.keyboard_shortcuts', defaultMessage: 'Keyboard shortcuts' },
|
keyboard_shortcuts: { id: 'navigation_bar.keyboard_shortcuts', defaultMessage: 'Keyboard shortcuts' },
|
||||||
featured_users: { id: 'navigation_bar.featured_users', defaultMessage: 'Featured users' },
|
featured_users: { id: 'navigation_bar.featured_users', defaultMessage: 'Featured users' },
|
||||||
});
|
});
|
||||||
|
@ -28,6 +26,11 @@ export default @connect()
|
||||||
@injectIntl
|
@injectIntl
|
||||||
class gettingStartedMisc extends ImmutablePureComponent {
|
class gettingStartedMisc extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static contextTypes = {
|
||||||
|
router: PropTypes.object.isRequired,
|
||||||
|
identity: PropTypes.object,
|
||||||
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
|
@ -43,8 +46,7 @@ class gettingStartedMisc extends ImmutablePureComponent {
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl } = this.props;
|
const { intl } = this.props;
|
||||||
|
const { signedIn } = this.context.identity;
|
||||||
let i = 1;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column icon='ellipsis-h' heading={intl.formatMessage(messages.heading)}>
|
<Column icon='ellipsis-h' heading={intl.formatMessage(messages.heading)}>
|
||||||
|
@ -52,15 +54,14 @@ class gettingStartedMisc extends ImmutablePureComponent {
|
||||||
|
|
||||||
<div className='scrollable'>
|
<div className='scrollable'>
|
||||||
<ColumnSubheading text={intl.formatMessage(messages.subheading)} />
|
<ColumnSubheading text={intl.formatMessage(messages.subheading)} />
|
||||||
<ColumnLink key='{i++}' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />
|
{signedIn && (<ColumnLink key='favourites' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />)}
|
||||||
<ColumnLink key='{i++}' icon='thumb-tack' text={intl.formatMessage(messages.pins)} to='/pinned' />
|
{signedIn && (<ColumnLink key='pinned' icon='thumb-tack' text={intl.formatMessage(messages.pins)} to='/pinned' />)}
|
||||||
<ColumnLink key='{i++}' icon='users' text={intl.formatMessage(messages.featured_users)} onClick={this.openFeaturedAccountsModal} />
|
{signedIn && (<ColumnLink key='featured_users' icon='users' text={intl.formatMessage(messages.featured_users)} onClick={this.openFeaturedAccountsModal} />)}
|
||||||
<ColumnLink key='{i++}' icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' />
|
{signedIn && (<ColumnLink key='mutes' icon='volume-off' text={intl.formatMessage(messages.mutes)} to='/mutes' />)}
|
||||||
<ColumnLink key='{i++}' icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' />
|
{signedIn && (<ColumnLink key='blocks' icon='ban' text={intl.formatMessage(messages.blocks)} to='/blocks' />)}
|
||||||
<ColumnLink key='{i++}' icon='minus-circle' text={intl.formatMessage(messages.domain_blocks)} to='/domain_blocks' />
|
{signedIn && (<ColumnLink key='domain_blocks' icon='minus-circle' text={intl.formatMessage(messages.domain_blocks)} to='/domain_blocks' />)}
|
||||||
<ColumnLink key='{i++}' icon='question' text={intl.formatMessage(messages.keyboard_shortcuts)} to='/keyboard-shortcuts' />
|
<ColumnLink key='shortcuts' icon='question' text={intl.formatMessage(messages.keyboard_shortcuts)} to='/keyboard-shortcuts' />
|
||||||
<ColumnLink key='{i++}' icon='book' text={intl.formatMessage(messages.info)} href='/about/more' />
|
{signedIn && (<ColumnLink key='onboarding' icon='hand-o-right' text={intl.formatMessage(messages.show_me_around)} onClick={this.openOnboardingModal} />)}
|
||||||
<ColumnLink key='{i++}' icon='hand-o-right' text={intl.formatMessage(messages.show_me_around)} onClick={this.openOnboardingModal} />
|
|
||||||
</div>
|
</div>
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
|
|
|
@ -173,6 +173,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
||||||
<Audio
|
<Audio
|
||||||
src={attachment.get('url')}
|
src={attachment.get('url')}
|
||||||
alt={attachment.get('description')}
|
alt={attachment.get('description')}
|
||||||
|
lang={status.get('language')}
|
||||||
duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
|
duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
|
||||||
poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
|
poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
|
||||||
backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
|
backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
|
||||||
|
@ -195,6 +196,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
||||||
blurhash={attachment.get('blurhash')}
|
blurhash={attachment.get('blurhash')}
|
||||||
src={attachment.get('url')}
|
src={attachment.get('url')}
|
||||||
alt={attachment.get('description')}
|
alt={attachment.get('description')}
|
||||||
|
lang={status.get('language')}
|
||||||
inline
|
inline
|
||||||
sensitive={status.get('sensitive')}
|
sensitive={status.get('sensitive')}
|
||||||
letterbox={settings.getIn(['media', 'letterbox'])}
|
letterbox={settings.getIn(['media', 'letterbox'])}
|
||||||
|
@ -213,6 +215,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
||||||
standalone
|
standalone
|
||||||
sensitive={status.get('sensitive')}
|
sensitive={status.get('sensitive')}
|
||||||
media={status.get('media_attachments')}
|
media={status.get('media_attachments')}
|
||||||
|
lang={status.get('language')}
|
||||||
letterbox={settings.getIn(['media', 'letterbox'])}
|
letterbox={settings.getIn(['media', 'letterbox'])}
|
||||||
fullwidth={settings.getIn(['media', 'fullwidth'])}
|
fullwidth={settings.getIn(['media', 'fullwidth'])}
|
||||||
hidden={!expanded}
|
hidden={!expanded}
|
||||||
|
@ -229,7 +232,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.get('poll')) {
|
if (status.get('poll')) {
|
||||||
contentMedia.push(<PollContainer pollId={status.get('poll')} />);
|
contentMedia.push(<PollContainer pollId={status.get('poll')} lang={status.get('language')} />);
|
||||||
contentMediaIcons.push('tasks');
|
contentMediaIcons.push('tasks');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,15 +7,17 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import Footer from 'flavours/glitch/features/picture_in_picture/components/footer';
|
import Footer from 'flavours/glitch/features/picture_in_picture/components/footer';
|
||||||
|
|
||||||
const mapStateToProps = (state, { statusId }) => ({
|
const mapStateToProps = (state, { statusId }) => ({
|
||||||
|
language: state.getIn(['statuses', statusId, 'language']),
|
||||||
accountStaticAvatar: state.getIn(['accounts', state.getIn(['statuses', statusId, 'account']), 'avatar_static']),
|
accountStaticAvatar: state.getIn(['accounts', state.getIn(['statuses', statusId, 'account']), 'avatar_static']),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default @connect(mapStateToProps)
|
export default @connect(mapStateToProps, null, null, { forwardRef: true })
|
||||||
class AudioModal extends ImmutablePureComponent {
|
class AudioModal extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
media: ImmutablePropTypes.map.isRequired,
|
media: ImmutablePropTypes.map.isRequired,
|
||||||
statusId: PropTypes.string.isRequired,
|
statusId: PropTypes.string.isRequired,
|
||||||
|
language: PropTypes.string,
|
||||||
accountStaticAvatar: PropTypes.string.isRequired,
|
accountStaticAvatar: PropTypes.string.isRequired,
|
||||||
options: PropTypes.shape({
|
options: PropTypes.shape({
|
||||||
autoPlay: PropTypes.bool,
|
autoPlay: PropTypes.bool,
|
||||||
|
@ -29,7 +31,7 @@ class AudioModal extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { media, accountStaticAvatar, statusId, onClose } = this.props;
|
const { media, language, accountStaticAvatar, statusId, onClose } = this.props;
|
||||||
const options = this.props.options || {};
|
const options = this.props.options || {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -38,6 +40,7 @@ class AudioModal extends ImmutablePureComponent {
|
||||||
<Audio
|
<Audio
|
||||||
src={media.get('url')}
|
src={media.get('url')}
|
||||||
alt={media.get('description')}
|
alt={media.get('description')}
|
||||||
|
lang={language}
|
||||||
duration={media.getIn(['meta', 'original', 'duration'], 0)}
|
duration={media.getIn(['meta', 'original', 'duration'], 0)}
|
||||||
height={150}
|
height={150}
|
||||||
poster={media.get('preview_url') || accountStaticAvatar}
|
poster={media.get('preview_url') || accountStaticAvatar}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp';
|
||||||
import MediaAttachments from 'flavours/glitch/components/media_attachments';
|
import MediaAttachments from 'flavours/glitch/components/media_attachments';
|
||||||
|
|
||||||
const mapStateToProps = (state, { statusId }) => ({
|
const mapStateToProps = (state, { statusId }) => ({
|
||||||
|
language: state.getIn(['statuses', statusId, 'language']),
|
||||||
versions: state.getIn(['history', statusId, 'items']),
|
versions: state.getIn(['history', statusId, 'items']),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -30,11 +31,12 @@ class CompareHistoryModal extends React.PureComponent {
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
index: PropTypes.number.isRequired,
|
index: PropTypes.number.isRequired,
|
||||||
statusId: PropTypes.string.isRequired,
|
statusId: PropTypes.string.isRequired,
|
||||||
|
language: PropTypes.string.isRequired,
|
||||||
versions: ImmutablePropTypes.list.isRequired,
|
versions: ImmutablePropTypes.list.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { index, versions, onClose } = this.props;
|
const { index, versions, language, onClose } = this.props;
|
||||||
const currentVersion = versions.get(index);
|
const currentVersion = versions.get(index);
|
||||||
|
|
||||||
const emojiMap = currentVersion.get('emojis').reduce((obj, emoji) => {
|
const emojiMap = currentVersion.get('emojis').reduce((obj, emoji) => {
|
||||||
|
@ -65,12 +67,12 @@ class CompareHistoryModal extends React.PureComponent {
|
||||||
<div className='status__content'>
|
<div className='status__content'>
|
||||||
{currentVersion.get('spoiler_text').length > 0 && (
|
{currentVersion.get('spoiler_text').length > 0 && (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<div className='translate' dangerouslySetInnerHTML={spoilerContent} />
|
<div className='translate' dangerouslySetInnerHTML={spoilerContent} lang={language} />
|
||||||
<hr />
|
<hr />
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className='status__content__text status__content__text--visible translate' dangerouslySetInnerHTML={content} />
|
<div className='status__content__text status__content__text--visible translate' dangerouslySetInnerHTML={content} lang={language} />
|
||||||
|
|
||||||
{!!currentVersion.get('poll') && (
|
{!!currentVersion.get('poll') && (
|
||||||
<div className='poll'>
|
<div className='poll'>
|
||||||
|
@ -82,6 +84,7 @@ class CompareHistoryModal extends React.PureComponent {
|
||||||
<span
|
<span
|
||||||
className='poll__option__text translate'
|
className='poll__option__text translate'
|
||||||
dangerouslySetInnerHTML={{ __html: emojify(escapeTextContentForBrowser(option.get('title')), emojiMap) }}
|
dangerouslySetInnerHTML={{ __html: emojify(escapeTextContentForBrowser(option.get('title')), emojiMap) }}
|
||||||
|
lang={language}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
@ -89,7 +92,7 @@ class CompareHistoryModal extends React.PureComponent {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<MediaAttachments status={currentVersion} />
|
<MediaAttachments status={currentVersion} lang={language} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -23,8 +23,8 @@ const mapDispatchToProps = (dispatch) => ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default @connect(null, mapDispatchToProps)
|
export default @withRouter
|
||||||
@withRouter
|
@connect(null, mapDispatchToProps)
|
||||||
class Header extends React.PureComponent {
|
class Header extends React.PureComponent {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
|
|
|
@ -8,6 +8,7 @@ export default class ImageLoader extends PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
alt: PropTypes.string,
|
alt: PropTypes.string,
|
||||||
|
lang: PropTypes.string,
|
||||||
src: PropTypes.string.isRequired,
|
src: PropTypes.string.isRequired,
|
||||||
previewSrc: PropTypes.string,
|
previewSrc: PropTypes.string,
|
||||||
width: PropTypes.number,
|
width: PropTypes.number,
|
||||||
|
@ -18,6 +19,7 @@ export default class ImageLoader extends PureComponent {
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
alt: '',
|
alt: '',
|
||||||
|
lang: '',
|
||||||
width: null,
|
width: null,
|
||||||
height: null,
|
height: null,
|
||||||
};
|
};
|
||||||
|
@ -129,7 +131,7 @@ export default class ImageLoader extends PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { alt, src, width, height, onClick } = this.props;
|
const { alt, lang, src, width, height, onClick } = this.props;
|
||||||
const { loading } = this.state;
|
const { loading } = this.state;
|
||||||
|
|
||||||
const className = classNames('image-loader', {
|
const className = classNames('image-loader', {
|
||||||
|
@ -154,6 +156,7 @@ export default class ImageLoader extends PureComponent {
|
||||||
) : (
|
) : (
|
||||||
<ZoomableImage
|
<ZoomableImage
|
||||||
alt={alt}
|
alt={alt}
|
||||||
|
lang={lang}
|
||||||
src={src}
|
src={src}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
width={width}
|
width={width}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import ReactSwipeableViews from 'react-swipeable-views';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Video from 'flavours/glitch/features/video';
|
import Video from 'flavours/glitch/features/video';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
import IconButton from 'flavours/glitch/components/icon_button';
|
import IconButton from 'flavours/glitch/components/icon_button';
|
||||||
|
@ -20,7 +21,12 @@ const messages = defineMessages({
|
||||||
next: { id: 'lightbox.next', defaultMessage: 'Next' },
|
next: { id: 'lightbox.next', defaultMessage: 'Next' },
|
||||||
});
|
});
|
||||||
|
|
||||||
export default @injectIntl
|
const mapStateToProps = (state, { statusId }) => ({
|
||||||
|
language: state.getIn(['statuses', statusId, 'language']),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default @connect(mapStateToProps, null, null, { forwardRef: true })
|
||||||
|
@injectIntl
|
||||||
class MediaModal extends ImmutablePureComponent {
|
class MediaModal extends ImmutablePureComponent {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
|
@ -131,7 +137,7 @@ class MediaModal extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { media, statusId, intl, onClose } = this.props;
|
const { media, language, statusId, intl, onClose } = this.props;
|
||||||
const { navigationHidden } = this.state;
|
const { navigationHidden } = this.state;
|
||||||
|
|
||||||
const index = this.getIndex();
|
const index = this.getIndex();
|
||||||
|
@ -151,6 +157,7 @@ class MediaModal extends ImmutablePureComponent {
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
alt={image.get('description')}
|
alt={image.get('description')}
|
||||||
|
lang={language}
|
||||||
key={image.get('url')}
|
key={image.get('url')}
|
||||||
onClick={this.toggleNavigation}
|
onClick={this.toggleNavigation}
|
||||||
zoomButtonHidden={this.state.zoomButtonHidden}
|
zoomButtonHidden={this.state.zoomButtonHidden}
|
||||||
|
@ -173,6 +180,7 @@ class MediaModal extends ImmutablePureComponent {
|
||||||
onCloseVideo={onClose}
|
onCloseVideo={onClose}
|
||||||
detailed
|
detailed
|
||||||
alt={image.get('description')}
|
alt={image.get('description')}
|
||||||
|
lang={language}
|
||||||
key={image.get('url')}
|
key={image.get('url')}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -184,6 +192,7 @@ class MediaModal extends ImmutablePureComponent {
|
||||||
height={height}
|
height={height}
|
||||||
key={image.get('preview_url')}
|
key={image.get('preview_url')}
|
||||||
alt={image.get('description')}
|
alt={image.get('description')}
|
||||||
|
lang={language}
|
||||||
onClick={this.toggleNavigation}
|
onClick={this.toggleNavigation}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -78,8 +78,8 @@ class NavigationPanel extends React.Component {
|
||||||
{signedIn && (
|
{signedIn && (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<ColumnLink transparent to='/conversations' icon='at' text={intl.formatMessage(messages.direct)} />
|
<ColumnLink transparent to='/conversations' icon='at' text={intl.formatMessage(messages.direct)} />
|
||||||
<ColumnLink transparent to='/favourites' icon='star' text={intl.formatMessage(messages.favourites)} />
|
|
||||||
<ColumnLink transparent to='/bookmarks' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} />
|
<ColumnLink transparent to='/bookmarks' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} />
|
||||||
|
<ColumnLink transparent to='/favourites' icon='star' text={intl.formatMessage(messages.favourites)} />
|
||||||
<ColumnLink transparent to='/lists' icon='list-ul' text={intl.formatMessage(messages.lists)} />
|
<ColumnLink transparent to='/lists' icon='list-ul' text={intl.formatMessage(messages.lists)} />
|
||||||
|
|
||||||
<ListPanel />
|
<ListPanel />
|
||||||
|
|
|
@ -2,11 +2,17 @@ import React from 'react';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Video from 'flavours/glitch/features/video';
|
import Video from 'flavours/glitch/features/video';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import Footer from 'flavours/glitch/features/picture_in_picture/components/footer';
|
import Footer from 'flavours/glitch/features/picture_in_picture/components/footer';
|
||||||
import { getAverageFromBlurhash } from 'flavours/glitch/blurhash';
|
import { getAverageFromBlurhash } from 'flavours/glitch/blurhash';
|
||||||
|
|
||||||
export default class VideoModal extends ImmutablePureComponent {
|
const mapStateToProps = (state, { statusId }) => ({
|
||||||
|
language: state.getIn(['statuses', statusId, 'language']),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default @connect(mapStateToProps, null, null, { forwardRef: true })
|
||||||
|
class VideoModal extends ImmutablePureComponent {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
router: PropTypes.object,
|
router: PropTypes.object,
|
||||||
|
@ -15,6 +21,7 @@ export default class VideoModal extends ImmutablePureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
media: ImmutablePropTypes.map.isRequired,
|
media: ImmutablePropTypes.map.isRequired,
|
||||||
statusId: PropTypes.string,
|
statusId: PropTypes.string,
|
||||||
|
language: PropTypes.string,
|
||||||
options: PropTypes.shape({
|
options: PropTypes.shape({
|
||||||
startTime: PropTypes.number,
|
startTime: PropTypes.number,
|
||||||
autoPlay: PropTypes.bool,
|
autoPlay: PropTypes.bool,
|
||||||
|
@ -35,7 +42,7 @@ export default class VideoModal extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { media, statusId, onClose } = this.props;
|
const { media, statusId, language, onClose } = this.props;
|
||||||
const options = this.props.options || {};
|
const options = this.props.options || {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -53,6 +60,7 @@ export default class VideoModal extends ImmutablePureComponent {
|
||||||
autoFocus
|
autoFocus
|
||||||
detailed
|
detailed
|
||||||
alt={media.get('description')}
|
alt={media.get('description')}
|
||||||
|
lang={language}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -96,6 +96,7 @@ class ZoomableImage extends React.PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
alt: PropTypes.string,
|
alt: PropTypes.string,
|
||||||
|
lang: PropTypes.string,
|
||||||
src: PropTypes.string.isRequired,
|
src: PropTypes.string.isRequired,
|
||||||
width: PropTypes.number,
|
width: PropTypes.number,
|
||||||
height: PropTypes.number,
|
height: PropTypes.number,
|
||||||
|
@ -106,6 +107,7 @@ class ZoomableImage extends React.PureComponent {
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
alt: '',
|
alt: '',
|
||||||
|
lang: '',
|
||||||
width: null,
|
width: null,
|
||||||
height: null,
|
height: null,
|
||||||
};
|
};
|
||||||
|
@ -403,7 +405,7 @@ class ZoomableImage extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { alt, src, width, height, intl } = this.props;
|
const { alt, lang, src, width, height, intl } = this.props;
|
||||||
const { scale, lockTranslate } = this.state;
|
const { scale, lockTranslate } = this.state;
|
||||||
const overflow = scale === MIN_SCALE ? 'hidden' : 'scroll';
|
const overflow = scale === MIN_SCALE ? 'hidden' : 'scroll';
|
||||||
const zoomButtonShouldHide = this.state.navigationHidden || this.props.zoomButtonHidden || this.state.zoomMatrix.rate <= MIN_SCALE ? 'media-modal__zoom-button--hidden' : '';
|
const zoomButtonShouldHide = this.state.navigationHidden || this.props.zoomButtonHidden || this.state.zoomMatrix.rate <= MIN_SCALE ? 'media-modal__zoom-button--hidden' : '';
|
||||||
|
@ -431,6 +433,7 @@ class ZoomableImage extends React.PureComponent {
|
||||||
ref={this.setImageRef}
|
ref={this.setImageRef}
|
||||||
alt={alt}
|
alt={alt}
|
||||||
title={alt}
|
title={alt}
|
||||||
|
lang={lang}
|
||||||
src={src}
|
src={src}
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { debounce } from 'lodash';
|
||||||
import { uploadCompose, resetCompose, changeComposeSpoilerness } from 'flavours/glitch/actions/compose';
|
import { uploadCompose, resetCompose, changeComposeSpoilerness } from 'flavours/glitch/actions/compose';
|
||||||
import { expandHomeTimeline } from 'flavours/glitch/actions/timelines';
|
import { expandHomeTimeline } from 'flavours/glitch/actions/timelines';
|
||||||
import { expandNotifications, notificationsSetVisibility } from 'flavours/glitch/actions/notifications';
|
import { expandNotifications, notificationsSetVisibility } from 'flavours/glitch/actions/notifications';
|
||||||
import { fetchServer } from 'flavours/glitch/actions/server';
|
import { fetchServer, fetchServerTranslationLanguages } from 'flavours/glitch/actions/server';
|
||||||
import { clearHeight } from 'flavours/glitch/actions/height_cache';
|
import { clearHeight } from 'flavours/glitch/actions/height_cache';
|
||||||
import { changeLayout } from 'flavours/glitch/actions/app';
|
import { changeLayout } from 'flavours/glitch/actions/app';
|
||||||
import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'flavours/glitch/actions/markers';
|
import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'flavours/glitch/actions/markers';
|
||||||
|
@ -419,6 +419,7 @@ class UI extends React.Component {
|
||||||
this.props.dispatch(fetchMarkers());
|
this.props.dispatch(fetchMarkers());
|
||||||
this.props.dispatch(expandHomeTimeline());
|
this.props.dispatch(expandHomeTimeline());
|
||||||
this.props.dispatch(expandNotifications());
|
this.props.dispatch(expandNotifications());
|
||||||
|
this.props.dispatch(fetchServerTranslationLanguages());
|
||||||
|
|
||||||
setTimeout(() => this.props.dispatch(fetchServer()), 3000);
|
setTimeout(() => this.props.dispatch(fetchServer()), 3000);
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,6 +101,7 @@ class Video extends React.PureComponent {
|
||||||
frameRate: PropTypes.string,
|
frameRate: PropTypes.string,
|
||||||
src: PropTypes.string.isRequired,
|
src: PropTypes.string.isRequired,
|
||||||
alt: PropTypes.string,
|
alt: PropTypes.string,
|
||||||
|
lang: PropTypes.string,
|
||||||
width: PropTypes.number,
|
width: PropTypes.number,
|
||||||
height: PropTypes.number,
|
height: PropTypes.number,
|
||||||
sensitive: PropTypes.bool,
|
sensitive: PropTypes.bool,
|
||||||
|
@ -538,7 +539,7 @@ class Video extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, letterbox, fullwidth, detailed, sensitive, editable, blurhash, autoFocus } = this.props;
|
const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, lang, letterbox, fullwidth, detailed, sensitive, editable, blurhash, autoFocus } = this.props;
|
||||||
const { containerWidth, currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
|
const { containerWidth, currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
|
||||||
const progress = Math.min((currentTime / duration) * 100, 100);
|
const progress = Math.min((currentTime / duration) * 100, 100);
|
||||||
const playerStyle = {};
|
const playerStyle = {};
|
||||||
|
@ -603,6 +604,7 @@ class Video extends React.PureComponent {
|
||||||
tabIndex='0'
|
tabIndex='0'
|
||||||
aria-label={alt}
|
aria-label={alt}
|
||||||
title={alt}
|
title={alt}
|
||||||
|
lang={lang}
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
volume={volume}
|
volume={volume}
|
||||||
|
|
|
@ -67,7 +67,6 @@
|
||||||
"moved_to_warning": "This account is marked as moved to {moved_to_link}, and may thus not accept new follows.",
|
"moved_to_warning": "This account is marked as moved to {moved_to_link}, and may thus not accept new follows.",
|
||||||
"navigation_bar.app_settings": "App settings",
|
"navigation_bar.app_settings": "App settings",
|
||||||
"navigation_bar.featured_users": "Featured users",
|
"navigation_bar.featured_users": "Featured users",
|
||||||
"navigation_bar.info": "Extended information",
|
|
||||||
"navigation_bar.keyboard_shortcuts": "Keyboard shortcuts",
|
"navigation_bar.keyboard_shortcuts": "Keyboard shortcuts",
|
||||||
"navigation_bar.misc": "Misc",
|
"navigation_bar.misc": "Misc",
|
||||||
"notification.markForDeletion": "Mark for deletion",
|
"notification.markForDeletion": "Mark for deletion",
|
||||||
|
|
|
@ -8,12 +8,13 @@
|
||||||
"advanced_options.icon_title": "Opciones avanzadas",
|
"advanced_options.icon_title": "Opciones avanzadas",
|
||||||
"advanced_options.local-only.long": "No publicar a otras instancias",
|
"advanced_options.local-only.long": "No publicar a otras instancias",
|
||||||
"advanced_options.local-only.short": "Local",
|
"advanced_options.local-only.short": "Local",
|
||||||
"advanced_options.local-only.tooltip": "Este toot es local",
|
"advanced_options.local-only.tooltip": "Esta publicación es local",
|
||||||
"advanced_options.threaded_mode.long": "Al publicar abre automáticamente una respuesta",
|
"advanced_options.threaded_mode.long": "Al publicar abre automáticamente una respuesta",
|
||||||
"advanced_options.threaded_mode.short": "Modo hilo",
|
"advanced_options.threaded_mode.short": "Modo hilo",
|
||||||
"advanced_options.threaded_mode.tooltip": "Modo hilo habilitado",
|
"advanced_options.threaded_mode.tooltip": "Modo hilo habilitado",
|
||||||
|
"boost_modal.missing_description": "Esta publicación contiene medios sin descripción",
|
||||||
"column.favourited_by": "Marcado como favorito por",
|
"column.favourited_by": "Marcado como favorito por",
|
||||||
"column.reblogged_by": "Retooteado por",
|
"column.reblogged_by": "Impulsado por",
|
||||||
"column_header.profile": "Perfil",
|
"column_header.profile": "Perfil",
|
||||||
"column_subheading.lists": "Listas",
|
"column_subheading.lists": "Listas",
|
||||||
"column_subheading.navigation": "Navegación",
|
"column_subheading.navigation": "Navegación",
|
||||||
|
@ -50,26 +51,26 @@
|
||||||
"settings.always_show_spoilers_field": "Siempre mostrar el campo de advertencia de contenido",
|
"settings.always_show_spoilers_field": "Siempre mostrar el campo de advertencia de contenido",
|
||||||
"settings.auto_collapse": "Colapsar automáticamente",
|
"settings.auto_collapse": "Colapsar automáticamente",
|
||||||
"settings.auto_collapse_all": "Todo",
|
"settings.auto_collapse_all": "Todo",
|
||||||
"settings.auto_collapse_lengthy": "Toots largos",
|
"settings.auto_collapse_lengthy": "Publicaciones largas",
|
||||||
"settings.auto_collapse_media": "Toots con medios",
|
"settings.auto_collapse_media": "Publicaciones multimedia",
|
||||||
"settings.auto_collapse_notifications": "Notificaciones",
|
"settings.auto_collapse_notifications": "Notificaciones",
|
||||||
"settings.auto_collapse_reblogs": "Retoots",
|
"settings.auto_collapse_reblogs": "Impulsos",
|
||||||
"settings.auto_collapse_replies": "Respuestas",
|
"settings.auto_collapse_replies": "Respuestas",
|
||||||
"settings.close": "Cerrar",
|
"settings.close": "Cerrar",
|
||||||
"settings.collapsed_statuses": "Toots colapsados",
|
"settings.collapsed_statuses": "Publicaciones colapsadas",
|
||||||
"settings.compose_box_opts": "Cuadro de redacción",
|
"settings.compose_box_opts": "Cuadro de redacción",
|
||||||
"settings.confirm_before_clearing_draft": "Mostrar diálogo de confirmación antes de sobreescribir un mensaje estabas escribiendo",
|
"settings.confirm_before_clearing_draft": "Mostrar diálogo de confirmación antes de sobreescribir el mensaje siendo redactado",
|
||||||
"settings.confirm_boost_missing_media_description": "Mostrar diálogo de confirmación antes de retootear publicaciones con medios sin descripción",
|
"settings.confirm_boost_missing_media_description": "Mostrar diálogo de confirmación antes de impulsar publicaciones con medios sin descripciones",
|
||||||
"settings.confirm_missing_media_description": "Mostrar diálogo de confirmación antes de publicar toots con medios sin descripción",
|
"settings.confirm_missing_media_description": "Mostrar diálogo de confirmación antes de enviar publicaciones con medios sin descripciones",
|
||||||
"settings.content_warnings": "Advertencias de contenido",
|
"settings.content_warnings": "Advertencias de contenido",
|
||||||
"settings.content_warnings.regexp": "Regexp (expresión regular)",
|
"settings.content_warnings.regexp": "Regexp (expresión regular)",
|
||||||
"settings.content_warnings_filter": "No descolapsar estas advertencias de contenido:",
|
"settings.content_warnings_filter": "No descolapsar estas advertencias de contenido:",
|
||||||
"settings.enable_collapsed": "Habilitar toots colapsados",
|
"settings.enable_collapsed": "Habilitar publicaciones colapsadas",
|
||||||
"settings.enable_content_warnings_auto_unfold": "Descolapsar automáticamente advertencias de contenido",
|
"settings.enable_content_warnings_auto_unfold": "Desplegar automáticamente advertencias de contenido",
|
||||||
"settings.hicolor_privacy_icons": "Íconos de privacidad más visibles",
|
"settings.hicolor_privacy_icons": "Íconos de privacidad más visibles",
|
||||||
"settings.image_backgrounds": "Fondos de imágenes",
|
"settings.image_backgrounds": "Fondos de imágenes",
|
||||||
"settings.image_backgrounds_media": "Vista previa de medios de toots colapsados",
|
"settings.image_backgrounds_media": "Vista previa de medios de publicaciones colapsadas",
|
||||||
"settings.image_backgrounds_users": "Darle fondo de imagen a toots colapsados",
|
"settings.image_backgrounds_users": "Darle fondo de imagen a publicaciones colapsadas",
|
||||||
"settings.inline_preview_cards": "Vista previa para enlaces externos",
|
"settings.inline_preview_cards": "Vista previa para enlaces externos",
|
||||||
"settings.layout": "Diseño",
|
"settings.layout": "Diseño",
|
||||||
"settings.layout_opts": "Opciones de diseño",
|
"settings.layout_opts": "Opciones de diseño",
|
||||||
|
@ -91,21 +92,21 @@
|
||||||
"settings.rewrite_mentions_acct": "Reescribir con nombre de usuarix y dominio (para cuentas remotas)",
|
"settings.rewrite_mentions_acct": "Reescribir con nombre de usuarix y dominio (para cuentas remotas)",
|
||||||
"settings.rewrite_mentions_no": "No reescribir menciones",
|
"settings.rewrite_mentions_no": "No reescribir menciones",
|
||||||
"settings.rewrite_mentions_username": "Reescribir con nombre de usuarix",
|
"settings.rewrite_mentions_username": "Reescribir con nombre de usuarix",
|
||||||
"settings.show_action_bar": "Mostrar botones de acción en toots colapsados",
|
"settings.show_action_bar": "Mostrar botones de acción en publicaciones colapsadas",
|
||||||
"settings.show_content_type_choice": "Mostrar selección de tipo de contenido al crear toots",
|
"settings.show_content_type_choice": "Mostrar selección de tipo de contenido al crear publicaciones",
|
||||||
"settings.show_reply_counter": "Mostrar un conteo estimado de respuestas",
|
"settings.show_reply_counter": "Mostrar un conteo estimado de respuestas",
|
||||||
"settings.side_arm": "Botón secundario:",
|
"settings.side_arm": "Botón secundario:",
|
||||||
"settings.side_arm.none": "Ninguno",
|
"settings.side_arm.none": "Ninguno",
|
||||||
"settings.side_arm_reply_mode": "Al responder a un toot, el botón de toot secundario debe:",
|
"settings.side_arm_reply_mode": "Al responder a una publicación, el botón de publicación secundario debe:",
|
||||||
"settings.side_arm_reply_mode.copy": "Copiar opción de privacidad del toot al que estás respondiendo",
|
"settings.side_arm_reply_mode.copy": "Copiar opción de privacidad de la publicación a la que estás respondiendo",
|
||||||
"settings.side_arm_reply_mode.keep": "Conservar opción de privacidad",
|
"settings.side_arm_reply_mode.keep": "Conservar opción de privacidad",
|
||||||
"settings.side_arm_reply_mode.restrict": "Restringir la opción de privacidad a la misma del toot al que estás respondiendo",
|
"settings.side_arm_reply_mode.restrict": "Restringir la opción de privacidad a la misma de la publicación a la que estás respondiendo",
|
||||||
"settings.swipe_to_change_columns": "Permitir deslizar para cambiar columnas (Sólo en móvil)",
|
"settings.swipe_to_change_columns": "Permitir deslizar para cambiar columnas (Sólo en móvil)",
|
||||||
"settings.tag_misleading_links": "Marcar enlaces engañosos",
|
"settings.tag_misleading_links": "Marcar enlaces engañosos",
|
||||||
"settings.tag_misleading_links.hint": "Añadir una indicación visual indicando el destino de los enlace que no los mencionen explícitamente",
|
"settings.tag_misleading_links.hint": "Añadir una indicación visual indicando el destino de los enlace que no los mencionen explícitamente",
|
||||||
"settings.wide_view": "Vista amplia (solo modo de escritorio)",
|
"settings.wide_view": "Vista amplia (solo modo de escritorio)",
|
||||||
"status.collapse": "Colapsar",
|
"status.collapse": "Colapsar",
|
||||||
"status.in_reply_to": "Este toot es una respuesta",
|
"status.in_reply_to": "Esta publicación es una respuesta",
|
||||||
"status.is_poll": "Este toot es una encuesta",
|
"status.is_poll": "Esta publicación es una encuesta",
|
||||||
"status.uncollapse": "Descolapsar"
|
"status.uncollapse": "Descolapsar"
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,9 @@ import {
|
||||||
SERVER_FETCH_REQUEST,
|
SERVER_FETCH_REQUEST,
|
||||||
SERVER_FETCH_SUCCESS,
|
SERVER_FETCH_SUCCESS,
|
||||||
SERVER_FETCH_FAIL,
|
SERVER_FETCH_FAIL,
|
||||||
|
SERVER_TRANSLATION_LANGUAGES_FETCH_REQUEST,
|
||||||
|
SERVER_TRANSLATION_LANGUAGES_FETCH_SUCCESS,
|
||||||
|
SERVER_TRANSLATION_LANGUAGES_FETCH_FAIL,
|
||||||
EXTENDED_DESCRIPTION_REQUEST,
|
EXTENDED_DESCRIPTION_REQUEST,
|
||||||
EXTENDED_DESCRIPTION_SUCCESS,
|
EXTENDED_DESCRIPTION_SUCCESS,
|
||||||
EXTENDED_DESCRIPTION_FAIL,
|
EXTENDED_DESCRIPTION_FAIL,
|
||||||
|
@ -35,6 +38,12 @@ export default function server(state = initialState, action) {
|
||||||
return state.set('server', fromJS(action.server)).setIn(['server', 'isLoading'], false);
|
return state.set('server', fromJS(action.server)).setIn(['server', 'isLoading'], false);
|
||||||
case SERVER_FETCH_FAIL:
|
case SERVER_FETCH_FAIL:
|
||||||
return state.setIn(['server', 'isLoading'], false);
|
return state.setIn(['server', 'isLoading'], false);
|
||||||
|
case SERVER_TRANSLATION_LANGUAGES_FETCH_REQUEST:
|
||||||
|
return state.setIn(['translationLanguages', 'isLoading'], true);
|
||||||
|
case SERVER_TRANSLATION_LANGUAGES_FETCH_SUCCESS:
|
||||||
|
return state.setIn(['translationLanguages', 'items'], fromJS(action.translationLanguages)).setIn(['translationLanguages', 'isLoading'], false);
|
||||||
|
case SERVER_TRANSLATION_LANGUAGES_FETCH_FAIL:
|
||||||
|
return state.setIn(['translationLanguages', 'isLoading'], false);
|
||||||
case EXTENDED_DESCRIPTION_REQUEST:
|
case EXTENDED_DESCRIPTION_REQUEST:
|
||||||
return state.setIn(['extendedDescription', 'isLoading'], true);
|
return state.setIn(['extendedDescription', 'isLoading'], true);
|
||||||
case EXTENDED_DESCRIPTION_SUCCESS:
|
case EXTENDED_DESCRIPTION_SUCCESS:
|
||||||
|
|
|
@ -168,8 +168,7 @@
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
|
|
||||||
p,
|
p,
|
||||||
pre,
|
pre {
|
||||||
blockquote {
|
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
|
|
||||||
|
@ -178,79 +177,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5 {
|
|
||||||
margin-top: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1,
|
|
||||||
h2 {
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5 {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquote {
|
|
||||||
padding-left: 10px;
|
|
||||||
border-left: 3px solid $inverted-text-color;
|
|
||||||
color: $inverted-text-color;
|
|
||||||
white-space: normal;
|
|
||||||
|
|
||||||
p:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
b,
|
|
||||||
strong {
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
em,
|
|
||||||
i {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub {
|
|
||||||
font-size: smaller;
|
|
||||||
vertical-align: sub;
|
|
||||||
}
|
|
||||||
|
|
||||||
sup {
|
|
||||||
font-size: smaller;
|
|
||||||
vertical-align: super;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul,
|
|
||||||
ol {
|
|
||||||
margin-left: 1em;
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style-type: disc;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol {
|
|
||||||
list-style-type: decimal;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: $lighter-text-color;
|
color: $lighter-text-color;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
|
@ -1628,6 +1628,7 @@ button.icon-button.active i.fa-retweet {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
color: $secondary-text-color;
|
color: $secondary-text-color;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|
|
@ -26,11 +26,13 @@
|
||||||
img {
|
img {
|
||||||
margin-top: 2em;
|
margin-top: 2em;
|
||||||
margin-bottom: 2em;
|
margin-bottom: 2em;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
video {
|
video {
|
||||||
margin-top: 2em;
|
margin-top: 2em;
|
||||||
margin-bottom: 2em;
|
margin-bottom: 2em;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
figure {
|
figure {
|
||||||
|
|
|
@ -68,8 +68,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
p,
|
p,
|
||||||
pre,
|
pre {
|
||||||
blockquote {
|
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
unicode-bidi: plaintext;
|
unicode-bidi: plaintext;
|
||||||
|
|
|
@ -21,3 +21,4 @@
|
||||||
@import 'accessibility';
|
@import 'accessibility';
|
||||||
@import 'rtl';
|
@import 'rtl';
|
||||||
@import 'dashboard';
|
@import 'dashboard';
|
||||||
|
@import 'rich_text';
|
||||||
|
|
99
app/javascript/flavours/glitch/styles/rich_text.scss
Normal file
99
app/javascript/flavours/glitch/styles/rich_text.scss
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
.status__content__text,
|
||||||
|
.e-content,
|
||||||
|
.reply-indicator__content {
|
||||||
|
pre,
|
||||||
|
blockquote {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
unicode-bidi: plaintext;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
padding-left: 10px;
|
||||||
|
border-left: 3px solid $darker-text-color;
|
||||||
|
color: $darker-text-color;
|
||||||
|
white-space: normal;
|
||||||
|
|
||||||
|
p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > ul,
|
||||||
|
& > ol {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5 {
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2 {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5 {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
em,
|
||||||
|
i {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
font-size: smaller;
|
||||||
|
vertical-align: sub;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
font-size: smaller;
|
||||||
|
vertical-align: super;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul,
|
||||||
|
ol {
|
||||||
|
margin-left: 2em;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style-type: disc;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol {
|
||||||
|
list-style-type: decimal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-indicator__content {
|
||||||
|
blockquote {
|
||||||
|
border-left-color: $inverted-text-color;
|
||||||
|
color: $inverted-text-color;
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,10 @@ export const SERVER_FETCH_REQUEST = 'Server_FETCH_REQUEST';
|
||||||
export const SERVER_FETCH_SUCCESS = 'Server_FETCH_SUCCESS';
|
export const SERVER_FETCH_SUCCESS = 'Server_FETCH_SUCCESS';
|
||||||
export const SERVER_FETCH_FAIL = 'Server_FETCH_FAIL';
|
export const SERVER_FETCH_FAIL = 'Server_FETCH_FAIL';
|
||||||
|
|
||||||
|
export const SERVER_TRANSLATION_LANGUAGES_FETCH_REQUEST = 'SERVER_TRANSLATION_LANGUAGES_FETCH_REQUEST';
|
||||||
|
export const SERVER_TRANSLATION_LANGUAGES_FETCH_SUCCESS = 'SERVER_TRANSLATION_LANGUAGES_FETCH_SUCCESS';
|
||||||
|
export const SERVER_TRANSLATION_LANGUAGES_FETCH_FAIL = 'SERVER_TRANSLATION_LANGUAGES_FETCH_FAIL';
|
||||||
|
|
||||||
export const EXTENDED_DESCRIPTION_REQUEST = 'EXTENDED_DESCRIPTION_REQUEST';
|
export const EXTENDED_DESCRIPTION_REQUEST = 'EXTENDED_DESCRIPTION_REQUEST';
|
||||||
export const EXTENDED_DESCRIPTION_SUCCESS = 'EXTENDED_DESCRIPTION_SUCCESS';
|
export const EXTENDED_DESCRIPTION_SUCCESS = 'EXTENDED_DESCRIPTION_SUCCESS';
|
||||||
export const EXTENDED_DESCRIPTION_FAIL = 'EXTENDED_DESCRIPTION_FAIL';
|
export const EXTENDED_DESCRIPTION_FAIL = 'EXTENDED_DESCRIPTION_FAIL';
|
||||||
|
@ -37,6 +41,29 @@ const fetchServerFail = error => ({
|
||||||
error,
|
error,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const fetchServerTranslationLanguages = () => (dispatch, getState) => {
|
||||||
|
dispatch(fetchServerTranslationLanguagesRequest());
|
||||||
|
|
||||||
|
api(getState)
|
||||||
|
.get('/api/v1/instance/translation_languages').then(({ data }) => {
|
||||||
|
dispatch(fetchServerTranslationLanguagesSuccess(data));
|
||||||
|
}).catch(err => dispatch(fetchServerTranslationLanguagesFail(err)));
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchServerTranslationLanguagesRequest = () => ({
|
||||||
|
type: SERVER_TRANSLATION_LANGUAGES_FETCH_REQUEST,
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchServerTranslationLanguagesSuccess = translationLanguages => ({
|
||||||
|
type: SERVER_TRANSLATION_LANGUAGES_FETCH_SUCCESS,
|
||||||
|
translationLanguages,
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchServerTranslationLanguagesFail = error => ({
|
||||||
|
type: SERVER_TRANSLATION_LANGUAGES_FETCH_FAIL,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
|
||||||
export const fetchExtendedDescription = () => (dispatch, getState) => {
|
export const fetchExtendedDescription = () => (dispatch, getState) => {
|
||||||
dispatch(fetchExtendedDescriptionRequest());
|
dispatch(fetchExtendedDescriptionRequest());
|
||||||
|
|
||||||
|
|
|
@ -15,10 +15,10 @@ export default class ColumnBackButton extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
handleClick = () => {
|
handleClick = () => {
|
||||||
if (window.history && window.history.length === 1) {
|
if (window.history && window.history.state) {
|
||||||
this.context.router.history.push('/');
|
|
||||||
} else {
|
|
||||||
this.context.router.history.goBack();
|
this.context.router.history.goBack();
|
||||||
|
} else {
|
||||||
|
this.context.router.history.push('/');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -43,14 +43,6 @@ class ColumnHeader extends React.PureComponent {
|
||||||
animating: false,
|
animating: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
historyBack = () => {
|
|
||||||
if (window.history && window.history.length === 1) {
|
|
||||||
this.context.router.history.push('/');
|
|
||||||
} else {
|
|
||||||
this.context.router.history.goBack();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleToggleClick = (e) => {
|
handleToggleClick = (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this.setState({ collapsed: !this.state.collapsed, animating: true });
|
this.setState({ collapsed: !this.state.collapsed, animating: true });
|
||||||
|
@ -69,7 +61,11 @@ class ColumnHeader extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
handleBackClick = () => {
|
handleBackClick = () => {
|
||||||
this.historyBack();
|
if (window.history && window.history.state) {
|
||||||
|
this.context.router.history.goBack();
|
||||||
|
} else {
|
||||||
|
this.context.router.history.push('/');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
handleTransitionEnd = () => {
|
handleTransitionEnd = () => {
|
||||||
|
|
|
@ -6,6 +6,7 @@ export default class GIFV extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
src: PropTypes.string.isRequired,
|
src: PropTypes.string.isRequired,
|
||||||
alt: PropTypes.string,
|
alt: PropTypes.string,
|
||||||
|
lang: PropTypes.string,
|
||||||
width: PropTypes.number,
|
width: PropTypes.number,
|
||||||
height: PropTypes.number,
|
height: PropTypes.number,
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
|
@ -35,7 +36,7 @@ export default class GIFV extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { src, width, height, alt } = this.props;
|
const { src, width, height, alt, lang } = this.props;
|
||||||
const { loading } = this.state;
|
const { loading } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -48,6 +49,7 @@ export default class GIFV extends React.PureComponent {
|
||||||
tabIndex='0'
|
tabIndex='0'
|
||||||
aria-label={alt}
|
aria-label={alt}
|
||||||
title={alt}
|
title={alt}
|
||||||
|
lang={lang}
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -58,6 +60,7 @@ export default class GIFV extends React.PureComponent {
|
||||||
tabIndex='0'
|
tabIndex='0'
|
||||||
aria-label={alt}
|
aria-label={alt}
|
||||||
title={alt}
|
title={alt}
|
||||||
|
lang={lang}
|
||||||
muted
|
muted
|
||||||
loop
|
loop
|
||||||
autoPlay
|
autoPlay
|
||||||
|
|
|
@ -10,6 +10,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
status: ImmutablePropTypes.map.isRequired,
|
status: ImmutablePropTypes.map.isRequired,
|
||||||
|
lang: PropTypes.string,
|
||||||
height: PropTypes.number,
|
height: PropTypes.number,
|
||||||
width: PropTypes.number,
|
width: PropTypes.number,
|
||||||
};
|
};
|
||||||
|
@ -48,7 +49,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { status, width, height } = this.props;
|
const { status, lang, width, height } = this.props;
|
||||||
const mediaAttachments = status.get('media_attachments');
|
const mediaAttachments = status.get('media_attachments');
|
||||||
|
|
||||||
if (mediaAttachments.size === 0) {
|
if (mediaAttachments.size === 0) {
|
||||||
|
@ -64,6 +65,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
|
||||||
<Component
|
<Component
|
||||||
src={audio.get('url')}
|
src={audio.get('url')}
|
||||||
alt={audio.get('description')}
|
alt={audio.get('description')}
|
||||||
|
lang={lang || status.get('language')}
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
poster={audio.get('preview_url') || status.getIn(['account', 'avatar_static'])}
|
poster={audio.get('preview_url') || status.getIn(['account', 'avatar_static'])}
|
||||||
|
@ -87,6 +89,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
|
||||||
blurhash={video.get('blurhash')}
|
blurhash={video.get('blurhash')}
|
||||||
src={video.get('url')}
|
src={video.get('url')}
|
||||||
alt={video.get('description')}
|
alt={video.get('description')}
|
||||||
|
lang={lang || status.get('language')}
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
inline
|
inline
|
||||||
|
@ -102,6 +105,7 @@ export default class MediaAttachments extends ImmutablePureComponent {
|
||||||
{Component => (
|
{Component => (
|
||||||
<Component
|
<Component
|
||||||
media={mediaAttachments}
|
media={mediaAttachments}
|
||||||
|
lang={lang || status.get('language')}
|
||||||
sensitive={status.get('sensitive')}
|
sensitive={status.get('sensitive')}
|
||||||
defaultWidth={width}
|
defaultWidth={width}
|
||||||
height={height}
|
height={height}
|
||||||
|
|
|
@ -17,6 +17,7 @@ class Item extends React.PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
attachment: ImmutablePropTypes.map.isRequired,
|
attachment: ImmutablePropTypes.map.isRequired,
|
||||||
|
lang: PropTypes.string,
|
||||||
standalone: PropTypes.bool,
|
standalone: PropTypes.bool,
|
||||||
index: PropTypes.number.isRequired,
|
index: PropTypes.number.isRequired,
|
||||||
size: PropTypes.number.isRequired,
|
size: PropTypes.number.isRequired,
|
||||||
|
@ -78,7 +79,7 @@ class Item extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { attachment, index, size, standalone, displayWidth, visible } = this.props;
|
const { attachment, lang, index, size, standalone, displayWidth, visible } = this.props;
|
||||||
|
|
||||||
let width = 50;
|
let width = 50;
|
||||||
let height = 100;
|
let height = 100;
|
||||||
|
@ -134,7 +135,7 @@ class Item extends React.PureComponent {
|
||||||
if (attachment.get('type') === 'unknown') {
|
if (attachment.get('type') === 'unknown') {
|
||||||
return (
|
return (
|
||||||
<div className={classNames('media-gallery__item', { standalone })} key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}>
|
<div className={classNames('media-gallery__item', { standalone })} key={attachment.get('id')} style={{ left: left, top: top, right: right, bottom: bottom, width: `${width}%`, height: `${height}%` }}>
|
||||||
<a className='media-gallery__item-thumbnail' href={attachment.get('remote_url') || attachment.get('url')} style={{ cursor: 'pointer' }} title={attachment.get('description')} target='_blank' rel='noopener noreferrer'>
|
<a className='media-gallery__item-thumbnail' href={attachment.get('remote_url') || attachment.get('url')} style={{ cursor: 'pointer' }} title={attachment.get('description')} lang={lang} target='_blank' rel='noopener noreferrer'>
|
||||||
<Blurhash
|
<Blurhash
|
||||||
hash={attachment.get('blurhash')}
|
hash={attachment.get('blurhash')}
|
||||||
className='media-gallery__preview'
|
className='media-gallery__preview'
|
||||||
|
@ -174,6 +175,7 @@ class Item extends React.PureComponent {
|
||||||
sizes={sizes}
|
sizes={sizes}
|
||||||
alt={attachment.get('description')}
|
alt={attachment.get('description')}
|
||||||
title={attachment.get('description')}
|
title={attachment.get('description')}
|
||||||
|
lang={lang}
|
||||||
style={{ objectPosition: `${x}% ${y}%` }}
|
style={{ objectPosition: `${x}% ${y}%` }}
|
||||||
onLoad={this.handleImageLoad}
|
onLoad={this.handleImageLoad}
|
||||||
/>
|
/>
|
||||||
|
@ -188,6 +190,7 @@ class Item extends React.PureComponent {
|
||||||
className='media-gallery__item-gifv-thumbnail'
|
className='media-gallery__item-gifv-thumbnail'
|
||||||
aria-label={attachment.get('description')}
|
aria-label={attachment.get('description')}
|
||||||
title={attachment.get('description')}
|
title={attachment.get('description')}
|
||||||
|
lang={lang}
|
||||||
role='application'
|
role='application'
|
||||||
src={attachment.get('url')}
|
src={attachment.get('url')}
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
|
@ -227,6 +230,7 @@ class MediaGallery extends React.PureComponent {
|
||||||
sensitive: PropTypes.bool,
|
sensitive: PropTypes.bool,
|
||||||
standalone: PropTypes.bool,
|
standalone: PropTypes.bool,
|
||||||
media: ImmutablePropTypes.list.isRequired,
|
media: ImmutablePropTypes.list.isRequired,
|
||||||
|
lang: PropTypes.string,
|
||||||
size: PropTypes.object,
|
size: PropTypes.object,
|
||||||
height: PropTypes.number.isRequired,
|
height: PropTypes.number.isRequired,
|
||||||
onOpenMedia: PropTypes.func.isRequired,
|
onOpenMedia: PropTypes.func.isRequired,
|
||||||
|
@ -310,9 +314,8 @@ class MediaGallery extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { media, intl, sensitive, height, defaultWidth, standalone, autoplay } = this.props;
|
const { media, lang, intl, sensitive, height, defaultWidth, standalone, autoplay } = this.props;
|
||||||
const { visible } = this.state;
|
const { visible } = this.state;
|
||||||
|
|
||||||
const width = this.state.width || defaultWidth;
|
const width = this.state.width || defaultWidth;
|
||||||
|
|
||||||
let children, spoilerButton;
|
let children, spoilerButton;
|
||||||
|
@ -333,9 +336,9 @@ class MediaGallery extends React.PureComponent {
|
||||||
const uncached = media.every(attachment => attachment.get('type') === 'unknown');
|
const uncached = media.every(attachment => attachment.get('type') === 'unknown');
|
||||||
|
|
||||||
if (standalone && this.isFullSizeEligible()) {
|
if (standalone && this.isFullSizeEligible()) {
|
||||||
children = <Item standalone autoplay={autoplay} onClick={this.handleClick} attachment={media.get(0)} displayWidth={width} visible={visible} />;
|
children = <Item standalone autoplay={autoplay} onClick={this.handleClick} attachment={media.get(0)} lang={lang} displayWidth={width} visible={visible} />;
|
||||||
} else {
|
} else {
|
||||||
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} autoplay={autoplay} onClick={this.handleClick} attachment={attachment} index={i} size={size} displayWidth={width} visible={visible || uncached} />);
|
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} autoplay={autoplay} onClick={this.handleClick} attachment={attachment} index={i} lang={lang} size={size} displayWidth={width} visible={visible || uncached} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uncached) {
|
if (uncached) {
|
||||||
|
|
|
@ -40,6 +40,7 @@ class Poll extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
poll: ImmutablePropTypes.map,
|
poll: ImmutablePropTypes.map,
|
||||||
|
lang: PropTypes.string,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
refresh: PropTypes.func,
|
refresh: PropTypes.func,
|
||||||
|
@ -126,7 +127,7 @@ class Poll extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
renderOption (option, optionIndex, showResults) {
|
renderOption (option, optionIndex, showResults) {
|
||||||
const { poll, disabled, intl } = this.props;
|
const { poll, lang, disabled, intl } = this.props;
|
||||||
const pollVotesCount = poll.get('voters_count') || poll.get('votes_count');
|
const pollVotesCount = poll.get('voters_count') || poll.get('votes_count');
|
||||||
const percent = pollVotesCount === 0 ? 0 : (option.get('votes_count') / pollVotesCount) * 100;
|
const percent = pollVotesCount === 0 ? 0 : (option.get('votes_count') / pollVotesCount) * 100;
|
||||||
const leading = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') >= other.get('votes_count'));
|
const leading = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') >= other.get('votes_count'));
|
||||||
|
@ -159,6 +160,7 @@ class Poll extends ImmutablePureComponent {
|
||||||
onKeyPress={this.handleOptionKeyPress}
|
onKeyPress={this.handleOptionKeyPress}
|
||||||
aria-checked={active}
|
aria-checked={active}
|
||||||
aria-label={option.get('title')}
|
aria-label={option.get('title')}
|
||||||
|
lang={lang}
|
||||||
data-index={optionIndex}
|
data-index={optionIndex}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -175,6 +177,7 @@ class Poll extends ImmutablePureComponent {
|
||||||
|
|
||||||
<span
|
<span
|
||||||
className='poll__option__text translate'
|
className='poll__option__text translate'
|
||||||
|
lang={lang}
|
||||||
dangerouslySetInnerHTML={{ __html: titleEmojified }}
|
dangerouslySetInnerHTML={{ __html: titleEmojified }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -422,6 +422,7 @@ class Status extends ImmutablePureComponent {
|
||||||
<Component
|
<Component
|
||||||
src={attachment.get('url')}
|
src={attachment.get('url')}
|
||||||
alt={attachment.get('description')}
|
alt={attachment.get('description')}
|
||||||
|
lang={status.get('language')}
|
||||||
poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
|
poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
|
||||||
backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
|
backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
|
||||||
foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])}
|
foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])}
|
||||||
|
@ -451,6 +452,7 @@ class Status extends ImmutablePureComponent {
|
||||||
blurhash={attachment.get('blurhash')}
|
blurhash={attachment.get('blurhash')}
|
||||||
src={attachment.get('url')}
|
src={attachment.get('url')}
|
||||||
alt={attachment.get('description')}
|
alt={attachment.get('description')}
|
||||||
|
lang={status.get('language')}
|
||||||
width={this.props.cachedMediaWidth}
|
width={this.props.cachedMediaWidth}
|
||||||
height={110}
|
height={110}
|
||||||
inline
|
inline
|
||||||
|
@ -470,6 +472,7 @@ class Status extends ImmutablePureComponent {
|
||||||
{Component => (
|
{Component => (
|
||||||
<Component
|
<Component
|
||||||
media={status.get('media_attachments')}
|
media={status.get('media_attachments')}
|
||||||
|
lang={status.get('language')}
|
||||||
sensitive={status.get('sensitive')}
|
sensitive={status.get('sensitive')}
|
||||||
height={110}
|
height={110}
|
||||||
onOpenMedia={this.handleOpenMedia}
|
onOpenMedia={this.handleOpenMedia}
|
||||||
|
|
|
@ -3,10 +3,11 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import PollContainer from 'mastodon/containers/poll_container';
|
import PollContainer from 'mastodon/containers/poll_container';
|
||||||
import Icon from 'mastodon/components/icon';
|
import Icon from 'mastodon/components/icon';
|
||||||
import { autoPlayGif, languages as preloadedLanguages, translationEnabled } from 'mastodon/initial_state';
|
import { autoPlayGif, languages as preloadedLanguages } from 'mastodon/initial_state';
|
||||||
|
|
||||||
const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top)
|
const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top)
|
||||||
|
|
||||||
|
@ -47,7 +48,12 @@ class TranslateButton extends React.PureComponent {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default @injectIntl
|
const mapStateToProps = state => ({
|
||||||
|
languages: state.getIn(['server', 'translationLanguages', 'items']),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default @connect(mapStateToProps)
|
||||||
|
@injectIntl
|
||||||
class StatusContent extends React.PureComponent {
|
class StatusContent extends React.PureComponent {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
|
@ -63,6 +69,7 @@ class StatusContent extends React.PureComponent {
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
collapsable: PropTypes.bool,
|
collapsable: PropTypes.bool,
|
||||||
onCollapsedToggle: PropTypes.func,
|
onCollapsedToggle: PropTypes.func,
|
||||||
|
languages: ImmutablePropTypes.map,
|
||||||
intl: PropTypes.object,
|
intl: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -220,7 +227,9 @@ class StatusContent extends React.PureComponent {
|
||||||
|
|
||||||
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
|
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
|
||||||
const renderReadMore = this.props.onClick && status.get('collapsed');
|
const renderReadMore = this.props.onClick && status.get('collapsed');
|
||||||
const renderTranslate = translationEnabled && this.context.identity.signedIn && this.props.onTranslate && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('contentHtml').length > 0 && status.get('language') !== null && intl.locale !== status.get('language');
|
const contentLocale = intl.locale.replace(/[_-].*/, '');
|
||||||
|
const targetLanguages = this.props.languages?.get(status.get('language') || 'und');
|
||||||
|
const renderTranslate = this.props.onTranslate && this.context.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('contentHtml').length > 0 && targetLanguages?.includes(contentLocale);
|
||||||
|
|
||||||
const content = { __html: status.get('translation') ? status.getIn(['translation', 'content']) : status.get('contentHtml') };
|
const content = { __html: status.get('translation') ? status.getIn(['translation', 'content']) : status.get('contentHtml') };
|
||||||
const spoilerContent = { __html: status.get('spoilerHtml') };
|
const spoilerContent = { __html: status.get('spoilerHtml') };
|
||||||
|
@ -242,7 +251,7 @@ class StatusContent extends React.PureComponent {
|
||||||
);
|
);
|
||||||
|
|
||||||
const poll = !!status.get('poll') && (
|
const poll = !!status.get('poll') && (
|
||||||
<PollContainer pollId={status.get('poll')} />
|
<PollContainer pollId={status.get('poll')} lang={status.get('language')} />
|
||||||
);
|
);
|
||||||
|
|
||||||
if (status.get('spoiler_text').length > 0) {
|
if (status.get('spoiler_text').length > 0) {
|
||||||
|
|
|
@ -58,6 +58,8 @@ const messages = defineMessages({
|
||||||
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' },
|
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' },
|
||||||
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
|
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
|
||||||
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
||||||
|
editConfirm: { id: 'confirmations.edit.confirm', defaultMessage: 'Edit' },
|
||||||
|
editMessage: { id: 'confirmations.edit.message', defaultMessage: 'Editing now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
||||||
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
|
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -160,7 +162,18 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
|
||||||
},
|
},
|
||||||
|
|
||||||
onEdit (status, history) {
|
onEdit (status, history) {
|
||||||
|
dispatch((_, getState) => {
|
||||||
|
let state = getState();
|
||||||
|
if (state.getIn(['compose', 'text']).trim().length !== 0) {
|
||||||
|
dispatch(openModal('CONFIRM', {
|
||||||
|
message: intl.formatMessage(messages.editMessage),
|
||||||
|
confirm: intl.formatMessage(messages.editConfirm),
|
||||||
|
onConfirm: () => dispatch(editStatus(status.get('id'), history)),
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
dispatch(editStatus(status.get('id'), history));
|
dispatch(editStatus(status.get('id'), history));
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onTranslate (status) {
|
onTranslate (status) {
|
||||||
|
|
|
@ -76,6 +76,7 @@ export default class MediaItem extends ImmutablePureComponent {
|
||||||
<img
|
<img
|
||||||
src={attachment.get('preview_url') || attachment.getIn(['account', 'avatar_static'])}
|
src={attachment.get('preview_url') || attachment.getIn(['account', 'avatar_static'])}
|
||||||
alt={attachment.get('description')}
|
alt={attachment.get('description')}
|
||||||
|
lang={status.get('language')}
|
||||||
onLoad={this.handleImageLoad}
|
onLoad={this.handleImageLoad}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -95,6 +96,7 @@ export default class MediaItem extends ImmutablePureComponent {
|
||||||
<img
|
<img
|
||||||
src={attachment.get('preview_url')}
|
src={attachment.get('preview_url')}
|
||||||
alt={attachment.get('description')}
|
alt={attachment.get('description')}
|
||||||
|
lang={status.get('language')}
|
||||||
style={{ objectPosition: `${x}% ${y}%` }}
|
style={{ objectPosition: `${x}% ${y}%` }}
|
||||||
onLoad={this.handleImageLoad}
|
onLoad={this.handleImageLoad}
|
||||||
/>
|
/>
|
||||||
|
@ -105,6 +107,7 @@ export default class MediaItem extends ImmutablePureComponent {
|
||||||
className='media-gallery__item-gifv-thumbnail'
|
className='media-gallery__item-gifv-thumbnail'
|
||||||
aria-label={attachment.get('description')}
|
aria-label={attachment.get('description')}
|
||||||
title={attachment.get('description')}
|
title={attachment.get('description')}
|
||||||
|
lang={status.get('language')}
|
||||||
role='application'
|
role='application'
|
||||||
src={attachment.get('url')}
|
src={attachment.get('url')}
|
||||||
onMouseEnter={this.handleMouseEnter}
|
onMouseEnter={this.handleMouseEnter}
|
||||||
|
|
|
@ -28,6 +28,7 @@ class Audio extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
src: PropTypes.string.isRequired,
|
src: PropTypes.string.isRequired,
|
||||||
alt: PropTypes.string,
|
alt: PropTypes.string,
|
||||||
|
lang: PropTypes.string,
|
||||||
poster: PropTypes.string,
|
poster: PropTypes.string,
|
||||||
duration: PropTypes.number,
|
duration: PropTypes.number,
|
||||||
width: PropTypes.number,
|
width: PropTypes.number,
|
||||||
|
@ -458,7 +459,7 @@ class Audio extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { src, intl, alt, editable, autoPlay, sensitive, blurhash } = this.props;
|
const { src, intl, alt, lang, editable, autoPlay, sensitive, blurhash } = this.props;
|
||||||
const { paused, muted, volume, currentTime, duration, buffer, dragging, revealed } = this.state;
|
const { paused, muted, volume, currentTime, duration, buffer, dragging, revealed } = this.state;
|
||||||
const progress = Math.min((currentTime / duration) * 100, 100);
|
const progress = Math.min((currentTime / duration) * 100, 100);
|
||||||
|
|
||||||
|
@ -503,6 +504,7 @@ class Audio extends React.PureComponent {
|
||||||
onKeyDown={this.handleAudioKeyDown}
|
onKeyDown={this.handleAudioKeyDown}
|
||||||
title={alt}
|
title={alt}
|
||||||
aria-label={alt}
|
aria-label={alt}
|
||||||
|
lang={lang}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className={classNames('spoiler-button', { 'spoiler-button--hidden': revealed || editable })}>
|
<div className={classNames('spoiler-button', { 'spoiler-button--hidden': revealed || editable })}>
|
||||||
|
|
|
@ -143,6 +143,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
||||||
<Audio
|
<Audio
|
||||||
src={attachment.get('url')}
|
src={attachment.get('url')}
|
||||||
alt={attachment.get('description')}
|
alt={attachment.get('description')}
|
||||||
|
lang={status.get('language')}
|
||||||
duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
|
duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
|
||||||
poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
|
poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
|
||||||
backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
|
backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
|
||||||
|
@ -165,6 +166,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
||||||
blurhash={attachment.get('blurhash')}
|
blurhash={attachment.get('blurhash')}
|
||||||
src={attachment.get('url')}
|
src={attachment.get('url')}
|
||||||
alt={attachment.get('description')}
|
alt={attachment.get('description')}
|
||||||
|
lang={status.get('language')}
|
||||||
width={300}
|
width={300}
|
||||||
height={150}
|
height={150}
|
||||||
inline
|
inline
|
||||||
|
@ -180,6 +182,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
||||||
standalone
|
standalone
|
||||||
sensitive={status.get('sensitive')}
|
sensitive={status.get('sensitive')}
|
||||||
media={status.get('media_attachments')}
|
media={status.get('media_attachments')}
|
||||||
|
lang={status.get('language')}
|
||||||
height={300}
|
height={300}
|
||||||
onOpenMedia={this.props.onOpenMedia}
|
onOpenMedia={this.props.onOpenMedia}
|
||||||
visible={this.props.showMedia}
|
visible={this.props.showMedia}
|
||||||
|
|
|
@ -7,15 +7,17 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import Footer from 'mastodon/features/picture_in_picture/components/footer';
|
import Footer from 'mastodon/features/picture_in_picture/components/footer';
|
||||||
|
|
||||||
const mapStateToProps = (state, { statusId }) => ({
|
const mapStateToProps = (state, { statusId }) => ({
|
||||||
|
language: state.getIn(['statuses', statusId, 'language']),
|
||||||
accountStaticAvatar: state.getIn(['accounts', state.getIn(['statuses', statusId, 'account']), 'avatar_static']),
|
accountStaticAvatar: state.getIn(['accounts', state.getIn(['statuses', statusId, 'account']), 'avatar_static']),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default @connect(mapStateToProps)
|
export default @connect(mapStateToProps, null, null, { forwardRef: true })
|
||||||
class AudioModal extends ImmutablePureComponent {
|
class AudioModal extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
media: ImmutablePropTypes.map.isRequired,
|
media: ImmutablePropTypes.map.isRequired,
|
||||||
statusId: PropTypes.string.isRequired,
|
statusId: PropTypes.string.isRequired,
|
||||||
|
language: PropTypes.string,
|
||||||
accountStaticAvatar: PropTypes.string.isRequired,
|
accountStaticAvatar: PropTypes.string.isRequired,
|
||||||
options: PropTypes.shape({
|
options: PropTypes.shape({
|
||||||
autoPlay: PropTypes.bool,
|
autoPlay: PropTypes.bool,
|
||||||
|
@ -25,7 +27,7 @@ class AudioModal extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { media, accountStaticAvatar, statusId, onClose } = this.props;
|
const { media, language, accountStaticAvatar, statusId, onClose } = this.props;
|
||||||
const options = this.props.options || {};
|
const options = this.props.options || {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -34,6 +36,7 @@ class AudioModal extends ImmutablePureComponent {
|
||||||
<Audio
|
<Audio
|
||||||
src={media.get('url')}
|
src={media.get('url')}
|
||||||
alt={media.get('description')}
|
alt={media.get('description')}
|
||||||
|
lang={language}
|
||||||
duration={media.getIn(['meta', 'original', 'duration'], 0)}
|
duration={media.getIn(['meta', 'original', 'duration'], 0)}
|
||||||
height={150}
|
height={150}
|
||||||
poster={media.get('preview_url') || accountStaticAvatar}
|
poster={media.get('preview_url') || accountStaticAvatar}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import RelativeTimestamp from 'mastodon/components/relative_timestamp';
|
||||||
import MediaAttachments from 'mastodon/components/media_attachments';
|
import MediaAttachments from 'mastodon/components/media_attachments';
|
||||||
|
|
||||||
const mapStateToProps = (state, { statusId }) => ({
|
const mapStateToProps = (state, { statusId }) => ({
|
||||||
|
language: state.getIn(['statuses', statusId, 'language']),
|
||||||
versions: state.getIn(['history', statusId, 'items']),
|
versions: state.getIn(['history', statusId, 'items']),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -30,11 +31,12 @@ class CompareHistoryModal extends React.PureComponent {
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
index: PropTypes.number.isRequired,
|
index: PropTypes.number.isRequired,
|
||||||
statusId: PropTypes.string.isRequired,
|
statusId: PropTypes.string.isRequired,
|
||||||
|
language: PropTypes.string.isRequired,
|
||||||
versions: ImmutablePropTypes.list.isRequired,
|
versions: ImmutablePropTypes.list.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { index, versions, onClose } = this.props;
|
const { index, versions, language, onClose } = this.props;
|
||||||
const currentVersion = versions.get(index);
|
const currentVersion = versions.get(index);
|
||||||
|
|
||||||
const emojiMap = currentVersion.get('emojis').reduce((obj, emoji) => {
|
const emojiMap = currentVersion.get('emojis').reduce((obj, emoji) => {
|
||||||
|
@ -65,12 +67,12 @@ class CompareHistoryModal extends React.PureComponent {
|
||||||
<div className='status__content'>
|
<div className='status__content'>
|
||||||
{currentVersion.get('spoiler_text').length > 0 && (
|
{currentVersion.get('spoiler_text').length > 0 && (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<div className='translate' dangerouslySetInnerHTML={spoilerContent} />
|
<div className='translate' dangerouslySetInnerHTML={spoilerContent} lang={language} />
|
||||||
<hr />
|
<hr />
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className='status__content__text status__content__text--visible translate' dangerouslySetInnerHTML={content} />
|
<div className='status__content__text status__content__text--visible translate' dangerouslySetInnerHTML={content} lang={language} />
|
||||||
|
|
||||||
{!!currentVersion.get('poll') && (
|
{!!currentVersion.get('poll') && (
|
||||||
<div className='poll'>
|
<div className='poll'>
|
||||||
|
@ -82,6 +84,7 @@ class CompareHistoryModal extends React.PureComponent {
|
||||||
<span
|
<span
|
||||||
className='poll__option__text translate'
|
className='poll__option__text translate'
|
||||||
dangerouslySetInnerHTML={{ __html: emojify(escapeTextContentForBrowser(option.get('title')), emojiMap) }}
|
dangerouslySetInnerHTML={{ __html: emojify(escapeTextContentForBrowser(option.get('title')), emojiMap) }}
|
||||||
|
lang={language}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
@ -89,7 +92,7 @@ class CompareHistoryModal extends React.PureComponent {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<MediaAttachments status={currentVersion} />
|
<MediaAttachments status={currentVersion} lang={language} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -22,8 +22,8 @@ const mapDispatchToProps = (dispatch) => ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default @connect(null, mapDispatchToProps)
|
export default @withRouter
|
||||||
@withRouter
|
@connect(null, mapDispatchToProps)
|
||||||
class Header extends React.PureComponent {
|
class Header extends React.PureComponent {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
|
|
|
@ -8,6 +8,7 @@ export default class ImageLoader extends PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
alt: PropTypes.string,
|
alt: PropTypes.string,
|
||||||
|
lang: PropTypes.string,
|
||||||
src: PropTypes.string.isRequired,
|
src: PropTypes.string.isRequired,
|
||||||
previewSrc: PropTypes.string,
|
previewSrc: PropTypes.string,
|
||||||
width: PropTypes.number,
|
width: PropTypes.number,
|
||||||
|
@ -18,6 +19,7 @@ export default class ImageLoader extends PureComponent {
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
alt: '',
|
alt: '',
|
||||||
|
lang: '',
|
||||||
width: null,
|
width: null,
|
||||||
height: null,
|
height: null,
|
||||||
};
|
};
|
||||||
|
@ -129,7 +131,7 @@ export default class ImageLoader extends PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { alt, src, width, height, onClick } = this.props;
|
const { alt, lang, src, width, height, onClick } = this.props;
|
||||||
const { loading } = this.state;
|
const { loading } = this.state;
|
||||||
|
|
||||||
const className = classNames('image-loader', {
|
const className = classNames('image-loader', {
|
||||||
|
@ -154,6 +156,7 @@ export default class ImageLoader extends PureComponent {
|
||||||
) : (
|
) : (
|
||||||
<ZoomableImage
|
<ZoomableImage
|
||||||
alt={alt}
|
alt={alt}
|
||||||
|
lang={lang}
|
||||||
src={src}
|
src={src}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
width={width}
|
width={width}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import ReactSwipeableViews from 'react-swipeable-views';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Video from 'mastodon/features/video';
|
import Video from 'mastodon/features/video';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
import IconButton from 'mastodon/components/icon_button';
|
import IconButton from 'mastodon/components/icon_button';
|
||||||
|
@ -20,7 +21,12 @@ const messages = defineMessages({
|
||||||
next: { id: 'lightbox.next', defaultMessage: 'Next' },
|
next: { id: 'lightbox.next', defaultMessage: 'Next' },
|
||||||
});
|
});
|
||||||
|
|
||||||
export default @injectIntl
|
const mapStateToProps = (state, { statusId }) => ({
|
||||||
|
language: state.getIn(['statuses', statusId, 'language']),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default @connect(mapStateToProps, null, null, { forwardRef: true })
|
||||||
|
@injectIntl
|
||||||
class MediaModal extends ImmutablePureComponent {
|
class MediaModal extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -129,7 +135,7 @@ class MediaModal extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { media, statusId, intl, onClose } = this.props;
|
const { media, language, statusId, intl, onClose } = this.props;
|
||||||
const { navigationHidden } = this.state;
|
const { navigationHidden } = this.state;
|
||||||
|
|
||||||
const index = this.getIndex();
|
const index = this.getIndex();
|
||||||
|
@ -149,6 +155,7 @@ class MediaModal extends ImmutablePureComponent {
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
alt={image.get('description')}
|
alt={image.get('description')}
|
||||||
|
lang={language}
|
||||||
key={image.get('url')}
|
key={image.get('url')}
|
||||||
onClick={this.toggleNavigation}
|
onClick={this.toggleNavigation}
|
||||||
zoomButtonHidden={this.state.zoomButtonHidden}
|
zoomButtonHidden={this.state.zoomButtonHidden}
|
||||||
|
@ -171,6 +178,7 @@ class MediaModal extends ImmutablePureComponent {
|
||||||
onCloseVideo={onClose}
|
onCloseVideo={onClose}
|
||||||
detailed
|
detailed
|
||||||
alt={image.get('description')}
|
alt={image.get('description')}
|
||||||
|
lang={language}
|
||||||
key={image.get('url')}
|
key={image.get('url')}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -182,6 +190,7 @@ class MediaModal extends ImmutablePureComponent {
|
||||||
height={height}
|
height={height}
|
||||||
key={image.get('preview_url')}
|
key={image.get('preview_url')}
|
||||||
alt={image.get('description')}
|
alt={image.get('description')}
|
||||||
|
lang={language}
|
||||||
onClick={this.toggleNavigation}
|
onClick={this.toggleNavigation}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -82,8 +82,8 @@ class NavigationPanel extends React.Component {
|
||||||
{signedIn && (
|
{signedIn && (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<ColumnLink transparent to='/conversations' icon='at' text={intl.formatMessage(messages.direct)} />
|
<ColumnLink transparent to='/conversations' icon='at' text={intl.formatMessage(messages.direct)} />
|
||||||
<ColumnLink transparent to='/favourites' icon='star' text={intl.formatMessage(messages.favourites)} />
|
|
||||||
<ColumnLink transparent to='/bookmarks' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} />
|
<ColumnLink transparent to='/bookmarks' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} />
|
||||||
|
<ColumnLink transparent to='/favourites' icon='star' text={intl.formatMessage(messages.favourites)} />
|
||||||
<ColumnLink transparent to='/lists' icon='list-ul' text={intl.formatMessage(messages.lists)} />
|
<ColumnLink transparent to='/lists' icon='list-ul' text={intl.formatMessage(messages.lists)} />
|
||||||
|
|
||||||
<ListPanel />
|
<ListPanel />
|
||||||
|
|
|
@ -2,15 +2,22 @@ import React from 'react';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Video from 'mastodon/features/video';
|
import Video from 'mastodon/features/video';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import Footer from 'mastodon/features/picture_in_picture/components/footer';
|
import Footer from 'mastodon/features/picture_in_picture/components/footer';
|
||||||
import { getAverageFromBlurhash } from 'mastodon/blurhash';
|
import { getAverageFromBlurhash } from 'mastodon/blurhash';
|
||||||
|
|
||||||
export default class VideoModal extends ImmutablePureComponent {
|
const mapStateToProps = (state, { statusId }) => ({
|
||||||
|
language: state.getIn(['statuses', statusId, 'language']),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default @connect(mapStateToProps, null, null, { forwardRef: true })
|
||||||
|
class VideoModal extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
media: ImmutablePropTypes.map.isRequired,
|
media: ImmutablePropTypes.map.isRequired,
|
||||||
statusId: PropTypes.string,
|
statusId: PropTypes.string,
|
||||||
|
language: PropTypes.string,
|
||||||
options: PropTypes.shape({
|
options: PropTypes.shape({
|
||||||
startTime: PropTypes.number,
|
startTime: PropTypes.number,
|
||||||
autoPlay: PropTypes.bool,
|
autoPlay: PropTypes.bool,
|
||||||
|
@ -31,7 +38,7 @@ export default class VideoModal extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { media, statusId, onClose } = this.props;
|
const { media, statusId, language, onClose } = this.props;
|
||||||
const options = this.props.options || {};
|
const options = this.props.options || {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -49,6 +56,7 @@ export default class VideoModal extends ImmutablePureComponent {
|
||||||
autoFocus
|
autoFocus
|
||||||
detailed
|
detailed
|
||||||
alt={media.get('description')}
|
alt={media.get('description')}
|
||||||
|
lang={language}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -96,6 +96,7 @@ class ZoomableImage extends React.PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
alt: PropTypes.string,
|
alt: PropTypes.string,
|
||||||
|
lang: PropTypes.string,
|
||||||
src: PropTypes.string.isRequired,
|
src: PropTypes.string.isRequired,
|
||||||
width: PropTypes.number,
|
width: PropTypes.number,
|
||||||
height: PropTypes.number,
|
height: PropTypes.number,
|
||||||
|
@ -106,6 +107,7 @@ class ZoomableImage extends React.PureComponent {
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
alt: '',
|
alt: '',
|
||||||
|
lang: '',
|
||||||
width: null,
|
width: null,
|
||||||
height: null,
|
height: null,
|
||||||
};
|
};
|
||||||
|
@ -403,7 +405,7 @@ class ZoomableImage extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { alt, src, width, height, intl } = this.props;
|
const { alt, lang, src, width, height, intl } = this.props;
|
||||||
const { scale, lockTranslate } = this.state;
|
const { scale, lockTranslate } = this.state;
|
||||||
const overflow = scale === MIN_SCALE ? 'hidden' : 'scroll';
|
const overflow = scale === MIN_SCALE ? 'hidden' : 'scroll';
|
||||||
const zoomButtonShouldHide = this.state.navigationHidden || this.props.zoomButtonHidden || this.state.zoomMatrix.rate <= MIN_SCALE ? 'media-modal__zoom-button--hidden' : '';
|
const zoomButtonShouldHide = this.state.navigationHidden || this.props.zoomButtonHidden || this.state.zoomMatrix.rate <= MIN_SCALE ? 'media-modal__zoom-button--hidden' : '';
|
||||||
|
@ -431,6 +433,7 @@ class ZoomableImage extends React.PureComponent {
|
||||||
ref={this.setImageRef}
|
ref={this.setImageRef}
|
||||||
alt={alt}
|
alt={alt}
|
||||||
title={alt}
|
title={alt}
|
||||||
|
lang={lang}
|
||||||
src={src}
|
src={src}
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { debounce } from 'lodash';
|
||||||
import { uploadCompose, resetCompose, changeComposeSpoilerness } from '../../actions/compose';
|
import { uploadCompose, resetCompose, changeComposeSpoilerness } from '../../actions/compose';
|
||||||
import { expandHomeTimeline } from '../../actions/timelines';
|
import { expandHomeTimeline } from '../../actions/timelines';
|
||||||
import { expandNotifications } from '../../actions/notifications';
|
import { expandNotifications } from '../../actions/notifications';
|
||||||
import { fetchServer } from '../../actions/server';
|
import { fetchServer, fetchServerTranslationLanguages } from '../../actions/server';
|
||||||
import { clearHeight } from '../../actions/height_cache';
|
import { clearHeight } from '../../actions/height_cache';
|
||||||
import { focusApp, unfocusApp, changeLayout } from 'mastodon/actions/app';
|
import { focusApp, unfocusApp, changeLayout } from 'mastodon/actions/app';
|
||||||
import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodon/actions/markers';
|
import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodon/actions/markers';
|
||||||
|
@ -399,6 +399,7 @@ class UI extends React.PureComponent {
|
||||||
this.props.dispatch(fetchMarkers());
|
this.props.dispatch(fetchMarkers());
|
||||||
this.props.dispatch(expandHomeTimeline());
|
this.props.dispatch(expandHomeTimeline());
|
||||||
this.props.dispatch(expandNotifications());
|
this.props.dispatch(expandNotifications());
|
||||||
|
this.props.dispatch(fetchServerTranslationLanguages());
|
||||||
|
|
||||||
setTimeout(() => this.props.dispatch(fetchServer()), 3000);
|
setTimeout(() => this.props.dispatch(fetchServer()), 3000);
|
||||||
}
|
}
|
||||||
|
@ -474,10 +475,10 @@ class UI extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
handleHotkeyBack = () => {
|
handleHotkeyBack = () => {
|
||||||
if (window.history && window.history.length === 1) {
|
if (window.history && window.history.state) {
|
||||||
this.context.router.history.push('/');
|
|
||||||
} else {
|
|
||||||
this.context.router.history.goBack();
|
this.context.router.history.goBack();
|
||||||
|
} else {
|
||||||
|
this.context.router.history.push('/');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -102,6 +102,7 @@ class Video extends React.PureComponent {
|
||||||
frameRate: PropTypes.string,
|
frameRate: PropTypes.string,
|
||||||
src: PropTypes.string.isRequired,
|
src: PropTypes.string.isRequired,
|
||||||
alt: PropTypes.string,
|
alt: PropTypes.string,
|
||||||
|
lang: PropTypes.string,
|
||||||
width: PropTypes.number,
|
width: PropTypes.number,
|
||||||
height: PropTypes.number,
|
height: PropTypes.number,
|
||||||
sensitive: PropTypes.bool,
|
sensitive: PropTypes.bool,
|
||||||
|
@ -524,7 +525,7 @@ class Video extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, detailed, sensitive, editable, blurhash, autoFocus } = this.props;
|
const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, lang, detailed, sensitive, editable, blurhash, autoFocus } = this.props;
|
||||||
const { containerWidth, currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
|
const { containerWidth, currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
|
||||||
const progress = Math.min((currentTime / duration) * 100, 100);
|
const progress = Math.min((currentTime / duration) * 100, 100);
|
||||||
const playerStyle = {};
|
const playerStyle = {};
|
||||||
|
@ -585,6 +586,7 @@ class Video extends React.PureComponent {
|
||||||
tabIndex='0'
|
tabIndex='0'
|
||||||
aria-label={alt}
|
aria-label={alt}
|
||||||
title={alt}
|
title={alt}
|
||||||
|
lang={lang}
|
||||||
width={width}
|
width={width}
|
||||||
height={height}
|
height={height}
|
||||||
volume={volume}
|
volume={volume}
|
||||||
|
|
|
@ -162,6 +162,8 @@
|
||||||
"confirmations.discard_edit_media.message": "You have unsaved changes to the media description or preview, discard them anyway?",
|
"confirmations.discard_edit_media.message": "You have unsaved changes to the media description or preview, discard them anyway?",
|
||||||
"confirmations.domain_block.confirm": "Blokkeer die hele domein",
|
"confirmations.domain_block.confirm": "Blokkeer die hele domein",
|
||||||
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
|
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
|
||||||
|
"confirmations.edit.confirm": "Edit",
|
||||||
|
"confirmations.edit.message": "Editing now will overwrite the message you are currently composing. Are you sure you want to proceed?",
|
||||||
"confirmations.logout.confirm": "Teken Uit",
|
"confirmations.logout.confirm": "Teken Uit",
|
||||||
"confirmations.logout.message": "Is jy seker jy wil uitteken?",
|
"confirmations.logout.message": "Is jy seker jy wil uitteken?",
|
||||||
"confirmations.mute.confirm": "Mute",
|
"confirmations.mute.confirm": "Mute",
|
||||||
|
|
|
@ -162,6 +162,8 @@
|
||||||
"confirmations.discard_edit_media.message": "Tiens cambios sin alzar en a descripción u vista previa d'o fichero audiovisual, descartar-los de totz modos?",
|
"confirmations.discard_edit_media.message": "Tiens cambios sin alzar en a descripción u vista previa d'o fichero audiovisual, descartar-los de totz modos?",
|
||||||
"confirmations.domain_block.confirm": "Amagar dominio entero",
|
"confirmations.domain_block.confirm": "Amagar dominio entero",
|
||||||
"confirmations.domain_block.message": "Yes seguro que quiers blocar lo dominio {domain} entero? En cheneral ye prou, y preferible, fer uns quantos bloqueyos y silenciaus concretos. Los tuyos seguidros d'ixe dominio serán eliminaus.",
|
"confirmations.domain_block.message": "Yes seguro que quiers blocar lo dominio {domain} entero? En cheneral ye prou, y preferible, fer uns quantos bloqueyos y silenciaus concretos. Los tuyos seguidros d'ixe dominio serán eliminaus.",
|
||||||
|
"confirmations.edit.confirm": "Edit",
|
||||||
|
"confirmations.edit.message": "Editing now will overwrite the message you are currently composing. Are you sure you want to proceed?",
|
||||||
"confirmations.logout.confirm": "Zarrar sesión",
|
"confirmations.logout.confirm": "Zarrar sesión",
|
||||||
"confirmations.logout.message": "Yes seguro de querer zarrar la sesión?",
|
"confirmations.logout.message": "Yes seguro de querer zarrar la sesión?",
|
||||||
"confirmations.mute.confirm": "Silenciar",
|
"confirmations.mute.confirm": "Silenciar",
|
||||||
|
|
|
@ -138,7 +138,7 @@
|
||||||
"compose_form.poll.remove_option": "إزالة هذا الخيار",
|
"compose_form.poll.remove_option": "إزالة هذا الخيار",
|
||||||
"compose_form.poll.switch_to_multiple": "تغيِير الاستطلاع للسماح باِخيارات مُتعدِّدة",
|
"compose_form.poll.switch_to_multiple": "تغيِير الاستطلاع للسماح باِخيارات مُتعدِّدة",
|
||||||
"compose_form.poll.switch_to_single": "تغيِير الاستطلاع للسماح باِخيار واحد فقط",
|
"compose_form.poll.switch_to_single": "تغيِير الاستطلاع للسماح باِخيار واحد فقط",
|
||||||
"compose_form.publish": "انشر",
|
"compose_form.publish": "نشر",
|
||||||
"compose_form.publish_form": "انشر",
|
"compose_form.publish_form": "انشر",
|
||||||
"compose_form.publish_loud": "{publish}!",
|
"compose_form.publish_loud": "{publish}!",
|
||||||
"compose_form.save_changes": "احفظ التعديلات",
|
"compose_form.save_changes": "احفظ التعديلات",
|
||||||
|
@ -162,6 +162,8 @@
|
||||||
"confirmations.discard_edit_media.message": "لديك تغييرات غير محفوظة لوصف الوسائط أو معاينتها، تجاهلها على أي حال؟",
|
"confirmations.discard_edit_media.message": "لديك تغييرات غير محفوظة لوصف الوسائط أو معاينتها، تجاهلها على أي حال؟",
|
||||||
"confirmations.domain_block.confirm": "حظر اِسم النِّطاق بشكلٍ كامل",
|
"confirmations.domain_block.confirm": "حظر اِسم النِّطاق بشكلٍ كامل",
|
||||||
"confirmations.domain_block.message": "متأكد من أنك تود حظر اسم النطاق {domain} بالكامل ؟ في غالب الأحيان يُستَحسَن كتم أو حظر بعض الحسابات بدلا من حظر نطاق بالكامل.\nلن تتمكن مِن رؤية محتوى هذا النطاق لا على خيوطك العمومية و لا في إشعاراتك. سوف يتم كذلك إزالة كافة متابعيك المنتمين إلى هذا النطاق.",
|
"confirmations.domain_block.message": "متأكد من أنك تود حظر اسم النطاق {domain} بالكامل ؟ في غالب الأحيان يُستَحسَن كتم أو حظر بعض الحسابات بدلا من حظر نطاق بالكامل.\nلن تتمكن مِن رؤية محتوى هذا النطاق لا على خيوطك العمومية و لا في إشعاراتك. سوف يتم كذلك إزالة كافة متابعيك المنتمين إلى هذا النطاق.",
|
||||||
|
"confirmations.edit.confirm": "تعديل",
|
||||||
|
"confirmations.edit.message": "التعديل في الحين سوف يُعيد كتابة الرسالة التي أنت بصدد تحريرها. متأكد من أنك تريد المواصلة؟",
|
||||||
"confirmations.logout.confirm": "خروج",
|
"confirmations.logout.confirm": "خروج",
|
||||||
"confirmations.logout.message": "متأكد من أنك تريد الخروج؟",
|
"confirmations.logout.message": "متأكد من أنك تريد الخروج؟",
|
||||||
"confirmations.mute.confirm": "أكتم",
|
"confirmations.mute.confirm": "أكتم",
|
||||||
|
@ -176,7 +178,7 @@
|
||||||
"conversation.delete": "احذف المحادثة",
|
"conversation.delete": "احذف المحادثة",
|
||||||
"conversation.mark_as_read": "اعتبرها كمقروءة",
|
"conversation.mark_as_read": "اعتبرها كمقروءة",
|
||||||
"conversation.open": "اعرض المحادثة",
|
"conversation.open": "اعرض المحادثة",
|
||||||
"conversation.with": "بـ {names}",
|
"conversation.with": "مع {names}",
|
||||||
"copypaste.copied": "تم نسخه",
|
"copypaste.copied": "تم نسخه",
|
||||||
"copypaste.copy": "انسخ",
|
"copypaste.copy": "انسخ",
|
||||||
"directory.federated": "مِن الفديفرس المعروف",
|
"directory.federated": "مِن الفديفرس المعروف",
|
||||||
|
@ -215,7 +217,7 @@
|
||||||
"empty_column.bookmarked_statuses": "ليس لديك أية منشورات في الفواصل المرجعية بعد. عندما ستقوم بإضافة البعض منها، ستظهر هنا.",
|
"empty_column.bookmarked_statuses": "ليس لديك أية منشورات في الفواصل المرجعية بعد. عندما ستقوم بإضافة البعض منها، ستظهر هنا.",
|
||||||
"empty_column.community": "الخط العام المحلي فارغ. أكتب شيئا ما للعامة كبداية!",
|
"empty_column.community": "الخط العام المحلي فارغ. أكتب شيئا ما للعامة كبداية!",
|
||||||
"empty_column.direct": "لم تتلق أية رسالة خاصة مباشِرة بعد. سوف يتم عرض الرسائل المباشرة هنا إن قمت بإرسال واحدة أو تلقيت البعض منها.",
|
"empty_column.direct": "لم تتلق أية رسالة خاصة مباشِرة بعد. سوف يتم عرض الرسائل المباشرة هنا إن قمت بإرسال واحدة أو تلقيت البعض منها.",
|
||||||
"empty_column.domain_blocks": "ليس هناك نطاقات مخفية بعد.",
|
"empty_column.domain_blocks": "ليس هناك نطاقات تم حجبها بعد.",
|
||||||
"empty_column.explore_statuses": "ليس هناك ما هو متداوَل الآن. عد في وقت لاحق!",
|
"empty_column.explore_statuses": "ليس هناك ما هو متداوَل الآن. عد في وقت لاحق!",
|
||||||
"empty_column.favourited_statuses": "ليس لديك أية منشورات مفضلة بعد. عندما ستقوم بالإعجاب بواحدة، ستظهر هنا.",
|
"empty_column.favourited_statuses": "ليس لديك أية منشورات مفضلة بعد. عندما ستقوم بالإعجاب بواحدة، ستظهر هنا.",
|
||||||
"empty_column.favourites": "لم يقم أي أحد بالإعجاب بهذا المنشور بعد. عندما يقوم أحدهم بذلك سوف يظهر هنا.",
|
"empty_column.favourites": "لم يقم أي أحد بالإعجاب بهذا المنشور بعد. عندما يقوم أحدهم بذلك سوف يظهر هنا.",
|
||||||
|
@ -245,7 +247,7 @@
|
||||||
"filter_modal.added.context_mismatch_explanation": "فئة عامل التصفية هذه لا تنطبق على السياق الذي وصلت فيه إلى هذه المشاركة. إذا كنت ترغب في تصفية المنشور في هذا السياق أيضا، فسيتعين عليك تعديل عامل التصفية.",
|
"filter_modal.added.context_mismatch_explanation": "فئة عامل التصفية هذه لا تنطبق على السياق الذي وصلت فيه إلى هذه المشاركة. إذا كنت ترغب في تصفية المنشور في هذا السياق أيضا، فسيتعين عليك تعديل عامل التصفية.",
|
||||||
"filter_modal.added.context_mismatch_title": "عدم تطابق السياق!",
|
"filter_modal.added.context_mismatch_title": "عدم تطابق السياق!",
|
||||||
"filter_modal.added.expired_explanation": "انتهت صلاحية فئة عامل التصفية هذه، سوف تحتاج إلى تغيير تاريخ انتهاء الصلاحية لتطبيقها.",
|
"filter_modal.added.expired_explanation": "انتهت صلاحية فئة عامل التصفية هذه، سوف تحتاج إلى تغيير تاريخ انتهاء الصلاحية لتطبيقها.",
|
||||||
"filter_modal.added.expired_title": "تصفية منتهية الصلاحية!",
|
"filter_modal.added.expired_title": "عامل تصفية انتهت صلاحيته!",
|
||||||
"filter_modal.added.review_and_configure": "لمراجعة وزيادة تكوين فئة عوامل التصفية هذه، انتقل إلى {settings_link}.",
|
"filter_modal.added.review_and_configure": "لمراجعة وزيادة تكوين فئة عوامل التصفية هذه، انتقل إلى {settings_link}.",
|
||||||
"filter_modal.added.review_and_configure_title": "إعدادات التصفية",
|
"filter_modal.added.review_and_configure_title": "إعدادات التصفية",
|
||||||
"filter_modal.added.settings_link": "صفحة الإعدادات",
|
"filter_modal.added.settings_link": "صفحة الإعدادات",
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
"account.follows.empty": "Esti perfil entá nun sigue a naide.",
|
"account.follows.empty": "Esti perfil entá nun sigue a naide.",
|
||||||
"account.follows_you": "Síguete",
|
"account.follows_you": "Síguete",
|
||||||
"account.go_to_profile": "Go to profile",
|
"account.go_to_profile": "Go to profile",
|
||||||
"account.hide_reblogs": "Hide boosts from @{name}",
|
"account.hide_reblogs": "Anubrir los artículos compartíos de @{name}",
|
||||||
"account.joined_short": "Data de xunión",
|
"account.joined_short": "Data de xunión",
|
||||||
"account.languages": "Change subscribed languages",
|
"account.languages": "Change subscribed languages",
|
||||||
"account.link_verified_on": "Ownership of this link was checked on {date}",
|
"account.link_verified_on": "Ownership of this link was checked on {date}",
|
||||||
|
@ -65,7 +65,7 @@
|
||||||
"account.unfollow": "Dexar de siguir",
|
"account.unfollow": "Dexar de siguir",
|
||||||
"account.unmute": "Activar los avisos de @{name}",
|
"account.unmute": "Activar los avisos de @{name}",
|
||||||
"account.unmute_notifications": "Activar los avisos de @{name}",
|
"account.unmute_notifications": "Activar los avisos de @{name}",
|
||||||
"account.unmute_short": "Unmute",
|
"account.unmute_short": "Activar los avisos",
|
||||||
"account_note.placeholder": "Calca equí p'amestar una nota",
|
"account_note.placeholder": "Calca equí p'amestar una nota",
|
||||||
"admin.dashboard.daily_retention": "User retention rate by day after sign-up",
|
"admin.dashboard.daily_retention": "User retention rate by day after sign-up",
|
||||||
"admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
|
"admin.dashboard.monthly_retention": "User retention rate by month after sign-up",
|
||||||
|
@ -162,6 +162,8 @@
|
||||||
"confirmations.discard_edit_media.message": "You have unsaved changes to the media description or preview, discard them anyway?",
|
"confirmations.discard_edit_media.message": "You have unsaved changes to the media description or preview, discard them anyway?",
|
||||||
"confirmations.domain_block.confirm": "Bloquiar tol dominiu",
|
"confirmations.domain_block.confirm": "Bloquiar tol dominiu",
|
||||||
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
|
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
|
||||||
|
"confirmations.edit.confirm": "Edit",
|
||||||
|
"confirmations.edit.message": "La edición va sobrescribir el mensaxe que tas escribiendo. ¿De xuru que quies siguir?",
|
||||||
"confirmations.logout.confirm": "Zarrar la sesión",
|
"confirmations.logout.confirm": "Zarrar la sesión",
|
||||||
"confirmations.logout.message": "¿De xuru que quies zarrar la sesión?",
|
"confirmations.logout.message": "¿De xuru que quies zarrar la sesión?",
|
||||||
"confirmations.mute.confirm": "Mute",
|
"confirmations.mute.confirm": "Mute",
|
||||||
|
@ -213,7 +215,7 @@
|
||||||
"empty_column.account_unavailable": "Profile unavailable",
|
"empty_column.account_unavailable": "Profile unavailable",
|
||||||
"empty_column.blocks": "Entá nun bloquiesti a nengún perfil.",
|
"empty_column.blocks": "Entá nun bloquiesti a nengún perfil.",
|
||||||
"empty_column.bookmarked_statuses": "Entá nun tienes nengún artículu en Marcadores. Cuando amiestes dalgún, apaez equí.",
|
"empty_column.bookmarked_statuses": "Entá nun tienes nengún artículu en Marcadores. Cuando amiestes dalgún, apaez equí.",
|
||||||
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
|
"empty_column.community": "La llinia de tiempu llocal ta balera. ¡Espubliza daqué públicamente pa comenzar l'alderique!",
|
||||||
"empty_column.direct": "Entá nun tienes nengún mensaxe direutu. Cuando unvies o recibas dalgún, apaez equí.",
|
"empty_column.direct": "Entá nun tienes nengún mensaxe direutu. Cuando unvies o recibas dalgún, apaez equí.",
|
||||||
"empty_column.domain_blocks": "Entá nun hai nengún dominiu bloquiáu.",
|
"empty_column.domain_blocks": "Entá nun hai nengún dominiu bloquiáu.",
|
||||||
"empty_column.explore_statuses": "Agora nun hai nada en tendencia. ¡Volvi equí dempués!",
|
"empty_column.explore_statuses": "Agora nun hai nada en tendencia. ¡Volvi equí dempués!",
|
||||||
|
@ -233,7 +235,7 @@
|
||||||
"error.unexpected_crash.explanation": "Pola mor d'un fallu nel códigu o un problema de compatibilidá del restolador, esta páxina nun se pudo amosar correutamente.",
|
"error.unexpected_crash.explanation": "Pola mor d'un fallu nel códigu o un problema de compatibilidá del restolador, esta páxina nun se pudo amosar correutamente.",
|
||||||
"error.unexpected_crash.explanation_addons": "Esta páxina nun se pudo amosar correutamente. Ye probable que dalgún complementu del restolador o dalguna ferramienta de traducción automática produxere esti error.",
|
"error.unexpected_crash.explanation_addons": "Esta páxina nun se pudo amosar correutamente. Ye probable que dalgún complementu del restolador o dalguna ferramienta de traducción automática produxere esti error.",
|
||||||
"error.unexpected_crash.next_steps": "Prueba a anovar la páxina. Si nun sirve, ye posible que tovía seyas a usar Mastodon pente otru restolador o una aplicación nativa.",
|
"error.unexpected_crash.next_steps": "Prueba a anovar la páxina. Si nun sirve, ye posible que tovía seyas a usar Mastodon pente otru restolador o una aplicación nativa.",
|
||||||
"error.unexpected_crash.next_steps_addons": "Try disabling them and refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
|
"error.unexpected_crash.next_steps_addons": "Prueba a desactivalos ya a anovar la páxina. Si nun sirve, ye posible que tovía seyas a usar Mastodon pente otru restolador o una aplicación nativa.",
|
||||||
"errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
|
"errors.unexpected_crash.copy_stacktrace": "Copy stacktrace to clipboard",
|
||||||
"errors.unexpected_crash.report_issue": "Report issue",
|
"errors.unexpected_crash.report_issue": "Report issue",
|
||||||
"explore.search_results": "Resultaos de la busca",
|
"explore.search_results": "Resultaos de la busca",
|
||||||
|
@ -274,7 +276,7 @@
|
||||||
"footer.source_code": "Ver el códigu fonte",
|
"footer.source_code": "Ver el códigu fonte",
|
||||||
"footer.status": "Estáu",
|
"footer.status": "Estáu",
|
||||||
"generic.saved": "Guardóse",
|
"generic.saved": "Guardóse",
|
||||||
"getting_started.heading": "Getting started",
|
"getting_started.heading": "Comienzu",
|
||||||
"hashtag.column_header.tag_mode.all": "y {additional}",
|
"hashtag.column_header.tag_mode.all": "y {additional}",
|
||||||
"hashtag.column_header.tag_mode.any": "o {additional}",
|
"hashtag.column_header.tag_mode.any": "o {additional}",
|
||||||
"hashtag.column_header.tag_mode.none": "ensin {additional}",
|
"hashtag.column_header.tag_mode.none": "ensin {additional}",
|
||||||
|
@ -383,7 +385,7 @@
|
||||||
"navigation_bar.filters": "Pallabres desactivaes",
|
"navigation_bar.filters": "Pallabres desactivaes",
|
||||||
"navigation_bar.follow_requests": "Solicitúes de siguimientu",
|
"navigation_bar.follow_requests": "Solicitúes de siguimientu",
|
||||||
"navigation_bar.followed_tags": "Followed hashtags",
|
"navigation_bar.followed_tags": "Followed hashtags",
|
||||||
"navigation_bar.follows_and_followers": "Follows and followers",
|
"navigation_bar.follows_and_followers": "Perfiles que sigues ya te siguen",
|
||||||
"navigation_bar.lists": "Llistes",
|
"navigation_bar.lists": "Llistes",
|
||||||
"navigation_bar.logout": "Zarrar la sesión",
|
"navigation_bar.logout": "Zarrar la sesión",
|
||||||
"navigation_bar.mutes": "Perfiles colos avisos desactivaos",
|
"navigation_bar.mutes": "Perfiles colos avisos desactivaos",
|
||||||
|
@ -491,7 +493,7 @@
|
||||||
"report.comment.title": "¿Hai daqué más qu'habríemos saber?",
|
"report.comment.title": "¿Hai daqué más qu'habríemos saber?",
|
||||||
"report.forward": "Reunviar a {target}",
|
"report.forward": "Reunviar a {target}",
|
||||||
"report.forward_hint": "La cuenta ye d'otru sirvidor. ¿Quies unviar a esi sirvidor una copia anónima del informe?",
|
"report.forward_hint": "La cuenta ye d'otru sirvidor. ¿Quies unviar a esi sirvidor una copia anónima del informe?",
|
||||||
"report.mute": "Mute",
|
"report.mute": "Desactivar los avisos",
|
||||||
"report.mute_explanation": "You will not see their posts. They can still follow you and see your posts and will not know that they are muted.",
|
"report.mute_explanation": "You will not see their posts. They can still follow you and see your posts and will not know that they are muted.",
|
||||||
"report.next": "Siguiente",
|
"report.next": "Siguiente",
|
||||||
"report.placeholder": "Comentarios adicionales",
|
"report.placeholder": "Comentarios adicionales",
|
||||||
|
@ -508,11 +510,11 @@
|
||||||
"report.statuses.subtitle": "Select all that apply",
|
"report.statuses.subtitle": "Select all that apply",
|
||||||
"report.statuses.title": "¿Hai dalgún artículu qu'apoye esti informe?",
|
"report.statuses.title": "¿Hai dalgún artículu qu'apoye esti informe?",
|
||||||
"report.submit": "Unviar",
|
"report.submit": "Unviar",
|
||||||
"report.target": "Report {target}",
|
"report.target": "Informe de: {target}",
|
||||||
"report.thanks.take_action": "Equí tienes les opciones pa controlar qué ves en Mastodon:",
|
"report.thanks.take_action": "Equí tienes les opciones pa controlar qué ves en Mastodon:",
|
||||||
"report.thanks.take_action_actionable": "While we review this, you can take action against @{name}:",
|
"report.thanks.take_action_actionable": "Mentanto revisamos esti informe, pues tomar midíes contra @{name}:",
|
||||||
"report.thanks.title": "Don't want to see this?",
|
"report.thanks.title": "¿Nun quies ver esti conteníu?",
|
||||||
"report.thanks.title_actionable": "Thanks for reporting, we'll look into this.",
|
"report.thanks.title_actionable": "Gracies pol informe, el casu xá ta n'investigación.",
|
||||||
"report.unfollow": "Dexar de siguir a @{name}",
|
"report.unfollow": "Dexar de siguir a @{name}",
|
||||||
"report.unfollow_explanation": "Sigues a esta cuenta. Pa dexar de ver los sos artículos nel to feed d'aniciu, dexa de siguila.",
|
"report.unfollow_explanation": "Sigues a esta cuenta. Pa dexar de ver los sos artículos nel to feed d'aniciu, dexa de siguila.",
|
||||||
"report_notification.attached_statuses": "{count, plural, one {Axuntóse {count} artículu} other {Axuntáronse {count} artículos}}",
|
"report_notification.attached_statuses": "{count, plural, one {Axuntóse {count} artículu} other {Axuntáronse {count} artículos}}",
|
||||||
|
|
|
@ -162,6 +162,8 @@
|
||||||
"confirmations.discard_edit_media.message": "У вас ёсць незахаваныя змены ў апісанні або прэв'ю, усе роўна скасаваць іх?",
|
"confirmations.discard_edit_media.message": "У вас ёсць незахаваныя змены ў апісанні або прэв'ю, усе роўна скасаваць іх?",
|
||||||
"confirmations.domain_block.confirm": "Заблакіраваць дамен цалкам",
|
"confirmations.domain_block.confirm": "Заблакіраваць дамен цалкам",
|
||||||
"confirmations.domain_block.message": "Вы абсалютна дакладна ўпэўнены, што хочаце заблакіраваць {domain} зусім? У большасці выпадкаў, дастаткова некалькіх мэтавых блакіровак ці ігнараванняў. Вы перастанеце бачыць змесціва з гэтага дамену ва ўсіх стужках і апавяшчэннях. Вашы падпіскі з гэтага дамену будуць выдаленыя.",
|
"confirmations.domain_block.message": "Вы абсалютна дакладна ўпэўнены, што хочаце заблакіраваць {domain} зусім? У большасці выпадкаў, дастаткова некалькіх мэтавых блакіровак ці ігнараванняў. Вы перастанеце бачыць змесціва з гэтага дамену ва ўсіх стужках і апавяшчэннях. Вашы падпіскі з гэтага дамену будуць выдаленыя.",
|
||||||
|
"confirmations.edit.confirm": "Рэдагаваць",
|
||||||
|
"confirmations.edit.message": "Editing now will overwrite the message you are currently composing. Are you sure you want to proceed?",
|
||||||
"confirmations.logout.confirm": "Выйсці",
|
"confirmations.logout.confirm": "Выйсці",
|
||||||
"confirmations.logout.message": "Вы ўпэўненыя, што хочаце выйсці?",
|
"confirmations.logout.message": "Вы ўпэўненыя, што хочаце выйсці?",
|
||||||
"confirmations.mute.confirm": "Ігнараваць",
|
"confirmations.mute.confirm": "Ігнараваць",
|
||||||
|
@ -203,7 +205,7 @@
|
||||||
"emoji_button.not_found": "Адпаведныя эмодзі не знойдзены",
|
"emoji_button.not_found": "Адпаведныя эмодзі не знойдзены",
|
||||||
"emoji_button.objects": "Прадметы",
|
"emoji_button.objects": "Прадметы",
|
||||||
"emoji_button.people": "Людзі",
|
"emoji_button.people": "Людзі",
|
||||||
"emoji_button.recent": "Карыстаныя найчасцей",
|
"emoji_button.recent": "Чата выкарыстаныя",
|
||||||
"emoji_button.search": "Пошук...",
|
"emoji_button.search": "Пошук...",
|
||||||
"emoji_button.search_results": "Вынікі пошуку",
|
"emoji_button.search_results": "Вынікі пошуку",
|
||||||
"emoji_button.symbols": "Сімвалы",
|
"emoji_button.symbols": "Сімвалы",
|
||||||
|
@ -354,10 +356,10 @@
|
||||||
"lists.edit.submit": "Змяніць назву",
|
"lists.edit.submit": "Змяніць назву",
|
||||||
"lists.new.create": "Дадаць спіс",
|
"lists.new.create": "Дадаць спіс",
|
||||||
"lists.new.title_placeholder": "Назва новага спіса",
|
"lists.new.title_placeholder": "Назва новага спіса",
|
||||||
"lists.replies_policy.followed": "Любы карыстальнік, за якім вы падпісаліся",
|
"lists.replies_policy.followed": "Любы карыстальнік, на якога вы падпісаліся",
|
||||||
"lists.replies_policy.list": "Удзельнікі гэтага спісу",
|
"lists.replies_policy.list": "Удзельнікі гэтага спісу",
|
||||||
"lists.replies_policy.none": "Нікога",
|
"lists.replies_policy.none": "Нікога",
|
||||||
"lists.replies_policy.title": "Паказаць адказы да:",
|
"lists.replies_policy.title": "Паказваць адказы:",
|
||||||
"lists.search": "Шукайце сярод людзей, на якіх Вы падпісаны",
|
"lists.search": "Шукайце сярод людзей, на якіх Вы падпісаны",
|
||||||
"lists.subheading": "Вашыя спісы",
|
"lists.subheading": "Вашыя спісы",
|
||||||
"load_pending": "{count, plural, one {# новы элемент} few {# новыя элементы} many {# новых элементаў} other {# новых элементаў}}",
|
"load_pending": "{count, plural, one {# новы элемент} few {# новыя элементы} many {# новых элементаў} other {# новых элементаў}}",
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
"account.block": "Блокиране на @{name}",
|
"account.block": "Блокиране на @{name}",
|
||||||
"account.block_domain": "Блокиране на домейн {domain}",
|
"account.block_domain": "Блокиране на домейн {domain}",
|
||||||
"account.blocked": "Блокирани",
|
"account.blocked": "Блокирани",
|
||||||
"account.browse_more_on_origin_server": "Разглеждане на още в първообразния профил",
|
"account.browse_more_on_origin_server": "Разглеждане на още в оригиналния профил",
|
||||||
"account.cancel_follow_request": "Оттегляне на заявката за последване",
|
"account.cancel_follow_request": "Оттегляне на заявката за последване",
|
||||||
"account.direct": "Директно съобщение до @{name}",
|
"account.direct": "Директно съобщение до @{name}",
|
||||||
"account.disable_notifications": "Сприране на известия при публикуване от @{name}",
|
"account.disable_notifications": "Сприране на известия при публикуване от @{name}",
|
||||||
|
@ -162,6 +162,8 @@
|
||||||
"confirmations.discard_edit_media.message": "Не сте запазили промени на описанието или огледа на мултимедията, отхвърляте ли ги?",
|
"confirmations.discard_edit_media.message": "Не сте запазили промени на описанието или огледа на мултимедията, отхвърляте ли ги?",
|
||||||
"confirmations.domain_block.confirm": "Блокиране на целия домейн",
|
"confirmations.domain_block.confirm": "Блокиране на целия домейн",
|
||||||
"confirmations.domain_block.message": "Наистина ли искате да блокирате целия {domain}? В повечето случаи няколко блокирания или заглушавания са достатъчно и за предпочитане. Няма да виждате съдържание от домейна из публични часови оси или известията си. Вашите последователи от този домейн ще се премахнат.",
|
"confirmations.domain_block.message": "Наистина ли искате да блокирате целия {domain}? В повечето случаи няколко блокирания или заглушавания са достатъчно и за предпочитане. Няма да виждате съдържание от домейна из публични часови оси или известията си. Вашите последователи от този домейн ще се премахнат.",
|
||||||
|
"confirmations.edit.confirm": "Редактиране",
|
||||||
|
"confirmations.edit.message": "Редактирането сега ще замени съобщението, което в момента съставяте. Сигурни ли сте, че искате да продължите?",
|
||||||
"confirmations.logout.confirm": "Излизане",
|
"confirmations.logout.confirm": "Излизане",
|
||||||
"confirmations.logout.message": "Наистина ли искате да излезете?",
|
"confirmations.logout.message": "Наистина ли искате да излезете?",
|
||||||
"confirmations.mute.confirm": "Заглушаване",
|
"confirmations.mute.confirm": "Заглушаване",
|
||||||
|
@ -353,7 +355,7 @@
|
||||||
"lists.edit": "Промяна на списъка",
|
"lists.edit": "Промяна на списъка",
|
||||||
"lists.edit.submit": "Промяна на заглавие",
|
"lists.edit.submit": "Промяна на заглавие",
|
||||||
"lists.new.create": "Добавяне на списък",
|
"lists.new.create": "Добавяне на списък",
|
||||||
"lists.new.title_placeholder": "Име на нов списък",
|
"lists.new.title_placeholder": "Ново заглавие на списъка",
|
||||||
"lists.replies_policy.followed": "Някой последван потребител",
|
"lists.replies_policy.followed": "Някой последван потребител",
|
||||||
"lists.replies_policy.list": "Членуващите в списъка",
|
"lists.replies_policy.list": "Членуващите в списъка",
|
||||||
"lists.replies_policy.none": "Никого",
|
"lists.replies_policy.none": "Никого",
|
||||||
|
|
|
@ -162,6 +162,8 @@
|
||||||
"confirmations.discard_edit_media.message": "You have unsaved changes to the media description or preview, discard them anyway?",
|
"confirmations.discard_edit_media.message": "You have unsaved changes to the media description or preview, discard them anyway?",
|
||||||
"confirmations.domain_block.confirm": "এই ডোমেন থেকে সব লুকান",
|
"confirmations.domain_block.confirm": "এই ডোমেন থেকে সব লুকান",
|
||||||
"confirmations.domain_block.message": "আপনি কি সত্যিই সত্যই নিশ্চিত যে আপনি পুরো {domain}'টি ব্লক করতে চান? বেশিরভাগ ক্ষেত্রে কয়েকটি লক্ষ্যযুক্ত ব্লক বা নীরবতা যথেষ্ট এবং পছন্দসই। আপনি কোনও পাবলিক টাইমলাইন বা আপনার বিজ্ঞপ্তিগুলিতে সেই ডোমেন থেকে সামগ্রী দেখতে পাবেন না। সেই ডোমেন থেকে আপনার অনুসরণকারীদের সরানো হবে।",
|
"confirmations.domain_block.message": "আপনি কি সত্যিই সত্যই নিশ্চিত যে আপনি পুরো {domain}'টি ব্লক করতে চান? বেশিরভাগ ক্ষেত্রে কয়েকটি লক্ষ্যযুক্ত ব্লক বা নীরবতা যথেষ্ট এবং পছন্দসই। আপনি কোনও পাবলিক টাইমলাইন বা আপনার বিজ্ঞপ্তিগুলিতে সেই ডোমেন থেকে সামগ্রী দেখতে পাবেন না। সেই ডোমেন থেকে আপনার অনুসরণকারীদের সরানো হবে।",
|
||||||
|
"confirmations.edit.confirm": "Edit",
|
||||||
|
"confirmations.edit.message": "Editing now will overwrite the message you are currently composing. Are you sure you want to proceed?",
|
||||||
"confirmations.logout.confirm": "প্রস্থান",
|
"confirmations.logout.confirm": "প্রস্থান",
|
||||||
"confirmations.logout.message": "আপনি লগ আউট করতে চান?",
|
"confirmations.logout.message": "আপনি লগ আউট করতে চান?",
|
||||||
"confirmations.mute.confirm": "সরিয়ে ফেলুন",
|
"confirmations.mute.confirm": "সরিয়ে ফেলুন",
|
||||||
|
|
|
@ -162,6 +162,8 @@
|
||||||
"confirmations.discard_edit_media.message": "Bez ez eus kemmoù n'int ket enrollet e deskrivadur ar media pe ar rakwel, nullañ anezho evelato?",
|
"confirmations.discard_edit_media.message": "Bez ez eus kemmoù n'int ket enrollet e deskrivadur ar media pe ar rakwel, nullañ anezho evelato?",
|
||||||
"confirmations.domain_block.confirm": "Berzañ an domani a-bezh",
|
"confirmations.domain_block.confirm": "Berzañ an domani a-bezh",
|
||||||
"confirmations.domain_block.message": "Ha sur oc'h e fell deoc'h berzañ an {domain} a-bezh? Peurvuiañ eo trawalc'h berzañ pe mudañ un nebeud implijer·ezed·ien. Ne welot danvez ebet o tont eus an domani-mañ. Dilamet e vo ar c'houmanantoù war an domani-mañ.",
|
"confirmations.domain_block.message": "Ha sur oc'h e fell deoc'h berzañ an {domain} a-bezh? Peurvuiañ eo trawalc'h berzañ pe mudañ un nebeud implijer·ezed·ien. Ne welot danvez ebet o tont eus an domani-mañ. Dilamet e vo ar c'houmanantoù war an domani-mañ.",
|
||||||
|
"confirmations.edit.confirm": "Edit",
|
||||||
|
"confirmations.edit.message": "Editing now will overwrite the message you are currently composing. Are you sure you want to proceed?",
|
||||||
"confirmations.logout.confirm": "Digevreañ",
|
"confirmations.logout.confirm": "Digevreañ",
|
||||||
"confirmations.logout.message": "Ha sur oc'h e fell deoc'h digevreañ ?",
|
"confirmations.logout.message": "Ha sur oc'h e fell deoc'h digevreañ ?",
|
||||||
"confirmations.mute.confirm": "Kuzhat",
|
"confirmations.mute.confirm": "Kuzhat",
|
||||||
|
|
|
@ -162,6 +162,8 @@
|
||||||
"confirmations.discard_edit_media.message": "You have unsaved changes to the media description or preview, discard them anyway?",
|
"confirmations.discard_edit_media.message": "You have unsaved changes to the media description or preview, discard them anyway?",
|
||||||
"confirmations.domain_block.confirm": "Hide entire domain",
|
"confirmations.domain_block.confirm": "Hide entire domain",
|
||||||
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
|
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
|
||||||
|
"confirmations.edit.confirm": "Edit",
|
||||||
|
"confirmations.edit.message": "Editing now will overwrite the message you are currently composing. Are you sure you want to proceed?",
|
||||||
"confirmations.logout.confirm": "Log out",
|
"confirmations.logout.confirm": "Log out",
|
||||||
"confirmations.logout.message": "Are you sure you want to log out?",
|
"confirmations.logout.message": "Are you sure you want to log out?",
|
||||||
"confirmations.mute.confirm": "Mute",
|
"confirmations.mute.confirm": "Mute",
|
||||||
|
|
|
@ -162,6 +162,8 @@
|
||||||
"confirmations.discard_edit_media.message": "Tens canvis no desats en la descripció del contingut o en la previsualització, els vols descartar?",
|
"confirmations.discard_edit_media.message": "Tens canvis no desats en la descripció del contingut o en la previsualització, els vols descartar?",
|
||||||
"confirmations.domain_block.confirm": "Bloca el domini sencer",
|
"confirmations.domain_block.confirm": "Bloca el domini sencer",
|
||||||
"confirmations.domain_block.message": "Segur que vols blocar {domain} del tot? En la majoria dels casos, només amb blocar o silenciar uns pocs comptes n'hi ha prou i és millor. No veuràs el contingut d’aquest domini en cap de les línies de temps ni en les notificacions. S'eliminaran els teus seguidors d’aquest domini.",
|
"confirmations.domain_block.message": "Segur que vols blocar {domain} del tot? En la majoria dels casos, només amb blocar o silenciar uns pocs comptes n'hi ha prou i és millor. No veuràs el contingut d’aquest domini en cap de les línies de temps ni en les notificacions. S'eliminaran els teus seguidors d’aquest domini.",
|
||||||
|
"confirmations.edit.confirm": "Edita",
|
||||||
|
"confirmations.edit.message": "Editant ara sobreescriuràs el missatge que estàs editant. Segur que vols continuar?",
|
||||||
"confirmations.logout.confirm": "Tanca la sessió",
|
"confirmations.logout.confirm": "Tanca la sessió",
|
||||||
"confirmations.logout.message": "Segur que vols tancar la sessió?",
|
"confirmations.logout.message": "Segur que vols tancar la sessió?",
|
||||||
"confirmations.mute.confirm": "Silencia",
|
"confirmations.mute.confirm": "Silencia",
|
||||||
|
@ -437,7 +439,7 @@
|
||||||
"notifications.group": "{count} notificacions",
|
"notifications.group": "{count} notificacions",
|
||||||
"notifications.mark_as_read": "Marca cada notificació com a llegida",
|
"notifications.mark_as_read": "Marca cada notificació com a llegida",
|
||||||
"notifications.permission_denied": "Les notificacions d’escriptori no estan disponibles perquè prèviament s’ha denegat el permís al navegador",
|
"notifications.permission_denied": "Les notificacions d’escriptori no estan disponibles perquè prèviament s’ha denegat el permís al navegador",
|
||||||
"notifications.permission_denied_alert": "No es poden activar les notificacions de l'escriptori perquè el permís del navegador ha estat denegat abans",
|
"notifications.permission_denied_alert": "No es poden activar les notificacions de l'escriptori perquè abans s'ha denegat el permís del navegador",
|
||||||
"notifications.permission_required": "Les notificacions d'escriptori no estan disponibles perquè el permís requerit no ha estat concedit.",
|
"notifications.permission_required": "Les notificacions d'escriptori no estan disponibles perquè el permís requerit no ha estat concedit.",
|
||||||
"notifications_permission_banner.enable": "Activa les notificacions d’escriptori",
|
"notifications_permission_banner.enable": "Activa les notificacions d’escriptori",
|
||||||
"notifications_permission_banner.how_to_control": "Per a rebre notificacions quan Mastodon no és obert cal activar les notificacions d’escriptori. Pots controlar amb precisió quins tipus d’interaccions generen notificacions d’escriptori després d’activar el botó {icon} de dalt.",
|
"notifications_permission_banner.how_to_control": "Per a rebre notificacions quan Mastodon no és obert cal activar les notificacions d’escriptori. Pots controlar amb precisió quins tipus d’interaccions generen notificacions d’escriptori després d’activar el botó {icon} de dalt.",
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
"account.posts_with_replies": "توتس و وەڵامەکان",
|
"account.posts_with_replies": "توتس و وەڵامەکان",
|
||||||
"account.report": "گوزارشت @{name}",
|
"account.report": "گوزارشت @{name}",
|
||||||
"account.requested": "چاوەڕێی ڕەزامەندین. کرتە بکە بۆ هەڵوەشاندنەوەی داواکاری شوێنکەوتن",
|
"account.requested": "چاوەڕێی ڕەزامەندین. کرتە بکە بۆ هەڵوەشاندنەوەی داواکاری شوێنکەوتن",
|
||||||
"account.requested_follow": "{name} has requested to follow you",
|
"account.requested_follow": "{name} داوای کردووە شوێنت بکەوێت",
|
||||||
"account.share": "پرۆفایلی @{name} هاوبەش بکە",
|
"account.share": "پرۆفایلی @{name} هاوبەش بکە",
|
||||||
"account.show_reblogs": "پیشاندانی بەرزکردنەوەکان لە @{name}",
|
"account.show_reblogs": "پیشاندانی بەرزکردنەوەکان لە @{name}",
|
||||||
"account.statuses_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
|
"account.statuses_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}",
|
||||||
|
@ -128,7 +128,7 @@
|
||||||
"compose.language.search": "گەڕان بە زمانەکان...",
|
"compose.language.search": "گەڕان بە زمانەکان...",
|
||||||
"compose_form.direct_message_warning_learn_more": "زیاتر فێربه",
|
"compose_form.direct_message_warning_learn_more": "زیاتر فێربه",
|
||||||
"compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.",
|
"compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.",
|
||||||
"compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
|
"compose_form.hashtag_warning": "ئەم بڵاوکراوەیە لە ژێر هیچ هاشتاگێکدا دا نانرێت وەک ئەوەیە، کە گشتی نەبێت. تەنها بڵاوکراوە گشتیەکان دەتوانرێ بە هاشتاگ گەڕانی بۆ بکرێت.",
|
||||||
"compose_form.lock_disclaimer": "هەژمێرەکەی لە حاڵەتی {locked}. هەر کەسێک دەتوانێت شوێنت بکەوێت بۆ پیشاندانی بابەتەکانی تەنها دوایخۆی.",
|
"compose_form.lock_disclaimer": "هەژمێرەکەی لە حاڵەتی {locked}. هەر کەسێک دەتوانێت شوێنت بکەوێت بۆ پیشاندانی بابەتەکانی تەنها دوایخۆی.",
|
||||||
"compose_form.lock_disclaimer.lock": "قفڵ دراوە",
|
"compose_form.lock_disclaimer.lock": "قفڵ دراوە",
|
||||||
"compose_form.placeholder": "چی لە مێشکتدایە?",
|
"compose_form.placeholder": "چی لە مێشکتدایە?",
|
||||||
|
@ -162,6 +162,8 @@
|
||||||
"confirmations.discard_edit_media.message": "گۆڕانکاریت لە وەسف یان پێشبینی میدیادا هەڵنەگیراوە، بەهەر حاڵ فڕێیان بدە؟",
|
"confirmations.discard_edit_media.message": "گۆڕانکاریت لە وەسف یان پێشبینی میدیادا هەڵنەگیراوە، بەهەر حاڵ فڕێیان بدە؟",
|
||||||
"confirmations.domain_block.confirm": "بلۆککردنی هەموو دۆمەینەکە",
|
"confirmations.domain_block.confirm": "بلۆککردنی هەموو دۆمەینەکە",
|
||||||
"confirmations.domain_block.message": "ئایا بەڕاستی، بەڕاستی تۆ دەتەوێت هەموو {domain} بلۆک بکەیت؟ لە زۆربەی حاڵەتەکاندا چەند بلۆکێکی ئامانجدار یان بێدەنگەکان پێویست و پەسەندن. تۆ ناوەڕۆک ێک نابینیت لە دۆمەینەکە لە هیچ هێڵی کاتی گشتی یان ئاگانامەکانت. شوێنکەوتوانی تۆ لەو دۆمەینەوە لادەبرێن.",
|
"confirmations.domain_block.message": "ئایا بەڕاستی، بەڕاستی تۆ دەتەوێت هەموو {domain} بلۆک بکەیت؟ لە زۆربەی حاڵەتەکاندا چەند بلۆکێکی ئامانجدار یان بێدەنگەکان پێویست و پەسەندن. تۆ ناوەڕۆک ێک نابینیت لە دۆمەینەکە لە هیچ هێڵی کاتی گشتی یان ئاگانامەکانت. شوێنکەوتوانی تۆ لەو دۆمەینەوە لادەبرێن.",
|
||||||
|
"confirmations.edit.confirm": "دەستکاری",
|
||||||
|
"confirmations.edit.message": "دەستکاری کردنی ئێستا: دەبێتە هۆی نووسینەوەی ئەو پەیامەی، کە ئێستا داتدەڕشت. ئایا دڵنیای، کە دەتەوێت بەردەوام بیت؟",
|
||||||
"confirmations.logout.confirm": "چوونە دەرەوە",
|
"confirmations.logout.confirm": "چوونە دەرەوە",
|
||||||
"confirmations.logout.message": "ئایا دڵنیایت لەوەی دەتەوێت بچیتە دەرەوە?",
|
"confirmations.logout.message": "ئایا دڵنیایت لەوەی دەتەوێت بچیتە دەرەوە?",
|
||||||
"confirmations.mute.confirm": "بێدەنگ",
|
"confirmations.mute.confirm": "بێدەنگ",
|
||||||
|
@ -221,7 +223,7 @@
|
||||||
"empty_column.favourites": "کەس ئەم توتەی دڵخواز نەکردووە،کاتێک کەسێک وا بکات، لێرە دەرئەکەون.",
|
"empty_column.favourites": "کەس ئەم توتەی دڵخواز نەکردووە،کاتێک کەسێک وا بکات، لێرە دەرئەکەون.",
|
||||||
"empty_column.follow_recommendations": "پێدەچێت هیچ پێشنیارێک بۆ تۆ دروست نەکرێت. دەتوانیت هەوڵبدەیت گەڕان بەکاربهێنیت بۆ گەڕان بەدوای ئەو کەسانەی کە ڕەنگە بیانناسیت یان بەدوای هاشتاگە ڕەوتەکاندا بگەڕێیت.",
|
"empty_column.follow_recommendations": "پێدەچێت هیچ پێشنیارێک بۆ تۆ دروست نەکرێت. دەتوانیت هەوڵبدەیت گەڕان بەکاربهێنیت بۆ گەڕان بەدوای ئەو کەسانەی کە ڕەنگە بیانناسیت یان بەدوای هاشتاگە ڕەوتەکاندا بگەڕێیت.",
|
||||||
"empty_column.follow_requests": "تۆ هێشتا هیچ داواکارییەکی بەدواداچووت نیە. کاتێک یەکێکت بۆ هات، لێرە دەرئەکەویت.",
|
"empty_column.follow_requests": "تۆ هێشتا هیچ داواکارییەکی بەدواداچووت نیە. کاتێک یەکێکت بۆ هات، لێرە دەرئەکەویت.",
|
||||||
"empty_column.followed_tags": "You have not followed any hashtags yet. When you do, they will show up here.",
|
"empty_column.followed_tags": "تۆ هێشتا شوێن هیچ هاشتاگێک نەکەوتوویت. کاتێک کردت، ئەوان لێرە دەردەکەون.",
|
||||||
"empty_column.hashtag": "هێشتا هیچ شتێک لەم هاشتاگەدا نییە.",
|
"empty_column.hashtag": "هێشتا هیچ شتێک لەم هاشتاگەدا نییە.",
|
||||||
"empty_column.home": "تایم لاینی ماڵەوەت بەتاڵە! سەردانی {public} بکە یان گەڕان بەکاربێنە بۆ دەستپێکردن و بینینی بەکارهێنەرانی تر.",
|
"empty_column.home": "تایم لاینی ماڵەوەت بەتاڵە! سەردانی {public} بکە یان گەڕان بەکاربێنە بۆ دەستپێکردن و بینینی بەکارهێنەرانی تر.",
|
||||||
"empty_column.home.suggestions": "چەند پێشنیارێک ببینە",
|
"empty_column.home.suggestions": "چەند پێشنیارێک ببینە",
|
||||||
|
@ -237,42 +239,42 @@
|
||||||
"errors.unexpected_crash.copy_stacktrace": "کۆپیکردنی ستێکتراسی بۆ کلیپ بۆرد",
|
"errors.unexpected_crash.copy_stacktrace": "کۆپیکردنی ستێکتراسی بۆ کلیپ بۆرد",
|
||||||
"errors.unexpected_crash.report_issue": "کێشەی گوزارشت",
|
"errors.unexpected_crash.report_issue": "کێشەی گوزارشت",
|
||||||
"explore.search_results": "ئەنجامەکانی گەڕان",
|
"explore.search_results": "ئەنجامەکانی گەڕان",
|
||||||
"explore.suggested_follows": "For you",
|
"explore.suggested_follows": "بۆ تۆ",
|
||||||
"explore.title": "گەڕان",
|
"explore.title": "گەڕان",
|
||||||
"explore.trending_links": "News",
|
"explore.trending_links": "هەواڵەکان",
|
||||||
"explore.trending_statuses": "Posts",
|
"explore.trending_statuses": "بڵاوکراوەکان",
|
||||||
"explore.trending_tags": "Hashtags",
|
"explore.trending_tags": "هاشتاگەکان",
|
||||||
"filter_modal.added.context_mismatch_explanation": "ئەم پۆلە فلتەرە ئەو چوارچێوەیە ناگرێتەوە کە تۆ تێیدا دەستت بەم پۆستە کردووە. ئەگەر بتەوێت پۆستەکە لەم چوارچێوەیەشدا فلتەر بکرێت، دەبێت دەستکاری فلتەرەکە بکەیت.",
|
"filter_modal.added.context_mismatch_explanation": "ئەم پۆلە فلتەرە ئەو چوارچێوەیە ناگرێتەوە کە تۆ تێیدا دەستت بەم پۆستە کردووە. ئەگەر بتەوێت پۆستەکە لەم چوارچێوەیەشدا فلتەر بکرێت، دەبێت دەستکاری فلتەرەکە بکەیت.",
|
||||||
"filter_modal.added.context_mismatch_title": "ناتەبایی دەقی نووسراو!",
|
"filter_modal.added.context_mismatch_title": "ناتەبایی دەقی نووسراو!",
|
||||||
"filter_modal.added.expired_explanation": "ئەم پۆلە فلتەرە بەسەرچووە، پێویستە بەرواری بەسەرچوونی بگۆڕیت بۆ ئەوەی جێبەجێی بکات.",
|
"filter_modal.added.expired_explanation": "ئەم پۆلە فلتەرە بەسەرچووە، پێویستە بەرواری بەسەرچوونی بگۆڕیت بۆ ئەوەی جێبەجێی بکات.",
|
||||||
"filter_modal.added.expired_title": "فلتەری بەسەرچووە!",
|
"filter_modal.added.expired_title": "فلتەری بەسەرچووە!",
|
||||||
"filter_modal.added.review_and_configure": "بۆ پێداچوونەوە و ڕێکخستنی زیاتری ئەم پۆلە فلتەرە، بچۆ بۆ {settings_link}.",
|
"filter_modal.added.review_and_configure": "بۆ پێداچوونەوە و ڕێکخستنی زیاتری ئەم پۆلە فلتەرە، بچۆ بۆ {settings_link}.",
|
||||||
"filter_modal.added.review_and_configure_title": "Filter settings",
|
"filter_modal.added.review_and_configure_title": "ڕێکخستنەکانی پاڵاوتن",
|
||||||
"filter_modal.added.settings_link": "settings page",
|
"filter_modal.added.settings_link": "پەڕەی ڕێکخستن",
|
||||||
"filter_modal.added.short_explanation": "This post has been added to the following filter category: {title}.",
|
"filter_modal.added.short_explanation": "بڵاوکراوەکە زیادکرا بۆ ئەو پاڵاوتنانەی خوارەوە: {title}.",
|
||||||
"filter_modal.added.title": "Filter added!",
|
"filter_modal.added.title": "پاڵێو زیادکرا!",
|
||||||
"filter_modal.select_filter.context_mismatch": "does not apply to this context",
|
"filter_modal.select_filter.context_mismatch": "لەم چوارچێوەیەدا کارپێکراو نییە",
|
||||||
"filter_modal.select_filter.expired": "expired",
|
"filter_modal.select_filter.expired": "بەسەرچووە",
|
||||||
"filter_modal.select_filter.prompt_new": "New category: {name}",
|
"filter_modal.select_filter.prompt_new": "پۆلێنی نوێ: {name}",
|
||||||
"filter_modal.select_filter.search": "Search or create",
|
"filter_modal.select_filter.search": "گەڕان یان دروستکردن",
|
||||||
"filter_modal.select_filter.subtitle": "Use an existing category or create a new one",
|
"filter_modal.select_filter.subtitle": "بەکارهێنانی پۆلێنی بەردەست یان دروستکردنی پۆلێنێکی نوێ",
|
||||||
"filter_modal.select_filter.title": "Filter this post",
|
"filter_modal.select_filter.title": "ئەم بڵاوکراوەیە بپاڵێوە",
|
||||||
"filter_modal.title.status": "Filter a post",
|
"filter_modal.title.status": "بڵاوکراوەیەک بپاڵێوە",
|
||||||
"follow_recommendations.done": "تەواو",
|
"follow_recommendations.done": "تەواو",
|
||||||
"follow_recommendations.heading": "شوێن ئەو کەسانە بکەون کە دەتەوێت پۆستەکان ببینیت لە! لێرەدا چەند پێشنیارێک هەیە.",
|
"follow_recommendations.heading": "شوێن ئەو کەسانە بکەون کە دەتەوێت پۆستەکان ببینیت لە! لێرەدا چەند پێشنیارێک هەیە.",
|
||||||
"follow_recommendations.lead": "بابەتەکانی ئەو کەسانەی کە بەدوایدا دەگەڕێیت بە فەرمانی کرۆنۆلۆجی لە خواردنەکانی ماڵەکەت دەردەکەون. مەترسە لە هەڵەکردن، دەتوانیت بە ئاسانی خەڵک هەڵبکەیت هەر کاتێک!",
|
"follow_recommendations.lead": "بابەتەکانی ئەو کەسانەی کە بەدوایدا دەگەڕێیت بە فەرمانی کرۆنۆلۆجی لە خواردنەکانی ماڵەکەت دەردەکەون. مەترسە لە هەڵەکردن، دەتوانیت بە ئاسانی خەڵک هەڵبکەیت هەر کاتێک!",
|
||||||
"follow_request.authorize": "دهسهڵاتپێدراو",
|
"follow_request.authorize": "دهسهڵاتپێدراو",
|
||||||
"follow_request.reject": "ڕەتکردنەوە",
|
"follow_request.reject": "ڕەتکردنەوە",
|
||||||
"follow_requests.unlocked_explanation": "هەرچەندە هەژمارەکەت داخراو نییە، ستافی {domain} وا بیریان کردەوە کە لەوانەیە بتانەوێت پێداچوونەوە بە داواکاریەکانی ئەم هەژمارەدا بکەن بە دەستی.",
|
"follow_requests.unlocked_explanation": "هەرچەندە هەژمارەکەت داخراو نییە، ستافی {domain} وا بیریان کردەوە کە لەوانەیە بتانەوێت پێداچوونەوە بە داواکاریەکانی ئەم هەژمارەدا بکەن بە دەستی.",
|
||||||
"followed_tags": "Followed hashtags",
|
"followed_tags": "هاشتاگە شوێنکەوتووەکان",
|
||||||
"footer.about": "About",
|
"footer.about": "دەربارە",
|
||||||
"footer.directory": "Profiles directory",
|
"footer.directory": "ڕابەری پەڕەی ناساندن",
|
||||||
"footer.get_app": "Get the app",
|
"footer.get_app": "بەرنامەکە بەدەست بێنە",
|
||||||
"footer.invite": "Invite people",
|
"footer.invite": "بانگهێشتکردنی خەڵک",
|
||||||
"footer.keyboard_shortcuts": "Keyboard shortcuts",
|
"footer.keyboard_shortcuts": "کورتەڕێکانی تەختەکلیک",
|
||||||
"footer.privacy_policy": "Privacy policy",
|
"footer.privacy_policy": "سیاسەتی تایبەتمەندێتی",
|
||||||
"footer.source_code": "View source code",
|
"footer.source_code": "پیشاندانی کۆدی سەرچاوە",
|
||||||
"footer.status": "Status",
|
"footer.status": "دۆخ",
|
||||||
"generic.saved": "پاشکەوتکرا",
|
"generic.saved": "پاشکەوتکرا",
|
||||||
"getting_started.heading": "دەست پێکردن",
|
"getting_started.heading": "دەست پێکردن",
|
||||||
"hashtag.column_header.tag_mode.all": "و {additional}",
|
"hashtag.column_header.tag_mode.all": "و {additional}",
|
||||||
|
@ -284,19 +286,19 @@
|
||||||
"hashtag.column_settings.tag_mode.any": "هەر کام لەمانە",
|
"hashtag.column_settings.tag_mode.any": "هەر کام لەمانە",
|
||||||
"hashtag.column_settings.tag_mode.none": "هیچ کام لەمانە",
|
"hashtag.column_settings.tag_mode.none": "هیچ کام لەمانە",
|
||||||
"hashtag.column_settings.tag_toggle": "تاگی زیادە ی ئەم ستوونە لەخۆ بنووسە",
|
"hashtag.column_settings.tag_toggle": "تاگی زیادە ی ئەم ستوونە لەخۆ بنووسە",
|
||||||
"hashtag.follow": "Follow hashtag",
|
"hashtag.follow": "شوێنکەوتنی هاشتاگ",
|
||||||
"hashtag.unfollow": "Unfollow hashtag",
|
"hashtag.unfollow": "شوێن نەکەوتنی هاشتاگ",
|
||||||
"home.column_settings.basic": "بنەڕەتی",
|
"home.column_settings.basic": "بنەڕەتی",
|
||||||
"home.column_settings.show_reblogs": "پیشاندانی بەهێزکردن",
|
"home.column_settings.show_reblogs": "پیشاندانی بەهێزکردن",
|
||||||
"home.column_settings.show_replies": "وەڵامدانەوەکان پیشان بدە",
|
"home.column_settings.show_replies": "وەڵامدانەوەکان پیشان بدە",
|
||||||
"home.hide_announcements": "شاردنەوەی راگەیەنراوەکان",
|
"home.hide_announcements": "شاردنەوەی راگەیەنراوەکان",
|
||||||
"home.show_announcements": "پیشاندانی راگەیەنراوەکان",
|
"home.show_announcements": "پیشاندانی راگەیەنراوەکان",
|
||||||
"interaction_modal.description.favourite": "With an account on Mastodon, you can favourite this post to let the author know you appreciate it and save it for later.",
|
"interaction_modal.description.favourite": "بە هەژمارێک لەسەر ماستدۆن، دەتوانیت ئەم بڵاوکراوەیە زیادبکەیت بۆ دڵخوازەکانت. بۆ ئاگادارکردنەوەی بڵاوکەرەوەکە لە پێزانینەکەت و هێشتنەوەی بۆ دواتر.",
|
||||||
"interaction_modal.description.follow": "With an account on Mastodon, you can follow {name} to receive their posts in your home feed.",
|
"interaction_modal.description.follow": "بە هەژمارێک لەسەر ماستدۆن، ئەتوانیت شوێن {name} بکەویت بۆ ئەوەی بڵاوکراوەکانی بگاتە پەڕەی سەرەکیت.",
|
||||||
"interaction_modal.description.reblog": "With an account on Mastodon, you can boost this post to share it with your own followers.",
|
"interaction_modal.description.reblog": "بە هەژمارێک لەسەر ماستدۆن، ئەتوانیت ئەم بڵاوکراوەیە بەرزبکەیتەوە تاوەکو بەژداری پێبکەیت لەگەل شوێنکەوتوانت.",
|
||||||
"interaction_modal.description.reply": "With an account on Mastodon, you can respond to this post.",
|
"interaction_modal.description.reply": "بە هەژمارێک لەسەر ماستدۆن، ئەتوانیت وەڵامی ئەم بڵاوکراوەیە بدەیتەوە.",
|
||||||
"interaction_modal.on_another_server": "On a different server",
|
"interaction_modal.on_another_server": "لەسەر ڕاژەیەکی جیا",
|
||||||
"interaction_modal.on_this_server": "On this server",
|
"interaction_modal.on_this_server": "لەسەر ئەم ڕاژەیە",
|
||||||
"interaction_modal.other_server_instructions": "Copy and paste this URL into the search field of your favourite Mastodon app or the web interface of your Mastodon server.",
|
"interaction_modal.other_server_instructions": "Copy and paste this URL into the search field of your favourite Mastodon app or the web interface of your Mastodon server.",
|
||||||
"interaction_modal.preamble": "Since Mastodon is decentralized, you can use your existing account hosted by another Mastodon server or compatible platform if you don't have an account on this one.",
|
"interaction_modal.preamble": "Since Mastodon is decentralized, you can use your existing account hosted by another Mastodon server or compatible platform if you don't have an account on this one.",
|
||||||
"interaction_modal.title.favourite": "Favourite {name}'s post",
|
"interaction_modal.title.favourite": "Favourite {name}'s post",
|
||||||
|
|
|
@ -162,6 +162,8 @@
|
||||||
"confirmations.discard_edit_media.message": "You have unsaved changes to the media description or preview, discard them anyway?",
|
"confirmations.discard_edit_media.message": "You have unsaved changes to the media description or preview, discard them anyway?",
|
||||||
"confirmations.domain_block.confirm": "Piattà tuttu u duminiu",
|
"confirmations.domain_block.confirm": "Piattà tuttu u duminiu",
|
||||||
"confirmations.domain_block.message": "Site veramente sicuru·a che vulete piattà tuttu à {domain}? Saria forse abbastanza di bluccà ò piattà alcuni conti da quallà. Ùn viderete più nunda da quallà indè e linee pubbliche o e nutificazione. I vostri abbunati da stu duminiu saranu tolti.",
|
"confirmations.domain_block.message": "Site veramente sicuru·a che vulete piattà tuttu à {domain}? Saria forse abbastanza di bluccà ò piattà alcuni conti da quallà. Ùn viderete più nunda da quallà indè e linee pubbliche o e nutificazione. I vostri abbunati da stu duminiu saranu tolti.",
|
||||||
|
"confirmations.edit.confirm": "Edit",
|
||||||
|
"confirmations.edit.message": "Editing now will overwrite the message you are currently composing. Are you sure you want to proceed?",
|
||||||
"confirmations.logout.confirm": "Scunnettassi",
|
"confirmations.logout.confirm": "Scunnettassi",
|
||||||
"confirmations.logout.message": "Site sicuru·a che vulete scunnettà vi?",
|
"confirmations.logout.message": "Site sicuru·a che vulete scunnettà vi?",
|
||||||
"confirmations.mute.confirm": "Piattà",
|
"confirmations.mute.confirm": "Piattà",
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue