mirror of
https://git.kescher.at/CatCatNya/catstodon.git
synced 2024-11-22 07:08:07 +01:00
Merge branch 'upstream-main' into develop
This commit is contained in:
commit
7fa9c34dee
433 changed files with 5909 additions and 3828 deletions
|
@ -73,6 +73,16 @@ DB_PORT=5432
|
||||||
SECRET_KEY_BASE=
|
SECRET_KEY_BASE=
|
||||||
OTP_SECRET=
|
OTP_SECRET=
|
||||||
|
|
||||||
|
# Encryption secrets
|
||||||
|
# ------------------
|
||||||
|
# Must be available (and set to same values) for all server processes
|
||||||
|
# These are private/secret values, do not share outside hosting environment
|
||||||
|
# Use `bin/rails db:encryption:init` to generate fresh secrets
|
||||||
|
# Do not change these secrets once in use, as this would cause data loss and other issues
|
||||||
|
# ------------------
|
||||||
|
# ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=
|
||||||
|
# ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=
|
||||||
|
# ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=
|
||||||
|
|
||||||
# Web Push
|
# Web Push
|
||||||
# --------
|
# --------
|
||||||
|
|
13
.github/ISSUE_TEMPLATE/1.web_bug_report.yml
vendored
13
.github/ISSUE_TEMPLATE/1.web_bug_report.yml
vendored
|
@ -1,6 +1,7 @@
|
||||||
name: Bug Report (Web Interface)
|
name: Bug Report (Web Interface)
|
||||||
description: If you are using Mastodon's web interface and something is not working as expected
|
description: There is a problem using Mastodon's web interface.
|
||||||
labels: [bug, 'status/to triage', 'area/web interface']
|
labels: ['status/to triage', 'area/web interface']
|
||||||
|
type: Bug
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
|
@ -47,8 +48,8 @@ body:
|
||||||
attributes:
|
attributes:
|
||||||
label: Mastodon version
|
label: Mastodon version
|
||||||
description: |
|
description: |
|
||||||
This is displayed at the bottom of the About page, eg. `v4.1.2+nightly-20230627`
|
This is displayed at the bottom of the About page, eg. `v4.4.0-alpha.1`
|
||||||
placeholder: v4.1.2
|
placeholder: v4.3.0
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
|
@ -56,7 +57,7 @@ body:
|
||||||
label: Browser name and version
|
label: Browser name and version
|
||||||
description: |
|
description: |
|
||||||
What browser are you using when getting this bug? Please specify the version as well.
|
What browser are you using when getting this bug? Please specify the version as well.
|
||||||
placeholder: Firefox 105.0.3
|
placeholder: Firefox 131.0.0
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
|
@ -64,7 +65,7 @@ body:
|
||||||
label: Operating system
|
label: Operating system
|
||||||
description: |
|
description: |
|
||||||
What OS are you running? Please specify the version as well.
|
What OS are you running? Please specify the version as well.
|
||||||
placeholder: macOS 13.4.1
|
placeholder: macOS 15.0.1
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
|
13
.github/ISSUE_TEMPLATE/2.server_bug_report.yml
vendored
13
.github/ISSUE_TEMPLATE/2.server_bug_report.yml
vendored
|
@ -1,7 +1,8 @@
|
||||||
name: Bug Report (server / API)
|
name: Bug Report (server / API)
|
||||||
description: |
|
description: |
|
||||||
If something is not working as expected, but is not from using the web interface.
|
There is a problem with the HTTP server, REST API, ActivityPub interaction, etc.
|
||||||
labels: [bug, 'status/to triage']
|
labels: ['status/to triage']
|
||||||
|
type: 'Bug'
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
|
@ -48,8 +49,8 @@ body:
|
||||||
attributes:
|
attributes:
|
||||||
label: Mastodon version
|
label: Mastodon version
|
||||||
description: |
|
description: |
|
||||||
This is displayed at the bottom of the About page, eg. `v4.1.2+nightly-20230627`
|
This is displayed at the bottom of the About page, eg. `v4.4.0-alpha.1`
|
||||||
placeholder: v4.1.2
|
placeholder: v4.3.0
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
@ -59,7 +60,7 @@ body:
|
||||||
Any additional technical details you may have, like logs or error traces
|
Any additional technical details you may have, like logs or error traces
|
||||||
value: |
|
value: |
|
||||||
If this is happening on your own Mastodon server, please fill out those:
|
If this is happening on your own Mastodon server, please fill out those:
|
||||||
- Ruby version: (from `ruby --version`, eg. v3.1.2)
|
- Ruby version: (from `ruby --version`, eg. v3.3.5)
|
||||||
- Node.js version: (from `node --version`, eg. v18.16.0)
|
- Node.js version: (from `node --version`, eg. v20.18.0)
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
|
74
.github/ISSUE_TEMPLATE/3.troubleshooting.yml
vendored
Normal file
74
.github/ISSUE_TEMPLATE/3.troubleshooting.yml
vendored
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
name: Deployment troubleshooting
|
||||||
|
description: |
|
||||||
|
You are a server administrator and you are encountering a technical issue during installation, upgrade or operations of Mastodon.
|
||||||
|
labels: ['status/to triage']
|
||||||
|
type: 'Troubleshooting'
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Make sure that you are submitting a new bug that was not previously reported or already fixed.
|
||||||
|
|
||||||
|
Please use a concise and distinct title for the issue.
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Steps to reproduce the problem
|
||||||
|
description: What were you trying to do?
|
||||||
|
value: |
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Expected behaviour
|
||||||
|
description: What should have happened?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Actual behaviour
|
||||||
|
description: What happened?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Detailed description
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Mastodon instance
|
||||||
|
description: The address of the Mastodon instance where you experienced the issue
|
||||||
|
placeholder: mastodon.social
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Mastodon version
|
||||||
|
description: |
|
||||||
|
This is displayed at the bottom of the About page, eg. `v4.4.0-alpha.1`
|
||||||
|
placeholder: v4.3.0
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Environment
|
||||||
|
description: |
|
||||||
|
Details about your environment, like how Mastodon is deployed, if containers are used, version numbers, etc.
|
||||||
|
value: |
|
||||||
|
Please at least include those informations:
|
||||||
|
- Operating system: (eg. Ubuntu 22.04)
|
||||||
|
- Ruby version: (from `ruby --version`, eg. v3.3.5)
|
||||||
|
- Node.js version: (from `node --version`, eg. v20.18.0)
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Technical details
|
||||||
|
description: |
|
||||||
|
Any additional technical details you may have, like logs or error traces
|
||||||
|
validations:
|
||||||
|
required: false
|
|
@ -1,6 +1,6 @@
|
||||||
name: Feature Request
|
name: Feature Request
|
||||||
description: I have a suggestion
|
description: I have a suggestion
|
||||||
labels: [suggestion]
|
type: Suggestion
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
6
.github/workflows/build-push-pr.yml
vendored
6
.github/workflows/build-push-pr.yml
vendored
|
@ -21,9 +21,11 @@ jobs:
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- id: version_vars
|
- id: version_vars
|
||||||
run: |
|
run: |
|
||||||
echo mastodon_version_metadata=pr-${{ github.event.pull_request.number }}-$(git rev-parse --short HEAD) >> $GITHUB_OUTPUT
|
echo mastodon_version_metadata=pr-${{ github.event.pull_request.number }}-$(git rev-parse --short ${{github.event.pull_request.head.sha}}) >> $GITHUB_OUTPUT
|
||||||
|
echo mastodon_short_sha=$(git rev-parse --short ${{github.event.pull_request.head.sha}}) >> $GITHUB_OUTPUT
|
||||||
outputs:
|
outputs:
|
||||||
metadata: ${{ steps.version_vars.outputs.mastodon_version_metadata }}
|
metadata: ${{ steps.version_vars.outputs.mastodon_version_metadata }}
|
||||||
|
short_sha: ${{ steps.version_vars.outputs.mastodon_short_sha }}
|
||||||
|
|
||||||
build-image:
|
build-image:
|
||||||
needs: compute-suffix
|
needs: compute-suffix
|
||||||
|
@ -39,6 +41,7 @@ jobs:
|
||||||
latest=auto
|
latest=auto
|
||||||
tags: |
|
tags: |
|
||||||
type=ref,event=pr
|
type=ref,event=pr
|
||||||
|
type=ref,event=pr,suffix=-${{ needs.compute-suffix.outputs.short_sha }}
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
build-image-streaming:
|
build-image-streaming:
|
||||||
|
@ -55,4 +58,5 @@ jobs:
|
||||||
latest=auto
|
latest=auto
|
||||||
tags: |
|
tags: |
|
||||||
type=ref,event=pr
|
type=ref,event=pr
|
||||||
|
type=ref,event=pr,suffix=-${{ needs.compute-suffix.outputs.short_sha }}
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
2
.github/workflows/build-releases.yml
vendored
2
.github/workflows/build-releases.yml
vendored
|
@ -22,7 +22,7 @@ jobs:
|
||||||
# Only tag with latest when ran against the latest stable branch
|
# Only tag with latest when ran against the latest stable branch
|
||||||
# This needs to be updated after each minor version release
|
# This needs to be updated after each minor version release
|
||||||
flavor: |
|
flavor: |
|
||||||
latest=${{ startsWith(github.ref, 'refs/tags/v4.2.') }}
|
latest=${{ startsWith(github.ref, 'refs/tags/v4.3.') }}
|
||||||
tags: |
|
tags: |
|
||||||
type=pep440,pattern={{raw}}
|
type=pep440,pattern={{raw}}
|
||||||
type=pep440,pattern=v{{major}}.{{minor}}
|
type=pep440,pattern=v{{major}}.{{minor}}
|
||||||
|
|
2
.github/workflows/check-i18n.yml
vendored
2
.github/workflows/check-i18n.yml
vendored
|
@ -18,7 +18,7 @@ permissions:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check-i18n:
|
check-i18n:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
1
.github/workflows/crowdin-upload.yml
vendored
1
.github/workflows/crowdin-upload.yml
vendored
|
@ -1,7 +1,6 @@
|
||||||
name: Crowdin / Upload translations
|
name: Crowdin / Upload translations
|
||||||
|
|
||||||
on:
|
on:
|
||||||
merge_group:
|
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'main'
|
- 'main'
|
||||||
|
|
2
.github/workflows/test-migrations.yml
vendored
2
.github/workflows/test-migrations.yml
vendored
|
@ -32,6 +32,8 @@ jobs:
|
||||||
postgres:
|
postgres:
|
||||||
- 14-alpine
|
- 14-alpine
|
||||||
- 15-alpine
|
- 15-alpine
|
||||||
|
- 16-alpine
|
||||||
|
- 17-alpine
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
|
|
8
.github/workflows/test-ruby.yml
vendored
8
.github/workflows/test-ruby.yml
vendored
|
@ -143,7 +143,7 @@ jobs:
|
||||||
uses: ./.github/actions/setup-ruby
|
uses: ./.github/actions/setup-ruby
|
||||||
with:
|
with:
|
||||||
ruby-version: ${{ matrix.ruby-version}}
|
ruby-version: ${{ matrix.ruby-version}}
|
||||||
additional-system-dependencies: ffmpeg libpam-dev
|
additional-system-dependencies: ffmpeg imagemagick libpam-dev
|
||||||
|
|
||||||
- name: Load database schema
|
- name: Load database schema
|
||||||
run: |
|
run: |
|
||||||
|
@ -245,7 +245,7 @@ jobs:
|
||||||
uses: ./.github/actions/setup-ruby
|
uses: ./.github/actions/setup-ruby
|
||||||
with:
|
with:
|
||||||
ruby-version: ${{ matrix.ruby-version}}
|
ruby-version: ${{ matrix.ruby-version}}
|
||||||
additional-system-dependencies: ffmpeg libpam-dev libyaml-dev
|
additional-system-dependencies: ffmpeg libpam-dev
|
||||||
|
|
||||||
- name: Load database schema
|
- name: Load database schema
|
||||||
run: './bin/rails db:create db:schema:load db:seed'
|
run: './bin/rails db:create db:schema:load db:seed'
|
||||||
|
@ -325,7 +325,7 @@ jobs:
|
||||||
uses: ./.github/actions/setup-ruby
|
uses: ./.github/actions/setup-ruby
|
||||||
with:
|
with:
|
||||||
ruby-version: ${{ matrix.ruby-version}}
|
ruby-version: ${{ matrix.ruby-version}}
|
||||||
additional-system-dependencies: ffmpeg
|
additional-system-dependencies: ffmpeg imagemagick
|
||||||
|
|
||||||
- name: Set up Javascript environment
|
- name: Set up Javascript environment
|
||||||
uses: ./.github/actions/setup-javascript
|
uses: ./.github/actions/setup-javascript
|
||||||
|
@ -445,7 +445,7 @@ jobs:
|
||||||
uses: ./.github/actions/setup-ruby
|
uses: ./.github/actions/setup-ruby
|
||||||
with:
|
with:
|
||||||
ruby-version: ${{ matrix.ruby-version}}
|
ruby-version: ${{ matrix.ruby-version}}
|
||||||
additional-system-dependencies: ffmpeg
|
additional-system-dependencies: ffmpeg imagemagick
|
||||||
|
|
||||||
- name: Set up Javascript environment
|
- name: Set up Javascript environment
|
||||||
uses: ./.github/actions/setup-javascript
|
uses: ./.github/actions/setup-javascript
|
||||||
|
|
|
@ -191,7 +191,7 @@ FROM build AS libvips
|
||||||
|
|
||||||
# libvips version to compile, change with [--build-arg VIPS_VERSION="8.15.2"]
|
# libvips version to compile, change with [--build-arg VIPS_VERSION="8.15.2"]
|
||||||
# renovate: datasource=github-releases depName=libvips packageName=libvips/libvips
|
# renovate: datasource=github-releases depName=libvips packageName=libvips/libvips
|
||||||
ARG VIPS_VERSION=8.15.3
|
ARG VIPS_VERSION=8.15.5
|
||||||
# libvips download URL, change with [--build-arg VIPS_URL="https://github.com/libvips/libvips/releases/download"]
|
# libvips download URL, change with [--build-arg VIPS_URL="https://github.com/libvips/libvips/releases/download"]
|
||||||
ARG VIPS_URL=https://github.com/libvips/libvips/releases/download
|
ARG VIPS_URL=https://github.com/libvips/libvips/releases/download
|
||||||
|
|
||||||
|
|
6
Gemfile
6
Gemfile
|
@ -61,7 +61,7 @@ gem 'irb', '~> 1.8'
|
||||||
gem 'kaminari', '~> 1.2'
|
gem 'kaminari', '~> 1.2'
|
||||||
gem 'link_header', '~> 0.0'
|
gem 'link_header', '~> 0.0'
|
||||||
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
|
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
|
||||||
gem 'mime-types', '~> 3.5.0', require: 'mime/types/columnar'
|
gem 'mime-types', '~> 3.6.0', require: 'mime/types/columnar'
|
||||||
gem 'nokogiri', '~> 1.15'
|
gem 'nokogiri', '~> 1.15'
|
||||||
gem 'oj', '~> 3.14'
|
gem 'oj', '~> 3.14'
|
||||||
gem 'ox', '~> 2.14'
|
gem 'ox', '~> 2.14'
|
||||||
|
@ -111,8 +111,8 @@ group :opentelemetry do
|
||||||
gem 'opentelemetry-instrumentation-http_client', '~> 0.22.3', require: false
|
gem 'opentelemetry-instrumentation-http_client', '~> 0.22.3', require: false
|
||||||
gem 'opentelemetry-instrumentation-net_http', '~> 0.22.4', require: false
|
gem 'opentelemetry-instrumentation-net_http', '~> 0.22.4', require: false
|
||||||
gem 'opentelemetry-instrumentation-pg', '~> 0.29.0', require: false
|
gem 'opentelemetry-instrumentation-pg', '~> 0.29.0', require: false
|
||||||
gem 'opentelemetry-instrumentation-rack', '~> 0.24.1', require: false
|
gem 'opentelemetry-instrumentation-rack', '~> 0.25.0', require: false
|
||||||
gem 'opentelemetry-instrumentation-rails', '~> 0.31.0', require: false
|
gem 'opentelemetry-instrumentation-rails', '~> 0.32.0', require: false
|
||||||
gem 'opentelemetry-instrumentation-redis', '~> 0.25.3', require: false
|
gem 'opentelemetry-instrumentation-redis', '~> 0.25.3', require: false
|
||||||
gem 'opentelemetry-instrumentation-sidekiq', '~> 0.25.2', require: false
|
gem 'opentelemetry-instrumentation-sidekiq', '~> 0.25.2', require: false
|
||||||
gem 'opentelemetry-sdk', '~> 1.4', require: false
|
gem 'opentelemetry-sdk', '~> 1.4', require: false
|
||||||
|
|
163
Gemfile.lock
163
Gemfile.lock
|
@ -10,35 +10,35 @@ GIT
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actioncable (7.1.4)
|
actioncable (7.1.4.1)
|
||||||
actionpack (= 7.1.4)
|
actionpack (= 7.1.4.1)
|
||||||
activesupport (= 7.1.4)
|
activesupport (= 7.1.4.1)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
zeitwerk (~> 2.6)
|
zeitwerk (~> 2.6)
|
||||||
actionmailbox (7.1.4)
|
actionmailbox (7.1.4.1)
|
||||||
actionpack (= 7.1.4)
|
actionpack (= 7.1.4.1)
|
||||||
activejob (= 7.1.4)
|
activejob (= 7.1.4.1)
|
||||||
activerecord (= 7.1.4)
|
activerecord (= 7.1.4.1)
|
||||||
activestorage (= 7.1.4)
|
activestorage (= 7.1.4.1)
|
||||||
activesupport (= 7.1.4)
|
activesupport (= 7.1.4.1)
|
||||||
mail (>= 2.7.1)
|
mail (>= 2.7.1)
|
||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
net-smtp
|
net-smtp
|
||||||
actionmailer (7.1.4)
|
actionmailer (7.1.4.1)
|
||||||
actionpack (= 7.1.4)
|
actionpack (= 7.1.4.1)
|
||||||
actionview (= 7.1.4)
|
actionview (= 7.1.4.1)
|
||||||
activejob (= 7.1.4)
|
activejob (= 7.1.4.1)
|
||||||
activesupport (= 7.1.4)
|
activesupport (= 7.1.4.1)
|
||||||
mail (~> 2.5, >= 2.5.4)
|
mail (~> 2.5, >= 2.5.4)
|
||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
net-smtp
|
net-smtp
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
actionpack (7.1.4)
|
actionpack (7.1.4.1)
|
||||||
actionview (= 7.1.4)
|
actionview (= 7.1.4.1)
|
||||||
activesupport (= 7.1.4)
|
activesupport (= 7.1.4.1)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
racc
|
racc
|
||||||
rack (>= 2.2.4)
|
rack (>= 2.2.4)
|
||||||
|
@ -46,15 +46,15 @@ GEM
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
rails-html-sanitizer (~> 1.6)
|
rails-html-sanitizer (~> 1.6)
|
||||||
actiontext (7.1.4)
|
actiontext (7.1.4.1)
|
||||||
actionpack (= 7.1.4)
|
actionpack (= 7.1.4.1)
|
||||||
activerecord (= 7.1.4)
|
activerecord (= 7.1.4.1)
|
||||||
activestorage (= 7.1.4)
|
activestorage (= 7.1.4.1)
|
||||||
activesupport (= 7.1.4)
|
activesupport (= 7.1.4.1)
|
||||||
globalid (>= 0.6.0)
|
globalid (>= 0.6.0)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (7.1.4)
|
actionview (7.1.4.1)
|
||||||
activesupport (= 7.1.4)
|
activesupport (= 7.1.4.1)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.11)
|
erubi (~> 1.11)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
|
@ -64,22 +64,22 @@ GEM
|
||||||
activemodel (>= 4.1)
|
activemodel (>= 4.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)
|
||||||
activejob (7.1.4)
|
activejob (7.1.4.1)
|
||||||
activesupport (= 7.1.4)
|
activesupport (= 7.1.4.1)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (7.1.4)
|
activemodel (7.1.4.1)
|
||||||
activesupport (= 7.1.4)
|
activesupport (= 7.1.4.1)
|
||||||
activerecord (7.1.4)
|
activerecord (7.1.4.1)
|
||||||
activemodel (= 7.1.4)
|
activemodel (= 7.1.4.1)
|
||||||
activesupport (= 7.1.4)
|
activesupport (= 7.1.4.1)
|
||||||
timeout (>= 0.4.0)
|
timeout (>= 0.4.0)
|
||||||
activestorage (7.1.4)
|
activestorage (7.1.4.1)
|
||||||
actionpack (= 7.1.4)
|
actionpack (= 7.1.4.1)
|
||||||
activejob (= 7.1.4)
|
activejob (= 7.1.4.1)
|
||||||
activerecord (= 7.1.4)
|
activerecord (= 7.1.4.1)
|
||||||
activesupport (= 7.1.4)
|
activesupport (= 7.1.4.1)
|
||||||
marcel (~> 1.0)
|
marcel (~> 1.0)
|
||||||
activesupport (7.1.4)
|
activesupport (7.1.4.1)
|
||||||
base64
|
base64
|
||||||
bigdecimal
|
bigdecimal
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
|
@ -100,17 +100,17 @@ GEM
|
||||||
attr_required (1.0.2)
|
attr_required (1.0.2)
|
||||||
awrence (1.2.1)
|
awrence (1.2.1)
|
||||||
aws-eventstream (1.3.0)
|
aws-eventstream (1.3.0)
|
||||||
aws-partitions (1.983.0)
|
aws-partitions (1.992.0)
|
||||||
aws-sdk-core (3.209.1)
|
aws-sdk-core (3.210.0)
|
||||||
aws-eventstream (~> 1, >= 1.3.0)
|
aws-eventstream (~> 1, >= 1.3.0)
|
||||||
aws-partitions (~> 1, >= 1.651.0)
|
aws-partitions (~> 1, >= 1.992.0)
|
||||||
aws-sigv4 (~> 1.9)
|
aws-sigv4 (~> 1.9)
|
||||||
jmespath (~> 1, >= 1.6.1)
|
jmespath (~> 1, >= 1.6.1)
|
||||||
aws-sdk-kms (1.94.0)
|
aws-sdk-kms (1.95.0)
|
||||||
aws-sdk-core (~> 3, >= 3.207.0)
|
aws-sdk-core (~> 3, >= 3.210.0)
|
||||||
aws-sigv4 (~> 1.5)
|
aws-sigv4 (~> 1.5)
|
||||||
aws-sdk-s3 (1.167.0)
|
aws-sdk-s3 (1.169.0)
|
||||||
aws-sdk-core (~> 3, >= 3.207.0)
|
aws-sdk-core (~> 3, >= 3.210.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.5)
|
aws-sigv4 (~> 1.5)
|
||||||
aws-sigv4 (1.10.0)
|
aws-sigv4 (1.10.0)
|
||||||
|
@ -137,7 +137,7 @@ GEM
|
||||||
blurhash (0.1.8)
|
blurhash (0.1.8)
|
||||||
bootsnap (1.18.4)
|
bootsnap (1.18.4)
|
||||||
msgpack (~> 1.2)
|
msgpack (~> 1.2)
|
||||||
brakeman (6.2.1)
|
brakeman (6.2.2)
|
||||||
racc
|
racc
|
||||||
browser (5.3.1)
|
browser (5.3.1)
|
||||||
brpoplpush-redis_script (0.1.3)
|
brpoplpush-redis_script (0.1.3)
|
||||||
|
@ -233,7 +233,7 @@ GEM
|
||||||
tzinfo
|
tzinfo
|
||||||
excon (0.111.0)
|
excon (0.111.0)
|
||||||
fabrication (2.31.0)
|
fabrication (2.31.0)
|
||||||
faker (3.4.2)
|
faker (3.5.1)
|
||||||
i18n (>= 1.8.11, < 2)
|
i18n (>= 1.8.11, < 2)
|
||||||
faraday (1.10.3)
|
faraday (1.10.3)
|
||||||
faraday-em_http (~> 1.0)
|
faraday-em_http (~> 1.0)
|
||||||
|
@ -429,9 +429,10 @@ GEM
|
||||||
azure-storage-blob (~> 2.0.1)
|
azure-storage-blob (~> 2.0.1)
|
||||||
hashie (~> 5.0)
|
hashie (~> 5.0)
|
||||||
memory_profiler (1.1.0)
|
memory_profiler (1.1.0)
|
||||||
mime-types (3.5.2)
|
mime-types (3.6.0)
|
||||||
|
logger
|
||||||
mime-types-data (~> 3.2015)
|
mime-types-data (~> 3.2015)
|
||||||
mime-types-data (3.2024.0820)
|
mime-types-data (3.2024.1001)
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
mini_portile2 (2.8.7)
|
mini_portile2 (2.8.7)
|
||||||
minitest (5.25.1)
|
minitest (5.25.1)
|
||||||
|
@ -503,7 +504,7 @@ GEM
|
||||||
opentelemetry-semantic_conventions
|
opentelemetry-semantic_conventions
|
||||||
opentelemetry-helpers-sql-obfuscation (0.2.0)
|
opentelemetry-helpers-sql-obfuscation (0.2.0)
|
||||||
opentelemetry-common (~> 0.21)
|
opentelemetry-common (~> 0.21)
|
||||||
opentelemetry-instrumentation-action_mailer (0.1.0)
|
opentelemetry-instrumentation-action_mailer (0.2.0)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-active_support (~> 0.1)
|
opentelemetry-instrumentation-active_support (~> 0.1)
|
||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
|
@ -515,13 +516,13 @@ GEM
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-active_support (~> 0.1)
|
opentelemetry-instrumentation-active_support (~> 0.1)
|
||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
opentelemetry-instrumentation-active_job (0.7.7)
|
opentelemetry-instrumentation-active_job (0.7.8)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
opentelemetry-instrumentation-active_model_serializers (0.20.2)
|
opentelemetry-instrumentation-active_model_serializers (0.20.2)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
opentelemetry-instrumentation-active_record (0.7.3)
|
opentelemetry-instrumentation-active_record (0.8.0)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
opentelemetry-instrumentation-active_support (0.6.0)
|
opentelemetry-instrumentation-active_support (0.6.0)
|
||||||
|
@ -553,16 +554,16 @@ GEM
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-helpers-sql-obfuscation
|
opentelemetry-helpers-sql-obfuscation
|
||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
opentelemetry-instrumentation-rack (0.24.6)
|
opentelemetry-instrumentation-rack (0.25.0)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
opentelemetry-instrumentation-rails (0.31.2)
|
opentelemetry-instrumentation-rails (0.32.0)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-action_mailer (~> 0.1.0)
|
opentelemetry-instrumentation-action_mailer (~> 0.2.0)
|
||||||
opentelemetry-instrumentation-action_pack (~> 0.9.0)
|
opentelemetry-instrumentation-action_pack (~> 0.9.0)
|
||||||
opentelemetry-instrumentation-action_view (~> 0.7.0)
|
opentelemetry-instrumentation-action_view (~> 0.7.0)
|
||||||
opentelemetry-instrumentation-active_job (~> 0.7.0)
|
opentelemetry-instrumentation-active_job (~> 0.7.0)
|
||||||
opentelemetry-instrumentation-active_record (~> 0.7.0)
|
opentelemetry-instrumentation-active_record (~> 0.8.0)
|
||||||
opentelemetry-instrumentation-active_support (~> 0.6.0)
|
opentelemetry-instrumentation-active_support (~> 0.6.0)
|
||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
opentelemetry-instrumentation-redis (0.25.7)
|
opentelemetry-instrumentation-redis (0.25.7)
|
||||||
|
@ -590,8 +591,8 @@ GEM
|
||||||
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.5.8)
|
pg (1.5.9)
|
||||||
pghero (3.6.0)
|
pghero (3.6.1)
|
||||||
activerecord (>= 6.1)
|
activerecord (>= 6.1)
|
||||||
premailer (1.27.0)
|
premailer (1.27.0)
|
||||||
addressable
|
addressable
|
||||||
|
@ -615,7 +616,7 @@ GEM
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
raabro (1.4.0)
|
raabro (1.4.0)
|
||||||
racc (1.8.1)
|
racc (1.8.1)
|
||||||
rack (2.2.9)
|
rack (2.2.10)
|
||||||
rack-attack (6.7.0)
|
rack-attack (6.7.0)
|
||||||
rack (>= 1.0, < 4)
|
rack (>= 1.0, < 4)
|
||||||
rack-cors (2.0.2)
|
rack-cors (2.0.2)
|
||||||
|
@ -638,20 +639,20 @@ GEM
|
||||||
rackup (1.0.0)
|
rackup (1.0.0)
|
||||||
rack (< 3)
|
rack (< 3)
|
||||||
webrick
|
webrick
|
||||||
rails (7.1.4)
|
rails (7.1.4.1)
|
||||||
actioncable (= 7.1.4)
|
actioncable (= 7.1.4.1)
|
||||||
actionmailbox (= 7.1.4)
|
actionmailbox (= 7.1.4.1)
|
||||||
actionmailer (= 7.1.4)
|
actionmailer (= 7.1.4.1)
|
||||||
actionpack (= 7.1.4)
|
actionpack (= 7.1.4.1)
|
||||||
actiontext (= 7.1.4)
|
actiontext (= 7.1.4.1)
|
||||||
actionview (= 7.1.4)
|
actionview (= 7.1.4.1)
|
||||||
activejob (= 7.1.4)
|
activejob (= 7.1.4.1)
|
||||||
activemodel (= 7.1.4)
|
activemodel (= 7.1.4.1)
|
||||||
activerecord (= 7.1.4)
|
activerecord (= 7.1.4.1)
|
||||||
activestorage (= 7.1.4)
|
activestorage (= 7.1.4.1)
|
||||||
activesupport (= 7.1.4)
|
activesupport (= 7.1.4.1)
|
||||||
bundler (>= 1.15.0)
|
bundler (>= 1.15.0)
|
||||||
railties (= 7.1.4)
|
railties (= 7.1.4.1)
|
||||||
rails-controller-testing (1.0.5)
|
rails-controller-testing (1.0.5)
|
||||||
actionpack (>= 5.0.1.rc1)
|
actionpack (>= 5.0.1.rc1)
|
||||||
actionview (>= 5.0.1.rc1)
|
actionview (>= 5.0.1.rc1)
|
||||||
|
@ -666,9 +667,9 @@ GEM
|
||||||
rails-i18n (7.0.9)
|
rails-i18n (7.0.9)
|
||||||
i18n (>= 0.7, < 2)
|
i18n (>= 0.7, < 2)
|
||||||
railties (>= 6.0.0, < 8)
|
railties (>= 6.0.0, < 8)
|
||||||
railties (7.1.4)
|
railties (7.1.4.1)
|
||||||
actionpack (= 7.1.4)
|
actionpack (= 7.1.4.1)
|
||||||
activesupport (= 7.1.4)
|
activesupport (= 7.1.4.1)
|
||||||
irb
|
irb
|
||||||
rackup (>= 1.0.0)
|
rackup (>= 1.0.0)
|
||||||
rake (>= 12.2)
|
rake (>= 12.2)
|
||||||
|
@ -761,7 +762,7 @@ GEM
|
||||||
rubocop-rspec_rails (2.30.0)
|
rubocop-rspec_rails (2.30.0)
|
||||||
rubocop (~> 1.61)
|
rubocop (~> 1.61)
|
||||||
rubocop-rspec (~> 3, >= 3.0.1)
|
rubocop-rspec (~> 3, >= 3.0.1)
|
||||||
ruby-prof (1.7.0)
|
ruby-prof (1.7.1)
|
||||||
ruby-progressbar (1.13.0)
|
ruby-progressbar (1.13.0)
|
||||||
ruby-saml (1.17.0)
|
ruby-saml (1.17.0)
|
||||||
nokogiri (>= 1.13.10)
|
nokogiri (>= 1.13.10)
|
||||||
|
@ -822,7 +823,7 @@ GEM
|
||||||
stoplight (4.1.0)
|
stoplight (4.1.0)
|
||||||
redlock (~> 1.0)
|
redlock (~> 1.0)
|
||||||
stringio (3.1.1)
|
stringio (3.1.1)
|
||||||
strong_migrations (2.0.0)
|
strong_migrations (2.0.1)
|
||||||
activerecord (>= 6.1)
|
activerecord (>= 6.1)
|
||||||
swd (1.3.0)
|
swd (1.3.0)
|
||||||
activesupport (>= 3)
|
activesupport (>= 3)
|
||||||
|
@ -970,7 +971,7 @@ DEPENDENCIES
|
||||||
mario-redis-lock (~> 1.2)
|
mario-redis-lock (~> 1.2)
|
||||||
md-paperclip-azure (~> 2.2)
|
md-paperclip-azure (~> 2.2)
|
||||||
memory_profiler
|
memory_profiler
|
||||||
mime-types (~> 3.5.0)
|
mime-types (~> 3.6.0)
|
||||||
net-http (~> 0.4.0)
|
net-http (~> 0.4.0)
|
||||||
net-ldap (~> 0.18)
|
net-ldap (~> 0.18)
|
||||||
nokogiri (~> 1.15)
|
nokogiri (~> 1.15)
|
||||||
|
@ -991,8 +992,8 @@ DEPENDENCIES
|
||||||
opentelemetry-instrumentation-http_client (~> 0.22.3)
|
opentelemetry-instrumentation-http_client (~> 0.22.3)
|
||||||
opentelemetry-instrumentation-net_http (~> 0.22.4)
|
opentelemetry-instrumentation-net_http (~> 0.22.4)
|
||||||
opentelemetry-instrumentation-pg (~> 0.29.0)
|
opentelemetry-instrumentation-pg (~> 0.29.0)
|
||||||
opentelemetry-instrumentation-rack (~> 0.24.1)
|
opentelemetry-instrumentation-rack (~> 0.25.0)
|
||||||
opentelemetry-instrumentation-rails (~> 0.31.0)
|
opentelemetry-instrumentation-rails (~> 0.32.0)
|
||||||
opentelemetry-instrumentation-redis (~> 0.25.3)
|
opentelemetry-instrumentation-redis (~> 0.25.3)
|
||||||
opentelemetry-instrumentation-sidekiq (~> 0.25.2)
|
opentelemetry-instrumentation-sidekiq (~> 0.25.2)
|
||||||
opentelemetry-sdk (~> 1.4)
|
opentelemetry-sdk (~> 1.4)
|
||||||
|
@ -1057,7 +1058,7 @@ DEPENDENCIES
|
||||||
xorcist (~> 1.1)
|
xorcist (~> 1.1)
|
||||||
|
|
||||||
RUBY VERSION
|
RUBY VERSION
|
||||||
ruby 3.3.4p94
|
ruby 3.3.5p100
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.5.18
|
2.5.22
|
||||||
|
|
|
@ -52,7 +52,7 @@ class Api::V1::Notifications::RequestsController < Api::BaseController
|
||||||
private
|
private
|
||||||
|
|
||||||
def load_requests
|
def load_requests
|
||||||
requests = NotificationRequest.where(account: current_account).includes(:last_status, from_account: [:account_stat, :user]).to_a_paginated_by_id(
|
requests = NotificationRequest.where(account: current_account).without_suspended.includes(:last_status, from_account: [:account_stat, :user]).to_a_paginated_by_id(
|
||||||
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||||
params_slice(:max_id, :since_id, :min_id)
|
params_slice(:max_id, :since_id, :min_id)
|
||||||
)
|
)
|
||||||
|
|
|
@ -23,6 +23,6 @@ class Api::V1::Statuses::TranslationsController < Api::V1::Statuses::BaseControl
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_translation
|
def set_translation
|
||||||
@translation = TranslateStatusService.new.call(@status, content_locale)
|
@translation = TranslateStatusService.new.call(@status, I18n.locale.to_s)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::Web::PushSubscriptionsController < Api::Web::BaseController
|
class Api::Web::PushSubscriptionsController < Api::Web::BaseController
|
||||||
before_action :require_user!
|
before_action :require_user!, except: :destroy
|
||||||
before_action :set_push_subscription, only: :update
|
before_action :set_push_subscription, only: :update
|
||||||
before_action :destroy_previous_subscriptions, only: :create, if: :prior_subscriptions?
|
before_action :destroy_previous_subscriptions, only: :create, if: :prior_subscriptions?
|
||||||
after_action :update_session_with_subscription, only: :create
|
after_action :update_session_with_subscription, only: :create
|
||||||
|
@ -17,6 +17,13 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
|
||||||
render json: @push_subscription, serializer: REST::WebPushSubscriptionSerializer
|
render json: @push_subscription, serializer: REST::WebPushSubscriptionSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
push_subscription = ::Web::PushSubscription.find_by_token_for(:unsubscribe, params[:id])
|
||||||
|
push_subscription&.destroy
|
||||||
|
|
||||||
|
head 200
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def active_session
|
def active_session
|
||||||
|
|
|
@ -35,7 +35,7 @@ class ApplicationController < ActionController::Base
|
||||||
rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity
|
rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity
|
||||||
rescue_from Mastodon::RateLimitExceededError, with: :too_many_requests
|
rescue_from Mastodon::RateLimitExceededError, with: :too_many_requests
|
||||||
|
|
||||||
rescue_from HTTP::Error, OpenSSL::SSL::SSLError, with: :internal_server_error
|
rescue_from(*Mastodon::HTTP_CONNECTION_ERRORS, with: :internal_server_error)
|
||||||
rescue_from Mastodon::RaceConditionError, Stoplight::Error::RedLight, ActiveRecord::SerializationFailure, with: :service_unavailable
|
rescue_from Mastodon::RaceConditionError, Stoplight::Error::RedLight, ActiveRecord::SerializationFailure, with: :service_unavailable
|
||||||
|
|
||||||
rescue_from Seahorse::Client::NetworkingError do |e|
|
rescue_from Seahorse::Client::NetworkingError do |e|
|
||||||
|
|
|
@ -20,7 +20,7 @@ module Api::ErrorHandling
|
||||||
render json: { error: 'Record not found' }, status: 404
|
render json: { error: 'Record not found' }, status: 404
|
||||||
end
|
end
|
||||||
|
|
||||||
rescue_from HTTP::Error, Mastodon::UnexpectedResponseError do
|
rescue_from(*Mastodon::HTTP_CONNECTION_ERRORS, Mastodon::UnexpectedResponseError) do
|
||||||
render json: { error: 'Remote data could not be fetched' }, status: 503
|
render json: { error: 'Remote data could not be fetched' }, status: 503
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ module Auth::CaptchaConcern
|
||||||
end
|
end
|
||||||
|
|
||||||
def captcha_available?
|
def captcha_available?
|
||||||
ENV['HCAPTCHA_SECRET_KEY'].present? && ENV['HCAPTCHA_SITE_KEY'].present?
|
Rails.configuration.x.captcha.secret_key.present? && Rails.configuration.x.captcha.site_key.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
def captcha_enabled?
|
def captcha_enabled?
|
||||||
|
|
|
@ -80,7 +80,7 @@ module SignatureVerification
|
||||||
fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri} using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)", signed_string: compare_signed_string, signature: signature_params['signature']
|
fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri} using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)", signed_string: compare_signed_string, signature: signature_params['signature']
|
||||||
rescue SignatureVerificationError => e
|
rescue SignatureVerificationError => e
|
||||||
fail_with! e.message
|
fail_with! e.message
|
||||||
rescue HTTP::Error, OpenSSL::SSL::SSLError => e
|
rescue *Mastodon::HTTP_CONNECTION_ERRORS => e
|
||||||
fail_with! "Failed to fetch remote data: #{e.message}"
|
fail_with! "Failed to fetch remote data: #{e.message}"
|
||||||
rescue Mastodon::UnexpectedResponseError
|
rescue Mastodon::UnexpectedResponseError
|
||||||
fail_with! 'Failed to fetch remote data (got unexpected reply from server)'
|
fail_with! 'Failed to fetch remote data (got unexpected reply from server)'
|
||||||
|
|
|
@ -13,7 +13,7 @@ class MediaProxyController < ApplicationController
|
||||||
rescue_from ActiveRecord::RecordInvalid, with: :not_found
|
rescue_from ActiveRecord::RecordInvalid, with: :not_found
|
||||||
rescue_from Mastodon::UnexpectedResponseError, with: :not_found
|
rescue_from Mastodon::UnexpectedResponseError, with: :not_found
|
||||||
rescue_from Mastodon::NotPermittedError, with: :not_found
|
rescue_from Mastodon::NotPermittedError, with: :not_found
|
||||||
rescue_from HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError, with: :internal_server_error
|
rescue_from(*Mastodon::HTTP_CONNECTION_ERRORS, with: :internal_server_error)
|
||||||
|
|
||||||
def show
|
def show
|
||||||
with_redis_lock("media_download:#{params[:id]}") do
|
with_redis_lock("media_download:#{params[:id]}") do
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
module Admin::SettingsHelper
|
module Admin::SettingsHelper
|
||||||
def captcha_available?
|
def captcha_available?
|
||||||
ENV['HCAPTCHA_SECRET_KEY'].present? && ENV['HCAPTCHA_SITE_KEY'].present?
|
Rails.configuration.x.captcha.secret_key.present? && Rails.configuration.x.captcha.site_key.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
def login_activity_title(activity)
|
def login_activity_title(activity)
|
||||||
|
|
|
@ -120,18 +120,6 @@ module ApplicationHelper
|
||||||
inline_svg_tag 'check.svg'
|
inline_svg_tag 'check.svg'
|
||||||
end
|
end
|
||||||
|
|
||||||
def visibility_icon(status)
|
|
||||||
if status.public_visibility?
|
|
||||||
material_symbol('globe', title: I18n.t('statuses.visibilities.public'))
|
|
||||||
elsif status.unlisted_visibility?
|
|
||||||
material_symbol('lock_open', title: I18n.t('statuses.visibilities.unlisted'))
|
|
||||||
elsif status.private_visibility? || status.limited_visibility?
|
|
||||||
material_symbol('lock', title: I18n.t('statuses.visibilities.private'))
|
|
||||||
elsif status.direct_visibility?
|
|
||||||
material_symbol('alternate_email', title: I18n.t('statuses.visibilities.direct'))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def interrelationships_icon(relationships, account_id)
|
def interrelationships_icon(relationships, account_id)
|
||||||
if relationships.following[account_id] && relationships.followed_by[account_id]
|
if relationships.following[account_id] && relationships.followed_by[account_id]
|
||||||
material_symbol('sync_alt', title: I18n.t('relationships.mutual'), class: 'active passive')
|
material_symbol('sync_alt', title: I18n.t('relationships.mutual'), class: 'active passive')
|
||||||
|
@ -245,6 +233,11 @@ module ApplicationHelper
|
||||||
tag.input(type: :text, maxlength: 999, spellcheck: false, readonly: true, **options)
|
tag.input(type: :text, maxlength: 999, spellcheck: false, readonly: true, **options)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def recent_tag_usage(tag)
|
||||||
|
people = tag.history.aggregate(2.days.ago.to_date..Time.zone.today).accounts
|
||||||
|
I18n.t 'user_mailer.welcome.hashtags_recent_count', people: number_with_delimiter(people), count: people
|
||||||
|
end
|
||||||
|
|
||||||
# glitch-soc addition to handle the multiple flavors
|
# glitch-soc addition to handle the multiple flavors
|
||||||
def preload_locale_pack
|
def preload_locale_pack
|
||||||
supported_locales = Themes.instance.flavour(current_flavour)['locales']
|
supported_locales = Themes.instance.flavour(current_flavour)['locales']
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module FormattingHelper
|
module FormattingHelper
|
||||||
|
SYNDICATED_EMOJI_STYLES = <<~CSS.squish
|
||||||
|
height: 1.1em;
|
||||||
|
margin: -.2ex .15em .2ex;
|
||||||
|
object-fit: contain;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: 1.1em;
|
||||||
|
CSS
|
||||||
|
|
||||||
def html_aware_format(text, local, options = {})
|
def html_aware_format(text, local, options = {})
|
||||||
HtmlAwareFormatter.new(text, local, options).to_s
|
HtmlAwareFormatter.new(text, local, options).to_s
|
||||||
end
|
end
|
||||||
|
@ -23,33 +31,10 @@ module FormattingHelper
|
||||||
end
|
end
|
||||||
|
|
||||||
def rss_status_content_format(status)
|
def rss_status_content_format(status)
|
||||||
html = status_content_format(status)
|
|
||||||
|
|
||||||
before_html = if status.spoiler_text?
|
|
||||||
tag.p do
|
|
||||||
tag.strong do
|
|
||||||
I18n.t('rss.content_warning', locale: available_locale_or_nil(status.language) || I18n.default_locale)
|
|
||||||
end
|
|
||||||
|
|
||||||
status.spoiler_text
|
|
||||||
end + tag.hr
|
|
||||||
end
|
|
||||||
|
|
||||||
after_html = if status.preloadable_poll
|
|
||||||
tag.p do
|
|
||||||
safe_join(
|
|
||||||
status.preloadable_poll.options.map do |o|
|
|
||||||
tag.send(status.preloadable_poll.multiple? ? 'checkbox' : 'radio', o, disabled: true)
|
|
||||||
end,
|
|
||||||
tag.br
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
prerender_custom_emojis(
|
prerender_custom_emojis(
|
||||||
safe_join([before_html, html, after_html]),
|
wrapped_status_content_format(status),
|
||||||
status.emojis,
|
status.emojis,
|
||||||
style: 'width: 1.1em; height: 1.1em; object-fit: contain; vertical-align: middle; margin: -.2ex .15em .2ex'
|
style: SYNDICATED_EMOJI_STYLES
|
||||||
).to_str
|
).to_str
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -64,4 +49,47 @@ module FormattingHelper
|
||||||
html_aware_format(field.value, field.account.local?, with_rel_me: with_rel_me, with_domains: true, multiline: false)
|
html_aware_format(field.value, field.account.local?, with_rel_me: with_rel_me, with_domains: true, multiline: false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def wrapped_status_content_format(status)
|
||||||
|
safe_join [
|
||||||
|
rss_content_preroll(status),
|
||||||
|
status_content_format(status),
|
||||||
|
rss_content_postroll(status),
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
def rss_content_preroll(status)
|
||||||
|
if status.spoiler_text?
|
||||||
|
safe_join [
|
||||||
|
tag.p { spoiler_with_warning(status) },
|
||||||
|
tag.hr,
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def spoiler_with_warning(status)
|
||||||
|
safe_join [
|
||||||
|
tag.strong { I18n.t('rss.content_warning', locale: available_locale_or_nil(status.language) || I18n.default_locale) },
|
||||||
|
status.spoiler_text,
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
def rss_content_postroll(status)
|
||||||
|
if status.preloadable_poll
|
||||||
|
tag.p do
|
||||||
|
poll_option_tags(status)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def poll_option_tags(status)
|
||||||
|
safe_join(
|
||||||
|
status.preloadable_poll.options.map do |option|
|
||||||
|
tag.send(status.preloadable_poll.multiple? ? 'checkbox' : 'radio', option, disabled: true)
|
||||||
|
end,
|
||||||
|
tag.br
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -162,7 +162,7 @@ module LanguagesHelper
|
||||||
th: ['Thai', 'ไทย'].freeze,
|
th: ['Thai', 'ไทย'].freeze,
|
||||||
ti: ['Tigrinya', 'ትግርኛ'].freeze,
|
ti: ['Tigrinya', 'ትግርኛ'].freeze,
|
||||||
tk: ['Turkmen', 'Türkmen'].freeze,
|
tk: ['Turkmen', 'Türkmen'].freeze,
|
||||||
tl: ['Tagalog', 'Wikang Tagalog'].freeze,
|
tl: ['Tagalog', 'Tagalog'].freeze,
|
||||||
tn: ['Tswana', 'Setswana'].freeze,
|
tn: ['Tswana', 'Setswana'].freeze,
|
||||||
to: ['Tonga', 'faka Tonga'].freeze,
|
to: ['Tonga', 'faka Tonga'].freeze,
|
||||||
tr: ['Turkish', 'Türkçe'].freeze,
|
tr: ['Turkish', 'Türkçe'].freeze,
|
||||||
|
@ -193,6 +193,7 @@ module LanguagesHelper
|
||||||
ckb: ['Sorani (Kurdish)', 'سۆرانی'].freeze,
|
ckb: ['Sorani (Kurdish)', 'سۆرانی'].freeze,
|
||||||
cnr: ['Montenegrin', 'crnogorski'].freeze,
|
cnr: ['Montenegrin', 'crnogorski'].freeze,
|
||||||
csb: ['Kashubian', 'Kaszëbsczi'].freeze,
|
csb: ['Kashubian', 'Kaszëbsczi'].freeze,
|
||||||
|
gsw: ['Swiss German', 'Schwiizertütsch'].freeze,
|
||||||
jbo: ['Lojban', 'la .lojban.'].freeze,
|
jbo: ['Lojban', 'la .lojban.'].freeze,
|
||||||
kab: ['Kabyle', 'Taqbaylit'].freeze,
|
kab: ['Kabyle', 'Taqbaylit'].freeze,
|
||||||
ldn: ['Láadan', 'Láadan'].freeze,
|
ldn: ['Láadan', 'Láadan'].freeze,
|
||||||
|
|
|
@ -12,7 +12,7 @@ module StatusesHelper
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
def nothing_here(extra_classes = '')
|
def nothing_here(extra_classes = '')
|
||||||
content_tag(:div, class: "nothing-here #{extra_classes}") do
|
tag.div(class: ['nothing-here', extra_classes]) do
|
||||||
t('accounts.nothing_here')
|
t('accounts.nothing_here')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -327,31 +327,24 @@ Rails.delegate(document, '.input-copy button', 'click', ({ target }) => {
|
||||||
|
|
||||||
if (!input) return;
|
if (!input) return;
|
||||||
|
|
||||||
const oldReadOnly = input.readOnly;
|
navigator.clipboard
|
||||||
|
.writeText(input.value)
|
||||||
input.readOnly = false;
|
.then(() => {
|
||||||
input.focus();
|
|
||||||
input.select();
|
|
||||||
input.setSelectionRange(0, input.value.length);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (document.execCommand('copy')) {
|
|
||||||
input.blur();
|
|
||||||
|
|
||||||
const parent = target.parentElement;
|
const parent = target.parentElement;
|
||||||
|
|
||||||
if (!parent) return;
|
if (parent) {
|
||||||
parent.classList.add('copied');
|
parent.classList.add('copied');
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
parent.classList.remove('copied');
|
parent.classList.remove('copied');
|
||||||
}, 700);
|
}, 700);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
input.readOnly = oldReadOnly;
|
return true;
|
||||||
|
})
|
||||||
|
.catch((error: unknown) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const toggleSidebar = () => {
|
const toggleSidebar = () => {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import type { ApiAccountJSON } from 'flavours/glitch/api_types/accounts';
|
||||||
import type {
|
import type {
|
||||||
ApiNotificationGroupJSON,
|
ApiNotificationGroupJSON,
|
||||||
ApiNotificationJSON,
|
ApiNotificationJSON,
|
||||||
|
NotificationType,
|
||||||
} from 'flavours/glitch/api_types/notifications';
|
} from 'flavours/glitch/api_types/notifications';
|
||||||
import { allNotificationTypes } from 'flavours/glitch/api_types/notifications';
|
import { allNotificationTypes } from 'flavours/glitch/api_types/notifications';
|
||||||
import type { ApiStatusJSON } from 'flavours/glitch/api_types/statuses';
|
import type { ApiStatusJSON } from 'flavours/glitch/api_types/statuses';
|
||||||
|
@ -15,6 +16,7 @@ import { usePendingItems } from 'flavours/glitch/initial_state';
|
||||||
import type { NotificationGap } from 'flavours/glitch/reducers/notification_groups';
|
import type { NotificationGap } from 'flavours/glitch/reducers/notification_groups';
|
||||||
import {
|
import {
|
||||||
selectSettingsNotificationsExcludedTypes,
|
selectSettingsNotificationsExcludedTypes,
|
||||||
|
selectSettingsNotificationsGroupFollows,
|
||||||
selectSettingsNotificationsQuickFilterActive,
|
selectSettingsNotificationsQuickFilterActive,
|
||||||
selectSettingsNotificationsShows,
|
selectSettingsNotificationsShows,
|
||||||
} from 'flavours/glitch/selectors/settings';
|
} from 'flavours/glitch/selectors/settings';
|
||||||
|
@ -68,17 +70,19 @@ function dispatchAssociatedRecords(
|
||||||
dispatch(importFetchedStatuses(fetchedStatuses));
|
dispatch(importFetchedStatuses(fetchedStatuses));
|
||||||
}
|
}
|
||||||
|
|
||||||
const supportedGroupedNotificationTypes = ['favourite', 'reblog'];
|
function selectNotificationGroupedTypes(state: RootState) {
|
||||||
|
const types: NotificationType[] = ['favourite', 'reblog'];
|
||||||
|
|
||||||
export function shouldGroupNotificationType(type: string) {
|
if (selectSettingsNotificationsGroupFollows(state)) types.push('follow');
|
||||||
return supportedGroupedNotificationTypes.includes(type);
|
|
||||||
|
return types;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchNotifications = createDataLoadingThunk(
|
export const fetchNotifications = createDataLoadingThunk(
|
||||||
'notificationGroups/fetch',
|
'notificationGroups/fetch',
|
||||||
async (_params, { getState }) =>
|
async (_params, { getState }) =>
|
||||||
apiFetchNotificationGroups({
|
apiFetchNotificationGroups({
|
||||||
grouped_types: supportedGroupedNotificationTypes,
|
grouped_types: selectNotificationGroupedTypes(getState()),
|
||||||
exclude_types: getExcludedTypes(getState()),
|
exclude_types: getExcludedTypes(getState()),
|
||||||
}),
|
}),
|
||||||
({ notifications, accounts, statuses }, { dispatch }) => {
|
({ notifications, accounts, statuses }, { dispatch }) => {
|
||||||
|
@ -102,7 +106,7 @@ export const fetchNotificationsGap = createDataLoadingThunk(
|
||||||
'notificationGroups/fetchGap',
|
'notificationGroups/fetchGap',
|
||||||
async (params: { gap: NotificationGap }, { getState }) =>
|
async (params: { gap: NotificationGap }, { getState }) =>
|
||||||
apiFetchNotificationGroups({
|
apiFetchNotificationGroups({
|
||||||
grouped_types: supportedGroupedNotificationTypes,
|
grouped_types: selectNotificationGroupedTypes(getState()),
|
||||||
max_id: params.gap.maxId,
|
max_id: params.gap.maxId,
|
||||||
exclude_types: getExcludedTypes(getState()),
|
exclude_types: getExcludedTypes(getState()),
|
||||||
}),
|
}),
|
||||||
|
@ -119,7 +123,7 @@ export const pollRecentNotifications = createDataLoadingThunk(
|
||||||
'notificationGroups/pollRecentNotifications',
|
'notificationGroups/pollRecentNotifications',
|
||||||
async (_params, { getState }) => {
|
async (_params, { getState }) => {
|
||||||
return apiFetchNotificationGroups({
|
return apiFetchNotificationGroups({
|
||||||
grouped_types: supportedGroupedNotificationTypes,
|
grouped_types: selectNotificationGroupedTypes(getState()),
|
||||||
max_id: undefined,
|
max_id: undefined,
|
||||||
exclude_types: getExcludedTypes(getState()),
|
exclude_types: getExcludedTypes(getState()),
|
||||||
// In slow mode, we don't want to include notifications that duplicate the already-displayed ones
|
// In slow mode, we don't want to include notifications that duplicate the already-displayed ones
|
||||||
|
@ -168,7 +172,10 @@ export const processNewNotificationForGroups = createAppAsyncThunk(
|
||||||
|
|
||||||
dispatchAssociatedRecords(dispatch, [notification]);
|
dispatchAssociatedRecords(dispatch, [notification]);
|
||||||
|
|
||||||
return notification;
|
return {
|
||||||
|
notification,
|
||||||
|
groupedTypes: selectNotificationGroupedTypes(state),
|
||||||
|
};
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ export interface ApiAccountRoleJSON {
|
||||||
}
|
}
|
||||||
|
|
||||||
// See app/serializers/rest/account_serializer.rb
|
// See app/serializers/rest/account_serializer.rb
|
||||||
export interface ApiAccountJSON {
|
export interface BaseApiAccountJSON {
|
||||||
acct: string;
|
acct: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
avatar_static: string;
|
avatar_static: string;
|
||||||
|
@ -45,3 +45,12 @@ export interface ApiAccountJSON {
|
||||||
memorial?: boolean;
|
memorial?: boolean;
|
||||||
hide_collections: boolean;
|
hide_collections: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See app/serializers/rest/muted_account_serializer.rb
|
||||||
|
export interface ApiMutedAccountJSON extends BaseApiAccountJSON {
|
||||||
|
mute_expires_at?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For now, we have the same type representing both `Account` and `MutedAccount`
|
||||||
|
// objects, but we should refactor this in the future.
|
||||||
|
export type ApiAccountJSON = ApiMutedAccountJSON;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { PropsWithChildren } from 'react';
|
import type { PropsWithChildren, JSX } from 'react';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
/* Significantly rewritten from upstream to keep the old design for now */
|
import { StatusBanner, BannerVariant } from './status_banner';
|
||||||
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
export const ContentWarning: React.FC<{
|
export const ContentWarning: React.FC<{
|
||||||
text: string;
|
text: string;
|
||||||
|
@ -8,20 +6,12 @@ export const ContentWarning: React.FC<{
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
icons?: React.ReactNode[];
|
icons?: React.ReactNode[];
|
||||||
}> = ({ text, expanded, onClick, icons }) => (
|
}> = ({ text, expanded, onClick, icons }) => (
|
||||||
<p>
|
<StatusBanner
|
||||||
<span dangerouslySetInnerHTML={{ __html: text }} className='translate' />{' '}
|
expanded={expanded}
|
||||||
<button
|
|
||||||
type='button'
|
|
||||||
className='status__content__spoiler-link'
|
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
aria-expanded={expanded}
|
variant={BannerVariant.Warning}
|
||||||
>
|
>
|
||||||
{expanded ? (
|
|
||||||
<FormattedMessage id='status.show_less' defaultMessage='Show less' />
|
|
||||||
) : (
|
|
||||||
<FormattedMessage id='status.show_more' defaultMessage='Show more' />
|
|
||||||
)}
|
|
||||||
{icons}
|
{icons}
|
||||||
</button>
|
<p dangerouslySetInnerHTML={{ __html: text }} />
|
||||||
</p>
|
</StatusBanner>
|
||||||
);
|
);
|
||||||
|
|
|
@ -10,13 +10,16 @@ export const FilterWarning: React.FC<{
|
||||||
<StatusBanner
|
<StatusBanner
|
||||||
expanded={expanded}
|
expanded={expanded}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
variant={BannerVariant.Blue}
|
variant={BannerVariant.Filter}
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='filter_warning.matches_filter'
|
id='filter_warning.matches_filter'
|
||||||
defaultMessage='Matches filter “{title}”'
|
defaultMessage='Matches filter “<span>{title}</span>”'
|
||||||
values={{ title }}
|
values={{
|
||||||
|
title,
|
||||||
|
span: (chunks) => <span className='filter-name'>{chunks}</span>,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
</StatusBanner>
|
</StatusBanner>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
import type { JSX } from 'react';
|
||||||
|
|
||||||
import { FormattedMessage, FormattedNumber } from 'react-intl';
|
import { FormattedMessage, FormattedNumber } from 'react-intl';
|
||||||
|
|
||||||
|
|
|
@ -654,7 +654,7 @@ class Status extends ImmutablePureComponent {
|
||||||
media={status.get('media_attachments')}
|
media={status.get('media_attachments')}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
} else if (['image', 'gifv'].includes(status.getIn(['media_attachments', 0, 'type'])) || status.get('media_attachments').size > 1) {
|
} else if (['image', 'gifv', 'unknown'].includes(status.getIn(['media_attachments', 0, 'type'])) || status.get('media_attachments').size > 1) {
|
||||||
media.push(
|
media.push(
|
||||||
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}>
|
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}>
|
||||||
{Component => (
|
{Component => (
|
||||||
|
|
|
@ -241,7 +241,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
menu.push({ text: intl.formatMessage(messages.share), action: this.handleShareClick });
|
menu.push({ text: intl.formatMessage(messages.share), action: this.handleShareClick });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (publicStatus && (signedIn || !isRemote)) {
|
if (publicStatus && !isRemote) {
|
||||||
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
|
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
export enum BannerVariant {
|
export enum BannerVariant {
|
||||||
Yellow = 'yellow',
|
Warning = 'warning',
|
||||||
Blue = 'blue',
|
Filter = 'filter',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StatusBanner: React.FC<{
|
export const StatusBanner: React.FC<{
|
||||||
|
@ -11,9 +11,9 @@ export const StatusBanner: React.FC<{
|
||||||
expanded?: boolean;
|
expanded?: boolean;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}> = ({ children, variant, expanded, onClick }) => (
|
}> = ({ children, variant, expanded, onClick }) => (
|
||||||
<div
|
<label
|
||||||
className={
|
className={
|
||||||
variant === BannerVariant.Yellow
|
variant === BannerVariant.Warning
|
||||||
? 'content-warning'
|
? 'content-warning'
|
||||||
: 'content-warning content-warning--filter'
|
: 'content-warning content-warning--filter'
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,11 @@ export const StatusBanner: React.FC<{
|
||||||
id='content_warning.hide'
|
id='content_warning.hide'
|
||||||
defaultMessage='Hide post'
|
defaultMessage='Hide post'
|
||||||
/>
|
/>
|
||||||
|
) : variant === BannerVariant.Warning ? (
|
||||||
|
<FormattedMessage
|
||||||
|
id='content_warning.show_more'
|
||||||
|
defaultMessage='Show more'
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='content_warning.show'
|
id='content_warning.show'
|
||||||
|
@ -33,5 +38,5 @@ export const StatusBanner: React.FC<{
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</label>
|
||||||
);
|
);
|
||||||
|
|
|
@ -378,7 +378,7 @@ class StatusContent extends PureComponent {
|
||||||
)).reduce((aggregate, item) => [...aggregate, item, ' '], []);
|
)).reduce((aggregate, item) => [...aggregate, item, ' '], []);
|
||||||
|
|
||||||
let spoilerIcons = [];
|
let spoilerIcons = [];
|
||||||
if (hidden && mediaIcons) {
|
if (mediaIcons) {
|
||||||
const mediaComponents = {
|
const mediaComponents = {
|
||||||
'link': LinkIcon,
|
'link': LinkIcon,
|
||||||
'picture-o': ImageIcon,
|
'picture-o': ImageIcon,
|
||||||
|
|
|
@ -327,31 +327,24 @@ Rails.delegate(document, '.input-copy button', 'click', ({ target }) => {
|
||||||
|
|
||||||
if (!input) return;
|
if (!input) return;
|
||||||
|
|
||||||
const oldReadOnly = input.readOnly;
|
navigator.clipboard
|
||||||
|
.writeText(input.value)
|
||||||
input.readOnly = false;
|
.then(() => {
|
||||||
input.focus();
|
|
||||||
input.select();
|
|
||||||
input.setSelectionRange(0, input.value.length);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (document.execCommand('copy')) {
|
|
||||||
input.blur();
|
|
||||||
|
|
||||||
const parent = target.parentElement;
|
const parent = target.parentElement;
|
||||||
|
|
||||||
if (!parent) return;
|
if (parent) {
|
||||||
parent.classList.add('copied');
|
parent.classList.add('copied');
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
parent.classList.remove('copied');
|
parent.classList.remove('copied');
|
||||||
}, 700);
|
}, 700);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
input.readOnly = oldReadOnly;
|
return true;
|
||||||
|
})
|
||||||
|
.catch((error: unknown) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const toggleSidebar = () => {
|
const toggleSidebar = () => {
|
||||||
|
|
|
@ -27,15 +27,19 @@ class ColumnSettings extends PureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='column-settings'>
|
<div className='column-settings'>
|
||||||
|
<section>
|
||||||
<div className='column-settings__row'>
|
<div className='column-settings__row'>
|
||||||
<SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media only' />} />
|
<SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media only' />} />
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
<span className='column-settings__section'><FormattedMessage id='home.column_settings.advanced' defaultMessage='Advanced' /></span>
|
<span className='column-settings__section'><FormattedMessage id='home.column_settings.advanced' defaultMessage='Advanced' /></span>
|
||||||
|
|
||||||
<div className='column-settings__row'>
|
<div className='column-settings__row'>
|
||||||
<SettingText settings={settings} settingPath={['regex', 'body']} onChange={onChange} label={intl.formatMessage(messages.filter_regex)} />
|
<SettingText settings={settings} settingPath={['regex', 'body']} onChange={onChange} label={intl.formatMessage(messages.filter_regex)} />
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ const messages = defineMessages({
|
||||||
minutes: { id: 'intervals.full.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}}' },
|
minutes: { id: 'intervals.full.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}}' },
|
||||||
hours: { id: 'intervals.full.hours', defaultMessage: '{number, plural, one {# hour} other {# hours}}' },
|
hours: { id: 'intervals.full.hours', defaultMessage: '{number, plural, one {# hour} other {# hours}}' },
|
||||||
days: { id: 'intervals.full.days', defaultMessage: '{number, plural, one {# day} other {# days}}' },
|
days: { id: 'intervals.full.days', defaultMessage: '{number, plural, one {# day} other {# days}}' },
|
||||||
singleChoice: { id: 'compose_form.poll.single', defaultMessage: 'Pick one' },
|
singleChoice: { id: 'compose_form.poll.single', defaultMessage: 'Single choice' },
|
||||||
multipleChoice: { id: 'compose_form.poll.multiple', defaultMessage: 'Multiple choice' },
|
multipleChoice: { id: 'compose_form.poll.multiple', defaultMessage: 'Multiple choice' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -129,8 +129,13 @@ export const InlineFollowSuggestions = ({ hidden }) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getComputedStyle(bodyRef.current).direction === 'rtl') {
|
||||||
|
setCanScrollLeft((bodyRef.current.clientWidth - bodyRef.current.scrollLeft) < bodyRef.current.scrollWidth);
|
||||||
|
setCanScrollRight(bodyRef.current.scrollLeft < 0);
|
||||||
|
} else {
|
||||||
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
|
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
|
||||||
setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth);
|
setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth);
|
||||||
|
}
|
||||||
}, [setCanScrollRight, setCanScrollLeft, bodyRef, suggestions]);
|
}, [setCanScrollRight, setCanScrollLeft, bodyRef, suggestions]);
|
||||||
|
|
||||||
const handleLeftNav = useCallback(() => {
|
const handleLeftNav = useCallback(() => {
|
||||||
|
@ -146,8 +151,13 @@ export const InlineFollowSuggestions = ({ hidden }) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getComputedStyle(bodyRef.current).direction === 'rtl') {
|
||||||
|
setCanScrollLeft((bodyRef.current.clientWidth - bodyRef.current.scrollLeft) < bodyRef.current.scrollWidth);
|
||||||
|
setCanScrollRight(bodyRef.current.scrollLeft < 0);
|
||||||
|
} else {
|
||||||
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
|
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
|
||||||
setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth);
|
setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth);
|
||||||
|
}
|
||||||
}, [setCanScrollRight, setCanScrollLeft, bodyRef]);
|
}, [setCanScrollRight, setCanScrollLeft, bodyRef]);
|
||||||
|
|
||||||
const handleDismiss = useCallback(() => {
|
const handleDismiss = useCallback(() => {
|
||||||
|
|
|
@ -40,6 +40,7 @@ class ColumnSettings extends PureComponent {
|
||||||
const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />;
|
const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />;
|
||||||
const showStr = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />;
|
const showStr = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />;
|
||||||
const soundStr = <FormattedMessage id='notifications.column_settings.sound' defaultMessage='Play sound' />;
|
const soundStr = <FormattedMessage id='notifications.column_settings.sound' defaultMessage='Play sound' />;
|
||||||
|
const groupStr = <FormattedMessage id='notifications.column_settings.group' defaultMessage='Group' />;
|
||||||
|
|
||||||
const showPushSettings = pushSettings.get('browserSupport') && pushSettings.get('isSubscribed');
|
const showPushSettings = pushSettings.get('browserSupport') && pushSettings.get('isSubscribed');
|
||||||
const pushStr = showPushSettings && <FormattedMessage id='notifications.column_settings.push' defaultMessage='Push notifications' />;
|
const pushStr = showPushSettings && <FormattedMessage id='notifications.column_settings.push' defaultMessage='Push notifications' />;
|
||||||
|
@ -96,6 +97,10 @@ class ColumnSettings extends PureComponent {
|
||||||
<PillBarButton prefix='notifications' settings={settings} settingPath={['shows', 'follow']} onChange={onChange} label={showStr} />
|
<PillBarButton prefix='notifications' settings={settings} settingPath={['shows', 'follow']} onChange={onChange} label={showStr} />
|
||||||
<PillBarButton prefix='notifications' settings={settings} settingPath={['sounds', 'follow']} onChange={onChange} label={soundStr} />
|
<PillBarButton prefix='notifications' settings={settings} settingPath={['sounds', 'follow']} onChange={onChange} label={soundStr} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className='column-settings__row'>
|
||||||
|
<SettingToggle prefix='notifications' settings={settings} settingPath={['group', 'follow']} onChange={onChange} label={groupStr} />
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section role='group' aria-labelledby='notifications-follow-request'>
|
<section role='group' aria-labelledby='notifications-follow-request'>
|
||||||
|
|
|
@ -56,11 +56,12 @@ const mapDispatchToProps = (dispatch) => ({
|
||||||
} else {
|
} else {
|
||||||
dispatch(changeSetting(['notifications', ...path], checked));
|
dispatch(changeSetting(['notifications', ...path], checked));
|
||||||
}
|
}
|
||||||
} else if(path[0] === 'groupingBeta') {
|
|
||||||
dispatch(changeSetting(['notifications', ...path], checked));
|
|
||||||
dispatch(initializeNotifications());
|
|
||||||
} else {
|
} else {
|
||||||
dispatch(changeSetting(['notifications', ...path], checked));
|
dispatch(changeSetting(['notifications', ...path], checked));
|
||||||
|
|
||||||
|
if(path[0] === 'group' && path[1] === 'follow') {
|
||||||
|
dispatch(initializeNotifications());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,21 @@
|
||||||
|
import type { JSX } from 'react';
|
||||||
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import PersonAddIcon from '@/material-icons/400-24px/person_add-fill.svg?react';
|
import PersonAddIcon from '@/material-icons/400-24px/person_add-fill.svg?react';
|
||||||
import { FollowersCounter } from 'flavours/glitch/components/counters';
|
import { FollowersCounter } from 'flavours/glitch/components/counters';
|
||||||
import { FollowButton } from 'flavours/glitch/components/follow_button';
|
import { FollowButton } from 'flavours/glitch/components/follow_button';
|
||||||
import { ShortNumber } from 'flavours/glitch/components/short_number';
|
import { ShortNumber } from 'flavours/glitch/components/short_number';
|
||||||
|
import { me } from 'flavours/glitch/initial_state';
|
||||||
import type { NotificationGroupFollow } from 'flavours/glitch/models/notification_group';
|
import type { NotificationGroupFollow } from 'flavours/glitch/models/notification_group';
|
||||||
import { useAppSelector } from 'flavours/glitch/store';
|
import { useAppSelector } from 'flavours/glitch/store';
|
||||||
|
|
||||||
import type { LabelRenderer } from './notification_group_with_status';
|
import type { LabelRenderer } from './notification_group_with_status';
|
||||||
import { NotificationGroupWithStatus } from './notification_group_with_status';
|
import { NotificationGroupWithStatus } from './notification_group_with_status';
|
||||||
|
|
||||||
const labelRenderer: LabelRenderer = (displayedName, total) => {
|
const labelRenderer: LabelRenderer = (displayedName, total, seeMoreHref) => {
|
||||||
if (total === 1)
|
if (total === 1)
|
||||||
return (
|
return (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
@ -23,10 +28,12 @@ const labelRenderer: LabelRenderer = (displayedName, total) => {
|
||||||
return (
|
return (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='notification.follow.name_and_others'
|
id='notification.follow.name_and_others'
|
||||||
defaultMessage='{name} and {count, plural, one {# other} other {# others}} followed you'
|
defaultMessage='{name} and <a>{count, plural, one {# other} other {# others}}</a> followed you'
|
||||||
values={{
|
values={{
|
||||||
name: displayedName,
|
name: displayedName,
|
||||||
count: total - 1,
|
count: total - 1,
|
||||||
|
a: (chunks) =>
|
||||||
|
seeMoreHref ? <Link to={seeMoreHref}>{chunks}</Link> : chunks,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -46,6 +53,10 @@ export const NotificationFollow: React.FC<{
|
||||||
notification: NotificationGroupFollow;
|
notification: NotificationGroupFollow;
|
||||||
unread: boolean;
|
unread: boolean;
|
||||||
}> = ({ notification, unread }) => {
|
}> = ({ notification, unread }) => {
|
||||||
|
const username = useAppSelector(
|
||||||
|
(state) => state.accounts.getIn([me, 'username']) as string,
|
||||||
|
);
|
||||||
|
|
||||||
let actions: JSX.Element | undefined;
|
let actions: JSX.Element | undefined;
|
||||||
let additionalContent: JSX.Element | undefined;
|
let additionalContent: JSX.Element | undefined;
|
||||||
|
|
||||||
|
@ -68,6 +79,7 @@ export const NotificationFollow: React.FC<{
|
||||||
timestamp={notification.latest_page_notification_at}
|
timestamp={notification.latest_page_notification_at}
|
||||||
count={notification.notifications_count}
|
count={notification.notifications_count}
|
||||||
labelRenderer={labelRenderer}
|
labelRenderer={labelRenderer}
|
||||||
|
labelSeeMoreHref={`/@${username}/followers`}
|
||||||
unread={unread}
|
unread={unread}
|
||||||
actions={actions}
|
actions={actions}
|
||||||
additionalContent={additionalContent}
|
additionalContent={additionalContent}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import type { JSX } from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
import type { IconProp } from 'flavours/glitch/components/icon';
|
import type { IconProp } from 'flavours/glitch/components/icon';
|
||||||
import { Icon } from 'flavours/glitch/components/icon';
|
import { Icon } from 'flavours/glitch/components/icon';
|
||||||
import Status from 'flavours/glitch/containers/status_container';
|
import Status from 'flavours/glitch/containers/status_container';
|
||||||
|
import { getStatusHidden } from 'flavours/glitch/selectors/filters';
|
||||||
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
|
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
|
||||||
|
|
||||||
import { DisplayedName } from './displayed_name';
|
import { DisplayedName } from './displayed_name';
|
||||||
|
@ -51,6 +52,12 @@ export const NotificationWithStatus: React.FC<{
|
||||||
(state) => state.statuses.getIn([statusId, 'visibility']) === 'direct',
|
(state) => state.statuses.getIn([statusId, 'visibility']) === 'direct',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isFiltered = useAppSelector(
|
||||||
|
(state) =>
|
||||||
|
statusId &&
|
||||||
|
getStatusHidden(state, { id: statusId, contextType: 'notifications' }),
|
||||||
|
);
|
||||||
|
|
||||||
const handlers = useMemo(
|
const handlers = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
open: () => {
|
open: () => {
|
||||||
|
@ -77,7 +84,7 @@ export const NotificationWithStatus: React.FC<{
|
||||||
[dispatch, statusId],
|
[dispatch, statusId],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!statusId) return null;
|
if (!statusId || isFiltered) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HotKeys handlers={handlers}>
|
<HotKeys handlers={handlers}>
|
||||||
|
|
|
@ -14,6 +14,8 @@ import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
||||||
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
||||||
import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react';
|
import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react';
|
||||||
import StarIcon from '@/material-icons/400-24px/star.svg?react';
|
import StarIcon from '@/material-icons/400-24px/star.svg?react';
|
||||||
|
import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react';
|
||||||
|
import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react';
|
||||||
import { replyCompose } from 'flavours/glitch/actions/compose';
|
import { replyCompose } from 'flavours/glitch/actions/compose';
|
||||||
import { toggleReblog, toggleFavourite } from 'flavours/glitch/actions/interactions';
|
import { toggleReblog, toggleFavourite } from 'flavours/glitch/actions/interactions';
|
||||||
import { openModal } from 'flavours/glitch/actions/modal';
|
import { openModal } from 'flavours/glitch/actions/modal';
|
||||||
|
@ -161,16 +163,20 @@ class Footer extends ImmutablePureComponent {
|
||||||
replyTitle = intl.formatMessage(messages.replyAll);
|
replyTitle = intl.formatMessage(messages.replyAll);
|
||||||
}
|
}
|
||||||
|
|
||||||
let reblogTitle = '';
|
let reblogTitle, reblogIconComponent;
|
||||||
|
|
||||||
if (status.get('reblogged')) {
|
if (status.get('reblogged')) {
|
||||||
reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
|
reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
|
||||||
|
reblogIconComponent = publicStatus ? RepeatIcon : RepeatPrivateIcon;
|
||||||
} else if (publicStatus) {
|
} else if (publicStatus) {
|
||||||
reblogTitle = intl.formatMessage(messages.reblog);
|
reblogTitle = intl.formatMessage(messages.reblog);
|
||||||
|
reblogIconComponent = RepeatIcon;
|
||||||
} else if (reblogPrivate) {
|
} else if (reblogPrivate) {
|
||||||
reblogTitle = intl.formatMessage(messages.reblog_private);
|
reblogTitle = intl.formatMessage(messages.reblog_private);
|
||||||
|
reblogIconComponent = RepeatPrivateIcon;
|
||||||
} else {
|
} else {
|
||||||
reblogTitle = intl.formatMessage(messages.cannot_reblog);
|
reblogTitle = intl.formatMessage(messages.cannot_reblog);
|
||||||
|
reblogIconComponent = RepeatDisabledIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
let replyButton = null;
|
let replyButton = null;
|
||||||
|
@ -201,7 +207,7 @@ class Footer extends ImmutablePureComponent {
|
||||||
return (
|
return (
|
||||||
<div className='picture-in-picture__footer'>
|
<div className='picture-in-picture__footer'>
|
||||||
{replyButton}
|
{replyButton}
|
||||||
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' iconComponent={RepeatIcon} onClick={this.handleReblogClick} counter={status.get('reblogs_count')} />
|
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' iconComponent={reblogIconComponent} onClick={this.handleReblogClick} counter={status.get('reblogs_count')} />
|
||||||
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' iconComponent={StarIcon} onClick={this.handleFavouriteClick} counter={status.get('favourites_count')} />
|
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' iconComponent={StarIcon} onClick={this.handleFavouriteClick} counter={status.get('favourites_count')} />
|
||||||
{withOpenButton && <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.open)} icon='external-link' iconComponent={OpenInNewIcon} onClick={this.handleOpenClick} href={status.get('url')} />}
|
{withOpenButton && <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.open)} icon='external-link' iconComponent={OpenInNewIcon} onClick={this.handleOpenClick} href={status.get('url')} />}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -116,6 +116,7 @@ export const MuteModal = ({ accountId, acct }) => {
|
||||||
<div className='safety-action-modal__bottom__collapsible'>
|
<div className='safety-action-modal__bottom__collapsible'>
|
||||||
<div className='safety-action-modal__field-group'>
|
<div className='safety-action-modal__field-group'>
|
||||||
<RadioButtonLabel name='duration' value='0' label={intl.formatMessage(messages.indefinite)} currentValue={muteDuration} onChange={handleChangeMuteDuration} />
|
<RadioButtonLabel name='duration' value='0' label={intl.formatMessage(messages.indefinite)} currentValue={muteDuration} onChange={handleChangeMuteDuration} />
|
||||||
|
<RadioButtonLabel name='duration' value='21600' label={intl.formatMessage(messages.hours, { number: 6 })} currentValue={muteDuration} onChange={handleChangeMuteDuration} />
|
||||||
<RadioButtonLabel name='duration' value='86400' label={intl.formatMessage(messages.hours, { number: 24 })} currentValue={muteDuration} onChange={handleChangeMuteDuration} />
|
<RadioButtonLabel name='duration' value='86400' label={intl.formatMessage(messages.hours, { number: 24 })} currentValue={muteDuration} onChange={handleChangeMuteDuration} />
|
||||||
<RadioButtonLabel name='duration' value='604800' label={intl.formatMessage(messages.days, { number: 7 })} currentValue={muteDuration} onChange={handleChangeMuteDuration} />
|
<RadioButtonLabel name='duration' value='604800' label={intl.formatMessage(messages.days, { number: 7 })} currentValue={muteDuration} onChange={handleChangeMuteDuration} />
|
||||||
<RadioButtonLabel name='duration' value='2592000' label={intl.formatMessage(messages.days, { number: 30 })} currentValue={muteDuration} onChange={handleChangeMuteDuration} />
|
<RadioButtonLabel name='duration' value='2592000' label={intl.formatMessage(messages.days, { number: 30 })} currentValue={muteDuration} onChange={handleChangeMuteDuration} />
|
||||||
|
|
|
@ -159,7 +159,5 @@
|
||||||
"status.local_only": "Only visible from your instance",
|
"status.local_only": "Only visible from your instance",
|
||||||
"status.react": "React",
|
"status.react": "React",
|
||||||
"status.show_filter_reason": "Show anyway",
|
"status.show_filter_reason": "Show anyway",
|
||||||
"status.show_less": "Show less",
|
|
||||||
"status.show_more": "Show more",
|
|
||||||
"status.uncollapse": "Uncollapse"
|
"status.uncollapse": "Uncollapse"
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,6 +95,9 @@ export const accountDefaultValues: AccountShape = {
|
||||||
limited: false,
|
limited: false,
|
||||||
moved: null,
|
moved: null,
|
||||||
hide_collections: false,
|
hide_collections: false,
|
||||||
|
// This comes from `ApiMutedAccountJSON`, but we should eventually
|
||||||
|
// store that in a different object.
|
||||||
|
mute_expires_at: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const AccountFactory = ImmutableRecord<AccountShape>(accountDefaultValues);
|
const AccountFactory = ImmutableRecord<AccountShape>(accountDefaultValues);
|
||||||
|
|
|
@ -57,7 +57,10 @@ export const accountsReducer: Reducer<typeof initialState> = (
|
||||||
return state.setIn([action.payload.id, 'hidden'], false);
|
return state.setIn([action.payload.id, 'hidden'], false);
|
||||||
else if (importAccounts.match(action))
|
else if (importAccounts.match(action))
|
||||||
return normalizeAccounts(state, action.payload.accounts);
|
return normalizeAccounts(state, action.payload.accounts);
|
||||||
else if (followAccountSuccess.match(action)) {
|
else if (
|
||||||
|
followAccountSuccess.match(action) &&
|
||||||
|
!action.payload.alreadyFollowing
|
||||||
|
) {
|
||||||
return state
|
return state
|
||||||
.update(action.payload.relationship.id, (account) =>
|
.update(action.payload.relationship.id, (account) =>
|
||||||
account?.update('followers_count', (n) => n + 1),
|
account?.update('followers_count', (n) => n + 1),
|
||||||
|
|
|
@ -21,7 +21,6 @@ import {
|
||||||
unmountNotifications,
|
unmountNotifications,
|
||||||
refreshStaleNotificationGroups,
|
refreshStaleNotificationGroups,
|
||||||
pollRecentNotifications,
|
pollRecentNotifications,
|
||||||
shouldGroupNotificationType,
|
|
||||||
} from 'flavours/glitch/actions/notification_groups';
|
} from 'flavours/glitch/actions/notification_groups';
|
||||||
import {
|
import {
|
||||||
disconnectTimeline,
|
disconnectTimeline,
|
||||||
|
@ -30,6 +29,7 @@ import {
|
||||||
import type {
|
import type {
|
||||||
ApiNotificationJSON,
|
ApiNotificationJSON,
|
||||||
ApiNotificationGroupJSON,
|
ApiNotificationGroupJSON,
|
||||||
|
NotificationType,
|
||||||
} from 'flavours/glitch/api_types/notifications';
|
} from 'flavours/glitch/api_types/notifications';
|
||||||
import { compareId } from 'flavours/glitch/compare_id';
|
import { compareId } from 'flavours/glitch/compare_id';
|
||||||
import { usePendingItems } from 'flavours/glitch/initial_state';
|
import { usePendingItems } from 'flavours/glitch/initial_state';
|
||||||
|
@ -205,8 +205,9 @@ function mergeGapsAround(
|
||||||
function processNewNotification(
|
function processNewNotification(
|
||||||
groups: NotificationGroupsState['groups'],
|
groups: NotificationGroupsState['groups'],
|
||||||
notification: ApiNotificationJSON,
|
notification: ApiNotificationJSON,
|
||||||
|
groupedTypes: NotificationType[],
|
||||||
) {
|
) {
|
||||||
if (!shouldGroupNotificationType(notification.type)) {
|
if (!groupedTypes.includes(notification.type)) {
|
||||||
notification = {
|
notification = {
|
||||||
...notification,
|
...notification,
|
||||||
group_key: `ungrouped-${notification.id}`,
|
group_key: `ungrouped-${notification.id}`,
|
||||||
|
@ -476,11 +477,13 @@ export const notificationGroupsReducer = createReducer<NotificationGroupsState>(
|
||||||
trimNotifications(state);
|
trimNotifications(state);
|
||||||
})
|
})
|
||||||
.addCase(processNewNotificationForGroups.fulfilled, (state, action) => {
|
.addCase(processNewNotificationForGroups.fulfilled, (state, action) => {
|
||||||
const notification = action.payload;
|
if (action.payload) {
|
||||||
if (notification) {
|
const { notification, groupedTypes } = action.payload;
|
||||||
|
|
||||||
processNewNotification(
|
processNewNotification(
|
||||||
usePendingItems ? state.pendingGroups : state.groups,
|
usePendingItems ? state.pendingGroups : state.groups,
|
||||||
notification,
|
notification,
|
||||||
|
groupedTypes,
|
||||||
);
|
);
|
||||||
updateLastReadId(state);
|
updateLastReadId(state);
|
||||||
trimNotifications(state);
|
trimNotifications(state);
|
||||||
|
@ -559,7 +562,10 @@ export const notificationGroupsReducer = createReducer<NotificationGroupsState>(
|
||||||
compareId(state.lastReadId, mostRecentGroup.page_max_id) < 0
|
compareId(state.lastReadId, mostRecentGroup.page_max_id) < 0
|
||||||
)
|
)
|
||||||
state.lastReadId = mostRecentGroup.page_max_id;
|
state.lastReadId = mostRecentGroup.page_max_id;
|
||||||
commitLastReadId(state);
|
|
||||||
|
// We don't call `commitLastReadId`, because that is conditional
|
||||||
|
// and we want to unconditionally update the state instead.
|
||||||
|
state.readMarkerId = state.lastReadId;
|
||||||
})
|
})
|
||||||
.addCase(fetchMarkers.fulfilled, (state, action) => {
|
.addCase(fetchMarkers.fulfilled, (state, action) => {
|
||||||
if (
|
if (
|
||||||
|
|
|
@ -82,6 +82,10 @@ const initialState = ImmutableMap({
|
||||||
'admin.sign_up': true,
|
'admin.sign_up': true,
|
||||||
'admin.report': true,
|
'admin.report': true,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
group: ImmutableMap({
|
||||||
|
follow: true
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
firehose: ImmutableMap({
|
firehose: ImmutableMap({
|
||||||
|
|
50
app/javascript/flavours/glitch/selectors/filters.ts
Normal file
50
app/javascript/flavours/glitch/selectors/filters.ts
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
import type { RootState } from 'flavours/glitch/store';
|
||||||
|
import { toServerSideType } from 'flavours/glitch/utils/filters';
|
||||||
|
|
||||||
|
// TODO: move to `app/javascript/flavours/glitch/models` and use more globally
|
||||||
|
type Filter = Immutable.Map<string, unknown>;
|
||||||
|
|
||||||
|
// TODO: move to `app/javascript/flavours/glitch/models` and use more globally
|
||||||
|
type FilterResult = Immutable.Map<string, unknown>;
|
||||||
|
|
||||||
|
export const getFilters = createSelector(
|
||||||
|
[
|
||||||
|
(state: RootState) => state.filters as Immutable.Map<string, Filter>,
|
||||||
|
(_, { contextType }: { contextType: string }) => contextType,
|
||||||
|
],
|
||||||
|
(filters, contextType) => {
|
||||||
|
if (!contextType) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const serverSideType = toServerSideType(contextType);
|
||||||
|
|
||||||
|
return filters.filter((filter) => {
|
||||||
|
const context = filter.get('context') as Immutable.List<string>;
|
||||||
|
const expiration = filter.get('expires_at') as Date | null;
|
||||||
|
return (
|
||||||
|
context.includes(serverSideType) &&
|
||||||
|
(expiration === null || expiration > now)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getStatusHidden = (
|
||||||
|
state: RootState,
|
||||||
|
{ id, contextType }: { id: string; contextType: string },
|
||||||
|
) => {
|
||||||
|
const filters = getFilters(state, { contextType });
|
||||||
|
if (filters === null) return false;
|
||||||
|
|
||||||
|
const filtered = state.statuses.getIn([id, 'filtered']) as
|
||||||
|
| Immutable.List<FilterResult>
|
||||||
|
| undefined;
|
||||||
|
return filtered?.some(
|
||||||
|
(result) =>
|
||||||
|
filters.getIn([result.get('filter'), 'filter_action']) === 'hide',
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,23 +1,12 @@
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
|
import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
|
||||||
|
|
||||||
import { toServerSideType } from 'flavours/glitch/utils/filters';
|
|
||||||
|
|
||||||
import { me } from '../initial_state';
|
import { me } from '../initial_state';
|
||||||
|
|
||||||
|
import { getFilters } from './filters';
|
||||||
|
|
||||||
export { makeGetAccount } from "./accounts";
|
export { makeGetAccount } from "./accounts";
|
||||||
|
|
||||||
const getFilters = createSelector([state => state.get('filters'), (_, { contextType }) => contextType], (filters, contextType) => {
|
|
||||||
if (!contextType) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
const serverSideType = toServerSideType(contextType);
|
|
||||||
|
|
||||||
return filters.filter(filter => filter.get('context').includes(serverSideType) && (filter.get('expires_at') === null || filter.get('expires_at') > now));
|
|
||||||
});
|
|
||||||
|
|
||||||
export const makeGetStatus = () => {
|
export const makeGetStatus = () => {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
[
|
[
|
||||||
|
|
|
@ -52,4 +52,7 @@ export const selectSettingsNotificationsMinimizeFilteredBanner = (
|
||||||
) =>
|
) =>
|
||||||
state.settings.getIn(['notifications', 'minimizeFilteredBanner']) as boolean;
|
state.settings.getIn(['notifications', 'minimizeFilteredBanner']) as boolean;
|
||||||
|
|
||||||
|
export const selectSettingsNotificationsGroupFollows = (state: RootState) =>
|
||||||
|
state.settings.getIn(['notifications', 'group', 'follow']) as boolean;
|
||||||
|
|
||||||
/* eslint-enable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */
|
/* eslint-enable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */
|
||||||
|
|
|
@ -1050,6 +1050,12 @@ a.name-tag,
|
||||||
color: var(--user-role-accent);
|
color: var(--user-role-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.applications-list {
|
||||||
|
.icon {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.announcements-list,
|
.announcements-list,
|
||||||
.filters-list {
|
.filters-list {
|
||||||
border: 1px solid var(--background-border-color);
|
border: 1px solid var(--background-border-color);
|
||||||
|
|
|
@ -1399,9 +1399,9 @@ body > [data-popper-placement] {
|
||||||
}
|
}
|
||||||
|
|
||||||
.status__content__spoiler-link {
|
.status__content__spoiler-link {
|
||||||
display: inline-flex; // glitch: media icon in spoiler button
|
display: inline-block;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
background: $action-button-color; // glitch: design used in more places
|
background: transparent;
|
||||||
border: 0;
|
border: 0;
|
||||||
color: $inverted-text-color;
|
color: $inverted-text-color;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
@ -1411,23 +1411,6 @@ body > [data-popper-placement] {
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
align-items: center; // glitch: content indicator
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
// glitch: design used in more places
|
|
||||||
background: lighten($action-button-color, 7%);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status__content__spoiler-icon {
|
|
||||||
display: inline-block;
|
|
||||||
margin-inline-start: 5px;
|
|
||||||
border-inline-start: 1px solid currentColor;
|
|
||||||
padding: 0;
|
|
||||||
padding-inline-start: 4px;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.status__wrapper--filtered {
|
.status__wrapper--filtered {
|
||||||
|
@ -1952,6 +1935,14 @@ body > [data-popper-placement] {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content-warning {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
@ -8542,79 +8533,23 @@ noscript {
|
||||||
background: rgba($base-overlay-background, 0.5);
|
background: rgba($base-overlay-background, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.list-adder,
|
||||||
.list-editor {
|
.list-editor {
|
||||||
background: $ui-base-color;
|
backdrop-filter: var(--background-filter);
|
||||||
|
background: var(--modal-background-color);
|
||||||
|
border: 1px solid var(--modal-border-color);
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
|
|
||||||
width: 380px;
|
width: 380px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
@media screen and (width <= 420px) {
|
@media screen and (width <= 420px) {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
h4 {
|
|
||||||
padding: 15px 0;
|
|
||||||
background: lighten($ui-base-color, 13%);
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 16px;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 8px 8px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drawer__pager {
|
|
||||||
height: 50vh;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drawer__inner {
|
|
||||||
border-radius: 0 0 8px 8px;
|
|
||||||
|
|
||||||
&.backdrop {
|
|
||||||
width: calc(100% - 60px);
|
|
||||||
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
|
|
||||||
border-radius: 0 0 0 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__accounts {
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account__display-name {
|
|
||||||
&:hover strong {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.account__avatar {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-adder {
|
.list-adder {
|
||||||
background: $ui-base-color;
|
|
||||||
flex-direction: column;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
|
|
||||||
width: 380px;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
@media screen and (width <= 420px) {
|
|
||||||
width: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__account {
|
|
||||||
background: lighten($ui-base-color, 13%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__lists {
|
&__lists {
|
||||||
background: lighten($ui-base-color, 13%);
|
|
||||||
height: 50vh;
|
height: 50vh;
|
||||||
border-radius: 0 0 8px 8px;
|
border-radius: 0 0 8px 8px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
@ -8635,6 +8570,52 @@ noscript {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-editor {
|
||||||
|
h4 {
|
||||||
|
padding: 15px 0;
|
||||||
|
background: lighten($ui-base-color, 13%);
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 16px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer__pager {
|
||||||
|
height: 50vh;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer__inner {
|
||||||
|
&.backdrop {
|
||||||
|
width: calc(100% - 60px);
|
||||||
|
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
|
||||||
|
border-radius: 0 0 0 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__accounts {
|
||||||
|
background: unset;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account__display-name {
|
||||||
|
&:hover strong {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.account__avatar {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11387,21 +11368,17 @@ noscript {
|
||||||
color: $darker-text-color;
|
color: $darker-text-color;
|
||||||
-webkit-line-clamp: 4;
|
-webkit-line-clamp: 4;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
max-height: 4 * 22px;
|
max-height: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
p {
|
|
||||||
display: none;
|
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
display: initial;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p,
|
p,
|
||||||
a {
|
a {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.reply-indicator__attachments {
|
.reply-indicator__attachments {
|
||||||
|
@ -11686,19 +11663,21 @@ noscript {
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-warning {
|
.content-warning {
|
||||||
|
display: block;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background: rgba($ui-highlight-color, 0.05);
|
background: rgba($ui-highlight-color, 0.05);
|
||||||
color: $secondary-text-color;
|
color: $secondary-text-color;
|
||||||
border-top: 1px solid;
|
border: 1px solid rgba($ui-highlight-color, 0.15);
|
||||||
border-bottom: 1px solid;
|
border-radius: 8px;
|
||||||
border-color: rgba($ui-highlight-color, 0.15);
|
|
||||||
padding: 8px (5px + 8px);
|
padding: 8px (5px + 8px);
|
||||||
position: relative;
|
position: relative;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link-button {
|
.link-button {
|
||||||
|
@ -11707,31 +11686,22 @@ noscript {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::before,
|
&--filter {
|
||||||
&::after {
|
color: $darker-text-color;
|
||||||
content: '';
|
|
||||||
display: block;
|
p {
|
||||||
position: absolute;
|
font-weight: normal;
|
||||||
height: 100%;
|
|
||||||
background: url('~images/warning-stripes.svg') repeat-y;
|
|
||||||
width: 5px;
|
|
||||||
top: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&::before {
|
.filter-name {
|
||||||
border-start-start-radius: 4px;
|
font-weight: 500;
|
||||||
border-end-start-radius: 4px;
|
color: $secondary-text-color;
|
||||||
inset-inline-start: 0;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
.status__content__spoiler-icon {
|
||||||
border-start-end-radius: 4px;
|
float: inline-end;
|
||||||
border-end-end-radius: 4px;
|
width: 20px;
|
||||||
inset-inline-end: 0;
|
height: 20px;
|
||||||
}
|
|
||||||
|
|
||||||
&--filter::before,
|
|
||||||
&--filter::after {
|
|
||||||
background-image: url('~images/filter-stripes.svg');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,8 @@ code {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 160px;
|
height: 160px;
|
||||||
|
max-width: 566px;
|
||||||
|
margin-inline: auto;
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
content: '';
|
content: '';
|
||||||
|
|
|
@ -76,4 +76,7 @@ body {
|
||||||
--background-color-tint: rgba(255, 255, 255, 80%);
|
--background-color-tint: rgba(255, 255, 255, 80%);
|
||||||
--background-filter: blur(10px);
|
--background-filter: blur(10px);
|
||||||
--on-surface-color: #{transparentize($ui-base-color, 0.65)};
|
--on-surface-color: #{transparentize($ui-base-color, 0.65)};
|
||||||
|
--rich-text-container-color: rgba(255, 216, 231, 100%);
|
||||||
|
--rich-text-text-color: rgba(114, 47, 83, 100%);
|
||||||
|
--rich-text-decorations-color: rgba(255, 175, 212, 100%);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,29 @@
|
||||||
.e-content,
|
.e-content,
|
||||||
.edit-indicator__content,
|
.edit-indicator__content,
|
||||||
.reply-indicator__content {
|
.reply-indicator__content {
|
||||||
|
code {
|
||||||
|
background: var(--rich-text-container-color);
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--rich-text-text-color);
|
||||||
|
font-size: 0.85em;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background: var(--rich-text-container-color);
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--rich-text-text-color);
|
||||||
|
|
||||||
|
code {
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pre,
|
pre,
|
||||||
blockquote {
|
blockquote {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 22px;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
unicode-bidi: plaintext;
|
unicode-bidi: plaintext;
|
||||||
|
|
||||||
|
@ -14,19 +34,45 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
blockquote {
|
blockquote {
|
||||||
padding-inline-start: 10px;
|
padding-inline-start: 32px;
|
||||||
border-inline-start: 3px solid $darker-text-color;
|
color: var(--rich-text-text-color);
|
||||||
color: $darker-text-color;
|
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
p:last-child {
|
&::before {
|
||||||
|
display: block;
|
||||||
|
content: '';
|
||||||
|
width: 24px;
|
||||||
|
height: 20px;
|
||||||
|
mask-image: url('~images/quote.svg');
|
||||||
|
background-color: var(--rich-text-decorations-color);
|
||||||
|
position: absolute;
|
||||||
|
inset-inline-start: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
margin-top: 4px;
|
||||||
|
border-inline-start: 3px solid var(--rich-text-decorations-color);
|
||||||
|
padding-inline-start: 16px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p:last-of-type {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& > ul,
|
& > ul,
|
||||||
& > ol {
|
& > ol {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 22px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
|
@ -76,7 +122,15 @@
|
||||||
|
|
||||||
ul,
|
ul,
|
||||||
ol {
|
ol {
|
||||||
margin-inline-start: 2em;
|
padding-inline-start: 24px;
|
||||||
|
|
||||||
|
li {
|
||||||
|
padding-inline-start: 8px;
|
||||||
|
|
||||||
|
&::marker {
|
||||||
|
text-align: end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -84,7 +138,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
list-style-type: disc;
|
list-style-type: '•';
|
||||||
|
|
||||||
|
li::marker {
|
||||||
|
text-align: start;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ol {
|
ol {
|
||||||
|
|
|
@ -90,6 +90,10 @@ body.rtl {
|
||||||
direction: rtl;
|
direction: rtl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.column-back-button__icon {
|
||||||
|
transform: scale(-1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
.simple_form select {
|
.simple_form select {
|
||||||
background: $ui-base-color
|
background: $ui-base-color
|
||||||
url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14.933 18.467' height='19.698' width='15.929'><path d='M3.467 14.967l-3.393-3.5H14.86l-3.392 3.5c-1.866 1.925-3.666 3.5-4 3.5-.335 0-2.135-1.575-4-3.5zm.266-11.234L7.467 0 11.2 3.733l3.733 3.734H0l3.733-3.734z' fill='#{hex-color(lighten($ui-base-color, 12%))}'/></svg>")
|
url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14.933 18.467' height='19.698' width='15.929'><path d='M3.467 14.967l-3.393-3.5H14.86l-3.392 3.5c-1.866 1.925-3.666 3.5-4 3.5-.335 0-2.135-1.575-4-3.5zm.266-11.234L7.467 0 11.2 3.733l3.733 3.734H0l3.733-3.734z' fill='#{hex-color(lighten($ui-base-color, 12%))}'/></svg>")
|
||||||
|
|
|
@ -122,4 +122,7 @@ $dismiss-overlay-width: 4rem;
|
||||||
--error-background-color: #{darken($error-red, 16%)};
|
--error-background-color: #{darken($error-red, 16%)};
|
||||||
--error-active-background-color: #{darken($error-red, 12%)};
|
--error-active-background-color: #{darken($error-red, 12%)};
|
||||||
--on-error-color: #fff;
|
--on-error-color: #fff;
|
||||||
|
--rich-text-container-color: rgba(87, 24, 60, 100%);
|
||||||
|
--rich-text-text-color: rgba(255, 175, 212, 100%);
|
||||||
|
--rich-text-decorations-color: rgba(128, 58, 95, 100%);
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 5.6 KiB |
|
@ -1 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg"><symbol id="mastodon-svg-logo" viewBox="0 0 216.4144 232.00976"><path d="M107.86523 0C78.203984.2425 49.672422 3.4535937 33.044922 11.089844c0 0-32.97656262 14.752031-32.97656262 65.082031 0 11.525-.224375 25.306175.140625 39.919925 1.19750002 49.22 9.02375002 97.72843 54.53124962 109.77343 20.9825 5.55375 38.99711 6.71547 53.505856 5.91797 26.31125-1.45875 41.08203-9.38867 41.08203-9.38867l-.86914-19.08984s-18.80171 5.92758-39.91796 5.20508c-20.921254-.7175-43.006879-2.25516-46.390629-27.94141-.3125-2.25625-.46875-4.66938-.46875-7.20313 0 0 20.536953 5.0204 46.564449 6.21289 15.915.73001 30.8393-.93343 45.99805-2.74218 29.07-3.47125 54.38125-21.3818 57.5625-37.74805 5.0125-25.78125 4.59961-62.916015 4.59961-62.916015 0-50.33-32.97461-65.082031-32.97461-65.082031C166.80539 3.4535938 138.255.2425 108.59375 0h-.72852zM74.296875 39.326172c12.355 0 21.710234 4.749297 27.896485 14.248047l6.01367 10.080078 6.01563-10.080078c6.185-9.49875 15.54023-14.248047 27.89648-14.248047 10.6775 0 19.28156 3.753672 25.85156 11.076172 6.36875 7.3225 9.53907 17.218828 9.53907 29.673828v60.941408h-24.14454V81.869141c0-12.46875-5.24453-18.798829-15.73828-18.798829-11.6025 0-17.41797 7.508516-17.41797 22.353516v32.375002H96.207031V85.423828c0-14.845-5.815468-22.353515-17.417969-22.353516-10.49375 0-15.740234 6.330079-15.740234 18.798829v59.148439H38.904297V80.076172c0-12.455 3.171016-22.351328 9.541015-29.673828 6.568751-7.3225 15.172813-11.076172 25.851563-11.076172z" /></symbol></svg>
|
|
Before Width: | Height: | Size: 1.5 KiB |
3
app/javascript/images/quote.svg
Normal file
3
app/javascript/images/quote.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg width="24" height="20" viewBox="0 0 24 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M23.933 2.82414C22.324 4.07931 21.0726 5.3569 20.1788 6.6569C19.3296 7.91207 18.905 9.1 18.905 10.2207C19.0838 10.131 19.3073 10.0862 19.5754 10.0862C19.8883 10.0414 20.1564 10.019 20.3799 10.019C21.4078 10.019 22.257 10.4448 22.9274 11.2966C23.6425 12.1034 24 13.1121 24 14.3224C24 15.8017 23.5084 17.0345 22.5251 18.0207C21.5419 19.0069 20.3129 19.5 18.838 19.5C17.2737 19.5 16.0447 18.9397 15.1508 17.819C14.257 16.6535 13.8101 15.1069 13.8101 13.1793C13.8101 10.8931 14.5028 8.62931 15.8883 6.38793C17.2737 4.14655 19.3073 2.01724 21.9888 0L23.933 2.82414ZM10.1229 2.82414C8.51397 4.07931 7.26257 5.3569 6.36872 6.6569C5.51955 7.91207 5.09497 9.1 5.09497 10.2207C5.27374 10.131 5.49721 10.0862 5.76536 10.0862C6.07821 10.0414 6.34637 10.019 6.56983 10.019C7.59777 10.019 8.44693 10.4448 9.11732 11.2966C9.8324 12.1034 10.1899 13.1121 10.1899 14.3224C10.1899 15.8017 9.69832 17.0345 8.71508 18.0207C7.73184 19.0069 6.50279 19.5 5.02793 19.5C3.46369 19.5 2.23464 18.9397 1.34078 17.819C0.446927 16.6535 0 15.1069 0 13.1793C0 10.8931 0.692738 8.62931 2.07821 6.38793C3.46369 4.14655 5.49721 2.01724 8.17877 0L10.1229 2.82414Z" fill="currentColor" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -8,6 +8,7 @@ import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
|
||||||
import type {
|
import type {
|
||||||
ApiNotificationGroupJSON,
|
ApiNotificationGroupJSON,
|
||||||
ApiNotificationJSON,
|
ApiNotificationJSON,
|
||||||
|
NotificationType,
|
||||||
} from 'mastodon/api_types/notifications';
|
} from 'mastodon/api_types/notifications';
|
||||||
import { allNotificationTypes } from 'mastodon/api_types/notifications';
|
import { allNotificationTypes } from 'mastodon/api_types/notifications';
|
||||||
import type { ApiStatusJSON } from 'mastodon/api_types/statuses';
|
import type { ApiStatusJSON } from 'mastodon/api_types/statuses';
|
||||||
|
@ -15,6 +16,7 @@ import { usePendingItems } from 'mastodon/initial_state';
|
||||||
import type { NotificationGap } from 'mastodon/reducers/notification_groups';
|
import type { NotificationGap } from 'mastodon/reducers/notification_groups';
|
||||||
import {
|
import {
|
||||||
selectSettingsNotificationsExcludedTypes,
|
selectSettingsNotificationsExcludedTypes,
|
||||||
|
selectSettingsNotificationsGroupFollows,
|
||||||
selectSettingsNotificationsQuickFilterActive,
|
selectSettingsNotificationsQuickFilterActive,
|
||||||
selectSettingsNotificationsShows,
|
selectSettingsNotificationsShows,
|
||||||
} from 'mastodon/selectors/settings';
|
} from 'mastodon/selectors/settings';
|
||||||
|
@ -68,17 +70,19 @@ function dispatchAssociatedRecords(
|
||||||
dispatch(importFetchedStatuses(fetchedStatuses));
|
dispatch(importFetchedStatuses(fetchedStatuses));
|
||||||
}
|
}
|
||||||
|
|
||||||
const supportedGroupedNotificationTypes = ['favourite', 'reblog'];
|
function selectNotificationGroupedTypes(state: RootState) {
|
||||||
|
const types: NotificationType[] = ['favourite', 'reblog'];
|
||||||
|
|
||||||
export function shouldGroupNotificationType(type: string) {
|
if (selectSettingsNotificationsGroupFollows(state)) types.push('follow');
|
||||||
return supportedGroupedNotificationTypes.includes(type);
|
|
||||||
|
return types;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fetchNotifications = createDataLoadingThunk(
|
export const fetchNotifications = createDataLoadingThunk(
|
||||||
'notificationGroups/fetch',
|
'notificationGroups/fetch',
|
||||||
async (_params, { getState }) =>
|
async (_params, { getState }) =>
|
||||||
apiFetchNotificationGroups({
|
apiFetchNotificationGroups({
|
||||||
grouped_types: supportedGroupedNotificationTypes,
|
grouped_types: selectNotificationGroupedTypes(getState()),
|
||||||
exclude_types: getExcludedTypes(getState()),
|
exclude_types: getExcludedTypes(getState()),
|
||||||
}),
|
}),
|
||||||
({ notifications, accounts, statuses }, { dispatch }) => {
|
({ notifications, accounts, statuses }, { dispatch }) => {
|
||||||
|
@ -102,7 +106,7 @@ export const fetchNotificationsGap = createDataLoadingThunk(
|
||||||
'notificationGroups/fetchGap',
|
'notificationGroups/fetchGap',
|
||||||
async (params: { gap: NotificationGap }, { getState }) =>
|
async (params: { gap: NotificationGap }, { getState }) =>
|
||||||
apiFetchNotificationGroups({
|
apiFetchNotificationGroups({
|
||||||
grouped_types: supportedGroupedNotificationTypes,
|
grouped_types: selectNotificationGroupedTypes(getState()),
|
||||||
max_id: params.gap.maxId,
|
max_id: params.gap.maxId,
|
||||||
exclude_types: getExcludedTypes(getState()),
|
exclude_types: getExcludedTypes(getState()),
|
||||||
}),
|
}),
|
||||||
|
@ -119,7 +123,7 @@ export const pollRecentNotifications = createDataLoadingThunk(
|
||||||
'notificationGroups/pollRecentNotifications',
|
'notificationGroups/pollRecentNotifications',
|
||||||
async (_params, { getState }) => {
|
async (_params, { getState }) => {
|
||||||
return apiFetchNotificationGroups({
|
return apiFetchNotificationGroups({
|
||||||
grouped_types: supportedGroupedNotificationTypes,
|
grouped_types: selectNotificationGroupedTypes(getState()),
|
||||||
max_id: undefined,
|
max_id: undefined,
|
||||||
exclude_types: getExcludedTypes(getState()),
|
exclude_types: getExcludedTypes(getState()),
|
||||||
// In slow mode, we don't want to include notifications that duplicate the already-displayed ones
|
// In slow mode, we don't want to include notifications that duplicate the already-displayed ones
|
||||||
|
@ -168,7 +172,10 @@ export const processNewNotificationForGroups = createAppAsyncThunk(
|
||||||
|
|
||||||
dispatchAssociatedRecords(dispatch, [notification]);
|
dispatchAssociatedRecords(dispatch, [notification]);
|
||||||
|
|
||||||
return notification;
|
return {
|
||||||
|
notification,
|
||||||
|
groupedTypes: selectNotificationGroupedTypes(state),
|
||||||
|
};
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ export interface ApiAccountRoleJSON {
|
||||||
}
|
}
|
||||||
|
|
||||||
// See app/serializers/rest/account_serializer.rb
|
// See app/serializers/rest/account_serializer.rb
|
||||||
export interface ApiAccountJSON {
|
export interface BaseApiAccountJSON {
|
||||||
acct: string;
|
acct: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
avatar_static: string;
|
avatar_static: string;
|
||||||
|
@ -45,3 +45,12 @@ export interface ApiAccountJSON {
|
||||||
memorial?: boolean;
|
memorial?: boolean;
|
||||||
hide_collections: boolean;
|
hide_collections: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See app/serializers/rest/muted_account_serializer.rb
|
||||||
|
export interface ApiMutedAccountJSON extends BaseApiAccountJSON {
|
||||||
|
mute_expires_at?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For now, we have the same type representing both `Account` and `MutedAccount`
|
||||||
|
// objects, but we should refactor this in the future.
|
||||||
|
export type ApiAccountJSON = ApiMutedAccountJSON;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { PropsWithChildren } from 'react';
|
import type { PropsWithChildren, JSX } from 'react';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
|
@ -8,7 +8,7 @@ export const ContentWarning: React.FC<{
|
||||||
<StatusBanner
|
<StatusBanner
|
||||||
expanded={expanded}
|
expanded={expanded}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
variant={BannerVariant.Yellow}
|
variant={BannerVariant.Warning}
|
||||||
>
|
>
|
||||||
<p dangerouslySetInnerHTML={{ __html: text }} />
|
<p dangerouslySetInnerHTML={{ __html: text }} />
|
||||||
</StatusBanner>
|
</StatusBanner>
|
||||||
|
|
|
@ -10,13 +10,16 @@ export const FilterWarning: React.FC<{
|
||||||
<StatusBanner
|
<StatusBanner
|
||||||
expanded={expanded}
|
expanded={expanded}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
variant={BannerVariant.Blue}
|
variant={BannerVariant.Filter}
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='filter_warning.matches_filter'
|
id='filter_warning.matches_filter'
|
||||||
defaultMessage='Matches filter “{title}”'
|
defaultMessage='Matches filter “<span>{title}</span>”'
|
||||||
values={{ title }}
|
values={{
|
||||||
|
title,
|
||||||
|
span: (chunks) => <span className='filter-name'>{chunks}</span>,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
</StatusBanner>
|
</StatusBanner>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
import type { JSX } from 'react';
|
||||||
|
|
||||||
import { FormattedMessage, FormattedNumber } from 'react-intl';
|
import { FormattedMessage, FormattedNumber } from 'react-intl';
|
||||||
|
|
||||||
|
|
|
@ -449,7 +449,7 @@ class Status extends ImmutablePureComponent {
|
||||||
} else if (status.get('media_attachments').size > 0) {
|
} else if (status.get('media_attachments').size > 0) {
|
||||||
const language = status.getIn(['translation', 'language']) || status.get('language');
|
const language = status.getIn(['translation', 'language']) || status.get('language');
|
||||||
|
|
||||||
if (['image', 'gifv'].includes(status.getIn(['media_attachments', 0, 'type'])) || status.get('media_attachments').size > 1) {
|
if (['image', 'gifv', 'unknown'].includes(status.getIn(['media_attachments', 0, 'type'])) || status.get('media_attachments').size > 1) {
|
||||||
media = (
|
media = (
|
||||||
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}>
|
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}>
|
||||||
{Component => (
|
{Component => (
|
||||||
|
|
|
@ -264,7 +264,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
menu.push({ text: intl.formatMessage(messages.share), action: this.handleShareClick });
|
menu.push({ text: intl.formatMessage(messages.share), action: this.handleShareClick });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (publicStatus && (signedIn || !isRemote)) {
|
if (publicStatus && !isRemote) {
|
||||||
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
|
menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
export enum BannerVariant {
|
export enum BannerVariant {
|
||||||
Yellow = 'yellow',
|
Warning = 'warning',
|
||||||
Blue = 'blue',
|
Filter = 'filter',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StatusBanner: React.FC<{
|
export const StatusBanner: React.FC<{
|
||||||
|
@ -11,9 +11,9 @@ export const StatusBanner: React.FC<{
|
||||||
expanded?: boolean;
|
expanded?: boolean;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}> = ({ children, variant, expanded, onClick }) => (
|
}> = ({ children, variant, expanded, onClick }) => (
|
||||||
<div
|
<label
|
||||||
className={
|
className={
|
||||||
variant === BannerVariant.Yellow
|
variant === BannerVariant.Warning
|
||||||
? 'content-warning'
|
? 'content-warning'
|
||||||
: 'content-warning content-warning--filter'
|
: 'content-warning content-warning--filter'
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,11 @@ export const StatusBanner: React.FC<{
|
||||||
id='content_warning.hide'
|
id='content_warning.hide'
|
||||||
defaultMessage='Hide post'
|
defaultMessage='Hide post'
|
||||||
/>
|
/>
|
||||||
|
) : variant === BannerVariant.Warning ? (
|
||||||
|
<FormattedMessage
|
||||||
|
id='content_warning.show_more'
|
||||||
|
defaultMessage='Show more'
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='content_warning.show'
|
id='content_warning.show'
|
||||||
|
@ -33,5 +38,5 @@ export const StatusBanner: React.FC<{
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</label>
|
||||||
);
|
);
|
||||||
|
|
|
@ -21,9 +21,11 @@ class ColumnSettings extends PureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='column-settings'>
|
<div className='column-settings'>
|
||||||
|
<section>
|
||||||
<div className='column-settings__row'>
|
<div className='column-settings__row'>
|
||||||
<SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media only' />} />
|
<SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media only' />} />
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ const messages = defineMessages({
|
||||||
minutes: { id: 'intervals.full.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}}' },
|
minutes: { id: 'intervals.full.minutes', defaultMessage: '{number, plural, one {# minute} other {# minutes}}' },
|
||||||
hours: { id: 'intervals.full.hours', defaultMessage: '{number, plural, one {# hour} other {# hours}}' },
|
hours: { id: 'intervals.full.hours', defaultMessage: '{number, plural, one {# hour} other {# hours}}' },
|
||||||
days: { id: 'intervals.full.days', defaultMessage: '{number, plural, one {# day} other {# days}}' },
|
days: { id: 'intervals.full.days', defaultMessage: '{number, plural, one {# day} other {# days}}' },
|
||||||
singleChoice: { id: 'compose_form.poll.single', defaultMessage: 'Pick one' },
|
singleChoice: { id: 'compose_form.poll.single', defaultMessage: 'Single choice' },
|
||||||
multipleChoice: { id: 'compose_form.poll.multiple', defaultMessage: 'Multiple choice' },
|
multipleChoice: { id: 'compose_form.poll.multiple', defaultMessage: 'Multiple choice' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -129,8 +129,13 @@ export const InlineFollowSuggestions = ({ hidden }) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getComputedStyle(bodyRef.current).direction === 'rtl') {
|
||||||
|
setCanScrollLeft((bodyRef.current.clientWidth - bodyRef.current.scrollLeft) < bodyRef.current.scrollWidth);
|
||||||
|
setCanScrollRight(bodyRef.current.scrollLeft < 0);
|
||||||
|
} else {
|
||||||
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
|
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
|
||||||
setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth);
|
setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth);
|
||||||
|
}
|
||||||
}, [setCanScrollRight, setCanScrollLeft, bodyRef, suggestions]);
|
}, [setCanScrollRight, setCanScrollLeft, bodyRef, suggestions]);
|
||||||
|
|
||||||
const handleLeftNav = useCallback(() => {
|
const handleLeftNav = useCallback(() => {
|
||||||
|
@ -146,8 +151,13 @@ export const InlineFollowSuggestions = ({ hidden }) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getComputedStyle(bodyRef.current).direction === 'rtl') {
|
||||||
|
setCanScrollLeft((bodyRef.current.clientWidth - bodyRef.current.scrollLeft) < bodyRef.current.scrollWidth);
|
||||||
|
setCanScrollRight(bodyRef.current.scrollLeft < 0);
|
||||||
|
} else {
|
||||||
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
|
setCanScrollLeft(bodyRef.current.scrollLeft > 0);
|
||||||
setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth);
|
setCanScrollRight((bodyRef.current.scrollLeft + bodyRef.current.clientWidth) < bodyRef.current.scrollWidth);
|
||||||
|
}
|
||||||
}, [setCanScrollRight, setCanScrollLeft, bodyRef]);
|
}, [setCanScrollRight, setCanScrollLeft, bodyRef]);
|
||||||
|
|
||||||
const handleDismiss = useCallback(() => {
|
const handleDismiss = useCallback(() => {
|
||||||
|
|
|
@ -38,6 +38,7 @@ class ColumnSettings extends PureComponent {
|
||||||
const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />;
|
const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />;
|
||||||
const showStr = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />;
|
const showStr = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />;
|
||||||
const soundStr = <FormattedMessage id='notifications.column_settings.sound' defaultMessage='Play sound' />;
|
const soundStr = <FormattedMessage id='notifications.column_settings.sound' defaultMessage='Play sound' />;
|
||||||
|
const groupStr = <FormattedMessage id='notifications.column_settings.group' defaultMessage='Group' />;
|
||||||
|
|
||||||
const showPushSettings = pushSettings.get('browserSupport') && pushSettings.get('isSubscribed');
|
const showPushSettings = pushSettings.get('browserSupport') && pushSettings.get('isSubscribed');
|
||||||
const pushStr = showPushSettings && <FormattedMessage id='notifications.column_settings.push' defaultMessage='Push notifications' />;
|
const pushStr = showPushSettings && <FormattedMessage id='notifications.column_settings.push' defaultMessage='Push notifications' />;
|
||||||
|
@ -94,6 +95,7 @@ class ColumnSettings extends PureComponent {
|
||||||
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'follow']} onChange={this.onPushChange} label={pushStr} />}
|
{showPushSettings && <SettingToggle prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'follow']} onChange={this.onPushChange} label={pushStr} />}
|
||||||
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'follow']} onChange={onChange} label={showStr} />
|
<SettingToggle prefix='notifications' settings={settings} settingPath={['shows', 'follow']} onChange={onChange} label={showStr} />
|
||||||
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'follow']} onChange={onChange} label={soundStr} />
|
<SettingToggle prefix='notifications' settings={settings} settingPath={['sounds', 'follow']} onChange={onChange} label={soundStr} />
|
||||||
|
<SettingToggle prefix='notifications' settings={settings} settingPath={['group', 'follow']} onChange={onChange} label={groupStr} />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
|
@ -56,11 +56,12 @@ const mapDispatchToProps = (dispatch) => ({
|
||||||
} else {
|
} else {
|
||||||
dispatch(changeSetting(['notifications', ...path], checked));
|
dispatch(changeSetting(['notifications', ...path], checked));
|
||||||
}
|
}
|
||||||
} else if(path[0] === 'groupingBeta') {
|
|
||||||
dispatch(changeSetting(['notifications', ...path], checked));
|
|
||||||
dispatch(initializeNotifications());
|
|
||||||
} else {
|
} else {
|
||||||
dispatch(changeSetting(['notifications', ...path], checked));
|
dispatch(changeSetting(['notifications', ...path], checked));
|
||||||
|
|
||||||
|
if(path[0] === 'group' && path[1] === 'follow') {
|
||||||
|
dispatch(initializeNotifications());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,21 @@
|
||||||
|
import type { JSX } from 'react';
|
||||||
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import PersonAddIcon from '@/material-icons/400-24px/person_add-fill.svg?react';
|
import PersonAddIcon from '@/material-icons/400-24px/person_add-fill.svg?react';
|
||||||
import { FollowersCounter } from 'mastodon/components/counters';
|
import { FollowersCounter } from 'mastodon/components/counters';
|
||||||
import { FollowButton } from 'mastodon/components/follow_button';
|
import { FollowButton } from 'mastodon/components/follow_button';
|
||||||
import { ShortNumber } from 'mastodon/components/short_number';
|
import { ShortNumber } from 'mastodon/components/short_number';
|
||||||
|
import { me } from 'mastodon/initial_state';
|
||||||
import type { NotificationGroupFollow } from 'mastodon/models/notification_group';
|
import type { NotificationGroupFollow } from 'mastodon/models/notification_group';
|
||||||
import { useAppSelector } from 'mastodon/store';
|
import { useAppSelector } from 'mastodon/store';
|
||||||
|
|
||||||
import type { LabelRenderer } from './notification_group_with_status';
|
import type { LabelRenderer } from './notification_group_with_status';
|
||||||
import { NotificationGroupWithStatus } from './notification_group_with_status';
|
import { NotificationGroupWithStatus } from './notification_group_with_status';
|
||||||
|
|
||||||
const labelRenderer: LabelRenderer = (displayedName, total) => {
|
const labelRenderer: LabelRenderer = (displayedName, total, seeMoreHref) => {
|
||||||
if (total === 1)
|
if (total === 1)
|
||||||
return (
|
return (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
@ -23,10 +28,12 @@ const labelRenderer: LabelRenderer = (displayedName, total) => {
|
||||||
return (
|
return (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='notification.follow.name_and_others'
|
id='notification.follow.name_and_others'
|
||||||
defaultMessage='{name} and {count, plural, one {# other} other {# others}} followed you'
|
defaultMessage='{name} and <a>{count, plural, one {# other} other {# others}}</a> followed you'
|
||||||
values={{
|
values={{
|
||||||
name: displayedName,
|
name: displayedName,
|
||||||
count: total - 1,
|
count: total - 1,
|
||||||
|
a: (chunks) =>
|
||||||
|
seeMoreHref ? <Link to={seeMoreHref}>{chunks}</Link> : chunks,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -46,6 +53,10 @@ export const NotificationFollow: React.FC<{
|
||||||
notification: NotificationGroupFollow;
|
notification: NotificationGroupFollow;
|
||||||
unread: boolean;
|
unread: boolean;
|
||||||
}> = ({ notification, unread }) => {
|
}> = ({ notification, unread }) => {
|
||||||
|
const username = useAppSelector(
|
||||||
|
(state) => state.accounts.getIn([me, 'username']) as string,
|
||||||
|
);
|
||||||
|
|
||||||
let actions: JSX.Element | undefined;
|
let actions: JSX.Element | undefined;
|
||||||
let additionalContent: JSX.Element | undefined;
|
let additionalContent: JSX.Element | undefined;
|
||||||
|
|
||||||
|
@ -68,6 +79,7 @@ export const NotificationFollow: React.FC<{
|
||||||
timestamp={notification.latest_page_notification_at}
|
timestamp={notification.latest_page_notification_at}
|
||||||
count={notification.notifications_count}
|
count={notification.notifications_count}
|
||||||
labelRenderer={labelRenderer}
|
labelRenderer={labelRenderer}
|
||||||
|
labelSeeMoreHref={`/@${username}/followers`}
|
||||||
unread={unread}
|
unread={unread}
|
||||||
actions={actions}
|
actions={actions}
|
||||||
additionalContent={additionalContent}
|
additionalContent={additionalContent}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import type { JSX } from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
import type { IconProp } from 'mastodon/components/icon';
|
import type { IconProp } from 'mastodon/components/icon';
|
||||||
import { Icon } from 'mastodon/components/icon';
|
import { Icon } from 'mastodon/components/icon';
|
||||||
import Status from 'mastodon/containers/status_container';
|
import Status from 'mastodon/containers/status_container';
|
||||||
|
import { getStatusHidden } from 'mastodon/selectors/filters';
|
||||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||||
|
|
||||||
import { DisplayedName } from './displayed_name';
|
import { DisplayedName } from './displayed_name';
|
||||||
|
@ -48,6 +49,12 @@ export const NotificationWithStatus: React.FC<{
|
||||||
(state) => state.statuses.getIn([statusId, 'visibility']) === 'direct',
|
(state) => state.statuses.getIn([statusId, 'visibility']) === 'direct',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isFiltered = useAppSelector(
|
||||||
|
(state) =>
|
||||||
|
statusId &&
|
||||||
|
getStatusHidden(state, { id: statusId, contextType: 'notifications' }),
|
||||||
|
);
|
||||||
|
|
||||||
const handlers = useMemo(
|
const handlers = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
open: () => {
|
open: () => {
|
||||||
|
@ -73,7 +80,7 @@ export const NotificationWithStatus: React.FC<{
|
||||||
[dispatch, statusId],
|
[dispatch, statusId],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!statusId) return null;
|
if (!statusId || isFiltered) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HotKeys handlers={handlers}>
|
<HotKeys handlers={handlers}>
|
||||||
|
|
|
@ -14,6 +14,8 @@ import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
||||||
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
||||||
import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react';
|
import ReplyAllIcon from '@/material-icons/400-24px/reply_all.svg?react';
|
||||||
import StarIcon from '@/material-icons/400-24px/star.svg?react';
|
import StarIcon from '@/material-icons/400-24px/star.svg?react';
|
||||||
|
import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react';
|
||||||
|
import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react';
|
||||||
import { replyCompose } from 'mastodon/actions/compose';
|
import { replyCompose } from 'mastodon/actions/compose';
|
||||||
import { toggleReblog, toggleFavourite } from 'mastodon/actions/interactions';
|
import { toggleReblog, toggleFavourite } from 'mastodon/actions/interactions';
|
||||||
import { openModal } from 'mastodon/actions/modal';
|
import { openModal } from 'mastodon/actions/modal';
|
||||||
|
@ -159,22 +161,26 @@ class Footer extends ImmutablePureComponent {
|
||||||
replyTitle = intl.formatMessage(messages.replyAll);
|
replyTitle = intl.formatMessage(messages.replyAll);
|
||||||
}
|
}
|
||||||
|
|
||||||
let reblogTitle = '';
|
let reblogTitle, reblogIconComponent;
|
||||||
|
|
||||||
if (status.get('reblogged')) {
|
if (status.get('reblogged')) {
|
||||||
reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
|
reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
|
||||||
|
reblogIconComponent = publicStatus ? RepeatIcon : RepeatPrivateIcon;
|
||||||
} else if (publicStatus) {
|
} else if (publicStatus) {
|
||||||
reblogTitle = intl.formatMessage(messages.reblog);
|
reblogTitle = intl.formatMessage(messages.reblog);
|
||||||
|
reblogIconComponent = RepeatIcon;
|
||||||
} else if (reblogPrivate) {
|
} else if (reblogPrivate) {
|
||||||
reblogTitle = intl.formatMessage(messages.reblog_private);
|
reblogTitle = intl.formatMessage(messages.reblog_private);
|
||||||
|
reblogIconComponent = RepeatPrivateIcon;
|
||||||
} else {
|
} else {
|
||||||
reblogTitle = intl.formatMessage(messages.cannot_reblog);
|
reblogTitle = intl.formatMessage(messages.cannot_reblog);
|
||||||
|
reblogIconComponent = RepeatDisabledIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='picture-in-picture__footer'>
|
<div className='picture-in-picture__footer'>
|
||||||
<IconButton className='status__action-bar-button' title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} iconComponent={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? ReplyIcon : replyIconComponent} onClick={this.handleReplyClick} counter={status.get('replies_count')} />
|
<IconButton className='status__action-bar-button' title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} iconComponent={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? ReplyIcon : replyIconComponent} onClick={this.handleReplyClick} counter={status.get('replies_count')} />
|
||||||
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' iconComponent={RepeatIcon} onClick={this.handleReblogClick} counter={status.get('reblogs_count')} />
|
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' iconComponent={reblogIconComponent} onClick={this.handleReblogClick} counter={status.get('reblogs_count')} />
|
||||||
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' iconComponent={StarIcon} onClick={this.handleFavouriteClick} counter={status.get('favourites_count')} />
|
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' iconComponent={StarIcon} onClick={this.handleFavouriteClick} counter={status.get('favourites_count')} />
|
||||||
{withOpenButton && <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.open)} icon='external-link' iconComponent={OpenInNewIcon} onClick={this.handleOpenClick} href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} />}
|
{withOpenButton && <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.open)} icon='external-link' iconComponent={OpenInNewIcon} onClick={this.handleOpenClick} href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} />}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -116,6 +116,7 @@ export const MuteModal = ({ accountId, acct }) => {
|
||||||
<div className='safety-action-modal__bottom__collapsible'>
|
<div className='safety-action-modal__bottom__collapsible'>
|
||||||
<div className='safety-action-modal__field-group'>
|
<div className='safety-action-modal__field-group'>
|
||||||
<RadioButtonLabel name='duration' value='0' label={intl.formatMessage(messages.indefinite)} currentValue={muteDuration} onChange={handleChangeMuteDuration} />
|
<RadioButtonLabel name='duration' value='0' label={intl.formatMessage(messages.indefinite)} currentValue={muteDuration} onChange={handleChangeMuteDuration} />
|
||||||
|
<RadioButtonLabel name='duration' value='21600' label={intl.formatMessage(messages.hours, { number: 6 })} currentValue={muteDuration} onChange={handleChangeMuteDuration} />
|
||||||
<RadioButtonLabel name='duration' value='86400' label={intl.formatMessage(messages.hours, { number: 24 })} currentValue={muteDuration} onChange={handleChangeMuteDuration} />
|
<RadioButtonLabel name='duration' value='86400' label={intl.formatMessage(messages.hours, { number: 24 })} currentValue={muteDuration} onChange={handleChangeMuteDuration} />
|
||||||
<RadioButtonLabel name='duration' value='604800' label={intl.formatMessage(messages.days, { number: 7 })} currentValue={muteDuration} onChange={handleChangeMuteDuration} />
|
<RadioButtonLabel name='duration' value='604800' label={intl.formatMessage(messages.days, { number: 7 })} currentValue={muteDuration} onChange={handleChangeMuteDuration} />
|
||||||
<RadioButtonLabel name='duration' value='2592000' label={intl.formatMessage(messages.days, { number: 30 })} currentValue={muteDuration} onChange={handleChangeMuteDuration} />
|
<RadioButtonLabel name='duration' value='2592000' label={intl.formatMessage(messages.days, { number: 30 })} currentValue={muteDuration} onChange={handleChangeMuteDuration} />
|
||||||
|
|
|
@ -157,7 +157,6 @@
|
||||||
"compose_form.poll.duration": "مُدَّة اِستطلاع الرأي",
|
"compose_form.poll.duration": "مُدَّة اِستطلاع الرأي",
|
||||||
"compose_form.poll.multiple": "متعدد الخيارات",
|
"compose_form.poll.multiple": "متعدد الخيارات",
|
||||||
"compose_form.poll.option_placeholder": "الخيار {number}",
|
"compose_form.poll.option_placeholder": "الخيار {number}",
|
||||||
"compose_form.poll.single": "اختر واحدا",
|
|
||||||
"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.poll.type": "الطراز",
|
"compose_form.poll.type": "الطراز",
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
"account.followers": "Siguidores",
|
"account.followers": "Siguidores",
|
||||||
"account.followers.empty": "Naide sigue a esti perfil.",
|
"account.followers.empty": "Naide sigue a esti perfil.",
|
||||||
"account.follows.empty": "Esti perfil nun sigue a naide.",
|
"account.follows.empty": "Esti perfil nun sigue a naide.",
|
||||||
"account.hide_reblogs": "Anubrir los artículos compartíos de @{name}",
|
"account.hide_reblogs": "Esconder los artículos compartíos de @{name}",
|
||||||
"account.in_memoriam": "N'alcordanza.",
|
"account.in_memoriam": "N'alcordanza.",
|
||||||
"account.joined_short": "Data de xunión",
|
"account.joined_short": "Data de xunión",
|
||||||
"account.link_verified_on": "La propiedá d'esti enllaz comprobóse'l {date}",
|
"account.link_verified_on": "La propiedá d'esti enllaz comprobóse'l {date}",
|
||||||
|
@ -122,6 +122,7 @@
|
||||||
"conversation.open": "Ver la conversación",
|
"conversation.open": "Ver la conversación",
|
||||||
"conversation.with": "Con {names}",
|
"conversation.with": "Con {names}",
|
||||||
"copypaste.copied": "Copióse",
|
"copypaste.copied": "Copióse",
|
||||||
|
"copypaste.copy_to_clipboard": "Copiar nel cartafueyu",
|
||||||
"directory.federated": "Del fediversu conocíu",
|
"directory.federated": "Del fediversu conocíu",
|
||||||
"directory.local": "De «{domain}» namás",
|
"directory.local": "De «{domain}» namás",
|
||||||
"directory.new_arrivals": "Cuentes nueves",
|
"directory.new_arrivals": "Cuentes nueves",
|
||||||
|
@ -130,7 +131,7 @@
|
||||||
"dismissable_banner.dismiss": "Escartar",
|
"dismissable_banner.dismiss": "Escartar",
|
||||||
"dismissable_banner.explore_tags": "Esta seición contién les etiquetes del fediversu que tán ganando popularidá güei. Les etiquetes más usaes polos perfiles apaecen no cimero.",
|
"dismissable_banner.explore_tags": "Esta seición contién les etiquetes del fediversu que tán ganando popularidá güei. Les etiquetes más usaes polos perfiles apaecen no cimero.",
|
||||||
"dismissable_banner.public_timeline": "Esta seición contién los artículos más nuevos de les persones na web social que les persones de {domain} siguen.",
|
"dismissable_banner.public_timeline": "Esta seición contién los artículos más nuevos de les persones na web social que les persones de {domain} siguen.",
|
||||||
"embed.instructions": "Empotra esti artículu nel to sitiu web pente la copia del códigu d'abaxo.",
|
"embed.instructions": "Empotra esti artículu nel to sitiu web copiando'l códigu d'abaxo.",
|
||||||
"embed.preview": "Va apaecer asina:",
|
"embed.preview": "Va apaecer asina:",
|
||||||
"emoji_button.activity": "Actividá",
|
"emoji_button.activity": "Actividá",
|
||||||
"emoji_button.flags": "Banderes",
|
"emoji_button.flags": "Banderes",
|
||||||
|
@ -251,7 +252,7 @@
|
||||||
"keyboard_shortcuts.requests": "Abrir la llista de solicitúes de siguimientu",
|
"keyboard_shortcuts.requests": "Abrir la llista de solicitúes de siguimientu",
|
||||||
"keyboard_shortcuts.search": "Enfocar la barra de busca",
|
"keyboard_shortcuts.search": "Enfocar la barra de busca",
|
||||||
"keyboard_shortcuts.start": "Abrir la columna «Entamar»",
|
"keyboard_shortcuts.start": "Abrir la columna «Entamar»",
|
||||||
"keyboard_shortcuts.toggle_sensitivity": "Amosar/anubrir el conteníu multimedia",
|
"keyboard_shortcuts.toggle_sensitivity": "Amosar/esconder el conteníu multimedia",
|
||||||
"keyboard_shortcuts.toot": "Comenzar un artículu nuevu",
|
"keyboard_shortcuts.toot": "Comenzar un artículu nuevu",
|
||||||
"keyboard_shortcuts.unfocus": "Desenfocar l'área de composición/busca",
|
"keyboard_shortcuts.unfocus": "Desenfocar l'área de composición/busca",
|
||||||
"keyboard_shortcuts.up": "Xubir na llista",
|
"keyboard_shortcuts.up": "Xubir na llista",
|
||||||
|
@ -299,6 +300,7 @@
|
||||||
"notifications.column_settings.admin.sign_up": "Rexistros nuevos:",
|
"notifications.column_settings.admin.sign_up": "Rexistros nuevos:",
|
||||||
"notifications.column_settings.follow": "Siguidores nuevos:",
|
"notifications.column_settings.follow": "Siguidores nuevos:",
|
||||||
"notifications.column_settings.follow_request": "Solicitúes de siguimientu nueves:",
|
"notifications.column_settings.follow_request": "Solicitúes de siguimientu nueves:",
|
||||||
|
"notifications.column_settings.group": "Agrupar",
|
||||||
"notifications.column_settings.mention": "Menciones:",
|
"notifications.column_settings.mention": "Menciones:",
|
||||||
"notifications.column_settings.poll": "Resultaos de les encuestes:",
|
"notifications.column_settings.poll": "Resultaos de les encuestes:",
|
||||||
"notifications.column_settings.reblog": "Artículos compartíos:",
|
"notifications.column_settings.reblog": "Artículos compartíos:",
|
||||||
|
@ -418,11 +420,12 @@
|
||||||
"status.direct": "Mentar a @{name} per privao",
|
"status.direct": "Mentar a @{name} per privao",
|
||||||
"status.direct_indicator": "Mención privada",
|
"status.direct_indicator": "Mención privada",
|
||||||
"status.edited_x_times": "Editóse {count, plural, one {{count} vegada} other {{count} vegaes}}",
|
"status.edited_x_times": "Editóse {count, plural, one {{count} vegada} other {{count} vegaes}}",
|
||||||
|
"status.embed": "Consiguir el códigu pa empotrar",
|
||||||
"status.filter": "Peñerar esti artículu",
|
"status.filter": "Peñerar esti artículu",
|
||||||
"status.history.created": "{name} creó {date}",
|
"status.history.created": "{name} creó {date}",
|
||||||
"status.history.edited": "{name} editó {date}",
|
"status.history.edited": "{name} editó {date}",
|
||||||
"status.load_more": "Cargar más",
|
"status.load_more": "Cargar más",
|
||||||
"status.media_hidden": "Conteníu multimedia anubríu",
|
"status.media_hidden": "Conteníu multimedia escondíu",
|
||||||
"status.mention": "Mentar a @{name}",
|
"status.mention": "Mentar a @{name}",
|
||||||
"status.more": "Más",
|
"status.more": "Más",
|
||||||
"status.mute": "Desactivar los avisos de @{name}",
|
"status.mute": "Desactivar los avisos de @{name}",
|
||||||
|
@ -468,14 +471,14 @@
|
||||||
"upload_modal.applying": "Aplicando…",
|
"upload_modal.applying": "Aplicando…",
|
||||||
"upload_modal.detect_text": "Detectar el testu de la semeya",
|
"upload_modal.detect_text": "Detectar el testu de la semeya",
|
||||||
"upload_modal.edit_media": "Edición",
|
"upload_modal.edit_media": "Edición",
|
||||||
"upload_modal.hint": "Calca o arrastra'l círculu de la previsualización pa escoyer el puntu d'enfoque que siempres va tar a la vista en toles miniatures.",
|
"upload_modal.hint": "Calca o arrastra'l círculu de la previsualización pa escoyer el puntu d'enfoque que siempre va tar a la vista en toles miniatures.",
|
||||||
"upload_progress.label": "Xubiendo…",
|
"upload_progress.label": "Xubiendo…",
|
||||||
"upload_progress.processing": "Procesando…",
|
"upload_progress.processing": "Procesando…",
|
||||||
"video.close": "Zarrar el videu",
|
"video.close": "Zarrar el videu",
|
||||||
"video.download": "Baxar el ficheru",
|
"video.download": "Baxar el ficheru",
|
||||||
"video.expand": "Espander el videu",
|
"video.expand": "Espander el videu",
|
||||||
"video.fullscreen": "Pantalla completa",
|
"video.fullscreen": "Pantalla completa",
|
||||||
"video.hide": "Anubrir el videu",
|
"video.hide": "Esconder el videu",
|
||||||
"video.mute": "Desactivar el soníu",
|
"video.mute": "Desactivar el soníu",
|
||||||
"video.pause": "Posar",
|
"video.pause": "Posar",
|
||||||
"video.play": "Reproducir",
|
"video.play": "Reproducir",
|
||||||
|
|
|
@ -156,7 +156,6 @@
|
||||||
"compose_form.poll.duration": "Працягласць апытання",
|
"compose_form.poll.duration": "Працягласць апытання",
|
||||||
"compose_form.poll.multiple": "Множны выбар",
|
"compose_form.poll.multiple": "Множны выбар",
|
||||||
"compose_form.poll.option_placeholder": "Варыянт {number}",
|
"compose_form.poll.option_placeholder": "Варыянт {number}",
|
||||||
"compose_form.poll.single": "Адзін варыянт",
|
|
||||||
"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.poll.type": "Стыль",
|
"compose_form.poll.type": "Стыль",
|
||||||
|
|
|
@ -158,7 +158,6 @@
|
||||||
"compose_form.poll.duration": "Времетраене на анкетата",
|
"compose_form.poll.duration": "Времетраене на анкетата",
|
||||||
"compose_form.poll.multiple": "Множествен избор",
|
"compose_form.poll.multiple": "Множествен избор",
|
||||||
"compose_form.poll.option_placeholder": "Избор {number}",
|
"compose_form.poll.option_placeholder": "Избор {number}",
|
||||||
"compose_form.poll.single": "Подберете нещо",
|
|
||||||
"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.poll.type": "Стил",
|
"compose_form.poll.type": "Стил",
|
||||||
|
@ -490,7 +489,6 @@
|
||||||
"notification.favourite": "{name} направи любима публикацията ви",
|
"notification.favourite": "{name} направи любима публикацията ви",
|
||||||
"notification.favourite.name_and_others_with_link": "{name} и <a>{count, plural, one {# друг} other {# други}}</a> направиха любима ваша публикация",
|
"notification.favourite.name_and_others_with_link": "{name} и <a>{count, plural, one {# друг} other {# други}}</a> направиха любима ваша публикация",
|
||||||
"notification.follow": "{name} ви последва",
|
"notification.follow": "{name} ви последва",
|
||||||
"notification.follow.name_and_others": "{name} и {count, plural, one {# друг} other {# други}} ви последваха",
|
|
||||||
"notification.follow_request": "{name} поиска да ви последва",
|
"notification.follow_request": "{name} поиска да ви последва",
|
||||||
"notification.follow_request.name_and_others": "{name} и {count, plural, one {# друг} other {# други}} поискаха да ви последват",
|
"notification.follow_request.name_and_others": "{name} и {count, plural, one {# друг} other {# други}} поискаха да ви последват",
|
||||||
"notification.label.mention": "Споменаване",
|
"notification.label.mention": "Споменаване",
|
||||||
|
|
|
@ -145,7 +145,6 @@
|
||||||
"compose_form.poll.duration": "Pad ar sontadeg",
|
"compose_form.poll.duration": "Pad ar sontadeg",
|
||||||
"compose_form.poll.multiple": "Meur a choaz",
|
"compose_form.poll.multiple": "Meur a choaz",
|
||||||
"compose_form.poll.option_placeholder": "Choaz {number}",
|
"compose_form.poll.option_placeholder": "Choaz {number}",
|
||||||
"compose_form.poll.single": "Dibabit unan",
|
|
||||||
"compose_form.poll.switch_to_multiple": "Kemmañ ar sontadeg evit aotren meur a zibab",
|
"compose_form.poll.switch_to_multiple": "Kemmañ ar sontadeg evit aotren meur a zibab",
|
||||||
"compose_form.poll.switch_to_single": "Kemmañ ar sontadeg evit aotren un dibab hepken",
|
"compose_form.poll.switch_to_single": "Kemmañ ar sontadeg evit aotren un dibab hepken",
|
||||||
"compose_form.poll.type": "Neuz",
|
"compose_form.poll.type": "Neuz",
|
||||||
|
@ -385,6 +384,7 @@
|
||||||
"notification.admin.report": "Disklêriet eo bet {target} gant {name}",
|
"notification.admin.report": "Disklêriet eo bet {target} gant {name}",
|
||||||
"notification.admin.sign_up": "{name} en·he deus lakaet e·hec'h anv",
|
"notification.admin.sign_up": "{name} en·he deus lakaet e·hec'h anv",
|
||||||
"notification.follow": "heuliañ a ra {name} ac'hanoc'h",
|
"notification.follow": "heuliañ a ra {name} ac'hanoc'h",
|
||||||
|
"notification.follow.name_and_others": "{name} <a>{count, plural, one {hag # den all} two {ha # zen all} few {ha # den all} many {ha # den all} other {ha # den all}}</a> zo o heuliañ ac'hanoc'h",
|
||||||
"notification.follow_request": "Gant {name} eo bet goulennet ho heuliañ",
|
"notification.follow_request": "Gant {name} eo bet goulennet ho heuliañ",
|
||||||
"notification.moderation-warning.learn_more": "Gouzout hiroc'h",
|
"notification.moderation-warning.learn_more": "Gouzout hiroc'h",
|
||||||
"notification.own_poll": "Echu eo ho sontadeg",
|
"notification.own_poll": "Echu eo ho sontadeg",
|
||||||
|
@ -399,6 +399,7 @@
|
||||||
"notifications.column_settings.favourite": "Muiañ-karet:",
|
"notifications.column_settings.favourite": "Muiañ-karet:",
|
||||||
"notifications.column_settings.follow": "Heulierien nevez:",
|
"notifications.column_settings.follow": "Heulierien nevez:",
|
||||||
"notifications.column_settings.follow_request": "Pedadoù heuliañ nevez :",
|
"notifications.column_settings.follow_request": "Pedadoù heuliañ nevez :",
|
||||||
|
"notifications.column_settings.group": "Strollañ",
|
||||||
"notifications.column_settings.mention": "Menegoù:",
|
"notifications.column_settings.mention": "Menegoù:",
|
||||||
"notifications.column_settings.poll": "Disoc'hoù ar sontadeg:",
|
"notifications.column_settings.poll": "Disoc'hoù ar sontadeg:",
|
||||||
"notifications.column_settings.push": "Kemennoù push",
|
"notifications.column_settings.push": "Kemennoù push",
|
||||||
|
|
|
@ -158,7 +158,6 @@
|
||||||
"compose_form.poll.duration": "Durada de l'enquesta",
|
"compose_form.poll.duration": "Durada de l'enquesta",
|
||||||
"compose_form.poll.multiple": "Opcions múltiples",
|
"compose_form.poll.multiple": "Opcions múltiples",
|
||||||
"compose_form.poll.option_placeholder": "Opció {number}",
|
"compose_form.poll.option_placeholder": "Opció {number}",
|
||||||
"compose_form.poll.single": "Trieu-ne una",
|
|
||||||
"compose_form.poll.switch_to_multiple": "Canvia l’enquesta per a permetre múltiples opcions",
|
"compose_form.poll.switch_to_multiple": "Canvia l’enquesta per a permetre múltiples opcions",
|
||||||
"compose_form.poll.switch_to_single": "Canvia l’enquesta per a permetre una única opció",
|
"compose_form.poll.switch_to_single": "Canvia l’enquesta per a permetre una única opció",
|
||||||
"compose_form.poll.type": "Estil",
|
"compose_form.poll.type": "Estil",
|
||||||
|
@ -508,7 +507,6 @@
|
||||||
"notification.favourite": "{name} ha afavorit el teu tut",
|
"notification.favourite": "{name} ha afavorit el teu tut",
|
||||||
"notification.favourite.name_and_others_with_link": "{name} i <a>{count, plural, one {# altre} other {# altres}}</a> han afavorit la vostra publicació",
|
"notification.favourite.name_and_others_with_link": "{name} i <a>{count, plural, one {# altre} other {# altres}}</a> han afavorit la vostra publicació",
|
||||||
"notification.follow": "{name} et segueix",
|
"notification.follow": "{name} et segueix",
|
||||||
"notification.follow.name_and_others": "{name} i {count, plural, one {# altre} other {# altres}} us han seguit",
|
|
||||||
"notification.follow_request": "{name} ha sol·licitat de seguir-te",
|
"notification.follow_request": "{name} ha sol·licitat de seguir-te",
|
||||||
"notification.follow_request.name_and_others": "{name} i {count, plural, one {# altre} other {# altres}} han demanat de seguir-vos",
|
"notification.follow_request.name_and_others": "{name} i {count, plural, one {# altre} other {# altres}} han demanat de seguir-vos",
|
||||||
"notification.label.mention": "Menció",
|
"notification.label.mention": "Menció",
|
||||||
|
|
|
@ -143,7 +143,6 @@
|
||||||
"compose_form.poll.duration": "ماوەی ڕاپرسی",
|
"compose_form.poll.duration": "ماوەی ڕاپرسی",
|
||||||
"compose_form.poll.multiple": "فرە هەڵبژاردە",
|
"compose_form.poll.multiple": "فرە هەڵبژاردە",
|
||||||
"compose_form.poll.option_placeholder": "بژاردەی {number}",
|
"compose_form.poll.option_placeholder": "بژاردەی {number}",
|
||||||
"compose_form.poll.single": "یەکێك هەلبژێرە",
|
|
||||||
"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.poll.type": "ستایڵ",
|
"compose_form.poll.type": "ستایڵ",
|
||||||
|
|
|
@ -157,7 +157,6 @@
|
||||||
"compose_form.poll.duration": "Doba trvání ankety",
|
"compose_form.poll.duration": "Doba trvání ankety",
|
||||||
"compose_form.poll.multiple": "Výběr z více možností",
|
"compose_form.poll.multiple": "Výběr z více možností",
|
||||||
"compose_form.poll.option_placeholder": "Volba {number}",
|
"compose_form.poll.option_placeholder": "Volba {number}",
|
||||||
"compose_form.poll.single": "Vyber jednu",
|
|
||||||
"compose_form.poll.switch_to_multiple": "Povolit u ankety výběr více voleb",
|
"compose_form.poll.switch_to_multiple": "Povolit u ankety výběr více voleb",
|
||||||
"compose_form.poll.switch_to_single": "Povolit u ankety výběr pouze jedné volby",
|
"compose_form.poll.switch_to_single": "Povolit u ankety výběr pouze jedné volby",
|
||||||
"compose_form.poll.type": "Styl",
|
"compose_form.poll.type": "Styl",
|
||||||
|
|
|
@ -158,7 +158,7 @@
|
||||||
"compose_form.poll.duration": "Cyfnod pleidlais",
|
"compose_form.poll.duration": "Cyfnod pleidlais",
|
||||||
"compose_form.poll.multiple": "Dewis lluosog",
|
"compose_form.poll.multiple": "Dewis lluosog",
|
||||||
"compose_form.poll.option_placeholder": "Dewis {number}",
|
"compose_form.poll.option_placeholder": "Dewis {number}",
|
||||||
"compose_form.poll.single": "Ddewis un",
|
"compose_form.poll.single": "Dewis unigol",
|
||||||
"compose_form.poll.switch_to_multiple": "Newid pleidlais i adael mwy nag un dewis",
|
"compose_form.poll.switch_to_multiple": "Newid pleidlais i adael mwy nag un dewis",
|
||||||
"compose_form.poll.switch_to_single": "Newid pleidlais i gyfyngu i un dewis",
|
"compose_form.poll.switch_to_single": "Newid pleidlais i gyfyngu i un dewis",
|
||||||
"compose_form.poll.type": "Arddull",
|
"compose_form.poll.type": "Arddull",
|
||||||
|
@ -222,6 +222,7 @@
|
||||||
"domain_block_modal.they_cant_follow": "Ni all neb o'r gweinydd hwn eich dilyn.",
|
"domain_block_modal.they_cant_follow": "Ni all neb o'r gweinydd hwn eich dilyn.",
|
||||||
"domain_block_modal.they_wont_know": "Fyddan nhw ddim yn gwybod eu bod wedi cael eu blocio.",
|
"domain_block_modal.they_wont_know": "Fyddan nhw ddim yn gwybod eu bod wedi cael eu blocio.",
|
||||||
"domain_block_modal.title": "Blocio parth?",
|
"domain_block_modal.title": "Blocio parth?",
|
||||||
|
"domain_block_modal.you_will_lose_num_followers": "Byddwch yn colli {followersCount, plural, one {{followersCountDisplay} dilynwr} other {{followersCountDisplay} dilynwyr}} a {followingCount, plural, one {{followingCountDisplay} person rydych yn dilyn} other {{followingCountDisplay} o bobl rydych yn eu dilyn}}.",
|
||||||
"domain_block_modal.you_will_lose_relationships": "Byddwch yn colli'r holl ddilynwyr a phobl rydych chi'n eu dilyn o'r gweinydd hwn.",
|
"domain_block_modal.you_will_lose_relationships": "Byddwch yn colli'r holl ddilynwyr a phobl rydych chi'n eu dilyn o'r gweinydd hwn.",
|
||||||
"domain_block_modal.you_wont_see_posts": "Fyddwch chi ddim yn gweld postiadau na hysbysiadau gan ddefnyddwyr ar y gweinydd hwn.",
|
"domain_block_modal.you_wont_see_posts": "Fyddwch chi ddim yn gweld postiadau na hysbysiadau gan ddefnyddwyr ar y gweinydd hwn.",
|
||||||
"domain_pill.activitypub_lets_connect": "Mae'n caniatáu ichi gysylltu a rhyngweithio â phobl nid yn unig ar Mastodon, ond ar draws gwahanol apiau cymdeithasol hefyd.",
|
"domain_pill.activitypub_lets_connect": "Mae'n caniatáu ichi gysylltu a rhyngweithio â phobl nid yn unig ar Mastodon, ond ar draws gwahanol apiau cymdeithasol hefyd.",
|
||||||
|
@ -507,7 +508,7 @@
|
||||||
"notification.favourite": "Ffafriodd {name} eich postiad",
|
"notification.favourite": "Ffafriodd {name} eich postiad",
|
||||||
"notification.favourite.name_and_others_with_link": "Ffafriodd {name} a <a>{count, plural, one {# arall} other {# eraill}}</a> eich postiad",
|
"notification.favourite.name_and_others_with_link": "Ffafriodd {name} a <a>{count, plural, one {# arall} other {# eraill}}</a> eich postiad",
|
||||||
"notification.follow": "Dilynodd {name} chi",
|
"notification.follow": "Dilynodd {name} chi",
|
||||||
"notification.follow.name_and_others": "Mae {name} a {count, plural, one {# other} other {# others}} wedi'ch dilyn chi",
|
"notification.follow.name_and_others": "Mae {name} a <a>{count, plural, zero {}one {# other} two {# others} few {# others} many {# others} other {# others}}</a> nawr yn eich dilyn chi",
|
||||||
"notification.follow_request": "Mae {name} wedi gwneud cais i'ch dilyn",
|
"notification.follow_request": "Mae {name} wedi gwneud cais i'ch dilyn",
|
||||||
"notification.follow_request.name_and_others": "Mae {name} a{count, plural, one {# other} other {# others}} wedi gofyn i'ch dilyn chi",
|
"notification.follow_request.name_and_others": "Mae {name} a{count, plural, one {# other} other {# others}} wedi gofyn i'ch dilyn chi",
|
||||||
"notification.label.mention": "Crybwyll",
|
"notification.label.mention": "Crybwyll",
|
||||||
|
@ -515,6 +516,7 @@
|
||||||
"notification.label.private_reply": "Ateb preifat",
|
"notification.label.private_reply": "Ateb preifat",
|
||||||
"notification.label.reply": "Ateb",
|
"notification.label.reply": "Ateb",
|
||||||
"notification.mention": "Crybwyll",
|
"notification.mention": "Crybwyll",
|
||||||
|
"notification.mentioned_you": "Rydych wedi'ch crybwyll gan {name}",
|
||||||
"notification.moderation-warning.learn_more": "Dysgu mwy",
|
"notification.moderation-warning.learn_more": "Dysgu mwy",
|
||||||
"notification.moderation_warning": "Rydych wedi derbyn rhybudd gan gymedrolwr",
|
"notification.moderation_warning": "Rydych wedi derbyn rhybudd gan gymedrolwr",
|
||||||
"notification.moderation_warning.action_delete_statuses": "Mae rhai o'ch postiadau wedi'u dileu.",
|
"notification.moderation_warning.action_delete_statuses": "Mae rhai o'ch postiadau wedi'u dileu.",
|
||||||
|
@ -565,6 +567,7 @@
|
||||||
"notifications.column_settings.filter_bar.category": "Bar hidlo cyflym",
|
"notifications.column_settings.filter_bar.category": "Bar hidlo cyflym",
|
||||||
"notifications.column_settings.follow": "Dilynwyr newydd:",
|
"notifications.column_settings.follow": "Dilynwyr newydd:",
|
||||||
"notifications.column_settings.follow_request": "Ceisiadau dilyn newydd:",
|
"notifications.column_settings.follow_request": "Ceisiadau dilyn newydd:",
|
||||||
|
"notifications.column_settings.group": "Grŵp",
|
||||||
"notifications.column_settings.mention": "Crybwylliadau:",
|
"notifications.column_settings.mention": "Crybwylliadau:",
|
||||||
"notifications.column_settings.poll": "Canlyniadau pleidlais:",
|
"notifications.column_settings.poll": "Canlyniadau pleidlais:",
|
||||||
"notifications.column_settings.push": "Hysbysiadau gwthiadwy",
|
"notifications.column_settings.push": "Hysbysiadau gwthiadwy",
|
||||||
|
|
|
@ -158,7 +158,6 @@
|
||||||
"compose_form.poll.duration": "Afstemningens varighed",
|
"compose_form.poll.duration": "Afstemningens varighed",
|
||||||
"compose_form.poll.multiple": "Multivalg",
|
"compose_form.poll.multiple": "Multivalg",
|
||||||
"compose_form.poll.option_placeholder": "Valgmulighed {number}",
|
"compose_form.poll.option_placeholder": "Valgmulighed {number}",
|
||||||
"compose_form.poll.single": "Vælg én",
|
|
||||||
"compose_form.poll.switch_to_multiple": "Ændr afstemning til flervalgstype",
|
"compose_form.poll.switch_to_multiple": "Ændr afstemning til flervalgstype",
|
||||||
"compose_form.poll.switch_to_single": "Ændr afstemning til enkeltvalgstype",
|
"compose_form.poll.switch_to_single": "Ændr afstemning til enkeltvalgstype",
|
||||||
"compose_form.poll.type": "Stil",
|
"compose_form.poll.type": "Stil",
|
||||||
|
@ -508,7 +507,7 @@
|
||||||
"notification.favourite": "{name} favoritmarkerede dit indlæg",
|
"notification.favourite": "{name} favoritmarkerede dit indlæg",
|
||||||
"notification.favourite.name_and_others_with_link": "{name} og <a>{count, plural, one {# anden} other {# andre}}</a> gjorde dit indlæg til favorit",
|
"notification.favourite.name_and_others_with_link": "{name} og <a>{count, plural, one {# anden} other {# andre}}</a> gjorde dit indlæg til favorit",
|
||||||
"notification.follow": "{name} begyndte at følge dig",
|
"notification.follow": "{name} begyndte at følge dig",
|
||||||
"notification.follow.name_and_others": "{name} og {count, plural, one {# anden} other {# andre}} følger dig",
|
"notification.follow.name_and_others": "{name} og <a>{count, plural, one {# andre} other {# andre}}</a> begyndte at følge dig",
|
||||||
"notification.follow_request": "{name} har anmodet om at følge dig",
|
"notification.follow_request": "{name} har anmodet om at følge dig",
|
||||||
"notification.follow_request.name_and_others": "{name} og {count, plural, one {# anden} other {# andre}} har anmodet om at følger dig",
|
"notification.follow_request.name_and_others": "{name} og {count, plural, one {# anden} other {# andre}} har anmodet om at følger dig",
|
||||||
"notification.label.mention": "Omtale",
|
"notification.label.mention": "Omtale",
|
||||||
|
@ -567,6 +566,7 @@
|
||||||
"notifications.column_settings.filter_bar.category": "Hurtigfiltreringsbjælke",
|
"notifications.column_settings.filter_bar.category": "Hurtigfiltreringsbjælke",
|
||||||
"notifications.column_settings.follow": "Nye følgere:",
|
"notifications.column_settings.follow": "Nye følgere:",
|
||||||
"notifications.column_settings.follow_request": "Nye følgeanmodninger:",
|
"notifications.column_settings.follow_request": "Nye følgeanmodninger:",
|
||||||
|
"notifications.column_settings.group": "Gruppere",
|
||||||
"notifications.column_settings.mention": "Omtaler:",
|
"notifications.column_settings.mention": "Omtaler:",
|
||||||
"notifications.column_settings.poll": "Afstemningsresultater:",
|
"notifications.column_settings.poll": "Afstemningsresultater:",
|
||||||
"notifications.column_settings.push": "Push-notifikationer",
|
"notifications.column_settings.push": "Push-notifikationer",
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
"account.requested_follow": "{name} möchte dir folgen",
|
"account.requested_follow": "{name} möchte dir folgen",
|
||||||
"account.share": "Profil von @{name} teilen",
|
"account.share": "Profil von @{name} teilen",
|
||||||
"account.show_reblogs": "Geteilte Beiträge von @{name} anzeigen",
|
"account.show_reblogs": "Geteilte Beiträge von @{name} anzeigen",
|
||||||
"account.statuses_counter": "{count, plural, one {{counter} post} other {{counter} posts}}",
|
"account.statuses_counter": "{count, plural, one {{counter} Beitrag} other {{counter} Beiträge}}",
|
||||||
"account.unblock": "Blockierung von @{name} aufheben",
|
"account.unblock": "Blockierung von @{name} aufheben",
|
||||||
"account.unblock_domain": "Blockierung von {domain} aufheben",
|
"account.unblock_domain": "Blockierung von {domain} aufheben",
|
||||||
"account.unblock_short": "Blockierung aufheben",
|
"account.unblock_short": "Blockierung aufheben",
|
||||||
|
@ -508,7 +508,7 @@
|
||||||
"notification.favourite": "{name} favorisierte deinen Beitrag",
|
"notification.favourite": "{name} favorisierte deinen Beitrag",
|
||||||
"notification.favourite.name_and_others_with_link": "{name} und <a>{count, plural, one {# weitere Person} other {# weitere Personen}}</a> favorisierten deinen Beitrag",
|
"notification.favourite.name_and_others_with_link": "{name} und <a>{count, plural, one {# weitere Person} other {# weitere Personen}}</a> favorisierten deinen Beitrag",
|
||||||
"notification.follow": "{name} folgt dir",
|
"notification.follow": "{name} folgt dir",
|
||||||
"notification.follow.name_and_others": "{name} und {count, plural, one {# weitere Person} other {# weitere Personen}} folgen dir",
|
"notification.follow.name_and_others": "{name} und <a>{count, plural, one {# weitere Person} other {# weitere Personen}}</a> folgen dir",
|
||||||
"notification.follow_request": "{name} möchte dir folgen",
|
"notification.follow_request": "{name} möchte dir folgen",
|
||||||
"notification.follow_request.name_and_others": "{name} und {count, plural, one {# weitere Person} other {# weitere Personen}} möchten dir folgen",
|
"notification.follow_request.name_and_others": "{name} und {count, plural, one {# weitere Person} other {# weitere Personen}} möchten dir folgen",
|
||||||
"notification.label.mention": "Erwähnung",
|
"notification.label.mention": "Erwähnung",
|
||||||
|
@ -567,6 +567,7 @@
|
||||||
"notifications.column_settings.filter_bar.category": "Filterleiste",
|
"notifications.column_settings.filter_bar.category": "Filterleiste",
|
||||||
"notifications.column_settings.follow": "Neue Follower:",
|
"notifications.column_settings.follow": "Neue Follower:",
|
||||||
"notifications.column_settings.follow_request": "Neue Follower-Anfragen:",
|
"notifications.column_settings.follow_request": "Neue Follower-Anfragen:",
|
||||||
|
"notifications.column_settings.group": "Gruppieren",
|
||||||
"notifications.column_settings.mention": "Erwähnungen:",
|
"notifications.column_settings.mention": "Erwähnungen:",
|
||||||
"notifications.column_settings.poll": "Umfrageergebnisse:",
|
"notifications.column_settings.poll": "Umfrageergebnisse:",
|
||||||
"notifications.column_settings.push": "Push-Benachrichtigungen",
|
"notifications.column_settings.push": "Push-Benachrichtigungen",
|
||||||
|
|
|
@ -158,7 +158,6 @@
|
||||||
"compose_form.poll.duration": "Διάρκεια δημοσκόπησης",
|
"compose_form.poll.duration": "Διάρκεια δημοσκόπησης",
|
||||||
"compose_form.poll.multiple": "Πολλαπλή επιλογή",
|
"compose_form.poll.multiple": "Πολλαπλή επιλογή",
|
||||||
"compose_form.poll.option_placeholder": "Επιλογή {number}",
|
"compose_form.poll.option_placeholder": "Επιλογή {number}",
|
||||||
"compose_form.poll.single": "Διάλεξε ένα",
|
|
||||||
"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.poll.type": "Στυλ",
|
"compose_form.poll.type": "Στυλ",
|
||||||
|
@ -508,7 +507,6 @@
|
||||||
"notification.favourite": "{name} favorited your post\n{name} προτίμησε την ανάρτηση σου",
|
"notification.favourite": "{name} favorited your post\n{name} προτίμησε την ανάρτηση σου",
|
||||||
"notification.favourite.name_and_others_with_link": "{name} και <a>{count, plural, one {# ακόμη} other {# ακόμη}}</a> αγάπησαν την ανάρτησή σου",
|
"notification.favourite.name_and_others_with_link": "{name} και <a>{count, plural, one {# ακόμη} other {# ακόμη}}</a> αγάπησαν την ανάρτησή σου",
|
||||||
"notification.follow": "Ο/Η {name} σε ακολούθησε",
|
"notification.follow": "Ο/Η {name} σε ακολούθησε",
|
||||||
"notification.follow.name_and_others": "{name} και {count, plural, one {# ακόμη} other {# ακόμη}} σε ακολούθησαν",
|
|
||||||
"notification.follow_request": "Ο/H {name} ζήτησε να σε ακολουθήσει",
|
"notification.follow_request": "Ο/H {name} ζήτησε να σε ακολουθήσει",
|
||||||
"notification.follow_request.name_and_others": "{name} και {count, plural, one {# άλλος} other {# άλλοι}} ζήτησαν να σε ακολουθήσουν",
|
"notification.follow_request.name_and_others": "{name} και {count, plural, one {# άλλος} other {# άλλοι}} ζήτησαν να σε ακολουθήσουν",
|
||||||
"notification.label.mention": "Επισήμανση",
|
"notification.label.mention": "Επισήμανση",
|
||||||
|
|
|
@ -158,7 +158,6 @@
|
||||||
"compose_form.poll.duration": "Poll duration",
|
"compose_form.poll.duration": "Poll duration",
|
||||||
"compose_form.poll.multiple": "Multiple choice",
|
"compose_form.poll.multiple": "Multiple choice",
|
||||||
"compose_form.poll.option_placeholder": "Option {number}",
|
"compose_form.poll.option_placeholder": "Option {number}",
|
||||||
"compose_form.poll.single": "Pick one",
|
|
||||||
"compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
|
"compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
|
||||||
"compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
|
"compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
|
||||||
"compose_form.poll.type": "Style",
|
"compose_form.poll.type": "Style",
|
||||||
|
@ -508,7 +507,6 @@
|
||||||
"notification.favourite": "{name} favourited your post",
|
"notification.favourite": "{name} favourited your post",
|
||||||
"notification.favourite.name_and_others_with_link": "{name} and <a>{count, plural, one {# other} other {# others}}</a> favourited your post",
|
"notification.favourite.name_and_others_with_link": "{name} and <a>{count, plural, one {# other} other {# others}}</a> favourited your post",
|
||||||
"notification.follow": "{name} followed you",
|
"notification.follow": "{name} followed you",
|
||||||
"notification.follow.name_and_others": "{name} and {count, plural, one {# other} other {# others}} followed you",
|
|
||||||
"notification.follow_request": "{name} has requested to follow you",
|
"notification.follow_request": "{name} has requested to follow you",
|
||||||
"notification.follow_request.name_and_others": "{name} and {count, plural, one {# other} other {# others}} has requested to follow you",
|
"notification.follow_request.name_and_others": "{name} and {count, plural, one {# other} other {# others}} has requested to follow you",
|
||||||
"notification.label.mention": "Mention",
|
"notification.label.mention": "Mention",
|
||||||
|
@ -791,7 +789,7 @@
|
||||||
"status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
|
"status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
|
||||||
"status.embed": "Get embed code",
|
"status.embed": "Get embed code",
|
||||||
"status.favourite": "Favourite",
|
"status.favourite": "Favourite",
|
||||||
"status.favourites": "{count, plural, one {favorite} other {favorites}}",
|
"status.favourites": "{count, plural, one {favourite} other {favourites}}",
|
||||||
"status.filter": "Filter this post",
|
"status.filter": "Filter this post",
|
||||||
"status.history.created": "{name} created {date}",
|
"status.history.created": "{name} created {date}",
|
||||||
"status.history.edited": "{name} edited {date}",
|
"status.history.edited": "{name} edited {date}",
|
||||||
|
|
|
@ -158,7 +158,7 @@
|
||||||
"compose_form.poll.duration": "Poll duration",
|
"compose_form.poll.duration": "Poll duration",
|
||||||
"compose_form.poll.multiple": "Multiple choice",
|
"compose_form.poll.multiple": "Multiple choice",
|
||||||
"compose_form.poll.option_placeholder": "Option {number}",
|
"compose_form.poll.option_placeholder": "Option {number}",
|
||||||
"compose_form.poll.single": "Pick one",
|
"compose_form.poll.single": "Single choice",
|
||||||
"compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
|
"compose_form.poll.switch_to_multiple": "Change poll to allow multiple choices",
|
||||||
"compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
|
"compose_form.poll.switch_to_single": "Change poll to allow for a single choice",
|
||||||
"compose_form.poll.type": "Style",
|
"compose_form.poll.type": "Style",
|
||||||
|
@ -197,6 +197,7 @@
|
||||||
"confirmations.unfollow.title": "Unfollow user?",
|
"confirmations.unfollow.title": "Unfollow user?",
|
||||||
"content_warning.hide": "Hide post",
|
"content_warning.hide": "Hide post",
|
||||||
"content_warning.show": "Show anyway",
|
"content_warning.show": "Show anyway",
|
||||||
|
"content_warning.show_more": "Show more",
|
||||||
"conversation.delete": "Delete conversation",
|
"conversation.delete": "Delete conversation",
|
||||||
"conversation.mark_as_read": "Mark as read",
|
"conversation.mark_as_read": "Mark as read",
|
||||||
"conversation.open": "View conversation",
|
"conversation.open": "View conversation",
|
||||||
|
@ -305,7 +306,7 @@
|
||||||
"filter_modal.select_filter.subtitle": "Use an existing category or create a new one",
|
"filter_modal.select_filter.subtitle": "Use an existing category or create a new one",
|
||||||
"filter_modal.select_filter.title": "Filter this post",
|
"filter_modal.select_filter.title": "Filter this post",
|
||||||
"filter_modal.title.status": "Filter a post",
|
"filter_modal.title.status": "Filter a post",
|
||||||
"filter_warning.matches_filter": "Matches filter “{title}”",
|
"filter_warning.matches_filter": "Matches filter “<span>{title}</span>”",
|
||||||
"filtered_notifications_banner.pending_requests": "From {count, plural, =0 {no one} one {one person} other {# people}} you may know",
|
"filtered_notifications_banner.pending_requests": "From {count, plural, =0 {no one} one {one person} other {# people}} you may know",
|
||||||
"filtered_notifications_banner.title": "Filtered notifications",
|
"filtered_notifications_banner.title": "Filtered notifications",
|
||||||
"firehose.all": "All",
|
"firehose.all": "All",
|
||||||
|
@ -508,7 +509,7 @@
|
||||||
"notification.favourite": "{name} favorited your post",
|
"notification.favourite": "{name} favorited your post",
|
||||||
"notification.favourite.name_and_others_with_link": "{name} and <a>{count, plural, one {# other} other {# others}}</a> favorited your post",
|
"notification.favourite.name_and_others_with_link": "{name} and <a>{count, plural, one {# other} other {# others}}</a> favorited your post",
|
||||||
"notification.follow": "{name} followed you",
|
"notification.follow": "{name} followed you",
|
||||||
"notification.follow.name_and_others": "{name} and {count, plural, one {# other} other {# others}} followed you",
|
"notification.follow.name_and_others": "{name} and <a>{count, plural, one {# other} other {# others}}</a> followed you",
|
||||||
"notification.follow_request": "{name} has requested to follow you",
|
"notification.follow_request": "{name} has requested to follow you",
|
||||||
"notification.follow_request.name_and_others": "{name} and {count, plural, one {# other} other {# others}} has requested to follow you",
|
"notification.follow_request.name_and_others": "{name} and {count, plural, one {# other} other {# others}} has requested to follow you",
|
||||||
"notification.label.mention": "Mention",
|
"notification.label.mention": "Mention",
|
||||||
|
@ -567,6 +568,7 @@
|
||||||
"notifications.column_settings.filter_bar.category": "Quick filter bar",
|
"notifications.column_settings.filter_bar.category": "Quick filter bar",
|
||||||
"notifications.column_settings.follow": "New followers:",
|
"notifications.column_settings.follow": "New followers:",
|
||||||
"notifications.column_settings.follow_request": "New follow requests:",
|
"notifications.column_settings.follow_request": "New follow requests:",
|
||||||
|
"notifications.column_settings.group": "Group",
|
||||||
"notifications.column_settings.mention": "Mentions:",
|
"notifications.column_settings.mention": "Mentions:",
|
||||||
"notifications.column_settings.poll": "Poll results:",
|
"notifications.column_settings.poll": "Poll results:",
|
||||||
"notifications.column_settings.push": "Push notifications",
|
"notifications.column_settings.push": "Push notifications",
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue