diff --git a/.bundler-audit.yml b/.bundler-audit.yml deleted file mode 100644 index 0671df390f..0000000000 --- a/.bundler-audit.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -ignore: - # devise-two-factor advisory about brute-forcing TOTP - # We have rate-limits on authentication endpoints in place (including second - # factor verification) since Mastodon v3.2.0 - - CVE-2024-0227 diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index b5e72a0973..c6dcc4d46a 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,20 +1,15 @@ # For details, see https://github.com/devcontainers/images/tree/main/src/ruby -FROM mcr.microsoft.com/devcontainers/ruby:1-3.2-bullseye +FROM mcr.microsoft.com/devcontainers/ruby:1-3.3-bookworm -# Install Rails -# RUN gem install rails webdrivers +# Install node version from .nvmrc +WORKDIR /app +COPY .nvmrc . +RUN /bin/bash --login -i -c "nvm install" -ARG NODE_VERSION="20" -RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1" +# Install additional OS packages +RUN apt-get update && \ + export DEBIAN_FRONTEND=noninteractive && \ + apt-get -y install --no-install-recommends libicu-dev libidn11-dev ffmpeg imagemagick libvips42 libpam-dev -# [Optional] Uncomment this section to install additional OS packages. -RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ - && apt-get -y install --no-install-recommends libicu-dev libidn11-dev ffmpeg imagemagick libpam-dev - -# [Optional] Uncomment this line to install additional gems. -RUN gem install foreman - -# [Optional] Uncomment this line to install global node packages. -RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && corepack enable" 2>&1 - -COPY welcome-message.txt /usr/local/etc/vscode-dev-containers/first-run-notice.txt +# Move welcome message to where VS Code expects it +COPY .devcontainer/welcome-message.txt /usr/local/etc/vscode-dev-containers/first-run-notice.txt diff --git a/.devcontainer/codespaces/devcontainer.json b/.devcontainer/codespaces/devcontainer.json index ca9156fdaa..d2358657f6 100644 --- a/.devcontainer/codespaces/devcontainer.json +++ b/.devcontainer/codespaces/devcontainer.json @@ -1,6 +1,6 @@ { "name": "Mastodon on GitHub Codespaces", - "dockerComposeFile": "../docker-compose.yml", + "dockerComposeFile": "../compose.yaml", "service": "app", "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", @@ -23,6 +23,8 @@ } }, + "remoteUser": "root", + "otherPortsAttributes": { "onAutoForward": "silent" }, @@ -37,7 +39,7 @@ }, "onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}", - "postCreateCommand": ".devcontainer/post-create.sh", + "postCreateCommand": "bin/setup", "waitFor": "postCreateCommand", "customizations": { diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/compose.yaml similarity index 90% rename from .devcontainer/docker-compose.yml rename to .devcontainer/compose.yaml index d14af5d7d9..1e2e1ba7de 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/compose.yaml @@ -1,12 +1,11 @@ -version: '3' - services: app: + working_dir: /workspaces/mastodon/ build: - context: . - dockerfile: Dockerfile + context: .. + dockerfile: .devcontainer/Dockerfile volumes: - - ../..:/workspaces:cached + - ..:/workspaces/mastodon:cached environment: RAILS_ENV: development NODE_ENV: development @@ -70,7 +69,7 @@ services: hard: -1 libretranslate: - image: libretranslate/libretranslate:v1.5.6 + image: libretranslate/libretranslate:v1.5.7 restart: unless-stopped volumes: - lt-data:/home/libretranslate/.local diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index fa8d6542c1..fb88f7801f 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ { "name": "Mastodon on local machine", - "dockerComposeFile": "docker-compose.yml", + "dockerComposeFile": "compose.yaml", "service": "app", "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", @@ -23,12 +23,14 @@ } }, + "remoteUser": "root", + "otherPortsAttributes": { "onAutoForward": "silent" }, "onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}", - "postCreateCommand": ".devcontainer/post-create.sh", + "postCreateCommand": "bin/setup", "waitFor": "postCreateCommand", "customizations": { diff --git a/.devcontainer/post-create.sh b/.devcontainer/post-create.sh deleted file mode 100755 index 82a2ccbb6c..0000000000 --- a/.devcontainer/post-create.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash - -set -e # Fail the whole script on first error - -# Fetch Ruby gem dependencies -bundle config path 'vendor/bundle' -bundle config with 'development test' -bundle install - -# Make Gemfile.lock pristine again -git checkout -- Gemfile.lock - -# Fetch Javascript dependencies -corepack prepare -yarn install --immutable - -# [re]create, migrate, and seed the test database -RAILS_ENV=test ./bin/rails db:setup - -# [re]create, migrate, and seed the development database -RAILS_ENV=development ./bin/rails db:setup - -# Precompile assets for development -RAILS_ENV=development ./bin/rails assets:precompile - -# Precompile assets for test -RAILS_ENV=test ./bin/rails assets:precompile diff --git a/.devcontainer/welcome-message.txt b/.devcontainer/welcome-message.txt index 488cf92857..dbc19c910c 100644 --- a/.devcontainer/welcome-message.txt +++ b/.devcontainer/welcome-message.txt @@ -1,8 +1,7 @@ -๐Ÿ‘‹ Welcome to "Mastodon" in GitHub Codespaces! +๐Ÿ‘‹ Welcome to your Mastodon Dev Container! -๐Ÿ› ๏ธ Your environment is fully setup with all the required software. +๐Ÿ› ๏ธ Your environment is fully setup with all the required software. -๐Ÿ” To explore VS Code to its fullest, search using the Command Palette (Cmd/Ctrl + Shift + P or F1). - -๐Ÿ“ Edit away, run your app as usual, and we'll automatically make it available for you to access. +๐Ÿ’ฅ Run `bin/dev` to start the application processes. +๐Ÿฅผ Run `RAILS_ENV=test bin/rails assets:precompile && RAILS_ENV=test bin/rspec` to run the test suite. diff --git a/.env.production.sample b/.env.production.sample index 8a446d9cdc..fa75a70c57 100644 --- a/.env.production.sample +++ b/.env.production.sample @@ -1,5 +1,5 @@ # This is a sample configuration file. You can generate your configuration -# with the `rake mastodon:setup` interactive setup wizard, but to customize +# with the `bundle exec rails mastodon:setup` interactive setup wizard, but to customize # your setup even further, you'll need to edit it manually. This sample does # not demonstrate all available configuration options. Please look at # https://docs.joinmastodon.org/admin/config/ for the full documentation. @@ -68,7 +68,7 @@ DB_PORT=5432 # Secrets # ------- -# Generate each with the `RAILS_ENV=production bundle exec rake secret` task (`docker-compose run --rm web bundle exec rake secret` if you use docker compose) +# Generate each with the `RAILS_ENV=production bundle exec rails secret` task (`docker-compose run --rm web bundle exec rails secret` if you use docker compose) # ------- SECRET_KEY_BASE= OTP_SECRET= @@ -76,7 +76,7 @@ OTP_SECRET= # Web Push # -------- -# Generate with `rake mastodon:webpush:generate_vapid_key` (first is the private key, second is the public one) +# Generate with `bundle exec rails mastodon:webpush:generate_vapid_key` (first is the private key, second is the public one) # You should only generate this once per instance. If you later decide to change it, all push subscription will # be invalidated, requiring the users to access the website again to resubscribe. # -------- diff --git a/.env.test b/.env.test index 9e6abea5c9..d2763e582a 100644 --- a/.env.test +++ b/.env.test @@ -4,7 +4,8 @@ NODE_ENV=production LOCAL_DOMAIN=cb6e6126.ngrok.io LOCAL_HTTPS=true -# Required by ActiveRecord encryption feature -ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=fkSxKD2bF396kdQbrP1EJ7WbU7ZgNokR -ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=r0hvVmzBVsjxC7AMlwhOzmtc36ZCOS1E -ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=PhdFyyfy5xJ7WVd2lWBpcPScRQHzRTNr +# Secret values required by ActiveRecord encryption feature +# Use `bin/rails db:encryption:init` to generate fresh secrets +ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=test_determinist_key_DO_NOT_USE_IN_PRODUCTION +ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=test_salt_DO_NOT_USE_IN_PRODUCTION +ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=test_primary_key_DO_NOT_USE_IN_PRODUCTION diff --git a/.eslintrc.js b/.eslintrc.js index 8fe3a98b4a..66a0f12ba1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -20,10 +20,6 @@ module.exports = defineConfig({ es6: true, }, - globals: { - ATTACHMENT_HOST: false, - }, - parser: '@typescript-eslint/parser', plugins: [ @@ -79,7 +75,7 @@ module.exports = defineConfig({ ], }, ], - 'no-empty': 'off', + 'no-empty': ['error', { "allowEmptyCatch": true }], 'no-restricted-properties': [ 'error', { property: 'substring', message: 'Use .slice instead of .substring.' }, @@ -94,7 +90,6 @@ module.exports = defineConfig({ message: "Use 'ยท' (middle dot) instead of 'โ€ข' (bullet)", }, ], - 'no-self-assign': 'off', 'no-unused-expressions': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': [ @@ -119,12 +114,10 @@ module.exports = defineConfig({ 'react/jsx-tag-spacing': 'error', 'react/jsx-uses-react': 'off', // not needed with new JSX transform 'react/jsx-wrap-multilines': 'error', - 'react/no-deprecated': 'off', 'react/react-in-jsx-scope': 'off', // not needed with new JSX transform 'react/self-closing-comp': 'error', // recommended values found in https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/v6.8.0/src/index.js#L46 - 'jsx-a11y/accessible-emoji': 'warn', 'jsx-a11y/click-events-have-key-events': 'off', 'jsx-a11y/label-has-associated-control': 'off', 'jsx-a11y/media-has-caption': 'off', @@ -139,23 +132,6 @@ module.exports = defineConfig({ // ], 'jsx-a11y/no-interactive-element-to-noninteractive-role': 'off', // recommended rule is: - // 'jsx-a11y/no-noninteractive-element-interactions': [ - // 'error', - // { - // body: ['onError', 'onLoad'], - // iframe: ['onError', 'onLoad'], - // img: ['onError', 'onLoad'], - // }, - // ], - 'jsx-a11y/no-noninteractive-element-interactions': [ - 'warn', - { - handlers: [ - 'onClick', - ], - }, - ], - // recommended rule is: // 'jsx-a11y/no-noninteractive-tabindex': [ // 'error', // { @@ -165,7 +141,6 @@ module.exports = defineConfig({ // }, // ], 'jsx-a11y/no-noninteractive-tabindex': 'off', - 'jsx-a11y/no-onchange': 'off', // recommended is full 'error' 'jsx-a11y/no-static-element-interactions': [ 'warn', @@ -366,6 +341,9 @@ module.exports = defineConfig({ // Disable formatting rules that have been enabled in the base config 'indent': 'off', + // This is not needed as we use noImplicitReturns, which handles this in addition to understanding types + 'consistent-return': 'off', + 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], '@typescript-eslint/consistent-type-definitions': ['warn', 'interface'], diff --git a/.github/actions/setup-ruby/action.yml b/.github/actions/setup-ruby/action.yml index 3a6fba9402..3e232f134c 100644 --- a/.github/actions/setup-ruby/action.yml +++ b/.github/actions/setup-ruby/action.yml @@ -14,7 +14,7 @@ runs: shell: bash run: | sudo apt-get update - sudo apt-get install -y libicu-dev libidn11-dev ${{ inputs.additional-system-dependencies }} + sudo apt-get install -y libicu-dev libidn11-dev libvips42 ${{ inputs.additional-system-dependencies }} - name: Set up Ruby uses: ruby/setup-ruby@v1 diff --git a/.github/codecov.yml b/.github/codecov.yml index 9d6413a106..701ba3af8f 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -3,9 +3,9 @@ coverage: status: project: default: - # Github status check is not blocking + # GitHub status check is not blocking informational: true patch: default: - # Github status check is not blocking + # GitHub status check is not blocking informational: true diff --git a/.github/renovate.json5 b/.github/renovate.json5 index e92608a437..2cf7bec8ee 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -2,6 +2,7 @@ $schema: 'https://docs.renovatebot.com/renovate-schema.json', extends: [ 'config:recommended', + 'customManagers:dockerfileVersions', ':labels(dependencies)', ':prConcurrentLimitNone', // Remove limit for open PRs at any time. ':prHourlyLimit2', // Rate limit PR creation to a maximum of two per hour. @@ -59,7 +60,7 @@ dependencyDashboardApproval: true, }, { - // Update Github Actions and Docker images weekly + // Update GitHub Actions and Docker images weekly matchManagers: ['github-actions', 'dockerfile', 'docker-compose'], extends: ['schedule:weekly'], }, @@ -141,6 +142,13 @@ matchUpdateTypes: ['patch', 'minor'], groupName: 'RSpec (non-major)', }, + { + // Group all opentelemetry-ruby packages in the same PR + matchManagers: ['bundler'], + matchPackagePrefixes: ['opentelemetry-'], + matchUpdateTypes: ['patch', 'minor'], + groupName: 'opentelemetry-ruby (non-major)', + }, // Add labels depending on package manager { matchManagers: ['npm', 'nvm'], addLabels: ['javascript'] }, { matchManagers: ['bundler', 'ruby-version'], addLabels: ['ruby'] }, diff --git a/.github/stylelint-matcher.json b/.github/stylelint-matcher.json deleted file mode 100644 index cdfd4086bd..0000000000 --- a/.github/stylelint-matcher.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "problemMatcher": [ - { - "owner": "stylelint", - "pattern": [ - { - "regexp": "^([^\\s].*)$", - "file": 1 - }, - { - "regexp": "^\\s+((\\d+):(\\d+))?\\s+(โœ–|ร—)\\s+(.*)\\s{2,}(.*)$", - "line": 2, - "column": 3, - "message": 5, - "code": 6, - "loop": true - } - ] - } - ] -} diff --git a/.github/workflows/build-container-image.yml b/.github/workflows/build-container-image.yml index e100e15821..dbb32af9bf 100644 --- a/.github/workflows/build-container-image.yml +++ b/.github/workflows/build-container-image.yml @@ -68,7 +68,7 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Log in to the Github Container registry + - name: Log in to the GitHub Container registry if: contains(inputs.push_to_images, 'ghcr.io') uses: docker/login-action@v3 with: diff --git a/.github/workflows/bundler-audit.yml b/.github/workflows/bundler-audit.yml index bbc31598c7..2341d6e67f 100644 --- a/.github/workflows/bundler-audit.yml +++ b/.github/workflows/bundler-audit.yml @@ -1,19 +1,19 @@ name: Bundler Audit on: + merge_group: push: - branches-ignore: - - 'dependabot/**' + branches: + - 'main' + - 'stable-*' paths: - 'Gemfile*' - '.ruby-version' - - '.bundler-audit.yml' - '.github/workflows/bundler-audit.yml' pull_request: paths: - 'Gemfile*' - '.ruby-version' - - '.bundler-audit.yml' - '.github/workflows/bundler-audit.yml' schedule: @@ -23,12 +23,17 @@ jobs: security: runs-on: ubuntu-latest + env: + BUNDLE_ONLY: development + steps: - name: Clone repository uses: actions/checkout@v4 - - name: Set up Ruby environment - uses: ./.github/actions/setup-ruby + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true - name: Run bundler-audit - run: bundle exec bundler-audit + run: bundle exec bundler-audit check --update diff --git a/.github/workflows/check-i18n.yml b/.github/workflows/check-i18n.yml index ceb385933b..5a1c051966 100644 --- a/.github/workflows/check-i18n.yml +++ b/.github/workflows/check-i18n.yml @@ -2,9 +2,13 @@ name: Check i18n on: push: - branches: [main] + branches: + - 'main' + - 'stable-*' pull_request: - branches: [main] + branches: + - 'main' + - 'stable-*' env: RAILS_ENV: test diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 6fb93b7fef..8690e9ed6d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,11 +1,15 @@ name: 'CodeQL' on: + merge_group: push: - branches: ['main'] + branches: + - 'main' + - 'stable-*' pull_request: - # The branches below must be a subset of the branches above - branches: ['main'] + branches: + - 'main' + - 'stable-*' schedule: - cron: '22 6 * * 1' diff --git a/.github/workflows/crowdin-download.yml b/.github/workflows/crowdin-download.yml index 41050b6f75..1212e66296 100644 --- a/.github/workflows/crowdin-download.yml +++ b/.github/workflows/crowdin-download.yml @@ -53,19 +53,19 @@ jobs: # Create or update the pull request - name: Create Pull Request - uses: peter-evans/create-pull-request@v6.0.4 + uses: peter-evans/create-pull-request@v6.0.5 with: commit-message: 'New Crowdin translations' title: 'New Crowdin Translations (automated)' author: 'GitHub Actions ' body: | - New Crowdin translations, automated with Github Actions + New Crowdin translations, automated with GitHub Actions See `.github/workflows/crowdin-download.yml` This PR will be updated every day with new translations. - Due to a limitation in Github Actions, checks are not running on this PR without manual action. + Due to a limitation in GitHub Actions, checks are not running on this PR without manual action. If you want to run the checks, then close and re-open it. branch: i18n/crowdin/translations base: main diff --git a/.github/workflows/crowdin-upload.yml b/.github/workflows/crowdin-upload.yml index 75d66c2a6b..6717853304 100644 --- a/.github/workflows/crowdin-upload.yml +++ b/.github/workflows/crowdin-upload.yml @@ -1,17 +1,19 @@ name: Crowdin / Upload translations on: + merge_group: push: branches: - - main + - 'main' + - 'stable-*' paths: - - crowdin.yml - - app/javascript/mastodon/locales/en.json - - config/locales/en.yml - - config/locales/simple_form.en.yml - - config/locales/activerecord.en.yml - - config/locales/devise.en.yml - - config/locales/doorkeeper.en.yml + - crowdin-glitch.yml + - app/javascript/flavours/glitch/locales/en.json + - config/locales-glitch/en.yml + - config/locales-glitch/simple_form.en.yml + - config/locales-glitch/activerecord.en.yml + - config/locales-glitch/devise.en.yml + - config/locales-glitch/doorkeeper.en.yml - .github/workflows/crowdin-upload.yml jobs: diff --git a/.github/workflows/format-check.yml b/.github/workflows/format-check.yml index 2d483b5022..c10f350a02 100644 --- a/.github/workflows/format-check.yml +++ b/.github/workflows/format-check.yml @@ -1,6 +1,10 @@ name: Check formatting on: + merge_group: push: + branches: + - 'main' + - 'stable-*' pull_request: jobs: diff --git a/.github/workflows/lint-css.yml b/.github/workflows/lint-css.yml index e5f4874877..95fcd56942 100644 --- a/.github/workflows/lint-css.yml +++ b/.github/workflows/lint-css.yml @@ -1,9 +1,10 @@ name: CSS Linting on: + merge_group: push: - branches-ignore: - - 'dependabot/**' - - 'renovate/**' + branches: + - 'main' + - 'stable-*' paths: - 'package.json' - 'yarn.lock' @@ -38,9 +39,5 @@ jobs: - name: Set up Javascript environment uses: ./.github/actions/setup-javascript - - uses: xt0rted/stylelint-problem-matcher@v1 - - - run: echo "::add-matcher::.github/stylelint-matcher.json" - - name: Stylelint - run: yarn lint:css + run: yarn lint:css -f github diff --git a/.github/workflows/lint-haml.yml b/.github/workflows/lint-haml.yml index 25615b720d..a1a9e99c90 100644 --- a/.github/workflows/lint-haml.yml +++ b/.github/workflows/lint-haml.yml @@ -1,9 +1,10 @@ name: Haml Linting on: + merge_group: push: - branches-ignore: - - 'dependabot/**' - - 'renovate/**' + branches: + - 'main' + - 'stable-*' paths: - '.github/workflows/haml-lint-problem-matcher.json' - '.github/workflows/lint-haml.yml' @@ -26,12 +27,18 @@ on: jobs: lint: runs-on: ubuntu-latest + + env: + BUNDLE_ONLY: development + steps: - name: Clone repository uses: actions/checkout@v4 - - name: Set up Ruby environment - uses: ./.github/actions/setup-ruby + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true - name: Run haml-lint run: | diff --git a/.github/workflows/lint-js.yml b/.github/workflows/lint-js.yml index 1c1ecc2b22..7d31a5e20e 100644 --- a/.github/workflows/lint-js.yml +++ b/.github/workflows/lint-js.yml @@ -1,9 +1,10 @@ name: JavaScript Linting on: + merge_group: push: - branches-ignore: - - 'dependabot/**' - - 'renovate/**' + branches: + - 'main' + - 'stable-*' paths: - 'package.json' - 'yarn.lock' diff --git a/.github/workflows/lint-ruby.yml b/.github/workflows/lint-ruby.yml index 411b323486..277e456146 100644 --- a/.github/workflows/lint-ruby.yml +++ b/.github/workflows/lint-ruby.yml @@ -1,9 +1,10 @@ name: Ruby Linting on: + merge_group: push: - branches-ignore: - - 'dependabot/**' - - 'renovate/**' + branches: + - 'main' + - 'stable-*' paths: - 'Gemfile*' - '.rubocop*.yml' @@ -27,19 +28,24 @@ jobs: lint: runs-on: ubuntu-latest + env: + BUNDLE_ONLY: development + steps: - name: Clone repository uses: actions/checkout@v4 - - name: Set up Ruby environment - uses: ./.github/actions/setup-ruby + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true - name: Set-up RuboCop Problem Matcher uses: r7kamura/rubocop-problem-matchers-action@v1 - name: Run rubocop - run: bundle exec rubocop + run: bin/rubocop - name: Run brakeman if: always() # Run both checks, even if the first failed - run: bundle exec brakeman + run: bin/brakeman diff --git a/.github/workflows/rebase-needed.yml b/.github/workflows/rebase-needed.yml index 06d835c090..8784397a8f 100644 --- a/.github/workflows/rebase-needed.yml +++ b/.github/workflows/rebase-needed.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Check for merge conflicts - uses: eps1lon/actions-label-merge-conflict@releases/2.x + uses: eps1lon/actions-label-merge-conflict@v3 with: dirtyLabel: 'rebase needed :construction:' repoToken: '${{ secrets.GITHUB_TOKEN }}' diff --git a/.github/workflows/test-js.yml b/.github/workflows/test-js.yml index 481afdba30..e9e43ac9e8 100644 --- a/.github/workflows/test-js.yml +++ b/.github/workflows/test-js.yml @@ -1,9 +1,10 @@ name: JavaScript Testing on: + merge_group: push: - branches-ignore: - - 'dependabot/**' - - 'renovate/**' + branches: + - 'main' + - 'stable-*' paths: - 'package.json' - 'yarn.lock' diff --git a/.github/workflows/test-migrations-one-step.yml b/.github/workflows/test-migrations-one-step.yml deleted file mode 100644 index 1ff5cc06b9..0000000000 --- a/.github/workflows/test-migrations-one-step.yml +++ /dev/null @@ -1,88 +0,0 @@ -name: Test one step migrations -on: - push: - branches-ignore: - - 'dependabot/**' - - 'renovate/**' - pull_request: - -jobs: - pre_job: - runs-on: ubuntu-latest - - outputs: - should_skip: ${{ steps.skip_check.outputs.should_skip }} - - steps: - - id: skip_check - uses: fkirc/skip-duplicate-actions@v5 - with: - paths: '["Gemfile*", ".ruby-version", "**/*.rb", ".github/workflows/test-migrations-one-step.yml", "lib/tasks/tests.rake"]' - - test: - runs-on: ubuntu-latest - needs: pre_job - if: needs.pre_job.outputs.should_skip != 'true' - - strategy: - fail-fast: false - - matrix: - postgres: - - 14-alpine - - 15-alpine - - services: - postgres: - image: postgres:${{ matrix.postgres}} - env: - POSTGRES_PASSWORD: postgres - POSTGRES_USER: postgres - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 5432:5432 - - redis: - image: redis:7-alpine - options: >- - --health-cmd "redis-cli ping" - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 6379:6379 - - env: - CONTINUOUS_INTEGRATION: true - DB_HOST: localhost - DB_USER: postgres - DB_PASS: postgres - DISABLE_SIMPLECOV: true - RAILS_ENV: test - BUNDLE_CLEAN: true - BUNDLE_FROZEN: true - BUNDLE_WITHOUT: 'development production' - BUNDLE_JOBS: 3 - BUNDLE_RETRY: 3 - - steps: - - uses: actions/checkout@v4 - - - name: Set up Ruby environment - uses: ./.github/actions/setup-ruby - - - name: Create database - run: './bin/rails db:create' - - - name: Run historical migrations with data population - run: './bin/rails tests:migrations:prepare_database' - - - name: Run all remaining migrations - run: './bin/rails db:migrate' - - - name: Check migration result - run: './bin/rails tests:migrations:check_database' diff --git a/.github/workflows/test-migrations-two-step.yml b/.github/workflows/test-migrations-two-step.yml deleted file mode 100644 index 6698847315..0000000000 --- a/.github/workflows/test-migrations-two-step.yml +++ /dev/null @@ -1,95 +0,0 @@ -name: Test two step migrations -on: - push: - branches-ignore: - - 'dependabot/**' - - 'renovate/**' - pull_request: - -jobs: - pre_job: - runs-on: ubuntu-latest - - outputs: - should_skip: ${{ steps.skip_check.outputs.should_skip }} - - steps: - - id: skip_check - uses: fkirc/skip-duplicate-actions@v5 - with: - paths: '["Gemfile*", ".ruby-version", "**/*.rb", ".github/workflows/test-migrations-two-step.yml", "lib/tasks/tests.rake"]' - - test: - runs-on: ubuntu-latest - needs: pre_job - if: needs.pre_job.outputs.should_skip != 'true' - - strategy: - fail-fast: false - - matrix: - postgres: - - 14-alpine - - 15-alpine - - services: - postgres: - image: postgres:${{ matrix.postgres}} - env: - POSTGRES_PASSWORD: postgres - POSTGRES_USER: postgres - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 5432:5432 - - redis: - image: redis:7-alpine - options: >- - --health-cmd "redis-cli ping" - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 6379:6379 - - env: - CONTINUOUS_INTEGRATION: true - DB_HOST: localhost - DB_USER: postgres - DB_PASS: postgres - DISABLE_SIMPLECOV: true - RAILS_ENV: test - BUNDLE_CLEAN: true - BUNDLE_FROZEN: true - BUNDLE_WITHOUT: 'development production' - BUNDLE_JOBS: 3 - BUNDLE_RETRY: 3 - - steps: - - uses: actions/checkout@v4 - - - name: Set up Ruby environment - uses: ./.github/actions/setup-ruby - - - name: Create database - run: './bin/rails db:create' - - - name: Run historical migrations with data population - run: './bin/rails tests:migrations:prepare_database' - env: - SKIP_POST_DEPLOYMENT_MIGRATIONS: true - - - name: Run all remaining pre-deployment migrations - run: './bin/rails db:migrate' - env: - SKIP_POST_DEPLOYMENT_MIGRATIONS: true - - - name: Run all post-deployment migrations - run: './bin/rails db:migrate' - - - name: Check migration result - run: './bin/rails tests:migrations:check_database' diff --git a/.github/workflows/test-migrations.yml b/.github/workflows/test-migrations.yml new file mode 100644 index 0000000000..6a0e67c58e --- /dev/null +++ b/.github/workflows/test-migrations.yml @@ -0,0 +1,93 @@ +name: Historical data migration test + +on: + merge_group: + push: + branches: + - 'main' + - 'stable-*' + paths: + - 'Gemfile*' + - '.ruby-version' + - '**/*.rb' + - '.github/workflows/test-migrations.yml' + - 'lib/tasks/tests.rake' + + pull_request: + paths: + - 'Gemfile*' + - '.ruby-version' + - '**/*.rb' + - '.github/workflows/test-migrations.yml' + - 'lib/tasks/tests.rake' + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + + matrix: + postgres: + - 14-alpine + - 15-alpine + + services: + postgres: + image: postgres:${{ matrix.postgres}} + env: + POSTGRES_PASSWORD: postgres + POSTGRES_USER: postgres + options: >- + --health-cmd pg_isready + --health-interval 10ms + --health-timeout 3s + --health-retries 50 + ports: + - 5432:5432 + + redis: + image: redis:7-alpine + options: >- + --health-cmd "redis-cli ping" + --health-interval 10ms + --health-timeout 3s + --health-retries 50 + ports: + - 6379:6379 + + env: + DB_HOST: localhost + DB_USER: postgres + DB_PASS: postgres + DISABLE_SIMPLECOV: true + RAILS_ENV: test + BUNDLE_CLEAN: true + BUNDLE_FROZEN: true + BUNDLE_WITHOUT: 'development:production' + BUNDLE_JOBS: 3 + BUNDLE_RETRY: 3 + + steps: + - uses: actions/checkout@v4 + + - name: Set up Ruby environment + uses: ./.github/actions/setup-ruby + + - name: Test "one step migration" flow + run: | + bin/rails db:drop + bin/rails db:create + bin/rails tests:migrations:prepare_database + bin/rails db:migrate + bin/rails tests:migrations:check_database + + - name: Test "two step migration" flow + run: | + bin/rails db:drop + bin/rails db:create + SKIP_POST_DEPLOYMENT_MIGRATIONS=true bin/rails tests:migrations:prepare_database + SKIP_POST_DEPLOYMENT_MIGRATIONS=true bin/rails db:migrate + bin/rails db:migrate + bin/rails tests:migrations:check_database diff --git a/.github/workflows/test-ruby.yml b/.github/workflows/test-ruby.yml index 3a78f8b43d..fcfeed5fba 100644 --- a/.github/workflows/test-ruby.yml +++ b/.github/workflows/test-ruby.yml @@ -1,10 +1,11 @@ name: Ruby Testing on: + merge_group: push: - branches-ignore: - - 'dependabot/**' - - 'renovate/**' + branches: + - 'main' + - 'stable-*' pull_request: env: @@ -28,11 +29,7 @@ jobs: env: RAILS_ENV: ${{ matrix.mode }} BUNDLE_WITH: ${{ matrix.mode }} - ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY: precompile_placeholder - ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT: precompile_placeholder - ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY: precompile_placeholder - OTP_SECRET: precompile_placeholder - SECRET_KEY_BASE: precompile_placeholder + SECRET_KEY_BASE_DUMMY: 1 steps: - uses: actions/checkout@v4 @@ -77,9 +74,9 @@ jobs: POSTGRES_USER: postgres options: >- --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 + --health-interval 10ms + --health-timeout 3s + --health-retries 50 ports: - 5432:5432 @@ -87,9 +84,9 @@ jobs: image: redis:7-alpine options: >- --health-cmd "redis-cli ping" - --health-interval 10s - --health-timeout 5s - --health-retries 5 + --health-interval 10ms + --health-timeout 3s + --health-retries 50 ports: - 6379:6379 @@ -115,8 +112,8 @@ jobs: matrix: ruby-version: - '3.1' + - '3.2' - '.ruby-version' - - '3.3' steps: - uses: actions/checkout@v4 @@ -133,18 +130,109 @@ jobs: uses: ./.github/actions/setup-ruby with: ruby-version: ${{ matrix.ruby-version}} - additional-system-dependencies: ffmpeg imagemagick libpam-dev + additional-system-dependencies: ffmpeg libpam-dev + + - name: Load database schema + run: | + bin/rails db:setup + bin/flatware fan bin/rails db:test:prepare + + - run: bin/flatware rspec -r ./spec/flatware_helper.rb + + - name: Upload coverage reports to Codecov + if: matrix.ruby-version == '.ruby-version' + uses: codecov/codecov-action@v4 + with: + files: coverage/lcov/*.lcov + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + test-libvips: + name: Libvips tests + runs-on: ubuntu-24.04 + + needs: + - build + + services: + postgres: + image: postgres:14-alpine + env: + POSTGRES_PASSWORD: postgres + POSTGRES_USER: postgres + options: >- + --health-cmd pg_isready + --health-interval 10ms + --health-timeout 3s + --health-retries 50 + ports: + - 5432:5432 + + redis: + image: redis:7-alpine + options: >- + --health-cmd "redis-cli ping" + --health-interval 10ms + --health-timeout 3s + --health-retries 50 + ports: + - 6379:6379 + + env: + DB_HOST: localhost + DB_USER: postgres + DB_PASS: postgres + DISABLE_SIMPLECOV: ${{ matrix.ruby-version != '.ruby-version' }} + RAILS_ENV: test + ALLOW_NOPAM: true + PAM_ENABLED: true + PAM_DEFAULT_SERVICE: pam_test + PAM_CONTROLLED_SERVICE: pam_test_controlled + OIDC_ENABLED: true + OIDC_SCOPE: read + SAML_ENABLED: true + CAS_ENABLED: true + BUNDLE_WITH: 'pam_authentication test' + GITHUB_RSPEC: ${{ matrix.ruby-version == '.ruby-version' && github.event.pull_request && 'true' }} + MASTODON_USE_LIBVIPS: true + + strategy: + fail-fast: false + matrix: + ruby-version: + - '3.1' + - '3.2' + - '.ruby-version' + steps: + - uses: actions/checkout@v4 + + - uses: actions/download-artifact@v4 + with: + path: './' + name: ${{ github.sha }} + + - name: Expand archived asset artifacts + run: | + tar xvzf artifacts.tar.gz + + - name: Set up Ruby environment + uses: ./.github/actions/setup-ruby + with: + ruby-version: ${{ matrix.ruby-version}} + additional-system-dependencies: ffmpeg libpam-dev libyaml-dev - name: Load database schema run: './bin/rails db:create db:schema:load db:seed' - - run: bin/rspec + - run: bin/rspec --tag attachment_processing - name: Upload coverage reports to Codecov if: matrix.ruby-version == '.ruby-version' uses: codecov/codecov-action@v4 with: files: coverage/lcov/mastodon.lcov + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} test-e2e: name: End to End testing @@ -161,9 +249,9 @@ jobs: POSTGRES_USER: postgres options: >- --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 + --health-interval 10ms + --health-timeout 3s + --health-retries 50 ports: - 5432:5432 @@ -171,9 +259,9 @@ jobs: image: redis:7-alpine options: >- --health-cmd "redis-cli ping" - --health-interval 10s - --health-timeout 5s - --health-retries 5 + --health-interval 10ms + --health-timeout 3s + --health-retries 50 ports: - 6379:6379 @@ -184,28 +272,34 @@ jobs: DISABLE_SIMPLECOV: true RAILS_ENV: test BUNDLE_WITH: test + LOCAL_DOMAIN: localhost:3000 + LOCAL_HTTPS: false strategy: fail-fast: false matrix: ruby-version: - '3.1' + - '3.2' - '.ruby-version' - - '3.3' steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 with: - path: './public' + path: './' name: ${{ github.sha }} + - name: Expand archived asset artifacts + run: | + tar xvzf artifacts.tar.gz + - name: Set up Ruby environment uses: ./.github/actions/setup-ruby with: ruby-version: ${{ matrix.ruby-version}} - additional-system-dependencies: ffmpeg imagemagick + additional-system-dependencies: ffmpeg - name: Set up Javascript environment uses: ./.github/actions/setup-javascript @@ -213,7 +307,7 @@ jobs: - name: Load database schema run: './bin/rails db:create db:schema:load db:seed' - - run: bundle exec rake spec:system + - run: bin/rspec spec/system --tag streaming --tag js - name: Archive logs uses: actions/upload-artifact@v4 @@ -244,9 +338,9 @@ jobs: POSTGRES_USER: postgres options: >- --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 + --health-interval 10ms + --health-timeout 3s + --health-retries 50 ports: - 5432:5432 @@ -254,22 +348,36 @@ jobs: image: redis:7-alpine options: >- --health-cmd "redis-cli ping" - --health-interval 10s - --health-timeout 5s - --health-retries 5 + --health-interval 10ms + --health-timeout 3s + --health-retries 50 ports: - 6379:6379 - search: - image: ${{ matrix.search-image }} + elasticsearch: + image: ${{ contains(matrix.search-image, 'elasticsearch') && matrix.search-image || '' }} env: discovery.type: single-node xpack.security.enabled: false options: >- --health-cmd "curl http://localhost:9200/_cluster/health" - --health-interval 10s - --health-timeout 5s - --health-retries 10 + --health-interval 2s + --health-timeout 3s + --health-retries 50 + ports: + - 9200:9200 + + opensearch: + image: ${{ contains(matrix.search-image, 'opensearch') && matrix.search-image || '' }} + env: + discovery.type: single-node + DISABLE_INSTALL_DEMO_CONFIG: true + DISABLE_SECURITY_PLUGIN: true + options: >- + --health-cmd "curl http://localhost:9200/_cluster/health" + --health-interval 2s + --health-timeout 3s + --health-retries 50 ports: - 9200:9200 @@ -289,27 +397,29 @@ jobs: matrix: ruby-version: - '3.1' + - '3.2' - '.ruby-version' - - '3.3' search-image: - docker.elastic.co/elasticsearch/elasticsearch:7.17.13 include: - ruby-version: '.ruby-version' search-image: docker.elastic.co/elasticsearch/elasticsearch:8.10.2 + - ruby-version: '.ruby-version' + search-image: opensearchproject/opensearch:2 steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 with: - path: './public' + path: './' name: ${{ github.sha }} - name: Set up Ruby environment uses: ./.github/actions/setup-ruby with: ruby-version: ${{ matrix.ruby-version}} - additional-system-dependencies: ffmpeg imagemagick + additional-system-dependencies: ffmpeg - name: Set up Javascript environment uses: ./.github/actions/setup-javascript diff --git a/.gitignore b/.gitignore index 2f94b751ab..a70f30f952 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,6 @@ yarn-debug.log # Ignore Docker option files docker-compose.override.yml + +# Ignore dotenv .local files +.env*.local diff --git a/.nanoignore b/.nanoignore deleted file mode 100644 index 80e9397035..0000000000 --- a/.nanoignore +++ /dev/null @@ -1,19 +0,0 @@ -.DS_Store -.git/ -.gitignore - -.bundle/ -.cache/ -config/deploy/* -coverage -docs/ -.env -log/*.log -neo4j/ -node_modules/ -public/assets/ -public/system/ -spec/ -tmp/ -.vagrant/ -vendor/bundle/ diff --git a/.nvmrc b/.nvmrc index 7795cadb57..cecb936289 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -20.12 +20.15 diff --git a/.rubocop.yml b/.rubocop.yml index 1b5ce67ee7..965f56f3e7 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,7 +1,27 @@ -# Can be removed once all rules are addressed or moved to this file as documented overrides -inherit_from: .rubocop_todo.yml +--- +AllCops: + CacheRootDirectory: tmp + DisplayStyleGuide: true + Exclude: + - Vagrantfile + - config/initializers/json_ld* + - lib/mastodon/migration_helpers.rb + ExtraDetails: true + NewCops: enable + TargetRubyVersion: 3.1 # Oldest supported ruby version + +inherit_from: + - .rubocop/layout.yml + - .rubocop/metrics.yml + - .rubocop/naming.yml + - .rubocop/rails.yml + - .rubocop/rspec_rails.yml + - .rubocop/rspec.yml + - .rubocop/style.yml + - .rubocop/custom.yml + - .rubocop_todo.yml + - .rubocop/strict.yml -# Used for merging with exclude lists with .rubocop_todo.yml inherit_mode: merge: - Exclude @@ -12,219 +32,3 @@ require: - rubocop-rspec_rails - rubocop-performance - rubocop-capybara - - ./lib/linter/rubocop_middle_dot - -AllCops: - TargetRubyVersion: 3.1 # Set to minimum supported version of CI - DisplayCopNames: true - DisplayStyleGuide: true - ExtraDetails: true - UseCache: true - CacheRootDirectory: tmp - NewCops: enable # Opt-in to newly added rules - Exclude: - - db/schema.rb - - 'bin/*' - - 'node_modules/**/*' - - 'Vagrantfile' - - 'vendor/**/*' - - 'config/initializers/json_ld*' # Generated files - - 'lib/mastodon/migration_helpers.rb' # Vendored from GitLab - - 'lib/templates/**/*' - -# Reason: Prefer Hashes without extreme indentation -# https://docs.rubocop.org/rubocop/cops_layout.html#layoutfirsthashelementindentation -Layout/FirstHashElementIndentation: - EnforcedStyle: consistent - -# Reason: Currently disabled in .rubocop_todo.yml -# https://docs.rubocop.org/rubocop/cops_layout.html#layoutlinelength -Layout/LineLength: - Max: 300 # Default of 120 causes a duplicate entry in generated todo file - -## Disable most Metrics/*Length cops -# Reason: those are often triggered and force significant refactors when this happend -# but the team feel they are not really improving the code quality. - -# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsblocklength -Metrics/BlockLength: - Enabled: false - -# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsclasslength -Metrics/ClassLength: - Enabled: false - -# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsmethodlength -Metrics/MethodLength: - Enabled: false - -# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsmodulelength -Metrics/ModuleLength: - Enabled: false - -## End Disable Metrics/*Length cops - -# Reason: Currently disabled in .rubocop_todo.yml -# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsabcsize -Metrics/AbcSize: - Exclude: - - 'lib/mastodon/cli/*.rb' - -# Reason: Currently disabled in .rubocop_todo.yml -# https://docs.rubocop.org/rubocop/cops_metrics.html#metricscyclomaticcomplexity -Metrics/CyclomaticComplexity: - Exclude: - - lib/mastodon/cli/*.rb - -# Reason: -# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsparameterlists -Metrics/ParameterLists: - CountKeywordArgs: false - -# Reason: Prefer seeing a variable name -# https://docs.rubocop.org/rubocop/cops_naming.html#namingblockforwarding -Naming/BlockForwarding: - EnforcedStyle: explicit - -# Reason: Prevailing style is argument file paths -# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsfilepath -Rails/FilePath: - EnforcedStyle: arguments - -# Reason: Prevailing style uses numeric status codes, matches RSpec/Rails/HttpStatus -# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railshttpstatus -Rails/HttpStatus: - EnforcedStyle: numeric - -# Reason: Conflicts with `Lint/UselessMethodDefinition` for inherited controller actions -# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railslexicallyscopedactionfilter -Rails/LexicallyScopedActionFilter: - Exclude: - - 'app/controllers/auth/*' - -# Reason: These tasks are doing local work which do not need full env loaded -# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsrakeenvironment -Rails/RakeEnvironment: - Exclude: - - 'lib/tasks/auto_annotate_models.rake' - - 'lib/tasks/emojis.rake' - - 'lib/tasks/mastodon.rake' - - 'lib/tasks/repo.rake' - - 'lib/tasks/statistics.rake' - -# Reason: There are appropriate times to use these features -# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsskipsmodelvalidations -Rails/SkipsModelValidations: - Enabled: false - -# Reason: We want to preserve the ability to migrate from arbitrary old versions, -# and cannot guarantee that every installation has run every migration as they upgrade. -# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsunusedignoredcolumns -Rails/UnusedIgnoredColumns: - Enabled: false - -# Reason: Prevailing style choice -# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsnegateinclude -Rails/NegateInclude: - Enabled: false - -# Reason: Enforce default limit, but allow some elements to span lines -# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecexamplelength -RSpec/ExampleLength: - CountAsOne: ['array', 'heredoc', 'method_call'] - -# Reason: Deprecated cop, will be removed in 3.0, replaced by SpecFilePathFormat -# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecfilepath -RSpec/FilePath: - Enabled: false - -# Reason: -# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecnamedsubject -RSpec/NamedSubject: - EnforcedStyle: named_only - -# Reason: Prevailing style choice -# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecnottonot -RSpec/NotToNot: - EnforcedStyle: to_not - -# Reason: Match overrides from Rspec/FilePath rule above -# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecspecfilepathformat -RSpec/SpecFilePathFormat: - CustomTransform: - ActivityPub: activitypub - DeepL: deepl - FetchOEmbedService: fetch_oembed_service - OEmbedController: oembed_controller - OStatus: ostatus - -# Reason: Prevailing style uses numeric status codes, matches Rails/HttpStatus -# https://docs.rubocop.org/rubocop-rspec/cops_rspec_rails.html#rspecrailshttpstatus -RSpecRails/HttpStatus: - EnforcedStyle: numeric - -# Reason: -# https://docs.rubocop.org/rubocop/cops_style.html#styleclassandmodulechildren -Style/ClassAndModuleChildren: - Enabled: false - -# Reason: Classes mostly self-document with their names -# https://docs.rubocop.org/rubocop/cops_style.html#styledocumentation -Style/Documentation: - Enabled: false - -# Reason: Route redirects are not token-formatted and must be skipped -# https://docs.rubocop.org/rubocop/cops_style.html#styleformatstringtoken -Style/FormatStringToken: - inherit_mode: - merge: - - AllowedMethods # The rubocop-rails config adds `redirect` - AllowedMethods: - - redirect_with_vary - -# Reason: Enforce modern Ruby style -# https://docs.rubocop.org/rubocop/cops_style.html#stylehashsyntax -Style/HashSyntax: - EnforcedStyle: ruby19_no_mixed_keys - EnforcedShorthandSyntax: either - -# Reason: -# https://docs.rubocop.org/rubocop/cops_style.html#stylenumericliterals -Style/NumericLiterals: - AllowedPatterns: - - \d{4}_\d{2}_\d{2}_\d{6} # For DB migration date version number readability - -# Reason: -# https://docs.rubocop.org/rubocop/cops_style.html#stylepercentliteraldelimiters -Style/PercentLiteralDelimiters: - PreferredDelimiters: - '%i': '()' - '%w': '()' - -# Reason: Prefer less indentation in conditional assignments -# https://docs.rubocop.org/rubocop/cops_style.html#styleredundantbegin -Style/RedundantBegin: - Enabled: false - -# Reason: Overridden to reduce implicit StandardError rescues -# https://docs.rubocop.org/rubocop/cops_style.html#stylerescuestandarderror -Style/RescueStandardError: - EnforcedStyle: implicit - -# Reason: Originally disabled for CodeClimate, and no config consensus has been found -# https://docs.rubocop.org/rubocop/cops_style.html#stylesymbolarray -Style/SymbolArray: - Enabled: false - -# Reason: -# https://docs.rubocop.org/rubocop/cops_style.html#styletrailingcommainarrayliteral -Style/TrailingCommaInArrayLiteral: - EnforcedStyleForMultiline: 'comma' - -# Reason: -# https://docs.rubocop.org/rubocop/cops_style.html#styletrailingcommainhashliteral -Style/TrailingCommaInHashLiteral: - EnforcedStyleForMultiline: 'comma' - -Style/MiddleDot: - Enabled: true diff --git a/.rubocop/custom.yml b/.rubocop/custom.yml new file mode 100644 index 0000000000..63035837f8 --- /dev/null +++ b/.rubocop/custom.yml @@ -0,0 +1,6 @@ +--- +require: + - ../lib/linter/rubocop_middle_dot + +Style/MiddleDot: + Enabled: true diff --git a/.rubocop/layout.yml b/.rubocop/layout.yml new file mode 100644 index 0000000000..487879ca2c --- /dev/null +++ b/.rubocop/layout.yml @@ -0,0 +1,6 @@ +--- +Layout/FirstHashElementIndentation: + EnforcedStyle: consistent + +Layout/LineLength: + Max: 300 # Default of 120 causes a duplicate entry in generated todo file diff --git a/.rubocop/metrics.yml b/.rubocop/metrics.yml new file mode 100644 index 0000000000..89532af42a --- /dev/null +++ b/.rubocop/metrics.yml @@ -0,0 +1,23 @@ +--- +Metrics/AbcSize: + Exclude: + - lib/mastodon/cli/*.rb + +Metrics/BlockLength: + Enabled: false + +Metrics/ClassLength: + Enabled: false + +Metrics/CyclomaticComplexity: + Exclude: + - lib/mastodon/cli/*.rb + +Metrics/MethodLength: + Enabled: false + +Metrics/ModuleLength: + Enabled: false + +Metrics/ParameterLists: + CountKeywordArgs: false diff --git a/.rubocop/naming.yml b/.rubocop/naming.yml new file mode 100644 index 0000000000..da6ad4ac57 --- /dev/null +++ b/.rubocop/naming.yml @@ -0,0 +1,3 @@ +--- +Naming/BlockForwarding: + EnforcedStyle: explicit diff --git a/.rubocop/rails.yml b/.rubocop/rails.yml new file mode 100644 index 0000000000..ae31c1f266 --- /dev/null +++ b/.rubocop/rails.yml @@ -0,0 +1,23 @@ +--- +Rails/BulkChangeTable: + Enabled: false # Conflicts with strong_migrations features + +Rails/FilePath: + EnforcedStyle: arguments + +Rails/HttpStatus: + EnforcedStyle: numeric + +Rails/NegateInclude: + Enabled: false + +Rails/RakeEnvironment: + Exclude: # Tasks are doing local work which do not need full env loaded + - lib/tasks/auto_annotate_models.rake + - lib/tasks/emojis.rake + - lib/tasks/mastodon.rake + - lib/tasks/repo.rake + - lib/tasks/statistics.rake + +Rails/SkipsModelValidations: + Enabled: false diff --git a/.rubocop/rspec.yml b/.rubocop/rspec.yml new file mode 100644 index 0000000000..d2d2f8325d --- /dev/null +++ b/.rubocop/rspec.yml @@ -0,0 +1,27 @@ +--- +RSpec/ExampleLength: + CountAsOne: ['array', 'heredoc', 'method_call'] + Max: 20 # Override default of 5 + +RSpec/MultipleExpectations: + Max: 10 # Overrides default of 1 + +RSpec/MultipleMemoizedHelpers: + Max: 20 # Overrides default of 5 + +RSpec/NamedSubject: + EnforcedStyle: named_only + +RSpec/NestedGroups: + Max: 10 # Overrides default of 3 + +RSpec/NotToNot: + EnforcedStyle: to_not + +RSpec/SpecFilePathFormat: + CustomTransform: + ActivityPub: activitypub + DeepL: deepl + FetchOEmbedService: fetch_oembed_service + OEmbedController: oembed_controller + OStatus: ostatus diff --git a/.rubocop/rspec_rails.yml b/.rubocop/rspec_rails.yml new file mode 100644 index 0000000000..993a5689ad --- /dev/null +++ b/.rubocop/rspec_rails.yml @@ -0,0 +1,3 @@ +--- +RSpecRails/HttpStatus: + EnforcedStyle: numeric diff --git a/.rubocop/strict.yml b/.rubocop/strict.yml new file mode 100644 index 0000000000..2222c6d8b9 --- /dev/null +++ b/.rubocop/strict.yml @@ -0,0 +1,19 @@ +Lint/Debugger: # Remove any `binding.pry` + Enabled: true + Exclude: [] + +RSpec/Focus: # Require full spec run on CI + Enabled: true + Exclude: [] + +Rails/Output: # Remove any `puts` debugging + Enabled: true + Exclude: [] + +Rails/FindEach: # Using `each` could impact performance, use `find_each` + Enabled: true + Exclude: [] + +Rails/UniqBeforePluck: # Require `uniq.pluck` and not `pluck.uniq` + Enabled: true + Exclude: [] diff --git a/.rubocop/style.yml b/.rubocop/style.yml new file mode 100644 index 0000000000..03e35a70ac --- /dev/null +++ b/.rubocop/style.yml @@ -0,0 +1,47 @@ +--- +Style/ClassAndModuleChildren: + Enabled: false + +Style/Documentation: + Enabled: false + +Style/FormatStringToken: + AllowedMethods: + - redirect_with_vary # Route redirects are not token-formatted + inherit_mode: + merge: + - AllowedMethods + +Style/HashAsLastArrayItem: + Enabled: false + +Style/HashSyntax: + EnforcedShorthandSyntax: either + EnforcedStyle: ruby19_no_mixed_keys + +Style/NumericLiterals: + AllowedPatterns: + - \d{4}_\d{2}_\d{2}_\d{6} + +Style/PercentLiteralDelimiters: + PreferredDelimiters: + '%i': () + '%w': () + +Style/RedundantBegin: + Enabled: false + +Style/RedundantFetchBlock: + Enabled: false + +Style/RescueStandardError: + EnforcedStyle: implicit + +Style/SymbolArray: + Enabled: false + +Style/TrailingCommaInArrayLiteral: + EnforcedStyleForMultiline: comma + +Style/TrailingCommaInHashLiteral: + EnforcedStyleForMultiline: comma diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 4b1292580c..2549202410 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by -# `rubocop --auto-gen-config --auto-gen-only-exclude --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp` -# using RuboCop version 1.62.1. +# `rubocop --auto-gen-config --auto-gen-only-exclude --no-offense-counts --no-auto-gen-timestamp` +# using RuboCop version 1.65.0. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -14,7 +14,7 @@ Lint/NonLocalExitFromIterator: Metrics/AbcSize: Max: 90 -# Configuration parameters: CountBlocks, Max. +# Configuration parameters: CountBlocks, CountModifierForms, Max. Metrics/BlockNesting: Exclude: - 'lib/tasks/mastodon.rake' @@ -27,45 +27,10 @@ Metrics/CyclomaticComplexity: Metrics/PerceivedComplexity: Max: 27 -# Configuration parameters: CountAsOne. -RSpec/ExampleLength: - Max: 18 - -RSpec/MultipleExpectations: - Max: 7 - -# Configuration parameters: AllowSubject. -RSpec/MultipleMemoizedHelpers: - Max: 17 - -# Configuration parameters: AllowedGroups. -RSpec/NestedGroups: - Max: 6 - -# Configuration parameters: Include. -# Include: app/models/**/*.rb -Rails/HasAndBelongsToMany: - Exclude: - - 'app/models/concerns/account/associations.rb' - - 'app/models/status.rb' - - 'app/models/tag.rb' - Rails/OutputSafety: Exclude: - 'config/initializers/simple_form.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowedMethods, AllowedPatterns. -# AllowedMethods: ==, equal?, eql? -Style/ClassEqualityComparison: - Exclude: - - 'app/helpers/jsonld_helper.rb' - - 'app/serializers/activitypub/outbox_serializer.rb' - -Style/ClassVars: - Exclude: - - 'config/initializers/devise.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowedVars. Style/FetchEnvVar: @@ -82,7 +47,6 @@ Style/FetchEnvVar: - 'config/initializers/vapid.rb' - 'lib/mastodon/redis_config.rb' - 'lib/tasks/repo.rake' - - 'spec/features/profile_spec.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, MaxUnannotatedPlaceholdersAllowed, AllowedMethods, AllowedPatterns. @@ -93,53 +57,10 @@ Style/FormatStringToken: - 'config/initializers/devise.rb' - 'lib/paperclip/color_extractor.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/GlobalStdStream: - Exclude: - - 'config/environments/development.rb' - - 'config/environments/production.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: MinBodyLength, AllowConsecutiveConditionals. Style/GuardClause: - Exclude: - - 'app/lib/activitypub/activity/block.rb' - - 'app/lib/request.rb' - - 'app/lib/request_pool.rb' - - 'app/lib/webfinger.rb' - - 'app/lib/webfinger_resource.rb' - - 'app/models/concerns/account/counters.rb' - - 'app/models/concerns/user/ldap_authenticable.rb' - - 'app/models/tag.rb' - - 'app/models/user.rb' - - 'app/services/fan_out_on_write_service.rb' - - 'app/services/post_status_service.rb' - - 'app/services/process_hashtags_service.rb' - - 'app/workers/move_worker.rb' - - 'app/workers/redownload_avatar_worker.rb' - - 'app/workers/redownload_header_worker.rb' - - 'app/workers/redownload_media_worker.rb' - - 'app/workers/remote_account_refresh_worker.rb' - - 'config/initializers/devise.rb' - - 'lib/devise/strategies/two_factor_ldap_authenticatable.rb' - - 'lib/devise/strategies/two_factor_pam_authenticatable.rb' - - 'lib/mastodon/cli/accounts.rb' - - 'lib/mastodon/cli/maintenance.rb' - - 'lib/mastodon/cli/media.rb' - - 'lib/tasks/repo.rake' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: braces, no_braces -Style/HashAsLastArrayItem: - Exclude: - - 'app/controllers/admin/statuses_controller.rb' - - 'app/controllers/api/v1/statuses_controller.rb' - - 'app/models/concerns/account/counters.rb' - - 'app/models/concerns/status/threading_concern.rb' - - 'app/models/status.rb' - - 'app/services/batched_remove_status_service.rb' - - 'app/services/notify_service.rb' + Enabled: false # This cop supports unsafe autocorrection (--autocorrect-all). Style/HashTransformValues: @@ -147,13 +68,6 @@ Style/HashTransformValues: - 'app/serializers/rest/web_push_subscription_serializer.rb' - 'app/services/import_service.rb' -# This cop supports safe autocorrection (--autocorrect). -Style/IfUnlessModifier: - Exclude: - - 'config/environments/production.rb' - - 'config/initializers/devise.rb' - - 'config/initializers/ffmpeg.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). Style/MapToHash: Exclude: @@ -168,16 +82,10 @@ Style/MutableConstant: - 'app/services/delete_account_service.rb' - 'lib/mastodon/migration_warning.rb' -# This cop supports safe autocorrection (--autocorrect). -Style/NilLambda: - Exclude: - - 'config/initializers/paperclip.rb' - # Configuration parameters: AllowedMethods. # AllowedMethods: respond_to_missing? Style/OptionalBooleanParameter: Exclude: - - 'app/helpers/admin/account_moderation_notes_helper.rb' - 'app/helpers/jsonld_helper.rb' - 'app/lib/admin/system_check/message.rb' - 'app/lib/request.rb' @@ -201,37 +109,6 @@ Style/RedundantConstantBase: - 'config/environments/production.rb' - 'config/initializers/sidekiq.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: SafeForConstants. -Style/RedundantFetchBlock: - Exclude: - - 'config/initializers/1_hosts.rb' - - 'config/initializers/chewy.rb' - - 'config/initializers/devise.rb' - - 'config/initializers/paperclip.rb' - - 'config/puma.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength. -# AllowedMethods: present?, blank?, presence, try, try! -Style/SafeNavigation: - Exclude: - - 'app/models/concerns/account/finder_concern.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: only_raise, only_fail, semantic -Style/SignalException: - Exclude: - - 'lib/devise/strategies/two_factor_ldap_authenticatable.rb' - - 'lib/devise/strategies/two_factor_pam_authenticatable.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: Mode. -Style/StringConcatenation: - Exclude: - - 'config/initializers/paperclip.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: WordRegex. # SupportedStyles: percent, brackets diff --git a/.ruby-version b/.ruby-version index b347b11eac..a0891f563f 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.2.3 +3.3.4 diff --git a/.simplecov b/.simplecov deleted file mode 100644 index fbd0207bec..0000000000 --- a/.simplecov +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -if ENV['CI'] - require 'simplecov-lcov' - SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true - SimpleCov.formatter = SimpleCov::Formatter::LcovFormatter -else - SimpleCov.formatter = SimpleCov::Formatter::HTMLFormatter -end - -SimpleCov.start 'rails' do - enable_coverage :branch - - add_filter 'lib/linter' - - add_group 'Libraries', 'lib' - add_group 'Policies', 'app/policies' - add_group 'Presenters', 'app/presenters' - add_group 'Serializers', 'app/serializers' - add_group 'Services', 'app/services' - add_group 'Validators', 'app/validators' -end diff --git a/CHANGELOG.md b/CHANGELOG.md index a53790afaf..7c3d96ba4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,92 @@ All notable changes to this project will be documented in this file. +## [4.2.10] - 2024-07-04 + +### Security + +- Fix incorrect permission checking on multiple API endpoints ([GHSA-58x8-3qxw-6hm7](https://github.com/mastodon/mastodon/security/advisories/GHSA-58x8-3qxw-6hm7)) +- Fix incorrect authorship checking when processing some activities (CVE-2024-37903, [GHSA-xjvf-fm67-4qc3](https://github.com/mastodon/mastodon/security/advisories/GHSA-xjvf-fm67-4qc3)) +- Fix ongoing streaming sessions not being invalidated when application tokens get revoked ([GHSA-vp5r-5pgw-jwqx](https://github.com/mastodon/mastodon/security/advisories/GHSA-vp5r-5pgw-jwqx)) +- Update dependencies + +### Added + +- Add yarn version specification to avoid confusion with Yarn 3 and Yarn 4 + +### Changed + +- Change preview cards generation to skip unusually long URLs ([oneiros](https://github.com/mastodon/mastodon/pull/30854)) +- Change search modifiers to be case-insensitive ([Gargron](https://github.com/mastodon/mastodon/pull/30865)) +- Change `STATSD_ADDR` handling to emit a warning rather than crashing if the address is unreachable ([timothyjrogers](https://github.com/mastodon/mastodon/pull/30691)) +- Change PWA start URL from `/home` to `/` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27377)) + +### Removed + +- Removed dependency on `posix-spawn` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18559)) + +### Fixed + +- Fix scheduled statuses scheduled in less than 5 minutes being immediately published ([danielmbrasil](https://github.com/mastodon/mastodon/pull/30584)) +- Fix encoding detection for link cards ([oneiros](https://github.com/mastodon/mastodon/pull/30780)) +- Fix `/admin/accounts/:account_id/statuses/:id` for edited posts with media attachments ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30819)) +- Fix duplicate `@context` attribute in user archive export ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30653)) + +## [4.2.9] - 2024-05-30 + +### Security + +- Update dependencies +- Fix private mention filtering ([GHSA-5fq7-3p3j-9vrf](https://github.com/mastodon/mastodon/security/advisories/GHSA-5fq7-3p3j-9vrf)) +- Fix password change endpoint not being rate-limited ([GHSA-q3rg-xx5v-4mxh](https://github.com/mastodon/mastodon/security/advisories/GHSA-q3rg-xx5v-4mxh)) +- Add hardening around rate-limit bypass ([GHSA-c2r5-cfqr-c553](https://github.com/mastodon/mastodon/security/advisories/GHSA-c2r5-cfqr-c553)) + +### Added + +- Add rate-limit on OAuth application registration ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/30316)) +- Add fallback redirection when getting a webfinger query `WEB_DOMAIN@WEB_DOMAIN` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28592)) +- Add `digest` attribute to `Admin::DomainBlock` entity in REST API ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/29092)) + +### Removed + +- Remove superfluous application-level caching in some controllers ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29862)) +- Remove aggressive OAuth application vacuuming ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/30316)) + +### Fixed + +- Fix leaking Elasticsearch connections in Sidekiq processes ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30450)) +- Fix language of remote posts not being recognized when using unusual casing ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30403)) +- Fix off-by-one in `tootctl media` commands ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30306)) +- Fix removal of allowed domains (in `LIMITED_FEDERATION_MODE`) not being recorded in the audit log ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/30125)) +- Fix not being able to block a subdomain of an already-blocked domain through the API ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30119)) +- Fix `Idempotency-Key` being ignored when scheduling a post ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30084)) +- Fix crash when supplying the `FFMPEG_BINARY` environment variable ([timothyjrogers](https://github.com/mastodon/mastodon/pull/30022)) +- Fix improper email address validation ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29838)) +- Fix results/query in `api/v1/featured_tags/suggestions` ([mjankowski](https://github.com/mastodon/mastodon/pull/29597)) +- Fix unblocking internationalized domain names under certain conditions ([tribela](https://github.com/mastodon/mastodon/pull/29530)) +- Fix admin account created by `mastodon:setup` not being auto-approved ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29379)) +- Fix reference to non-existent var in CLI maintenance command ([mjankowski](https://github.com/mastodon/mastodon/pull/28363)) + +## [4.2.8] - 2024-02-23 + +### Added + +- Add hourly task to automatically require approval for new registrations in the absence of moderators ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29318), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/29355)) + In order to prevent future abandoned Mastodon servers from being used for spam, harassment and other malicious activity, Mastodon will now automatically switch new user registrations to require moderator approval whenever they are left open and no activity (including non-moderation actions from apps) from any logged-in user with permission to access moderation reports has been detected in a full week. + When this happens, users with the permission to change server settings will receive an email notification. + This feature is disabled when `EMAIL_DOMAIN_ALLOWLIST` is used, and can also be disabled with `DISABLE_AUTOMATIC_SWITCHING_TO_APPROVED_REGISTRATIONS=true`. + +### Changed + +- Change registrations to be closed by default on new installations ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29280)) + If you are running a server and never changed your registrations mode from the default, updating will automatically close your registrations. + Simply re-enable them through the administration interface or using `tootctl settings registrations open` if you want to enable them again. + +### Fixed + +- Fix processing of remote ActivityPub actors making use of `Link` objects as `Image` `url` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29335)) +- Fix link verifications when page size exceeds 1MB ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29358)) + ## [4.2.7] - 2024-02-16 ### Fixed diff --git a/Dockerfile b/Dockerfile index 43bc24295d..758db9bcc9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,7 @@ -# syntax=docker/dockerfile:1.7 +# syntax=docker/dockerfile:1.8 + +# This file is designed for production server deployment, not local development work +# For a containerized local dev environment, see: https://github.com/mastodon/mastodon/blob/main/README.md#docker # Please see https://docs.docker.com/engine/reference/builder for information about # the extended buildx capabilities used in this file. @@ -7,29 +10,31 @@ ARG TARGETPLATFORM=${TARGETPLATFORM} ARG BUILDPLATFORM=${BUILDPLATFORM} -# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.2.3"] -ARG RUBY_VERSION="3.2.3" +# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.3.x"] +# renovate: datasource=docker depName=docker.io/ruby +ARG RUBY_VERSION="3.3.4" # # Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"] +# renovate: datasource=node-version depName=node ARG NODE_MAJOR_VERSION="20" # Debian image to use for base image, change with [--build-arg DEBIAN_VERSION="bookworm"] ARG DEBIAN_VERSION="bookworm" # Node image to use for base image based on combined variables (ex: 20-bookworm-slim) -FROM docker.io/node:${NODE_MAJOR_VERSION}-${DEBIAN_VERSION}-slim as node -# Ruby image to use for base image based on combined variables (ex: 3.2.3-slim-bookworm) -FROM docker.io/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} as ruby +FROM docker.io/node:${NODE_MAJOR_VERSION}-${DEBIAN_VERSION}-slim AS node +# Ruby image to use for base image based on combined variables (ex: 3.3.x-slim-bookworm) +FROM docker.io/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} AS ruby # Resulting version string is vX.X.X-MASTODON_VERSION_PRERELEASE+MASTODON_VERSION_METADATA -# Example: v4.2.0-nightly.2023.11.09+something -# Overwrite existence of 'alpha.0' in version.rb [--build-arg MASTODON_VERSION_PRERELEASE="nightly.2023.11.09"] +# Example: v4.3.0-nightly.2023.11.09+pr-123456 +# Overwrite existence of 'alpha.X' in version.rb [--build-arg MASTODON_VERSION_PRERELEASE="nightly.2023.11.09"] ARG MASTODON_VERSION_PRERELEASE="" -# Append build metadata or fork information to version.rb [--build-arg MASTODON_VERSION_METADATA="something"] +# Append build metadata or fork information to version.rb [--build-arg MASTODON_VERSION_METADATA="pr-123456"] ARG MASTODON_VERSION_METADATA="" # Allow Ruby on Rails to serve static files # See: https://docs.joinmastodon.org/admin/config/#rails_serve_static_files ARG RAILS_SERVE_STATIC_FILES="true" # Allow to use YJIT compiler -# See: https://github.com/ruby/ruby/blob/v3_2_3/doc/yjit/yjit.md +# See: https://github.com/ruby/ruby/blob/v3_2_4/doc/yjit/yjit.md ARG RUBY_YJIT_ENABLE="1" # Timezone used by the Docker container and runtime, change with [--build-arg TZ=Europe/Berlin] ARG TZ="Etc/UTC" @@ -60,7 +65,11 @@ ENV \ DEBIAN_FRONTEND="noninteractive" \ PATH="${PATH}:/opt/ruby/bin:/opt/mastodon/bin" \ # Optimize jemalloc 5.x performance - MALLOC_CONF="narenas:2,background_thread:true,thp:never,dirty_decay_ms:1000,muzzy_decay_ms:0" + MALLOC_CONF="narenas:2,background_thread:true,thp:never,dirty_decay_ms:1000,muzzy_decay_ms:0" \ +# Enable libvips, should not be changed + MASTODON_USE_LIBVIPS=true \ +# Sidekiq will touch tmp/sidekiq_process_has_started_and_will_begin_processing_jobs to indicate it is ready. This can be used for a readiness check in Kubernetes + MASTODON_SIDEKIQ_READY_FILENAME=sidekiq_process_has_started_and_will_begin_processing_jobs # Set default shell used for running commands SHELL ["/bin/bash", "-o", "pipefail", "-o", "errexit", "-c"] @@ -93,11 +102,8 @@ RUN \ apt-get dist-upgrade -yq; \ # Install jemalloc, curl and other necessary components apt-get install -y --no-install-recommends \ - ca-certificates \ curl \ - ffmpeg \ file \ - imagemagick \ libjemalloc2 \ patchelf \ procps \ @@ -113,7 +119,7 @@ RUN \ ; # Create temporary build layer from base image -FROM ruby as build +FROM ruby AS build # Copy Node package configuration files into working directory COPY package.json yarn.lock .yarnrc.yml /opt/mastodon/ @@ -131,18 +137,47 @@ RUN \ --mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \ # Install build tools and bundler dependencies from APT apt-get install -y --no-install-recommends \ - g++ \ - gcc \ + autoconf \ + automake \ + build-essential \ + cmake \ git \ libgdbm-dev \ + libglib2.0-dev \ libgmp-dev \ libicu-dev \ libidn-dev \ libpq-dev \ libssl-dev \ - make \ + libtool \ + meson \ + nasm \ + pkg-config \ shared-mime-info \ - zlib1g-dev \ + xz-utils \ + # libvips components + libcgif-dev \ + libexif-dev \ + libexpat1-dev \ + libgirepository1.0-dev \ + libheif-dev \ + libimagequant-dev \ + libjpeg62-turbo-dev \ + liblcms2-dev \ + liborc-dev \ + libspng-dev \ + libtiff-dev \ + libwebp-dev \ + # ffmpeg components + libdav1d-dev \ + liblzma-dev \ + libmp3lame-dev \ + libopus-dev \ + libsnappy-dev \ + libvorbis-dev \ + libvpx-dev \ + libx264-dev \ + libx265-dev \ ; RUN \ @@ -151,8 +186,70 @@ RUN \ corepack enable; \ corepack prepare --activate; +# Create temporary libvips specific build layer from build layer +FROM build AS libvips + +# libvips version to compile, change with [--build-arg VIPS_VERSION="8.15.2"] +# renovate: datasource=github-releases depName=libvips packageName=libvips/libvips +ARG VIPS_VERSION=8.15.2 +# 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 + +WORKDIR /usr/local/libvips/src + +RUN \ + curl -sSL -o vips-${VIPS_VERSION}.tar.xz ${VIPS_URL}/v${VIPS_VERSION}/vips-${VIPS_VERSION}.tar.xz; \ + tar xf vips-${VIPS_VERSION}.tar.xz; \ + cd vips-${VIPS_VERSION}; \ + meson setup build --prefix /usr/local/libvips --libdir=lib -Ddeprecated=false -Dintrospection=disabled -Dmodules=disabled -Dexamples=false; \ + cd build; \ + ninja; \ + ninja install; + +# Create temporary ffmpeg specific build layer from build layer +FROM build AS ffmpeg + +# ffmpeg version to compile, change with [--build-arg FFMPEG_VERSION="7.0.x"] +# renovate: datasource=repology depName=ffmpeg packageName=openpkg_current/ffmpeg +ARG FFMPEG_VERSION=7.0.1 +# ffmpeg download URL, change with [--build-arg FFMPEG_URL="https://ffmpeg.org/releases"] +ARG FFMPEG_URL=https://ffmpeg.org/releases + +WORKDIR /usr/local/ffmpeg/src + +RUN \ + curl -sSL -o ffmpeg-${FFMPEG_VERSION}.tar.xz ${FFMPEG_URL}/ffmpeg-${FFMPEG_VERSION}.tar.xz; \ + tar xf ffmpeg-${FFMPEG_VERSION}.tar.xz; \ + cd ffmpeg-${FFMPEG_VERSION}; \ + ./configure \ + --prefix=/usr/local/ffmpeg \ + --toolchain=hardened \ + --disable-debug \ + --disable-devices \ + --disable-doc \ + --disable-ffplay \ + --disable-network \ + --disable-static \ + --enable-ffmpeg \ + --enable-ffprobe \ + --enable-gpl \ + --enable-libdav1d \ + --enable-libmp3lame \ + --enable-libopus \ + --enable-libsnappy \ + --enable-libvorbis \ + --enable-libvpx \ + --enable-libwebp \ + --enable-libx264 \ + --enable-libx265 \ + --enable-shared \ + --enable-version3 \ + ; \ + make -j$(nproc); \ + make install; + # Create temporary bundler specific build layer from build layer -FROM build as bundler +FROM build AS bundler ARG TARGETPLATFORM @@ -174,7 +271,7 @@ RUN \ bundle install -j"$(nproc)"; # Create temporary node specific build layer from build layer -FROM build as yarn +FROM build AS yarn ARG TARGETPLATFORM @@ -191,7 +288,7 @@ RUN \ yarn workspaces focus --production @mastodon/mastodon; # Create temporary assets build layer from build layer -FROM build as precompiler +FROM build AS precompiler # Copy Mastodon sources into precompiler layer COPY . /opt/mastodon/ @@ -200,22 +297,22 @@ COPY . /opt/mastodon/ COPY --from=yarn /opt/mastodon /opt/mastodon/ COPY --from=bundler /opt/mastodon /opt/mastodon/ COPY --from=bundler /usr/local/bundle/ /usr/local/bundle/ +# Copy libvips components to layer for precompiler +COPY --from=libvips /usr/local/libvips/bin /usr/local/bin +COPY --from=libvips /usr/local/libvips/lib /usr/local/lib ARG TARGETPLATFORM RUN \ + ldconfig; \ # Use Ruby on Rails to create Mastodon assets - ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=precompile_placeholder \ - ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=precompile_placeholder \ - ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=precompile_placeholder \ - OTP_SECRET=precompile_placeholder \ - SECRET_KEY_BASE=precompile_placeholder \ + SECRET_KEY_BASE_DUMMY=1 \ bundle exec rails assets:precompile; \ # Cleanup temporary files rm -fr /opt/mastodon/tmp; # Prep final Mastodon Ruby layer -FROM ruby as mastodon +FROM ruby AS mastodon ARG TARGETPLATFORM @@ -229,12 +326,41 @@ RUN \ --mount=type=cache,id=yarn-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/yarn,sharing=locked \ # Apt update install non-dev versions of necessary components apt-get install -y --no-install-recommends \ - libssl3 \ - libpq5 \ + libexpat1 \ + libglib2.0-0 \ libicu72 \ libidn12 \ + libpq5 \ libreadline8 \ + libssl3 \ libyaml-0-2 \ + # libvips components + libcgif0 \ + libexif12 \ + libheif1 \ + libimagequant0 \ + libjpeg62-turbo \ + liblcms2-2 \ + liborc-0.4-0 \ + libspng0 \ + libtiff6 \ + libwebp7 \ + libwebpdemux2 \ + libwebpmux3 \ + # ffmpeg components + libdav1d6 \ + libmp3lame0 \ + libopencore-amrnb0 \ + libopencore-amrwb0 \ + libopus0 \ + libsnappy1v5 \ + libtheora0 \ + libvorbis0a \ + libvorbisenc2 \ + libvorbisfile3 \ + libvpx7 \ + libx264-164 \ + libx265-199 \ ; # Copy Mastodon sources into final layer @@ -245,9 +371,22 @@ COPY --from=precompiler /opt/mastodon/public/packs /opt/mastodon/public/packs COPY --from=precompiler /opt/mastodon/public/assets /opt/mastodon/public/assets # Copy bundler components to layer COPY --from=bundler /usr/local/bundle/ /usr/local/bundle/ +# Copy libvips components to layer +COPY --from=libvips /usr/local/libvips/bin /usr/local/bin +COPY --from=libvips /usr/local/libvips/lib /usr/local/lib +# Copy ffpmeg components to layer +COPY --from=ffmpeg /usr/local/ffmpeg/bin /usr/local/bin +COPY --from=ffmpeg /usr/local/ffmpeg/lib /usr/local/lib RUN \ -# Precompile bootsnap code for faster Rails startup + ldconfig; \ +# Smoketest media processors + vips -v; \ + ffmpeg -version; \ + ffprobe -version; + +RUN \ + # Precompile bootsnap code for faster Rails startup bundle exec bootsnap precompile --gemfile app/ lib/; RUN \ @@ -262,4 +401,4 @@ USER mastodon # Expose default Puma ports EXPOSE 3000 # Set container tini as default entry point -ENTRYPOINT ["/usr/bin/tini", "--"] \ No newline at end of file +ENTRYPOINT ["/usr/bin/tini", "--"] diff --git a/Gemfile b/Gemfile index a10613b30b..ef52d50cac 100644 --- a/Gemfile +++ b/Gemfile @@ -9,9 +9,6 @@ gem 'rack', '~> 2.2.7' gem 'rails', '~> 7.1.1' gem 'thor', '~> 1.2' -# For why irb is in the Gemfile, see: https://ruby.social/@st0012/111444685161478182 -gem 'irb', '~> 1.8' - gem 'dotenv' gem 'haml-rails', '~>2.0' gem 'pg', '~> 1.5' @@ -23,15 +20,16 @@ gem 'fog-core', '<= 2.4.0' gem 'fog-openstack', '~> 1.0', require: false gem 'kt-paperclip', '~> 7.2' gem 'md-paperclip-azure', '~> 2.2', require: false +gem 'ruby-vips', '~> 2.2', require: false gem 'active_model_serializers', '~> 0.10' gem 'addressable', '~> 2.8' gem 'bootsnap', '~> 1.18.0', require: false -gem 'browser' +gem 'browser', '< 6' # https://github.com/fnando/browser/issues/543 gem 'charlock_holmes', '~> 0.7.7' gem 'chewy', '~> 7.3' gem 'devise', '~> 4.9' -gem 'devise-two-factor', '~> 4.1' +gem 'devise-two-factor' group :pam_authentication, optional: true do gem 'devise_pam_authenticatable2', '~> 9.2' @@ -56,10 +54,11 @@ gem 'hiredis', '~> 0.6' gem 'htmlentities', '~> 4.3' gem 'http', '~> 5.2.0' gem 'http_accept_language', '~> 2.1' -gem 'httplog', '~> 1.6.2' -gem 'i18n', '1.14.1' # TODO: Remove version when resolved: https://github.com/glebm/i18n-tasks/issues/552 / https://github.com/ruby-i18n/i18n/pull/688 +gem 'httplog', '~> 1.7.0' +gem 'i18n' gem 'idn-ruby', require: 'idn' gem 'inline_svg' +gem 'irb', '~> 1.8' gem 'kaminari', '~> 1.2' gem 'link_header', '~> 0.0' gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock' @@ -70,7 +69,7 @@ gem 'oj', '~> 3.14' gem 'ox', '~> 2.14' gem 'parslet' gem 'premailer-rails' -gem 'public_suffix', '~> 5.0' +gem 'public_suffix', '~> 6.0' gem 'pundit', '~> 2.3' gem 'rack-attack', '~> 6.6' gem 'rack-cors', '~> 2.0', require: 'rack/cors' @@ -101,9 +100,30 @@ gem 'json-ld' gem 'json-ld-preloaded', '~> 3.2' gem 'rdf-normalize', '~> 0.5' -gem 'private_address_check', '~> 0.5' +gem 'opentelemetry-api', '~> 1.2.5' + +group :opentelemetry do + gem 'opentelemetry-exporter-otlp', '~> 0.28.0', require: false + gem 'opentelemetry-instrumentation-active_job', '~> 0.7.1', require: false + gem 'opentelemetry-instrumentation-active_model_serializers', '~> 0.20.1', require: false + gem 'opentelemetry-instrumentation-concurrent_ruby', '~> 0.21.2', require: false + gem 'opentelemetry-instrumentation-excon', '~> 0.22.0', require: false + gem 'opentelemetry-instrumentation-faraday', '~> 0.24.1', require: false + gem 'opentelemetry-instrumentation-http', '~> 0.23.2', require: false + gem 'opentelemetry-instrumentation-http_client', '~> 0.22.3', require: false + gem 'opentelemetry-instrumentation-net_http', '~> 0.22.4', require: false + gem 'opentelemetry-instrumentation-pg', '~> 0.27.1', require: false + gem 'opentelemetry-instrumentation-rack', '~> 0.24.1', require: false + gem 'opentelemetry-instrumentation-rails', '~> 0.31.0', require: false + gem 'opentelemetry-instrumentation-redis', '~> 0.25.3', require: false + gem 'opentelemetry-instrumentation-sidekiq', '~> 0.25.2', require: false + gem 'opentelemetry-sdk', '~> 1.4', require: false +end group :test do + # Enable usage of all available CPUs/cores during spec runs + gem 'flatware-rspec' + # Adds RSpec Error/Warning annotations to GitHub PRs on the Files tab gem 'rspec-github', '~> 2.4', require: false @@ -114,7 +134,7 @@ group :test do gem 'email_spec' # Extra RSpec extension methods and helpers for sidekiq - gem 'rspec-sidekiq', '~> 4.0' + gem 'rspec-sidekiq', '~> 5.0' # Browser integration testing gem 'capybara', '~> 3.39' @@ -150,6 +170,7 @@ group :development do gem 'rubocop-performance', require: false gem 'rubocop-rails', require: false gem 'rubocop-rspec', require: false + gem 'rubocop-rspec_rails', require: false # Annotates modules with schema gem 'annotate', '~> 3.2' @@ -160,7 +181,7 @@ group :development do # Preview mail in the browser gem 'letter_opener', '~> 1.8' - gem 'letter_opener_web', '~> 2.0' + gem 'letter_opener_web', '~> 3.0' # Security analysis CLI tools gem 'brakeman', '~> 6.0', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 7068f5dd55..98a3571731 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -10,35 +10,35 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (7.1.3.2) - actionpack (= 7.1.3.2) - activesupport (= 7.1.3.2) + actioncable (7.1.3.4) + actionpack (= 7.1.3.4) + activesupport (= 7.1.3.4) nio4r (~> 2.0) websocket-driver (>= 0.6.1) zeitwerk (~> 2.6) - actionmailbox (7.1.3.2) - actionpack (= 7.1.3.2) - activejob (= 7.1.3.2) - activerecord (= 7.1.3.2) - activestorage (= 7.1.3.2) - activesupport (= 7.1.3.2) + actionmailbox (7.1.3.4) + actionpack (= 7.1.3.4) + activejob (= 7.1.3.4) + activerecord (= 7.1.3.4) + activestorage (= 7.1.3.4) + activesupport (= 7.1.3.4) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.1.3.2) - actionpack (= 7.1.3.2) - actionview (= 7.1.3.2) - activejob (= 7.1.3.2) - activesupport (= 7.1.3.2) + actionmailer (7.1.3.4) + actionpack (= 7.1.3.4) + actionview (= 7.1.3.4) + activejob (= 7.1.3.4) + activesupport (= 7.1.3.4) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp rails-dom-testing (~> 2.2) - actionpack (7.1.3.2) - actionview (= 7.1.3.2) - activesupport (= 7.1.3.2) + actionpack (7.1.3.4) + actionview (= 7.1.3.4) + activesupport (= 7.1.3.4) nokogiri (>= 1.8.5) racc rack (>= 2.2.4) @@ -46,15 +46,15 @@ GEM rack-test (>= 0.6.3) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - actiontext (7.1.3.2) - actionpack (= 7.1.3.2) - activerecord (= 7.1.3.2) - activestorage (= 7.1.3.2) - activesupport (= 7.1.3.2) + actiontext (7.1.3.4) + actionpack (= 7.1.3.4) + activerecord (= 7.1.3.4) + activestorage (= 7.1.3.4) + activesupport (= 7.1.3.4) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.1.3.2) - activesupport (= 7.1.3.2) + actionview (7.1.3.4) + activesupport (= 7.1.3.4) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) @@ -64,22 +64,22 @@ GEM activemodel (>= 4.1) case_transform (>= 0.2) jsonapi-renderer (>= 0.1.1.beta1, < 0.3) - activejob (7.1.3.2) - activesupport (= 7.1.3.2) + activejob (7.1.3.4) + activesupport (= 7.1.3.4) globalid (>= 0.3.6) - activemodel (7.1.3.2) - activesupport (= 7.1.3.2) - activerecord (7.1.3.2) - activemodel (= 7.1.3.2) - activesupport (= 7.1.3.2) + activemodel (7.1.3.4) + activesupport (= 7.1.3.4) + activerecord (7.1.3.4) + activemodel (= 7.1.3.4) + activesupport (= 7.1.3.4) timeout (>= 0.4.0) - activestorage (7.1.3.2) - actionpack (= 7.1.3.2) - activejob (= 7.1.3.2) - activerecord (= 7.1.3.2) - activesupport (= 7.1.3.2) + activestorage (7.1.3.4) + actionpack (= 7.1.3.4) + activejob (= 7.1.3.4) + activerecord (= 7.1.3.4) + activesupport (= 7.1.3.4) marcel (~> 1.0) - activesupport (7.1.3.2) + activesupport (7.1.3.4) base64 bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) @@ -89,32 +89,30 @@ GEM minitest (>= 5.1) mutex_m tzinfo (~> 2.0) - addressable (2.8.6) - public_suffix (>= 2.0.2, < 6.0) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) aes_key_wrap (1.1.0) android_key_attestation (0.3.0) annotate (3.2.0) activerecord (>= 3.2, < 8.0) rake (>= 10.4, < 14.0) ast (2.4.2) - attr_encrypted (4.0.0) - encryptor (~> 3.0.0) attr_required (1.0.2) awrence (1.2.1) aws-eventstream (1.3.0) - aws-partitions (1.916.0) - aws-sdk-core (3.192.1) + aws-partitions (1.950.0) + aws-sdk-core (3.201.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.79.0) - aws-sdk-core (~> 3, >= 3.191.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.147.0) - aws-sdk-core (~> 3, >= 3.192.0) + aws-sdk-kms (1.88.0) + aws-sdk-core (~> 3, >= 3.201.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.156.0) + aws-sdk-core (~> 3, >= 3.201.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.8) + aws-sigv4 (~> 1.5) aws-sigv4 (1.8.0) aws-eventstream (~> 1, >= 1.0.2) azure-storage-blob (2.0.3) @@ -132,14 +130,7 @@ GEM erubi (>= 1.0.0) rack (>= 0.9.0) rouge (>= 1.0.0) - better_html (2.1.1) - actionview (>= 6.0) - activesupport (>= 6.0) - ast (~> 2.0) - erubi (~> 1.4) - parser (>= 2.4) - smart_properties - bigdecimal (3.1.7) + bigdecimal (3.1.8) bindata (2.5.0) binding_of_caller (1.0.1) debug_inspector (>= 1.2.0) @@ -152,7 +143,7 @@ GEM brpoplpush-redis_script (0.1.3) concurrent-ruby (~> 1.0, >= 1.0.5) redis (>= 1.0, < 6) - builder (3.2.4) + builder (3.3.0) bundler-audit (0.9.1) bundler (>= 1.2.0, < 3) thor (~> 1.0) @@ -168,16 +159,16 @@ GEM case_transform (0.2) activesupport cbor (0.5.9.8) - charlock_holmes (0.7.7) - chewy (7.5.1) + charlock_holmes (0.7.9) + chewy (7.6.0) activesupport (>= 5.2) - elasticsearch (>= 7.12.0, < 7.14.0) + elasticsearch (>= 7.14.0, < 8) elasticsearch-dsl chunky_png (1.4.0) climate_control (1.2.0) cocoon (1.2.15) color_diff (0.1) - concurrent-ruby (1.2.3) + concurrent-ruby (1.3.3) connection_pool (2.4.1) cose (1.3.0) cbor (~> 0.5.9) @@ -189,7 +180,7 @@ GEM css_parser (1.17.1) addressable csv (3.3.0) - database_cleaner-active_record (2.1.0) + database_cleaner-active_record (2.2.0) activerecord (>= 5.a) database_cleaner-core (~> 2.0.0) database_cleaner-core (2.0.1) @@ -204,9 +195,8 @@ GEM railties (>= 4.1.0) responders warden (~> 1.2.3) - devise-two-factor (4.1.1) + devise-two-factor (5.1.0) activesupport (~> 7.0) - attr_encrypted (>= 1.3, < 5, != 2) devise (~> 4.0) railties (~> 7.0) rotp (~> 6.0) @@ -218,31 +208,30 @@ GEM activerecord (>= 4.2, < 8) docile (1.4.0) domain_name (0.6.20240107) - doorkeeper (5.6.9) + doorkeeper (5.7.1) railties (>= 5) - dotenv (3.1.0) + dotenv (3.1.2) drb (2.2.1) ed25519 (1.3.0) - elasticsearch (7.13.3) - elasticsearch-api (= 7.13.3) - elasticsearch-transport (= 7.13.3) - elasticsearch-api (7.13.3) + elasticsearch (7.17.10) + elasticsearch-api (= 7.17.10) + elasticsearch-transport (= 7.17.10) + elasticsearch-api (7.17.10) multi_json elasticsearch-dsl (0.1.10) - elasticsearch-transport (7.13.3) - faraday (~> 1) + elasticsearch-transport (7.17.10) + faraday (>= 1, < 3) multi_json email_spec (2.2.2) htmlentities (~> 4.3.3) launchy (~> 2.1) mail (~> 2.7) - encryptor (3.0.0) - erubi (1.12.0) + erubi (1.13.0) et-orbi (1.2.11) tzinfo excon (0.110.0) fabrication (2.31.0) - faker (3.3.1) + faker (3.4.1) i18n (>= 1.8.11, < 2) faraday (1.10.3) faraday-em_http (~> 1.0) @@ -275,6 +264,11 @@ GEM ffi-compiler (1.3.2) ffi (>= 1.15.5) rake + flatware (2.3.2) + thor (< 2.0) + flatware-rspec (2.3.2) + flatware (= 2.3.2) + rspec (>= 3.6) fog-core (2.4.0) builder excon (~> 0.71) @@ -283,7 +277,7 @@ GEM fog-json (1.2.0) fog-core multi_json (~> 1.10) - fog-openstack (1.1.0) + fog-openstack (1.1.3) fog-core (~> 2.1) fog-json (>= 1.0) formatador (1.1.0) @@ -295,6 +289,9 @@ GEM ruby-progressbar (~> 1.4) globalid (1.2.1) activesupport (>= 6.1) + google-protobuf (3.25.3) + googleapis-common-protos-types (1.14.0) + google-protobuf (~> 3.18) haml (6.3.0) temple (>= 0.8.2) thor @@ -304,7 +301,7 @@ GEM activesupport (>= 5.1) haml (>= 4.0.6) railties (>= 5.1) - haml_lint (0.57.0) + haml_lint (0.58.0) haml (>= 5.0) parallel (~> 1.10) rainbow @@ -329,15 +326,14 @@ GEM http-form_data (2.3.0) http_accept_language (2.1.1) httpclient (2.8.3) - httplog (1.6.3) + httplog (1.7.0) rack (>= 2.0) rainbow (>= 2.0.0) - i18n (1.14.1) + i18n (1.14.5) concurrent-ruby (~> 1.0) - i18n-tasks (1.0.13) + i18n-tasks (1.0.14) activesupport (>= 4.0.2) ast (>= 2.1.0) - better_html (>= 1.0, < 3.0) erubi highline (>= 2.0.0) i18n @@ -350,8 +346,8 @@ GEM activesupport (>= 3.0) nokogiri (>= 1.6) io-console (0.7.2) - irb (1.12.0) - rdoc + irb (1.14.0) + rdoc (>= 4.0.0) reline (>= 0.4.2) jmespath (1.6.2) json (2.7.2) @@ -398,15 +394,16 @@ GEM addressable (~> 2.8) letter_opener (1.10.0) launchy (>= 2.2, < 4) - letter_opener_web (2.0.0) - actionmailer (>= 5.2) - letter_opener (~> 1.7) - railties (>= 5.2) + letter_opener_web (3.0.0) + actionmailer (>= 6.1) + letter_opener (~> 1.9) + railties (>= 6.1) rexml link_header (0.0.8) llhttp-ffi (0.5.0) ffi-compiler (~> 1.0) rake (~> 13.0) + logger (1.6.0) lograge (0.14.0) actionpack (>= 4) activesupport (>= 4) @@ -428,13 +425,13 @@ GEM addressable (~> 2.5) azure-storage-blob (~> 2.0.1) hashie (~> 5.0) - memory_profiler (1.0.1) + memory_profiler (1.0.2) mime-types (3.5.2) mime-types-data (~> 3.2015) - mime-types-data (3.2024.0305) + mime-types-data (3.2024.0604) mini_mime (1.1.5) - mini_portile2 (2.8.6) - minitest (5.22.3) + mini_portile2 (2.8.7) + minitest (5.24.1) msgpack (1.7.2) multi_json (1.15.0) multipart-post (2.4.0) @@ -443,7 +440,7 @@ GEM uri net-http-persistent (4.0.2) connection_pool (~> 2.2) - net-imap (0.4.10) + net-imap (0.4.12) date net-protocol net-ldap (0.19.0) @@ -453,8 +450,8 @@ GEM timeout net-smtp (0.5.0) net-protocol - nio4r (2.7.1) - nokogiri (1.16.4) + nio4r (2.7.3) + nokogiri (1.16.6) mini_portile2 (~> 2.8.2) racc (~> 1.4) nsa (0.3.0) @@ -462,7 +459,7 @@ GEM concurrent-ruby (~> 1.0, >= 1.0.2) sidekiq (>= 3.5) statsd-ruby (~> 1.4, >= 1.4.0) - oj (3.16.3) + oj (3.16.4) bigdecimal (>= 3.0) omniauth (2.1.2) hashie (>= 3.4.6) @@ -495,18 +492,106 @@ GEM openssl (3.2.0) openssl-signature_algorithm (1.3.0) openssl (> 2.0) + opentelemetry-api (1.2.5) + opentelemetry-common (0.20.1) + opentelemetry-api (~> 1.0) + opentelemetry-exporter-otlp (0.28.0) + google-protobuf (>= 3.18) + googleapis-common-protos-types (~> 1.3) + opentelemetry-api (~> 1.1) + opentelemetry-common (~> 0.20) + opentelemetry-sdk (~> 1.2) + opentelemetry-semantic_conventions + opentelemetry-helpers-sql-obfuscation (0.1.0) + opentelemetry-common (~> 0.20) + opentelemetry-instrumentation-action_mailer (0.1.0) + opentelemetry-api (~> 1.0) + opentelemetry-instrumentation-active_support (~> 0.1) + opentelemetry-instrumentation-base (~> 0.22.1) + opentelemetry-instrumentation-action_pack (0.9.0) + opentelemetry-api (~> 1.0) + opentelemetry-instrumentation-base (~> 0.22.1) + opentelemetry-instrumentation-rack (~> 0.21) + opentelemetry-instrumentation-action_view (0.7.0) + opentelemetry-api (~> 1.0) + opentelemetry-instrumentation-active_support (~> 0.1) + opentelemetry-instrumentation-base (~> 0.22.1) + opentelemetry-instrumentation-active_job (0.7.2) + opentelemetry-api (~> 1.0) + opentelemetry-instrumentation-base (~> 0.22.1) + opentelemetry-instrumentation-active_model_serializers (0.20.1) + opentelemetry-api (~> 1.0) + opentelemetry-instrumentation-base (~> 0.22.1) + opentelemetry-instrumentation-active_record (0.7.2) + opentelemetry-api (~> 1.0) + opentelemetry-instrumentation-base (~> 0.22.1) + opentelemetry-instrumentation-active_support (0.6.0) + opentelemetry-api (~> 1.0) + opentelemetry-instrumentation-base (~> 0.22.1) + opentelemetry-instrumentation-base (0.22.3) + opentelemetry-api (~> 1.0) + opentelemetry-registry (~> 0.1) + opentelemetry-instrumentation-concurrent_ruby (0.21.3) + opentelemetry-api (~> 1.0) + opentelemetry-instrumentation-base (~> 0.22.1) + opentelemetry-instrumentation-excon (0.22.3) + opentelemetry-api (~> 1.0) + opentelemetry-instrumentation-base (~> 0.22.1) + opentelemetry-instrumentation-faraday (0.24.5) + opentelemetry-api (~> 1.0) + opentelemetry-instrumentation-base (~> 0.22.1) + opentelemetry-instrumentation-http (0.23.3) + opentelemetry-api (~> 1.0) + opentelemetry-instrumentation-base (~> 0.22.1) + opentelemetry-instrumentation-http_client (0.22.6) + opentelemetry-api (~> 1.0) + opentelemetry-instrumentation-base (~> 0.22.1) + opentelemetry-instrumentation-net_http (0.22.6) + opentelemetry-api (~> 1.0) + opentelemetry-instrumentation-base (~> 0.22.1) + opentelemetry-instrumentation-pg (0.27.3) + opentelemetry-api (~> 1.0) + opentelemetry-helpers-sql-obfuscation + opentelemetry-instrumentation-base (~> 0.22.1) + opentelemetry-instrumentation-rack (0.24.5) + opentelemetry-api (~> 1.0) + opentelemetry-instrumentation-base (~> 0.22.1) + opentelemetry-instrumentation-rails (0.31.0) + opentelemetry-api (~> 1.0) + opentelemetry-instrumentation-action_mailer (~> 0.1.0) + opentelemetry-instrumentation-action_pack (~> 0.9.0) + opentelemetry-instrumentation-action_view (~> 0.7.0) + opentelemetry-instrumentation-active_job (~> 0.7.0) + opentelemetry-instrumentation-active_record (~> 0.7.0) + opentelemetry-instrumentation-active_support (~> 0.6.0) + opentelemetry-instrumentation-base (~> 0.22.1) + opentelemetry-instrumentation-redis (0.25.6) + opentelemetry-api (~> 1.0) + opentelemetry-instrumentation-base (~> 0.22.1) + opentelemetry-instrumentation-sidekiq (0.25.6) + opentelemetry-api (~> 1.0) + opentelemetry-instrumentation-base (~> 0.22.1) + opentelemetry-registry (0.3.1) + opentelemetry-api (~> 1.1) + opentelemetry-sdk (1.4.1) + opentelemetry-api (~> 1.1) + opentelemetry-common (~> 0.20) + opentelemetry-registry (~> 0.2) + opentelemetry-semantic_conventions + opentelemetry-semantic_conventions (1.10.0) + opentelemetry-api (~> 1.0) orm_adapter (0.5.0) ox (2.14.18) - parallel (1.24.0) - parser (3.3.0.5) + parallel (1.25.1) + parser (3.3.4.0) ast (~> 2.4.1) racc parslet (2.0.0) pastel (0.8.0) tty-color (~> 0.5) pg (1.5.6) - pghero (3.4.1) - activerecord (>= 6) + pghero (3.6.0) + activerecord (>= 6.1) premailer (1.23.0) addressable css_parser (>= 1.12.0) @@ -515,21 +600,20 @@ GEM actionmailer (>= 3) net-smtp premailer (~> 1.7, >= 1.7.9) - private_address_check (0.5.0) - propshaft (0.8.0) + propshaft (0.9.0) actionpack (>= 7.0.0) activesupport (>= 7.0.0) rack railties (>= 7.0.0) psych (5.1.2) stringio - public_suffix (5.0.5) + public_suffix (6.0.0) puma (6.4.2) nio4r (~> 2.0) - pundit (2.3.1) + pundit (2.3.2) activesupport (>= 3.0.0) raabro (1.4.0) - racc (1.7.3) + racc (1.8.0) rack (2.2.9) rack-attack (6.7.0) rack (>= 1.0, < 4) @@ -553,20 +637,20 @@ GEM rackup (1.0.0) rack (< 3) webrick - rails (7.1.3.2) - actioncable (= 7.1.3.2) - actionmailbox (= 7.1.3.2) - actionmailer (= 7.1.3.2) - actionpack (= 7.1.3.2) - actiontext (= 7.1.3.2) - actionview (= 7.1.3.2) - activejob (= 7.1.3.2) - activemodel (= 7.1.3.2) - activerecord (= 7.1.3.2) - activestorage (= 7.1.3.2) - activesupport (= 7.1.3.2) + rails (7.1.3.4) + actioncable (= 7.1.3.4) + actionmailbox (= 7.1.3.4) + actionmailer (= 7.1.3.4) + actionpack (= 7.1.3.4) + actiontext (= 7.1.3.4) + actionview (= 7.1.3.4) + activejob (= 7.1.3.4) + activemodel (= 7.1.3.4) + activerecord (= 7.1.3.4) + activestorage (= 7.1.3.4) + activesupport (= 7.1.3.4) bundler (>= 1.15.0) - railties (= 7.1.3.2) + railties (= 7.1.3.4) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1) @@ -581,9 +665,9 @@ GEM rails-i18n (7.0.9) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 8) - railties (7.1.3.2) - actionpack (= 7.1.3.2) - activesupport (= 7.1.3.2) + railties (7.1.3.4) + actionpack (= 7.1.3.4) + activesupport (= 7.1.3.4) irb rackup (>= 1.0.0) rake (>= 12.2) @@ -596,7 +680,7 @@ GEM link_header (~> 0.0, >= 0.0.8) rdf-normalize (0.7.0) rdf (~> 3.3) - rdoc (6.6.3.1) + rdoc (6.7.0) psych (>= 4.0.0) redcarpet (3.6.0) redis (4.8.1) @@ -604,15 +688,16 @@ GEM redis (>= 4) redlock (1.3.2) redis (>= 3.0.0, < 6.0) - regexp_parser (2.9.0) - reline (0.5.2) + regexp_parser (2.9.2) + reline (0.5.9) io-console (~> 0.5) request_store (1.6.0) rack (>= 1.4) responders (3.1.1) actionpack (>= 5.2) railties (>= 5.2) - rexml (3.2.6) + rexml (3.3.1) + strscan rotp (6.3.0) rouge (4.2.1) rpam2 (4.0.2) @@ -620,17 +705,21 @@ GEM chunky_png (~> 1.0) rqrcode_core (~> 1.0) rqrcode_core (1.2.0) + rspec (3.13.0) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) rspec-core (3.13.0) rspec-support (~> 3.13.0) - rspec-expectations (3.13.0) + rspec-expectations (3.13.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-github (2.4.0) rspec-core (~> 3.0) - rspec-mocks (3.13.0) + rspec-mocks (3.13.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-rails (6.1.2) + rspec-rails (6.1.3) actionpack (>= 6.1) activesupport (>= 6.1) railties (>= 6.1) @@ -638,63 +727,62 @@ GEM rspec-expectations (~> 3.13) rspec-mocks (~> 3.13) rspec-support (~> 3.13) - rspec-sidekiq (4.2.0) + rspec-sidekiq (5.0.0) rspec-core (~> 3.0) rspec-expectations (~> 3.0) rspec-mocks (~> 3.0) sidekiq (>= 5, < 8) rspec-support (3.13.1) - rubocop (1.63.3) + rubocop (1.65.0) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8, < 3.0) + regexp_parser (>= 2.4, < 3.0) rexml (>= 3.2.5, < 4.0) rubocop-ast (>= 1.31.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.31.2) - parser (>= 3.3.0.4) - rubocop-capybara (2.20.0) + rubocop-ast (1.31.3) + parser (>= 3.3.1.0) + rubocop-capybara (2.21.0) rubocop (~> 1.41) - rubocop-factory_bot (2.25.1) - rubocop (~> 1.41) - rubocop-performance (1.21.0) + rubocop-performance (1.21.1) rubocop (>= 1.48.1, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) - rubocop-rails (2.24.1) + rubocop-rails (2.25.1) activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.33.0, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) - rubocop-rspec (2.29.1) - rubocop (~> 1.40) - rubocop-capybara (~> 2.17) - rubocop-factory_bot (~> 2.22) - rubocop-rspec_rails (~> 2.28) - rubocop-rspec_rails (2.28.3) - rubocop (~> 1.40) + rubocop-rspec (3.0.3) + rubocop (~> 1.61) + rubocop-rspec_rails (2.30.0) + rubocop (~> 1.61) + rubocop-rspec (~> 3, >= 3.0.1) ruby-prof (1.7.0) ruby-progressbar (1.13.0) ruby-saml (1.16.0) nokogiri (>= 1.13.10) rexml + ruby-vips (2.2.1) + ffi (~> 1.12) ruby2_keywords (0.0.5) rubyzip (2.3.2) rufus-scheduler (3.9.1) fugit (~> 1.1, >= 1.1.6) safety_net_attestation (0.4.0) jwt (~> 2.0) - sanitize (6.1.0) + sanitize (6.1.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) scenic (1.8.0) activerecord (>= 4.0.0) railties (>= 4.0.0) - selenium-webdriver (4.19.0) + selenium-webdriver (4.22.0) base64 (~> 0.2) + logger (~> 1.4) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) @@ -705,10 +793,10 @@ GEM redis (>= 4.5.0, < 5) sidekiq-bulk (0.2.0) sidekiq - sidekiq-scheduler (5.0.3) + sidekiq-scheduler (5.0.5) rufus-scheduler (~> 3.2) sidekiq (>= 6, < 8) - tilt (>= 1.4.0) + tilt (>= 1.4.0, < 3) sidekiq-unique-jobs (7.1.33) brpoplpush-redis_script (> 0.1.1, <= 2.0.0) concurrent-ruby (~> 1.0, >= 1.0.5) @@ -717,7 +805,7 @@ GEM thor (>= 0.20, < 3.0) simple-navigation (4.4.0) activesupport (>= 2.3.2) - simple_form (5.3.0) + simple_form (5.3.1) actionpack (>= 5.2) activemodel (>= 5.2) simplecov (0.22.0) @@ -727,14 +815,14 @@ GEM simplecov-html (0.12.3) simplecov-lcov (0.8.0) simplecov_json_formatter (0.1.4) - smart_properties (1.17.0) stackprof (0.2.26) statsd-ruby (1.5.0) stoplight (4.1.0) redlock (~> 1.0) - stringio (3.1.0) + stringio (3.1.1) strong_migrations (1.8.0) activerecord (>= 5.2) + strscan (3.1.0) swd (1.3.0) activesupport (>= 3) attr_required (>= 0.0.5) @@ -745,7 +833,7 @@ GEM unicode-display_width (>= 1.1.1, < 3) terrapin (1.0.1) climate_control - test-prof (1.3.3) + test-prof (1.3.3.1) thor (1.3.1) tilt (2.3.0) timeout (0.4.1) @@ -795,7 +883,7 @@ GEM webfinger (1.2.0) activesupport httpclient (>= 2.4) - webmock (3.23.0) + webmock (3.23.1) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) @@ -813,7 +901,7 @@ GEM xorcist (1.1.3) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.13) + zeitwerk (2.6.16) PLATFORMS ruby @@ -828,7 +916,7 @@ DEPENDENCIES blurhash (~> 0.1) bootsnap (~> 1.18.0) brakeman (~> 6.0) - browser + browser (< 6) bundler-audit (~> 0.9) capybara (~> 3.39) charlock_holmes (~> 0.7.7) @@ -842,7 +930,7 @@ DEPENDENCIES database_cleaner-active_record debug (~> 1.8) devise (~> 4.9) - devise-two-factor (~> 4.1) + devise-two-factor devise_pam_authenticatable2 (~> 9.2) discard (~> 1.2) doorkeeper (~> 5.6) @@ -853,6 +941,7 @@ DEPENDENCIES faker (~> 3.2) fast_blank (~> 1.0) fastimage + flatware-rspec fog-core (<= 2.4.0) fog-openstack (~> 1.0) fuubar (~> 2.5) @@ -863,8 +952,8 @@ DEPENDENCIES htmlentities (~> 4.3) http (~> 5.2.0) http_accept_language (~> 2.1) - httplog (~> 1.6.2) - i18n (= 1.14.1) + httplog (~> 1.7.0) + i18n i18n-tasks (~> 1.0) idn-ruby inline_svg @@ -875,7 +964,7 @@ DEPENDENCIES kaminari (~> 1.2) kt-paperclip (~> 7.2) letter_opener (~> 1.8) - letter_opener_web (~> 2.0) + letter_opener_web (~> 3.0) link_header (~> 0.0) lograge (~> 0.12) mail (~> 2.8) @@ -893,14 +982,29 @@ DEPENDENCIES omniauth-rails_csrf_protection (~> 1.0) omniauth-saml (~> 2.0) omniauth_openid_connect (~> 0.6.1) + opentelemetry-api (~> 1.2.5) + opentelemetry-exporter-otlp (~> 0.28.0) + opentelemetry-instrumentation-active_job (~> 0.7.1) + opentelemetry-instrumentation-active_model_serializers (~> 0.20.1) + opentelemetry-instrumentation-concurrent_ruby (~> 0.21.2) + opentelemetry-instrumentation-excon (~> 0.22.0) + opentelemetry-instrumentation-faraday (~> 0.24.1) + opentelemetry-instrumentation-http (~> 0.23.2) + opentelemetry-instrumentation-http_client (~> 0.22.3) + opentelemetry-instrumentation-net_http (~> 0.22.4) + opentelemetry-instrumentation-pg (~> 0.27.1) + opentelemetry-instrumentation-rack (~> 0.24.1) + opentelemetry-instrumentation-rails (~> 0.31.0) + opentelemetry-instrumentation-redis (~> 0.25.3) + opentelemetry-instrumentation-sidekiq (~> 0.25.2) + opentelemetry-sdk (~> 1.4) ox (~> 2.14) parslet pg (~> 1.5) pghero premailer-rails - private_address_check (~> 0.5) propshaft - public_suffix (~> 5.0) + public_suffix (~> 6.0) puma (~> 6.3) pundit (~> 2.3) rack (~> 2.2.7) @@ -917,14 +1021,16 @@ DEPENDENCIES rqrcode (~> 2.2) rspec-github (~> 2.4) rspec-rails (~> 6.0) - rspec-sidekiq (~> 4.0) + rspec-sidekiq (~> 5.0) rubocop rubocop-capybara rubocop-performance rubocop-rails rubocop-rspec + rubocop-rspec_rails ruby-prof ruby-progressbar (~> 1.13) + ruby-vips (~> 2.2) rubyzip (~> 2.3) sanitize (~> 6.0) scenic (~> 1.7) @@ -952,7 +1058,7 @@ DEPENDENCIES xorcist (~> 1.1) RUBY VERSION - ruby 3.2.3p157 + ruby 3.3.2p78 BUNDLED WITH - 2.5.9 + 2.5.11 diff --git a/README.md b/README.md index b57535f578..f624b59704 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Should anything break, open `https://masto.example.com/logout.html` or clear loc --- -# Chuckya +# Chuckya Chuckya is a close-to-upstream soft fork of Mastodon Glitch Edition (more commonly known as glitch-soc) that aims to introduce more experimental features/fixes with the goal of making the overall experience more enjoyable. Although it's mainly developed for and used on the [wetdry.world](https://wetdry.world) instance, it can be deployed by any server admin as a drop-in, backwards-compatible replacement for Mastodon. diff --git a/SECURITY.md b/SECURITY.md index 81472b01b4..156954ce02 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,7 +2,7 @@ If you believe you've identified a security vulnerability in Mastodon (a bug that allows something to happen that shouldn't be possible), you can either: -- open a [Github security issue on the Mastodon project](https://github.com/mastodon/mastodon/security/advisories/new) +- open a [GitHub security issue on the Mastodon project](https://github.com/mastodon/mastodon/security/advisories/new) - reach us at You should _not_ report such issues on public GitHub issues or in other public spaces to give us time to publish a fix for the issue without exposing Mastodon's users to increased risk. diff --git a/Vagrantfile b/Vagrantfile index 8a95e91f36..89f5536edc 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -151,6 +151,12 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| vb.customize ["modifyvm", :id, "--nictype2", "virtio"] end + config.vm.provider :libvirt do |libvirt| + libvirt.cpus = 3 + libvirt.memory = 8192 + end + + # This uses the vagrant-hostsupdater plugin, and lets you # access the development site at http://mastodon.local. # If you change it, also change it in .env.vagrant before provisioning diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index af00038eae..685b02ae6d 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -25,7 +25,7 @@ class AccountsController < ApplicationController limit = params[:limit].present? ? [params[:limit].to_i, PAGE_SIZE_MAX].min : PAGE_SIZE @statuses = filtered_statuses.without_reblogs.limit(limit) - @statuses = cache_collection(@statuses, Status) + @statuses = preload_collection(@statuses, Status) end format.json do diff --git a/app/controllers/activitypub/collections_controller.rb b/app/controllers/activitypub/collections_controller.rb index 57480db8d8..15985c7f65 100644 --- a/app/controllers/activitypub/collections_controller.rb +++ b/app/controllers/activitypub/collections_controller.rb @@ -18,7 +18,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController def set_items case params[:id] when 'featured' - @items = for_signed_account { cache_collection(@account.pinned_statuses.not_local_only, Status) } + @items = for_signed_account { preload_collection(@account.pinned_statuses.not_local_only, Status) } @items = @items.map { |item| item.distributable? ? item : ActivityPub::TagManager.instance.uri_for(item) } when 'tags' @items = for_signed_account { @account.featured_tags } diff --git a/app/controllers/activitypub/outboxes_controller.rb b/app/controllers/activitypub/outboxes_controller.rb index 8079e011dd..b8baf64e1a 100644 --- a/app/controllers/activitypub/outboxes_controller.rb +++ b/app/controllers/activitypub/outboxes_controller.rb @@ -60,7 +60,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController def set_statuses return unless page_requested? - @statuses = cache_collection_paginated_by_id( + @statuses = preload_collection_paginated_by_id( AccountStatusesFilter.new(@account, signed_request_account).results, Status, LIMIT, diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb index d3be7817ff..9beb8fde6b 100644 --- a/app/controllers/admin/accounts_controller.rb +++ b/app/controllers/admin/accounts_controller.rb @@ -128,7 +128,7 @@ module Admin def unblock_email authorize @account, :unblock_email? - CanonicalEmailBlock.matching_account(@account).delete_all + CanonicalEmailBlock.where(reference_account: @account).delete_all log_action :unblock_email, @account diff --git a/app/controllers/admin/domain_allows_controller.rb b/app/controllers/admin/domain_allows_controller.rb index 31be1978bb..b0f139e3a8 100644 --- a/app/controllers/admin/domain_allows_controller.rb +++ b/app/controllers/admin/domain_allows_controller.rb @@ -25,6 +25,8 @@ class Admin::DomainAllowsController < Admin::BaseController def destroy authorize @domain_allow, :destroy? UnallowDomainService.new.call(@domain_allow) + log_action :destroy, @domain_allow + redirect_to admin_instances_path, notice: I18n.t('admin.domain_allows.destroyed_msg') end diff --git a/app/controllers/admin/domain_blocks_controller.rb b/app/controllers/admin/domain_blocks_controller.rb index 325b33df80..16a8cb9eea 100644 --- a/app/controllers/admin/domain_blocks_controller.rb +++ b/app/controllers/admin/domain_blocks_controller.rb @@ -4,6 +4,18 @@ module Admin class DomainBlocksController < BaseController before_action :set_domain_block, only: [:destroy, :edit, :update] + PERMITTED_PARAMS = %i( + domain + obfuscate + private_comment + public_comment + reject_media + reject_reports + severity + ).freeze + + PERMITTED_UPDATE_PARAMS = PERMITTED_PARAMS.without(:domain).freeze + def batch authorize :domain_block, :create? @form = Form::DomainBlockBatch.new(form_domain_block_batch_params.merge(current_account: current_account, action: action_from_button)) @@ -88,11 +100,17 @@ module Admin end def update_params - params.require(:domain_block).permit(:severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate) + params + .require(:domain_block) + .slice(*PERMITTED_UPDATE_PARAMS) + .permit(*PERMITTED_UPDATE_PARAMS) end def resource_params - params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate) + params + .require(:domain_block) + .slice(*PERMITTED_PARAMS) + .permit(*PERMITTED_PARAMS) end def form_domain_block_batch_params diff --git a/app/controllers/admin/site_uploads_controller.rb b/app/controllers/admin/site_uploads_controller.rb index a5d2cf41cf..96e61cf6bb 100644 --- a/app/controllers/admin/site_uploads_controller.rb +++ b/app/controllers/admin/site_uploads_controller.rb @@ -9,7 +9,7 @@ module Admin @site_upload.destroy! - redirect_to admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg') + redirect_back fallback_location: admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg') end private diff --git a/app/controllers/api/v1/accounts/credentials_controller.rb b/app/controllers/api/v1/accounts/credentials_controller.rb index e8f712457e..a378425183 100644 --- a/app/controllers/api/v1/accounts/credentials_controller.rb +++ b/app/controllers/api/v1/accounts/credentials_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class Api::V1::Accounts::CredentialsController < Api::BaseController - before_action -> { doorkeeper_authorize! :read, :'read:accounts', :'read:me' }, except: [:update] + before_action -> { doorkeeper_authorize! :profile, :read, :'read:accounts' }, except: [:update] before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, only: [:update] before_action :require_user! diff --git a/app/controllers/api/v1/accounts/follower_accounts_controller.rb b/app/controllers/api/v1/accounts/follower_accounts_controller.rb index 449866fa55..3f2ecb892d 100644 --- a/app/controllers/api/v1/accounts/follower_accounts_controller.rb +++ b/app/controllers/api/v1/accounts/follower_accounts_controller.rb @@ -60,8 +60,4 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController def records_continue? @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) end - - def pagination_params(core_params) - params.slice(:limit).permit(:limit).merge(core_params) - end end diff --git a/app/controllers/api/v1/accounts/following_accounts_controller.rb b/app/controllers/api/v1/accounts/following_accounts_controller.rb index c4f4313f8f..7c16a3487e 100644 --- a/app/controllers/api/v1/accounts/following_accounts_controller.rb +++ b/app/controllers/api/v1/accounts/following_accounts_controller.rb @@ -60,8 +60,4 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController def records_continue? @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) end - - def pagination_params(core_params) - params.slice(:limit).permit(:limit).merge(core_params) - end end diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb index 35ea9c8ec1..c42f27776c 100644 --- a/app/controllers/api/v1/accounts/statuses_controller.rb +++ b/app/controllers/api/v1/accounts/statuses_controller.rb @@ -19,11 +19,11 @@ class Api::V1::Accounts::StatusesController < Api::BaseController end def load_statuses - @account.unavailable? ? [] : cached_account_statuses + @account.unavailable? ? [] : preloaded_account_statuses end - def cached_account_statuses - cache_collection_paginated_by_id( + def preloaded_account_statuses + preload_collection_paginated_by_id( AccountStatusesFilter.new(@account, current_account, params).results, Status, limit_param(DEFAULT_STATUSES_LIMIT), diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index 23fc85b475..84b604b305 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -9,16 +9,22 @@ class Api::V1::AccountsController < Api::BaseController before_action -> { doorkeeper_authorize! :follow, :write, :'write:blocks' }, only: [:block, :unblock] before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, only: [:create] - before_action :require_user!, except: [:show, :create] - before_action :set_account, except: [:create] - before_action :check_account_approval, except: [:create] - before_action :check_account_confirmation, except: [:create] + before_action :require_user!, except: [:index, :show, :create] + before_action :set_account, except: [:index, :create] + before_action :set_accounts, only: [:index] + before_action :check_account_approval, except: [:index, :create] + before_action :check_account_confirmation, except: [:index, :create] before_action :check_enabled_registrations, only: [:create] + before_action :check_accounts_limit, only: [:index] skip_before_action :require_authenticated_user!, only: :create override_rate_limit_headers :follow, family: :follows + def index + render json: @accounts, each_serializer: REST::AccountSerializer + end + def show cache_if_unauthenticated! render json: @account, serializer: REST::AccountSerializer @@ -79,6 +85,10 @@ class Api::V1::AccountsController < Api::BaseController @account = Account.find(params[:id]) end + def set_accounts + @accounts = Account.where(id: account_ids).without_unapproved + end + def check_account_approval raise(ActiveRecord::RecordNotFound) if @account.local? && @account.user_pending? end @@ -87,10 +97,22 @@ class Api::V1::AccountsController < Api::BaseController raise(ActiveRecord::RecordNotFound) if @account.local? && !@account.user_confirmed? end + def check_accounts_limit + raise(Mastodon::ValidationError) if account_ids.size > DEFAULT_ACCOUNTS_LIMIT + end + def relationships(**options) AccountRelationshipsPresenter.new([@account], current_user.account_id, **options) end + def account_ids + Array(accounts_params[:id]).uniq.map(&:to_i) + end + + def accounts_params + params.permit(id: []) + end + def account_params params.permit(:username, :email, :password, :agreement, :locale, :reason, :time_zone, :invite_code) end diff --git a/app/controllers/api/v1/admin/canonical_email_blocks_controller.rb b/app/controllers/api/v1/admin/canonical_email_blocks_controller.rb index 701f668de6..c144a9e0f9 100644 --- a/app/controllers/api/v1/admin/canonical_email_blocks_controller.rb +++ b/app/controllers/api/v1/admin/canonical_email_blocks_controller.rb @@ -16,8 +16,6 @@ class Api::V1::Admin::CanonicalEmailBlocksController < Api::BaseController after_action :verify_authorized after_action :insert_pagination_headers, only: :index - PAGINATION_PARAMS = %i(limit).freeze - def index authorize :canonical_email_block, :index? render json: @canonical_email_blocks, each_serializer: REST::Admin::CanonicalEmailBlockSerializer @@ -80,8 +78,4 @@ class Api::V1::Admin::CanonicalEmailBlocksController < Api::BaseController def records_continue? @canonical_email_blocks.size == limit_param(LIMIT) end - - def pagination_params(core_params) - params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params) - end end diff --git a/app/controllers/api/v1/admin/domain_allows_controller.rb b/app/controllers/api/v1/admin/domain_allows_controller.rb index a7ae84e306..9801d832b8 100644 --- a/app/controllers/api/v1/admin/domain_allows_controller.rb +++ b/app/controllers/api/v1/admin/domain_allows_controller.rb @@ -14,8 +14,6 @@ class Api::V1::Admin::DomainAllowsController < Api::BaseController after_action :verify_authorized after_action :insert_pagination_headers, only: :index - PAGINATION_PARAMS = %i(limit).freeze - def index authorize :domain_allow, :index? render json: @domain_allows, each_serializer: REST::Admin::DomainAllowSerializer @@ -77,10 +75,6 @@ class Api::V1::Admin::DomainAllowsController < Api::BaseController @domain_allows.size == limit_param(LIMIT) end - def pagination_params(core_params) - params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params) - end - def resource_params params.permit(:domain) end diff --git a/app/controllers/api/v1/admin/domain_blocks_controller.rb b/app/controllers/api/v1/admin/domain_blocks_controller.rb index b589d277d5..a20a4a9c7f 100644 --- a/app/controllers/api/v1/admin/domain_blocks_controller.rb +++ b/app/controllers/api/v1/admin/domain_blocks_controller.rb @@ -14,8 +14,6 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController after_action :verify_authorized after_action :insert_pagination_headers, only: :index - PAGINATION_PARAMS = %i(limit).freeze - def index authorize :domain_block, :index? render json: @domain_blocks, each_serializer: REST::Admin::DomainBlockSerializer @@ -29,10 +27,11 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController def create authorize :domain_block, :create? + @domain_block = DomainBlock.new(resource_params) existing_domain_block = resource_params[:domain].present? ? DomainBlock.rule_for(resource_params[:domain]) : nil - return render json: existing_domain_block, serializer: REST::Admin::ExistingDomainBlockErrorSerializer, status: 422 if existing_domain_block.present? + return render json: existing_domain_block, serializer: REST::Admin::ExistingDomainBlockErrorSerializer, status: 422 if conflicts_with_existing_block?(@domain_block, existing_domain_block) - @domain_block = DomainBlock.create!(resource_params) + @domain_block.save! DomainBlockWorker.perform_async(@domain_block.id) log_action :create, @domain_block render json: @domain_block, serializer: REST::Admin::DomainBlockSerializer @@ -55,6 +54,10 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController private + def conflicts_with_existing_block?(domain_block, existing_domain_block) + existing_domain_block.present? && (existing_domain_block.domain == TagManager.instance.normalize_domain(domain_block.domain) || !domain_block.stricter_than?(existing_domain_block)) + end + def set_domain_blocks @domain_blocks = filtered_domain_blocks.order(id: :desc).to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id)) end @@ -88,10 +91,6 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController @domain_blocks.size == limit_param(LIMIT) end - def pagination_params(core_params) - params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params) - end - def resource_params params.permit(:domain, :severity, :reject_media, :reject_reports, :private_comment, :public_comment, :obfuscate) end diff --git a/app/controllers/api/v1/admin/email_domain_blocks_controller.rb b/app/controllers/api/v1/admin/email_domain_blocks_controller.rb index bdedb9d040..e7bd804e36 100644 --- a/app/controllers/api/v1/admin/email_domain_blocks_controller.rb +++ b/app/controllers/api/v1/admin/email_domain_blocks_controller.rb @@ -14,10 +14,6 @@ class Api::V1::Admin::EmailDomainBlocksController < Api::BaseController after_action :verify_authorized after_action :insert_pagination_headers, only: :index - PAGINATION_PARAMS = %i( - limit - ).freeze - def index authorize :email_domain_block, :index? render json: @email_domain_blocks, each_serializer: REST::Admin::EmailDomainBlockSerializer @@ -73,8 +69,4 @@ class Api::V1::Admin::EmailDomainBlocksController < Api::BaseController def records_continue? @email_domain_blocks.size == limit_param(LIMIT) end - - def pagination_params(core_params) - params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params) - end end diff --git a/app/controllers/api/v1/admin/ip_blocks_controller.rb b/app/controllers/api/v1/admin/ip_blocks_controller.rb index 3625781149..e132a3a87d 100644 --- a/app/controllers/api/v1/admin/ip_blocks_controller.rb +++ b/app/controllers/api/v1/admin/ip_blocks_controller.rb @@ -14,10 +14,6 @@ class Api::V1::Admin::IpBlocksController < Api::BaseController after_action :verify_authorized after_action :insert_pagination_headers, only: :index - PAGINATION_PARAMS = %i( - limit - ).freeze - def index authorize :ip_block, :index? render json: @ip_blocks, each_serializer: REST::Admin::IpBlockSerializer @@ -78,8 +74,4 @@ class Api::V1::Admin::IpBlocksController < Api::BaseController def records_continue? @ip_blocks.size == limit_param(LIMIT) end - - def pagination_params(core_params) - params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params) - end end diff --git a/app/controllers/api/v1/admin/tags_controller.rb b/app/controllers/api/v1/admin/tags_controller.rb index c754980720..283383acb4 100644 --- a/app/controllers/api/v1/admin/tags_controller.rb +++ b/app/controllers/api/v1/admin/tags_controller.rb @@ -12,7 +12,13 @@ class Api::V1::Admin::TagsController < Api::BaseController after_action :verify_authorized LIMIT = 100 - PAGINATION_PARAMS = %i(limit).freeze + + PERMITTED_PARAMS = %i( + display_name + listable + trendable + usable + ).freeze def index authorize :tag, :index? @@ -41,7 +47,9 @@ class Api::V1::Admin::TagsController < Api::BaseController end def tag_params - params.permit(:display_name, :trendable, :usable, :listable) + params + .slice(*PERMITTED_PARAMS) + .permit(*PERMITTED_PARAMS) end def next_path @@ -59,8 +67,4 @@ class Api::V1::Admin::TagsController < Api::BaseController def records_continue? @tags.size == limit_param(LIMIT) end - - def pagination_params(core_params) - params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params) - end end diff --git a/app/controllers/api/v1/admin/trends/links/preview_card_providers_controller.rb b/app/controllers/api/v1/admin/trends/links/preview_card_providers_controller.rb index 8bb5e22716..2b0f39b98f 100644 --- a/app/controllers/api/v1/admin/trends/links/preview_card_providers_controller.rb +++ b/app/controllers/api/v1/admin/trends/links/preview_card_providers_controller.rb @@ -12,8 +12,6 @@ class Api::V1::Admin::Trends::Links::PreviewCardProvidersController < Api::BaseC after_action :verify_authorized after_action :insert_pagination_headers, only: :index - PAGINATION_PARAMS = %i(limit).freeze - def index authorize :preview_card_provider, :index? @@ -57,8 +55,4 @@ class Api::V1::Admin::Trends::Links::PreviewCardProvidersController < Api::BaseC def records_continue? @providers.size == limit_param(LIMIT) end - - def pagination_params(core_params) - params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params) - end end diff --git a/app/controllers/api/v1/apps/credentials_controller.rb b/app/controllers/api/v1/apps/credentials_controller.rb index 6256bed64c..29ab920383 100644 --- a/app/controllers/api/v1/apps/credentials_controller.rb +++ b/app/controllers/api/v1/apps/credentials_controller.rb @@ -4,6 +4,6 @@ class Api::V1::Apps::CredentialsController < Api::BaseController def show return doorkeeper_render_error unless valid_doorkeeper_token? - render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer, fields: %i(name website vapid_key client_id scopes) + render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer end end diff --git a/app/controllers/api/v1/apps_controller.rb b/app/controllers/api/v1/apps_controller.rb index 97177547a2..50feaf1854 100644 --- a/app/controllers/api/v1/apps_controller.rb +++ b/app/controllers/api/v1/apps_controller.rb @@ -5,7 +5,7 @@ class Api::V1::AppsController < Api::BaseController def create @app = Doorkeeper::Application.create!(application_options) - render json: @app, serializer: REST::ApplicationSerializer + render json: @app, serializer: REST::CredentialApplicationSerializer end private @@ -24,6 +24,6 @@ class Api::V1::AppsController < Api::BaseController end def app_params - params.permit(:client_name, :redirect_uris, :scopes, :website) + params.permit(:client_name, :scopes, :website, :redirect_uris, redirect_uris: []) end end diff --git a/app/controllers/api/v1/blocks_controller.rb b/app/controllers/api/v1/blocks_controller.rb index 234ab2e82c..d7516c927b 100644 --- a/app/controllers/api/v1/blocks_controller.rb +++ b/app/controllers/api/v1/blocks_controller.rb @@ -43,8 +43,4 @@ class Api::V1::BlocksController < Api::BaseController def records_continue? paginated_blocks.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) end - - def pagination_params(core_params) - params.slice(:limit).permit(:limit).merge(core_params) - end end diff --git a/app/controllers/api/v1/bookmarks_controller.rb b/app/controllers/api/v1/bookmarks_controller.rb index b6bb987b6b..29f08e81d2 100644 --- a/app/controllers/api/v1/bookmarks_controller.rb +++ b/app/controllers/api/v1/bookmarks_controller.rb @@ -13,11 +13,11 @@ class Api::V1::BookmarksController < Api::BaseController private def load_statuses - cached_bookmarks + preloaded_bookmarks end - def cached_bookmarks - cache_collection(results.map(&:status), Status) + def preloaded_bookmarks + preload_collection(results.map(&:status), Status) end def results @@ -46,8 +46,4 @@ class Api::V1::BookmarksController < Api::BaseController def records_continue? results.size == limit_param(DEFAULT_STATUSES_LIMIT) end - - def pagination_params(core_params) - params.slice(:limit).permit(:limit).merge(core_params) - end end diff --git a/app/controllers/api/v1/conversations_controller.rb b/app/controllers/api/v1/conversations_controller.rb index a95c816e1c..60db082a8e 100644 --- a/app/controllers/api/v1/conversations_controller.rb +++ b/app/controllers/api/v1/conversations_controller.rb @@ -38,15 +38,15 @@ class Api::V1::ConversationsController < Api::BaseController def paginated_conversations AccountConversation.where(account: current_account) .includes( - account: :account_stat, + account: [:account_stat, user: :role], last_status: [ :media_attachments, :status_stat, :tags, { - preview_cards_status: :preview_card, - active_mentions: [account: :account_stat], - account: :account_stat, + preview_cards_status: { preview_card: { author_account: [:account_stat, user: :role] } }, + active_mentions: :account, + account: [:account_stat, user: :role], }, ] ) @@ -72,8 +72,4 @@ class Api::V1::ConversationsController < Api::BaseController def records_continue? @conversations.size == limit_param(LIMIT) end - - def pagination_params(core_params) - params.slice(:limit).permit(:limit).merge(core_params) - end end diff --git a/app/controllers/api/v1/crypto/encrypted_messages_controller.rb b/app/controllers/api/v1/crypto/encrypted_messages_controller.rb index d3de220393..93ae0e7771 100644 --- a/app/controllers/api/v1/crypto/encrypted_messages_controller.rb +++ b/app/controllers/api/v1/crypto/encrypted_messages_controller.rb @@ -44,8 +44,4 @@ class Api::V1::Crypto::EncryptedMessagesController < Api::BaseController def records_continue? @encrypted_messages.size == limit_param(LIMIT) end - - def pagination_params(core_params) - params.slice(:limit).permit(:limit).merge(core_params) - end end diff --git a/app/controllers/api/v1/domain_blocks_controller.rb b/app/controllers/api/v1/domain_blocks_controller.rb index 3dee2d176c..780ecbf189 100644 --- a/app/controllers/api/v1/domain_blocks_controller.rb +++ b/app/controllers/api/v1/domain_blocks_controller.rb @@ -54,10 +54,6 @@ class Api::V1::DomainBlocksController < Api::BaseController @blocks.size == limit_param(BLOCK_LIMIT) end - def pagination_params(core_params) - params.slice(:limit).permit(:limit).merge(core_params) - end - def domain_block_params params.permit(:domain) end diff --git a/app/controllers/api/v1/endorsements_controller.rb b/app/controllers/api/v1/endorsements_controller.rb index 9a723d89e4..09bafe0231 100644 --- a/app/controllers/api/v1/endorsements_controller.rb +++ b/app/controllers/api/v1/endorsements_controller.rb @@ -48,10 +48,6 @@ class Api::V1::EndorsementsController < Api::BaseController @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) end - def pagination_params(core_params) - params.slice(:limit).permit(:limit).merge(core_params) - end - def unlimited? params[:limit] == '0' end diff --git a/app/controllers/api/v1/favourites_controller.rb b/app/controllers/api/v1/favourites_controller.rb index 73da538f5c..a4454e4ded 100644 --- a/app/controllers/api/v1/favourites_controller.rb +++ b/app/controllers/api/v1/favourites_controller.rb @@ -13,11 +13,11 @@ class Api::V1::FavouritesController < Api::BaseController private def load_statuses - cached_favourites + preloaded_favourites end - def cached_favourites - cache_collection(results.map(&:status), Status) + def preloaded_favourites + preload_collection(results.map(&:status), Status) end def results @@ -46,8 +46,4 @@ class Api::V1::FavouritesController < Api::BaseController def records_continue? results.size == limit_param(DEFAULT_STATUSES_LIMIT) end - - def pagination_params(core_params) - params.slice(:limit).permit(:limit).merge(core_params) - end end diff --git a/app/controllers/api/v1/follow_requests_controller.rb b/app/controllers/api/v1/follow_requests_controller.rb index 7ffd7614bb..29a09fceef 100644 --- a/app/controllers/api/v1/follow_requests_controller.rb +++ b/app/controllers/api/v1/follow_requests_controller.rb @@ -67,8 +67,4 @@ class Api::V1::FollowRequestsController < Api::BaseController def records_continue? @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) end - - def pagination_params(core_params) - params.slice(:limit).permit(:limit).merge(core_params) - end end diff --git a/app/controllers/api/v1/followed_tags_controller.rb b/app/controllers/api/v1/followed_tags_controller.rb index 8888612b16..7d8f0eda1e 100644 --- a/app/controllers/api/v1/followed_tags_controller.rb +++ b/app/controllers/api/v1/followed_tags_controller.rb @@ -37,8 +37,4 @@ class Api::V1::FollowedTagsController < Api::BaseController def records_continue? @results.size == limit_param(TAGS_LIMIT) end - - def pagination_params(core_params) - params.slice(:limit).permit(:limit).merge(core_params) - end end diff --git a/app/controllers/api/v1/instances/extended_descriptions_controller.rb b/app/controllers/api/v1/instances/extended_descriptions_controller.rb index 73d2248117..db3d082f61 100644 --- a/app/controllers/api/v1/instances/extended_descriptions_controller.rb +++ b/app/controllers/api/v1/instances/extended_descriptions_controller.rb @@ -5,7 +5,7 @@ class Api::V1::Instances::ExtendedDescriptionsController < Api::V1::Instances::B before_action :set_extended_description - # Override `current_user` to avoid reading session cookies unless in whitelist mode + # Override `current_user` to avoid reading session cookies unless in limited federation mode def current_user super if limited_federation_mode? end diff --git a/app/controllers/api/v1/instances/peers_controller.rb b/app/controllers/api/v1/instances/peers_controller.rb index 83116472bb..fac763b405 100644 --- a/app/controllers/api/v1/instances/peers_controller.rb +++ b/app/controllers/api/v1/instances/peers_controller.rb @@ -5,7 +5,7 @@ class Api::V1::Instances::PeersController < Api::V1::Instances::BaseController skip_around_action :set_locale - # Override `current_user` to avoid reading session cookies unless in whitelist mode + # Override `current_user` to avoid reading session cookies unless in limited federation mode def current_user super if limited_federation_mode? end diff --git a/app/controllers/api/v1/instances/rules_controller.rb b/app/controllers/api/v1/instances/rules_controller.rb index d240d72464..3930eec0dd 100644 --- a/app/controllers/api/v1/instances/rules_controller.rb +++ b/app/controllers/api/v1/instances/rules_controller.rb @@ -5,7 +5,7 @@ class Api::V1::Instances::RulesController < Api::V1::Instances::BaseController before_action :set_rules - # Override `current_user` to avoid reading session cookies unless in whitelist mode + # Override `current_user` to avoid reading session cookies unless in limited federation mode def current_user super if limited_federation_mode? end diff --git a/app/controllers/api/v1/instances_controller.rb b/app/controllers/api/v1/instances_controller.rb index df4a14af15..49da75ed28 100644 --- a/app/controllers/api/v1/instances_controller.rb +++ b/app/controllers/api/v1/instances_controller.rb @@ -6,7 +6,7 @@ class Api::V1::InstancesController < Api::BaseController vary_by '' - # Override `current_user` to avoid reading session cookies unless in whitelist mode + # Override `current_user` to avoid reading session cookies unless in limited federation mode def current_user super if limited_federation_mode? end diff --git a/app/controllers/api/v1/lists/accounts_controller.rb b/app/controllers/api/v1/lists/accounts_controller.rb index aecf391049..b1c0e609d0 100644 --- a/app/controllers/api/v1/lists/accounts_controller.rb +++ b/app/controllers/api/v1/lists/accounts_controller.rb @@ -75,10 +75,6 @@ class Api::V1::Lists::AccountsController < Api::BaseController @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) end - def pagination_params(core_params) - params.slice(:limit).permit(:limit).merge(core_params) - end - def unlimited? params[:limit] == '0' end diff --git a/app/controllers/api/v1/mutes_controller.rb b/app/controllers/api/v1/mutes_controller.rb index dbfd7e103a..d2b50e3336 100644 --- a/app/controllers/api/v1/mutes_controller.rb +++ b/app/controllers/api/v1/mutes_controller.rb @@ -43,8 +43,4 @@ class Api::V1::MutesController < Api::BaseController def records_continue? paginated_mutes.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) end - - def pagination_params(core_params) - params.slice(:limit).permit(:limit).merge(core_params) - end end diff --git a/app/controllers/api/v1/notifications/requests_controller.rb b/app/controllers/api/v1/notifications/requests_controller.rb index 6a26cc0e8a..9ae80c28ed 100644 --- a/app/controllers/api/v1/notifications/requests_controller.rb +++ b/app/controllers/api/v1/notifications/requests_controller.rb @@ -28,20 +28,20 @@ class Api::V1::Notifications::RequestsController < Api::BaseController end def dismiss - @request.update!(dismissed: true) + @request.destroy! render_empty end private def load_requests - requests = NotificationRequest.where(account: current_account).where(dismissed: truthy_param?(:dismissed) || false).includes(:last_status, from_account: [:account_stat, :user]).to_a_paginated_by_id( + requests = NotificationRequest.where(account: current_account).includes(:last_status, from_account: [:account_stat, :user]).to_a_paginated_by_id( limit_param(DEFAULT_ACCOUNTS_LIMIT), params_slice(:max_id, :since_id, :min_id) ) NotificationRequest.preload_cache_collection(requests) do |statuses| - cache_collection(statuses, Status) + preload_collection(statuses, Status) end end @@ -68,8 +68,4 @@ class Api::V1::Notifications::RequestsController < Api::BaseController def pagination_since_id @requests.first.id end - - def pagination_params(core_params) - params.slice(:dismissed).permit(:dismissed).merge(core_params) - end end diff --git a/app/controllers/api/v1/notifications_controller.rb b/app/controllers/api/v1/notifications_controller.rb index 9c75516159..c82900ef66 100644 --- a/app/controllers/api/v1/notifications_controller.rb +++ b/app/controllers/api/v1/notifications_controller.rb @@ -50,7 +50,7 @@ class Api::V1::NotificationsController < Api::BaseController ) Notification.preload_cache_collection_target_statuses(notifications) do |target_statuses| - cache_collection(target_statuses, Status) + preload_collection(target_statuses, Status) end end diff --git a/app/controllers/api/v1/polls/votes_controller.rb b/app/controllers/api/v1/polls/votes_controller.rb index 513b937ef2..ad1b82cb52 100644 --- a/app/controllers/api/v1/polls/votes_controller.rb +++ b/app/controllers/api/v1/polls/votes_controller.rb @@ -8,7 +8,7 @@ class Api::V1::Polls::VotesController < Api::BaseController before_action :set_poll def create - VoteService.new.call(current_account, @poll, vote_params[:choices]) + VoteService.new.call(current_account, @poll, vote_params) render json: @poll, serializer: REST::PollSerializer end @@ -22,6 +22,6 @@ class Api::V1::Polls::VotesController < Api::BaseController end def vote_params - params.permit(choices: []) + params.require(:choices) end end diff --git a/app/controllers/api/v1/push/subscriptions_controller.rb b/app/controllers/api/v1/push/subscriptions_controller.rb index 3634acf956..e1ad89ee3e 100644 --- a/app/controllers/api/v1/push/subscriptions_controller.rb +++ b/app/controllers/api/v1/push/subscriptions_controller.rb @@ -1,9 +1,12 @@ # frozen_string_literal: true class Api::V1::Push::SubscriptionsController < Api::BaseController + include Redisable + include Lockable + before_action -> { doorkeeper_authorize! :push } before_action :require_user! - before_action :set_push_subscription + before_action :set_push_subscription, only: [:show, :update] before_action :check_push_subscription, only: [:show, :update] def show @@ -11,16 +14,18 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController end def create - @push_subscription&.destroy! + with_redis_lock("push_subscription:#{current_user.id}") do + destroy_web_push_subscriptions! - @push_subscription = Web::PushSubscription.create!( - endpoint: subscription_params[:endpoint], - key_p256dh: subscription_params[:keys][:p256dh], - key_auth: subscription_params[:keys][:auth], - data: data_params, - user_id: current_user.id, - access_token_id: doorkeeper_token.id - ) + @push_subscription = Web::PushSubscription.create!( + endpoint: subscription_params[:endpoint], + key_p256dh: subscription_params[:keys][:p256dh], + key_auth: subscription_params[:keys][:auth], + data: data_params, + user_id: current_user.id, + access_token_id: doorkeeper_token.id + ) + end render json: @push_subscription, serializer: REST::WebPushSubscriptionSerializer end @@ -31,14 +36,18 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController end def destroy - @push_subscription&.destroy! + destroy_web_push_subscriptions! render_empty end private + def destroy_web_push_subscriptions! + doorkeeper_token.web_push_subscriptions.destroy_all + end + def set_push_subscription - @push_subscription = Web::PushSubscription.find_by(access_token_id: doorkeeper_token.id) + @push_subscription = doorkeeper_token.web_push_subscriptions.first end def check_push_subscription diff --git a/app/controllers/api/v1/scheduled_statuses_controller.rb b/app/controllers/api/v1/scheduled_statuses_controller.rb index 1217ed014e..c62305d711 100644 --- a/app/controllers/api/v1/scheduled_statuses_controller.rb +++ b/app/controllers/api/v1/scheduled_statuses_controller.rb @@ -6,6 +6,7 @@ class Api::V1::ScheduledStatusesController < Api::BaseController before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, except: [:update, :destroy] before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:update, :destroy] + before_action :require_user! before_action :set_statuses, only: :index before_action :set_status, except: :index @@ -43,10 +44,6 @@ class Api::V1::ScheduledStatusesController < Api::BaseController params.permit(:scheduled_at) end - def pagination_params(core_params) - params.slice(:limit).permit(:limit).merge(core_params) - end - def next_path api_v1_scheduled_statuses_url pagination_params(max_id: pagination_max_id) if records_continue? end diff --git a/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb b/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb index bbc8082e0c..5a5c2fdc97 100644 --- a/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb +++ b/app/controllers/api/v1/statuses/favourited_by_accounts_controller.rb @@ -53,8 +53,4 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::V1::Statuses::Bas def records_continue? @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) end - - def pagination_params(core_params) - params.slice(:limit).permit(:limit).merge(core_params) - end end diff --git a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb index bac96b032b..0eba4fae32 100644 --- a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb +++ b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb @@ -49,8 +49,4 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::V1::Statuses::Base def records_continue? @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) end - - def pagination_params(core_params) - params.slice(:limit).permit(:limit).merge(core_params) - end end diff --git a/app/controllers/api/v1/statuses/translations_controller.rb b/app/controllers/api/v1/statuses/translations_controller.rb index 7d406b0a36..8cf495f78a 100644 --- a/app/controllers/api/v1/statuses/translations_controller.rb +++ b/app/controllers/api/v1/statuses/translations_controller.rb @@ -2,6 +2,7 @@ class Api::V1::Statuses::TranslationsController < Api::V1::Statuses::BaseController before_action -> { doorkeeper_authorize! :read, :'read:statuses' } + before_action :require_user! before_action :set_translation rescue_from TranslationService::NotConfiguredError, with: :not_found diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index 75e075be74..2593ef7da5 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -5,9 +5,11 @@ class Api::V1::StatusesController < Api::BaseController before_action -> { authorize_if_got_token! :read, :'read:statuses' }, except: [:create, :update, :destroy] before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:create, :update, :destroy] - before_action :require_user!, except: [:show, :context] - before_action :set_status, only: [:show, :context] - before_action :set_thread, only: [:create] + before_action :require_user!, except: [:index, :show, :context] + before_action :set_statuses, only: [:index] + before_action :set_status, only: [:show, :context] + before_action :set_thread, only: [:create] + before_action :check_statuses_limit, only: [:index] override_rate_limit_headers :create, family: :statuses override_rate_limit_headers :update, family: :statuses @@ -23,9 +25,14 @@ class Api::V1::StatusesController < Api::BaseController DESCENDANTS_LIMIT = 60 DESCENDANTS_DEPTH_LIMIT = 20 + def index + @statuses = preload_collection(@statuses, Status) + render json: @statuses, each_serializer: REST::StatusSerializer + end + def show cache_if_unauthenticated! - @status = cache_collection([@status], Status).first + @status = preload_collection([@status], Status).first render json: @status, serializer: REST::StatusSerializer end @@ -44,8 +51,8 @@ class Api::V1::StatusesController < Api::BaseController ancestors_results = @status.in_reply_to_id.nil? ? [] : @status.ancestors(ancestors_limit, current_account) descendants_results = @status.descendants(descendants_limit, current_account, descendants_depth_limit) - loaded_ancestors = cache_collection(ancestors_results, Status) - loaded_descendants = cache_collection(descendants_results, Status) + loaded_ancestors = preload_collection(ancestors_results, Status) + loaded_descendants = preload_collection(descendants_results, Status) @context = Context.new(ancestors: loaded_ancestors, descendants: loaded_descendants) statuses = [@status] + @context.ancestors + @context.descendants @@ -113,6 +120,10 @@ class Api::V1::StatusesController < Api::BaseController private + def set_statuses + @statuses = Status.permitted_statuses_from_ids(status_ids, current_account) + end + def set_status @status = Status.find(params[:id]) authorize @status, :show? @@ -127,6 +138,18 @@ class Api::V1::StatusesController < Api::BaseController render json: { error: I18n.t('statuses.errors.in_reply_not_found') }, status: 404 end + def check_statuses_limit + raise(Mastodon::ValidationError) if status_ids.size > DEFAULT_STATUSES_LIMIT + end + + def status_ids + Array(statuses_params[:id]).uniq.map(&:to_i) + end + + def statuses_params + params.permit(id: []) + end + def status_params params.permit( :status, @@ -168,8 +191,4 @@ class Api::V1::StatusesController < Api::BaseController def serialized_accounts(accounts) ActiveModel::Serializer::CollectionSerializer.new(accounts, serializer: REST::AccountSerializer) end - - def pagination_params(core_params) - params.slice(:limit).permit(:limit).merge(core_params) - end end diff --git a/app/controllers/api/v1/timelines/base_controller.rb b/app/controllers/api/v1/timelines/base_controller.rb index e79eba79ee..1dba4a5bb2 100644 --- a/app/controllers/api/v1/timelines/base_controller.rb +++ b/app/controllers/api/v1/timelines/base_controller.rb @@ -3,8 +3,14 @@ class Api::V1::Timelines::BaseController < Api::BaseController after_action :insert_pagination_headers, unless: -> { @statuses.empty? } + before_action :require_user!, if: :require_auth? + private + def require_auth? + !Setting.timeline_preview + end + def pagination_collection @statuses end diff --git a/app/controllers/api/v1/timelines/direct_controller.rb b/app/controllers/api/v1/timelines/direct_controller.rb index 6e98e9cacb..f295cee608 100644 --- a/app/controllers/api/v1/timelines/direct_controller.rb +++ b/app/controllers/api/v1/timelines/direct_controller.rb @@ -15,11 +15,11 @@ class Api::V1::Timelines::DirectController < Api::BaseController private def load_statuses - cached_direct_statuses + preloaded_direct_statuses end - def cached_direct_statuses - cache_collection direct_statuses, Status + def preloaded_direct_statuses + preload_collection direct_statuses, Status end def direct_statuses diff --git a/app/controllers/api/v1/timelines/home_controller.rb b/app/controllers/api/v1/timelines/home_controller.rb index 36fdbea647..d5d1828666 100644 --- a/app/controllers/api/v1/timelines/home_controller.rb +++ b/app/controllers/api/v1/timelines/home_controller.rb @@ -21,11 +21,11 @@ class Api::V1::Timelines::HomeController < Api::V1::Timelines::BaseController private def load_statuses - cached_home_statuses + preloaded_home_statuses end - def cached_home_statuses - cache_collection home_statuses, Status + def preloaded_home_statuses + preload_collection home_statuses, Status end def home_statuses diff --git a/app/controllers/api/v1/timelines/link_controller.rb b/app/controllers/api/v1/timelines/link_controller.rb new file mode 100644 index 0000000000..37ed084f06 --- /dev/null +++ b/app/controllers/api/v1/timelines/link_controller.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +class Api::V1::Timelines::LinkController < Api::V1::Timelines::BaseController + before_action -> { authorize_if_got_token! :read, :'read:statuses' } + before_action :set_preview_card + before_action :set_statuses + + PERMITTED_PARAMS = %i( + url + limit + ).freeze + + def show + cache_if_unauthenticated! + render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) + end + + private + + def set_preview_card + @preview_card = PreviewCard.joins(:trend).merge(PreviewCardTrend.allowed).find_by!(url: params[:url]) + end + + def set_statuses + @statuses = @preview_card.nil? ? [] : preload_collection(link_timeline_statuses, Status) + end + + def link_timeline_statuses + link_feed.get( + limit_param(DEFAULT_STATUSES_LIMIT), + params[:max_id], + params[:since_id], + params[:min_id] + ) + end + + def link_feed + LinkFeed.new(@preview_card, current_account) + end + + def next_path + api_v1_timelines_link_url next_path_params + end + + def prev_path + api_v1_timelines_link_url prev_path_params + end +end diff --git a/app/controllers/api/v1/timelines/list_controller.rb b/app/controllers/api/v1/timelines/list_controller.rb index 14b884ecd9..d8cdbdb74c 100644 --- a/app/controllers/api/v1/timelines/list_controller.rb +++ b/app/controllers/api/v1/timelines/list_controller.rb @@ -21,11 +21,11 @@ class Api::V1::Timelines::ListController < Api::V1::Timelines::BaseController end def set_statuses - @statuses = cached_list_statuses + @statuses = preloaded_list_statuses end - def cached_list_statuses - cache_collection list_statuses, Status + def preloaded_list_statuses + preload_collection list_statuses, Status end def list_statuses diff --git a/app/controllers/api/v1/timelines/public_controller.rb b/app/controllers/api/v1/timelines/public_controller.rb index 5bc8de8334..cd5445617b 100644 --- a/app/controllers/api/v1/timelines/public_controller.rb +++ b/app/controllers/api/v1/timelines/public_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class Api::V1::Timelines::PublicController < Api::V1::Timelines::BaseController - before_action :require_user!, only: [:show], if: :require_auth? + before_action -> { authorize_if_got_token! :read, :'read:statuses' } PERMITTED_PARAMS = %i(local remote limit only_media allow_local_only).freeze @@ -13,16 +13,12 @@ class Api::V1::Timelines::PublicController < Api::V1::Timelines::BaseController private - def require_auth? - !Setting.timeline_preview - end - def load_statuses - cached_public_statuses_page + preloaded_public_statuses_page end - def cached_public_statuses_page - cache_collection(public_statuses, Status) + def preloaded_public_statuses_page + preload_collection(public_statuses, Status) end def public_statuses diff --git a/app/controllers/api/v1/timelines/tag_controller.rb b/app/controllers/api/v1/timelines/tag_controller.rb index 4ba439dbb2..2b097aab0f 100644 --- a/app/controllers/api/v1/timelines/tag_controller.rb +++ b/app/controllers/api/v1/timelines/tag_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class Api::V1::Timelines::TagController < Api::V1::Timelines::BaseController - before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: :show, if: :require_auth? + before_action -> { authorize_if_got_token! :read, :'read:statuses' } before_action :load_tag PERMITTED_PARAMS = %i(local limit only_media).freeze @@ -23,11 +23,11 @@ class Api::V1::Timelines::TagController < Api::V1::Timelines::BaseController end def load_statuses - cached_tagged_statuses + preloaded_tagged_statuses end - def cached_tagged_statuses - @tag.nil? ? [] : cache_collection(tag_timeline_statuses, Status) + def preloaded_tagged_statuses + @tag.nil? ? [] : preload_collection(tag_timeline_statuses, Status) end def tag_timeline_statuses diff --git a/app/controllers/api/v1/trends/links_controller.rb b/app/controllers/api/v1/trends/links_controller.rb index 8edf5bbcef..3c5aecff43 100644 --- a/app/controllers/api/v1/trends/links_controller.rb +++ b/app/controllers/api/v1/trends/links_controller.rb @@ -34,10 +34,6 @@ class Api::V1::Trends::LinksController < Api::BaseController scope end - def pagination_params(core_params) - params.slice(:limit).permit(:limit).merge(core_params) - end - def next_path api_v1_trends_links_url pagination_params(offset: offset_param + limit_param(DEFAULT_LINKS_LIMIT)) if records_continue? end diff --git a/app/controllers/api/v1/trends/statuses_controller.rb b/app/controllers/api/v1/trends/statuses_controller.rb index 48bfe11991..cdbfce0685 100644 --- a/app/controllers/api/v1/trends/statuses_controller.rb +++ b/app/controllers/api/v1/trends/statuses_controller.rb @@ -20,7 +20,7 @@ class Api::V1::Trends::StatusesController < Api::BaseController def set_statuses @statuses = if enabled? - cache_collection(statuses_from_trends.offset(offset_param).limit(limit_param(DEFAULT_STATUSES_LIMIT)), Status) + preload_collection(statuses_from_trends.offset(offset_param).limit(limit_param(DEFAULT_STATUSES_LIMIT)), Status) else [] end @@ -32,10 +32,6 @@ class Api::V1::Trends::StatusesController < Api::BaseController scope end - def pagination_params(core_params) - params.slice(:limit).permit(:limit).merge(core_params) - end - def next_path api_v1_trends_statuses_url pagination_params(offset: offset_param + limit_param(DEFAULT_STATUSES_LIMIT)) if records_continue? end diff --git a/app/controllers/api/v1/trends/tags_controller.rb b/app/controllers/api/v1/trends/tags_controller.rb index 0d8e27552e..ee4cfab2ea 100644 --- a/app/controllers/api/v1/trends/tags_controller.rb +++ b/app/controllers/api/v1/trends/tags_controller.rb @@ -30,10 +30,6 @@ class Api::V1::Trends::TagsController < Api::BaseController Trends.tags.query.allowed end - def pagination_params(core_params) - params.slice(:limit).permit(:limit).merge(core_params) - end - def next_path api_v1_trends_tags_url pagination_params(offset: offset_param + limit_param(DEFAULT_TAGS_LIMIT)) if records_continue? end diff --git a/app/controllers/api/v2_alpha/notifications_controller.rb b/app/controllers/api/v2_alpha/notifications_controller.rb new file mode 100644 index 0000000000..edba23ab4a --- /dev/null +++ b/app/controllers/api/v2_alpha/notifications_controller.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +class Api::V2Alpha::NotificationsController < Api::BaseController + before_action -> { doorkeeper_authorize! :read, :'read:notifications' }, except: [:clear, :dismiss] + before_action -> { doorkeeper_authorize! :write, :'write:notifications' }, only: [:clear, :dismiss] + before_action :require_user! + after_action :insert_pagination_headers, only: :index + + DEFAULT_NOTIFICATIONS_LIMIT = 40 + + def index + with_read_replica do + @notifications = load_notifications + @group_metadata = load_group_metadata + @relationships = StatusRelationshipsPresenter.new(target_statuses_from_notifications, current_user&.account_id) + end + + render json: @notifications.map { |notification| NotificationGroup.from_notification(notification, max_id: @group_metadata.dig(notification.group_key, :max_id)) }, each_serializer: REST::NotificationGroupSerializer, relationships: @relationships, group_metadata: @group_metadata + end + + def show + @notification = current_account.notifications.without_suspended.find_by!(group_key: params[:id]) + render json: NotificationGroup.from_notification(@notification), serializer: REST::NotificationGroupSerializer + end + + def clear + current_account.notifications.delete_all + render_empty + end + + def dismiss + current_account.notifications.where(group_key: params[:id]).destroy_all + render_empty + end + + private + + def load_notifications + notifications = browserable_account_notifications.includes(from_account: [:account_stat, :user]).to_a_grouped_paginated_by_id( + limit_param(DEFAULT_NOTIFICATIONS_LIMIT), + params_slice(:max_id, :since_id, :min_id) + ) + + Notification.preload_cache_collection_target_statuses(notifications) do |target_statuses| + preload_collection(target_statuses, Status) + end + end + + def load_group_metadata + return {} if @notifications.empty? + + browserable_account_notifications + .where(group_key: @notifications.filter_map(&:group_key)) + .where(id: (@notifications.last.id)..(@notifications.first.id)) + .group(:group_key) + .pluck(:group_key, 'min(notifications.id) as min_id', 'max(notifications.id) as max_id', 'max(notifications.created_at) as latest_notification_at') + .to_h { |group_key, min_id, max_id, latest_notification_at| [group_key, { min_id: min_id, max_id: max_id, latest_notification_at: latest_notification_at }] } + end + + def browserable_account_notifications + current_account.notifications.without_suspended.browserable( + types: Array(browserable_params[:types]), + exclude_types: Array(browserable_params[:exclude_types]), + include_filtered: truthy_param?(:include_filtered) + ) + end + + def target_statuses_from_notifications + @notifications.filter_map(&:target_status) + end + + def next_path + api_v2_alpha_notifications_url pagination_params(max_id: pagination_max_id) unless @notifications.empty? + end + + def prev_path + api_v2_alpha_notifications_url pagination_params(min_id: pagination_since_id) unless @notifications.empty? + end + + def pagination_collection + @notifications + end + + def browserable_params + params.permit(:include_filtered, types: [], exclude_types: []) + end + + def pagination_params(core_params) + params.slice(:limit, :types, :exclude_types, :include_filtered).permit(:limit, :include_filtered, types: [], exclude_types: []).merge(core_params) + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index bd152dbb66..1d700fa282 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -9,6 +9,7 @@ class ApplicationController < ActionController::Base include UserTrackingConcern include SessionTrackingConcern include CacheConcern + include PreloadingConcern include DomainControlHelper include ThemingConcern include DatabaseHelper diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb index acfc0af0d9..e5a2ac0270 100644 --- a/app/controllers/auth/registrations_controller.rb +++ b/app/controllers/auth/registrations_controller.rb @@ -25,6 +25,14 @@ class Auth::RegistrationsController < Devise::RegistrationsController super(&:build_invite_request) end + def edit # rubocop:disable Lint/UselessMethodDefinition + super + end + + def create # rubocop:disable Lint/UselessMethodDefinition + super + end + def update super do |resource| resource.clear_other_sessions(current_session.session_id) if resource.saved_change_to_encrypted_password? @@ -44,7 +52,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController end def build_resource(hash = nil) - super(hash) + super resource.locale = I18n.locale resource.invite_code = @invite&.code if resource.invite_code.blank? diff --git a/app/controllers/concerns/api/pagination.rb b/app/controllers/concerns/api/pagination.rb index d84a1d99f7..7f06dc0202 100644 --- a/app/controllers/concerns/api/pagination.rb +++ b/app/controllers/concerns/api/pagination.rb @@ -3,6 +3,8 @@ module Api::Pagination extend ActiveSupport::Concern + PAGINATION_PARAMS = %i(limit).freeze + protected def pagination_max_id @@ -24,6 +26,13 @@ module Api::Pagination render json: { error: 'Pagination values for `offset` and `limit` must be positive' }, status: 400 if pagination_options_invalid? end + def pagination_params(core_params) + params + .slice(*PAGINATION_PARAMS) + .permit(*PAGINATION_PARAMS) + .merge(core_params) + end + private def insert_pagination_headers diff --git a/app/controllers/concerns/cache_concern.rb b/app/controllers/concerns/cache_concern.rb index 4656539f85..1823b5b8ed 100644 --- a/app/controllers/concerns/cache_concern.rb +++ b/app/controllers/concerns/cache_concern.rb @@ -45,20 +45,4 @@ module CacheConcern Rails.cache.write(key, response.body, expires_in: expires_in, raw: true) end end - - # TODO: Rename this method, as it does not perform any caching anymore. - def cache_collection(raw, klass) - return raw unless klass.respond_to?(:preload_cacheable_associations) - - records = raw.to_a - - klass.preload_cacheable_associations(records) - - records - end - - # TODO: Rename this method, as it does not perform any caching anymore. - def cache_collection_paginated_by_id(raw, klass, limit, options) - cache_collection raw.to_a_paginated_by_id(limit, options), klass - end end diff --git a/app/controllers/concerns/preloading_concern.rb b/app/controllers/concerns/preloading_concern.rb new file mode 100644 index 0000000000..61e2213649 --- /dev/null +++ b/app/controllers/concerns/preloading_concern.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module PreloadingConcern + extend ActiveSupport::Concern + + def preload_collection(scope, klass) + return scope unless klass.respond_to?(:preload_cacheable_associations) + + scope.to_a.tap do |records| + klass.preload_cacheable_associations(records) + end + end + + def preload_collection_paginated_by_id(scope, klass, limit, options) + preload_collection scope.to_a_paginated_by_id(limit, options), klass + end +end diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb index 8440df6b7e..7bb22453ca 100644 --- a/app/controllers/oauth/authorized_applications_controller.rb +++ b/app/controllers/oauth/authorized_applications_controller.rb @@ -17,6 +17,7 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio def destroy Web::PushSubscription.unsubscribe_for(params[:id], current_resource_owner) + Doorkeeper::Application.find_by(id: params[:id])&.close_streaming_sessions(current_resource_owner) super end diff --git a/app/controllers/settings/applications_controller.rb b/app/controllers/settings/applications_controller.rb index d4b7205681..d6573f9b49 100644 --- a/app/controllers/settings/applications_controller.rb +++ b/app/controllers/settings/applications_controller.rb @@ -13,7 +13,7 @@ class Settings::ApplicationsController < Settings::BaseController def new @application = Doorkeeper::Application.new( redirect_uri: Doorkeeper.configuration.native_redirect_uri, - scopes: 'read write follow' + scopes: 'profile' ) end diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb index b0bdbde956..d6c0d872c8 100644 --- a/app/controllers/tags_controller.rb +++ b/app/controllers/tags_controller.rb @@ -45,7 +45,7 @@ class TagsController < ApplicationController end def set_statuses - @statuses = cache_collection(TagFeed.new(@tag, nil, local: @local).get(limit_param), Status) + @statuses = preload_collection(TagFeed.new(@tag, nil, local: @local).get(limit_param), Status) end def limit_param diff --git a/app/controllers/well_known/oauth_metadata_controller.rb b/app/controllers/well_known/oauth_metadata_controller.rb new file mode 100644 index 0000000000..c80be2d652 --- /dev/null +++ b/app/controllers/well_known/oauth_metadata_controller.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module WellKnown + class OauthMetadataController < ActionController::Base # rubocop:disable Rails/ApplicationController + include CacheConcern + + # Prevent `active_model_serializer`'s `ActionController::Serialization` from calling `current_user` + # and thus re-issuing session cookies + serialization_scope nil + + def show + # Due to this document potentially changing between Mastodon versions (as + # new OAuth scopes are added), we don't use expires_in to cache upstream, + # instead just caching in the rails cache: + render_with_cache( + json: ::OauthMetadataPresenter.new, + serializer: ::OauthMetadataSerializer, + content_type: 'application/json', + expires_in: 15.minutes + ) + end + end +end diff --git a/app/helpers/admin/account_moderation_notes_helper.rb b/app/helpers/admin/account_moderation_notes_helper.rb index 3b9d580499..2a3d954a35 100644 --- a/app/helpers/admin/account_moderation_notes_helper.rb +++ b/app/helpers/admin/account_moderation_notes_helper.rb @@ -4,27 +4,42 @@ module Admin::AccountModerationNotesHelper def admin_account_link_to(account, path: nil) return if account.nil? - link_to path || admin_account_path(account.id), class: name_tag_classes(account), title: account.acct do - safe_join([ - image_tag(account.avatar.url, width: 15, height: 15, alt: '', class: 'avatar'), - content_tag(:span, account.acct, class: 'username'), - ], ' ') - end + link_to( + labeled_account_avatar(account), + path || admin_account_path(account.id), + class: class_names('name-tag', suspended: suspended_account?(account)), + title: account.acct + ) end def admin_account_inline_link_to(account) return if account.nil? - link_to admin_account_path(account.id), class: name_tag_classes(account, true), title: account.acct do - content_tag(:span, account.acct, class: 'username') - end + link_to( + account_inline_text(account), + admin_account_path(account.id), + class: class_names('inline-name-tag', suspended: suspended_account?(account)), + title: account.acct + ) end private - def name_tag_classes(account, inline = false) - classes = [inline ? 'inline-name-tag' : 'name-tag'] - classes << 'suspended' if account.suspended? || (account.local? && account.user.nil?) - classes.join(' ') + def labeled_account_avatar(account) + safe_join( + [ + image_tag(account.avatar.url, width: 15, height: 15, alt: '', class: 'avatar'), + account_inline_text(account), + ], + ' ' + ) + end + + def account_inline_text(account) + content_tag(:span, account.acct, class: 'username') + end + + def suspended_account?(account) + account.suspended? || (account.local? && account.user.nil?) end end diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb index 4018ef6b1c..e8d5634126 100644 --- a/app/helpers/admin/action_logs_helper.rb +++ b/app/helpers/admin/action_logs_helper.rb @@ -15,15 +15,15 @@ module Admin::ActionLogsHelper link_to log.human_identifier, admin_roles_path(log.target_id) when 'Report' link_to "##{log.human_identifier.presence || log.target_id}", admin_report_path(log.target_id) - when 'DomainBlock', 'DomainAllow', 'EmailDomainBlock', 'UnavailableDomain' - link_to log.human_identifier, "https://#{log.human_identifier.presence}" + when 'Instance', 'DomainBlock', 'DomainAllow', 'UnavailableDomain' + log.human_identifier.present? ? link_to(log.human_identifier, admin_instance_path(log.human_identifier)) : I18n.t('admin.action_logs.unavailable_instance') when 'Status' link_to log.human_identifier, log.permalink when 'AccountWarning' link_to log.human_identifier, disputes_strike_path(log.target_id) when 'Announcement' link_to truncate(log.human_identifier), edit_admin_announcement_path(log.target_id) - when 'IpBlock', 'Instance', 'CustomEmoji' + when 'IpBlock', 'EmailDomainBlock', 'CustomEmoji' log.human_identifier when 'CanonicalEmailBlock' content_tag(:samp, (log.human_identifier.presence || '')[0...7], title: log.human_identifier) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index b0114132cc..2369cff7e6 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -113,6 +113,14 @@ module ApplicationHelper content_tag(:i, nil, attributes.merge(class: class_names.join(' '))) end + def material_symbol(icon, attributes = {}) + inline_svg_tag( + "400-24px/#{icon}.svg", + class: %w(icon).concat(attributes[:class].to_s.split), + role: :img + ) + end + def check_icon inline_svg_tag 'check.svg' end @@ -233,6 +241,26 @@ module ApplicationHelper EmojiFormatter.new(html, custom_emojis, other_options.merge(animate: prefers_autoplay?)).to_s end + def mascot_url + full_asset_url(instance_presenter.mascot&.file&.url || frontend_asset_path('images/elephant_ui_plane.svg')) + end + + def instance_presenter + @instance_presenter ||= InstancePresenter.new + end + + def favicon_path(size = '48') + instance_presenter.favicon&.file&.url(size) + end + + def app_icon_path(size = '48') + instance_presenter.app_icon&.file&.url(size) + end + + def use_mask_icon? + instance_presenter.app_icon.blank? + end + # glitch-soc addition to handle the multiple flavors def preload_locale_pack supported_locales = Themes.instance.flavour(current_flavour)['locales'] diff --git a/app/helpers/jsonld_helper.rb b/app/helpers/jsonld_helper.rb index b0f2077db0..932a3420db 100644 --- a/app/helpers/jsonld_helper.rb +++ b/app/helpers/jsonld_helper.rb @@ -141,7 +141,7 @@ module JsonLdHelper def safe_for_forwarding?(original, compacted) original.without('@context', 'signature').all? do |key, value| compacted_value = compacted[key] - return false unless value.class == compacted_value.class + return false unless value.instance_of?(compacted_value.class) if value.is_a?(Hash) safe_for_forwarding?(value, compacted_value) diff --git a/app/helpers/mascot_helper.rb b/app/helpers/mascot_helper.rb deleted file mode 100644 index 34b656411e..0000000000 --- a/app/helpers/mascot_helper.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module MascotHelper - def mascot_url - full_asset_url(instance_presenter.mascot&.file&.url || frontend_asset_path('images/elephant_ui_plane.svg')) - end - - def instance_presenter - @instance_presenter ||= InstancePresenter.new - end -end diff --git a/app/javascript/packs/admin.tsx b/app/javascript/entrypoints/admin.tsx similarity index 100% rename from app/javascript/packs/admin.tsx rename to app/javascript/entrypoints/admin.tsx diff --git a/app/javascript/packs/application.js b/app/javascript/entrypoints/application.ts similarity index 81% rename from app/javascript/packs/application.js rename to app/javascript/entrypoints/application.ts index d13388b479..1087b1c4cb 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/entrypoints/application.ts @@ -1,5 +1,5 @@ import './public-path'; -import main from "mastodon/main"; +import main from 'mastodon/main'; import { start } from '../mastodon/common'; import { loadLocale } from '../mastodon/locales'; @@ -10,6 +10,6 @@ start(); loadPolyfills() .then(loadLocale) .then(main) - .catch(e => { + .catch((e: unknown) => { console.error(e); }); diff --git a/app/javascript/packs/common.js b/app/javascript/entrypoints/common.js similarity index 100% rename from app/javascript/packs/common.js rename to app/javascript/entrypoints/common.js diff --git a/app/javascript/packs/error.js b/app/javascript/entrypoints/error.ts similarity index 64% rename from app/javascript/packs/error.js rename to app/javascript/entrypoints/error.ts index 6376dc2f5d..db68484f3a 100644 --- a/app/javascript/packs/error.js +++ b/app/javascript/entrypoints/error.ts @@ -2,7 +2,9 @@ import './public-path'; import ready from '../mastodon/ready'; ready(() => { - const image = document.querySelector('img'); + const image = document.querySelector('img'); + + if (!image) return; image.addEventListener('mouseenter', () => { image.src = '/oops.gif'; @@ -11,4 +13,6 @@ ready(() => { image.addEventListener('mouseleave', () => { image.src = '/oops.png'; }); +}).catch((e: unknown) => { + console.error(e); }); diff --git a/app/javascript/packs/inert.js b/app/javascript/entrypoints/inert.ts similarity index 100% rename from app/javascript/packs/inert.js rename to app/javascript/entrypoints/inert.ts diff --git a/app/javascript/packs/mailer.js b/app/javascript/entrypoints/mailer.ts similarity index 100% rename from app/javascript/packs/mailer.js rename to app/javascript/entrypoints/mailer.ts diff --git a/app/javascript/packs/public-path.js b/app/javascript/entrypoints/public-path.ts similarity index 69% rename from app/javascript/packs/public-path.js rename to app/javascript/entrypoints/public-path.ts index f4d166a771..ac4b9355b9 100644 --- a/app/javascript/packs/public-path.js +++ b/app/javascript/entrypoints/public-path.ts @@ -2,7 +2,7 @@ // to share the same assets regardless of instance configuration. // See https://webpack.js.org/guides/public-path/#on-the-fly -function removeOuterSlashes(string) { +function removeOuterSlashes(string: string) { return string.replace(/^\/*/, '').replace(/\/*$/, ''); } @@ -15,7 +15,9 @@ function formatPublicPath(host = '', path = '') { return `${formattedHost}/${formattedPath}/`; } -const cdnHost = document.querySelector('meta[name=cdn-host]'); +const cdnHost = document.querySelector('meta[name=cdn-host]'); -// eslint-disable-next-line no-undef -__webpack_public_path__ = formatPublicPath(cdnHost ? cdnHost.content : '', process.env.PUBLIC_OUTPUT_PATH); +__webpack_public_path__ = formatPublicPath( + cdnHost ? cdnHost.content : '', + process.env.PUBLIC_OUTPUT_PATH, +); diff --git a/app/javascript/packs/public.tsx b/app/javascript/entrypoints/public.tsx similarity index 97% rename from app/javascript/packs/public.tsx rename to app/javascript/entrypoints/public.tsx index d45927226c..40a9b7c0ca 100644 --- a/app/javascript/packs/public.tsx +++ b/app/javascript/entrypoints/public.tsx @@ -65,7 +65,7 @@ window.addEventListener('message', (e) => { { type: 'setHeight', id: data.id, - height: document.getElementsByTagName('html')[0].scrollHeight, + height: document.getElementsByTagName('html')[0]?.scrollHeight, }, '*', ); @@ -135,7 +135,7 @@ function loaded() { ); }; const todayFormat = new IntlMessageFormat( - localeData['relative_format.today'] || 'Today at {time}', + localeData['relative_format.today'] ?? 'Today at {time}', locale, ); @@ -288,13 +288,13 @@ function loaded() { if (statusEl.dataset.spoiler === 'expanded') { statusEl.dataset.spoiler = 'folded'; this.textContent = new IntlMessageFormat( - localeData['status.show_more'] || 'Show more', + localeData['status.show_more'] ?? 'Show more', locale, ).format() as string; } else { statusEl.dataset.spoiler = 'expanded'; this.textContent = new IntlMessageFormat( - localeData['status.show_less'] || 'Show less', + localeData['status.show_less'] ?? 'Show less', locale, ).format() as string; } @@ -316,8 +316,8 @@ function loaded() { const message = statusEl.dataset.spoiler === 'expanded' - ? localeData['status.show_less'] || 'Show less' - : localeData['status.show_more'] || 'Show more'; + ? localeData['status.show_less'] ?? 'Show less' + : localeData['status.show_more'] ?? 'Show more'; spoilerLink.textContent = new IntlMessageFormat( message, locale, diff --git a/app/javascript/packs/remote_interaction_helper.ts b/app/javascript/entrypoints/remote_interaction_helper.ts similarity index 96% rename from app/javascript/packs/remote_interaction_helper.ts rename to app/javascript/entrypoints/remote_interaction_helper.ts index d5834c6c3d..419571c896 100644 --- a/app/javascript/packs/remote_interaction_helper.ts +++ b/app/javascript/entrypoints/remote_interaction_helper.ts @@ -67,7 +67,9 @@ const fetchInteractionURLFailure = () => { ); }; -const isValidDomain = (value: string) => { +const isValidDomain = (value: unknown) => { + if (typeof value !== 'string') return false; + const url = new URL('https:///path'); url.hostname = value; return url.hostname === value; @@ -124,6 +126,11 @@ const fromAcct = (acct: string) => { const domain = segments[1]; const fallbackTemplate = `https://${domain}/authorize_interaction?uri={uri}`; + if (!domain) { + fetchInteractionURLFailure(); + return; + } + axios .get(`https://${domain}/.well-known/webfinger`, { params: { resource: `acct:${acct}` }, diff --git a/app/javascript/packs/share.jsx b/app/javascript/entrypoints/share.tsx similarity index 64% rename from app/javascript/packs/share.jsx rename to app/javascript/entrypoints/share.tsx index 7b5723091c..7926250851 100644 --- a/app/javascript/packs/share.jsx +++ b/app/javascript/entrypoints/share.tsx @@ -2,7 +2,7 @@ import './public-path'; import { createRoot } from 'react-dom/client'; import { start } from '../mastodon/common'; -import ComposeContainer from '../mastodon/containers/compose_container'; +import ComposeContainer from '../mastodon/containers/compose_container'; import { loadPolyfills } from '../mastodon/polyfills'; import ready from '../mastodon/ready'; @@ -16,7 +16,7 @@ function loaded() { if (!attr) return; - const props = JSON.parse(attr); + const props = JSON.parse(attr) as object; const root = createRoot(mountNode); root.render(); @@ -24,9 +24,13 @@ function loaded() { } function main() { - ready(loaded); + ready(loaded).catch((error: unknown) => { + console.error(error); + }); } -loadPolyfills().then(main).catch(error => { - console.error(error); -}); +loadPolyfills() + .then(main) + .catch((error: unknown) => { + console.error(error); + }); diff --git a/app/javascript/entrypoints/sign_up.ts b/app/javascript/entrypoints/sign_up.ts new file mode 100644 index 0000000000..880738fcb7 --- /dev/null +++ b/app/javascript/entrypoints/sign_up.ts @@ -0,0 +1,48 @@ +import './public-path'; +import axios from 'axios'; + +import ready from '../mastodon/ready'; + +async function checkConfirmation() { + const response = await axios.get('/api/v1/emails/check_confirmation'); + + if (response.data) { + window.location.href = '/start'; + } +} + +ready(() => { + setInterval(() => { + void checkConfirmation(); + }, 5000); + + document + .querySelectorAll('button.timer-button') + .forEach((button) => { + let counter = 30; + + const container = document.createElement('span'); + + const updateCounter = () => { + container.innerText = ` (${counter})`; + }; + + updateCounter(); + + const countdown = setInterval(() => { + counter--; + + if (counter === 0) { + button.disabled = false; + button.removeChild(container); + clearInterval(countdown); + } else { + updateCounter(); + } + }, 1000); + + button.appendChild(container); + }); +}).catch((e: unknown) => { + throw e; +}); diff --git a/app/javascript/entrypoints/two_factor_authentication.ts b/app/javascript/entrypoints/two_factor_authentication.ts new file mode 100644 index 0000000000..981481694b --- /dev/null +++ b/app/javascript/entrypoints/two_factor_authentication.ts @@ -0,0 +1,197 @@ +import * as WebAuthnJSON from '@github/webauthn-json'; +import axios, { AxiosError } from 'axios'; + +import ready from '../mastodon/ready'; + +import 'regenerator-runtime/runtime'; + +type PublicKeyCredentialCreationOptionsJSON = + WebAuthnJSON.CredentialCreationOptionsJSON['publicKey']; + +function exceptionHasAxiosError( + error: unknown, +): error is AxiosError<{ error: unknown }> { + return ( + error instanceof AxiosError && + typeof error.response?.data === 'object' && + 'error' in error.response.data + ); +} + +function logAxiosResponseError(error: unknown) { + if (exceptionHasAxiosError(error)) console.error(error); +} + +function getCSRFToken() { + return document + .querySelector('meta[name="csrf-token"]') + ?.getAttribute('content'); +} + +function hideFlashMessages() { + document.querySelectorAll('.flash-message').forEach((flashMessage) => { + flashMessage.classList.add('hidden'); + }); +} + +async function callback( + url: string, + body: + | { + credential: WebAuthnJSON.PublicKeyCredentialWithAttestationJSON; + nickname: string; + } + | { + user: { credential: WebAuthnJSON.PublicKeyCredentialWithAssertionJSON }; + }, +) { + try { + const response = await axios.post<{ redirect_path: string }>( + url, + JSON.stringify(body), + { + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + 'X-CSRF-Token': getCSRFToken(), + }, + }, + ); + + window.location.replace(response.data.redirect_path); + } catch (error) { + if (error instanceof AxiosError && error.response?.status === 422) { + const errorMessage = document.getElementById( + 'security-key-error-message', + ); + errorMessage?.classList.remove('hidden'); + + logAxiosResponseError(error); + } else { + console.error(error); + } + } +} + +async function handleWebauthnCredentialRegistration(nickname: string) { + try { + const response = await axios.get( + '/settings/security_keys/options', + ); + + const credentialOptions = response.data; + + try { + const credential = await WebAuthnJSON.create({ + publicKey: credentialOptions, + }); + + const params = { + credential: credential, + nickname: nickname, + }; + + await callback('/settings/security_keys', params); + } catch (error) { + const errorMessage = document.getElementById( + 'security-key-error-message', + ); + errorMessage?.classList.remove('hidden'); + console.error(error); + } + } catch (error) { + logAxiosResponseError(error); + } +} + +async function handleWebauthnCredentialAuthentication() { + try { + const response = await axios.get( + 'sessions/security_key_options', + ); + + const credentialOptions = response.data; + + try { + const credential = await WebAuthnJSON.get({ + publicKey: credentialOptions, + }); + + const params = { user: { credential: credential } }; + void callback('sign_in', params); + } catch (error) { + const errorMessage = document.getElementById( + 'security-key-error-message', + ); + errorMessage?.classList.remove('hidden'); + console.error(error); + } + } catch (error) { + logAxiosResponseError(error); + } +} + +ready(() => { + if (!WebAuthnJSON.supported()) { + const unsupported_browser_message = document.getElementById( + 'unsupported-browser-message', + ); + if (unsupported_browser_message) { + unsupported_browser_message.classList.remove('hidden'); + const button = document.querySelector( + 'button.btn.js-webauthn', + ); + if (button) button.disabled = true; + } + } + + const webAuthnCredentialRegistrationForm = + document.querySelector('form#new_webauthn_credential'); + if (webAuthnCredentialRegistrationForm) { + webAuthnCredentialRegistrationForm.addEventListener('submit', (event) => { + event.preventDefault(); + + if (!(event.target instanceof HTMLFormElement)) return; + + const nickname = event.target.querySelector( + 'input[name="new_webauthn_credential[nickname]"]', + ); + + if (nickname?.value) { + void handleWebauthnCredentialRegistration(nickname.value); + } else { + nickname?.focus(); + } + }); + } + + const webAuthnCredentialAuthenticationForm = + document.getElementById('webauthn-form'); + if (webAuthnCredentialAuthenticationForm) { + webAuthnCredentialAuthenticationForm.addEventListener('submit', (event) => { + event.preventDefault(); + void handleWebauthnCredentialAuthentication(); + }); + + const otpAuthenticationForm = document.getElementById( + 'otp-authentication-form', + ); + + const linkToOtp = document.getElementById('link-to-otp'); + + linkToOtp?.addEventListener('click', () => { + webAuthnCredentialAuthenticationForm.classList.add('hidden'); + otpAuthenticationForm?.classList.remove('hidden'); + hideFlashMessages(); + }); + + const linkToWebAuthn = document.getElementById('link-to-webauthn'); + linkToWebAuthn?.addEventListener('click', () => { + otpAuthenticationForm?.classList.add('hidden'); + webAuthnCredentialAuthenticationForm.classList.remove('hidden'); + hideFlashMessages(); + }); + } +}).catch((e: unknown) => { + throw e; +}); diff --git a/app/javascript/flavours/glitch/actions/account_notes.ts b/app/javascript/flavours/glitch/actions/account_notes.ts index 1fb683e0d3..a71b342b06 100644 --- a/app/javascript/flavours/glitch/actions/account_notes.ts +++ b/app/javascript/flavours/glitch/actions/account_notes.ts @@ -1,18 +1,10 @@ -import type { ApiRelationshipJSON } from 'flavours/glitch/api_types/relationships'; -import { createAppAsyncThunk } from 'flavours/glitch/store/typed_functions'; +import { apiSubmitAccountNote } from 'flavours/glitch/api/accounts'; +import { createDataLoadingThunk } from 'flavours/glitch/store/typed_functions'; -import api from '../api'; - -export const submitAccountNote = createAppAsyncThunk( +export const submitAccountNote = createDataLoadingThunk( 'account_note/submit', - async (args: { id: string; value: string }, { getState }) => { - const response = await api(getState).post( - `/api/v1/accounts/${args.id}/note`, - { - comment: args.value, - }, - ); - - return { relationship: response.data }; - }, + ({ accountId, note }: { accountId: string; note: string }) => + apiSubmitAccountNote(accountId, note), + (relationship) => ({ relationship }), + { skipLoading: true }, ); diff --git a/app/javascript/flavours/glitch/actions/accounts.js b/app/javascript/flavours/glitch/actions/accounts.js index bb26035e97..7c31c16998 100644 --- a/app/javascript/flavours/glitch/actions/accounts.js +++ b/app/javascript/flavours/glitch/actions/accounts.js @@ -89,11 +89,11 @@ export const ACCOUNT_REVEAL = 'ACCOUNT_REVEAL'; export * from './accounts_typed'; export function fetchAccount(id) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(fetchRelationships([id])); dispatch(fetchAccountRequest(id)); - api(getState).get(`/api/v1/accounts/${id}`).then(response => { + api().get(`/api/v1/accounts/${id}`).then(response => { dispatch(importFetchedAccount(response.data)); dispatch(fetchAccountSuccess()); }).catch(error => { @@ -102,10 +102,10 @@ export function fetchAccount(id) { }; } -export const lookupAccount = acct => (dispatch, getState) => { +export const lookupAccount = acct => (dispatch) => { dispatch(lookupAccountRequest(acct)); - api(getState).get('/api/v1/accounts/lookup', { params: { acct } }).then(response => { + api().get('/api/v1/accounts/lookup', { params: { acct } }).then(response => { dispatch(fetchRelationships([response.data.id])); dispatch(importFetchedAccount(response.data)); dispatch(lookupAccountSuccess()); @@ -159,7 +159,7 @@ export function followAccount(id, options = { reblogs: true }) { dispatch(followAccountRequest({ id, locked })); - api(getState).post(`/api/v1/accounts/${id}/follow`, options).then(response => { + api().post(`/api/v1/accounts/${id}/follow`, options).then(response => { dispatch(followAccountSuccess({relationship: response.data, alreadyFollowing})); }).catch(error => { dispatch(followAccountFail({ id, error, locked })); @@ -171,7 +171,7 @@ export function unfollowAccount(id) { return (dispatch, getState) => { dispatch(unfollowAccountRequest(id)); - api(getState).post(`/api/v1/accounts/${id}/unfollow`).then(response => { + api().post(`/api/v1/accounts/${id}/unfollow`).then(response => { dispatch(unfollowAccountSuccess({relationship: response.data, statuses: getState().get('statuses')})); }).catch(error => { dispatch(unfollowAccountFail({ id, error })); @@ -183,7 +183,7 @@ export function blockAccount(id) { return (dispatch, getState) => { dispatch(blockAccountRequest(id)); - api(getState).post(`/api/v1/accounts/${id}/block`).then(response => { + api().post(`/api/v1/accounts/${id}/block`).then(response => { // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers dispatch(blockAccountSuccess({ relationship: response.data, statuses: getState().get('statuses') })); }).catch(error => { @@ -193,10 +193,10 @@ export function blockAccount(id) { } export function unblockAccount(id) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(unblockAccountRequest(id)); - api(getState).post(`/api/v1/accounts/${id}/unblock`).then(response => { + api().post(`/api/v1/accounts/${id}/unblock`).then(response => { dispatch(unblockAccountSuccess({ relationship: response.data })); }).catch(error => { dispatch(unblockAccountFail({ id, error })); @@ -236,7 +236,7 @@ export function muteAccount(id, notifications, duration=0) { return (dispatch, getState) => { dispatch(muteAccountRequest(id)); - api(getState).post(`/api/v1/accounts/${id}/mute`, { notifications, duration }).then(response => { + api().post(`/api/v1/accounts/${id}/mute`, { notifications, duration }).then(response => { // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers dispatch(muteAccountSuccess({ relationship: response.data, statuses: getState().get('statuses') })); }).catch(error => { @@ -246,10 +246,10 @@ export function muteAccount(id, notifications, duration=0) { } export function unmuteAccount(id) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(unmuteAccountRequest(id)); - api(getState).post(`/api/v1/accounts/${id}/unmute`).then(response => { + api().post(`/api/v1/accounts/${id}/unmute`).then(response => { dispatch(unmuteAccountSuccess({ relationship: response.data })); }).catch(error => { dispatch(unmuteAccountFail({ id, error })); @@ -287,10 +287,10 @@ export function unmuteAccountFail(error) { export function fetchFollowers(id) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(fetchFollowersRequest(id)); - api(getState).get(`/api/v1/accounts/${id}/followers`).then(response => { + api().get(`/api/v1/accounts/${id}/followers`).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); @@ -337,7 +337,7 @@ export function expandFollowers(id) { dispatch(expandFollowersRequest(id)); - api(getState).get(url).then(response => { + api().get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); @@ -374,10 +374,10 @@ export function expandFollowersFail(id, error) { } export function fetchFollowing(id) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(fetchFollowingRequest(id)); - api(getState).get(`/api/v1/accounts/${id}/following`).then(response => { + api().get(`/api/v1/accounts/${id}/following`).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); @@ -424,7 +424,7 @@ export function expandFollowing(id) { dispatch(expandFollowingRequest(id)); - api(getState).get(url).then(response => { + api().get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); @@ -473,7 +473,7 @@ export function fetchRelationships(accountIds) { dispatch(fetchRelationshipsRequest(newAccountIds)); - api(getState).get(`/api/v1/accounts/relationships?with_suspended=true&${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => { + api().get(`/api/v1/accounts/relationships?with_suspended=true&${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => { dispatch(fetchRelationshipsSuccess({ relationships: response.data })); }).catch(error => { dispatch(fetchRelationshipsFail(error)); @@ -499,10 +499,10 @@ export function fetchRelationshipsFail(error) { } export function fetchFollowRequests() { - return (dispatch, getState) => { + return (dispatch) => { dispatch(fetchFollowRequestsRequest()); - api(getState).get('/api/v1/follow_requests').then(response => { + api().get('/api/v1/follow_requests').then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); dispatch(fetchFollowRequestsSuccess(response.data, next ? next.uri : null)); @@ -541,7 +541,7 @@ export function expandFollowRequests() { dispatch(expandFollowRequestsRequest()); - api(getState).get(url).then(response => { + api().get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); dispatch(expandFollowRequestsSuccess(response.data, next ? next.uri : null)); @@ -571,10 +571,10 @@ export function expandFollowRequestsFail(error) { } export function authorizeFollowRequest(id) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(authorizeFollowRequestRequest(id)); - api(getState) + api() .post(`/api/v1/follow_requests/${id}/authorize`) .then(() => dispatch(authorizeFollowRequestSuccess({ id }))) .catch(error => dispatch(authorizeFollowRequestFail(id, error))); @@ -598,10 +598,10 @@ export function authorizeFollowRequestFail(id, error) { export function rejectFollowRequest(id) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(rejectFollowRequestRequest(id)); - api(getState) + api() .post(`/api/v1/follow_requests/${id}/reject`) .then(() => dispatch(rejectFollowRequestSuccess({ id }))) .catch(error => dispatch(rejectFollowRequestFail(id, error))); @@ -624,10 +624,10 @@ export function rejectFollowRequestFail(id, error) { } export function pinAccount(id) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(pinAccountRequest(id)); - api(getState).post(`/api/v1/accounts/${id}/pin`).then(response => { + api().post(`/api/v1/accounts/${id}/pin`).then(response => { dispatch(pinAccountSuccess({ relationship: response.data })); }).catch(error => { dispatch(pinAccountFail(error)); @@ -636,10 +636,10 @@ export function pinAccount(id) { } export function unpinAccount(id) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(unpinAccountRequest(id)); - api(getState).post(`/api/v1/accounts/${id}/unpin`).then(response => { + api().post(`/api/v1/accounts/${id}/unpin`).then(response => { dispatch(unpinAccountSuccess({ relationship: response.data })); }).catch(error => { dispatch(unpinAccountFail(error)); @@ -676,10 +676,10 @@ export function unpinAccountFail(error) { } export function fetchPinnedAccounts() { - return (dispatch, getState) => { + return (dispatch) => { dispatch(fetchPinnedAccountsRequest()); - api(getState).get('/api/v1/endorsements', { params: { limit: 0 } }).then(response => { + api().get('/api/v1/endorsements', { params: { limit: 0 } }).then(response => { dispatch(importFetchedAccounts(response.data)); dispatch(fetchPinnedAccountsSuccess(response.data)); }).catch(err => dispatch(fetchPinnedAccountsFail(err))); @@ -707,7 +707,7 @@ export function fetchPinnedAccountsFail(error) { }; } -export const updateAccount = ({ displayName, note, avatar, header, discoverable, indexable }) => (dispatch, getState) => { +export const updateAccount = ({ displayName, note, avatar, header, discoverable, indexable }) => (dispatch) => { const data = new FormData(); data.append('display_name', displayName); @@ -717,13 +717,13 @@ export const updateAccount = ({ displayName, note, avatar, header, discoverable, data.append('discoverable', discoverable); data.append('indexable', indexable); - return api(getState).patch('/api/v1/accounts/update_credentials', data).then(response => { + return api().patch('/api/v1/accounts/update_credentials', data).then(response => { dispatch(importFetchedAccount(response.data)); }); }; export function fetchPinnedAccountsSuggestions(q) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(fetchPinnedAccountsSuggestionsRequest()); const params = { @@ -733,7 +733,7 @@ export function fetchPinnedAccountsSuggestions(q) { following: true, }; - api(getState).get('/api/v1/accounts/search', { params }).then(response => { + api().get('/api/v1/accounts/search', { params }).then(response => { dispatch(importFetchedAccounts(response.data)); dispatch(fetchPinnedAccountsSuggestionsSuccess(q, response.data)); }).catch(err => dispatch(fetchPinnedAccountsSuggestionsFail(err))); diff --git a/app/javascript/flavours/glitch/actions/announcements.js b/app/javascript/flavours/glitch/actions/announcements.js index 339c5f3adc..7657b05dc4 100644 --- a/app/javascript/flavours/glitch/actions/announcements.js +++ b/app/javascript/flavours/glitch/actions/announcements.js @@ -26,10 +26,10 @@ export const ANNOUNCEMENTS_TOGGLE_SHOW = 'ANNOUNCEMENTS_TOGGLE_SHOW'; const noOp = () => {}; -export const fetchAnnouncements = (done = noOp) => (dispatch, getState) => { +export const fetchAnnouncements = (done = noOp) => (dispatch) => { dispatch(fetchAnnouncementsRequest()); - api(getState).get('/api/v1/announcements').then(response => { + api().get('/api/v1/announcements').then(response => { dispatch(fetchAnnouncementsSuccess(response.data.map(x => normalizeAnnouncement(x)))); }).catch(error => { dispatch(fetchAnnouncementsFail(error)); @@ -61,10 +61,10 @@ export const updateAnnouncements = announcement => ({ announcement: normalizeAnnouncement(announcement), }); -export const dismissAnnouncement = announcementId => (dispatch, getState) => { +export const dismissAnnouncement = announcementId => (dispatch) => { dispatch(dismissAnnouncementRequest(announcementId)); - api(getState).post(`/api/v1/announcements/${announcementId}/dismiss`).then(() => { + api().post(`/api/v1/announcements/${announcementId}/dismiss`).then(() => { dispatch(dismissAnnouncementSuccess(announcementId)); }).catch(error => { dispatch(dismissAnnouncementFail(announcementId, error)); @@ -103,7 +103,7 @@ export const addReaction = (announcementId, name) => (dispatch, getState) => { dispatch(addReactionRequest(announcementId, name, alreadyAdded)); } - api(getState).put(`/api/v1/announcements/${announcementId}/reactions/${encodeURIComponent(name)}`).then(() => { + api().put(`/api/v1/announcements/${announcementId}/reactions/${encodeURIComponent(name)}`).then(() => { dispatch(addReactionSuccess(announcementId, name, alreadyAdded)); }).catch(err => { if (!alreadyAdded) { @@ -134,10 +134,10 @@ export const addReactionFail = (announcementId, name, error) => ({ skipLoading: true, }); -export const removeReaction = (announcementId, name) => (dispatch, getState) => { +export const removeReaction = (announcementId, name) => (dispatch) => { dispatch(removeReactionRequest(announcementId, name)); - api(getState).delete(`/api/v1/announcements/${announcementId}/reactions/${encodeURIComponent(name)}`).then(() => { + api().delete(`/api/v1/announcements/${announcementId}/reactions/${encodeURIComponent(name)}`).then(() => { dispatch(removeReactionSuccess(announcementId, name)); }).catch(err => { dispatch(removeReactionFail(announcementId, name, err)); diff --git a/app/javascript/flavours/glitch/actions/blocks.js b/app/javascript/flavours/glitch/actions/blocks.js index 54296d0905..5c66e27bec 100644 --- a/app/javascript/flavours/glitch/actions/blocks.js +++ b/app/javascript/flavours/glitch/actions/blocks.js @@ -13,10 +13,10 @@ export const BLOCKS_EXPAND_SUCCESS = 'BLOCKS_EXPAND_SUCCESS'; export const BLOCKS_EXPAND_FAIL = 'BLOCKS_EXPAND_FAIL'; export function fetchBlocks() { - return (dispatch, getState) => { + return (dispatch) => { dispatch(fetchBlocksRequest()); - api(getState).get('/api/v1/blocks').then(response => { + api().get('/api/v1/blocks').then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); dispatch(fetchBlocksSuccess(response.data, next ? next.uri : null)); @@ -56,7 +56,7 @@ export function expandBlocks() { dispatch(expandBlocksRequest()); - api(getState).get(url).then(response => { + api().get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); dispatch(expandBlocksSuccess(response.data, next ? next.uri : null)); diff --git a/app/javascript/flavours/glitch/actions/bookmarks.js b/app/javascript/flavours/glitch/actions/bookmarks.js index 0b16f61e63..89716b224c 100644 --- a/app/javascript/flavours/glitch/actions/bookmarks.js +++ b/app/javascript/flavours/glitch/actions/bookmarks.js @@ -18,7 +18,7 @@ export function fetchBookmarkedStatuses() { dispatch(fetchBookmarkedStatusesRequest()); - api(getState).get('/api/v1/bookmarks').then(response => { + api().get('/api/v1/bookmarks').then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedStatuses(response.data)); dispatch(fetchBookmarkedStatusesSuccess(response.data, next ? next.uri : null)); @@ -59,7 +59,7 @@ export function expandBookmarkedStatuses() { dispatch(expandBookmarkedStatusesRequest()); - api(getState).get(url).then(response => { + api().get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedStatuses(response.data)); dispatch(expandBookmarkedStatusesSuccess(response.data, next ? next.uri : null)); diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js index fc4a0690e4..f0da25b5dc 100644 --- a/app/javascript/flavours/glitch/actions/compose.js +++ b/app/javascript/flavours/glitch/actions/compose.js @@ -211,7 +211,7 @@ export function submitCompose(routerHistory, overridePrivacy = null) { }); } - api(getState).request({ + api().request({ url: statusId === null ? '/api/v1/statuses' : `/api/v1/statuses/${statusId}`, method: statusId === null ? 'post' : 'put', data: { @@ -317,7 +317,7 @@ export function tenorSet(options) { export function uploadCompose(files, alt = '') { return function (dispatch, getState) { - const uploadLimit = 4; + const uploadLimit = getState().getIn(['server', 'server', 'configuration', 'statuses', 'max_media_attachments']); const media = getState().getIn(['compose', 'media_attachments']); const pending = getState().getIn(['compose', 'pending_media_attachments']); const progress = new Array(files.length).fill(0); @@ -332,7 +332,7 @@ export function uploadCompose(files, alt = '') { dispatch(uploadComposeRequest()); for (const [i, f] of Array.from(files).entries()) { - if (media.size + i > 3) break; + if (media.size + i > (uploadLimit - 1)) break; resizeImage(f).then(file => { const data = new FormData(); @@ -341,7 +341,7 @@ export function uploadCompose(files, alt = '') { // Account for disparity in size of original image and resized data total += file.size - f.size; - return api(getState).post('/api/v2/media', data, { + return api().post('/api/v2/media', data, { onUploadProgress: function({ loaded }){ progress[i] = loaded; dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total)); @@ -358,7 +358,7 @@ export function uploadCompose(files, alt = '') { let tryCount = 1; const poll = () => { - api(getState).get(`/api/v1/media/${data.id}`).then(response => { + api().get(`/api/v1/media/${data.id}`).then(response => { if (response.status === 200) { dispatch(uploadComposeSuccess(response.data, f)); } else if (response.status === 206) { @@ -381,7 +381,7 @@ export const uploadComposeProcessing = () => ({ type: COMPOSE_UPLOAD_PROCESSING, }); -export const uploadThumbnail = (id, file) => (dispatch, getState) => { +export const uploadThumbnail = (id, file) => (dispatch) => { dispatch(uploadThumbnailRequest()); const total = file.size; @@ -389,7 +389,7 @@ export const uploadThumbnail = (id, file) => (dispatch, getState) => { data.append('thumbnail', file); - api(getState).put(`/api/v1/media/${id}`, data, { + api().put(`/api/v1/media/${id}`, data, { onUploadProgress: ({ loaded }) => { dispatch(uploadThumbnailProgress(loaded, total)); }, @@ -472,7 +472,7 @@ export function changeUploadCompose(id, params) { dispatch(changeUploadComposeSuccess(data, true)); } else { - api(getState).put(`/api/v1/media/${id}`, params).then(response => { + api().put(`/api/v1/media/${id}`, params).then(response => { dispatch(changeUploadComposeSuccess(response.data, false)); }).catch(error => { dispatch(changeUploadComposeFail(id, error)); @@ -560,7 +560,7 @@ const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) => fetchComposeSuggestionsAccountsController = new AbortController(); - api(getState).get('/api/v1/accounts/search', { + api().get('/api/v1/accounts/search', { signal: fetchComposeSuggestionsAccountsController.signal, params: { @@ -594,7 +594,7 @@ const fetchComposeSuggestionsTags = throttle((dispatch, getState, token) => { fetchComposeSuggestionsTagsController = new AbortController(); - api(getState).get('/api/v2/search', { + api().get('/api/v2/search', { signal: fetchComposeSuggestionsTagsController.signal, params: { diff --git a/app/javascript/flavours/glitch/actions/conversations.js b/app/javascript/flavours/glitch/actions/conversations.js index 8c4c4529fb..03174c485d 100644 --- a/app/javascript/flavours/glitch/actions/conversations.js +++ b/app/javascript/flavours/glitch/actions/conversations.js @@ -28,13 +28,13 @@ export const unmountConversations = () => ({ type: CONVERSATIONS_UNMOUNT, }); -export const markConversationRead = conversationId => (dispatch, getState) => { +export const markConversationRead = conversationId => (dispatch) => { dispatch({ type: CONVERSATIONS_READ, id: conversationId, }); - api(getState).post(`/api/v1/conversations/${conversationId}/read`); + api().post(`/api/v1/conversations/${conversationId}/read`); }; export const expandConversations = ({ maxId } = {}) => (dispatch, getState) => { @@ -48,7 +48,7 @@ export const expandConversations = ({ maxId } = {}) => (dispatch, getState) => { const isLoadingRecent = !!params.since_id; - api(getState).get('/api/v1/conversations', { params }) + api().get('/api/v1/conversations', { params }) .then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); @@ -88,10 +88,10 @@ export const updateConversations = conversation => dispatch => { }); }; -export const deleteConversation = conversationId => (dispatch, getState) => { +export const deleteConversation = conversationId => (dispatch) => { dispatch(deleteConversationRequest(conversationId)); - api(getState).delete(`/api/v1/conversations/${conversationId}`) + api().delete(`/api/v1/conversations/${conversationId}`) .then(() => dispatch(deleteConversationSuccess(conversationId))) .catch(error => dispatch(deleteConversationFail(conversationId, error))); }; diff --git a/app/javascript/flavours/glitch/actions/custom_emojis.js b/app/javascript/flavours/glitch/actions/custom_emojis.js index 9ec8156b17..fb65f072dc 100644 --- a/app/javascript/flavours/glitch/actions/custom_emojis.js +++ b/app/javascript/flavours/glitch/actions/custom_emojis.js @@ -5,10 +5,10 @@ export const CUSTOM_EMOJIS_FETCH_SUCCESS = 'CUSTOM_EMOJIS_FETCH_SUCCESS'; export const CUSTOM_EMOJIS_FETCH_FAIL = 'CUSTOM_EMOJIS_FETCH_FAIL'; export function fetchCustomEmojis() { - return (dispatch, getState) => { + return (dispatch) => { dispatch(fetchCustomEmojisRequest()); - api(getState).get('/api/v1/custom_emojis').then(response => { + api().get('/api/v1/custom_emojis').then(response => { dispatch(fetchCustomEmojisSuccess(response.data)); }).catch(error => { dispatch(fetchCustomEmojisFail(error)); diff --git a/app/javascript/flavours/glitch/actions/directory.js b/app/javascript/flavours/glitch/actions/directory.js deleted file mode 100644 index cda63f2b5a..0000000000 --- a/app/javascript/flavours/glitch/actions/directory.js +++ /dev/null @@ -1,62 +0,0 @@ -import api from '../api'; - -import { fetchRelationships } from './accounts'; -import { importFetchedAccounts } from './importer'; - -export const DIRECTORY_FETCH_REQUEST = 'DIRECTORY_FETCH_REQUEST'; -export const DIRECTORY_FETCH_SUCCESS = 'DIRECTORY_FETCH_SUCCESS'; -export const DIRECTORY_FETCH_FAIL = 'DIRECTORY_FETCH_FAIL'; - -export const DIRECTORY_EXPAND_REQUEST = 'DIRECTORY_EXPAND_REQUEST'; -export const DIRECTORY_EXPAND_SUCCESS = 'DIRECTORY_EXPAND_SUCCESS'; -export const DIRECTORY_EXPAND_FAIL = 'DIRECTORY_EXPAND_FAIL'; - -export const fetchDirectory = params => (dispatch, getState) => { - dispatch(fetchDirectoryRequest()); - - api(getState).get('/api/v1/directory', { params: { ...params, limit: 20 } }).then(({ data }) => { - dispatch(importFetchedAccounts(data)); - dispatch(fetchDirectorySuccess(data)); - dispatch(fetchRelationships(data.map(x => x.id))); - }).catch(error => dispatch(fetchDirectoryFail(error))); -}; - -export const fetchDirectoryRequest = () => ({ - type: DIRECTORY_FETCH_REQUEST, -}); - -export const fetchDirectorySuccess = accounts => ({ - type: DIRECTORY_FETCH_SUCCESS, - accounts, -}); - -export const fetchDirectoryFail = error => ({ - type: DIRECTORY_FETCH_FAIL, - error, -}); - -export const expandDirectory = params => (dispatch, getState) => { - dispatch(expandDirectoryRequest()); - - const loadedItems = getState().getIn(['user_lists', 'directory', 'items']).size; - - api(getState).get('/api/v1/directory', { params: { ...params, offset: loadedItems, limit: 20 } }).then(({ data }) => { - dispatch(importFetchedAccounts(data)); - dispatch(expandDirectorySuccess(data)); - dispatch(fetchRelationships(data.map(x => x.id))); - }).catch(error => dispatch(expandDirectoryFail(error))); -}; - -export const expandDirectoryRequest = () => ({ - type: DIRECTORY_EXPAND_REQUEST, -}); - -export const expandDirectorySuccess = accounts => ({ - type: DIRECTORY_EXPAND_SUCCESS, - accounts, -}); - -export const expandDirectoryFail = error => ({ - type: DIRECTORY_EXPAND_FAIL, - error, -}); diff --git a/app/javascript/flavours/glitch/actions/directory.ts b/app/javascript/flavours/glitch/actions/directory.ts new file mode 100644 index 0000000000..3e0f1356b3 --- /dev/null +++ b/app/javascript/flavours/glitch/actions/directory.ts @@ -0,0 +1,37 @@ +import type { List as ImmutableList } from 'immutable'; + +import { apiGetDirectory } from 'flavours/glitch/api/directory'; +import { createDataLoadingThunk } from 'flavours/glitch/store/typed_functions'; + +import { fetchRelationships } from './accounts'; +import { importFetchedAccounts } from './importer'; + +export const fetchDirectory = createDataLoadingThunk( + 'directory/fetch', + async (params: Parameters[0]) => + apiGetDirectory(params), + (data, { dispatch }) => { + dispatch(importFetchedAccounts(data)); + dispatch(fetchRelationships(data.map((x) => x.id))); + + return { accounts: data }; + }, +); + +export const expandDirectory = createDataLoadingThunk( + 'directory/expand', + async (params: Parameters[0], { getState }) => { + const loadedItems = getState().user_lists.getIn([ + 'directory', + 'items', + ]) as ImmutableList; + + return apiGetDirectory({ ...params, offset: loadedItems.size }, 20); + }, + (data, { dispatch }) => { + dispatch(importFetchedAccounts(data)); + dispatch(fetchRelationships(data.map((x) => x.id))); + + return { accounts: data }; + }, +); diff --git a/app/javascript/flavours/glitch/actions/domain_blocks.js b/app/javascript/flavours/glitch/actions/domain_blocks.js index 55c0a6ce9d..727f800af3 100644 --- a/app/javascript/flavours/glitch/actions/domain_blocks.js +++ b/app/javascript/flavours/glitch/actions/domain_blocks.js @@ -24,7 +24,7 @@ export function blockDomain(domain) { return (dispatch, getState) => { dispatch(blockDomainRequest(domain)); - api(getState).post('/api/v1/domain_blocks', { domain }).then(() => { + api().post('/api/v1/domain_blocks', { domain }).then(() => { const at_domain = '@' + domain; const accounts = getState().get('accounts').filter(item => item.get('acct').endsWith(at_domain)).valueSeq().map(item => item.get('id')); @@ -54,7 +54,7 @@ export function unblockDomain(domain) { return (dispatch, getState) => { dispatch(unblockDomainRequest(domain)); - api(getState).delete('/api/v1/domain_blocks', { params: { domain } }).then(() => { + api().delete('/api/v1/domain_blocks', { params: { domain } }).then(() => { const at_domain = '@' + domain; const accounts = getState().get('accounts').filter(item => item.get('acct').endsWith(at_domain)).valueSeq().map(item => item.get('id')); dispatch(unblockDomainSuccess({ domain, accounts })); @@ -80,10 +80,10 @@ export function unblockDomainFail(domain, error) { } export function fetchDomainBlocks() { - return (dispatch, getState) => { + return (dispatch) => { dispatch(fetchDomainBlocksRequest()); - api(getState).get('/api/v1/domain_blocks').then(response => { + api().get('/api/v1/domain_blocks').then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(fetchDomainBlocksSuccess(response.data, next ? next.uri : null)); }).catch(err => { @@ -123,7 +123,7 @@ export function expandDomainBlocks() { dispatch(expandDomainBlocksRequest()); - api(getState).get(url).then(response => { + api().get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(expandDomainBlocksSuccess(response.data, next ? next.uri : null)); }).catch(err => { diff --git a/app/javascript/flavours/glitch/actions/favourites.js b/app/javascript/flavours/glitch/actions/favourites.js index 2d4d4e6206..ff475c82be 100644 --- a/app/javascript/flavours/glitch/actions/favourites.js +++ b/app/javascript/flavours/glitch/actions/favourites.js @@ -18,7 +18,7 @@ export function fetchFavouritedStatuses() { dispatch(fetchFavouritedStatusesRequest()); - api(getState).get('/api/v1/favourites').then(response => { + api().get('/api/v1/favourites').then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedStatuses(response.data)); dispatch(fetchFavouritedStatusesSuccess(response.data, next ? next.uri : null)); @@ -62,7 +62,7 @@ export function expandFavouritedStatuses() { dispatch(expandFavouritedStatusesRequest()); - api(getState).get(url).then(response => { + api().get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedStatuses(response.data)); dispatch(expandFavouritedStatusesSuccess(response.data, next ? next.uri : null)); diff --git a/app/javascript/flavours/glitch/actions/featured_tags.js b/app/javascript/flavours/glitch/actions/featured_tags.js index 18bb615394..6ee4dee2bc 100644 --- a/app/javascript/flavours/glitch/actions/featured_tags.js +++ b/app/javascript/flavours/glitch/actions/featured_tags.js @@ -11,7 +11,7 @@ export const fetchFeaturedTags = (id) => (dispatch, getState) => { dispatch(fetchFeaturedTagsRequest(id)); - api(getState).get(`/api/v1/accounts/${id}/featured_tags`) + api().get(`/api/v1/accounts/${id}/featured_tags`) .then(({ data }) => dispatch(fetchFeaturedTagsSuccess(id, data))) .catch(err => dispatch(fetchFeaturedTagsFail(id, err))); }; diff --git a/app/javascript/flavours/glitch/actions/filters.js b/app/javascript/flavours/glitch/actions/filters.js index a11956ac56..588e390f0a 100644 --- a/app/javascript/flavours/glitch/actions/filters.js +++ b/app/javascript/flavours/glitch/actions/filters.js @@ -23,13 +23,13 @@ export const initAddFilter = (status, { contextType }) => dispatch => }, })); -export const fetchFilters = () => (dispatch, getState) => { +export const fetchFilters = () => (dispatch) => { dispatch({ type: FILTERS_FETCH_REQUEST, skipLoading: true, }); - api(getState) + api() .get('/api/v2/filters') .then(({ data }) => dispatch({ type: FILTERS_FETCH_SUCCESS, @@ -44,10 +44,10 @@ export const fetchFilters = () => (dispatch, getState) => { })); }; -export const createFilterStatus = (params, onSuccess, onFail) => (dispatch, getState) => { +export const createFilterStatus = (params, onSuccess, onFail) => (dispatch) => { dispatch(createFilterStatusRequest()); - api(getState).post(`/api/v2/filters/${params.filter_id}/statuses`, params).then(response => { + api().post(`/api/v2/filters/${params.filter_id}/statuses`, params).then(response => { dispatch(createFilterStatusSuccess(response.data)); if (onSuccess) onSuccess(); }).catch(error => { @@ -70,10 +70,10 @@ export const createFilterStatusFail = error => ({ error, }); -export const createFilter = (params, onSuccess, onFail) => (dispatch, getState) => { +export const createFilter = (params, onSuccess, onFail) => (dispatch) => { dispatch(createFilterRequest()); - api(getState).post('/api/v2/filters', params).then(response => { + api().post('/api/v2/filters', params).then(response => { dispatch(createFilterSuccess(response.data)); if (onSuccess) onSuccess(response.data); }).catch(error => { diff --git a/app/javascript/flavours/glitch/actions/history.js b/app/javascript/flavours/glitch/actions/history.js index 52401b7dce..07732ea187 100644 --- a/app/javascript/flavours/glitch/actions/history.js +++ b/app/javascript/flavours/glitch/actions/history.js @@ -15,7 +15,7 @@ export const fetchHistory = statusId => (dispatch, getState) => { dispatch(fetchHistoryRequest(statusId)); - api(getState).get(`/api/v1/statuses/${statusId}/history`).then(({ data }) => { + api().get(`/api/v1/statuses/${statusId}/history`).then(({ data }) => { dispatch(importFetchedAccounts(data.map(x => x.account))); dispatch(fetchHistorySuccess(statusId, data)); }).catch(error => dispatch(fetchHistoryFail(error))); diff --git a/app/javascript/flavours/glitch/actions/importer/index.js b/app/javascript/flavours/glitch/actions/importer/index.js index 5fbc9bb5bb..7341ba8550 100644 --- a/app/javascript/flavours/glitch/actions/importer/index.js +++ b/app/javascript/flavours/glitch/actions/importer/index.js @@ -68,13 +68,17 @@ export function importFetchedStatuses(statuses) { status.filtered.forEach(result => pushUnique(filters, result.filter)); } - if (status.reblog && status.reblog.id) { + if (status.reblog?.id) { processStatus(status.reblog); } - if (status.poll && status.poll.id) { + if (status.poll?.id) { pushUnique(polls, normalizePoll(status.poll, getState().getIn(['polls', status.poll.id]))); } + + if (status.card) { + status.card.authors.forEach(author => author.account && pushUnique(accounts, author.account)); + } } statuses.forEach(processStatus); diff --git a/app/javascript/flavours/glitch/actions/importer/normalizer.js b/app/javascript/flavours/glitch/actions/importer/normalizer.js index c2ad0f9908..5f10c8d889 100644 --- a/app/javascript/flavours/glitch/actions/importer/normalizer.js +++ b/app/javascript/flavours/glitch/actions/importer/normalizer.js @@ -36,6 +36,17 @@ export function normalizeStatus(status, normalOldStatus, settings) { normalStatus.poll = status.poll.id; } + if (status.card) { + normalStatus.card = { + ...status.card, + authors: status.card.authors.map(author => ({ + ...author, + accountId: author.account?.id, + account: undefined, + })), + }; + } + if (status.filtered) { normalStatus.filtered = status.filtered.map(normalizeFilterResult); } diff --git a/app/javascript/flavours/glitch/actions/interactions.js b/app/javascript/flavours/glitch/actions/interactions.js index 60f46f0cbc..0b4d11cf43 100644 --- a/app/javascript/flavours/glitch/actions/interactions.js +++ b/app/javascript/flavours/glitch/actions/interactions.js @@ -3,10 +3,6 @@ import api, { getLinks } from '../api'; import { fetchRelationships } from './accounts'; import { importFetchedAccounts, importFetchedStatus } from './importer'; -export const REBLOG_REQUEST = 'REBLOG_REQUEST'; -export const REBLOG_SUCCESS = 'REBLOG_SUCCESS'; -export const REBLOG_FAIL = 'REBLOG_FAIL'; - export const REBLOGS_EXPAND_REQUEST = 'REBLOGS_EXPAND_REQUEST'; export const REBLOGS_EXPAND_SUCCESS = 'REBLOGS_EXPAND_SUCCESS'; export const REBLOGS_EXPAND_FAIL = 'REBLOGS_EXPAND_FAIL'; @@ -15,10 +11,6 @@ export const FAVOURITE_REQUEST = 'FAVOURITE_REQUEST'; export const FAVOURITE_SUCCESS = 'FAVOURITE_SUCCESS'; export const FAVOURITE_FAIL = 'FAVOURITE_FAIL'; -export const UNREBLOG_REQUEST = 'UNREBLOG_REQUEST'; -export const UNREBLOG_SUCCESS = 'UNREBLOG_SUCCESS'; -export const UNREBLOG_FAIL = 'UNREBLOG_FAIL'; - export const UNFAVOURITE_REQUEST = 'UNFAVOURITE_REQUEST'; export const UNFAVOURITE_SUCCESS = 'UNFAVOURITE_SUCCESS'; export const UNFAVOURITE_FAIL = 'UNFAVOURITE_FAIL'; @@ -61,89 +53,13 @@ export const REACTION_REMOVE_REQUEST = 'REACTION_REMOVE_REQUEST'; export const REACTION_REMOVE_SUCCESS = 'REACTION_REMOVE_SUCCESS'; export const REACTION_REMOVE_FAIL = 'REACTION_REMOVE_FAIL'; -export function reblog(status, visibility) { - return function (dispatch, getState) { - dispatch(reblogRequest(status)); - - api(getState).post(`/api/v1/statuses/${status.get('id')}/reblog`, { visibility }).then(function (response) { - // The reblog API method returns a new status wrapped around the original. In this case we are only - // interested in how the original is modified, hence passing it skipping the wrapper - dispatch(importFetchedStatus(response.data.reblog)); - dispatch(reblogSuccess(status)); - }).catch(function (error) { - dispatch(reblogFail(status, error)); - }); - }; -} - -export function unreblog(status) { - return (dispatch, getState) => { - dispatch(unreblogRequest(status)); - - api(getState).post(`/api/v1/statuses/${status.get('id')}/unreblog`).then(response => { - dispatch(importFetchedStatus(response.data)); - dispatch(unreblogSuccess(status)); - }).catch(error => { - dispatch(unreblogFail(status, error)); - }); - }; -} - -export function reblogRequest(status) { - return { - type: REBLOG_REQUEST, - status: status, - skipLoading: true, - }; -} - -export function reblogSuccess(status) { - return { - type: REBLOG_SUCCESS, - status: status, - skipLoading: true, - }; -} - -export function reblogFail(status, error) { - return { - type: REBLOG_FAIL, - status: status, - error: error, - skipLoading: true, - }; -} - -export function unreblogRequest(status) { - return { - type: UNREBLOG_REQUEST, - status: status, - skipLoading: true, - }; -} - -export function unreblogSuccess(status) { - return { - type: UNREBLOG_SUCCESS, - status: status, - skipLoading: true, - }; -} - -export function unreblogFail(status, error) { - return { - type: UNREBLOG_FAIL, - status: status, - error: error, - skipLoading: true, - }; -} +export * from "./interactions_typed"; export function favourite(status) { - return function (dispatch, getState) { + return function (dispatch) { dispatch(favouriteRequest(status)); - api(getState).post(`/api/v1/statuses/${status.get('id')}/favourite`).then(function (response) { + api().post(`/api/v1/statuses/${status.get('id')}/favourite`).then(function (response) { dispatch(importFetchedStatus(response.data)); dispatch(favouriteSuccess(status)); }).catch(function (error) { @@ -153,10 +69,10 @@ export function favourite(status) { } export function unfavourite(status) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(unfavouriteRequest(status)); - api(getState).post(`/api/v1/statuses/${status.get('id')}/unfavourite`).then(response => { + api().post(`/api/v1/statuses/${status.get('id')}/unfavourite`).then(response => { dispatch(importFetchedStatus(response.data)); dispatch(unfavouriteSuccess(status)); }).catch(error => { @@ -216,10 +132,10 @@ export function unfavouriteFail(status, error) { } export function bookmark(status) { - return function (dispatch, getState) { + return function (dispatch) { dispatch(bookmarkRequest(status)); - api(getState).post(`/api/v1/statuses/${status.get('id')}/bookmark`).then(function (response) { + api().post(`/api/v1/statuses/${status.get('id')}/bookmark`).then(function (response) { dispatch(importFetchedStatus(response.data)); dispatch(bookmarkSuccess(status, response.data)); }).catch(function (error) { @@ -229,10 +145,10 @@ export function bookmark(status) { } export function unbookmark(status) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(unbookmarkRequest(status)); - api(getState).post(`/api/v1/statuses/${status.get('id')}/unbookmark`).then(response => { + api().post(`/api/v1/statuses/${status.get('id')}/unbookmark`).then(response => { dispatch(importFetchedStatus(response.data)); dispatch(unbookmarkSuccess(status, response.data)); }).catch(error => { @@ -288,10 +204,10 @@ export function unbookmarkFail(status, error) { } export function fetchReblogs(id) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(fetchReblogsRequest(id)); - api(getState).get(`/api/v1/statuses/${id}/reblogged_by`).then(response => { + api().get(`/api/v1/statuses/${id}/reblogged_by`).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); dispatch(fetchReblogsSuccess(id, response.data, next ? next.uri : null)); @@ -335,7 +251,7 @@ export function expandReblogs(id) { dispatch(expandReblogsRequest(id)); - api(getState).get(url).then(response => { + api().get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); @@ -370,10 +286,10 @@ export function expandReblogsFail(id, error) { } export function fetchFavourites(id) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(fetchFavouritesRequest(id)); - api(getState).get(`/api/v1/statuses/${id}/favourited_by`).then(response => { + api().get(`/api/v1/statuses/${id}/favourited_by`).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); dispatch(fetchFavouritesSuccess(id, response.data, next ? next.uri : null)); @@ -417,7 +333,7 @@ export function expandFavourites(id) { dispatch(expandFavouritesRequest(id)); - api(getState).get(url).then(response => { + api().get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); @@ -452,10 +368,10 @@ export function expandFavouritesFail(id, error) { } export function pin(status) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(pinRequest(status)); - api(getState).post(`/api/v1/statuses/${status.get('id')}/pin`).then(response => { + api().post(`/api/v1/statuses/${status.get('id')}/pin`).then(response => { dispatch(importFetchedStatus(response.data)); dispatch(pinSuccess(status)); }).catch(error => { @@ -490,10 +406,10 @@ export function pinFail(status, error) { } export function unpin (status) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(unpinRequest(status)); - api(getState).post(`/api/v1/statuses/${status.get('id')}/unpin`).then(response => { + api().post(`/api/v1/statuses/${status.get('id')}/unpin`).then(response => { dispatch(importFetchedStatus(response.data)); dispatch(unpinSuccess(status)); }).catch(error => { diff --git a/app/javascript/flavours/glitch/actions/interactions_typed.ts b/app/javascript/flavours/glitch/actions/interactions_typed.ts new file mode 100644 index 0000000000..075fc242e4 --- /dev/null +++ b/app/javascript/flavours/glitch/actions/interactions_typed.ts @@ -0,0 +1,35 @@ +import { apiReblog, apiUnreblog } from 'flavours/glitch/api/interactions'; +import type { StatusVisibility } from 'flavours/glitch/models/status'; +import { createDataLoadingThunk } from 'flavours/glitch/store/typed_functions'; + +import { importFetchedStatus } from './importer'; + +export const reblog = createDataLoadingThunk( + 'status/reblog', + ({ + statusId, + visibility, + }: { + statusId: string; + visibility: StatusVisibility; + }) => apiReblog(statusId, visibility), + (data, { dispatch, discardLoadData }) => { + // The reblog API method returns a new status wrapped around the original. In this case we are only + // interested in how the original is modified, hence passing it skipping the wrapper + dispatch(importFetchedStatus(data.reblog)); + + // The payload is not used in any actions + return discardLoadData; + }, +); + +export const unreblog = createDataLoadingThunk( + 'status/unreblog', + ({ statusId }: { statusId: string }) => apiUnreblog(statusId), + (data, { dispatch, discardLoadData }) => { + dispatch(importFetchedStatus(data)); + + // The payload is not used in any actions + return discardLoadData; + }, +); diff --git a/app/javascript/flavours/glitch/actions/lists.js b/app/javascript/flavours/glitch/actions/lists.js index b0789cd426..9956059387 100644 --- a/app/javascript/flavours/glitch/actions/lists.js +++ b/app/javascript/flavours/glitch/actions/lists.js @@ -57,7 +57,7 @@ export const fetchList = id => (dispatch, getState) => { dispatch(fetchListRequest(id)); - api(getState).get(`/api/v1/lists/${id}`) + api().get(`/api/v1/lists/${id}`) .then(({ data }) => dispatch(fetchListSuccess(data))) .catch(err => dispatch(fetchListFail(id, err))); }; @@ -78,10 +78,10 @@ export const fetchListFail = (id, error) => ({ error, }); -export const fetchLists = () => (dispatch, getState) => { +export const fetchLists = () => (dispatch) => { dispatch(fetchListsRequest()); - api(getState).get('/api/v1/lists') + api().get('/api/v1/lists') .then(({ data }) => dispatch(fetchListsSuccess(data))) .catch(err => dispatch(fetchListsFail(err))); }; @@ -125,10 +125,10 @@ export const changeListEditorTitle = value => ({ value, }); -export const createList = (title, shouldReset) => (dispatch, getState) => { +export const createList = (title, shouldReset) => (dispatch) => { dispatch(createListRequest()); - api(getState).post('/api/v1/lists', { title }).then(({ data }) => { + api().post('/api/v1/lists', { title }).then(({ data }) => { dispatch(createListSuccess(data)); if (shouldReset) { @@ -151,10 +151,10 @@ export const createListFail = error => ({ error, }); -export const updateList = (id, title, shouldReset, isExclusive, replies_policy) => (dispatch, getState) => { +export const updateList = (id, title, shouldReset, isExclusive, replies_policy) => (dispatch) => { dispatch(updateListRequest(id)); - api(getState).put(`/api/v1/lists/${id}`, { title, replies_policy, exclusive: typeof isExclusive === 'undefined' ? undefined : !!isExclusive }).then(({ data }) => { + api().put(`/api/v1/lists/${id}`, { title, replies_policy, exclusive: typeof isExclusive === 'undefined' ? undefined : !!isExclusive }).then(({ data }) => { dispatch(updateListSuccess(data)); if (shouldReset) { @@ -183,10 +183,10 @@ export const resetListEditor = () => ({ type: LIST_EDITOR_RESET, }); -export const deleteList = id => (dispatch, getState) => { +export const deleteList = id => (dispatch) => { dispatch(deleteListRequest(id)); - api(getState).delete(`/api/v1/lists/${id}`) + api().delete(`/api/v1/lists/${id}`) .then(() => dispatch(deleteListSuccess(id))) .catch(err => dispatch(deleteListFail(id, err))); }; @@ -207,10 +207,10 @@ export const deleteListFail = (id, error) => ({ error, }); -export const fetchListAccounts = listId => (dispatch, getState) => { +export const fetchListAccounts = listId => (dispatch) => { dispatch(fetchListAccountsRequest(listId)); - api(getState).get(`/api/v1/lists/${listId}/accounts`, { params: { limit: 0 } }).then(({ data }) => { + api().get(`/api/v1/lists/${listId}/accounts`, { params: { limit: 0 } }).then(({ data }) => { dispatch(importFetchedAccounts(data)); dispatch(fetchListAccountsSuccess(listId, data)); }).catch(err => dispatch(fetchListAccountsFail(listId, err))); @@ -234,7 +234,7 @@ export const fetchListAccountsFail = (id, error) => ({ error, }); -export const fetchListSuggestions = q => (dispatch, getState) => { +export const fetchListSuggestions = q => (dispatch) => { const params = { q, resolve: false, @@ -242,7 +242,7 @@ export const fetchListSuggestions = q => (dispatch, getState) => { following: true, }; - api(getState).get('/api/v1/accounts/search', { params }).then(({ data }) => { + api().get('/api/v1/accounts/search', { params }).then(({ data }) => { dispatch(importFetchedAccounts(data)); dispatch(fetchListSuggestionsReady(q, data)); }).catch(error => dispatch(showAlertForError(error))); @@ -267,10 +267,10 @@ export const addToListEditor = accountId => (dispatch, getState) => { dispatch(addToList(getState().getIn(['listEditor', 'listId']), accountId)); }; -export const addToList = (listId, accountId) => (dispatch, getState) => { +export const addToList = (listId, accountId) => (dispatch) => { dispatch(addToListRequest(listId, accountId)); - api(getState).post(`/api/v1/lists/${listId}/accounts`, { account_ids: [accountId] }) + api().post(`/api/v1/lists/${listId}/accounts`, { account_ids: [accountId] }) .then(() => dispatch(addToListSuccess(listId, accountId))) .catch(err => dispatch(addToListFail(listId, accountId, err))); }; @@ -298,10 +298,10 @@ export const removeFromListEditor = accountId => (dispatch, getState) => { dispatch(removeFromList(getState().getIn(['listEditor', 'listId']), accountId)); }; -export const removeFromList = (listId, accountId) => (dispatch, getState) => { +export const removeFromList = (listId, accountId) => (dispatch) => { dispatch(removeFromListRequest(listId, accountId)); - api(getState).delete(`/api/v1/lists/${listId}/accounts`, { params: { account_ids: [accountId] } }) + api().delete(`/api/v1/lists/${listId}/accounts`, { params: { account_ids: [accountId] } }) .then(() => dispatch(removeFromListSuccess(listId, accountId))) .catch(err => dispatch(removeFromListFail(listId, accountId, err))); }; @@ -338,10 +338,10 @@ export const setupListAdder = accountId => (dispatch, getState) => { dispatch(fetchAccountLists(accountId)); }; -export const fetchAccountLists = accountId => (dispatch, getState) => { +export const fetchAccountLists = accountId => (dispatch) => { dispatch(fetchAccountListsRequest(accountId)); - api(getState).get(`/api/v1/accounts/${accountId}/lists`) + api().get(`/api/v1/accounts/${accountId}/lists`) .then(({ data }) => dispatch(fetchAccountListsSuccess(accountId, data))) .catch(err => dispatch(fetchAccountListsFail(accountId, err))); }; @@ -370,4 +370,3 @@ export const addToListAdder = listId => (dispatch, getState) => { export const removeFromListAdder = listId => (dispatch, getState) => { dispatch(removeFromList(listId, getState().getIn(['listAdder', 'accountId']))); }; - diff --git a/app/javascript/flavours/glitch/actions/markers.ts b/app/javascript/flavours/glitch/actions/markers.ts index 78362a9ca3..a85af1c4be 100644 --- a/app/javascript/flavours/glitch/actions/markers.ts +++ b/app/javascript/flavours/glitch/actions/markers.ts @@ -1,21 +1,24 @@ -import { List as ImmutableList } from 'immutable'; - import { debounce } from 'lodash'; import type { MarkerJSON } from 'flavours/glitch/api_types/markers'; -import type { RootState } from 'flavours/glitch/store'; +import { getAccessToken } from 'flavours/glitch/initial_state'; +import type { AppDispatch, RootState } from 'flavours/glitch/store'; import { createAppAsyncThunk } from 'flavours/glitch/store/typed_functions'; -import api, { authorizationTokenFromState } from '../api'; +import api from '../api'; import { compareId } from '../compare_id'; export const synchronouslySubmitMarkers = createAppAsyncThunk( 'markers/submit', async (_args, { getState }) => { - const accessToken = authorizationTokenFromState(getState); + const accessToken = getAccessToken(); const params = buildPostMarkersParams(getState()); - if (Object.keys(params).length === 0 || !accessToken) { + if ( + Object.keys(params).length === 0 || + !accessToken || + accessToken === '' + ) { return; } @@ -71,34 +74,17 @@ interface MarkerParam { last_read_id?: string; } -function getLastHomeId(state: RootState): string | undefined { - /* eslint-disable @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */ - return ( - state - // @ts-expect-error state.timelines is not yet typed - .getIn(['timelines', 'home', 'items'], ImmutableList()) - // @ts-expect-error state.timelines is not yet typed - .find((item) => item !== null) - ); -} - function getLastNotificationId(state: RootState): string | undefined { // @ts-expect-error state.notifications is not yet typed + // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call return state.getIn(['notifications', 'lastReadId']); } const buildPostMarkersParams = (state: RootState) => { const params = {} as { home?: MarkerParam; notifications?: MarkerParam }; - const lastHomeId = getLastHomeId(state); const lastNotificationId = getLastNotificationId(state); - if (lastHomeId && compareId(lastHomeId, state.markers.home) > 0) { - params.home = { - last_read_id: lastHomeId, - }; - } - if ( lastNotificationId && lastNotificationId !== '0' && @@ -116,14 +102,14 @@ export const submitMarkersAction = createAppAsyncThunk<{ home: string | undefined; notifications: string | undefined; }>('markers/submitAction', async (_args, { getState }) => { - const accessToken = authorizationTokenFromState(getState); + const accessToken = getAccessToken(); const params = buildPostMarkersParams(getState()); - if (Object.keys(params).length === 0 || accessToken === '') { + if (Object.keys(params).length === 0 || !accessToken || accessToken === '') { return { home: undefined, notifications: undefined }; } - await api(getState).post('/api/v1/markers', params); + await api().post('/api/v1/markers', params); return { home: params.home?.last_read_id, @@ -132,8 +118,8 @@ export const submitMarkersAction = createAppAsyncThunk<{ }); const debouncedSubmitMarkers = debounce( - (dispatch) => { - dispatch(submitMarkersAction()); + (dispatch: AppDispatch) => { + void dispatch(submitMarkersAction()); }, 300000, { @@ -153,14 +139,11 @@ export const submitMarkers = createAppAsyncThunk( }, ); -export const fetchMarkers = createAppAsyncThunk( - 'markers/fetch', - async (_args, { getState }) => { - const response = await api(getState).get>( - `/api/v1/markers`, - { params: { timeline: ['notifications'] } }, - ); +export const fetchMarkers = createAppAsyncThunk('markers/fetch', async () => { + const response = await api().get>( + `/api/v1/markers`, + { params: { timeline: ['notifications'] } }, + ); - return { markers: response.data }; - }, -); + return { markers: response.data }; +}); diff --git a/app/javascript/flavours/glitch/actions/mutes.js b/app/javascript/flavours/glitch/actions/mutes.js index 99c113f414..3676748cf3 100644 --- a/app/javascript/flavours/glitch/actions/mutes.js +++ b/app/javascript/flavours/glitch/actions/mutes.js @@ -13,10 +13,10 @@ export const MUTES_EXPAND_SUCCESS = 'MUTES_EXPAND_SUCCESS'; export const MUTES_EXPAND_FAIL = 'MUTES_EXPAND_FAIL'; export function fetchMutes() { - return (dispatch, getState) => { + return (dispatch) => { dispatch(fetchMutesRequest()); - api(getState).get('/api/v1/mutes').then(response => { + api().get('/api/v1/mutes').then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); dispatch(fetchMutesSuccess(response.data, next ? next.uri : null)); @@ -56,7 +56,7 @@ export function expandMutes() { dispatch(expandMutesRequest()); - api(getState).get(url).then(response => { + api().get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); dispatch(expandMutesSuccess(response.data, next ? next.uri : null)); diff --git a/app/javascript/flavours/glitch/actions/notification_policies.ts b/app/javascript/flavours/glitch/actions/notification_policies.ts new file mode 100644 index 0000000000..76452de324 --- /dev/null +++ b/app/javascript/flavours/glitch/actions/notification_policies.ts @@ -0,0 +1,16 @@ +import { + apiGetNotificationPolicy, + apiUpdateNotificationsPolicy, +} from 'flavours/glitch/api/notification_policies'; +import type { NotificationPolicy } from 'flavours/glitch/models/notification_policy'; +import { createDataLoadingThunk } from 'flavours/glitch/store/typed_functions'; + +export const fetchNotificationPolicy = createDataLoadingThunk( + 'notificationPolicy/fetch', + () => apiGetNotificationPolicy(), +); + +export const updateNotificationsPolicy = createDataLoadingThunk( + 'notificationPolicy/update', + (policy: Partial) => apiUpdateNotificationsPolicy(policy), +); diff --git a/app/javascript/flavours/glitch/actions/notifications.js b/app/javascript/flavours/glitch/actions/notifications.js index ff50e8f7d0..b9edbb7b6c 100644 --- a/app/javascript/flavours/glitch/actions/notifications.js +++ b/app/javascript/flavours/glitch/actions/notifications.js @@ -57,10 +57,6 @@ export const NOTIFICATIONS_MARK_AS_READ = 'NOTIFICATIONS_MARK_AS_READ'; export const NOTIFICATIONS_SET_BROWSER_SUPPORT = 'NOTIFICATIONS_SET_BROWSER_SUPPORT'; export const NOTIFICATIONS_SET_BROWSER_PERMISSION = 'NOTIFICATIONS_SET_BROWSER_PERMISSION'; -export const NOTIFICATION_POLICY_FETCH_REQUEST = 'NOTIFICATION_POLICY_FETCH_REQUEST'; -export const NOTIFICATION_POLICY_FETCH_SUCCESS = 'NOTIFICATION_POLICY_FETCH_SUCCESS'; -export const NOTIFICATION_POLICY_FETCH_FAIL = 'NOTIFICATION_POLICY_FETCH_FAIL'; - export const NOTIFICATION_REQUESTS_FETCH_REQUEST = 'NOTIFICATION_REQUESTS_FETCH_REQUEST'; export const NOTIFICATION_REQUESTS_FETCH_SUCCESS = 'NOTIFICATION_REQUESTS_FETCH_SUCCESS'; export const NOTIFICATION_REQUESTS_FETCH_FAIL = 'NOTIFICATION_REQUESTS_FETCH_FAIL'; @@ -229,7 +225,7 @@ export function expandNotifications({ maxId, forceLoad } = {}, done = noOp) { dispatch(expandNotificationsRequest(isLoadingMore)); - api(getState).get('/api/v1/notifications', { params, signal: expandNotificationsController.signal }).then(response => { + api().get('/api/v1/notifications', { params, signal: expandNotificationsController.signal }).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data.map(item => item.account))); @@ -275,12 +271,12 @@ export function expandNotificationsFail(error, isLoadingMore) { } export function clearNotifications() { - return (dispatch, getState) => { + return (dispatch) => { dispatch({ type: NOTIFICATIONS_CLEAR, }); - api(getState).post('/api/v1/notifications/clear'); + api().post('/api/v1/notifications/clear'); }; } @@ -306,7 +302,7 @@ export function deleteMarkedNotifications() { return; } - api(getState).delete(`/api/v1/notifications/destroy_multiple?ids[]=${ids.join('&ids[]=')}`).then(() => { + api().delete(`/api/v1/notifications/destroy_multiple?ids[]=${ids.join('&ids[]=')}`).then(() => { dispatch(deleteMarkedNotificationsSuccess()); }).catch(error => { console.error(error); @@ -435,40 +431,6 @@ export function setBrowserPermission (value) { }; } -export const fetchNotificationPolicy = () => (dispatch, getState) => { - dispatch(fetchNotificationPolicyRequest()); - - api(getState).get('/api/v1/notifications/policy').then(({ data }) => { - dispatch(fetchNotificationPolicySuccess(data)); - }).catch(err => { - dispatch(fetchNotificationPolicyFail(err)); - }); -}; - -export const fetchNotificationPolicyRequest = () => ({ - type: NOTIFICATION_POLICY_FETCH_REQUEST, -}); - -export const fetchNotificationPolicySuccess = policy => ({ - type: NOTIFICATION_POLICY_FETCH_SUCCESS, - policy, -}); - -export const fetchNotificationPolicyFail = error => ({ - type: NOTIFICATION_POLICY_FETCH_FAIL, - error, -}); - -export const updateNotificationsPolicy = params => (dispatch, getState) => { - dispatch(fetchNotificationPolicyRequest()); - - api(getState).put('/api/v1/notifications/policy', params).then(({ data }) => { - dispatch(fetchNotificationPolicySuccess(data)); - }).catch(err => { - dispatch(fetchNotificationPolicyFail(err)); - }); -}; - export const fetchNotificationRequests = () => (dispatch, getState) => { const params = {}; @@ -482,7 +444,7 @@ export const fetchNotificationRequests = () => (dispatch, getState) => { dispatch(fetchNotificationRequestsRequest()); - api(getState).get('/api/v1/notifications/requests', { params }).then(response => { + api().get('/api/v1/notifications/requests', { params }).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data.map(x => x.account))); dispatch(fetchNotificationRequestsSuccess(response.data, next ? next.uri : null)); @@ -515,7 +477,7 @@ export const expandNotificationRequests = () => (dispatch, getState) => { dispatch(expandNotificationRequestsRequest()); - api(getState).get(url).then(response => { + api().get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data.map(x => x.account))); dispatch(expandNotificationRequestsSuccess(response.data, next?.uri)); @@ -548,7 +510,7 @@ export const fetchNotificationRequest = id => (dispatch, getState) => { dispatch(fetchNotificationRequestRequest(id)); - api(getState).get(`/api/v1/notifications/requests/${id}`).then(({ data }) => { + api().get(`/api/v1/notifications/requests/${id}`).then(({ data }) => { dispatch(fetchNotificationRequestSuccess(data)); }).catch(err => { dispatch(fetchNotificationRequestFail(id, err)); @@ -571,10 +533,10 @@ export const fetchNotificationRequestFail = (id, error) => ({ error, }); -export const acceptNotificationRequest = id => (dispatch, getState) => { +export const acceptNotificationRequest = id => (dispatch) => { dispatch(acceptNotificationRequestRequest(id)); - api(getState).post(`/api/v1/notifications/requests/${id}/accept`).then(() => { + api().post(`/api/v1/notifications/requests/${id}/accept`).then(() => { dispatch(acceptNotificationRequestSuccess(id)); }).catch(err => { dispatch(acceptNotificationRequestFail(id, err)); @@ -597,10 +559,10 @@ export const acceptNotificationRequestFail = (id, error) => ({ error, }); -export const dismissNotificationRequest = id => (dispatch, getState) => { +export const dismissNotificationRequest = id => (dispatch) => { dispatch(dismissNotificationRequestRequest(id)); - api(getState).post(`/api/v1/notifications/requests/${id}/dismiss`).then(() =>{ + api().post(`/api/v1/notifications/requests/${id}/dismiss`).then(() =>{ dispatch(dismissNotificationRequestSuccess(id)); }).catch(err => { dispatch(dismissNotificationRequestFail(id, err)); @@ -639,7 +601,7 @@ export const fetchNotificationsForRequest = accountId => (dispatch, getState) => dispatch(fetchNotificationsForRequestRequest()); - api(getState).get('/api/v1/notifications', { params }).then(response => { + api().get('/api/v1/notifications', { params }).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data.map(item => item.account))); dispatch(importFetchedStatuses(response.data.map(item => item.status).filter(status => !!status))); @@ -675,7 +637,7 @@ export const expandNotificationsForRequest = () => (dispatch, getState) => { dispatch(expandNotificationsForRequestRequest()); - api(getState).get(url).then(response => { + api().get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data.map(item => item.account))); dispatch(importFetchedStatuses(response.data.map(item => item.status).filter(status => !!status))); diff --git a/app/javascript/flavours/glitch/actions/pin_statuses.js b/app/javascript/flavours/glitch/actions/pin_statuses.js index baa10d1562..d583eab573 100644 --- a/app/javascript/flavours/glitch/actions/pin_statuses.js +++ b/app/javascript/flavours/glitch/actions/pin_statuses.js @@ -8,10 +8,10 @@ export const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS'; export const PINNED_STATUSES_FETCH_FAIL = 'PINNED_STATUSES_FETCH_FAIL'; export function fetchPinnedStatuses() { - return (dispatch, getState) => { + return (dispatch) => { dispatch(fetchPinnedStatusesRequest()); - api(getState).get(`/api/v1/accounts/${me}/statuses`, { params: { pinned: true } }).then(response => { + api().get(`/api/v1/accounts/${me}/statuses`, { params: { pinned: true } }).then(response => { dispatch(importFetchedStatuses(response.data)); dispatch(fetchPinnedStatusesSuccess(response.data, null)); }).catch(error => { diff --git a/app/javascript/flavours/glitch/actions/polls.js b/app/javascript/flavours/glitch/actions/polls.js index a37410dc90..aa49341444 100644 --- a/app/javascript/flavours/glitch/actions/polls.js +++ b/app/javascript/flavours/glitch/actions/polls.js @@ -10,10 +10,10 @@ export const POLL_FETCH_REQUEST = 'POLL_FETCH_REQUEST'; export const POLL_FETCH_SUCCESS = 'POLL_FETCH_SUCCESS'; export const POLL_FETCH_FAIL = 'POLL_FETCH_FAIL'; -export const vote = (pollId, choices) => (dispatch, getState) => { +export const vote = (pollId, choices) => (dispatch) => { dispatch(voteRequest()); - api(getState).post(`/api/v1/polls/${pollId}/votes`, { choices }) + api().post(`/api/v1/polls/${pollId}/votes`, { choices }) .then(({ data }) => { dispatch(importFetchedPoll(data)); dispatch(voteSuccess(data)); @@ -21,10 +21,10 @@ export const vote = (pollId, choices) => (dispatch, getState) => { .catch(err => dispatch(voteFail(err))); }; -export const fetchPoll = pollId => (dispatch, getState) => { +export const fetchPoll = pollId => (dispatch) => { dispatch(fetchPollRequest()); - api(getState).get(`/api/v1/polls/${pollId}`) + api().get(`/api/v1/polls/${pollId}`) .then(({ data }) => { dispatch(importFetchedPoll(data)); dispatch(fetchPollSuccess(data)); diff --git a/app/javascript/flavours/glitch/actions/reports.js b/app/javascript/flavours/glitch/actions/reports.js index 756b8cd05e..49b89b0d13 100644 --- a/app/javascript/flavours/glitch/actions/reports.js +++ b/app/javascript/flavours/glitch/actions/reports.js @@ -15,10 +15,10 @@ export const initReport = (account, status) => dispatch => }, })); -export const submitReport = (params, onSuccess, onFail) => (dispatch, getState) => { +export const submitReport = (params, onSuccess, onFail) => (dispatch) => { dispatch(submitReportRequest()); - api(getState).post('/api/v1/reports', params).then(response => { + api().post('/api/v1/reports', params).then(response => { dispatch(submitReportSuccess(response.data)); if (onSuccess) onSuccess(); }).catch(error => { diff --git a/app/javascript/flavours/glitch/actions/search.js b/app/javascript/flavours/glitch/actions/search.js index 44344e2fb5..849fc6d33c 100644 --- a/app/javascript/flavours/glitch/actions/search.js +++ b/app/javascript/flavours/glitch/actions/search.js @@ -46,7 +46,7 @@ export function submitSearch(type) { dispatch(fetchSearchRequest(type)); - api(getState).get('/api/v2/search', { + api().get('/api/v2/search', { params: { q: value, resolve: signedIn, @@ -99,7 +99,7 @@ export const expandSearch = type => (dispatch, getState) => { dispatch(expandSearchRequest(type)); - api(getState).get('/api/v2/search', { + api().get('/api/v2/search', { params: { q: value, type, @@ -156,7 +156,7 @@ export const openURL = (value, history, onFailure) => (dispatch, getState) => { dispatch(fetchSearchRequest()); - api(getState).get('/api/v2/search', { params: { q: value, resolve: true } }).then(response => { + api().get('/api/v2/search', { params: { q: value, resolve: true } }).then(response => { if (response.data.accounts?.length > 0) { dispatch(importFetchedAccounts(response.data.accounts)); history.push(`/@${response.data.accounts[0].acct}`); diff --git a/app/javascript/flavours/glitch/actions/server.js b/app/javascript/flavours/glitch/actions/server.js index 65f3efc3a7..32ee093afa 100644 --- a/app/javascript/flavours/glitch/actions/server.js +++ b/app/javascript/flavours/glitch/actions/server.js @@ -25,7 +25,7 @@ export const fetchServer = () => (dispatch, getState) => { dispatch(fetchServerRequest()); - api(getState) + api() .get('/api/v2/instance').then(({ data }) => { if (data.contact.account) dispatch(importFetchedAccount(data.contact.account)); dispatch(fetchServerSuccess(data)); @@ -46,10 +46,10 @@ const fetchServerFail = error => ({ error, }); -export const fetchServerTranslationLanguages = () => (dispatch, getState) => { +export const fetchServerTranslationLanguages = () => (dispatch) => { dispatch(fetchServerTranslationLanguagesRequest()); - api(getState) + api() .get('/api/v1/instance/translation_languages').then(({ data }) => { dispatch(fetchServerTranslationLanguagesSuccess(data)); }).catch(err => dispatch(fetchServerTranslationLanguagesFail(err))); @@ -76,7 +76,7 @@ export const fetchExtendedDescription = () => (dispatch, getState) => { dispatch(fetchExtendedDescriptionRequest()); - api(getState) + api() .get('/api/v1/instance/extended_description') .then(({ data }) => dispatch(fetchExtendedDescriptionSuccess(data))) .catch(err => dispatch(fetchExtendedDescriptionFail(err))); @@ -103,7 +103,7 @@ export const fetchDomainBlocks = () => (dispatch, getState) => { dispatch(fetchDomainBlocksRequest()); - api(getState) + api() .get('/api/v1/instance/domain_blocks') .then(({ data }) => dispatch(fetchDomainBlocksSuccess(true, data))) .catch(err => { diff --git a/app/javascript/flavours/glitch/actions/settings.js b/app/javascript/flavours/glitch/actions/settings.js index 3685b0684e..fbd89f9d4b 100644 --- a/app/javascript/flavours/glitch/actions/settings.js +++ b/app/javascript/flavours/glitch/actions/settings.js @@ -20,7 +20,7 @@ export function changeSetting(path, value) { } const debouncedSave = debounce((dispatch, getState) => { - if (getState().getIn(['settings', 'saved'])) { + if (getState().getIn(['settings', 'saved']) || !getState().getIn(['meta', 'me'])) { return; } diff --git a/app/javascript/flavours/glitch/actions/statuses.js b/app/javascript/flavours/glitch/actions/statuses.js index 332057ee67..c4d292567d 100644 --- a/app/javascript/flavours/glitch/actions/statuses.js +++ b/app/javascript/flavours/glitch/actions/statuses.js @@ -59,7 +59,7 @@ export function fetchStatus(id, forceFetch = false) { dispatch(fetchStatusRequest(id, skipLoading)); - api(getState).get(`/api/v1/statuses/${id}`).then(response => { + api().get(`/api/v1/statuses/${id}`).then(response => { dispatch(importFetchedStatus(response.data)); dispatch(fetchStatusSuccess(skipLoading)); }).catch(error => { @@ -103,7 +103,7 @@ export const editStatus = (id, routerHistory) => (dispatch, getState) => { dispatch(fetchStatusSourceRequest()); - api(getState).get(`/api/v1/statuses/${id}/source`).then(response => { + api().get(`/api/v1/statuses/${id}/source`).then(response => { dispatch(fetchStatusSourceSuccess()); ensureComposeIsVisible(getState, routerHistory); dispatch(setComposeToStatus(status, response.data.text, response.data.spoiler_text, response.data.content_type)); @@ -135,7 +135,7 @@ export function deleteStatus(id, routerHistory, withRedraft = false) { dispatch(deleteStatusRequest(id)); - api(getState).delete(`/api/v1/statuses/${id}`).then(response => { + api().delete(`/api/v1/statuses/${id}`).then(response => { dispatch(deleteStatusSuccess(id)); dispatch(deleteFromTimelines(id)); dispatch(importFetchedAccount(response.data.account)); @@ -176,10 +176,10 @@ export const updateStatus = status => dispatch => dispatch(importFetchedStatus(status)); export function fetchContext(id) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(fetchContextRequest(id)); - api(getState).get(`/api/v1/statuses/${id}/context`).then(response => { + api().get(`/api/v1/statuses/${id}/context`).then(response => { dispatch(importFetchedStatuses(response.data.ancestors.concat(response.data.descendants))); dispatch(fetchContextSuccess(id, response.data.ancestors, response.data.descendants)); @@ -220,10 +220,10 @@ export function fetchContextFail(id, error) { } export function muteStatus(id) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(muteStatusRequest(id)); - api(getState).post(`/api/v1/statuses/${id}/mute`).then(() => { + api().post(`/api/v1/statuses/${id}/mute`).then(() => { dispatch(muteStatusSuccess(id)); }).catch(error => { dispatch(muteStatusFail(id, error)); @@ -254,10 +254,10 @@ export function muteStatusFail(id, error) { } export function unmuteStatus(id) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(unmuteStatusRequest(id)); - api(getState).post(`/api/v1/statuses/${id}/unmute`).then(() => { + api().post(`/api/v1/statuses/${id}/unmute`).then(() => { dispatch(unmuteStatusSuccess(id)); }).catch(error => { dispatch(unmuteStatusFail(id, error)); @@ -317,10 +317,10 @@ export function toggleStatusCollapse(id, isCollapsed) { }; } -export const translateStatus = id => (dispatch, getState) => { +export const translateStatus = id => (dispatch) => { dispatch(translateStatusRequest(id)); - api(getState).post(`/api/v1/statuses/${id}/translate`).then(response => { + api().post(`/api/v1/statuses/${id}/translate`).then(response => { dispatch(translateStatusSuccess(id, response.data)); }).catch(error => { dispatch(translateStatusFail(id, error)); diff --git a/app/javascript/flavours/glitch/actions/streaming.js b/app/javascript/flavours/glitch/actions/streaming.js index d8341a5c16..a55240646f 100644 --- a/app/javascript/flavours/glitch/actions/streaming.js +++ b/app/javascript/flavours/glitch/actions/streaming.js @@ -77,7 +77,7 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti }, onDisconnect() { - dispatch(disconnectTimeline(timelineId)); + dispatch(disconnectTimeline({ timeline: timelineId })); if (options.fallback) { // @ts-expect-error diff --git a/app/javascript/flavours/glitch/actions/suggestions.js b/app/javascript/flavours/glitch/actions/suggestions.js index 8eafe38b21..258ffa901d 100644 --- a/app/javascript/flavours/glitch/actions/suggestions.js +++ b/app/javascript/flavours/glitch/actions/suggestions.js @@ -10,10 +10,10 @@ export const SUGGESTIONS_FETCH_FAIL = 'SUGGESTIONS_FETCH_FAIL'; export const SUGGESTIONS_DISMISS = 'SUGGESTIONS_DISMISS'; export function fetchSuggestions(withRelationships = false) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(fetchSuggestionsRequest()); - api(getState).get('/api/v2/suggestions', { params: { limit: 20 } }).then(response => { + api().get('/api/v2/suggestions', { params: { limit: 20 } }).then(response => { dispatch(importFetchedAccounts(response.data.map(x => x.account))); dispatch(fetchSuggestionsSuccess(response.data)); @@ -48,11 +48,11 @@ export function fetchSuggestionsFail(error) { }; } -export const dismissSuggestion = accountId => (dispatch, getState) => { +export const dismissSuggestion = accountId => (dispatch) => { dispatch({ type: SUGGESTIONS_DISMISS, id: accountId, }); - api(getState).delete(`/api/v1/suggestions/${accountId}`).catch(() => {}); + api().delete(`/api/v1/suggestions/${accountId}`).catch(() => {}); }; diff --git a/app/javascript/flavours/glitch/actions/tags.js b/app/javascript/flavours/glitch/actions/tags.js index dda8c924bb..d18d7e514f 100644 --- a/app/javascript/flavours/glitch/actions/tags.js +++ b/app/javascript/flavours/glitch/actions/tags.js @@ -20,10 +20,10 @@ export const HASHTAG_UNFOLLOW_REQUEST = 'HASHTAG_UNFOLLOW_REQUEST'; export const HASHTAG_UNFOLLOW_SUCCESS = 'HASHTAG_UNFOLLOW_SUCCESS'; export const HASHTAG_UNFOLLOW_FAIL = 'HASHTAG_UNFOLLOW_FAIL'; -export const fetchHashtag = name => (dispatch, getState) => { +export const fetchHashtag = name => (dispatch) => { dispatch(fetchHashtagRequest()); - api(getState).get(`/api/v1/tags/${name}`).then(({ data }) => { + api().get(`/api/v1/tags/${name}`).then(({ data }) => { dispatch(fetchHashtagSuccess(name, data)); }).catch(err => { dispatch(fetchHashtagFail(err)); @@ -45,10 +45,10 @@ export const fetchHashtagFail = error => ({ error, }); -export const fetchFollowedHashtags = () => (dispatch, getState) => { +export const fetchFollowedHashtags = () => (dispatch) => { dispatch(fetchFollowedHashtagsRequest()); - api(getState).get('/api/v1/followed_tags').then(response => { + api().get('/api/v1/followed_tags').then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(fetchFollowedHashtagsSuccess(response.data, next ? next.uri : null)); }).catch(err => { @@ -87,7 +87,7 @@ export function expandFollowedHashtags() { dispatch(expandFollowedHashtagsRequest()); - api(getState).get(url).then(response => { + api().get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(expandFollowedHashtagsSuccess(response.data, next ? next.uri : null)); }).catch(error => { @@ -117,10 +117,10 @@ export function expandFollowedHashtagsFail(error) { }; } -export const followHashtag = name => (dispatch, getState) => { +export const followHashtag = name => (dispatch) => { dispatch(followHashtagRequest(name)); - api(getState).post(`/api/v1/tags/${name}/follow`).then(({ data }) => { + api().post(`/api/v1/tags/${name}/follow`).then(({ data }) => { dispatch(followHashtagSuccess(name, data)); }).catch(err => { dispatch(followHashtagFail(name, err)); @@ -144,10 +144,10 @@ export const followHashtagFail = (name, error) => ({ error, }); -export const unfollowHashtag = name => (dispatch, getState) => { +export const unfollowHashtag = name => (dispatch) => { dispatch(unfollowHashtagRequest(name)); - api(getState).post(`/api/v1/tags/${name}/unfollow`).then(({ data }) => { + api().post(`/api/v1/tags/${name}/unfollow`).then(({ data }) => { dispatch(unfollowHashtagSuccess(name, data)); }).catch(err => { dispatch(unfollowHashtagFail(name, err)); diff --git a/app/javascript/flavours/glitch/actions/timelines.js b/app/javascript/flavours/glitch/actions/timelines.js index 980b4af66d..eb5050f152 100644 --- a/app/javascript/flavours/glitch/actions/timelines.js +++ b/app/javascript/flavours/glitch/actions/timelines.js @@ -7,9 +7,11 @@ import { toServerSideType } from 'flavours/glitch/utils/filters'; import { importFetchedStatus, importFetchedStatuses } from './importer'; import { submitMarkers } from './markers'; +import {timelineDelete} from './timelines_typed'; + +export { disconnectTimeline } from './timelines_typed'; export const TIMELINE_UPDATE = 'TIMELINE_UPDATE'; -export const TIMELINE_DELETE = 'TIMELINE_DELETE'; export const TIMELINE_CLEAR = 'TIMELINE_CLEAR'; export const TIMELINE_EXPAND_REQUEST = 'TIMELINE_EXPAND_REQUEST'; @@ -18,7 +20,6 @@ export const TIMELINE_EXPAND_FAIL = 'TIMELINE_EXPAND_FAIL'; export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP'; export const TIMELINE_LOAD_PENDING = 'TIMELINE_LOAD_PENDING'; -export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT'; export const TIMELINE_CONNECT = 'TIMELINE_CONNECT'; export const TIMELINE_MARK_AS_PARTIAL = 'TIMELINE_MARK_AS_PARTIAL'; @@ -73,16 +74,10 @@ export function updateTimeline(timeline, status, accept) { export function deleteFromTimelines(id) { return (dispatch, getState) => { const accountId = getState().getIn(['statuses', id, 'account']); - const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => status.get('id')); + const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => status.get('id')).valueSeq().toJSON(); const reblogOf = getState().getIn(['statuses', id, 'reblog'], null); - dispatch({ - type: TIMELINE_DELETE, - id, - accountId, - references, - reblogOf, - }); + dispatch(timelineDelete({ statusId: id, accountId, references, reblogOf })); }; } @@ -125,7 +120,7 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) { dispatch(expandTimelineRequest(timelineId, isLoadingMore)); - api(getState).get(path, { params }).then(response => { + api().get(path, { params }).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedStatuses(response.data)); @@ -175,6 +170,7 @@ export const expandAccountTimeline = (accountId, { maxId, withReplies, t export const expandAccountFeaturedTimeline = (accountId, { tagged } = {}) => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true, tagged }); export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true, limit: 40 }); export const expandListTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done); +export const expandLinkTimeline = (url, { maxId } = {}, done = noOp) => expandTimeline(`link:${url}`, `/api/v1/timelines/link`, { url, max_id: maxId }, done); export const expandHashtagTimeline = (hashtag, { maxId, tags, local } = {}, done = noOp) => { return expandTimeline(`hashtag:${hashtag}${local ? ':local' : ''}`, `/api/v1/timelines/tag/${hashtag}`, { max_id: maxId, @@ -237,12 +233,6 @@ export function connectTimeline(timeline) { }; } -export const disconnectTimeline = timeline => ({ - type: TIMELINE_DISCONNECT, - timeline, - usePendingItems: preferPendingItems, -}); - export const markAsPartial = timeline => ({ type: TIMELINE_MARK_AS_PARTIAL, timeline, diff --git a/app/javascript/flavours/glitch/actions/timelines_typed.ts b/app/javascript/flavours/glitch/actions/timelines_typed.ts new file mode 100644 index 0000000000..485b94ed52 --- /dev/null +++ b/app/javascript/flavours/glitch/actions/timelines_typed.ts @@ -0,0 +1,20 @@ +import { createAction } from '@reduxjs/toolkit'; + +import { usePendingItems as preferPendingItems } from 'flavours/glitch/initial_state'; + +export const disconnectTimeline = createAction( + 'timeline/disconnect', + ({ timeline }: { timeline: string }) => ({ + payload: { + timeline, + usePendingItems: preferPendingItems, + }, + }), +); + +export const timelineDelete = createAction<{ + statusId: string; + accountId: string; + references: string[]; + reblogOf: string | null; +}>('timelines/delete'); diff --git a/app/javascript/flavours/glitch/actions/trends.js b/app/javascript/flavours/glitch/actions/trends.js index d314423884..0bdf17a5d2 100644 --- a/app/javascript/flavours/glitch/actions/trends.js +++ b/app/javascript/flavours/glitch/actions/trends.js @@ -1,6 +1,6 @@ import api, { getLinks } from '../api'; -import { importFetchedStatuses } from './importer'; +import { importFetchedStatuses, importFetchedAccounts } from './importer'; export const TRENDS_TAGS_FETCH_REQUEST = 'TRENDS_TAGS_FETCH_REQUEST'; export const TRENDS_TAGS_FETCH_SUCCESS = 'TRENDS_TAGS_FETCH_SUCCESS'; @@ -18,10 +18,10 @@ export const TRENDS_STATUSES_EXPAND_REQUEST = 'TRENDS_STATUSES_EXPAND_REQUEST'; export const TRENDS_STATUSES_EXPAND_SUCCESS = 'TRENDS_STATUSES_EXPAND_SUCCESS'; export const TRENDS_STATUSES_EXPAND_FAIL = 'TRENDS_STATUSES_EXPAND_FAIL'; -export const fetchTrendingHashtags = () => (dispatch, getState) => { +export const fetchTrendingHashtags = () => (dispatch) => { dispatch(fetchTrendingHashtagsRequest()); - api(getState) + api() .get('/api/v1/trends/tags') .then(({ data }) => dispatch(fetchTrendingHashtagsSuccess(data))) .catch(err => dispatch(fetchTrendingHashtagsFail(err))); @@ -45,12 +45,15 @@ export const fetchTrendingHashtagsFail = error => ({ skipAlert: true, }); -export const fetchTrendingLinks = () => (dispatch, getState) => { +export const fetchTrendingLinks = () => (dispatch) => { dispatch(fetchTrendingLinksRequest()); - api(getState) - .get('/api/v1/trends/links') - .then(({ data }) => dispatch(fetchTrendingLinksSuccess(data))) + api() + .get('/api/v1/trends/links', { params: { limit: 20 } }) + .then(({ data }) => { + dispatch(importFetchedAccounts(data.flatMap(link => link.authors.map(author => author.account)).filter(account => !!account))); + dispatch(fetchTrendingLinksSuccess(data)); + }) .catch(err => dispatch(fetchTrendingLinksFail(err))); }; @@ -79,7 +82,7 @@ export const fetchTrendingStatuses = () => (dispatch, getState) => { dispatch(fetchTrendingStatusesRequest()); - api(getState).get('/api/v1/trends/statuses').then(response => { + api().get('/api/v1/trends/statuses').then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedStatuses(response.data)); dispatch(fetchTrendingStatusesSuccess(response.data, next ? next.uri : null)); @@ -115,7 +118,7 @@ export const expandTrendingStatuses = () => (dispatch, getState) => { dispatch(expandTrendingStatusesRequest()); - api(getState).get(url).then(response => { + api().get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedStatuses(response.data)); dispatch(expandTrendingStatusesSuccess(response.data, next ? next.uri : null)); diff --git a/app/javascript/flavours/glitch/api.ts b/app/javascript/flavours/glitch/api.ts index 73a4c6567a..da825f76e1 100644 --- a/app/javascript/flavours/glitch/api.ts +++ b/app/javascript/flavours/glitch/api.ts @@ -1,9 +1,9 @@ -import type { AxiosResponse, RawAxiosRequestHeaders } from 'axios'; +import type { AxiosResponse, Method, RawAxiosRequestHeaders } from 'axios'; import axios from 'axios'; import LinkHeader from 'http-link-header'; +import { getAccessToken } from './initial_state'; import ready from './ready'; -import type { GetState } from './store'; export const getLinks = (response: AxiosResponse) => { const value = response.headers.link as string | undefined; @@ -29,22 +29,14 @@ const setCSRFHeader = () => { void ready(setCSRFHeader); -export const authorizationTokenFromState = (getState?: GetState) => { - return ( - getState && (getState().meta.get('access_token', '') as string | false) - ); -}; +const authorizationTokenFromInitialState = (): RawAxiosRequestHeaders => { + const accessToken = getAccessToken(); -const authorizationHeaderFromState = (getState?: GetState) => { - const accessToken = authorizationTokenFromState(getState); - - if (!accessToken) { - return {}; - } + if (!accessToken) return {}; return { Authorization: `Bearer ${accessToken}`, - } as RawAxiosRequestHeaders; + }; }; const baseUrlFromState = (getState?: GetState) => { @@ -53,11 +45,11 @@ const baseUrlFromState = (getState?: GetState) => { }; // eslint-disable-next-line import/no-default-export -export default function api(getState: GetState) { +export default function api(withAuthorization = true) { return axios.create({ headers: { ...csrfHeader, - ...authorizationHeaderFromState(getState), + ...(withAuthorization ? authorizationTokenFromInitialState() : {}), }, baseURL: baseUrlFromState(getState), @@ -73,3 +65,50 @@ export default function api(getState: GetState) { ], }); } + +type RequestParamsOrData = Record; + +export async function apiRequest( + method: Method, + url: string, + args: { + params?: RequestParamsOrData; + data?: RequestParamsOrData; + } = {}, +) { + const { data } = await api().request({ + method, + url: '/api/' + url, + ...args, + }); + + return data; +} + +export async function apiRequestGet( + url: string, + params?: RequestParamsOrData, +) { + return apiRequest('GET', url, { params }); +} + +export async function apiRequestPost( + url: string, + data?: RequestParamsOrData, +) { + return apiRequest('POST', url, { data }); +} + +export async function apiRequestPut( + url: string, + data?: RequestParamsOrData, +) { + return apiRequest('PUT', url, { data }); +} + +export async function apiRequestDelete( + url: string, + params?: RequestParamsOrData, +) { + return apiRequest('DELETE', url, { params }); +} diff --git a/app/javascript/flavours/glitch/api/accounts.ts b/app/javascript/flavours/glitch/api/accounts.ts new file mode 100644 index 0000000000..410f3d20e3 --- /dev/null +++ b/app/javascript/flavours/glitch/api/accounts.ts @@ -0,0 +1,7 @@ +import { apiRequestPost } from 'flavours/glitch/api'; +import type { ApiRelationshipJSON } from 'flavours/glitch/api_types/relationships'; + +export const apiSubmitAccountNote = (id: string, value: string) => + apiRequestPost(`v1/accounts/${id}/note`, { + comment: value, + }); diff --git a/app/javascript/flavours/glitch/api/directory.ts b/app/javascript/flavours/glitch/api/directory.ts new file mode 100644 index 0000000000..72743a2584 --- /dev/null +++ b/app/javascript/flavours/glitch/api/directory.ts @@ -0,0 +1,15 @@ +import { apiRequestGet } from 'flavours/glitch/api'; +import type { ApiAccountJSON } from 'flavours/glitch/api_types/accounts'; + +export const apiGetDirectory = ( + params: { + order: string; + local: boolean; + offset?: number; + }, + limit = 20, +) => + apiRequestGet('v1/directory', { + ...params, + limit, + }); diff --git a/app/javascript/flavours/glitch/api/interactions.ts b/app/javascript/flavours/glitch/api/interactions.ts new file mode 100644 index 0000000000..172f97a256 --- /dev/null +++ b/app/javascript/flavours/glitch/api/interactions.ts @@ -0,0 +1,10 @@ +import { apiRequestPost } from 'flavours/glitch/api'; +import type { Status, StatusVisibility } from 'flavours/glitch/models/status'; + +export const apiReblog = (statusId: string, visibility: StatusVisibility) => + apiRequestPost<{ reblog: Status }>(`v1/statuses/${statusId}/reblog`, { + visibility, + }); + +export const apiUnreblog = (statusId: string) => + apiRequestPost(`v1/statuses/${statusId}/unreblog`); diff --git a/app/javascript/flavours/glitch/api/notification_policies.ts b/app/javascript/flavours/glitch/api/notification_policies.ts new file mode 100644 index 0000000000..e52ea64f41 --- /dev/null +++ b/app/javascript/flavours/glitch/api/notification_policies.ts @@ -0,0 +1,9 @@ +import { apiRequestGet, apiRequestPut } from 'flavours/glitch/api'; +import type { NotificationPolicyJSON } from 'flavours/glitch/api_types/notification_policies'; + +export const apiGetNotificationPolicy = () => + apiRequestGet('/v1/notifications/policy'); + +export const apiUpdateNotificationsPolicy = ( + policy: Partial, +) => apiRequestPut('/v1/notifications/policy', policy); diff --git a/app/javascript/flavours/glitch/api_types/notification_policies.ts b/app/javascript/flavours/glitch/api_types/notification_policies.ts new file mode 100644 index 0000000000..0f4a2d132e --- /dev/null +++ b/app/javascript/flavours/glitch/api_types/notification_policies.ts @@ -0,0 +1,12 @@ +// See app/serializers/rest/notification_policy_serializer.rb + +export interface NotificationPolicyJSON { + filter_not_following: boolean; + filter_not_followers: boolean; + filter_new_accounts: boolean; + filter_private_mentions: boolean; + summary: { + pending_requests_count: number; + pending_notifications_count: number; + }; +} diff --git a/app/javascript/flavours/glitch/api_types/statuses.ts b/app/javascript/flavours/glitch/api_types/statuses.ts index d63441873d..9de86e7fa6 100644 --- a/app/javascript/flavours/glitch/api_types/statuses.ts +++ b/app/javascript/flavours/glitch/api_types/statuses.ts @@ -30,6 +30,12 @@ export interface ApiMentionJSON { acct: string; } +export interface ApiPreviewCardAuthorJSON { + name: string; + url: string; + account?: ApiAccountJSON; +} + export interface ApiPreviewCardJSON { url: string; title: string; @@ -38,6 +44,7 @@ export interface ApiPreviewCardJSON { type: string; author_name: string; author_url: string; + author_account?: ApiAccountJSON; provider_name: string; provider_url: string; html: string; @@ -48,6 +55,7 @@ export interface ApiPreviewCardJSON { embed_url: string; blurhash: string; published_at: string; + authors: ApiPreviewCardAuthorJSON[]; } export interface ApiStatusJSON { diff --git a/app/javascript/flavours/glitch/components/account.jsx b/app/javascript/flavours/glitch/components/account.jsx index 7e5209653e..e2ea899524 100644 --- a/app/javascript/flavours/glitch/components/account.jsx +++ b/app/javascript/flavours/glitch/components/account.jsx @@ -1,16 +1,18 @@ import PropTypes from 'prop-types'; +import { useCallback } from 'react'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; +import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; import { EmptyAccount } from 'flavours/glitch/components/empty_account'; import { ShortNumber } from 'flavours/glitch/components/short_number'; import { VerifiedBadge } from 'flavours/glitch/components/verified_badge'; +import DropdownMenuContainer from '../containers/dropdown_menu_container'; import { me } from '../initial_state'; import { Avatar } from './avatar'; @@ -30,153 +32,152 @@ const messages = defineMessages({ unmute_notifications: { id: 'account.unmute_notifications_short', defaultMessage: 'Unmute notifications' }, mute: { id: 'account.mute_short', defaultMessage: 'Mute' }, block: { id: 'account.block_short', defaultMessage: 'Block' }, + more: { id: 'status.more', defaultMessage: 'More' }, }); -class Account extends ImmutablePureComponent { +const Account = ({ size = 46, account, onFollow, onBlock, onMute, onMuteNotifications, hidden, minimal, defaultAction, withBio }) => { + const intl = useIntl(); - static propTypes = { - size: PropTypes.number, - account: ImmutablePropTypes.record, - onFollow: PropTypes.func, - onBlock: PropTypes.func, - onMute: PropTypes.func, - onMuteNotifications: PropTypes.func, - intl: PropTypes.object.isRequired, - hidden: PropTypes.bool, - minimal: PropTypes.bool, - defaultAction: PropTypes.string, - withBio: PropTypes.bool, - }; + const handleFollow = useCallback(() => { + onFollow(account); + }, [onFollow, account]); - static defaultProps = { - size: 46, - }; + const handleBlock = useCallback(() => { + onBlock(account); + }, [onBlock, account]); - handleFollow = () => { - this.props.onFollow(this.props.account); - }; + const handleMute = useCallback(() => { + onMute(account); + }, [onMute, account]); - handleBlock = () => { - this.props.onBlock(this.props.account); - }; + const handleMuteNotifications = useCallback(() => { + onMuteNotifications(account, true); + }, [onMuteNotifications, account]); - handleMute = () => { - this.props.onMute(this.props.account); - }; + const handleUnmuteNotifications = useCallback(() => { + onMuteNotifications(account, false); + }, [onMuteNotifications, account]); - handleMuteNotifications = () => { - this.props.onMuteNotifications(this.props.account, true); - }; - - handleUnmuteNotifications = () => { - this.props.onMuteNotifications(this.props.account, false); - }; - - render () { - const { account, intl, hidden, withBio, defaultAction, size, minimal } = this.props; - - if (!account) { - return ; - } - - if (hidden) { - return ( - <> - {account.get('display_name')} - {account.get('username')} - - ); - } - - let buttons; - - if (account.get('id') !== me && account.get('relationship', null) !== null) { - const following = account.getIn(['relationship', 'following']); - const requested = account.getIn(['relationship', 'requested']); - const blocking = account.getIn(['relationship', 'blocking']); - const muting = account.getIn(['relationship', 'muting']); - - if (requested) { - buttons = - ); -}; - -BackButton.propTypes = { - onlyIcon: PropTypes.bool, -}; - -class ColumnHeader extends PureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - - static propTypes = { - intl: PropTypes.object.isRequired, - title: PropTypes.node, - icon: PropTypes.string, - iconComponent: PropTypes.func, - active: PropTypes.bool, - multiColumn: PropTypes.bool, - extraButton: PropTypes.node, - showBackButton: PropTypes.bool, - children: PropTypes.node, - pinned: PropTypes.bool, - placeholder: PropTypes.bool, - onPin: PropTypes.func, - onMove: PropTypes.func, - onClick: PropTypes.func, - appendContent: PropTypes.node, - collapseIssues: PropTypes.bool, - ...WithRouterPropTypes, - }; - - state = { - collapsed: true, - animating: false, - }; - - handleToggleClick = (e) => { - e.stopPropagation(); - this.setState({ collapsed: !this.state.collapsed, animating: true }); - }; - - handleTitleClick = () => { - this.props.onClick?.(); - }; - - handleMoveLeft = () => { - this.props.onMove(-1); - }; - - handleMoveRight = () => { - this.props.onMove(1); - }; - - handleTransitionEnd = () => { - this.setState({ animating: false }); - }; - - handlePin = () => { - if (!this.props.pinned) { - this.props.history.replace('/'); - } - - this.props.onPin(); - }; - - render () { - const { title, icon, iconComponent, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues, history } = this.props; - const { collapsed, animating } = this.state; - - const wrapperClassName = classNames('column-header__wrapper', { - 'active': active, - }); - - const buttonClassName = classNames('column-header', { - 'active': active, - }); - - const collapsibleClassName = classNames('column-header__collapsible', { - 'collapsed': collapsed, - 'animating': animating, - }); - - const collapsibleButtonClassName = classNames('column-header__button', { - 'active': !collapsed, - }); - - let extraContent, pinButton, moveButtons, backButton, collapseButton; - - if (children) { - extraContent = ( -
- {children} -
- ); - } - - if (multiColumn && pinned) { - pinButton = ; - - moveButtons = ( -
- - -
- ); - } else if (multiColumn && this.props.onPin) { - pinButton = ; - } - - if (history && !pinned && ((multiColumn && history.location?.state?.fromMastodon) || showBackButton)) { - backButton = ; - } - - const collapsedContent = [ - extraContent, - ]; - - if (multiColumn) { - collapsedContent.push( -
- {pinButton} - {moveButtons} -
- ); - } - - if (this.context.identity.signedIn && (children || (multiColumn && this.props.onPin))) { - collapseButton = ( - - ); - } - - const hasTitle = (icon || iconComponent) && title; - - const component = ( -
-

- {hasTitle && ( - <> - {backButton} - - - - )} - - {!hasTitle && backButton} - -
- {extraButton} - {collapseButton} -
-

- -
-
- {(!collapsed || animating) && collapsedContent} -
-
- - {appendContent} -
- ); - - if (placeholder) { - return component; - } else { - return ( - {component} - ); - } - } - -} - -export default injectIntl(withRouter(ColumnHeader)); diff --git a/app/javascript/flavours/glitch/components/column_header.tsx b/app/javascript/flavours/glitch/components/column_header.tsx new file mode 100644 index 0000000000..9bd1559904 --- /dev/null +++ b/app/javascript/flavours/glitch/components/column_header.tsx @@ -0,0 +1,301 @@ +import { useCallback, useState } from 'react'; + +import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; + +import classNames from 'classnames'; + +import AddIcon from '@/material-icons/400-24px/add.svg?react'; +import ArrowBackIcon from '@/material-icons/400-24px/arrow_back.svg?react'; +import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react'; +import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; +import SettingsIcon from '@/material-icons/400-24px/settings.svg?react'; +import type { IconProp } from 'flavours/glitch/components/icon'; +import { Icon } from 'flavours/glitch/components/icon'; +import { ButtonInTabsBar } from 'flavours/glitch/features/ui/util/columns_context'; +import { useIdentity } from 'flavours/glitch/identity_context'; + +import { useAppHistory } from './router'; + +const messages = defineMessages({ + show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' }, + hide: { id: 'column_header.hide_settings', defaultMessage: 'Hide settings' }, + moveLeft: { + id: 'column_header.moveLeft_settings', + defaultMessage: 'Move column to the left', + }, + moveRight: { + id: 'column_header.moveRight_settings', + defaultMessage: 'Move column to the right', + }, + back: { id: 'column_back_button.label', defaultMessage: 'Back' }, +}); + +const BackButton: React.FC<{ + onlyIcon: boolean; +}> = ({ onlyIcon }) => { + const history = useAppHistory(); + const intl = useIntl(); + + const handleBackClick = useCallback(() => { + if (history.location.state?.fromMastodon) { + history.goBack(); + } else { + history.push('/'); + } + }, [history]); + + return ( + + ); +}; + +export interface Props { + title?: string; + icon?: string; + iconComponent?: IconProp; + active?: boolean; + children?: React.ReactNode; + pinned?: boolean; + multiColumn?: boolean; + extraButton?: React.ReactNode; + showBackButton?: boolean; + placeholder?: boolean; + appendContent?: React.ReactNode; + collapseIssues?: boolean; + onClick?: () => void; + onMove?: (arg0: number) => void; + onPin?: () => void; +} + +export const ColumnHeader: React.FC = ({ + title, + icon, + iconComponent, + active, + children, + pinned, + multiColumn, + extraButton, + showBackButton, + placeholder, + appendContent, + collapseIssues, + onClick, + onMove, + onPin, +}) => { + const intl = useIntl(); + const { signedIn } = useIdentity(); + const history = useAppHistory(); + const [collapsed, setCollapsed] = useState(true); + const [animating, setAnimating] = useState(false); + + const handleToggleClick = useCallback( + (e: React.MouseEvent) => { + e.stopPropagation(); + setCollapsed((value) => !value); + setAnimating(true); + }, + [setCollapsed, setAnimating], + ); + + const handleTitleClick = useCallback(() => { + onClick?.(); + }, [onClick]); + + const handleMoveLeft = useCallback(() => { + onMove?.(-1); + }, [onMove]); + + const handleMoveRight = useCallback(() => { + onMove?.(1); + }, [onMove]); + + const handleTransitionEnd = useCallback(() => { + setAnimating(false); + }, [setAnimating]); + + const handlePin = useCallback(() => { + if (!pinned) { + history.replace('/'); + } + + onPin?.(); + }, [history, pinned, onPin]); + + const wrapperClassName = classNames('column-header__wrapper', { + active, + }); + + const buttonClassName = classNames('column-header', { + active, + }); + + const collapsibleClassName = classNames('column-header__collapsible', { + collapsed, + animating, + }); + + const collapsibleButtonClassName = classNames('column-header__button', { + active: !collapsed, + }); + + let extraContent, pinButton, moveButtons, backButton, collapseButton; + + if (children) { + extraContent = ( +
+ {children} +
+ ); + } + + if (multiColumn && pinned) { + pinButton = ( + + ); + + moveButtons = ( +
+ + +
+ ); + } else if (multiColumn && onPin) { + pinButton = ( + + ); + } + + if ( + !pinned && + ((multiColumn && history.location.state?.fromMastodon) || showBackButton) + ) { + backButton = ; + } + + const collapsedContent = [extraContent]; + + if (multiColumn) { + collapsedContent.push( +
+ {pinButton} + {moveButtons} +
, + ); + } + + if (signedIn && (children || (multiColumn && onPin))) { + collapseButton = ( + + ); + } + + const hasIcon = icon && iconComponent; + const hasTitle = hasIcon && title; + + const component = ( +
+

+ {hasTitle && ( + <> + {backButton} + + + + )} + + {!hasTitle && backButton} + +
+ {extraButton} + {collapseButton} +
+

+ +
+
+ {(!collapsed || animating) && collapsedContent} +
+
+ + {appendContent} +
+ ); + + if (placeholder) { + return component; + } else { + return {component}; + } +}; + +// eslint-disable-next-line import/no-default-export +export default ColumnHeader; diff --git a/app/javascript/flavours/glitch/components/follow_button.tsx b/app/javascript/flavours/glitch/components/follow_button.tsx new file mode 100644 index 0000000000..a6b2064703 --- /dev/null +++ b/app/javascript/flavours/glitch/components/follow_button.tsx @@ -0,0 +1,125 @@ +import { useCallback, useEffect } from 'react'; + +import { useIntl, defineMessages, FormattedMessage } from 'react-intl'; + +import { useIdentity } from '@/flavours/glitch/identity_context'; +import { + fetchRelationships, + followAccount, + unfollowAccount, +} from 'flavours/glitch/actions/accounts'; +import { openModal } from 'flavours/glitch/actions/modal'; +import { Button } from 'flavours/glitch/components/button'; +import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; +import { me } from 'flavours/glitch/initial_state'; +import { useAppDispatch, useAppSelector } from 'flavours/glitch/store'; + +const messages = defineMessages({ + unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, + follow: { id: 'account.follow', defaultMessage: 'Follow' }, + followBack: { id: 'account.follow_back', defaultMessage: 'Follow back' }, + edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, +}); + +export const FollowButton: React.FC<{ + accountId?: string; +}> = ({ accountId }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + const { signedIn } = useIdentity(); + const account = useAppSelector((state) => + accountId ? state.accounts.get(accountId) : undefined, + ); + const relationship = useAppSelector((state) => + accountId ? state.relationships.get(accountId) : undefined, + ); + const following = relationship?.following || relationship?.requested; + + useEffect(() => { + if (accountId && signedIn) { + dispatch(fetchRelationships([accountId])); + } + }, [dispatch, accountId, signedIn]); + + const handleClick = useCallback(() => { + if (!signedIn) { + dispatch( + openModal({ + modalType: 'INTERACTION', + modalProps: { + type: 'follow', + accountId: accountId, + url: account?.url, + }, + }), + ); + } + + if (!relationship) return; + + if (accountId === me) { + return; + } else if (relationship.following || relationship.requested) { + dispatch( + openModal({ + modalType: 'CONFIRM', + modalProps: { + message: ( + @{account?.acct} }} + /> + ), + confirm: intl.formatMessage(messages.unfollow), + onConfirm: () => { + dispatch(unfollowAccount(accountId)); + }, + }, + }), + ); + } else { + dispatch(followAccount(accountId)); + } + }, [dispatch, intl, accountId, relationship, account, signedIn]); + + let label; + + if (!signedIn) { + label = intl.formatMessage(messages.follow); + } else if (accountId === me) { + label = intl.formatMessage(messages.edit_profile); + } else if (!relationship) { + label = ; + } else if (!relationship.following && relationship.followed_by) { + label = intl.formatMessage(messages.followBack); + } else if (relationship.following || relationship.requested) { + label = intl.formatMessage(messages.unfollow); + } else { + label = intl.formatMessage(messages.follow); + } + + if (accountId === me) { + return ( + + {label} + + ); + } + + return ( + + ); +}; diff --git a/app/javascript/flavours/glitch/components/hashtag_bar.tsx b/app/javascript/flavours/glitch/components/hashtag_bar.tsx index ed5de7d3a5..1642ba6504 100644 --- a/app/javascript/flavours/glitch/components/hashtag_bar.tsx +++ b/app/javascript/flavours/glitch/components/hashtag_bar.tsx @@ -52,7 +52,10 @@ function uniqueHashtagsWithCaseHandling(hashtags: string[]) { ); return Object.values(groups).map((tags) => { - if (tags.length === 1) return tags[0]; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- we know that the array has at least one element + const firstTag = tags[0]!; + + if (tags.length === 1) return firstTag; // The best match is the one where we have the less difference between upper and lower case letter count const best = minBy(tags, (tag) => { @@ -66,7 +69,7 @@ function uniqueHashtagsWithCaseHandling(hashtags: string[]) { return Math.abs(lowerCase - upperCase); }); - return best ?? tags[0]; + return best ?? firstTag; }); } diff --git a/app/javascript/flavours/glitch/components/hover_card_account.tsx b/app/javascript/flavours/glitch/components/hover_card_account.tsx new file mode 100644 index 0000000000..56f431a786 --- /dev/null +++ b/app/javascript/flavours/glitch/components/hover_card_account.tsx @@ -0,0 +1,78 @@ +import { useEffect, forwardRef } from 'react'; + +import classNames from 'classnames'; + +import { fetchAccount } from 'flavours/glitch/actions/accounts'; +import { AccountBio } from 'flavours/glitch/components/account_bio'; +import { AccountFields } from 'flavours/glitch/components/account_fields'; +import { Avatar } from 'flavours/glitch/components/avatar'; +import { FollowersCounter } from 'flavours/glitch/components/counters'; +import { DisplayName } from 'flavours/glitch/components/display_name'; +import { FollowButton } from 'flavours/glitch/components/follow_button'; +import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; +import { Permalink } from 'flavours/glitch/components/permalink'; +import { ShortNumber } from 'flavours/glitch/components/short_number'; +import { domain } from 'flavours/glitch/initial_state'; +import { useAppSelector, useAppDispatch } from 'flavours/glitch/store'; + +export const HoverCardAccount = forwardRef< + HTMLDivElement, + { accountId?: string } +>(({ accountId }, ref) => { + const dispatch = useAppDispatch(); + + const account = useAppSelector((state) => + accountId ? state.accounts.get(accountId) : undefined, + ); + + useEffect(() => { + if (accountId && !account) { + dispatch(fetchAccount(accountId)); + } + }, [dispatch, accountId, account]); + + return ( + + ); +}); + +HoverCardAccount.displayName = 'HoverCardAccount'; diff --git a/app/javascript/flavours/glitch/components/hover_card_controller.tsx b/app/javascript/flavours/glitch/components/hover_card_controller.tsx new file mode 100644 index 0000000000..d2a636e939 --- /dev/null +++ b/app/javascript/flavours/glitch/components/hover_card_controller.tsx @@ -0,0 +1,189 @@ +import { useEffect, useRef, useState, useCallback } from 'react'; + +import { useLocation } from 'react-router-dom'; + +import Overlay from 'react-overlays/Overlay'; +import type { + OffsetValue, + UsePopperOptions, +} from 'react-overlays/esm/usePopper'; + +import { HoverCardAccount } from 'flavours/glitch/components/hover_card_account'; +import { useTimeout } from 'flavours/glitch/hooks/useTimeout'; + +const offset = [-12, 4] as OffsetValue; +const enterDelay = 750; +const leaveDelay = 150; +const popperConfig = { strategy: 'fixed' } as UsePopperOptions; + +const isHoverCardAnchor = (element: HTMLElement) => + element.matches('[data-hover-card-account]'); + +export const HoverCardController: React.FC = () => { + const [open, setOpen] = useState(false); + const [accountId, setAccountId] = useState(); + const [anchor, setAnchor] = useState(null); + const cardRef = useRef(null); + const [setLeaveTimeout, cancelLeaveTimeout] = useTimeout(); + const [setEnterTimeout, cancelEnterTimeout, delayEnterTimeout] = useTimeout(); + const [setScrollTimeout] = useTimeout(); + const location = useLocation(); + + const handleClose = useCallback(() => { + cancelEnterTimeout(); + cancelLeaveTimeout(); + setOpen(false); + setAnchor(null); + }, [cancelEnterTimeout, cancelLeaveTimeout, setOpen, setAnchor]); + + useEffect(() => { + handleClose(); + }, [handleClose, location]); + + useEffect(() => { + let isScrolling = false; + let currentAnchor: HTMLElement | null = null; + let currentTitle: string | null = null; + + const open = (target: HTMLElement) => { + target.setAttribute('aria-describedby', 'hover-card'); + setOpen(true); + setAnchor(target); + setAccountId(target.getAttribute('data-hover-card-account') ?? undefined); + }; + + const close = () => { + currentAnchor?.removeAttribute('aria-describedby'); + currentAnchor = null; + setOpen(false); + setAnchor(null); + setAccountId(undefined); + }; + + const handleMouseEnter = (e: MouseEvent) => { + const { target } = e; + + // We've exited the window + if (!(target instanceof HTMLElement)) { + close(); + return; + } + + // We've entered an anchor + if (!isScrolling && isHoverCardAnchor(target)) { + cancelLeaveTimeout(); + + currentAnchor?.removeAttribute('aria-describedby'); + currentAnchor = target; + + currentTitle = target.getAttribute('title'); + target.removeAttribute('title'); + + setEnterTimeout(() => { + open(target); + }, enterDelay); + } + + // We've entered the hover card + if ( + !isScrolling && + (target === currentAnchor || target === cardRef.current) + ) { + cancelLeaveTimeout(); + } + }; + + const handleMouseLeave = (e: MouseEvent) => { + const { target } = e; + + if (!currentAnchor) { + return; + } + + if ( + currentTitle && + target instanceof HTMLElement && + target === currentAnchor + ) + target.setAttribute('title', currentTitle); + + if (target === currentAnchor || target === cardRef.current) { + cancelEnterTimeout(); + + setLeaveTimeout(() => { + close(); + }, leaveDelay); + } + }; + + const handleScrollEnd = () => { + isScrolling = false; + }; + + const handleScroll = () => { + isScrolling = true; + cancelEnterTimeout(); + setScrollTimeout(handleScrollEnd, 100); + }; + + const handleMouseMove = () => { + delayEnterTimeout(enterDelay); + }; + + document.body.addEventListener('mouseenter', handleMouseEnter, { + passive: true, + capture: true, + }); + + document.body.addEventListener('mousemove', handleMouseMove, { + passive: true, + capture: false, + }); + + document.body.addEventListener('mouseleave', handleMouseLeave, { + passive: true, + capture: true, + }); + + document.addEventListener('scroll', handleScroll, { + passive: true, + capture: true, + }); + + return () => { + document.body.removeEventListener('mouseenter', handleMouseEnter); + document.body.removeEventListener('mousemove', handleMouseMove); + document.body.removeEventListener('mouseleave', handleMouseLeave); + document.removeEventListener('scroll', handleScroll); + }; + }, [ + setEnterTimeout, + setLeaveTimeout, + setScrollTimeout, + cancelEnterTimeout, + cancelLeaveTimeout, + delayEnterTimeout, + setOpen, + setAccountId, + setAnchor, + ]); + + return ( + + {({ props }) => ( +
+ +
+ )} +
+ ); +}; diff --git a/app/javascript/flavours/glitch/components/media_gallery.jsx b/app/javascript/flavours/glitch/components/media_gallery.jsx index ec18f882b6..5c40e2b22f 100644 --- a/app/javascript/flavours/glitch/components/media_gallery.jsx +++ b/app/javascript/flavours/glitch/components/media_gallery.jsx @@ -311,7 +311,7 @@ class MediaGallery extends PureComponent { render () { const { media, lang, intl, sensitive, letterbox, fullwidth, defaultWidth, autoplay } = this.props; const { visible } = this.state; - const size = media.take(4).size; + const size = media.size; const uncached = media.every(attachment => attachment.get('type') === 'unknown'); const width = this.state.width || defaultWidth; @@ -331,7 +331,7 @@ class MediaGallery extends PureComponent { if (this.isStandaloneEligible()) { children = ; } else { - children = media.take(4).map((attachment, i) => ); + children = media.map((attachment, i) => ); } if (uncached) { diff --git a/app/javascript/flavours/glitch/components/more_from_author.jsx b/app/javascript/flavours/glitch/components/more_from_author.jsx new file mode 100644 index 0000000000..4f20ae76bf --- /dev/null +++ b/app/javascript/flavours/glitch/components/more_from_author.jsx @@ -0,0 +1,19 @@ +import PropTypes from 'prop-types'; + +import { FormattedMessage } from 'react-intl'; + +import { AuthorLink } from 'flavours/glitch/features/explore/components/author_link'; + +export const MoreFromAuthor = ({ accountId }) => ( +
+ + + + + }} /> +
+); + +MoreFromAuthor.propTypes = { + accountId: PropTypes.string.isRequired, +}; diff --git a/app/javascript/flavours/glitch/components/poll.jsx b/app/javascript/flavours/glitch/components/poll.jsx index f9e008bd72..a4fb4b62d1 100644 --- a/app/javascript/flavours/glitch/components/poll.jsx +++ b/app/javascript/flavours/glitch/components/poll.jsx @@ -14,6 +14,7 @@ import CheckIcon from '@/material-icons/400-24px/check.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; import emojify from 'flavours/glitch/features/emoji/emoji'; import Motion from 'flavours/glitch/features/ui/util/optional_motion'; +import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import { RelativeTimestamp } from './relative_timestamp'; @@ -38,12 +39,8 @@ const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => { }, {}); class Poll extends ImmutablePureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - static propTypes = { + identity: identityContextPropShape, poll: ImmutablePropTypes.map, lang: PropTypes.string, intl: PropTypes.object.isRequired, @@ -235,7 +232,7 @@ class Poll extends ImmutablePureComponent {
- {!showResults && } + {!showResults && } {!showResults && <> ยท } {showResults && !this.props.disabled && <> ยท } {votesCount} @@ -247,4 +244,4 @@ class Poll extends ImmutablePureComponent { } -export default injectIntl(Poll); +export default injectIntl(withIdentity(Poll)); diff --git a/app/javascript/flavours/glitch/components/server_banner.jsx b/app/javascript/flavours/glitch/components/server_banner.jsx index 1b449ff1a8..43afebe9cf 100644 --- a/app/javascript/flavours/glitch/components/server_banner.jsx +++ b/app/javascript/flavours/glitch/components/server_banner.jsx @@ -42,10 +42,12 @@ class ServerBanner extends PureComponent { return (
- {domain}, mastodon: Mastodon }} /> + {domain}, mastodon: Mastodon }} />
- + + +
{isLoading ? ( @@ -84,10 +86,6 @@ class ServerBanner extends PureComponent { )}
- -
- -
); } diff --git a/app/javascript/flavours/glitch/components/short_number.tsx b/app/javascript/flavours/glitch/components/short_number.tsx index 74c3c5d75e..a0b523aaad 100644 --- a/app/javascript/flavours/glitch/components/short_number.tsx +++ b/app/javascript/flavours/glitch/components/short_number.tsx @@ -48,7 +48,7 @@ const ShortNumberCounter: React.FC = ({ value }) => { const count = ( ); diff --git a/app/javascript/flavours/glitch/components/status.jsx b/app/javascript/flavours/glitch/components/status.jsx index 0faa6d1dbd..cec097ee66 100644 --- a/app/javascript/flavours/glitch/components/status.jsx +++ b/app/javascript/flavours/glitch/components/status.jsx @@ -12,6 +12,7 @@ import { HotKeys } from 'react-hotkeys'; import PictureInPicturePlaceholder from 'flavours/glitch/components/picture_in_picture_placeholder'; import PollContainer from 'flavours/glitch/containers/poll_container'; import NotificationOverlayContainer from 'flavours/glitch/features/notifications/containers/overlay_container'; +import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import { autoUnfoldCW } from 'flavours/glitch/utils/content_warning'; import { withOptionalRouter, WithOptionalRouterPropTypes } from 'flavours/glitch/utils/react_router'; @@ -20,7 +21,6 @@ import Card from '../features/status/components/card'; // to use the progress bar to show download progress import Bundle from '../features/ui/components/bundle'; import { MediaGallery, Video, Audio } from '../features/ui/util/async-components'; -import { IdentityConsumer } from '../features/ui/util/identity_consumer'; import { SensitiveMediaContext } from '../features/ui/util/sensitive_media_context'; import { displayMedia, visibleReactions } from '../initial_state'; @@ -78,6 +78,7 @@ class Status extends ImmutablePureComponent { static contextType = SensitiveMediaContext; static propTypes = { + identity: identityContextPropShape, containerId: PropTypes.string, id: PropTypes.string, status: ImmutablePropTypes.map, @@ -545,6 +546,7 @@ class Status extends ImmutablePureComponent { nextInReplyToId, rootId, history, + identity, ...other } = this.props; const { isCollapsed } = this.state; @@ -807,7 +809,7 @@ class Status extends ImmutablePureComponent { {prepend}
@@ -846,18 +848,14 @@ class Status extends ImmutablePureComponent { {...statusContentProps} /> - - {identity => ( - - )} - + {(!isCollapsed || !(muted || !settings.getIn(['collapsed', 'show_action_bar']))) && ( { - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; if (signedIn) { this.props.onReply(this.props.status, this.props.history); @@ -128,7 +125,7 @@ class StatusActionBar extends ImmutablePureComponent { }; handleFavouriteClick = (e) => { - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; if (signedIn) { this.props.onFavourite(this.props.status, e); @@ -142,7 +139,7 @@ class StatusActionBar extends ImmutablePureComponent { }; handleReblogClick = e => { - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; if (signedIn) { this.props.onReblog(this.props.status, e); @@ -218,7 +215,7 @@ class StatusActionBar extends ImmutablePureComponent { render () { const { status, intl, withDismiss, withCounters, showReplyCount, scrollKey } = this.props; - const { permissions, signedIn } = this.context.identity; + const { permissions, signedIn } = this.props.identity; const mutingConversation = status.get('muted'); const publicStatus = ['public', 'unlisted'].includes(status.get('visibility')); @@ -370,4 +367,4 @@ class StatusActionBar extends ImmutablePureComponent { } -export default withRouter(injectIntl(StatusActionBar)); +export default withRouter(withIdentity(injectIntl(StatusActionBar))); diff --git a/app/javascript/flavours/glitch/components/status_content.jsx b/app/javascript/flavours/glitch/components/status_content.jsx index f5d5ccc70a..634ab0db29 100644 --- a/app/javascript/flavours/glitch/components/status_content.jsx +++ b/app/javascript/flavours/glitch/components/status_content.jsx @@ -15,6 +15,7 @@ import LinkIcon from '@/material-icons/400-24px/link.svg?react'; import MovieIcon from '@/material-icons/400-24px/movie.svg?react'; import MusicNoteIcon from '@/material-icons/400-24px/music_note.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; +import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import { autoPlayGif, languages as preloadedLanguages } from 'flavours/glitch/initial_state'; import { decode as decodeIDNA } from 'flavours/glitch/utils/idna'; @@ -126,12 +127,8 @@ const mapStateToProps = state => ({ }); class StatusContent extends PureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - static propTypes = { + identity: identityContextPropShape, status: ImmutablePropTypes.map.isRequired, statusContent: PropTypes.string, expanded: PropTypes.bool, @@ -185,6 +182,7 @@ class StatusContent extends PureComponent { if (mention) { link.addEventListener('click', this.onMentionClick.bind(this, mention), false); link.setAttribute('title', `@${mention.get('acct')}`); + link.setAttribute('data-hover-card-account', mention.get('id')); if (rewriteMentions !== 'no') { while (link.firstChild) link.removeChild(link.firstChild); link.appendChild(document.createTextNode('@')); @@ -349,7 +347,7 @@ class StatusContent extends PureComponent { const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden; const contentLocale = intl.locale.replace(/[_-].*/, ''); const targetLanguages = this.props.languages?.get(status.get('language') || 'und'); - const renderTranslate = this.props.onTranslate && this.context.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('search_index').trim().length > 0 && targetLanguages?.includes(contentLocale); + const renderTranslate = this.props.onTranslate && this.props.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('search_index').trim().length > 0 && targetLanguages?.includes(contentLocale); const content = { __html: statusContent ?? getStatusContent(status) }; const spoilerContent = { __html: status.getIn(['translation', 'spoilerHtml']) || status.get('spoilerHtml') }; @@ -503,4 +501,4 @@ class StatusContent extends PureComponent { } -export default withRouter(connect(mapStateToProps)(injectIntl(StatusContent))); +export default withRouter(withIdentity(connect(mapStateToProps)(injectIntl(StatusContent)))); diff --git a/app/javascript/flavours/glitch/components/status_header.jsx b/app/javascript/flavours/glitch/components/status_header.jsx index 692dca5c7b..2dd09d68ef 100644 --- a/app/javascript/flavours/glitch/components/status_header.jsx +++ b/app/javascript/flavours/glitch/components/status_header.jsx @@ -51,6 +51,8 @@ export default class StatusHeader extends PureComponent { target='_blank' onClick={this.handleAccountClick} rel='noopener noreferrer' + title={status.getIn(['account', 'acct'])} + data-hover-card-account={status.getIn(['account', 'id'])} >
{statusAvatar} diff --git a/app/javascript/flavours/glitch/components/status_list.jsx b/app/javascript/flavours/glitch/components/status_list.jsx index dde8bd9663..374d14a56a 100644 --- a/app/javascript/flavours/glitch/components/status_list.jsx +++ b/app/javascript/flavours/glitch/components/status_list.jsx @@ -33,6 +33,7 @@ export default class StatusList extends ImmutablePureComponent { withCounters: PropTypes.bool, timelineId: PropTypes.string.isRequired, lastId: PropTypes.string, + bindToDocument: PropTypes.bool, regex: PropTypes.string, }; diff --git a/app/javascript/flavours/glitch/components/status_prepend.jsx b/app/javascript/flavours/glitch/components/status_prepend.jsx index e2ccea8183..7502c04240 100644 --- a/app/javascript/flavours/glitch/components/status_prepend.jsx +++ b/app/javascript/flavours/glitch/components/status_prepend.jsx @@ -39,6 +39,7 @@ export default class StatusPrepend extends PureComponent { onClick={this.handleClick} href={account.get('url')} className='status__display-name' + data-hover-card-account={account.get('id')} > ({ - signedIn: !!state.meta.me, - accountId: state.meta.me, - disabledAccountId: state.meta.disabled_account_id, - accessToken: state.meta.access_token, - permissions: state.role ? state.role.permissions : 0, -}); - export default class Mastodon extends PureComponent { - - static childContextTypes = { - identity: PropTypes.shape({ - signedIn: PropTypes.bool.isRequired, - accountId: PropTypes.string, - disabledAccountId: PropTypes.string, - accessToken: PropTypes.string, - }).isRequired, - }; - identity = createIdentityContext(initialState); - getChildContext() { - return { - identity: this.identity, - }; - } - componentDidMount() { if (this.identity.signedIn) { this.disconnect = store.dispatch(connectUserStream()); @@ -79,19 +55,21 @@ export default class Mastodon extends PureComponent { render () { return ( - - - - - - - - + + + + + + + + + - - - - + + + + + ); } diff --git a/app/javascript/flavours/glitch/containers/status_container.js b/app/javascript/flavours/glitch/containers/status_container.js index 88a8a9785d..703dbdf47b 100644 --- a/app/javascript/flavours/glitch/containers/status_container.js +++ b/app/javascript/flavours/glitch/containers/status_container.js @@ -117,9 +117,9 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({ onModalReblog (status, privacy) { if (status.get('reblogged')) { - dispatch(unreblog(status)); + dispatch(unreblog({ statusId: status.get('id') })); } else { - dispatch(reblog(status, privacy)); + dispatch(reblog({ statusId: status.get('id'), visibility: privacy })); } }, diff --git a/app/javascript/flavours/glitch/packs/admin.tsx b/app/javascript/flavours/glitch/entrypoints/admin.tsx similarity index 99% rename from app/javascript/flavours/glitch/packs/admin.tsx rename to app/javascript/flavours/glitch/entrypoints/admin.tsx index 5c1e43d1cf..209799ca26 100644 --- a/app/javascript/flavours/glitch/packs/admin.tsx +++ b/app/javascript/flavours/glitch/entrypoints/admin.tsx @@ -1,4 +1,4 @@ -import 'packs/public-path'; +import '@/entrypoints/public-path'; import { createRoot } from 'react-dom/client'; import Rails from '@rails/ujs'; diff --git a/app/javascript/flavours/glitch/packs/application.js b/app/javascript/flavours/glitch/entrypoints/application.ts similarity index 70% rename from app/javascript/flavours/glitch/packs/application.js rename to app/javascript/flavours/glitch/entrypoints/application.ts index 336a250f54..3659d8212b 100644 --- a/app/javascript/flavours/glitch/packs/application.js +++ b/app/javascript/flavours/glitch/entrypoints/application.ts @@ -1,8 +1,8 @@ -import 'packs/public-path'; +import '@/entrypoints/public-path'; import { start } from 'flavours/glitch/common'; import { loadLocale } from 'flavours/glitch/locales'; -import main from "flavours/glitch/main"; +import main from 'flavours/glitch/main'; import { loadPolyfills } from 'flavours/glitch/polyfills'; start(); @@ -10,6 +10,6 @@ start(); loadPolyfills() .then(loadLocale) .then(main) - .catch(e => { + .catch((e: unknown) => { console.error(e); }); diff --git a/app/javascript/flavours/glitch/packs/common.js b/app/javascript/flavours/glitch/entrypoints/common.js similarity index 90% rename from app/javascript/flavours/glitch/packs/common.js rename to app/javascript/flavours/glitch/entrypoints/common.js index 79467fc493..ade52fc413 100644 --- a/app/javascript/flavours/glitch/packs/common.js +++ b/app/javascript/flavours/glitch/entrypoints/common.js @@ -1,7 +1,7 @@ /* This file is a hack to have something more reliable than the upstream `common` tag that is implicitly generated as the common chunk through webpack's `splitChunks` config */ -import 'packs/public-path'; +import '@/entrypoints/public-path'; import 'font-awesome/css/font-awesome.css'; // This is a hack to ensures that webpack compiles our images. diff --git a/app/javascript/flavours/glitch/packs/error.js b/app/javascript/flavours/glitch/entrypoints/error.ts similarity index 57% rename from app/javascript/flavours/glitch/packs/error.js rename to app/javascript/flavours/glitch/entrypoints/error.ts index f13e321498..9e067d4caa 100644 --- a/app/javascript/flavours/glitch/packs/error.js +++ b/app/javascript/flavours/glitch/entrypoints/error.ts @@ -1,8 +1,10 @@ -import 'packs/public-path'; +import '@/entrypoints/public-path'; import ready from 'flavours/glitch/ready'; ready(() => { - const image = document.querySelector('img'); + const image = document.querySelector('img'); + + if (!image) return; image.addEventListener('mouseenter', () => { image.src = '/oops.gif'; @@ -11,4 +13,6 @@ ready(() => { image.addEventListener('mouseleave', () => { image.src = '/oops.png'; }); +}).catch((e: unknown) => { + console.error(e); }); diff --git a/app/javascript/flavours/glitch/packs/inert.js b/app/javascript/flavours/glitch/entrypoints/inert.ts similarity index 100% rename from app/javascript/flavours/glitch/packs/inert.js rename to app/javascript/flavours/glitch/entrypoints/inert.ts diff --git a/app/javascript/flavours/glitch/packs/mailer.js b/app/javascript/flavours/glitch/entrypoints/mailer.ts similarity index 100% rename from app/javascript/flavours/glitch/packs/mailer.js rename to app/javascript/flavours/glitch/entrypoints/mailer.ts diff --git a/app/javascript/flavours/glitch/packs/public.tsx b/app/javascript/flavours/glitch/entrypoints/public.tsx similarity index 96% rename from app/javascript/flavours/glitch/packs/public.tsx rename to app/javascript/flavours/glitch/entrypoints/public.tsx index d4002a7a3b..5a8d5e08cb 100644 --- a/app/javascript/flavours/glitch/packs/public.tsx +++ b/app/javascript/flavours/glitch/entrypoints/public.tsx @@ -1,6 +1,6 @@ import { createRoot } from 'react-dom/client'; -import 'packs/public-path'; +import '@/entrypoints/public-path'; import { IntlMessageFormat } from 'intl-messageformat'; import type { MessageDescriptor, PrimitiveType } from 'react-intl'; @@ -65,7 +65,7 @@ window.addEventListener('message', (e) => { { type: 'setHeight', id: data.id, - height: document.getElementsByTagName('html')[0].scrollHeight, + height: document.getElementsByTagName('html')[0]?.scrollHeight, }, '*', ); @@ -135,7 +135,7 @@ function loaded() { ); }; const todayFormat = new IntlMessageFormat( - localeData['relative_format.today'] || 'Today at {time}', + localeData['relative_format.today'] ?? 'Today at {time}', locale, ); @@ -288,13 +288,13 @@ function loaded() { if (statusEl.dataset.spoiler === 'expanded') { statusEl.dataset.spoiler = 'folded'; this.textContent = new IntlMessageFormat( - localeData['status.show_more'] || 'Show more', + localeData['status.show_more'] ?? 'Show more', locale, ).format() as string; } else { statusEl.dataset.spoiler = 'expanded'; this.textContent = new IntlMessageFormat( - localeData['status.show_less'] || 'Show less', + localeData['status.show_less'] ?? 'Show less', locale, ).format() as string; } @@ -316,8 +316,8 @@ function loaded() { const message = statusEl.dataset.spoiler === 'expanded' - ? localeData['status.show_less'] || 'Show less' - : localeData['status.show_more'] || 'Show more'; + ? localeData['status.show_less'] ?? 'Show less' + : localeData['status.show_more'] ?? 'Show more'; spoilerLink.textContent = new IntlMessageFormat( message, locale, diff --git a/app/javascript/flavours/glitch/packs/remote_interaction_helper.ts b/app/javascript/flavours/glitch/entrypoints/remote_interaction_helper.ts similarity index 95% rename from app/javascript/flavours/glitch/packs/remote_interaction_helper.ts rename to app/javascript/flavours/glitch/entrypoints/remote_interaction_helper.ts index 4da4d49f6e..3bfc1fc139 100644 --- a/app/javascript/flavours/glitch/packs/remote_interaction_helper.ts +++ b/app/javascript/flavours/glitch/entrypoints/remote_interaction_helper.ts @@ -8,7 +8,7 @@ and performs no other task. */ -import 'packs/public-path'; +import '@/entrypoints/public-path'; import axios from 'axios'; @@ -67,7 +67,9 @@ const fetchInteractionURLFailure = () => { ); }; -const isValidDomain = (value: string) => { +const isValidDomain = (value: unknown) => { + if (typeof value !== 'string') return false; + const url = new URL('https:///path'); url.hostname = value; return url.hostname === value; @@ -124,6 +126,11 @@ const fromAcct = (acct: string) => { const domain = segments[1]; const fallbackTemplate = `https://${domain}/authorize_interaction?uri={uri}`; + if (!domain) { + fetchInteractionURLFailure(); + return; + } + axios .get(`https://${domain}/.well-known/webfinger`, { params: { resource: `acct:${acct}` }, diff --git a/app/javascript/flavours/glitch/packs/share.jsx b/app/javascript/flavours/glitch/entrypoints/share.tsx similarity index 70% rename from app/javascript/flavours/glitch/packs/share.jsx rename to app/javascript/flavours/glitch/entrypoints/share.tsx index 3d938f5b2a..0eda442506 100644 --- a/app/javascript/flavours/glitch/packs/share.jsx +++ b/app/javascript/flavours/glitch/entrypoints/share.tsx @@ -1,4 +1,4 @@ -import 'packs/public-path'; +import '@/entrypoints/public-path'; import { createRoot } from 'react-dom/client'; import { start } from 'flavours/glitch/common'; @@ -16,7 +16,7 @@ function loaded() { if (!attr) return; - const props = JSON.parse(attr); + const props = JSON.parse(attr) as object; const root = createRoot(mountNode); root.render(); @@ -24,9 +24,13 @@ function loaded() { } function main() { - ready(loaded); + ready(loaded).catch((error: unknown) => { + console.error(error); + }); } -loadPolyfills().then(main).catch(error => { - console.error(error); -}); +loadPolyfills() + .then(main) + .catch((error: unknown) => { + console.error(error); + }); diff --git a/app/javascript/flavours/glitch/entrypoints/sign_up.ts b/app/javascript/flavours/glitch/entrypoints/sign_up.ts new file mode 100644 index 0000000000..18e2931546 --- /dev/null +++ b/app/javascript/flavours/glitch/entrypoints/sign_up.ts @@ -0,0 +1,48 @@ +import '@/entrypoints/public-path'; +import axios from 'axios'; + +import ready from 'flavours/glitch/ready'; + +async function checkConfirmation() { + const response = await axios.get('/api/v1/emails/check_confirmation'); + + if (response.data) { + window.location.href = '/start'; + } +} + +ready(() => { + setInterval(() => { + void checkConfirmation(); + }, 5000); + + document + .querySelectorAll('button.timer-button') + .forEach((button) => { + let counter = 30; + + const container = document.createElement('span'); + + const updateCounter = () => { + container.innerText = ` (${counter})`; + }; + + updateCounter(); + + const countdown = setInterval(() => { + counter--; + + if (counter === 0) { + button.disabled = false; + button.removeChild(container); + clearInterval(countdown); + } else { + updateCounter(); + } + }, 1000); + + button.appendChild(container); + }); +}).catch((e: unknown) => { + throw e; +}); diff --git a/app/javascript/flavours/glitch/entrypoints/two_factor_authentication.ts b/app/javascript/flavours/glitch/entrypoints/two_factor_authentication.ts new file mode 100644 index 0000000000..3106167117 --- /dev/null +++ b/app/javascript/flavours/glitch/entrypoints/two_factor_authentication.ts @@ -0,0 +1,197 @@ +import * as WebAuthnJSON from '@github/webauthn-json'; +import axios, { AxiosError } from 'axios'; + +import ready from 'flavours/glitch/ready'; + +import 'regenerator-runtime/runtime'; + +type PublicKeyCredentialCreationOptionsJSON = + WebAuthnJSON.CredentialCreationOptionsJSON['publicKey']; + +function exceptionHasAxiosError( + error: unknown, +): error is AxiosError<{ error: unknown }> { + return ( + error instanceof AxiosError && + typeof error.response?.data === 'object' && + 'error' in error.response.data + ); +} + +function logAxiosResponseError(error: unknown) { + if (exceptionHasAxiosError(error)) console.error(error); +} + +function getCSRFToken() { + return document + .querySelector('meta[name="csrf-token"]') + ?.getAttribute('content'); +} + +function hideFlashMessages() { + document.querySelectorAll('.flash-message').forEach((flashMessage) => { + flashMessage.classList.add('hidden'); + }); +} + +async function callback( + url: string, + body: + | { + credential: WebAuthnJSON.PublicKeyCredentialWithAttestationJSON; + nickname: string; + } + | { + user: { credential: WebAuthnJSON.PublicKeyCredentialWithAssertionJSON }; + }, +) { + try { + const response = await axios.post<{ redirect_path: string }>( + url, + JSON.stringify(body), + { + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + 'X-CSRF-Token': getCSRFToken(), + }, + }, + ); + + window.location.replace(response.data.redirect_path); + } catch (error) { + if (error instanceof AxiosError && error.response?.status === 422) { + const errorMessage = document.getElementById( + 'security-key-error-message', + ); + errorMessage?.classList.remove('hidden'); + + logAxiosResponseError(error); + } else { + console.error(error); + } + } +} + +async function handleWebauthnCredentialRegistration(nickname: string) { + try { + const response = await axios.get( + '/settings/security_keys/options', + ); + + const credentialOptions = response.data; + + try { + const credential = await WebAuthnJSON.create({ + publicKey: credentialOptions, + }); + + const params = { + credential: credential, + nickname: nickname, + }; + + await callback('/settings/security_keys', params); + } catch (error) { + const errorMessage = document.getElementById( + 'security-key-error-message', + ); + errorMessage?.classList.remove('hidden'); + console.error(error); + } + } catch (error) { + logAxiosResponseError(error); + } +} + +async function handleWebauthnCredentialAuthentication() { + try { + const response = await axios.get( + 'sessions/security_key_options', + ); + + const credentialOptions = response.data; + + try { + const credential = await WebAuthnJSON.get({ + publicKey: credentialOptions, + }); + + const params = { user: { credential: credential } }; + void callback('sign_in', params); + } catch (error) { + const errorMessage = document.getElementById( + 'security-key-error-message', + ); + errorMessage?.classList.remove('hidden'); + console.error(error); + } + } catch (error) { + logAxiosResponseError(error); + } +} + +ready(() => { + if (!WebAuthnJSON.supported()) { + const unsupported_browser_message = document.getElementById( + 'unsupported-browser-message', + ); + if (unsupported_browser_message) { + unsupported_browser_message.classList.remove('hidden'); + const button = document.querySelector( + 'button.btn.js-webauthn', + ); + if (button) button.disabled = true; + } + } + + const webAuthnCredentialRegistrationForm = + document.querySelector('form#new_webauthn_credential'); + if (webAuthnCredentialRegistrationForm) { + webAuthnCredentialRegistrationForm.addEventListener('submit', (event) => { + event.preventDefault(); + + if (!(event.target instanceof HTMLFormElement)) return; + + const nickname = event.target.querySelector( + 'input[name="new_webauthn_credential[nickname]"]', + ); + + if (nickname?.value) { + void handleWebauthnCredentialRegistration(nickname.value); + } else { + nickname?.focus(); + } + }); + } + + const webAuthnCredentialAuthenticationForm = + document.getElementById('webauthn-form'); + if (webAuthnCredentialAuthenticationForm) { + webAuthnCredentialAuthenticationForm.addEventListener('submit', (event) => { + event.preventDefault(); + void handleWebauthnCredentialAuthentication(); + }); + + const otpAuthenticationForm = document.getElementById( + 'otp-authentication-form', + ); + + const linkToOtp = document.getElementById('link-to-otp'); + + linkToOtp?.addEventListener('click', () => { + webAuthnCredentialAuthenticationForm.classList.add('hidden'); + otpAuthenticationForm?.classList.remove('hidden'); + hideFlashMessages(); + }); + + const linkToWebAuthn = document.getElementById('link-to-webauthn'); + linkToWebAuthn?.addEventListener('click', () => { + otpAuthenticationForm?.classList.add('hidden'); + webAuthnCredentialAuthenticationForm.classList.remove('hidden'); + hideFlashMessages(); + }); + } +}).catch((e: unknown) => { + throw e; +}); diff --git a/app/javascript/flavours/glitch/features/account/components/header.jsx b/app/javascript/flavours/glitch/features/account/components/header.jsx index e311936af9..62c8c84c66 100644 --- a/app/javascript/flavours/glitch/features/account/components/header.jsx +++ b/app/javascript/flavours/glitch/features/account/components/header.jsx @@ -23,6 +23,7 @@ import { Icon } from 'flavours/glitch/components/icon'; import { IconButton } from 'flavours/glitch/components/icon_button'; import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container'; +import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import { autoPlayGif, me, domain as localDomain } from 'flavours/glitch/initial_state'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions'; import { preferencesLink, profileLink, accountAdminLink } from 'flavours/glitch/utils/backend_links'; @@ -94,6 +95,7 @@ const dateFormatOptions = { class Header extends ImmutablePureComponent { static propTypes = { + identity: identityContextPropShape, account: ImmutablePropTypes.record, identity_props: ImmutablePropTypes.list, onFollow: PropTypes.func.isRequired, @@ -117,10 +119,6 @@ class Header extends ImmutablePureComponent { ...WithRouterPropTypes, }; - static contextTypes = { - identity: PropTypes.object, - }; - openEditProfile = () => { window.open(profileLink, '_blank'); }; @@ -170,7 +168,7 @@ class Header extends ImmutablePureComponent { render () { const { account, hidden, intl } = this.props; - const { signedIn, permissions } = this.context.identity; + const { signedIn, permissions } = this.props.identity; if (!account) { return null; @@ -414,4 +412,4 @@ class Header extends ImmutablePureComponent { } -export default withRouter(injectIntl(Header)); +export default withRouter(withIdentity(injectIntl(Header))); diff --git a/app/javascript/flavours/glitch/features/account/containers/account_note_container.js b/app/javascript/flavours/glitch/features/account/containers/account_note_container.js index d98a3996d0..135e772f3e 100644 --- a/app/javascript/flavours/glitch/features/account/containers/account_note_container.js +++ b/app/javascript/flavours/glitch/features/account/containers/account_note_container.js @@ -11,7 +11,7 @@ const mapStateToProps = (state, { account }) => ({ const mapDispatchToProps = (dispatch, { account }) => ({ onSave (value) { - dispatch(submitAccountNote({ id: account.get('id'), value})); + dispatch(submitAccountNote({ accountId: account.get('id'), note: value })); }, }); diff --git a/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.jsx b/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.jsx index a87c7fd3e4..1e85a08cd9 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.jsx +++ b/app/javascript/flavours/glitch/features/account_timeline/containers/header_container.jsx @@ -23,7 +23,6 @@ import { makeGetAccount, getAccountHidden } from '../../../selectors'; import Header from '../components/header'; const messages = defineMessages({ - cancelFollowRequestConfirm: { id: 'confirmations.cancel_follow_request.confirm', defaultMessage: 'Withdraw request' }, unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' }, blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Block entire domain' }, }); @@ -43,7 +42,7 @@ const makeMapStateToProps = () => { const mapDispatchToProps = (dispatch, { intl }) => ({ onFollow (account) { - if (account.getIn(['relationship', 'following'])) { + if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) { dispatch(openModal({ modalType: 'CONFIRM', modalProps: { @@ -52,15 +51,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ onConfirm: () => dispatch(unfollowAccount(account.get('id'))), }, })); - } else if (account.getIn(['relationship', 'requested'])) { - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: @{account.get('acct')} }} />, - confirm: intl.formatMessage(messages.cancelFollowRequestConfirm), - onConfirm: () => dispatch(unfollowAccount(account.get('id'))), - }, - })); } else { dispatch(followAccount(account.get('id'))); } diff --git a/app/javascript/flavours/glitch/features/community_timeline/index.jsx b/app/javascript/flavours/glitch/features/community_timeline/index.jsx index 7be2196511..9f306923e3 100644 --- a/app/javascript/flavours/glitch/features/community_timeline/index.jsx +++ b/app/javascript/flavours/glitch/features/community_timeline/index.jsx @@ -9,6 +9,7 @@ import { connect } from 'react-redux'; import PeopleIcon from '@/material-icons/400-24px/group.svg?react'; import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner'; +import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import { domain } from 'flavours/glitch/initial_state'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; @@ -40,16 +41,12 @@ const mapStateToProps = (state, { columnId }) => { }; class CommunityTimeline extends PureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - static defaultProps = { onlyMedia: false, }; static propTypes = { + identity: identityContextPropShape, dispatch: PropTypes.func.isRequired, columnId: PropTypes.string, intl: PropTypes.object.isRequired, @@ -80,7 +77,7 @@ class CommunityTimeline extends PureComponent { componentDidMount () { const { dispatch, onlyMedia } = this.props; - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; dispatch(expandCommunityTimeline({ onlyMedia })); @@ -90,7 +87,7 @@ class CommunityTimeline extends PureComponent { } componentDidUpdate (prevProps) { - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; if (prevProps.onlyMedia !== this.props.onlyMedia) { const { dispatch, onlyMedia } = this.props; @@ -165,4 +162,4 @@ class CommunityTimeline extends PureComponent { } -export default connect(mapStateToProps)(injectIntl(CommunityTimeline)); +export default withIdentity(connect(mapStateToProps)(injectIntl(CommunityTimeline))); diff --git a/app/javascript/flavours/glitch/features/compose/components/language_dropdown.jsx b/app/javascript/flavours/glitch/features/compose/components/language_dropdown.jsx index 8edf75203f..1ad9e03040 100644 --- a/app/javascript/flavours/glitch/features/compose/components/language_dropdown.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/language_dropdown.jsx @@ -110,18 +110,6 @@ class LanguageDropdownMenu extends PureComponent { }).map(result => result.obj); } - frequentlyUsed () { - const { languages, value } = this.props; - const current = languages.find(lang => lang[0] === value); - const results = []; - - if (current) { - results.push(current); - } - - return results; - } - handleClick = e => { const value = e.currentTarget.getAttribute('data-index'); diff --git a/app/javascript/flavours/glitch/features/compose/components/search.jsx b/app/javascript/flavours/glitch/features/compose/components/search.jsx index 4b64038f57..f5a51334af 100644 --- a/app/javascript/flavours/glitch/features/compose/components/search.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/search.jsx @@ -12,6 +12,7 @@ import CancelIcon from '@/material-icons/400-24px/cancel-fill.svg?react'; import CloseIcon from '@/material-icons/400-24px/close.svg?react'; import SearchIcon from '@/material-icons/400-24px/search.svg?react'; import { Icon } from 'flavours/glitch/components/icon'; +import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import { domain, searchEnabled } from 'flavours/glitch/initial_state'; import { HASHTAG_REGEX } from 'flavours/glitch/utils/hashtags'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; @@ -33,12 +34,8 @@ const labelForRecentSearch = search => { }; class Search extends PureComponent { - - static contextTypes = { - identity: PropTypes.object.isRequired, - }; - static propTypes = { + identity: identityContextPropShape, value: PropTypes.string.isRequired, recent: ImmutablePropTypes.orderedSet, submitted: PropTypes.bool, @@ -276,7 +273,7 @@ class Search extends PureComponent { } _calculateOptions (value) { - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; const trimmedValue = value.trim(); const options = []; @@ -318,7 +315,7 @@ class Search extends PureComponent { render () { const { intl, value, submitted, recent } = this.props; const { expanded, options, selectedOption } = this.state; - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; const hasValue = value.length > 0 || submitted; @@ -402,4 +399,4 @@ class Search extends PureComponent { } -export default withRouter(injectIntl(Search)); +export default withRouter(withIdentity(injectIntl(Search))); diff --git a/app/javascript/flavours/glitch/features/compose/components/search_results.jsx b/app/javascript/flavours/glitch/features/compose/components/search_results.jsx index 3df025db55..350fc41c51 100644 --- a/app/javascript/flavours/glitch/features/compose/components/search_results.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/search_results.jsx @@ -1,16 +1,16 @@ -import PropTypes from 'prop-types'; +import { useCallback } from 'react'; import { FormattedMessage } from 'react-intl'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; - import FindInPageIcon from '@/material-icons/400-24px/find_in_page.svg?react'; import PeopleIcon from '@/material-icons/400-24px/group.svg?react'; import TagIcon from '@/material-icons/400-24px/tag.svg?react'; +import { expandSearch } from 'flavours/glitch/actions/search'; import { Icon } from 'flavours/glitch/components/icon'; import { LoadMore } from 'flavours/glitch/components/load_more'; +import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; import { SearchSection } from 'flavours/glitch/features/explore/components/search_section'; +import { useAppDispatch, useAppSelector } from 'flavours/glitch/store'; import { ImmutableHashtag as Hashtag } from '../../../components/hashtag'; import AccountContainer from '../../../containers/account_container'; @@ -26,62 +26,68 @@ const withoutLastResult = list => { } }; -class SearchResults extends ImmutablePureComponent { +export const SearchResults = () => { + const results = useAppSelector((state) => state.getIn(['search', 'results'])); + const isLoading = useAppSelector((state) => state.getIn(['search', 'isLoading'])); - static propTypes = { - results: ImmutablePropTypes.map.isRequired, - expandSearch: PropTypes.func.isRequired, - searchTerm: PropTypes.string, - }; + const dispatch = useAppDispatch(); - handleLoadMoreAccounts = () => this.props.expandSearch('accounts'); + const handleLoadMoreAccounts = useCallback(() => { + dispatch(expandSearch('accounts')); + }, [dispatch]); - handleLoadMoreStatuses = () => this.props.expandSearch('statuses'); + const handleLoadMoreStatuses = useCallback(() => { + dispatch(expandSearch('statuses')); + }, [dispatch]); - handleLoadMoreHashtags = () => this.props.expandSearch('hashtags'); + const handleLoadMoreHashtags = useCallback(() => { + dispatch(expandSearch('hashtags')); + }, [dispatch]); - render () { - const { results } = this.props; + let accounts, statuses, hashtags; - let accounts, statuses, hashtags; - - if (results.get('accounts') && results.get('accounts').size > 0) { - accounts = ( - }> - {withoutLastResult(results.get('accounts')).map(accountId => )} - {(results.get('accounts').size > INITIAL_PAGE_LIMIT && results.get('accounts').size % INITIAL_PAGE_LIMIT === 1) && } - - ); - } - - if (results.get('hashtags') && results.get('hashtags').size > 0) { - hashtags = ( - }> - {withoutLastResult(results.get('hashtags')).map(hashtag => )} - {(results.get('hashtags').size > INITIAL_PAGE_LIMIT && results.get('hashtags').size % INITIAL_PAGE_LIMIT === 1) && } - - ); - } - - if (results.get('statuses') && results.get('statuses').size > 0) { - statuses = ( - }> - {withoutLastResult(results.get('statuses')).map(statusId => )} - {(results.get('statuses').size > INITIAL_PAGE_LIMIT && results.get('statuses').size % INITIAL_PAGE_LIMIT === 1) && } - - ); - } - - - return ( -
- {accounts} - {hashtags} - {statuses} -
+ if (results.get('accounts') && results.get('accounts').size > 0) { + accounts = ( + }> + {withoutLastResult(results.get('accounts')).map(accountId => )} + {(results.get('accounts').size > INITIAL_PAGE_LIMIT && results.get('accounts').size % INITIAL_PAGE_LIMIT === 1) && } + ); } -} + if (results.get('hashtags') && results.get('hashtags').size > 0) { + hashtags = ( + }> + {withoutLastResult(results.get('hashtags')).map(hashtag => )} + {(results.get('hashtags').size > INITIAL_PAGE_LIMIT && results.get('hashtags').size % INITIAL_PAGE_LIMIT === 1) && } + + ); + } -export default SearchResults; + if (results.get('statuses') && results.get('statuses').size > 0) { + statuses = ( + }> + {withoutLastResult(results.get('statuses')).map(statusId => )} + {(results.get('statuses').size > INITIAL_PAGE_LIMIT && results.get('statuses').size % INITIAL_PAGE_LIMIT === 1) && } + + ); + } + + return ( +
+ {!accounts && !hashtags && !statuses && ( + isLoading ? ( + + ) : ( +
+ +
+ ) + )} + {accounts} + {hashtags} + {statuses} +
+ ); + +}; diff --git a/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js b/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js deleted file mode 100644 index 7287a022b4..0000000000 --- a/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js +++ /dev/null @@ -1,20 +0,0 @@ -import { connect } from 'react-redux'; - -import { expandSearch } from 'flavours/glitch/actions/search'; -import { fetchSuggestions, dismissSuggestion } from 'flavours/glitch/actions/suggestions'; - -import SearchResults from '../components/search_results'; - -const mapStateToProps = state => ({ - results: state.getIn(['search', 'results']), - suggestions: state.getIn(['suggestions', 'items']), - searchTerm: state.getIn(['search', 'searchTerm']), -}); - -const mapDispatchToProps = dispatch => ({ - fetchSuggestions: () => dispatch(fetchSuggestions()), - expandSearch: type => dispatch(expandSearch(type)), - dismissSuggestion: account => dispatch(dismissSuggestion(account.get('id'))), -}); - -export default connect(mapStateToProps, mapDispatchToProps)(SearchResults); diff --git a/app/javascript/flavours/glitch/features/compose/containers/upload_button_container.js b/app/javascript/flavours/glitch/features/compose/containers/upload_button_container.js index d2941c4e45..7c187050f3 100644 --- a/app/javascript/flavours/glitch/features/compose/containers/upload_button_container.js +++ b/app/javascript/flavours/glitch/features/compose/containers/upload_button_container.js @@ -9,7 +9,7 @@ const mapStateToProps = state => { const readyAttachmentsSize = state.getIn(['compose', 'media_attachments']).size ?? 0; const pendingAttachmentsSize = state.getIn(['compose', 'pending_media_attachments']).size ?? 0; const attachmentsSize = readyAttachmentsSize + pendingAttachmentsSize; - const isOverLimit = attachmentsSize > 3; + const isOverLimit = attachmentsSize > state.getIn(['server', 'server', 'configuration', 'statuses', 'max_media_attachments'])-1; const hasVideoOrAudio = state.getIn(['compose', 'media_attachments']).some(m => ['video', 'audio'].includes(m.get('type'))); return { diff --git a/app/javascript/flavours/glitch/features/compose/index.jsx b/app/javascript/flavours/glitch/features/compose/index.jsx index 0bba28b787..b59fafe048 100644 --- a/app/javascript/flavours/glitch/features/compose/index.jsx +++ b/app/javascript/flavours/glitch/features/compose/index.jsx @@ -32,9 +32,9 @@ import { mascot } from '../../initial_state'; import { isMobile } from '../../is_mobile'; import Motion from '../ui/util/optional_motion'; +import { SearchResults } from './components/search_results'; import ComposeFormContainer from './containers/compose_form_container'; import SearchContainer from './containers/search_container'; -import SearchResultsContainer from './containers/search_results_container'; const messages = defineMessages({ start: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, @@ -183,7 +183,7 @@ class Compose extends PureComponent { {({ x }) => (
- +
)}
diff --git a/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx b/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx index 458a547d02..7071c8719d 100644 --- a/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx +++ b/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx @@ -185,7 +185,7 @@ export const Conversation = ({ conversation, scrollKey, onMoveUp, onMoveDown }) menu.push({ text: intl.formatMessage(messages.delete), action: handleDelete }); const names = accounts.map(a => ( - + { - const getAccount = makeGetAccount(); - - const mapStateToProps = (state, { id }) => ({ - account: getAccount(state, id), - }); - - return mapStateToProps; -}; - -const mapDispatchToProps = (dispatch, { intl }) => ({ - onFollow(account) { - if (account.getIn(['relationship', 'following'])) { - dispatch( - openModal({ - modalType: 'CONFIRM', - modalProps: { - message: ( - @{account.get('acct')} }} - /> - ), - confirm: intl.formatMessage(messages.unfollowConfirm), - onConfirm: () => dispatch(unfollowAccount(account.get('id'))), - } }), - ); - } else if (account.getIn(['relationship', 'requested'])) { - dispatch(openModal({ - modalType: 'CONFIRM', - modalProps: { - message: @{account.get('acct')} }} />, - confirm: intl.formatMessage(messages.cancelFollowRequestConfirm), - onConfirm: () => dispatch(unfollowAccount(account.get('id'))), - }, - })); - } else { - dispatch(followAccount(account.get('id'))); - } - }, - - onBlock(account) { - if (account.getIn(['relationship', 'blocking'])) { - dispatch(unblockAccount(account.get('id'))); - } - }, - - onMute(account) { - if (account.getIn(['relationship', 'muting'])) { - dispatch(unmuteAccount(account.get('id'))); - } - }, - -}); - -class AccountCard extends ImmutablePureComponent { - - static propTypes = { - account: ImmutablePropTypes.record.isRequired, - intl: PropTypes.object.isRequired, - onFollow: PropTypes.func.isRequired, - onBlock: PropTypes.func.isRequired, - onMute: PropTypes.func.isRequired, - onDismiss: PropTypes.func, - }; - - handleMouseEnter = ({ currentTarget }) => { - if (autoPlayGif) { - return; - } - - const emojis = currentTarget.querySelectorAll('.custom-emoji'); - - for (var i = 0; i < emojis.length; i++) { - let emoji = emojis[i]; - emoji.src = emoji.getAttribute('data-original'); - } - }; - - handleMouseLeave = ({ currentTarget }) => { - if (autoPlayGif) { - return; - } - - const emojis = currentTarget.querySelectorAll('.custom-emoji'); - - for (var i = 0; i < emojis.length; i++) { - let emoji = emojis[i]; - emoji.src = emoji.getAttribute('data-static'); - } - }; - - handleFollow = () => { - this.props.onFollow(this.props.account); - }; - - handleBlock = () => { - this.props.onBlock(this.props.account); - }; - - handleMute = () => { - this.props.onMute(this.props.account); - }; - - handleEditProfile = () => { - window.open('/settings/profile', '_blank'); - }; - - handleDismiss = (e) => { - const { account, onDismiss } = this.props; - onDismiss(account.get('id')); - - e.preventDefault(); - e.stopPropagation(); - }; - - render() { - const { account, intl } = this.props; - - let actionBtn; - - if (me !== account.get('id')) { - if (!account.get('relationship')) { // Wait until the relationship is loaded - actionBtn = ''; - } else if (account.getIn(['relationship', 'requested'])) { - actionBtn =
); }; diff --git a/app/javascript/flavours/glitch/features/home_timeline/index.jsx b/app/javascript/flavours/glitch/features/home_timeline/index.jsx index a2683f587f..3220f777e1 100644 --- a/app/javascript/flavours/glitch/features/home_timeline/index.jsx +++ b/app/javascript/flavours/glitch/features/home_timeline/index.jsx @@ -14,6 +14,7 @@ import { fetchAnnouncements, toggleShowAnnouncements } from 'flavours/glitch/act import { IconWithBadge } from 'flavours/glitch/components/icon_with_badge'; import { NotSignedInIndicator } from 'flavours/glitch/components/not_signed_in_indicator'; import AnnouncementsContainer from 'flavours/glitch/features/getting_started/containers/announcements_container'; +import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import { criticalUpdatesPending } from 'flavours/glitch/initial_state'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; @@ -41,12 +42,8 @@ const mapStateToProps = state => ({ }); class HomeTimeline extends PureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - static propTypes = { + identity: identityContextPropShape, dispatch: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, hasUnread: PropTypes.bool, @@ -128,7 +125,7 @@ class HomeTimeline extends PureComponent { render () { const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props; const pinned = !!columnId; - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; const banners = []; let announcementsButton; @@ -192,4 +189,4 @@ class HomeTimeline extends PureComponent { } -export default connect(mapStateToProps)(injectIntl(HomeTimeline)); +export default connect(mapStateToProps)(withIdentity(injectIntl(HomeTimeline))); diff --git a/app/javascript/flavours/glitch/features/link_timeline/index.tsx b/app/javascript/flavours/glitch/features/link_timeline/index.tsx new file mode 100644 index 0000000000..bbe295d474 --- /dev/null +++ b/app/javascript/flavours/glitch/features/link_timeline/index.tsx @@ -0,0 +1,77 @@ +import { useRef, useEffect, useCallback } from 'react'; + +import { Helmet } from 'react-helmet'; +import { useParams } from 'react-router-dom'; + +import ExploreIcon from '@/material-icons/400-24px/explore.svg?react'; +import { expandLinkTimeline } from 'flavours/glitch/actions/timelines'; +import Column from 'flavours/glitch/components/column'; +import { ColumnHeader } from 'flavours/glitch/components/column_header'; +import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container'; +import type { Card } from 'flavours/glitch/models/status'; +import { useAppDispatch, useAppSelector } from 'flavours/glitch/store'; + +export const LinkTimeline: React.FC<{ + multiColumn: boolean; +}> = ({ multiColumn }) => { + const { url } = useParams<{ url: string }>(); + const decodedUrl = url ? decodeURIComponent(url) : undefined; + const dispatch = useAppDispatch(); + const columnRef = useRef(null); + const firstStatusId = useAppSelector((state) => + decodedUrl + ? // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + (state.timelines.getIn([`link:${decodedUrl}`, 'items', 0]) as string) + : undefined, + ); + const story = useAppSelector((state) => + firstStatusId + ? (state.statuses.getIn([firstStatusId, 'card']) as Card) + : undefined, + ); + + const handleHeaderClick = useCallback(() => { + columnRef.current?.scrollTop(); + }, []); + + const handleLoadMore = useCallback( + (maxId: string) => { + dispatch(expandLinkTimeline(decodedUrl, { maxId })); + }, + [dispatch, decodedUrl], + ); + + useEffect(() => { + dispatch(expandLinkTimeline(decodedUrl)); + }, [dispatch, decodedUrl]); + + return ( + + + + + + + {story?.title} + + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export default LinkTimeline; diff --git a/app/javascript/flavours/glitch/features/notifications/components/column_settings.jsx b/app/javascript/flavours/glitch/features/notifications/components/column_settings.jsx index b13ce2f4ab..0d77b3d581 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/column_settings.jsx +++ b/app/javascript/flavours/glitch/features/notifications/components/column_settings.jsx @@ -5,6 +5,7 @@ import { FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_REPORTS } from 'flavours/glitch/permissions'; import { CheckboxWithLabel } from './checkbox_with_label'; @@ -13,13 +14,9 @@ import GrantPermissionButton from './grant_permission_button'; import PillBarButton from './pill_bar_button'; import SettingToggle from './setting_toggle'; -export default class ColumnSettings extends PureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - +class ColumnSettings extends PureComponent { static propTypes = { + identity: identityContextPropShape, settings: ImmutablePropTypes.map.isRequired, pushSettings: ImmutablePropTypes.map.isRequired, onChange: PropTypes.func.isRequired, @@ -28,7 +25,7 @@ export default class ColumnSettings extends PureComponent { alertsEnabled: PropTypes.bool, browserSupport: PropTypes.bool, browserPermission: PropTypes.string, - notificationPolicy: ImmutablePropTypes.map, + notificationPolicy: PropTypes.object.isRequired, onChangePolicy: PropTypes.func.isRequired, }; @@ -87,22 +84,22 @@ export default class ColumnSettings extends PureComponent {

- + - + - + - + @@ -227,7 +224,7 @@ export default class ColumnSettings extends PureComponent {
- {((this.context.identity.permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) && ( + {((this.props.identity.permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) && (

@@ -240,7 +237,7 @@ export default class ColumnSettings extends PureComponent {
)} - {((this.context.identity.permissions & PERMISSION_MANAGE_REPORTS) === PERMISSION_MANAGE_REPORTS) && ( + {((this.props.identity.permissions & PERMISSION_MANAGE_REPORTS) === PERMISSION_MANAGE_REPORTS) && (

@@ -257,3 +254,5 @@ export default class ColumnSettings extends PureComponent { } } + +export default withIdentity(ColumnSettings); diff --git a/app/javascript/flavours/glitch/features/notifications/components/filtered_notifications_banner.jsx b/app/javascript/flavours/glitch/features/notifications/components/filtered_notifications_banner.jsx deleted file mode 100644 index 8c8b9e4b86..0000000000 --- a/app/javascript/flavours/glitch/features/notifications/components/filtered_notifications_banner.jsx +++ /dev/null @@ -1,49 +0,0 @@ -import { useEffect } from 'react'; - -import { FormattedMessage } from 'react-intl'; - -import { Link } from 'react-router-dom'; - -import { useDispatch, useSelector } from 'react-redux'; - -import InventoryIcon from '@/material-icons/400-24px/inventory_2.svg?react'; -import { fetchNotificationPolicy } from 'flavours/glitch/actions/notifications'; -import { Icon } from 'flavours/glitch/components/icon'; -import { toCappedNumber } from 'flavours/glitch/utils/numbers'; - -export const FilteredNotificationsBanner = () => { - const dispatch = useDispatch(); - const policy = useSelector(state => state.get('notificationPolicy')); - - useEffect(() => { - dispatch(fetchNotificationPolicy()); - - const interval = setInterval(() => { - dispatch(fetchNotificationPolicy()); - }, 120000); - - return () => { - clearInterval(interval); - }; - }, [dispatch]); - - if (policy === null || policy.getIn(['summary', 'pending_notifications_count']) === 0) { - return null; - } - - return ( - - - -
- - -
- -
-
{toCappedNumber(policy.getIn(['summary', 'pending_notifications_count']))}
- -
- - ); -}; diff --git a/app/javascript/flavours/glitch/features/notifications/components/filtered_notifications_banner.tsx b/app/javascript/flavours/glitch/features/notifications/components/filtered_notifications_banner.tsx new file mode 100644 index 0000000000..70762be0e3 --- /dev/null +++ b/app/javascript/flavours/glitch/features/notifications/components/filtered_notifications_banner.tsx @@ -0,0 +1,68 @@ +import { useEffect } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { Link } from 'react-router-dom'; + +import InventoryIcon from '@/material-icons/400-24px/inventory_2.svg?react'; +import { fetchNotificationPolicy } from 'flavours/glitch/actions/notification_policies'; +import { Icon } from 'flavours/glitch/components/icon'; +import { useAppSelector, useAppDispatch } from 'flavours/glitch/store'; +import { toCappedNumber } from 'flavours/glitch/utils/numbers'; + +export const FilteredNotificationsBanner: React.FC = () => { + const dispatch = useAppDispatch(); + const policy = useAppSelector((state) => state.notificationPolicy); + + useEffect(() => { + void dispatch(fetchNotificationPolicy()); + + const interval = setInterval(() => { + void dispatch(fetchNotificationPolicy()); + }, 120000); + + return () => { + clearInterval(interval); + }; + }, [dispatch]); + + if (policy === null || policy.summary.pending_notifications_count === 0) { + return null; + } + + return ( + + + +
+ + + + + + +
+ +
+
+ {toCappedNumber(policy.summary.pending_notifications_count)} +
+ +
+ + ); +}; diff --git a/app/javascript/flavours/glitch/features/notifications/components/moderation_warning.tsx b/app/javascript/flavours/glitch/features/notifications/components/moderation_warning.tsx new file mode 100644 index 0000000000..2fd19c5108 --- /dev/null +++ b/app/javascript/flavours/glitch/features/notifications/components/moderation_warning.tsx @@ -0,0 +1,78 @@ +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; + +import GavelIcon from '@/material-icons/400-24px/gavel.svg?react'; +import { Icon } from 'flavours/glitch/components/icon'; + +// This needs to be kept in sync with app/models/account_warning.rb +const messages = defineMessages({ + none: { + id: 'notification.moderation_warning.action_none', + defaultMessage: 'Your account has received a moderation warning.', + }, + disable: { + id: 'notification.moderation_warning.action_disable', + defaultMessage: 'Your account has been disabled.', + }, + mark_statuses_as_sensitive: { + id: 'notification.moderation_warning.action_mark_statuses_as_sensitive', + defaultMessage: 'Some of your posts have been marked as sensitive.', + }, + delete_statuses: { + id: 'notification.moderation_warning.action_delete_statuses', + defaultMessage: 'Some of your posts have been removed.', + }, + sensitive: { + id: 'notification.moderation_warning.action_sensitive', + defaultMessage: 'Your posts will be marked as sensitive from now on.', + }, + silence: { + id: 'notification.moderation_warning.action_silence', + defaultMessage: 'Your account has been limited.', + }, + suspend: { + id: 'notification.moderation_warning.action_suspend', + defaultMessage: 'Your account has been suspended.', + }, +}); + +interface Props { + action: + | 'none' + | 'disable' + | 'mark_statuses_as_sensitive' + | 'delete_statuses' + | 'sensitive' + | 'silence' + | 'suspend'; + id: string; + hidden: boolean; +} + +export const ModerationWarning: React.FC = ({ action, id, hidden }) => { + const intl = useIntl(); + + if (hidden) { + return null; + } + + return ( + + + +
+

{intl.formatMessage(messages[action])}

+ + + +
+
+ ); +}; diff --git a/app/javascript/flavours/glitch/features/notifications/components/notification.jsx b/app/javascript/flavours/glitch/features/notifications/components/notification.jsx index bd8d6ad53b..8e74584165 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/notification.jsx +++ b/app/javascript/flavours/glitch/features/notifications/components/notification.jsx @@ -22,6 +22,7 @@ import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import FollowRequestContainer from '../containers/follow_request_container'; import NotificationOverlayContainer from '../containers/overlay_container'; +import { ModerationWarning } from './moderation_warning'; import { RelationshipsSeveranceEvent } from './relationships_severance_event'; import Report from './report'; @@ -30,6 +31,7 @@ const messages = defineMessages({ adminSignUp: { id: 'notification.admin.sign_up', defaultMessage: '{name} signed up' }, adminReport: { id: 'notification.admin.report', defaultMessage: '{name} reported {target}' }, relationshipsSevered: { id: 'notification.relationships_severance_event', defaultMessage: 'Lost connections with {name}' }, + moderationWarning: { id: 'notification.moderation_warning', defaultMessage: 'You have received a moderation warning' }, }); const notificationForScreenReader = (intl, message, timestamp) => { @@ -353,6 +355,27 @@ class Notification extends ImmutablePureComponent { ); } + renderModerationWarning (notification) { + const { intl, unread, hidden } = this.props; + const warning = notification.get('moderation_warning'); + + if (!warning) { + return null; + } + + return ( + +
+
+
+ ); + } + renderAdminSignUp (notification, account, link) { const { intl, unread } = this.props; @@ -391,6 +414,7 @@ class Notification extends ImmutablePureComponent { title={targetAccount.get('acct')} to={`/@${targetAccount.get('acct')}`} dangerouslySetInnerHTML={targetDisplayNameHtml} + data-hover-card-account={targetAccount.get('id')} /> ); @@ -425,6 +449,7 @@ class Notification extends ImmutablePureComponent { title={account.get('acct')} to={`/@${account.get('acct')}`} dangerouslySetInnerHTML={displayNameHtml} + data-hover-card-account={account.get('id')} /> ); @@ -450,6 +475,8 @@ class Notification extends ImmutablePureComponent { return this.renderPoll(notification); case 'severed_relationships': return this.renderRelationshipsSevered(notification); + case 'moderation_warning': + return this.renderModerationWarning(notification); case 'admin.sign_up': return this.renderAdminSignUp(notification, account, link); case 'admin.report': diff --git a/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js b/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js index de266160f8..4547fde042 100644 --- a/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js +++ b/app/javascript/flavours/glitch/features/notifications/containers/column_settings_container.js @@ -4,7 +4,8 @@ import { connect } from 'react-redux'; import { showAlert } from '../../../actions/alerts'; import { openModal } from '../../../actions/modal'; -import { setFilter, clearNotifications, requestBrowserPermission, updateNotificationsPolicy } from '../../../actions/notifications'; +import { updateNotificationsPolicy } from '../../../actions/notification_policies'; +import { setFilter, clearNotifications, requestBrowserPermission } from '../../../actions/notifications'; import { changeAlerts as changePushNotifications } from '../../../actions/push_notifications'; import { changeSetting } from '../../../actions/settings'; import ColumnSettings from '../components/column_settings'; @@ -15,13 +16,16 @@ const messages = defineMessages({ permissionDenied: { id: 'notifications.permission_denied_alert', defaultMessage: 'Desktop notifications can\'t be enabled, as browser permission has been denied before' }, }); +/** + * @param {import('flavours/glitch/store').RootState} state + */ const mapStateToProps = state => ({ settings: state.getIn(['settings', 'notifications']), pushSettings: state.get('push_notifications'), alertsEnabled: state.getIn(['settings', 'notifications', 'alerts']).includes(true), browserSupport: state.getIn(['notifications', 'browserSupport']), browserPermission: state.getIn(['notifications', 'browserPermission']), - notificationPolicy: state.get('notificationPolicy'), + notificationPolicy: state.notificationPolicy, }); const mapDispatchToProps = (dispatch, { intl }) => ({ diff --git a/app/javascript/flavours/glitch/features/notifications/containers/notification_container.js b/app/javascript/flavours/glitch/features/notifications/containers/notification_container.js index de5cb2b11e..338d27d8d6 100644 --- a/app/javascript/flavours/glitch/features/notifications/containers/notification_container.js +++ b/app/javascript/flavours/glitch/features/notifications/containers/notification_container.js @@ -36,12 +36,12 @@ const mapDispatchToProps = dispatch => ({ }, onModalReblog (status, privacy) { - dispatch(reblog(status, privacy)); + dispatch(reblog({ statusId: status.get('id'), visibility: privacy })); }, onReblog (status, e) { if (status.get('reblogged')) { - dispatch(unreblog(status)); + dispatch(unreblog({ statusId: status.get('id') })); } else { if (e.shiftKey || !boostModal) { this.onModalReblog(status); diff --git a/app/javascript/flavours/glitch/features/notifications/index.jsx b/app/javascript/flavours/glitch/features/notifications/index.jsx index e84ef70b05..b4f1bf5dfe 100644 --- a/app/javascript/flavours/glitch/features/notifications/index.jsx +++ b/app/javascript/flavours/glitch/features/notifications/index.jsx @@ -19,6 +19,7 @@ import NotificationsIcon from '@/material-icons/400-24px/notifications-fill.svg? import { compareId } from 'flavours/glitch/compare_id'; import { Icon } from 'flavours/glitch/components/icon'; import { NotSignedInIndicator } from 'flavours/glitch/components/not_signed_in_indicator'; +import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { submitMarkers } from '../../actions/markers'; @@ -93,12 +94,8 @@ const mapDispatchToProps = dispatch => ({ }); class Notifications extends PureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - static propTypes = { + identity: identityContextPropShape, columnId: PropTypes.string, notifications: ImmutablePropTypes.list.isRequired, showFilterBar: PropTypes.bool.isRequired, @@ -225,7 +222,7 @@ class Notifications extends PureComponent { const { animatingNCD } = this.state; const pinned = !!columnId; const emptyMessage = ; - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; let scrollableContent = null; @@ -373,4 +370,4 @@ class Notifications extends PureComponent { } -export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(Notifications)); +export default connect(mapStateToProps, mapDispatchToProps)(withIdentity(injectIntl(Notifications))); diff --git a/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.jsx b/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.jsx index e21ee2d3d3..13ad86f801 100644 --- a/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.jsx +++ b/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.jsx @@ -18,6 +18,7 @@ import { replyCompose } from 'flavours/glitch/actions/compose'; import { reblog, favourite, unreblog, unfavourite } from 'flavours/glitch/actions/interactions'; import { openModal } from 'flavours/glitch/actions/modal'; import { IconButton } from 'flavours/glitch/components/icon_button'; +import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import { me, boostModal } from 'flavours/glitch/initial_state'; import { makeGetStatus } from 'flavours/glitch/selectors'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; @@ -48,12 +49,8 @@ const makeMapStateToProps = () => { }; class Footer extends ImmutablePureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - static propTypes = { + identity: identityContextPropShape, statusId: PropTypes.string.isRequired, status: ImmutablePropTypes.map.isRequired, intl: PropTypes.object.isRequired, @@ -77,7 +74,7 @@ class Footer extends ImmutablePureComponent { handleReplyClick = () => { const { dispatch, askReplyConfirmation, status, intl } = this.props; - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; if (signedIn) { if (askReplyConfirmation) { @@ -106,7 +103,7 @@ class Footer extends ImmutablePureComponent { handleFavouriteClick = () => { const { dispatch, status } = this.props; - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; if (signedIn) { if (status.get('favourited')) { @@ -128,16 +125,16 @@ class Footer extends ImmutablePureComponent { _performReblog = (status, privacy) => { const { dispatch } = this.props; - dispatch(reblog(status, privacy)); + dispatch(reblog({ statusId: status.get('id'), visibility: privacy })); }; handleReblogClick = e => { const { dispatch, status } = this.props; - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; if (signedIn) { if (status.get('reblogged')) { - dispatch(unreblog(status)); + dispatch(unreblog({ statusId: status.get('id') })); } else if ((e && e.shiftKey) || !boostModal) { this._performReblog(status); } else { @@ -236,4 +233,4 @@ class Footer extends ImmutablePureComponent { } -export default connect(makeMapStateToProps)(withRouter(injectIntl(Footer))); +export default connect(makeMapStateToProps)(withIdentity(withRouter(injectIntl(Footer)))); diff --git a/app/javascript/flavours/glitch/features/public_timeline/index.jsx b/app/javascript/flavours/glitch/features/public_timeline/index.jsx index 8b9503928b..1f255a468d 100644 --- a/app/javascript/flavours/glitch/features/public_timeline/index.jsx +++ b/app/javascript/flavours/glitch/features/public_timeline/index.jsx @@ -9,6 +9,7 @@ import { connect } from 'react-redux'; import PublicIcon from '@/material-icons/400-24px/public.svg?react'; import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner'; +import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import { domain } from 'flavours/glitch/initial_state'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; @@ -44,16 +45,12 @@ const mapStateToProps = (state, { columnId }) => { }; class PublicTimeline extends PureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - static defaultProps = { onlyMedia: false, }; static propTypes = { + identity: identityContextPropShape, dispatch: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, columnId: PropTypes.string, @@ -86,7 +83,7 @@ class PublicTimeline extends PureComponent { componentDidMount () { const { dispatch, onlyMedia, onlyRemote, allowLocalOnly } = this.props; - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; dispatch(expandPublicTimeline({ onlyMedia, onlyRemote, allowLocalOnly })); if (signedIn) { @@ -95,7 +92,7 @@ class PublicTimeline extends PureComponent { } componentDidUpdate (prevProps) { - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; if (prevProps.onlyMedia !== this.props.onlyMedia || prevProps.onlyRemote !== this.props.onlyRemote || prevProps.allowLocalOnly !== this.props.allowLocalOnly) { const { dispatch, onlyMedia, onlyRemote, allowLocalOnly } = this.props; @@ -170,4 +167,4 @@ class PublicTimeline extends PureComponent { } -export default connect(mapStateToProps)(injectIntl(PublicTimeline)); +export default connect(mapStateToProps)(withIdentity(injectIntl(PublicTimeline))); diff --git a/app/javascript/flavours/glitch/features/status/components/action_bar.jsx b/app/javascript/flavours/glitch/features/status/components/action_bar.jsx index 084d67aa82..7e041fac58 100644 --- a/app/javascript/flavours/glitch/features/status/components/action_bar.jsx +++ b/app/javascript/flavours/glitch/features/status/components/action_bar.jsx @@ -21,6 +21,7 @@ import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react'; import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react'; import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react'; import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react'; +import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions'; import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; @@ -62,12 +63,8 @@ const messages = defineMessages({ }); class ActionBar extends PureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - static propTypes = { + identity: identityContextPropShape, status: ImmutablePropTypes.map.isRequired, onReply: PropTypes.func.isRequired, onReblog: PropTypes.func.isRequired, @@ -165,7 +162,7 @@ class ActionBar extends PureComponent { render () { const { status, intl } = this.props; - const { signedIn, permissions } = this.context.identity; + const { signedIn, permissions } = this.props.identity; const publicStatus = ['public', 'unlisted'].includes(status.get('visibility')); const pinnableStatus = ['public', 'unlisted', 'private'].includes(status.get('visibility')); @@ -276,4 +273,4 @@ class ActionBar extends PureComponent { } -export default withRouter(injectIntl(ActionBar)); +export default withRouter(withIdentity(injectIntl(ActionBar))); diff --git a/app/javascript/flavours/glitch/features/status/components/card.jsx b/app/javascript/flavours/glitch/features/status/components/card.jsx index 4c535b0a46..933f2b1045 100644 --- a/app/javascript/flavours/glitch/features/status/components/card.jsx +++ b/app/javascript/flavours/glitch/features/status/components/card.jsx @@ -13,6 +13,7 @@ import OpenInNewIcon from '@/material-icons/400-24px/open_in_new.svg?react'; import PlayArrowIcon from '@/material-icons/400-24px/play_arrow-fill.svg?react'; import { Blurhash } from 'flavours/glitch/components/blurhash'; import { Icon } from 'flavours/glitch/components/icon'; +import { MoreFromAuthor } from 'flavours/glitch/components/more_from_author'; import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp'; import { useBlurhash } from 'flavours/glitch/initial_state'; import { decode as decodeIDNA } from 'flavours/glitch/utils/idna'; @@ -126,9 +127,10 @@ export default class Card extends PureComponent { const interactive = card.get('type') === 'video'; const language = card.get('language') || ''; const largeImage = (card.get('image')?.length > 0 && card.get('width') > card.get('height')) || interactive; + const showAuthor = !!card.getIn(['authors', 0, 'accountId']); const description = ( -
+
{provider} {card.get('published_at') && <> ยท } @@ -136,7 +138,7 @@ export default class Card extends PureComponent { {card.get('title')} - {card.get('author_name').length > 0 ? {card.get('author_name')} }} /> : {card.get('description')}} + {!showAuthor && (card.get('author_name').length > 0 ? {card.get('author_name')} }} /> : {card.get('description')})}
); @@ -225,10 +227,14 @@ export default class Card extends PureComponent { } return ( - - {embed} - {description} - + <> + + {embed} + {description} + + + {showAuthor && } + ); } diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx b/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx index 5fe87eb593..57bcf09d91 100644 --- a/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx +++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx @@ -15,6 +15,7 @@ import { getHashtagBarForStatus } from 'flavours/glitch/components/hashtag_bar'; import PictureInPicturePlaceholder from 'flavours/glitch/components/picture_in_picture_placeholder'; import { VisibilityIcon } from 'flavours/glitch/components/visibility_icon'; import PollContainer from 'flavours/glitch/containers/poll_container'; +import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import { Avatar } from '../../../components/avatar'; @@ -22,6 +23,7 @@ import { DisplayName } from '../../../components/display_name'; import MediaGallery from '../../../components/media_gallery'; import StatusContent from '../../../components/status_content'; import StatusReactions from '../../../components/status_reactions'; +import { visibleReactions } from '../../../initial_state'; import Audio from '../../audio'; import scheduleIdleTask from '../../ui/util/schedule_idle_task'; import Video from '../../video'; @@ -29,12 +31,8 @@ import Video from '../../video'; import Card from './card'; class DetailedStatus extends ImmutablePureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - static propTypes = { + identity: identityContextPropShape, status: ImmutablePropTypes.map, settings: ImmutablePropTypes.map.isRequired, onOpenMedia: PropTypes.func.isRequired, @@ -292,7 +290,7 @@ class DetailedStatus extends ImmutablePureComponent { return (
- +
@@ -314,13 +312,13 @@ class DetailedStatus extends ImmutablePureComponent { {...statusContentProps} /> - 0 && ( + canReact={this.props.identity.signedIn} + />)}
@@ -348,4 +346,4 @@ class DetailedStatus extends ImmutablePureComponent { } -export default withRouter(DetailedStatus); +export default withRouter(withIdentity(DetailedStatus)); diff --git a/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js b/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js index 7e8fe49b23..7cb9735cfe 100644 --- a/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js +++ b/app/javascript/flavours/glitch/features/status/containers/detailed_status_container.js @@ -71,12 +71,12 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ }, onModalReblog (status, privacy) { - dispatch(reblog(status, privacy)); + dispatch(reblog({ statusId: status.get('id'), visibility: privacy })); }, onReblog (status, e) { if (status.get('reblogged')) { - dispatch(unreblog(status)); + dispatch(unreblog({ statusId: status.get('id') })); } else { if (e.shiftKey || !boostModal) { this.onModalReblog(status); diff --git a/app/javascript/flavours/glitch/features/status/index.jsx b/app/javascript/flavours/glitch/features/status/index.jsx index e975e21c18..5993a79f3d 100644 --- a/app/javascript/flavours/glitch/features/status/index.jsx +++ b/app/javascript/flavours/glitch/features/status/index.jsx @@ -21,6 +21,7 @@ import { Icon } from 'flavours/glitch/components/icon'; import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; import ScrollContainer from 'flavours/glitch/containers/scroll_container'; import BundleColumnError from 'flavours/glitch/features/ui/components/bundle_column_error'; +import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import { autoUnfoldCW } from 'flavours/glitch/utils/content_warning'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; @@ -187,12 +188,8 @@ const titleFromStatus = (intl, status) => { }; class Status extends ImmutablePureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - static propTypes = { + identity: identityContextPropShape, params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, status: ImmutablePropTypes.map, @@ -279,7 +276,7 @@ class Status extends ImmutablePureComponent { handleFavouriteClick = (status, e) => { const { dispatch } = this.props; - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; if (signedIn) { if (status.get('favourited')) { @@ -310,8 +307,8 @@ class Status extends ImmutablePureComponent { }; handleReactionAdd = (statusId, name, url) => { - const { dispatch } = this.props; - const { signedIn } = this.context.identity; + const { dispatch, identity } = this.props; + const { signedIn } = identity; if (signedIn) { dispatch(addReaction(statusId, name, url)); @@ -332,7 +329,7 @@ class Status extends ImmutablePureComponent { handleReplyClick = (status) => { const { askReplyConfirmation, dispatch, intl } = this.props; - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; if (signedIn) { if (askReplyConfirmation) { @@ -364,15 +361,15 @@ class Status extends ImmutablePureComponent { const { dispatch } = this.props; if (status.get('reblogged')) { - dispatch(unreblog(status)); + dispatch(unreblog({ statusId: status.get('id') })); } else { - dispatch(reblog(status, privacy)); + dispatch(reblog({ statusId: status.get('id'), visibility: privacy })); } }; handleReblogClick = (status, e) => { const { settings, dispatch } = this.props; - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; if (signedIn) { if (settings.get('confirm_boost_missing_media_description') && status.get('media_attachments').some(item => !item.get('description')) && !status.get('reblogged')) { @@ -810,4 +807,4 @@ class Status extends ImmutablePureComponent { } -export default withRouter(injectIntl(connect(makeMapStateToProps)(Status))); +export default withRouter(injectIntl(connect(makeMapStateToProps)(withIdentity(Status)))); diff --git a/app/javascript/flavours/glitch/features/ui/components/column_loading.tsx b/app/javascript/flavours/glitch/features/ui/components/column_loading.tsx index 42174838cf..aa6d105dcc 100644 --- a/app/javascript/flavours/glitch/features/ui/components/column_loading.tsx +++ b/app/javascript/flavours/glitch/features/ui/components/column_loading.tsx @@ -1,11 +1,8 @@ -import Column from '../../../components/column'; -import ColumnHeader from '../../../components/column_header'; +import Column from 'flavours/glitch/components/column'; +import { ColumnHeader } from 'flavours/glitch/components/column_header'; +import type { Props as ColumnHeaderProps } from 'flavours/glitch/components/column_header'; -interface Props { - multiColumn?: boolean; -} - -export const ColumnLoading: React.FC = (otherProps) => ( +export const ColumnLoading: React.FC = (otherProps) => (
diff --git a/app/javascript/flavours/glitch/features/ui/components/compose_panel.jsx b/app/javascript/flavours/glitch/features/ui/components/compose_panel.jsx index a99d76c1a4..e530b87d26 100644 --- a/app/javascript/flavours/glitch/features/ui/components/compose_panel.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/compose_panel.jsx @@ -7,16 +7,13 @@ import { mountCompose, unmountCompose } from 'flavours/glitch/actions/compose'; import ServerBanner from 'flavours/glitch/components/server_banner'; import ComposeFormContainer from 'flavours/glitch/features/compose/containers/compose_form_container'; import SearchContainer from 'flavours/glitch/features/compose/containers/search_container'; +import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import LinkFooter from './link_footer'; class ComposePanel extends PureComponent { - - static contextTypes = { - identity: PropTypes.object.isRequired, - }; - static propTypes = { + identity: identityContextPropShape, dispatch: PropTypes.func.isRequired, }; @@ -31,7 +28,7 @@ class ComposePanel extends PureComponent { } render() { - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; return (
@@ -55,4 +52,4 @@ class ComposePanel extends PureComponent { } -export default connect()(ComposePanel); +export default connect()(withIdentity(ComposePanel)); diff --git a/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.jsx index 0ee84dd0c2..ac0ad4cd92 100644 --- a/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.jsx @@ -408,7 +408,6 @@ class FocalPointModal extends ImmutablePureComponent { blurhash={media.get('blurhash')} src={media.get('url')} detailed - inline editable /> )} diff --git a/app/javascript/flavours/glitch/features/ui/components/header.jsx b/app/javascript/flavours/glitch/features/ui/components/header.jsx index 618a6b63d4..f102912faa 100644 --- a/app/javascript/flavours/glitch/features/ui/components/header.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/header.jsx @@ -14,6 +14,7 @@ import { Avatar } from 'flavours/glitch/components/avatar'; import { Icon } from 'flavours/glitch/components/icon'; import { WordmarkLogo, SymbolLogo } from 'flavours/glitch/components/logo'; import { Permalink } from 'flavours/glitch/components/permalink'; +import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import { registrationsOpen, me, sso_redirect } from 'flavours/glitch/initial_state'; const Account = connect(state => ({ @@ -42,12 +43,8 @@ const mapDispatchToProps = (dispatch) => ({ }); class Header extends PureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - static propTypes = { + identity: identityContextPropShape, openClosedRegistrationsModal: PropTypes.func, location: PropTypes.object, signupUrl: PropTypes.string.isRequired, @@ -61,7 +58,7 @@ class Header extends PureComponent { } render () { - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; const { location, openClosedRegistrationsModal, signupUrl, intl } = this.props; let content; @@ -122,4 +119,4 @@ class Header extends PureComponent { } -export default injectIntl(withRouter(connect(mapStateToProps, mapDispatchToProps)(Header))); +export default injectIntl(withRouter(withIdentity(connect(mapStateToProps, mapDispatchToProps)(Header)))); diff --git a/app/javascript/flavours/glitch/features/ui/components/link_footer.jsx b/app/javascript/flavours/glitch/features/ui/components/link_footer.jsx index d5a94861b2..73e007a424 100644 --- a/app/javascript/flavours/glitch/features/ui/components/link_footer.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/link_footer.jsx @@ -8,6 +8,7 @@ import { Link } from 'react-router-dom'; import { connect } from 'react-redux'; import { openModal } from 'flavours/glitch/actions/modal'; +import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import { domain, version, source_url, statusPageUrl, profile_directory as profileDirectory } from 'flavours/glitch/initial_state'; import { PERMISSION_INVITE_USERS } from 'flavours/glitch/permissions'; import { logOut } from 'flavours/glitch/utils/log_out'; @@ -32,12 +33,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ }); class LinkFooter extends PureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - static propTypes = { + identity: identityContextPropShape, multiColumn: PropTypes.bool, onLogout: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, @@ -53,7 +50,7 @@ class LinkFooter extends PureComponent { }; render () { - const { signedIn, permissions } = this.context.identity; + const { signedIn, permissions } = this.props.identity; const { multiColumn } = this.props; const canInvite = signedIn && ((permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS); @@ -78,4 +75,4 @@ class LinkFooter extends PureComponent { } -export default injectIntl(connect(null, mapDispatchToProps)(LinkFooter)); +export default injectIntl(withIdentity(connect(null, mapDispatchToProps)(LinkFooter))); diff --git a/app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx b/app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx index b8bb47a7a4..415830f769 100644 --- a/app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx @@ -30,6 +30,7 @@ import StarIcon from '@/material-icons/400-24px/star.svg?react'; import { fetchFollowRequests } from 'flavours/glitch/actions/accounts'; import { IconWithBadge } from 'flavours/glitch/components/icon_with_badge'; import { NavigationPortal } from 'flavours/glitch/components/navigation_portal'; +import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import { timelinePreview, trendsEnabled } from 'flavours/glitch/initial_state'; import { transientSingleColumn } from 'flavours/glitch/is_mobile'; import { preferencesLink } from 'flavours/glitch/utils/backend_links'; @@ -98,12 +99,8 @@ const FollowRequestsLink = () => { }; class NavigationPanel extends Component { - - static contextTypes = { - identity: PropTypes.object.isRequired, - }; - static propTypes = { + identity: identityContextPropShape, intl: PropTypes.object.isRequired, onOpenSettings: PropTypes.func, }; @@ -114,7 +111,7 @@ class NavigationPanel extends Component { render () { const { intl, onOpenSettings } = this.props; - const { signedIn, disabledAccountId } = this.context.identity; + const { signedIn, disabledAccountId } = this.props.identity; return (
@@ -171,4 +168,4 @@ class NavigationPanel extends Component { } -export default injectIntl(NavigationPanel); +export default injectIntl(withIdentity(NavigationPanel)); diff --git a/app/javascript/flavours/glitch/features/ui/components/sign_in_banner.jsx b/app/javascript/flavours/glitch/features/ui/components/sign_in_banner.jsx index 5db3cb492b..bda9aed025 100644 --- a/app/javascript/flavours/glitch/features/ui/components/sign_in_banner.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/sign_in_banner.jsx @@ -22,7 +22,8 @@ const SignInBanner = () => { if (sso_redirect) { return (
-

+

+

); @@ -44,7 +45,8 @@ const SignInBanner = () => { return (
-

+

+

{signupButton}
diff --git a/app/javascript/flavours/glitch/features/ui/index.jsx b/app/javascript/flavours/glitch/features/ui/index.jsx index a71809c6ba..672e1869dd 100644 --- a/app/javascript/flavours/glitch/features/ui/index.jsx +++ b/app/javascript/flavours/glitch/features/ui/index.jsx @@ -15,8 +15,10 @@ import { HotKeys } from 'react-hotkeys'; import { changeLayout } from 'flavours/glitch/actions/app'; import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'flavours/glitch/actions/markers'; import { INTRODUCTION_VERSION } from 'flavours/glitch/actions/onboarding'; +import { HoverCardController } from 'flavours/glitch/components/hover_card_controller'; import { Permalink } from 'flavours/glitch/components/permalink'; import { PictureInPicture } from 'flavours/glitch/features/picture_in_picture'; +import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context'; import { layoutFromWindow } from 'flavours/glitch/is_mobile'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; @@ -25,7 +27,7 @@ import { clearHeight } from '../../actions/height_cache'; import { expandNotifications, notificationsSetVisibility } from '../../actions/notifications'; import { fetchServer, fetchServerTranslationLanguages } from '../../actions/server'; import { expandHomeTimeline } from '../../actions/timelines'; -import initialState, { me, owner, singleUserMode, trendsEnabled, trendsAsLanding } from '../../initial_state'; +import initialState, { me, owner, singleUserMode, trendsEnabled, trendsAsLanding, disableHoverCards } from '../../initial_state'; import BundleColumnError from './components/bundle_column_error'; import Header from './components/header'; @@ -56,6 +58,7 @@ import { FavouritedStatuses, BookmarkedStatuses, FollowedTags, + LinkTimeline, ListTimeline, Blocks, DomainBlocks, @@ -129,12 +132,8 @@ const keyMap = { }; class SwitchingColumnsArea extends PureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - static propTypes = { + identity: identityContextPropShape, children: PropTypes.node, location: PropTypes.object, singleColumn: PropTypes.bool, @@ -169,7 +168,7 @@ class SwitchingColumnsArea extends PureComponent { render () { const { children, singleColumn } = this.props; - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; const pathName = this.props.location.pathname; let redirect; @@ -212,6 +211,7 @@ class SwitchingColumnsArea extends PureComponent { + @@ -261,12 +261,8 @@ class SwitchingColumnsArea extends PureComponent { } class UI extends PureComponent { - - static contextTypes = { - identity: PropTypes.object.isRequired, - }; - static propTypes = { + identity: identityContextPropShape, dispatch: PropTypes.func.isRequired, children: PropTypes.node, isWide: PropTypes.bool, @@ -322,7 +318,7 @@ class UI extends PureComponent { this.dragTargets.push(e.target); } - if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files') && this.props.canUploadMore && this.context.identity.signedIn) { + if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files') && this.props.canUploadMore && this.props.identity.signedIn) { this.setState({ draggingOver: true }); } }; @@ -350,7 +346,7 @@ class UI extends PureComponent { this.setState({ draggingOver: false }); this.dragTargets = []; - if (e.dataTransfer && e.dataTransfer.files.length >= 1 && this.props.canUploadMore && this.context.identity.signedIn) { + if (e.dataTransfer && e.dataTransfer.files.length >= 1 && this.props.canUploadMore && this.props.identity.signedIn) { this.props.dispatch(uploadCompose(e.dataTransfer.files)); } }; @@ -402,7 +398,7 @@ class UI extends PureComponent { }; componentDidMount () { - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; window.addEventListener('beforeunload', this.handleBeforeUnload, false); window.addEventListener('resize', this.handleResize, { passive: true }); @@ -648,12 +644,13 @@ class UI extends PureComponent {
- + {children} {layout !== 'mobile' && } + {!disableHoverCards && } @@ -664,4 +661,4 @@ class UI extends PureComponent { } -export default connect(mapStateToProps)(injectIntl(withRouter(UI))); +export default connect(mapStateToProps)(injectIntl(withRouter(withIdentity(UI)))); diff --git a/app/javascript/flavours/glitch/features/ui/util/async-components.js b/app/javascript/flavours/glitch/features/ui/util/async-components.js index 6a140e3fd7..a312cefff7 100644 --- a/app/javascript/flavours/glitch/features/ui/util/async-components.js +++ b/app/javascript/flavours/glitch/features/ui/util/async-components.js @@ -213,3 +213,7 @@ export function NotificationRequests () { export function NotificationRequest () { return import(/*webpackChunkName: "features/glitch/notifications/request" */'../../notifications/request'); } + +export function LinkTimeline () { + return import(/*webpackChunkName: "features/glitch/link_timeline" */'../../link_timeline'); +} diff --git a/app/javascript/flavours/glitch/features/ui/util/identity_consumer.jsx b/app/javascript/flavours/glitch/features/ui/util/identity_consumer.jsx deleted file mode 100644 index bf7c7e70f9..0000000000 --- a/app/javascript/flavours/glitch/features/ui/util/identity_consumer.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; - -export class IdentityConsumer extends PureComponent { - static contextTypes = { - identity: PropTypes.object - }; - - static propTypes = { - children: PropTypes.func.isRequired - }; - - render() { - return this.props.children(this.context.identity); - } -} diff --git a/app/javascript/flavours/glitch/hooks/useLinks.ts b/app/javascript/flavours/glitch/hooks/useLinks.ts new file mode 100644 index 0000000000..bb988665fd --- /dev/null +++ b/app/javascript/flavours/glitch/hooks/useLinks.ts @@ -0,0 +1,61 @@ +import { useCallback } from 'react'; + +import { useHistory } from 'react-router-dom'; + +import { openURL } from 'flavours/glitch/actions/search'; +import { useAppDispatch } from 'flavours/glitch/store'; + +const isMentionClick = (element: HTMLAnchorElement) => + element.classList.contains('mention'); + +const isHashtagClick = (element: HTMLAnchorElement) => + element.textContent?.[0] === '#' || + element.previousSibling?.textContent?.endsWith('#'); + +export const useLinks = () => { + const history = useHistory(); + const dispatch = useAppDispatch(); + + const handleHashtagClick = useCallback( + (element: HTMLAnchorElement) => { + const { textContent } = element; + + if (!textContent) return; + + history.push(`/tags/${textContent.replace(/^#/, '')}`); + }, + [history], + ); + + const handleMentionClick = useCallback( + (element: HTMLAnchorElement) => { + dispatch( + openURL(element.href, history, () => { + window.location.href = element.href; + }), + ); + }, + [dispatch, history], + ); + + const handleClick = useCallback( + (e: React.MouseEvent) => { + const target = (e.target as HTMLElement).closest('a'); + + if (!target || e.button !== 0 || e.ctrlKey || e.metaKey) { + return; + } + + if (isMentionClick(target)) { + e.preventDefault(); + handleMentionClick(target); + } else if (isHashtagClick(target)) { + e.preventDefault(); + handleHashtagClick(target); + } + }, + [handleMentionClick, handleHashtagClick], + ); + + return handleClick; +}; diff --git a/app/javascript/flavours/glitch/hooks/useTimeout.ts b/app/javascript/flavours/glitch/hooks/useTimeout.ts new file mode 100644 index 0000000000..bb1e8848dd --- /dev/null +++ b/app/javascript/flavours/glitch/hooks/useTimeout.ts @@ -0,0 +1,44 @@ +import { useRef, useCallback, useEffect } from 'react'; + +export const useTimeout = () => { + const timeoutRef = useRef>(); + const callbackRef = useRef<() => void>(); + + const set = useCallback((callback: () => void, delay: number) => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + + callbackRef.current = callback; + timeoutRef.current = setTimeout(callback, delay); + }, []); + + const delay = useCallback((delay: number) => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + + if (!callbackRef.current) { + return; + } + + timeoutRef.current = setTimeout(callbackRef.current, delay); + }, []); + + const cancel = useCallback(() => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + timeoutRef.current = undefined; + callbackRef.current = undefined; + } + }, []); + + useEffect( + () => () => { + cancel(); + }, + [cancel], + ); + + return [set, cancel, delay] as const; +}; diff --git a/app/javascript/flavours/glitch/identity_context.tsx b/app/javascript/flavours/glitch/identity_context.tsx new file mode 100644 index 0000000000..42d1b6c475 --- /dev/null +++ b/app/javascript/flavours/glitch/identity_context.tsx @@ -0,0 +1,70 @@ +import PropTypes from 'prop-types'; +import { createContext, useContext } from 'react'; + +import hoistStatics from 'hoist-non-react-statics'; + +import type { InitialState } from 'flavours/glitch/initial_state'; + +export interface IdentityContextType { + signedIn: boolean; + accountId: string | undefined; + disabledAccountId: string | undefined; + permissions: number; +} + +export const identityContextPropShape = PropTypes.shape({ + signedIn: PropTypes.bool.isRequired, + accountId: PropTypes.string, + disabledAccountId: PropTypes.string, +}).isRequired; + +export const createIdentityContext = (state: InitialState) => ({ + signedIn: !!state.meta.me, + accountId: state.meta.me, + disabledAccountId: state.meta.disabled_account_id, + permissions: state.role?.permissions ?? 0, +}); + +export const IdentityContext = createContext({ + signedIn: false, + permissions: 0, + accountId: undefined, + disabledAccountId: undefined, +}); + +export const useIdentity = () => useContext(IdentityContext); + +export interface IdentityProps { + ref?: unknown; + wrappedComponentRef?: unknown; +} + +/* Injects an `identity` props into the wrapped component to be able to use the new context in class components */ +export function withIdentity< + ComponentType extends React.ComponentType, +>(Component: ComponentType) { + const displayName = `withIdentity(${Component.displayName ?? Component.name})`; + const C = (props: React.ComponentProps) => { + const { wrappedComponentRef, ...remainingProps } = props; + + return ( + + {(context) => { + return ( + // @ts-expect-error - Dynamic covariant generic components are tough to type. + + ); + }} + + ); + }; + + C.displayName = displayName; + C.WrappedComponent = Component; + + return hoistStatics(C, Component); +} diff --git a/app/javascript/flavours/glitch/initial_state.js b/app/javascript/flavours/glitch/initial_state.js index fad03f636f..45a97033ce 100644 --- a/app/javascript/flavours/glitch/initial_state.js +++ b/app/javascript/flavours/glitch/initial_state.js @@ -17,6 +17,7 @@ * @property {boolean} crop_images * @property {boolean=} delete_modal * @property {boolean=} disable_swiping + * @property {boolean=} disable_hover_cards * @property {string=} disabled_account_id * @property {string} display_media * @property {string} domain @@ -52,12 +53,22 @@ * @property {string} default_content_type */ +/** + * @typedef Role + * @property {string} id + * @property {string} name + * @property {string} permissions + * @property {string} color + * @property {boolean} highlighted + */ + /** * @typedef InitialState * @property {Record} accounts * @property {InitialStateLanguage[]} languages * @property {boolean=} critical_updates_pending * @property {InitialStateMeta} meta + * @property {Role?} role * @property {object} local_settings * @property {number} max_feed_hashtags * @property {number} poll_limits @@ -100,6 +111,7 @@ export const boostModal = getMeta('boost_modal'); export const cropImages = getMeta('crop_images'); export const deleteModal = getMeta('delete_modal'); export const disableSwiping = getMeta('disable_swiping'); +export const disableHoverCards = getMeta('disable_hover_cards'); export const disabledAccountId = getMeta('disabled_account_id'); export const displayMedia = getMeta('display_media'); export const domain = getMeta('domain'); @@ -139,6 +151,13 @@ export const pollLimits = (initialState && initialState.poll_limits); export const defaultContentType = getMeta('default_content_type'); export const useSystemEmojiFont = getMeta('system_emoji_font'); +/** + * @returns {string | undefined} + */ +export function getAccessToken() { + return getMeta('access_token'); +} + // Standalone-specific settings export const isSingleColumn = !forceSingleColumn && (initialState?.local_settings?.single_column ?? !hasMultiColumnPath); diff --git a/app/javascript/flavours/glitch/locales/de.json b/app/javascript/flavours/glitch/locales/de.json index 6b41b611be..41ca51b7a6 100644 --- a/app/javascript/flavours/glitch/locales/de.json +++ b/app/javascript/flavours/glitch/locales/de.json @@ -157,6 +157,5 @@ "status.in_reply_to": "Dieser Toot ist eine Antwort", "status.is_poll": "Dieser Toot ist eine Umfrage", "status.local_only": "Nur auf deiner Instanz sichtbar", - "status.uncollapse": "Ausklappen", - "suggestions.dismiss": "Vorschlag ablehnen" + "status.uncollapse": "Ausklappen" } diff --git a/app/javascript/flavours/glitch/locales/es-AR.json b/app/javascript/flavours/glitch/locales/es-AR.json index 95dc7c85a6..706109d6e8 100644 --- a/app/javascript/flavours/glitch/locales/es-AR.json +++ b/app/javascript/flavours/glitch/locales/es-AR.json @@ -155,6 +155,5 @@ "status.in_reply_to": "Esta publicaciรณn es una respuesta", "status.is_poll": "Esta publicaciรณn es una encuesta", "status.local_only": "Sรณlo visible para tu instancia", - "status.uncollapse": "Descolapsar", - "suggestions.dismiss": "Descartar sugerencia" + "status.uncollapse": "Descolapsar" } diff --git a/app/javascript/flavours/glitch/locales/fr-CA.json b/app/javascript/flavours/glitch/locales/fr-CA.json index 30ca374d20..991d206a99 100644 --- a/app/javascript/flavours/glitch/locales/fr-CA.json +++ b/app/javascript/flavours/glitch/locales/fr-CA.json @@ -14,9 +14,15 @@ "column_subheading.lists": "Listes", "column_subheading.navigation": "Navigation", "community.column_settings.allow_local_only": "Afficher seulement les posts locaux", + "compose.attach.doodle": "Dessinez quelque chose", + "compose.change_federation": "Changer les paramรจtres de fรฉdรฉration", + "compose.content-type.change": "Changer les options de mise en forme avancรฉe", "compose.content-type.html": "HTML", + "compose.content-type.html_meta": "Formatez vos messages en HTML", "compose.content-type.markdown": "Markdown", + "compose.content-type.markdown_meta": "Formatez vos messages en Markdown", "compose.content-type.plain": "Text brut", + "compose.content-type.plain_meta": "ร‰crire sans formatage avancรฉ", "compose.disable_threaded_mode": "Dรฉsactiver le mode thread", "compose.enable_threaded_mode": "Activer le mode thread", "confirmation_modal.do_not_ask_again": "Ne plus demander confirmation", @@ -32,6 +38,10 @@ "direct.group_by_conversations": "Grouper par conversation", "endorsed_accounts_editor.endorsed_accounts": "Comptes mis en avant", "favourite_modal.combo": "Vous pouvez appuyer sur {combo} pour passer ceci la prochaine fois", + "federation.federated.long": "Permettre ร  ce post dโ€™atteindre d'autres serveurs", + "federation.federated.short": "Fรฉdรฉrรฉ", + "federation.local_only.long": "Empรชcher ce post dโ€™atteindre d'autres serveurs", + "federation.local_only.short": "Local uniquement", "firehose.column_settings.allow_local_only": "Afficher les messages locaux dans \"Tous\"", "home.column_settings.advanced": "Avancรฉ", "home.column_settings.filter_regex": "Filtrer par expression rรฉguliรจre", @@ -114,6 +124,7 @@ "settings.shared_settings_link": "prรฉfรฉrences de l'utilisateur", "settings.show_action_bar": "Afficher les boutons d'action dans les posts repliรฉs", "settings.show_content_type_choice": "Afficher le choix du type de contenu lors de la crรฉation des posts", + "settings.show_published_toast": "Afficher une notification quand un post est envoyรฉ/sauvegardรฉ", "settings.show_reply_counter": "Afficher une estimation du nombre de rรฉponses", "settings.side_arm": "Bouton secondaire de publication :", "settings.side_arm.none": "Aucun", diff --git a/app/javascript/flavours/glitch/locales/fr.json b/app/javascript/flavours/glitch/locales/fr.json index 29a587bccb..924a50e928 100644 --- a/app/javascript/flavours/glitch/locales/fr.json +++ b/app/javascript/flavours/glitch/locales/fr.json @@ -14,9 +14,15 @@ "column_subheading.lists": "Listes", "column_subheading.navigation": "Navigation", "community.column_settings.allow_local_only": "Afficher seulement les posts locaux", + "compose.attach.doodle": "Dessinez quelque chose", + "compose.change_federation": "Changer les paramรจtres de fรฉdรฉration", + "compose.content-type.change": "Changer les options de mise en forme avancรฉe", "compose.content-type.html": "HTML", + "compose.content-type.html_meta": "Formatez vos messages en HTML", "compose.content-type.markdown": "Markdown", + "compose.content-type.markdown_meta": "Formatez vos messages en Markdown", "compose.content-type.plain": "Text brut", + "compose.content-type.plain_meta": "ร‰crire sans formatage avancรฉ", "compose.disable_threaded_mode": "Dรฉsactiver le mode thread", "compose.enable_threaded_mode": "Activer le mode thread", "confirmation_modal.do_not_ask_again": "Ne plus demander confirmation", @@ -32,6 +38,10 @@ "direct.group_by_conversations": "Grouper par conversation", "endorsed_accounts_editor.endorsed_accounts": "Comptes mis en avant", "favourite_modal.combo": "Vous pouvez appuyer sur {combo} pour passer ceci la prochaine fois", + "federation.federated.long": "Permettre ร  ce post dโ€™atteindre d'autres serveurs", + "federation.federated.short": "Fรฉdรฉrรฉ", + "federation.local_only.long": "Empรชcher ce post dโ€™atteindre d'autres serveurs", + "federation.local_only.short": "Local uniquement", "firehose.column_settings.allow_local_only": "Afficher les messages locaux dans \"Tous\"", "home.column_settings.advanced": "Avancรฉ", "home.column_settings.filter_regex": "Filtrer par expression rรฉguliรจre", @@ -67,9 +77,9 @@ "settings.close": "Fermer", "settings.collapsed_statuses": "Posts repliรฉs", "settings.compose_box_opts": "Zone de rรฉdaction", - "settings.confirm_before_clearing_draft": "Afficher une fenรชtre de confirmation avant d'รฉcraser le message en cours de rรฉdaction", - "settings.confirm_boost_missing_media_description": "Afficher une fenรชtre de confirmation avant de partager des posts manquant de description des mรฉdias", - "settings.confirm_missing_media_description": "Afficher une fenรชtre de confirmation avant de publier des posts manquant de description de mรฉdia", + "settings.confirm_before_clearing_draft": "Demander confirmation avant dโ€™effacer le message en cours de rรฉdaction", + "settings.confirm_boost_missing_media_description": "Demander confirmation avant de partager des posts sans description des mรฉdias", + "settings.confirm_missing_media_description": "Demander confirmation avant de publier des posts sans description des mรฉdias", "settings.content_warnings": "Content warnings", "settings.content_warnings.regexp": "Expression rationnelle", "settings.content_warnings_filter": "Avertissement de contenu ร  ne pas automatiquement dรฉplier :", @@ -116,6 +126,7 @@ "settings.shared_settings_link": "prรฉfรฉrences de l'utilisateur", "settings.show_action_bar": "Afficher les boutons d'action dans les posts repliรฉs", "settings.show_content_type_choice": "Afficher le choix du type de contenu lors de la crรฉation des posts", + "settings.show_published_toast": "Afficher une notification quand un post est envoyรฉ/sauvegardรฉ", "settings.show_reply_counter": "Afficher une estimation du nombre de rรฉponses", "settings.side_arm": "Bouton secondaire de publication :", "settings.side_arm.none": "Aucun", diff --git a/app/javascript/flavours/glitch/locales/ko.json b/app/javascript/flavours/glitch/locales/ko.json index b1da79d102..18e61dc034 100644 --- a/app/javascript/flavours/glitch/locales/ko.json +++ b/app/javascript/flavours/glitch/locales/ko.json @@ -25,6 +25,9 @@ "compose.content-type.plain_meta": "๊ณ ๊ธ‰ ์–‘์‹ ์—†์ด ์ž‘์„ฑ", "compose.disable_threaded_mode": "๊ธ€ํƒ€๋ž˜ ๋ชจ๋“œ ๋น„ํ™œ์„ฑํ™”", "compose.enable_threaded_mode": "๊ธ€ํƒ€๋ž˜ ๋ชจ๋“œ ํ™œ์„ฑํ™”", + "compose_form.sensitive.hide": "{count, plural, other {๋ฏธ๋””์–ด๋ฅผ ๋ฏผ๊ฐํ•จ์œผ๋กœ ํ‘œ์‹œ}}", + "compose_form.sensitive.marked": "{count, plural, other {๋ฏธ๋””์–ด๊ฐ€ ๋ฏผ๊ฐํ•จ์œผ๋กœ ํ‘œ์‹œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค}}", + "compose_form.sensitive.unmarked": "{count, plural, other {๋ฏธ๋””์–ด๊ฐ€ ๋ฏผ๊ฐํ•จ์œผ๋กœ ํ‘œ์‹œ๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค}}", "confirmation_modal.do_not_ask_again": "๋‹ค์Œ๋ถ€ํ„ฐ ํ™•์ธ์ฐฝ์„ ๋„์šฐ์ง€ ์•Š๊ธฐ", "confirmations.deprecated_settings.confirm": "๋งˆ์Šคํ† ๋ˆ ์„ค์ • ์‚ฌ์šฉ", "confirmations.deprecated_settings.message": "์‚ฌ์šฉํ•˜๋˜ ๋ช‡๋ช‡ ๊ธฐ๊ธฐ๋ณ„ ๊ธ€๋ฆฌ์น˜ {app_settings}์€ ๋งˆ์Šคํ† ๋ˆ {preferences}์œผ๋กœ ๋Œ€์ฒด๋˜์—ˆ์Šต๋‹ˆ๋‹ค:", @@ -61,6 +64,7 @@ "notification_purge.btn_invert": "์„ ํƒ๋ฐ˜์ „", "notification_purge.btn_none": "์ „์ฒด์„ ํƒํ•ด์ œ", "notification_purge.start": "์•Œ๋ฆผ ์‚ญ์ œ๋ชจ๋“œ๋กœ ๋“ค์–ด๊ฐ€๊ธฐ", + "notifications.column_settings.filter_bar.show_bar": "ํ•„ํ„ฐ ๋ง‰๋Œ€ ํ‘œ์‹œ", "notifications.marked_clear": "์„ ํƒ๋œ ์•Œ๋ฆผ ๋ชจ๋‘ ์‚ญ์ œ", "notifications.marked_clear_confirmation": "์ •๋ง๋กœ ์„ ํƒ๋œ ์•Œ๋ฆผ๋“ค์„ ์˜๊ตฌ์ ์œผ๋กœ ์‚ญ์ œํ• ๊นŒ์š”?", "settings.always_show_spoilers_field": "์—ด๋žŒ์ฃผ์˜ ํ•ญ๋ชฉ์„ ์–ธ์ œ๋‚˜ ํ™œ์„ฑํ™”", @@ -124,6 +128,7 @@ "settings.shared_settings_link": "์‚ฌ์šฉ์ž ์„ค์ •", "settings.show_action_bar": "์ ‘ํžŒ ๊ธ€์— ์•ก์…˜ ๋ฒ„ํŠผ๋“ค ๋ณด์ด๊ธฐ", "settings.show_content_type_choice": "๊ธ€์„ ์ž‘์„ฑํ•  ๋•Œ ์ฝ˜ํ…ํŠธ ํƒ€์ž…์„ ๊ณ ๋ฅผ ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค", + "settings.show_published_toast": "๊ฒŒ์‹œ๋ฌผ์„ ๊ฒŒ์‹œ/์ €์žฅํ•  ๋•Œ ํ† ์ŠคํŠธ ํ‘œ์‹œ", "settings.show_reply_counter": "๋Œ€๋žต์ ์ธ ๋‹ต๊ธ€ ๊ฐœ์ˆ˜๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค", "settings.side_arm": "๋ณด์กฐ ์ž‘์„ฑ ๋ฒ„ํŠผ:", "settings.side_arm.none": "์—†์Œ", diff --git a/app/javascript/flavours/glitch/locales/pt-PT.json b/app/javascript/flavours/glitch/locales/pt-PT.json index fd0b010159..634f3db12a 100644 --- a/app/javascript/flavours/glitch/locales/pt-PT.json +++ b/app/javascript/flavours/glitch/locales/pt-PT.json @@ -7,6 +7,32 @@ "boost_modal.missing_description": "Este post contรฉm alguns media sem descriรงรฃo", "column.favourited_by": "Adicionado aos favoritos de", "column.heading": "Diversos", + "moved_to_warning": "Esta conta mudou-se para {moved_to_link} e, portanto, pode nรฃo aceitar novos seguidores.", + "navigation_bar.featured_users": "Utilizadores em destaque", + "notification.markForDeletion": "Marcada para eliminaรงรฃo", + "notification_purge.btn_all": "Seleccionar tudo", + "notification_purge.btn_apply": "Limpar Selecionadas", + "notification_purge.btn_none": "Desselecionar tudo", + "notification_purge.start": "Entrar em modo de limpeza de notificaรงรตes", + "notifications.column_settings.filter_bar.show_bar": "Mostrar barra de filtros", + "notifications.marked_clear": "Limpar as notificaรงรตes selecionadas", + "notifications.marked_clear_confirmation": "Tem a certeza que deseja limpar todas as notificaรงรตes selecionadas permanentemente?", + "settings.always_show_spoilers_field": "Mostrar sempre o campo Aviso de Conteรบdo", + "settings.auto_collapse": "Colapso automรกtico", + "settings.auto_collapse_height": "Altura (em pixels) a partir do qual um toot รฉ considerado longo", + "settings.auto_collapse_media": "Toots com conteรบdo multimรฉdia", "settings.content_warnings": "Content warnings", - "settings.preferences": "Preferences" + "settings.layout_opts": "Disposiรงรฃo do conteรบdo", + "settings.media": "Conteรบdo multimรฉdia", + "settings.media_fullwidth": "Prรฉ-visualizaรงรฃo a todo o comprimento", + "settings.media_reveal_behind_cw": "Mostrar sempre conteรบdos com aviso", + "settings.notifications.favicon_badge": "Mostrar nรบmero de notificaรงรตes no distintivo da pรกgina", + "settings.notifications.tab_badge": "Mostrar nรบmero de notificaรงรตes nรฃo lidas", + "settings.notifications.tab_badge.hint": "Mostra um distintivo com o nรบmero de notificaรงรตes nรฃo lidas quando a coluna de notificaรงรตes nรฃo estรก aberta", + "settings.notifications_opts": "Opรงรตes de notificaรงรฃo", + "settings.pop_in_left": "Esquerda", + "settings.pop_in_right": "Direita", + "settings.preferences": "Preferences", + "settings.prepend_cw_re": "Iniciar respostas a toots com aviso de conteรบdo com \"re:\"", + "settings.preselect_on_reply": "Prรฉ-seleccionar nomes de utilizador nas respostas" } diff --git a/app/javascript/flavours/glitch/locales/zh-CN.json b/app/javascript/flavours/glitch/locales/zh-CN.json index 26f142807a..e9999136fb 100644 --- a/app/javascript/flavours/glitch/locales/zh-CN.json +++ b/app/javascript/flavours/glitch/locales/zh-CN.json @@ -155,6 +155,5 @@ "status.in_reply_to": "ๆญคๅ˜Ÿๆ–‡ๆ˜ฏๅ›žๅค", "status.is_poll": "ๆญคๅ˜Ÿๆ–‡ๆ˜ฏๆŠ•็ฅจ", "status.local_only": "ๆญคๅ˜Ÿๆ–‡ไป…ๆœฌ็ซ™ๅฏ่ง", - "status.uncollapse": "ๅฑ•ๅผ€", - "suggestions.dismiss": "ๅ…ณ้—ญๅปบ่ฎฎ" + "status.uncollapse": "ๅฑ•ๅผ€" } diff --git a/app/javascript/flavours/glitch/locales/zh-TW.json b/app/javascript/flavours/glitch/locales/zh-TW.json index 2fcb217434..aaf0065150 100644 --- a/app/javascript/flavours/glitch/locales/zh-TW.json +++ b/app/javascript/flavours/glitch/locales/zh-TW.json @@ -151,6 +151,5 @@ "status.in_reply_to": "่ฒผๆ–‡ๆœ‰ๅ›ž่ฆ†", "status.is_poll": "่ฒผๆ–‡ๆœ‰ๆŠ•็ฅจ", "status.local_only": "ๅชๅœจๆญคๅฏฆไพ‹ๅฏ่ฆ‹", - "status.uncollapse": "ๅฑ•้–‹", - "suggestions.dismiss": "้—œ้–‰ๅปบ่ญฐ" + "status.uncollapse": "ๅฑ•้–‹" } diff --git a/app/javascript/flavours/glitch/models/notification_policy.ts b/app/javascript/flavours/glitch/models/notification_policy.ts new file mode 100644 index 0000000000..7d97559413 --- /dev/null +++ b/app/javascript/flavours/glitch/models/notification_policy.ts @@ -0,0 +1,3 @@ +import type { NotificationPolicyJSON } from 'flavours/glitch/api_types/notification_policies'; + +export type NotificationPolicy = NotificationPolicyJSON; // No changes from the API type diff --git a/app/javascript/flavours/glitch/models/status.ts b/app/javascript/flavours/glitch/models/status.ts index d9566daf39..bf1784bc61 100644 --- a/app/javascript/flavours/glitch/models/status.ts +++ b/app/javascript/flavours/glitch/models/status.ts @@ -1,4 +1,12 @@ +import type { RecordOf } from 'immutable'; + +import type { ApiPreviewCardJSON } from 'flavours/glitch/api_types/statuses'; + export type { StatusVisibility } from 'flavours/glitch/api_types/statuses'; // Temporary until we type it correctly export type Status = Immutable.Map; + +type CardShape = Required; + +export type Card = RecordOf; diff --git a/app/javascript/flavours/glitch/packs/sign_up.js b/app/javascript/flavours/glitch/packs/sign_up.js deleted file mode 100644 index 4e0af41a2e..0000000000 --- a/app/javascript/flavours/glitch/packs/sign_up.js +++ /dev/null @@ -1,42 +0,0 @@ -import 'packs/public-path'; -import axios from 'axios'; - -import ready from 'flavours/glitch/ready'; - -ready(() => { - setInterval(() => { - axios.get('/api/v1/emails/check_confirmation').then((response) => { - if (response.data) { - window.location = '/start'; - } - }).catch(error => { - console.error(error); - }); - }, 5000); - - document.querySelectorAll('.timer-button').forEach(button => { - let counter = 30; - - const container = document.createElement('span'); - - const updateCounter = () => { - container.innerText = ` (${counter})`; - }; - - updateCounter(); - - const countdown = setInterval(() => { - counter--; - - if (counter === 0) { - button.disabled = false; - button.removeChild(container); - clearInterval(countdown); - } else { - updateCounter(); - } - }, 1000); - - button.appendChild(container); - }); -}); diff --git a/app/javascript/flavours/glitch/packs/two_factor_authentication.js b/app/javascript/flavours/glitch/packs/two_factor_authentication.js deleted file mode 100644 index 8b606fcc7a..0000000000 --- a/app/javascript/flavours/glitch/packs/two_factor_authentication.js +++ /dev/null @@ -1,119 +0,0 @@ -import * as WebAuthnJSON from '@github/webauthn-json'; -import axios from 'axios'; - -import ready from 'flavours/glitch/ready'; -import 'regenerator-runtime/runtime'; - -function getCSRFToken() { - var CSRFSelector = document.querySelector('meta[name="csrf-token"]'); - if (CSRFSelector) { - return CSRFSelector.getAttribute('content'); - } else { - return null; - } -} - -function hideFlashMessages() { - Array.from(document.getElementsByClassName('flash-message')).forEach(function(flashMessage) { - flashMessage.classList.add('hidden'); - }); -} - -function callback(url, body) { - axios.post(url, JSON.stringify(body), { - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'X-CSRF-Token': getCSRFToken(), - }, - credentials: 'same-origin', - }).then(function(response) { - window.location.replace(response.data.redirect_path); - }).catch(function(error) { - if (error.response.status === 422) { - const errorMessage = document.getElementById('security-key-error-message'); - errorMessage.classList.remove('hidden'); - console.error(error.response.data.error); - } else { - console.error(error); - } - }); -} - -ready(() => { - if (!WebAuthnJSON.supported()) { - const unsupported_browser_message = document.getElementById('unsupported-browser-message'); - if (unsupported_browser_message) { - unsupported_browser_message.classList.remove('hidden'); - document.querySelector('.btn.js-webauthn').disabled = true; - } - } - - - const webAuthnCredentialRegistrationForm = document.getElementById('new_webauthn_credential'); - if (webAuthnCredentialRegistrationForm) { - webAuthnCredentialRegistrationForm.addEventListener('submit', (event) => { - event.preventDefault(); - - var nickname = event.target.querySelector('input[name="new_webauthn_credential[nickname]"]'); - if (nickname.value) { - axios.get('/settings/security_keys/options') - .then((response) => { - const credentialOptions = response.data; - - WebAuthnJSON.create({ 'publicKey': credentialOptions }).then((credential) => { - var params = { 'credential': credential, 'nickname': nickname.value }; - callback('/settings/security_keys', params); - }).catch((error) => { - const errorMessage = document.getElementById('security-key-error-message'); - errorMessage.classList.remove('hidden'); - console.error(error); - }); - }).catch((error) => { - console.error(error.response.data.error); - }); - } else { - nickname.focus(); - } - }); - } - - const webAuthnCredentialAuthenticationForm = document.getElementById('webauthn-form'); - if (webAuthnCredentialAuthenticationForm) { - webAuthnCredentialAuthenticationForm.addEventListener('submit', (event) => { - event.preventDefault(); - - axios.get('sessions/security_key_options') - .then((response) => { - const credentialOptions = response.data; - - WebAuthnJSON.get({ 'publicKey': credentialOptions }).then((credential) => { - var params = { 'user': { 'credential': credential } }; - callback('sign_in', params); - }).catch((error) => { - const errorMessage = document.getElementById('security-key-error-message'); - errorMessage.classList.remove('hidden'); - console.error(error); - }); - }).catch((error) => { - console.error(error.response.data.error); - }); - }); - - const otpAuthenticationForm = document.getElementById('otp-authentication-form'); - - const linkToOtp = document.getElementById('link-to-otp'); - linkToOtp.addEventListener('click', () => { - webAuthnCredentialAuthenticationForm.classList.add('hidden'); - otpAuthenticationForm.classList.remove('hidden'); - hideFlashMessages(); - }); - - const linkToWebAuthn = document.getElementById('link-to-webauthn'); - linkToWebAuthn.addEventListener('click', () => { - otpAuthenticationForm.classList.add('hidden'); - webAuthnCredentialAuthenticationForm.classList.remove('hidden'); - hideFlashMessages(); - }); - } -}); diff --git a/app/javascript/flavours/glitch/reducers/compose.js b/app/javascript/flavours/glitch/reducers/compose.js index 5d29d9bc3d..aa01ecb300 100644 --- a/app/javascript/flavours/glitch/reducers/compose.js +++ b/app/javascript/flavours/glitch/reducers/compose.js @@ -1,5 +1,7 @@ import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable'; +import { timelineDelete } from 'flavours/glitch/actions/timelines_typed'; + import { COMPOSE_MOUNT, COMPOSE_UNMOUNT, @@ -55,7 +57,6 @@ import { } from '../actions/compose'; import { REDRAFT } from '../actions/statuses'; import { STORE_HYDRATE } from '../actions/store'; -import { TIMELINE_DELETE } from '../actions/timelines'; import { me, defaultContentType } from '../initial_state'; import { recoverHashtags } from '../utils/hashtag'; import { unescapeHTML } from '../utils/html'; @@ -553,10 +554,10 @@ export default function compose(state = initialState, action) { return updateSuggestionTags(state, action.token); case COMPOSE_TAG_HISTORY_UPDATE: return state.set('tagHistory', fromJS(action.tags)); - case TIMELINE_DELETE: - if (action.id === state.get('in_reply_to')) { + case timelineDelete.type: + if (action.payload.statusId === state.get('in_reply_to')) { return state.set('in_reply_to', null); - } else if (action.id === state.get('id')) { + } else if (action.payload.statusId === state.get('id')) { return state.set('id', null); } else { return state; diff --git a/app/javascript/flavours/glitch/reducers/contexts.js b/app/javascript/flavours/glitch/reducers/contexts.js index f7d7419a4e..07fe8ffb8a 100644 --- a/app/javascript/flavours/glitch/reducers/contexts.js +++ b/app/javascript/flavours/glitch/reducers/contexts.js @@ -1,11 +1,13 @@ import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; +import { timelineDelete } from 'flavours/glitch/actions/timelines_typed'; + import { blockAccountSuccess, muteAccountSuccess, } from '../actions/accounts'; import { CONTEXT_FETCH_SUCCESS } from '../actions/statuses'; -import { TIMELINE_DELETE, TIMELINE_UPDATE } from '../actions/timelines'; +import { TIMELINE_UPDATE } from '../actions/timelines'; import { compareId } from '../compare_id'; const initialState = ImmutableMap({ @@ -97,8 +99,8 @@ export default function replies(state = initialState, action) { return filterContexts(state, action.payload.relationship, action.payload.statuses); case CONTEXT_FETCH_SUCCESS: return normalizeContext(state, action.id, action.ancestors, action.descendants); - case TIMELINE_DELETE: - return deleteFromContexts(state, [action.id]); + case timelineDelete.type: + return deleteFromContexts(state, [action.payload.statusId]); case TIMELINE_UPDATE: return updateContext(state, action.status); default: diff --git a/app/javascript/flavours/glitch/reducers/meta.js b/app/javascript/flavours/glitch/reducers/meta.js index 1fc669375c..d729924099 100644 --- a/app/javascript/flavours/glitch/reducers/meta.js +++ b/app/javascript/flavours/glitch/reducers/meta.js @@ -6,7 +6,6 @@ import { layoutFromWindow } from 'flavours/glitch/is_mobile'; const initialState = ImmutableMap({ streaming_api_base_url: null, - access_token: null, layout: layoutFromWindow(), permissions: '0', }); @@ -14,7 +13,8 @@ const initialState = ImmutableMap({ export default function meta(state = initialState, action) { switch(action.type) { case STORE_HYDRATE: - return state.merge(action.state.get('meta')).set('permissions', action.state.getIn(['role', 'permissions'])); + // we do not want `access_token` to be stored in the state + return state.merge(action.state.get('meta')).delete('access_token').set('permissions', action.state.getIn(['role', 'permissions'])); case changeLayout.type: return state.set('layout', action.payload.layout); default: diff --git a/app/javascript/flavours/glitch/reducers/modal.ts b/app/javascript/flavours/glitch/reducers/modal.ts index 368f26542c..c9ecdb7fdf 100644 --- a/app/javascript/flavours/glitch/reducers/modal.ts +++ b/app/javascript/flavours/glitch/reducers/modal.ts @@ -1,10 +1,11 @@ import type { Reducer } from '@reduxjs/toolkit'; import { Record as ImmutableRecord, Stack } from 'immutable'; +import { timelineDelete } from 'flavours/glitch/actions/timelines_typed'; + import { COMPOSE_UPLOAD_CHANGE_SUCCESS } from '../actions/compose'; import type { ModalType } from '../actions/modal'; import { openModal, closeModal } from '../actions/modal'; -import { TIMELINE_DELETE } from '../actions/timelines'; export type ModalProps = Record; interface Modal { @@ -72,10 +73,10 @@ export const modalReducer: Reducer = (state = initialState, action) => { // TODO: type those actions else if (action.type === COMPOSE_UPLOAD_CHANGE_SUCCESS) return popModal(state, { modalType: 'FOCAL_POINT', ignoreFocus: false }); - else if (action.type === TIMELINE_DELETE) + else if (timelineDelete.match(action)) return state.update('stack', (stack) => stack.filterNot( - (modal) => modal.get('modalProps').statusId === action.id, + (modal) => modal.get('modalProps').statusId === action.payload.statusId, ), ); else return state; diff --git a/app/javascript/flavours/glitch/reducers/notification_policy.js b/app/javascript/flavours/glitch/reducers/notification_policy.js deleted file mode 100644 index 579f2afdb2..0000000000 --- a/app/javascript/flavours/glitch/reducers/notification_policy.js +++ /dev/null @@ -1,12 +0,0 @@ -import { fromJS } from 'immutable'; - -import { NOTIFICATION_POLICY_FETCH_SUCCESS } from 'flavours/glitch/actions/notifications'; - -export const notificationPolicyReducer = (state = null, action) => { - switch(action.type) { - case NOTIFICATION_POLICY_FETCH_SUCCESS: - return fromJS(action.policy); - default: - return state; - } -}; diff --git a/app/javascript/flavours/glitch/reducers/notification_policy.ts b/app/javascript/flavours/glitch/reducers/notification_policy.ts new file mode 100644 index 0000000000..2d5450ce44 --- /dev/null +++ b/app/javascript/flavours/glitch/reducers/notification_policy.ts @@ -0,0 +1,18 @@ +import { createReducer, isAnyOf } from '@reduxjs/toolkit'; + +import { + fetchNotificationPolicy, + updateNotificationsPolicy, +} from 'flavours/glitch/actions/notification_policies'; +import type { NotificationPolicy } from 'flavours/glitch/models/notification_policy'; + +export const notificationPolicyReducer = + createReducer(null, (builder) => { + builder.addMatcher( + isAnyOf( + fetchNotificationPolicy.fulfilled, + updateNotificationsPolicy.fulfilled, + ), + (_state, action) => action.payload, + ); + }); diff --git a/app/javascript/flavours/glitch/reducers/notifications.js b/app/javascript/flavours/glitch/reducers/notifications.js index 6edf84b67a..226f2affe1 100644 --- a/app/javascript/flavours/glitch/reducers/notifications.js +++ b/app/javascript/flavours/glitch/reducers/notifications.js @@ -1,6 +1,7 @@ import { fromJS, Map as ImmutableMap, List as ImmutableList } from 'immutable'; import { blockDomainSuccess } from 'flavours/glitch/actions/domain_blocks'; +import { timelineDelete } from 'flavours/glitch/actions/timelines_typed'; import { authorizeFollowRequestSuccess, @@ -33,7 +34,7 @@ import { NOTIFICATIONS_SET_BROWSER_SUPPORT, NOTIFICATIONS_SET_BROWSER_PERMISSION, } from '../actions/notifications'; -import { TIMELINE_DELETE, TIMELINE_DISCONNECT } from '../actions/timelines'; +import { disconnectTimeline } from '../actions/timelines'; import { compareId } from '../compare_id'; const initialState = ImmutableMap({ @@ -62,6 +63,7 @@ export const notificationToMap = (notification, markForDelete = false) => Immuta status: notification.status ? notification.status.id : null, report: notification.report ? fromJS(notification.report) : null, event: notification.event ? fromJS(notification.event) : null, + moderation_warning: notification.moderation_warning ? fromJS(notification.moderation_warning) : null, }); const normalizeNotification = (state, notification, usePendingItems) => { @@ -332,11 +334,11 @@ export default function notifications(state = initialState, action) { return filterNotifications(state, [action.payload.id], 'follow_request'); case NOTIFICATIONS_CLEAR: return state.set('items', ImmutableList()).set('pendingItems', ImmutableList()).set('hasMore', false); - case TIMELINE_DELETE: - return deleteByStatus(state, action.id); - case TIMELINE_DISCONNECT: - return action.timeline === 'home' ? - state.update(action.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(null) : items) : + case timelineDelete.type: + return deleteByStatus(state, action.payload.statusId); + case disconnectTimeline.type: + return action.payload.timeline === 'home' ? + state.update(action.payload.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(null) : items) : state; case NOTIFICATIONS_SET_BROWSER_SUPPORT: return state.set('browserSupport', action.value); diff --git a/app/javascript/flavours/glitch/reducers/picture_in_picture.ts b/app/javascript/flavours/glitch/reducers/picture_in_picture.ts index 35eb10cf57..6ad31d42bf 100644 --- a/app/javascript/flavours/glitch/reducers/picture_in_picture.ts +++ b/app/javascript/flavours/glitch/reducers/picture_in_picture.ts @@ -4,8 +4,7 @@ import { deployPictureInPictureAction, removePictureInPicture, } from 'flavours/glitch/actions/picture_in_picture'; - -import { TIMELINE_DELETE } from '../actions/timelines'; +import { timelineDelete } from 'flavours/glitch/actions/timelines_typed'; export interface PIPMediaProps { src: string; @@ -49,8 +48,9 @@ export const pictureInPictureReducer: Reducer = ( ...action.payload.props, }; else if (removePictureInPicture.match(action)) return initialState; - else if (action.type === TIMELINE_DELETE) - if (state.type && state.statusId === action.id) return initialState; + else if (timelineDelete.match(action)) + if (state.type && state.statusId === action.payload.statusId) + return initialState; return state; }; diff --git a/app/javascript/flavours/glitch/reducers/search.js b/app/javascript/flavours/glitch/reducers/search.js index 72835eb917..7828d49eee 100644 --- a/app/javascript/flavours/glitch/reducers/search.js +++ b/app/javascript/flavours/glitch/reducers/search.js @@ -50,6 +50,7 @@ export default function search(state = initialState, action) { return state.set('hidden', true); case SEARCH_FETCH_REQUEST: return state.withMutations(map => { + map.set('results', ImmutableMap()); map.set('isLoading', true); map.set('submitted', true); map.set('type', action.searchType); diff --git a/app/javascript/flavours/glitch/reducers/statuses.js b/app/javascript/flavours/glitch/reducers/statuses.js index 07c7b02fde..e2ca345291 100644 --- a/app/javascript/flavours/glitch/reducers/statuses.js +++ b/app/javascript/flavours/glitch/reducers/statuses.js @@ -1,12 +1,10 @@ import { Map as ImmutableMap, fromJS } from 'immutable'; +import { timelineDelete } from 'flavours/glitch/actions/timelines_typed'; + import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer'; import { normalizeStatusTranslation } from '../actions/importer/normalizer'; import { - REBLOG_REQUEST, - REBLOG_FAIL, - UNREBLOG_REQUEST, - UNREBLOG_FAIL, FAVOURITE_REQUEST, FAVOURITE_FAIL, UNFAVOURITE_REQUEST, @@ -21,6 +19,10 @@ import { UNBOOKMARK_REQUEST, UNBOOKMARK_FAIL, } from '../actions/interactions'; +import { + reblog, + unreblog, +} from '../actions/interactions_typed'; import { STATUS_MUTE_SUCCESS, STATUS_UNMUTE_SUCCESS, @@ -32,7 +34,6 @@ import { STATUS_FETCH_REQUEST, STATUS_FETCH_FAIL, } from '../actions/statuses'; -import { TIMELINE_DELETE } from '../actions/timelines'; const importStatus = (state, status) => state.set(status.id, fromJS(status)); @@ -107,6 +108,7 @@ const statusTranslateUndo = (state, id) => { const initialState = ImmutableMap(); +/** @type {import('@reduxjs/toolkit').Reducer} */ export default function statuses(state = initialState, action) { switch(action.type) { case STATUS_FETCH_REQUEST: @@ -133,10 +135,6 @@ export default function statuses(state = initialState, action) { return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'bookmarked'], false); case UNBOOKMARK_FAIL: return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'bookmarked'], true); - case REBLOG_REQUEST: - return state.setIn([action.status.get('id'), 'reblogged'], true); - case REBLOG_FAIL: - return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'reblogged'], false); case REACTION_UPDATE: return updateReactionCount(state, action.reaction); case REACTION_ADD_REQUEST: @@ -145,10 +143,6 @@ export default function statuses(state = initialState, action) { case REACTION_REMOVE_REQUEST: case REACTION_ADD_FAIL: return removeReaction(state, action.id, action.name); - case UNREBLOG_REQUEST: - return state.setIn([action.status.get('id'), 'reblogged'], false); - case UNREBLOG_FAIL: - return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'reblogged'], true); case STATUS_MUTE_SUCCESS: return state.setIn([action.id, 'muted'], true); case STATUS_UNMUTE_SUCCESS: @@ -171,13 +165,22 @@ export default function statuses(state = initialState, action) { }); case STATUS_COLLAPSE: return state.setIn([action.id, 'collapsed'], action.isCollapsed); - case TIMELINE_DELETE: - return deleteStatus(state, action.id, action.references); + case timelineDelete.type: + return deleteStatus(state, action.payload.statusId, action.payload.references); case STATUS_TRANSLATE_SUCCESS: return statusTranslateSuccess(state, action.id, action.translation); case STATUS_TRANSLATE_UNDO: return statusTranslateUndo(state, action.id); default: - return state; + if(reblog.pending.match(action)) + return state.setIn([action.meta.arg.statusId, 'reblogged'], true); + else if(reblog.rejected.match(action)) + return state.get(action.meta.arg.statusId) === undefined ? state : state.setIn([action.meta.arg.statusId, 'reblogged'], false); + else if(unreblog.pending.match(action)) + return state.setIn([action.meta.arg.statusId, 'reblogged'], false); + else if(unreblog.rejected.match(action)) + return state.get(action.meta.arg.statusId) === undefined ? state : state.setIn([action.meta.arg.statusId, 'reblogged'], true); + else + return state; } } diff --git a/app/javascript/flavours/glitch/reducers/timelines.js b/app/javascript/flavours/glitch/reducers/timelines.js index 08c7a48336..f4944472bb 100644 --- a/app/javascript/flavours/glitch/reducers/timelines.js +++ b/app/javascript/flavours/glitch/reducers/timelines.js @@ -1,5 +1,7 @@ import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable'; +import { timelineDelete } from 'flavours/glitch/actions/timelines_typed'; + import { blockAccountSuccess, muteAccountSuccess, @@ -7,19 +9,18 @@ import { } from '../actions/accounts'; import { TIMELINE_UPDATE, - TIMELINE_DELETE, TIMELINE_CLEAR, TIMELINE_EXPAND_SUCCESS, TIMELINE_EXPAND_REQUEST, TIMELINE_EXPAND_FAIL, TIMELINE_SCROLL_TOP, TIMELINE_CONNECT, - TIMELINE_DISCONNECT, TIMELINE_LOAD_PENDING, TIMELINE_MARK_AS_PARTIAL, TIMELINE_INSERT, TIMELINE_GAP, TIMELINE_SUGGESTIONS, + disconnectTimeline, } from '../actions/timelines'; import { compareId } from '../compare_id'; @@ -164,7 +165,7 @@ const filterTimelines = (state, relationship, statuses) => { return; } - references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => item.get('id')); + references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => item.get('id')).valueSeq().toJSON(); state = deleteStatus(state, status.get('id'), references, relationship.id); }); @@ -207,8 +208,8 @@ export default function timelines(state = initialState, action) { return expandNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial, action.isLoadingRecent, action.usePendingItems); case TIMELINE_UPDATE: return updateTimeline(state, action.timeline, fromJS(action.status), action.usePendingItems, action.filtered); - case TIMELINE_DELETE: - return deleteStatus(state, action.id, action.references, action.reblogOf); + case timelineDelete.type: + return deleteStatus(state, action.payload.statusId, action.payload.references, action.payload.reblogOf); case TIMELINE_CLEAR: return clearTimeline(state, action.timeline); case blockAccountSuccess.type: @@ -220,11 +221,11 @@ export default function timelines(state = initialState, action) { return updateTop(state, action.timeline, action.top); case TIMELINE_CONNECT: return state.update(action.timeline, initialTimeline, map => reconnectTimeline(map, action.usePendingItems)); - case TIMELINE_DISCONNECT: + case disconnectTimeline.type: return state.update( - action.timeline, + action.payload.timeline, initialTimeline, - map => map.set('online', false).update(action.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(TIMELINE_GAP) : items), + map => map.set('online', false).update(action.payload.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(TIMELINE_GAP) : items), ); case TIMELINE_MARK_AS_PARTIAL: return state.update( diff --git a/app/javascript/flavours/glitch/reducers/user_lists.js b/app/javascript/flavours/glitch/reducers/user_lists.js index 3eb80da437..0ea3debc6c 100644 --- a/app/javascript/flavours/glitch/reducers/user_lists.js +++ b/app/javascript/flavours/glitch/reducers/user_lists.js @@ -1,12 +1,8 @@ import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; import { - DIRECTORY_FETCH_REQUEST, - DIRECTORY_FETCH_SUCCESS, - DIRECTORY_FETCH_FAIL, - DIRECTORY_EXPAND_REQUEST, - DIRECTORY_EXPAND_SUCCESS, - DIRECTORY_EXPAND_FAIL, + expandDirectory, + fetchDirectory } from 'flavours/glitch/actions/directory'; import { FEATURED_TAGS_FETCH_REQUEST, @@ -117,6 +113,7 @@ const normalizeFeaturedTags = (state, path, featuredTags, accountId) => { })); }; +/** @type {import('@reduxjs/toolkit').Reducer} */ export default function userLists(state = initialState, action) { switch(action.type) { case FOLLOWERS_FETCH_SUCCESS: @@ -194,16 +191,6 @@ export default function userLists(state = initialState, action) { case MUTES_FETCH_FAIL: case MUTES_EXPAND_FAIL: return state.setIn(['mutes', 'isLoading'], false); - case DIRECTORY_FETCH_SUCCESS: - return normalizeList(state, ['directory'], action.accounts, action.next); - case DIRECTORY_EXPAND_SUCCESS: - return appendToList(state, ['directory'], action.accounts, action.next); - case DIRECTORY_FETCH_REQUEST: - case DIRECTORY_EXPAND_REQUEST: - return state.setIn(['directory', 'isLoading'], true); - case DIRECTORY_FETCH_FAIL: - case DIRECTORY_EXPAND_FAIL: - return state.setIn(['directory', 'isLoading'], false); case FEATURED_TAGS_FETCH_SUCCESS: return normalizeFeaturedTags(state, ['featured_tags', action.id], action.tags, action.id); case FEATURED_TAGS_FETCH_REQUEST: @@ -211,6 +198,17 @@ export default function userLists(state = initialState, action) { case FEATURED_TAGS_FETCH_FAIL: return state.setIn(['featured_tags', action.id, 'isLoading'], false); default: - return state; + if(fetchDirectory.fulfilled.match(action)) + return normalizeList(state, ['directory'], action.payload.accounts, undefined); + else if( expandDirectory.fulfilled.match(action)) + return appendToList(state, ['directory'], action.payload.accounts, undefined); + else if(fetchDirectory.pending.match(action) || + expandDirectory.pending.match(action)) + return state.setIn(['directory', 'isLoading'], true); + else if(fetchDirectory.rejected.match(action) || + expandDirectory.rejected.match(action)) + return state.setIn(['directory', 'isLoading'], false); + else + return state; } } diff --git a/app/javascript/flavours/glitch/store/middlewares/sounds.ts b/app/javascript/flavours/glitch/store/middlewares/sounds.ts index ab04f6c94b..ced4d910bf 100644 --- a/app/javascript/flavours/glitch/store/middlewares/sounds.ts +++ b/app/javascript/flavours/glitch/store/middlewares/sounds.ts @@ -74,8 +74,9 @@ export const soundsMiddleware = (): Middleware< if (isActionWithMetaSound(action)) { const sound = action.meta.sound; - if (sound && Object.hasOwn(soundCache, sound)) { - play(soundCache[sound]); + if (sound) { + const s = soundCache[sound]; + if (s) play(s); } } diff --git a/app/javascript/flavours/glitch/store/typed_functions.ts b/app/javascript/flavours/glitch/store/typed_functions.ts index b66d7545c5..e5820149db 100644 --- a/app/javascript/flavours/glitch/store/typed_functions.ts +++ b/app/javascript/flavours/glitch/store/typed_functions.ts @@ -2,6 +2,8 @@ import { createAsyncThunk } from '@reduxjs/toolkit'; // eslint-disable-next-line @typescript-eslint/no-restricted-imports import { useDispatch, useSelector } from 'react-redux'; +import type { BaseThunkAPI } from '@reduxjs/toolkit/dist/createAsyncThunk'; + import type { AppDispatch, RootState } from './store'; export const useAppDispatch = useDispatch.withTypes(); @@ -13,8 +15,199 @@ export interface AsyncThunkRejectValue { error?: unknown; } +interface AppMeta { + skipLoading?: boolean; +} + export const createAppAsyncThunk = createAsyncThunk.withTypes<{ state: RootState; dispatch: AppDispatch; rejectValue: AsyncThunkRejectValue; }>(); + +type AppThunkApi = Pick< + BaseThunkAPI< + RootState, + unknown, + AppDispatch, + AsyncThunkRejectValue, + AppMeta, + AppMeta + >, + 'getState' | 'dispatch' +>; + +interface AppThunkOptions { + skipLoading?: boolean; +} + +const createBaseAsyncThunk = createAsyncThunk.withTypes<{ + state: RootState; + dispatch: AppDispatch; + rejectValue: AsyncThunkRejectValue; + fulfilledMeta: AppMeta; + rejectedMeta: AppMeta; +}>(); + +export function createThunk( + name: string, + creator: (arg: Arg, api: AppThunkApi) => Returned | Promise, + options: AppThunkOptions = {}, +) { + return createBaseAsyncThunk( + name, + async ( + arg: Arg, + { getState, dispatch, fulfillWithValue, rejectWithValue }, + ) => { + try { + const result = await creator(arg, { dispatch, getState }); + + return fulfillWithValue(result, { + skipLoading: options.skipLoading, + }); + } catch (error) { + return rejectWithValue({ error }, { skipLoading: true }); + } + }, + { + getPendingMeta() { + if (options.skipLoading) return { skipLoading: true }; + return {}; + }, + }, + ); +} + +const discardLoadDataInPayload = Symbol('discardLoadDataInPayload'); +type DiscardLoadData = typeof discardLoadDataInPayload; + +type OnData = ( + data: LoadDataResult, + api: AppThunkApi & { + actionArg: ActionArg; + discardLoadData: DiscardLoadData; + }, +) => ReturnedData | DiscardLoadData | Promise; + +type LoadData = ( + args: Args, + api: AppThunkApi, +) => Promise; + +type ArgsType = Record | undefined; + +// Overload when there is no `onData` method, the payload is the `onData` result +export function createDataLoadingThunk( + name: string, + loadData: (args: Args) => Promise, + thunkOptions?: AppThunkOptions, +): ReturnType>; + +// Overload when the `onData` method returns discardLoadDataInPayload, then the payload is empty +export function createDataLoadingThunk( + name: string, + loadData: LoadData, + onDataOrThunkOptions?: + | AppThunkOptions + | OnData, + thunkOptions?: AppThunkOptions, +): ReturnType>; + +// Overload when the `onData` method returns nothing, then the mayload is the `onData` result +export function createDataLoadingThunk( + name: string, + loadData: LoadData, + onDataOrThunkOptions?: AppThunkOptions | OnData, + thunkOptions?: AppThunkOptions, +): ReturnType>; + +// Overload when there is an `onData` method returning something +export function createDataLoadingThunk< + LoadDataResult, + Args extends ArgsType, + Returned, +>( + name: string, + loadData: LoadData, + onDataOrThunkOptions?: + | AppThunkOptions + | OnData, + thunkOptions?: AppThunkOptions, +): ReturnType>; + +/** + * This function creates a Redux Thunk that handles loading data asynchronously (usually from the API), dispatching `pending`, `fullfilled` and `rejected` actions. + * + * You can run a callback on the `onData` results to either dispatch side effects or modify the payload. + * + * It is a wrapper around RTK's [`createAsyncThunk`](https://redux-toolkit.js.org/api/createAsyncThunk) + * @param name Prefix for the actions types + * @param loadData Function that loads the data. It's (object) argument will become the thunk's argument + * @param onDataOrThunkOptions + * Callback called on the results from `loadData`. + * + * First argument will be the return from `loadData`. + * + * Second argument is an object with: `dispatch`, `getState` and `discardLoadData`. + * It can return: + * - `undefined` (or no explicit return), meaning that the `onData` results will be the payload + * - `discardLoadData` to discard the `onData` results and return an empty payload + * - anything else, which will be the payload + * + * You can also omit this parameter and pass `thunkOptions` directly + * @param maybeThunkOptions + * Additional Mastodon specific options for the thunk. Currently supports: + * - `skipLoading` to avoid showing the loading bar when the request is in progress + * @returns The created thunk + */ +export function createDataLoadingThunk< + LoadDataResult, + Args extends ArgsType, + Returned, +>( + name: string, + loadData: LoadData, + onDataOrThunkOptions?: + | AppThunkOptions + | OnData, + maybeThunkOptions?: AppThunkOptions, +) { + let onData: OnData | undefined; + let thunkOptions: AppThunkOptions | undefined; + + if (typeof onDataOrThunkOptions === 'function') onData = onDataOrThunkOptions; + else if (typeof onDataOrThunkOptions === 'object') + thunkOptions = onDataOrThunkOptions; + + if (maybeThunkOptions) { + thunkOptions = maybeThunkOptions; + } + + return createThunk( + name, + async (arg, { getState, dispatch }) => { + const data = await loadData(arg, { + dispatch, + getState, + }); + + if (!onData) return data as Returned; + + const result = await onData(data, { + dispatch, + getState, + discardLoadData: discardLoadDataInPayload, + actionArg: arg, + }); + + // if there is no return in `onData`, we return the `onData` result + if (typeof result === 'undefined') return data as Returned; + // the user explicitely asked to discard the payload + else if (result === discardLoadDataInPayload) + return undefined as Returned; + else return result; + }, + thunkOptions, + ); +} diff --git a/app/javascript/flavours/glitch/stream.js b/app/javascript/flavours/glitch/stream.js index 2113849274..7f430af9c2 100644 --- a/app/javascript/flavours/glitch/stream.js +++ b/app/javascript/flavours/glitch/stream.js @@ -2,6 +2,8 @@ import WebSocketClient from '@gamestdio/websocket'; +import { getAccessToken } from './initial_state'; + /** * @type {WebSocketClient | undefined} */ @@ -145,9 +147,11 @@ const channelNameWithInlineParams = (channelName, params) => { // @ts-expect-error export const connectStream = (channelName, params, callbacks) => (dispatch, getState) => { const streamingAPIBaseURL = getState().getIn(['meta', 'streaming_api_base_url']); - const accessToken = getState().getIn(['meta', 'access_token']); + const accessToken = getAccessToken(); const { onConnect, onReceive, onDisconnect } = callbacks(dispatch, getState); + if(!accessToken) throw new Error("Trying to connect to the streaming server but no access token is available."); + // If we cannot use a websockets connection, we must fall back // to using individual connections for each channel if (!streamingAPIBaseURL.startsWith('ws')) { diff --git a/app/javascript/flavours/glitch/styles/components.scss b/app/javascript/flavours/glitch/styles/components.scss index 47f2226ccb..3cdf3eb7b8 100644 --- a/app/javascript/flavours/glitch/styles/components.scss +++ b/app/javascript/flavours/glitch/styles/components.scss @@ -120,8 +120,27 @@ text-decoration: none; } - &:disabled { - opacity: 0.5; + &.button--destructive { + &:active, + &:focus, + &:hover { + border-color: $ui-button-destructive-focus-background-color; + color: $ui-button-destructive-focus-background-color; + } + } + + &:disabled, + &.disabled { + opacity: 0.7; + border-color: $ui-primary-color; + color: $ui-primary-color; + + &:active, + &:focus, + &:hover { + border-color: $ui-primary-color; + color: $ui-primary-color; + } } } @@ -952,9 +971,15 @@ body > [data-popper-placement] { padding: 10px; p { + font-size: 15px; + line-height: 22px; color: $darker-text-color; margin-bottom: 20px; + strong { + font-weight: 700; + } + a { color: $secondary-text-color; text-decoration: none; @@ -1405,6 +1430,8 @@ body > [data-popper-placement] { min-height: 54px; border-bottom: 1px solid var(--background-border-color); cursor: auto; + opacity: 1; + animation: fade 150ms linear; @keyframes fade { 0% { @@ -1416,9 +1443,6 @@ body > [data-popper-placement] { } } - opacity: 1; - animation: fade 150ms linear; - .media-gallery, .video-player, .audio-player, @@ -1472,7 +1496,7 @@ body > [data-popper-placement] { .status__action-bar, .reactions-bar { margin-inline-start: $thread-margin; - width: calc(100% - ($thread-margin)); + width: calc(100% - $thread-margin); } .status__content__read-more-button { @@ -1517,74 +1541,49 @@ body > [data-popper-placement] { } } } +} - &.collapsed { +.status__wrapper.collapsed { + .status { background-position: center; background-size: cover; user-select: none; min-height: 0; + } - &.has-background::before { - display: block; - position: absolute; - inset-inline-start: 0; - inset-inline-end: 0; - top: 0; - bottom: 0; - background-image: linear-gradient( - to bottom, - rgba($base-shadow-color, 0.75), - rgba($base-shadow-color, 0.65) 24px, - rgba($base-shadow-color, 0.8) - ); - pointer-events: none; - content: ''; - } + &.has-background::before { + display: block; + position: absolute; + inset-inline-start: 0; + inset-inline-end: 0; + top: 0; + bottom: 0; + background-image: linear-gradient( + to bottom, + rgba($base-shadow-color, 0.75), + rgba($base-shadow-color, 0.65) 24px, + rgba($base-shadow-color, 0.8) + ); + pointer-events: none; + content: ''; + } - .display-name:hover .display-name__html { + .display-name:hover .display-name__html { + text-decoration: none; + } + + .status__content { + height: 20px; + overflow: hidden; + text-overflow: ellipsis; + padding-top: 0; + mask-image: linear-gradient(rgb(0 0 0 / 100%), transparent); + + a:hover { text-decoration: none; } - - .status__content { - height: 20px; - overflow: hidden; - text-overflow: ellipsis; - padding-top: 0; - - &::after { - content: ''; - position: absolute; - top: 0; - bottom: 0; - inset-inline-start: 0; - inset-inline-end: 0; - background: linear-gradient(transparent, var(--background-color)); - pointer-events: none; - } - - a:hover { - text-decoration: none; - } - } - - &:focus > .status__content::after { - background: linear-gradient( - rgba(lighten($ui-base-color, 4%), 0), - rgba(lighten($ui-base-color, 4%), 1) - ); - } - - // TODO: review - &.status-direct > .status__content::after { - background: linear-gradient( - rgba(mix($ui-base-color, $ui-highlight-color, 95%), 0), - rgba(mix($ui-base-color, $ui-highlight-color, 95%), 1) - ); - } } -} -.status__wrapper.collapsed { .notification__message { margin-bottom: 0; white-space: nowrap; @@ -1799,10 +1798,6 @@ body > [data-popper-placement] { .status__wrapper-direct { background: rgba($ui-highlight-color, 0.05); - &:focus { - background: rgba($ui-highlight-color, 0.05); - } - .status__prepend { color: $highlight-text-color; } @@ -2241,7 +2236,22 @@ a .account__avatar { white-space: nowrap; display: flex; align-items: center; - gap: 4px; + gap: 8px; +} + +.account__relationship, +.explore__suggestions__card { + .icon-button { + border: 1px solid var(--background-border-color); + border-radius: 4px; + box-sizing: content-box; + padding: 5px; + + .icon { + width: 24px; + height: 24px; + } + } } .account-authorize { @@ -2392,7 +2402,8 @@ a.account__display-name { } } -.notification__relationships-severance-event { +.notification__relationships-severance-event, +.notification__moderation-warning { display: flex; gap: 16px; color: $secondary-text-color; @@ -2620,7 +2631,7 @@ a.account__display-name { } .dropdown-animation { - animation: dropdown 150ms cubic-bezier(0.1, 0.7, 0.1, 1); + animation: dropdown 250ms cubic-bezier(0.1, 0.7, 0.1, 1); @keyframes dropdown { from { @@ -3179,6 +3190,75 @@ $ui-header-logo-wordmark-width: 99px; display: none; } +.explore__suggestions__card { + padding: 12px 16px; + gap: 8px; + display: flex; + flex-direction: column; + border-bottom: 1px solid var(--background-border-color); + + &:last-child { + border-bottom: 0; + } + + &__source { + padding-inline-start: 60px; + font-size: 13px; + line-height: 16px; + color: $dark-text-color; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + + &__body { + display: flex; + gap: 12px; + align-items: center; + + &__main { + flex: 1 1 auto; + display: flex; + flex-direction: column; + gap: 8px; + min-width: 0; + + &__name-button { + display: flex; + align-items: center; + gap: 8px; + + &__name { + display: block; + color: inherit; + text-decoration: none; + flex: 1 1 auto; + min-width: 0; + } + + .button { + min-width: 80px; + } + + .display-name { + font-size: 15px; + line-height: 20px; + color: $secondary-text-color; + + strong { + font-weight: 700; + } + + &__account { + color: $darker-text-color; + display: block; + } + } + } + } + } +} + @media screen and (max-width: $no-gap-breakpoint - 1px) { .columns-area__panels__pane--compositional { display: none; @@ -4061,6 +4141,10 @@ input.glitch-setting-text { border: 1px solid var(--background-border-color); border-radius: 8px; + &.bottomless { + border-radius: 8px 8px 0 0; + } + &__actions { bottom: 0; inset-inline-start: 0; @@ -4290,6 +4374,13 @@ a.status-card { border-end-start-radius: 0; } +.status-card.bottomless .status-card__image, +.status-card.bottomless .status-card__image-image, +.status-card.bottomless .status-card__image-preview { + border-end-end-radius: 0; + border-end-start-radius: 0; +} + .status-card.expanded > a { width: 100%; } @@ -4536,6 +4627,10 @@ a.status-card { &:hover { color: $primary-text-color; } + + .icon-sliders { + transform: rotate(60deg); + } } &:disabled { @@ -4583,6 +4678,10 @@ a.status-card { padding: 0; } +.no-reduce-motion .column-header__button .icon-sliders { + transition: transform 150ms ease-in-out; +} + .column-header__collapsible { max-height: 70vh; overflow: hidden; @@ -5116,8 +5215,10 @@ a.status-card { &__menu { @include search-popout; - padding: 0; - background: $ui-secondary-color; + & { + padding: 0; + background: $ui-secondary-color; + } } &__menu-list { @@ -6046,7 +6147,7 @@ a.status-card { user-select: text; display: flex; - @media screen and (max-width: $no-gap-breakpoint) { + @media screen and (width <= 630px) { margin-top: auto; } } @@ -7880,10 +7981,11 @@ img.modal-warning { content: ''; position: absolute; bottom: -1px; - left: 0; - width: 100%; + left: 50%; + transform: translateX(-50%); + width: 40px; height: 3px; - border-radius: 4px; + border-radius: 4px 4px 0 0; background: $highlight-text-color; } } @@ -9294,43 +9396,80 @@ noscript { display: flex; align-items: center; color: $primary-text-color; - text-decoration: none; - padding: 15px; + padding: 16px; border-bottom: 1px solid var(--background-border-color); - gap: 15px; + gap: 16px; &:last-child { border-bottom: 0; } - &:hover, - &:active, - &:focus { - color: $highlight-text-color; - - .story__details__publisher, - .story__details__shared { - color: $highlight-text-color; - } - } - &__details { flex: 1 1 auto; &__publisher { color: $darker-text-color; margin-bottom: 8px; + font-size: 14px; + line-height: 20px; } &__title { + display: block; font-size: 19px; line-height: 24px; font-weight: 500; margin-bottom: 8px; + text-decoration: none; + color: $primary-text-color; + + &:hover, + &:active, + &:focus { + color: $highlight-text-color; + } } &__shared { + display: flex; + align-items: center; color: $darker-text-color; + gap: 8px; + justify-content: space-between; + font-size: 14px; + line-height: 20px; + + & > span { + display: flex; + align-items: center; + gap: 4px; + } + + &__pill { + background: var(--surface-variant-background-color); + border-radius: 4px; + color: inherit; + text-decoration: none; + padding: 4px 12px; + font-size: 12px; + font-weight: 500; + line-height: 16px; + } + + &__author-link { + display: inline-flex; + align-items: center; + gap: 4px; + color: $primary-text-color; + font-weight: 500; + text-decoration: none; + + &:hover, + &:active, + &:focus { + color: $highlight-text-color; + } + } } strong { @@ -9395,14 +9534,14 @@ noscript { } .server-banner { - padding: 20px 0; - &__introduction { + font-size: 15px; + line-height: 22px; color: $darker-text-color; margin-bottom: 20px; strong { - font-weight: 600; + font-weight: 700; } a { @@ -9430,6 +9569,9 @@ noscript { } &__description { + font-size: 15px; + line-height: 22px; + color: $darker-text-color; margin-bottom: 20px; } @@ -10401,14 +10543,14 @@ noscript { color: inherit; text-decoration: none; padding: 4px 12px; - background: $ui-base-color; + background: var(--surface-variant-background-color); border-radius: 4px; font-weight: 500; &:hover, &:focus, &:active { - background: lighten($ui-base-color, 4%); + background: var(--surface-variant-active-background-color); } } @@ -10667,6 +10809,7 @@ noscript { font-weight: 500; font-size: 11px; line-height: 16px; + word-break: keep-all; &__badge { background: $ui-button-background-color; @@ -10737,6 +10880,201 @@ noscript { } } +.more-from-author { + box-sizing: border-box; + font-size: 14px; + color: $darker-text-color; + background: var(--surface-background-color); + border: 1px solid var(--background-border-color); + border-top: 0; + border-radius: 0 0 8px 8px; + padding: 15px; + display: flex; + align-items: center; + gap: 8px; + + .logo { + height: 16px; + color: $darker-text-color; + } + + & > span { + display: flex; + align-items: center; + gap: 8px; + } + + a { + display: inline-flex; + align-items: center; + gap: 4px; + font-weight: 500; + color: $primary-text-color; + text-decoration: none; + + &:hover, + &:focus, + &:active { + color: $highlight-text-color; + } + } +} + +.hover-card-controller[data-popper-reference-hidden='true'] { + opacity: 0; + pointer-events: none; +} + +.hover-card { + box-shadow: var(--dropdown-shadow); + background: var(--modal-background-color); + backdrop-filter: var(--background-filter); + border: 1px solid var(--modal-border-color); + border-radius: 8px; + padding: 16px; + width: 270px; + display: flex; + flex-direction: column; + gap: 12px; + + &--loading { + position: relative; + min-height: 100px; + } + + &__name { + display: flex; + gap: 12px; + text-decoration: none; + color: inherit; + } + + &__number { + font-size: 15px; + line-height: 22px; + color: $secondary-text-color; + + strong { + font-weight: 700; + } + } + + &__text-row { + display: flex; + flex-direction: column; + gap: 8px; + } + + &__bio { + color: $secondary-text-color; + font-size: 14px; + line-height: 20px; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + max-height: 2 * 20px; + overflow: hidden; + + p { + margin-bottom: 0; + } + + a { + color: inherit; + text-decoration: underline; + + &:hover, + &:focus, + &:active { + text-decoration: none; + } + } + } + + .display-name { + font-size: 15px; + line-height: 22px; + + bdi { + font-weight: 500; + color: $primary-text-color; + } + + &__account { + display: block; + color: $dark-text-color; + } + } + + .account-fields { + color: $secondary-text-color; + font-size: 14px; + line-height: 20px; + + a { + color: inherit; + text-decoration: none; + + &:focus, + &:hover, + &:active { + text-decoration: underline; + } + } + + dl { + display: flex; + align-items: center; + gap: 4px; + + dt { + flex: 0 1 auto; + color: $dark-text-color; + min-width: 0; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + dd { + flex: 1 1 auto; + font-weight: 500; + min-width: 0; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + text-align: end; + } + + &.verified { + dd { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 4px; + overflow: hidden; + white-space: nowrap; + color: $valid-value-color; + + & > span { + overflow: hidden; + text-overflow: ellipsis; + } + + a { + font-weight: 500; + } + + .icon { + width: 16px; + height: 16px; + } + } + } + } + } +} + .tenor-modal__container { text-align: center; padding: 20px; diff --git a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss index 119b0fc2a4..1f5059c321 100644 --- a/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss +++ b/app/javascript/flavours/glitch/styles/mastodon-light/diff.scss @@ -48,6 +48,10 @@ html { } } +.icon-button:disabled { + color: darken($action-button-color, 25%); +} + .account__header__bar .avatar .account__avatar { border-color: $white; } @@ -513,13 +517,6 @@ html { } } -.status.collapsed .status__content::after { - background: linear-gradient( - rgba(darken($ui-base-color, 13%), 0), - rgba(darken($ui-base-color, 13%), 1) - ); -} - .drawer__inner__mastodon { background: $white url('data:image/svg+xml;utf8,') diff --git a/app/javascript/flavours/glitch/styles/mastodon-light/variables.scss b/app/javascript/flavours/glitch/styles/mastodon-light/variables.scss index 09a75a834b..9f571b3f26 100644 --- a/app/javascript/flavours/glitch/styles/mastodon-light/variables.scss +++ b/app/javascript/flavours/glitch/styles/mastodon-light/variables.scss @@ -56,11 +56,13 @@ $account-background-color: $white !default; $emojis-requiring-inversion: 'chains'; -.theme-mastodon-light { +body { --dropdown-border-color: #d9e1e8; --dropdown-background-color: #fff; + --modal-border-color: #d9e1e8; + --modal-background-color: var(--background-color-tint); --background-border-color: #d9e1e8; --background-color: #fff; - --background-color-tint: rgba(255, 255, 255, 90%); + --background-color-tint: rgba(255, 255, 255, 80%); --background-filter: blur(10px); } diff --git a/app/javascript/flavours/glitch/styles/modern-contrast.scss b/app/javascript/flavours/glitch/styles/modern-contrast.scss index 063534249b..6485bb9b54 100644 --- a/app/javascript/flavours/glitch/styles/modern-contrast.scss +++ b/app/javascript/flavours/glitch/styles/modern-contrast.scss @@ -1,4 +1,4 @@ -// Mastodon Modern theme by Freeplay! Check the original repo for more info: https://codeberg.org/Freeplay/Mastodon-Modern +// Mastodon Modern theme by Freeplay! Check the original repo for more info: https://git.gay/freeplay/Mastodon-Modern // Everything in the "modern" directory is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License: http://creativecommons.org/licenses/by-sa/4.0/ @import 'contrast/variables'; diff --git a/app/javascript/flavours/glitch/styles/modern-dark.scss b/app/javascript/flavours/glitch/styles/modern-dark.scss index d1cf5e1463..66a6c54219 100644 --- a/app/javascript/flavours/glitch/styles/modern-dark.scss +++ b/app/javascript/flavours/glitch/styles/modern-dark.scss @@ -1,4 +1,4 @@ -// Mastodon Modern theme by Freeplay! Check the original repo for more info: https://codeberg.org/Freeplay/Mastodon-Modern +// Mastodon Modern theme by Freeplay! Check the original repo for more info: https://git.gay/freeplay/Mastodon-Modern // Everything in the "modern" directory is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License: http://creativecommons.org/licenses/by-sa/4.0/ @import 'variables'; diff --git a/app/javascript/flavours/glitch/styles/modern-light.scss b/app/javascript/flavours/glitch/styles/modern-light.scss index 214bcaef1a..2b30a01d3a 100644 --- a/app/javascript/flavours/glitch/styles/modern-light.scss +++ b/app/javascript/flavours/glitch/styles/modern-light.scss @@ -1,4 +1,4 @@ -// Mastodon Modern theme by Freeplay! Check the original repo for more info: https://codeberg.org/Freeplay/Mastodon-Modern +// Mastodon Modern theme by Freeplay! Check the original repo for more info: https://git.gay/freeplay/Mastodon-Modern // Everything in the "modern" directory is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License: http://creativecommons.org/licenses/by-sa/4.0/ @import 'mastodon-light/variables'; diff --git a/app/javascript/flavours/glitch/styles/modern/glitch-fixes.scss b/app/javascript/flavours/glitch/styles/modern/glitch-fixes.scss index 96f2b7f05b..bc50e6f9ed 100644 --- a/app/javascript/flavours/glitch/styles/modern/glitch-fixes.scss +++ b/app/javascript/flavours/glitch/styles/modern/glitch-fixes.scss @@ -1,3 +1,6 @@ +:root { + --background-color-alt: transparent; +} body.app-body.flavour-glitch > #mastodon .compose-form__autosuggest-wrapper > :last-child { padding-bottom: 2em !important; } @@ -37,6 +40,7 @@ body.app-body.flavour-glitch > #mastodon .compose-form__submit button { body.app-body.flavour-glitch > #mastodon .collapsed .status__content { height: auto !important; overflow: visible; + mask: unset !important; } body.app-body.flavour-glitch > #mastodon .collapsed .status__content .status__content__text { mask: linear-gradient(to bottom, #000 50px, transparent) !important; @@ -55,32 +59,36 @@ body.app-body.flavour-glitch > #mastodon .collapsed .status__content::after { body.app-body.flavour-glitch > #mastodon .collapsed.muted .status__content__text ~ * { display: none; } +@media (pointer: coarse) { + body.app-body.flavour-glitch > #mastodon .status__info { + align-items: center; + } + body.app-body.flavour-glitch > #mastodon .status__info__icons { + height: auto; + } +} +body.app-body.flavour-glitch > #mastodon .status { + isolation: isolate; + overflow: hidden; +} +body.app-body.flavour-glitch > #mastodon .status__info { + pointer-events: none; +} +body.app-body.flavour-glitch > #mastodon .status__avatar, +body.app-body.flavour-glitch > #mastodon .status__info__icons { + pointer-events: all; +} +body.app-body.flavour-glitch > #mastodon .status > :not(.status__content):not(.status__line) { + position: relative; + z-index: 2; +} body.app-body.flavour-glitch > #mastodon .status:not(.status-direct) > .status__content { - margin-block: -90px -100px !important; + margin-block: -100px !important; padding-block: 100px !important; } -body.app-body.flavour-glitch > #mastodon .status:not(.status-direct) > .status__content .status__content__text { - margin-top: 0px; -} -body.app-body.flavour-glitch > #mastodon .status:not(.status-direct) > .status__content > :last-child:not(.status__content__text) { - margin-bottom: 5px !important; -} -body.app-body.flavour-glitch > #mastodon .status .full-width { +body.app-body.flavour-glitch > #mastodon .full-width { margin-inline: 0 !important; } -body.app-body.flavour-glitch > #mastodon .status .status__action-bar { - position: static; -} -body.app-body.flavour-glitch > #mastodon .status__info .notification__message { - padding-top: 0 !important; - padding-inline-start: 0 !important; -} -body.app-body.flavour-glitch > #mastodon .status__display-name { - line-height: 22px; -} -body.app-body.flavour-glitch > #mastodon .display-name__account { - font-size: 15px; -} body.app-body.flavour-glitch > #mastodon .media-gallery__item > .media-gallery__preview { display: unset; } @@ -93,6 +101,7 @@ body.app-body.flavour-glitch > #mastodon .status__relative-time { flex-grow: 0 !important; min-width: 5ch !important; max-width: unset !important; + justify-content: flex-end; } body.app-body.flavour-glitch > #mastodon .status__relative-time time { display: inline !important; @@ -100,8 +109,10 @@ body.app-body.flavour-glitch > #mastodon .status__relative-time time { body.app-body.flavour-glitch > #mastodon .reactions-bar { width: unset; margin-top: 8px; + pointer-events: none; } body.app-body.flavour-glitch > #mastodon .reactions-bar button { + pointer-events: all; border-radius: 6px !important; padding-block: 2px; border: 1px solid var(--border-color-2); @@ -116,69 +127,61 @@ body.app-body.flavour-glitch > #mastodon .reactions-bar button:not(.active) { body.app-body.flavour-glitch > #mastodon .reactions-bar:empty { display: none; } +body.app-body.flavour-glitch > #mastodon .notification__message { + padding-top: 15px; +} body.app-body.flavour-glitch > #mastodon .notification__message + .status { - padding-top: 0 !important; -} -body.app-body.flavour-glitch > #mastodon .notification > .notification__message { - padding-inline: 15px !important; - padding-top: 18px !important; -} -body.app-body.flavour-glitch > #mastodon .notification__favourite-icon-wrapper { - position: static; - margin-inline-end: 10px; -} -body.app-body.flavour-glitch > #mastodon .notification__favourite-icon-wrapper i { - width: 1.28571429em !important; - text-align: center; -} -body.app-body.flavour-glitch > #mastodon .detailed-status__wrapper .focusable:not(.status)::before { - content: unset !important; -} -body.app-body.flavour-glitch > #mastodon .setting-text { - border-radius: 0 !important; - margin: 4px; - width: calc(100% - 8px); + padding-top: 5px !important; } body.app-body.flavour-glitch > #mastodon .column-settings__pillbar { border-radius: var(--radius); } body.app-body.flavour-glitch > #mastodon .pillbar-button { - border-radius: 0 !important; - padding: 6px; + padding: 10px; + border-radius: 4px; } -body.app-body.flavour-glitch > #mastodon .account__header__account-note:focus-within { - border-radius: var(--radius) !important; +body.app-body.flavour-glitch > #mastodon .column-header__notif-cleaning-buttons { + flex-wrap: wrap; } -body.app-body.flavour-glitch > #mastodon .account__header__account-note__header { - align-items: center; +body.app-body.flavour-glitch > #mastodon .column-header__notif-cleaning-buttons button { + min-width: 50% !important; } -body.app-body.flavour-glitch > #mastodon .account__header__account-note__header button { - display: flex; - gap: 0.2em; +body.app-body.flavour-glitch > #mastodon .notification__dismiss-overlay { + position: absolute !important; } -body.app-body.flavour-glitch > #mastodon .account__header__account-note__content { +body.app-body.flavour-glitch > #mastodon .notification__dismiss-overlay .wrappy { + box-shadow: none; + background: none; + border-top: 0; +} +body.app-body.flavour-glitch > #mastodon .local-settings { + max-height: 700px !important; width: 100%; - padding: 0 !important; - margin: 0 !important; } -body.app-body.flavour-glitch > #mastodon .account-card .media-modal__close { - left: 10px; - top: 10px; +body.app-body.flavour-glitch > #mastodon .glitch.local-settings__page { + padding: 20px; } -body.app-body.flavour-glitch > #mastodon .account-card .media-modal__close::before { - content: ""; - position: absolute; - inset: -60px -30px; - background: linear-gradient(to right, #000, transparent); - transform: rotate(45deg); - z-index: -1; - opacity: 0.5; +body.app-body.flavour-glitch > #mastodon .local-settings__navigation { + display: flex; + flex-direction: column; + padding: 8px; + background: none; + border-right: 1px solid var(--border-color); + width: auto; } -.layout-multiple-columns.flavour-glitch .drawer { - flex-grow: 0.2; +body.app-body.flavour-glitch > #mastodon .local-settings__navigation .local-settings__navigation__item:not(.close):not(.active) { + background: none; } -.layout-multiple-columns.flavour-glitch .drawer__inner { - margin-top: -20px; - max-height: unset !important; - min-height: calc(100% + 40px); +body.app-body.flavour-glitch > #mastodon .local-settings__navigation .local-settings__navigation__item { + border: 0; + flex-direction: column; + padding-inline: 8px; +} +body.app-body.flavour-glitch > #mastodon .local-settings__navigation .local-settings__navigation__item span { + font-size: 0.8em; +} +body.app-body.flavour-glitch > #mastodon .local-settings__navigation [href="/settings/preferences"] { + margin-block: auto 10px; + border-radius: var(--radius); + border: 1px solid var(--border-color); } \ No newline at end of file diff --git a/app/javascript/flavours/glitch/styles/modern/style.scss b/app/javascript/flavours/glitch/styles/modern/style.scss index 1046f10711..d2eefa69d2 100644 --- a/app/javascript/flavours/glitch/styles/modern/style.scss +++ b/app/javascript/flavours/glitch/styles/modern/style.scss @@ -1,48 +1,46 @@ :root { - --tl-width: 750px; + --tl-width: 720px; + --emoji-size: 2em; + --avatar-size: 46px; --radius: 12px; --radius-round: 24px; + --panel-radius: var(--radius); --hover-color: rgba(170,170,170,0.07); --elevated-color: rgba(150,150,200,0.1); --elevated-tint: rgba(200,200,240,0.07); --border-color: rgba(120,120,200,0.2); --border-color-2: #787878; --shadow: 0 10px 40px -10px rgba(0,0,0,0.15); - --shadow-low: 0 8px 16px -10px rgba(0,0,0,0.4); + --shadow-low: 0 8px 24px -16px rgba(0,0,0,0.2); --shadow-med: 0 8px 60px -30px rgba(0,0,0,0.1); + --column-shadow: 0 8px 24px 12px rgba(0,0,0,0.02); } -body::before { +@media (max-width: 889px) { + :root { + --panel-radius: 0px; + } +} +.layout-multiple-columns { + --panel-radius: 0px; +} +body { + font-display: swap !important; +} +body:not(.admin)::before { content: ""; position: fixed; inset: 0; background: rgba(0,0,0,0.06); z-index: -1; } -:not(body):not(.scrollable)::-webkit-scrollbar { - width: 6px; - margin-block: 10px; +p { + line-height: 1.5; } -:not(body):not(.scrollable)::-webkit-scrollbar-track { - background: none; -} -:not(body):not(.scrollable)::-webkit-scrollbar-thumb { - border-radius: 100px; - transition: background-color 0.2s; -} -:not(body):not(.scrollable):not(:hover)::-webkit-scrollbar-thumb { - background: none; - padding-block: 10px; -} -.rtl textarea { - text-align: right; -} -a, -button, -label { - user-select: none; +input { + text-align: start; } .button--block { - font-weight: 700; + font-weight: bold; } .unhandled-link span, .mention span { @@ -64,7 +62,7 @@ video, .react-toggle-track, .reply-indicator, .compose-form__warning { - border-radius: var(--radius) !important; + border-radius: var(--radius); } button:focus-visible, .focusable:focus-visible, @@ -74,14 +72,51 @@ a:focus-visible, outline: 2px #dc7b18 solid; outline-offset: -2px; } -*:not(.radio-button__input):not(input) { - font-display: swap !important; -} :not(.radio-button__input):not(span) { border-color: var(--border-color) !important; } -.setting-text { - padding-inline: 10px; +.nothing-here, +.column-inline-form, +.scrollable, +.detailed-status__action-bar, +.column-back-button, +.column-header__collapsible.collapsed, +.column-header__collapsible-inner, +.audio-player, +.search__input { + border: 0 !important; +} +.account__section-headline, +.notification__filter-bar, +.column-header { + border-inline: 0; +} +.account__section-headline, +.notification__filter-bar, +.column > .scrollable { + background: none; +} +.account__avatar, +#profile_page_avatar, +.account__avatar-composite, +.account-card__title__avatar img { + border-radius: 30%; + flex: none; +} +:not(body):not(.scrollable)::-webkit-scrollbar { + width: 6px; + margin-block: 10px; +} +:not(body):not(.scrollable)::-webkit-scrollbar-track { + background: none; +} +:not(body):not(.scrollable)::-webkit-scrollbar-thumb { + border-radius: 100px; + transition: background-color 0.2s; +} +:not(body):not(.scrollable):not(:hover)::-webkit-scrollbar-thumb { + background: none; + padding-block: 10px; } @media (prefers-reduced-motion: no-preference) { body:not(.reduce-motion) .load-more, @@ -136,7 +171,7 @@ a:focus-visible, body:not(.reduce-motion) .react-toggle-track, body:not(.reduce-motion) .icon-button, body:not(.reduce-motion) .floating-action-button { - transition: transform 0.4s cubic-bezier(0, 0, 0, 4) !important; + transition: transform 0.4s cubic-bezier(0, 0, 0, 4), background 0.2s !important; } body:not(.reduce-motion) .column-header__button:active, body:not(.reduce-motion) .column-header__buttons > .column-header__back-button:active, @@ -273,25 +308,25 @@ a:focus-visible, filter: opacity(0); } } -@-moz-keyframes slideDowFade { +@-moz-keyframes slideDownFade { from { transform: translateY(-20px); filter: opacity(0); } } -@-webkit-keyframes slideDowFade { +@-webkit-keyframes slideDownFade { from { transform: translateY(-20px); filter: opacity(0); } } -@-o-keyframes slideDowFade { +@-o-keyframes slideDownFade { from { transform: translateY(-20px); filter: opacity(0); } } -@keyframes slideDowFade { +@keyframes slideDownFade { from { transform: translateY(-20px); filter: opacity(0); @@ -406,71 +441,6 @@ a:focus-visible, } } } -.account__avatar, -#profile_page_avatar, -.account__avatar-composite, -.account-card__title__avatar img { - border-radius: 30% !important; -} -.scrollable, -.detailed-status__action-bar, -.column-back-button, -.column-header__collapsible.collapsed, -.column-header__collapsible-inner, -.audio-player, -.search__input { - border: 0 !important; -} -.account__section-headline, -.notification__filter-bar, -.column-header { - border-inline: 0; -} -.dropdown-menu, -.dropdown-animation { - border-radius: var(--radius); - animation: scaleIn 0.2s cubic-bezier(0, 0, 0, 1.1); -} -.dropdown-menu__container__list { - overflow: hidden auto; - border-radius: var(--radius); - max-height: 70vh; -} -.dropdown-menu__item { - overflow: hidden; -} -.dropdown-menu__item a { - padding: 0.7em 1em !important; - transition: background-color 0.2s, color 0.2s; - min-width: 150px; -} -.dropdown-menu__separator { - margin: 0 !important; -} -.interaction-modal { - border-radius: var(--radius); - overflow-y: auto; -} -.interaction-modal__choices { - gap: 10px; - display: flex; - flex-wrap: wrap; -} -.interaction-modal__choices .interaction-modal__choices__choice { - max-height: 50vh; - overflow-y: auto; - border: 1px solid var(--border-color); - flex: 1 0 270px; - border-radius: var(--radius); - transition: background 0.2s; - position: relative; -} -.compare-history-modal { - margin-block: 20px; -} -.compare-history-modal__container { - overflow: hidden auto; -} .columns-area__panels { --top: 5px; gap: 0; @@ -489,57 +459,7 @@ a:focus-visible, --top: 30px; } } -.emoji-picker-dropdown__menu { - border-radius: var(--radius); - overflow: hidden; - resize: both; - width: 400px; -} -.emoji-mart { - display: flex !important; - flex-direction: column !important; - width: 100% !important; - height: 100% !important; -} -.emoji-mart-scroll { - flex-grow: 1; - max-height: unset !important; -} -.emoji-mart-bar { - order: 2; -} -.emoji-mart-category-list { - overflow: visible !important; - display: grid; - grid-template-columns: repeat(auto-fill, minmax(calc(20px + 6%), 1fr)); -} -.emoji-mart-category-list li { - display: contents; -} -.emoji-mart-category-list button { - position: relative; - padding: 0 !important; - padding-top: 100% !important; -} -.emoji-mart-category-list button img, -.emoji-mart-category-list button span { - height: calc(100% - 10px) !important; - width: calc(100% - 10px) !important; - inset: 5px; - position: absolute !important; - transition: transform 0.1s cubic-bezier(0, 0, 0, 1); -} -.emoji-mart-category-list button:hover img, -.emoji-mart-category-list button:hover span { - transform: scale(1.2); -} -.emoji-picker-dropdown__modifiers { - top: 16px; -} -#mastodon { - overflow: clip; -} -#mastodon .compose-panel { +.compose-panel { overflow-y: auto; margin-top: calc(0px - var(--top)); padding-top: var(--top); @@ -549,27 +469,27 @@ a:focus-visible, max-height: unset !important; height: 100%; } -#mastodon .compose-panel > * { +.compose-panel > * { padding-inline: 0; } -#mastodon .compose-panel > .navigation-bar { +.compose-panel > .navigation-bar { padding-top: 0 !important; } -#mastodon .compose-panel .search, +.compose-panel .search, .drawer .search { margin-bottom: 25px; } -#mastodon .search { +.search { border-radius: var(--radius); margin-inline: -5px; } -#mastodon .search label { +.search label { box-sizing: border-box; } -#mastodon .search input { +.search input { border-radius: var(--radius-round) !important; } -#mastodon .search .search__icon > i { +.search .search__icon > i { margin-inline: 5px; } .search__popout { @@ -580,65 +500,66 @@ a:focus-visible, margin-inline: 4px; width: calc(100% - 8px); } -#mastodon .navigation-bar .icon-button { +.navigation-bar .icon-button { width: auto !important; height: auto !important; padding: 8px; } -#mastodon .compose-form { +.compose-form { min-height: unset; overflow: unset; gap: 15px; + flex: 1 0 auto !important; } -#mastodon .compose-form > * { +.compose-form > * { margin: 0 !important; } -#mastodon .compose-form > [aria-hidden="true"] { +.compose-form > [aria-hidden="true"] { display: none; } -#mastodon .compose-form > .navigation-bar { +.compose-form > .navigation-bar { margin-top: 10px; } -#mastodon .compose-form .reply-indicator { +.compose-form .reply-indicator { position: relative; transition: min-height 0.1s; } -#mastodon .compose-form .reply-indicator__display-name { +.compose-form .reply-indicator__display-name { padding: 0; } -#mastodon .compose-form .compose-form__autosuggest-wrapper, -#mastodon .compose-form .autosuggest-textarea__textarea { +.compose-form .compose-form__autosuggest-wrapper, +.compose-form .autosuggest-textarea__textarea { border-radius: var(--radius) var(--radius) 0 0 !important; border-bottom: 0; } -#mastodon .compose-form .compose-form__buttons-wrapper { +.compose-form .compose-form__buttons-wrapper { border-radius: 0 0 var(--radius) var(--radius) !important; } -#mastodon .compose-form .compose-form__publish-button-wrapper { +.compose-form .compose-form__publish-button-wrapper { overflow: visible !important; max-width: 100%; padding: 0; } -#mastodon .compose-form .compose-form__upload__actions { +.compose-form .compose-form__upload__actions { z-index: 5; position: relative; } -#mastodon .compose-form .compose-form__upload__actions button { +.compose-form .compose-form__upload__actions button { background: none; } -#mastodon .compose-form .compose-form__upload__thumbnail { +.compose-form .compose-form__upload__thumbnail { display: flex; flex-direction: column; } -#mastodon .compose-form .compose-form__upload__warning { +.compose-form .compose-form__upload__warning { position: relative; flex-grow: 1; display: flex; } -#mastodon .compose-form .compose-form__upload__warning button { +.compose-form .compose-form__upload__warning button { margin-top: auto; } -#mastodon .compose-form .compose-form__upload__warning button.active { +.compose-form .compose-form__upload__warning button.active { box-shadow: 0 0 0 100px rgba(0,0,0,0.75); width: 100%; height: 100%; @@ -647,61 +568,61 @@ a:focus-visible, color: inherit; transition: background 0.2s, transform 0.2s cubic-bezier(0, 0, 0, 1) !important; } -#mastodon .compose-form .compose-form__upload__warning button.active svg { +.compose-form .compose-form__upload__warning button.active svg { height: 1.2em; width: 1.2em; } -#mastodon .compose-form .compose-form__upload__warning button.active:hover, -#mastodon .compose-form .compose-form__upload__warning button.active:focus { +.compose-form .compose-form__upload__warning button.active:hover, +.compose-form .compose-form__upload__warning button.active:focus { background: rgba(20,20,20,0.75); } -#mastodon .compose-form__highlightable { +.compose-form__highlightable { border-radius: var(--radius); overflow: visible !important; } -#mastodon .compose-form__highlightable #cw-spoiler-input { +.compose-form__highlightable #cw-spoiler-input { border-radius: 0 !important; } -#mastodon .compose-form__highlightable textarea { +.compose-form__highlightable textarea { background: none !important; } -#mastodon .compose-form__highlightable > .compose-form__footer { +.compose-form__highlightable > .compose-form__footer { gap: 12px; } -#mastodon .compose-form__highlightable > .compose-form__footer .compose-form__dropdowns { +.compose-form__highlightable > .compose-form__footer .compose-form__dropdowns { max-width: calc(100% - 7ch); } -#mastodon .compose-form__highlightable > .compose-form__footer .compose-form__actions { +.compose-form__highlightable > .compose-form__footer .compose-form__actions { position: relative; } -#mastodon .compose-form__highlightable > .compose-form__footer .compose-form__buttons { +.compose-form__highlightable > .compose-form__footer .compose-form__buttons { display: flex; flex-wrap: wrap; flex-direction: row; gap: 0; flex-grow: 9999; } -#mastodon .compose-form__highlightable > .compose-form__footer .compose-form__buttons > * { +.compose-form__highlightable > .compose-form__footer .compose-form__buttons * { + display: flex; flex-grow: 1; - box-sizing: border-box; } -#mastodon .compose-form__highlightable > .compose-form__footer .compose-form__buttons button { +.compose-form__highlightable > .compose-form__footer .compose-form__buttons label { + display: none; +} +.compose-form__highlightable > .compose-form__footer .compose-form__buttons button { flex-grow: 1; padding: 5px; } -#mastodon .compose-form__highlightable > .compose-form__footer .compose-form__submit button { +.compose-form__highlightable > .compose-form__footer .compose-form__submit button { padding: 8px 16px; } -#mastodon .compose-form__highlightable > .compose-form__footer .character-counter { +.compose-form__highlightable > .compose-form__footer .character-counter { position: absolute; inset-inline-end: 0; bottom: calc(100% + 12px); padding: 4px; font-size: 13px; } -.server-banner { - padding: 10px; -} .server-banner .server-banner__hero { border-radius: var(--radius); width: 100%; @@ -753,18 +674,17 @@ a:focus-visible, margin-inline: 5px; position: relative; } -.navigation-panel__sign-in-banner .sign-in-banner p { - line-height: 1.5; -} -.navigation-panel__sign-in-banner .sign-in-banner > :last-child { - margin-bottom: 0; -} -#mastodon .link-footer { +.link-footer { margin-top: 20px; } -#mastodon .link-footer > p:last-child { +.link-footer > p:last-child { margin-bottom: 0; } +.columns-area { + box-shadow: var(--column-shadow); + padding: 0; + overflow: visible; +} .columns-area__panels__main { overflow: visible !important; transition: max-width 0.2s cubic-bezier(0, 0, 0, 1.1), margin 0.2s cubic-bezier(0, 0, 0, 1.1); @@ -773,14 +693,13 @@ a:focus-visible, .columns-area__panels__main { width: 0; flex-grow: 1; - padding-inline: 10px; + margin-inline: 10px; + max-width: var(--tl-width) !important; } } @media (min-width: 1320px) { .columns-area__panels__main { - max-width: var(--tl-width) !important; - padding: 0 15px; - margin: 0 10px; + margin: 0 20px; } } @media (min-width: 890px) { @@ -790,6 +709,7 @@ a:focus-visible, } .columns-area__panels__main .column, .columns-area__panels__main .columns-area { + grid-column: 1; overflow: clip !important; border-radius: var(--radius) var(--radius) 0 0 !important; } @@ -801,2219 +721,294 @@ a:focus-visible, .columns-area__panels__main > div { grid-row: 1; } -.columns-area__panels__main :not(.scrollable--flex) > .scrollable { - padding-bottom: 40vh !important; -} -#mastodon .column { +.column { background: var(--background-color); + overflow: clip; } -#mastodon .column::before { +.column::before { content: ""; position: absolute; inset: 0; background: var(--elevated-tint); pointer-events: none; } -#mastodon .columns-area { - box-shadow: 0 8px 24px 12px rgba(0,0,0,0.02); -} -#mastodon .column-header__wrapper ~ .scrollable { - overflow: auto !important; -} -#mastodon .scrollable:not(.scrollable--flex), -#mastodon .activity-stream { - contain: unset !important; -} -#mastodon .scrollable:not(.scrollable--flex):not(.activity-stream):not(.privacy-policy), -#mastodon .activity-stream:not(.activity-stream):not(.privacy-policy) { - padding: 10px; -} -#mastodon .scrollable:not(.scrollable--flex) > [tabindex]:first-child > .focusable, -#mastodon .activity-stream > [tabindex]:first-child > .focusable { - margin-top: 0 !important; -} -#mastodon .search-results__section__header { - margin: 0px -10px 10px; - color: unset; - background: none; - padding-inline: 20px; - font-weight: 600; -} -#mastodon .search-results__section { - border: 0; - margin-bottom: 20px; -} -#mastodon .dismissable-banner, -#mastodon .follow_requests-unlocked_explanation { - display: flex; - align-items: center; - border: 0 !important; - margin: -10px !important; - margin-bottom: 10px !important; - border-radius: 0px !important; - padding: 15px !important; - height: max-content; - isolation: isolate; - overflow: hidden; -} -.dismissable-banner__message { - padding-block: 10px; - order: -1; -} -#mastodon .dismissable-banner .dismissable-banner__action, -#mastodon .follow_requests-unlocked_explanation .dismissable-banner__action { - position: static; -} -#mastodon .dismissable-banner .scrollable:not(.scrollable--flex), -#mastodon .follow_requests-unlocked_explanation .scrollable:not(.scrollable--flex) { - padding: 0px !important; - padding-bottom: 40vh !important; -} -#mastodon .dismissable-banner .scrollable:not(.scrollable--flex)::before, -#mastodon .follow_requests-unlocked_explanation .scrollable:not(.scrollable--flex)::before { - content: ""; - position: absolute; - inset: 0; - background-color: inherit; - z-index: -1; -} -#mastodon .dismissable-banner .scrollable:not(.scrollable--flex) .account-timeline__header, -#mastodon .follow_requests-unlocked_explanation .scrollable:not(.scrollable--flex) .account-timeline__header, -#mastodon .dismissable-banner .scrollable:not(.scrollable--flex) .dismissable-banner, -#mastodon .follow_requests-unlocked_explanation .scrollable:not(.scrollable--flex) .dismissable-banner { - margin: 0px !important; -} -#mastodon .dismissable-banner .focusable, -#mastodon .follow_requests-unlocked_explanation .focusable, -#mastodon .dismissable-banner .entry, -#mastodon .follow_requests-unlocked_explanation .entry, -#mastodon .dismissable-banner .statuses-grid__item .detailed-status, -#mastodon .follow_requests-unlocked_explanation .statuses-grid__item .detailed-status, -#mastodon .dismissable-banner .trends__item, -#mastodon .follow_requests-unlocked_explanation .trends__item, -#mastodon .dismissable-banner .story, -#mastodon .follow_requests-unlocked_explanation .story, -#mastodon .dismissable-banner .account-card, -#mastodon .follow_requests-unlocked_explanation .account-card, -#mastodon .dismissable-banner .scrollable :not(.focusable) > .account, -#mastodon .follow_requests-unlocked_explanation .scrollable :not(.focusable) > .account, -#mastodon .dismissable-banner .timeline-hint, -#mastodon .follow_requests-unlocked_explanation .timeline-hint { - border-radius: 0; -} -#mastodon .dismissable-banner .focusable::before, -#mastodon .follow_requests-unlocked_explanation .focusable::before, -#mastodon .dismissable-banner .entry::before, -#mastodon .follow_requests-unlocked_explanation .entry::before, -#mastodon .dismissable-banner .statuses-grid__item .detailed-status::before, -#mastodon .follow_requests-unlocked_explanation .statuses-grid__item .detailed-status::before, -#mastodon .dismissable-banner .trends__item::before, -#mastodon .follow_requests-unlocked_explanation .trends__item::before, -#mastodon .dismissable-banner .story::before, -#mastodon .follow_requests-unlocked_explanation .story::before, -#mastodon .dismissable-banner .account-card::before, -#mastodon .follow_requests-unlocked_explanation .account-card::before, -#mastodon .dismissable-banner .scrollable :not(.focusable) > .account::before, -#mastodon .follow_requests-unlocked_explanation .scrollable :not(.focusable) > .account::before, -#mastodon .dismissable-banner .timeline-hint::before, -#mastodon .follow_requests-unlocked_explanation .timeline-hint::before { - border-radius: 0 !important; -} -#mastodon .dismissable-banner .focusable::after, -#mastodon .follow_requests-unlocked_explanation .focusable::after, -#mastodon .dismissable-banner .entry::after, -#mastodon .follow_requests-unlocked_explanation .entry::after, -#mastodon .dismissable-banner .statuses-grid__item .detailed-status::after, -#mastodon .follow_requests-unlocked_explanation .statuses-grid__item .detailed-status::after, -#mastodon .dismissable-banner .trends__item::after, -#mastodon .follow_requests-unlocked_explanation .trends__item::after, -#mastodon .dismissable-banner .story::after, -#mastodon .follow_requests-unlocked_explanation .story::after, -#mastodon .dismissable-banner .account-card::after, -#mastodon .follow_requests-unlocked_explanation .account-card::after, -#mastodon .dismissable-banner .scrollable :not(.focusable) > .account::after, -#mastodon .follow_requests-unlocked_explanation .scrollable :not(.focusable) > .account::after, -#mastodon .dismissable-banner .timeline-hint::after, -#mastodon .follow_requests-unlocked_explanation .timeline-hint::after { - inset-inline: 0 !important; -} -#mastodon .dismissable-banner [class*="explore__"] > *, -#mastodon .follow_requests-unlocked_explanation [class*="explore__"] > * { - border-radius: var(--radius); -} -#mastodon .dismissable-banner .detailed-status__wrapper, -#mastodon .follow_requests-unlocked_explanation .detailed-status__wrapper { - margin: 0 !important; -} -#mastodon .dismissable-banner .status__action-bar, -#mastodon .follow_requests-unlocked_explanation .status__action-bar { - margin-bottom: 0px; - gap: 0; - margin-inline-end: 0 !important; -} -#mastodon .dismissable-banner .status__action-bar :not(i):not(.status__action-bar-spacer), -#mastodon .follow_requests-unlocked_explanation .status__action-bar :not(i):not(.status__action-bar-spacer) { - display: flex; - flex-grow: 9999; - justify-content: center !important; - max-width: 55px; - min-width: max-content; -} -#mastodon .dismissable-banner .status__action-bar > .icon-button:first-child, -#mastodon .follow_requests-unlocked_explanation .status__action-bar > .icon-button:first-child { - margin-inline-start: -8px !important; -} -#mastodon .dismissable-banner .status__action-bar, -#mastodon .follow_requests-unlocked_explanation .status__action-bar, -#mastodon .dismissable-banner .detailed-status__action-bar, -#mastodon .follow_requests-unlocked_explanation .detailed-status__action-bar, -#mastodon .dismissable-banner .picture-in-picture__footer, -#mastodon .follow_requests-unlocked_explanation .picture-in-picture__footer { - flex-wrap: wrap; -} -@media (max-width: 890px) { - #mastodon .dismissable-banner, - #mastodon .follow_requests-unlocked_explanation { - margin: 0 !important; - } -} -#mastodon .column:not(.scrollable--flex) > .dismissable-banner { - margin: 0 !important; -} -#mastodon .column:not(.scrollable--flex) > .dismissable-banner ~ .scrollable { - border-radius: 0 !important; -} -#mastodon :not(.account__section-headline) + .scrollable > .dismissable-banner { - margin: 0px !important; - margin-bottom: 0 !important; -} -#mastodon .empty-column-indicator { - contain: unset; - padding: 10px !important; - color: unset; - opacity: 0.8; - font-size: 1.2em; - line-height: 2; -} -#mastodon .empty-column-indicator a { - display: block; - font-weight: 700; - font-size: 1.1em; -} -#mastodon .scrollable--flex .account-timeline__header { - margin: 0 !important; -} -#mastodon .item-list { - background-color: inherit; - border-radius: var(--radius); -} -#mastodon .account-timeline__header { - margin: -10px; - margin-bottom: 10px; - background-color: inherit; - border-radius: var(--radius) !important; - animation: fade backwards 0.4s cubic-bezier(0, 1, 1, 1); -} -#mastodon .account-timeline__header .account__moved-note { - box-sizing: border-box; - border-radius: var(--radius); - margin-bottom: calc(0px - var(--radius)); - padding: 30px; - padding-bottom: calc(var(--radius) + 30px); - background: inherit; -} -#mastodon .account-timeline__header .account__moved-note .detailed-status__display-name { - overflow: visible !important; -} -#mastodon .account-timeline__header .account__header { - overflow: visible !important; - border-radius: var(--radius) var(--radius) 0 0; - background: inherit; -} -#mastodon .account-timeline__header .account__header__info { - z-index: 2; -} -#mastodon .account-timeline__header .account__header__image { - height: 0; - padding-bottom: 35%; - border-radius: var(--radius) var(--radius) 0 0; - position: sticky; - top: calc(0px - var(--radius)); - overflow: clip; -} -#mastodon .account-timeline__header .account__header__image img { - position: absolute; -} -#mastodon .account-timeline__header .account__header__image .account__header__info { - height: 100%; -} -#mastodon .account-timeline__header .account__header__image .account__header__info > span { - position: sticky; - top: var(--radius); -} -#mastodon .account-timeline__header .account__header__bar { - position: relative; - z-index: 2; - border: 0; - padding-inline: 20px; - border-radius: var(--radius) var(--radius) 0 0; - margin-top: calc(0px - var(--radius)) !important; - display: flex; - flex-direction: column; - background: var(--background-color); - isolation: isolate; -} -@media (max-width: 890px) { - #mastodon .account-timeline__header .account__header__bar { - padding-inline: 15px; - } -} -#mastodon .account-timeline__header .account__header__bar::before { - content: ""; - background: var(--elevated-tint); - position: absolute; - inset: 0; - pointer-events: none; -} -#mastodon .account-timeline__header .account__header__bar::after { - content: ""; - position: absolute; - inset-inline: 0; - height: 95px; - background: inherit; - z-index: -1; - border-radius: var(--radius); - mask: linear-gradient(to bottom, transparent, #000); -} -#mastodon .account-timeline__header .account__header__bar .account__header__tabs { - overflow: visible !important; - align-items: flex-end; - padding: 0; - height: unset !important; - margin-top: -55px !important; -} -#mastodon .account-timeline__header .account__header__bar .account__header__tabs::before { - content: ""; - position: absolute; - top: -55px; - inset-inline: 0; - height: 150px; - backdrop-filter: blur(40px); - filter: brightness(1.1); - pointer-events: none; - opacity: 0.7; - z-index: -2; - clip-path: inset(55px 0 0 0 round var(--radius)); -} -#mastodon .account-timeline__header .account__header__bar .account__header__tabs ~ div { - z-index: 2; -} -#mastodon .account-timeline__header .account__header__bar .avatar { - margin-inline-start: 0 !important; - overflow: visible !important; - position: relative; - margin-top: 20px; -} -#mastodon .account-timeline__header .account__header__bar .avatar .account-role { - position: absolute; - bottom: 0; - left: 110%; - border-radius: var(--radius); -} -#mastodon .account-timeline__header .account__header__bar .account__avatar { - border: 0; - box-shadow: var(--shadow); - background: none; - background-size: cover !important; -} -#mastodon .account-timeline__header .account__header__tabs__name { - margin-bottom: 0; - padding: 0; - margin-top: 16px; -} -#mastodon .account-timeline__header .account__header__tabs__name h1 { - display: flex; - flex-wrap: wrap; - white-space: unset; - gap: 0 0.4em; - font-weight: 600; -} -#mastodon .account-timeline__header .account__header__extra { - margin-top: 5px; -} -#mastodon .account-timeline__header .account__header__bio { - margin: 0; -} -#mastodon .account-timeline__header .account__header__bio > * { - padding-inline: 0; -} -#mastodon .account-timeline__header .account__header__bio .account__header__content { - padding: 0px; -} -#mastodon .account-timeline__header .account__header__badges { - margin-top: 10px; -} -#mastodon .account-timeline__header .account__header__badges span { - font-weight: 600; -} -#mastodon .account__header__fields, -#mastodon .account__header__account-note { - display: flex; - flex-wrap: wrap; - gap: 2px; - background: none; - border-radius: var(--radius); - overflow: hidden; - width: max-content; - max-width: 100%; - border: 0; -} -#mastodon .account__header__fields:first-child, -#mastodon .account__header__account-note:first-child { - margin-block: 5px 15px; -} -#mastodon .account__header__fields dl, -#mastodon .account__header__account-note dl { - display: inline; - border-radius: 0; - overflow: hidden; - border: 0; - padding: 8px 12px !important; - position: relative; - overflow: hidden; - flex-grow: 1; -} -#mastodon .account__header__fields dl:not(.verified), -#mastodon .account__header__account-note dl:not(.verified) { - background-color: var(--elevated-color); -} -#mastodon .account__header__fields dl dt, -#mastodon .account__header__account-note dl dt { - all: unset; - color: unset; - opacity: 0.9; -} -#mastodon .account__header__fields dl dd, -#mastodon .account__header__account-note dl dd { - padding: 0; - white-space: unset; - max-height: unset; - text-align: unset; -} -#mastodon .account__header__fields dl dd > span > a:first-child:last-child::after, -#mastodon .account__header__account-note dl dd > span > a:first-child:last-child::after, -#mastodon .account__header__fields dl dd .h-card:first-child:last-child a::after, -#mastodon .account__header__account-note dl dd .h-card:first-child:last-child a::after { - content: ""; - position: absolute; - inset: 0; - background-color: var(--hover-color); - opacity: 0; - transition: opacity 0.2s; -} -#mastodon .account__header__fields dl dd > span > a:first-child:last-child:hover:after, -#mastodon .account__header__account-note dl dd > span > a:first-child:last-child:hover:after, -#mastodon .account__header__fields dl dd .h-card:first-child:last-child a:hover:after, -#mastodon .account__header__account-note dl dd .h-card:first-child:last-child a:hover:after, -#mastodon .account__header__fields dl dd > span > a:first-child:last-child:focus:after, -#mastodon .account__header__account-note dl dd > span > a:first-child:last-child:focus:after, -#mastodon .account__header__fields dl dd .h-card:first-child:last-child a:focus:after, -#mastodon .account__header__account-note dl dd .h-card:first-child:last-child a:focus:after { - opacity: 1; -} -#mastodon .account__header__fields dl dd.verified, -#mastodon .account__header__account-note dl dd.verified { - overflow: visible !important; - border: 0; - background: none; -} -#mastodon .account__header__fields dl dd.verified a::before, -#mastodon .account__header__account-note dl dd.verified a::before, -#mastodon .account__header__fields dl dd.verified a::after, -#mastodon .account__header__account-note dl dd.verified a::after { - content: ""; - position: absolute; - inset: 0; - background: currentcolor; - opacity: 0.2; -} -#mastodon .account__header__fields dl dd.verified a::after, -#mastodon .account__header__account-note dl dd.verified a::after { - background: linear-gradient(20deg, currentcolor, transparent) !important; - opacity: 0.2 !important; - z-index: -2; -} -#mastodon .account__header__fields.account__header__account-note, -#mastodon .account__header__account-note.account__header__account-note { - display: flex; - margin-bottom: 10px !important; - gap: 0; - border: 0; - padding: 0 !important; - margin-inline: 0 !important; - background: none !important; - border-radius: 8px; - font-size: 12px; - width: unset; -} -#mastodon .account__header__fields.account__header__account-note label, -#mastodon .account__header__account-note.account__header__account-note label { - z-index: 2; - padding: 0 !important; - padding-inline-end: 0.7em !important; - pointer-events: none; - line-height: inherit; - font-size: inherit; - font-weight: inherit; - vertical-align: unset; -} -#mastodon .account__header__fields.account__header__account-note:focus-within, -#mastodon .account__header__account-note.account__header__account-note:focus-within { - padding: 0.5em 0.7em !important; - margin-inline: -5px !important; - border: 1px solid; -} -#mastodon .account__header__fields.account__header__account-note:not(:focus-within), -#mastodon .account__header__account-note.account__header__account-note:not(:focus-within) { - border-radius: 0; - border-bottom: 1px solid; - padding-bottom: 0.4em !important; -} -#mastodon .account__header__fields.account__header__account-note textarea, -#mastodon .account__header__account-note.account__header__account-note textarea { - width: 0; - flex-grow: 1; - margin: 0 !important; - border-radius: 0; - padding: 0; - margin: -100px !important; - padding: 100px !important; - padding-inline-end: 0.7em !important; - margin-inline-end: -0.7em !important; - line-height: inherit; - font-size: inherit; - font-weight: inherit; - vertical-align: unset; - background: none; -} -#mastodon .account__header__fields.account__header__account-note textarea::placeholder, -#mastodon .account__header__account-note.account__header__account-note textarea::placeholder { - font-weight: 600; -} -#mastodon .account__header__fields.account__header__account-note label, -#mastodon .account__header__account-note.account__header__account-note label { - margin: 0; - font-weight: 600; - padding-inline: 14px; -} -#mastodon :not(.explore__search-header) + .account__section-headline:not(:first-child) { - background: none; - position: relative; - z-index: 2; -} -.with-modals #mastodon :not(.explore__search-header) + .account__section-headline:not(:first-child) { - border: 0 !important; -} -#mastodon :not(.explore__search-header) + .account__section-headline:not(:first-child) a, -#mastodon :not(.explore__search-header) + .account__section-headline:not(:first-child) button { - background: none; - border-radius: 0 !important; -} -#mastodon :not(.explore__search-header) + .account__section-headline:not(:first-child) a span, -#mastodon :not(.explore__search-header) + .account__section-headline:not(:first-child) button span { - font-weight: 400; - opacity: 0.7; - transition: opacity 0.2s; -} -#mastodon :not(.explore__search-header) + .account__section-headline:not(:first-child) a.active span, -#mastodon :not(.explore__search-header) + .account__section-headline:not(:first-child) button.active span { - font-weight: 700; - opacity: 1; -} -#mastodon :not(.explore__search-header) + .account__section-headline:not(:first-child) a:hover span, -#mastodon :not(.explore__search-header) + .account__section-headline:not(:first-child) button:hover span, -#mastodon :not(.explore__search-header) + .account__section-headline:not(:first-child) a:active span, -#mastodon :not(.explore__search-header) + .account__section-headline:not(:first-child) button:active span { - opacity: 1 !important; -} -#mastodon :not(.explore__search-header) + .account__section-headline:not(:first-child) a::before, -#mastodon :not(.explore__search-header) + .account__section-headline:not(:first-child) button::before { - border-color: transparent transparent var(--border-color); -} -#mastodon .account__disclaimer { - border-top: 1px solid; -} -#mastodon .account-gallery__container { - border-radius: var(--radius); - overflow: clip; - padding: 0; - margin: 4px; - gap: 4px; - margin-bottom: calc(-40vh + 4px); -} -.account-gallery__item { - margin: 0; - flex: 1 1 calc(100px + 15%); - transition: flex 0.7s cubic-bezier(0, 0, 0, 1); - min-height: 180px !important; -} -.media-gallery__item-thumbnail { - transition: transform 0.2s; -} -.account-gallery__item:hover, -.account-gallery__item:focus-within { - flex-grow: 1.5; -} -.account-gallery__item:hover .media-gallery__item-thumbnail, -.account-gallery__item:focus-within .media-gallery__item-thumbnail { - transform: scale(1.02); -} -#mastodon .account-gallery__container > button { - width: unset; - flex-grow: 1; - flex: 1 1 calc(100px + 15% - 24px); - margin: 12px; - font-size: 1.2em; - font-weight: 700; - background: var(--elevated-color); - color: inherit; -} -#mastodon .account-gallery__container > button:hover:not(:focus) { - transform: scale(1.01); -} -@media (max-width: 890px) { - #mastodon #Follow-requests.column-header { - display: none; - } -} @media (min-width: 890px) { - #mastodon #Follow-requests ~ .scrollable .item-list { - display: grid; - align-items: stretch; - grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); - gap: 0 10px; - } - #mastodon #Follow-requests ~ .scrollable .item-list > article { - display: flex; - } -} -.layout-multiple-columns article:first-child .account-authorize__wrapper { - margin-top: 10px; -} -@media (max-width: 890px) { - #mastodon article:first-child .account-authorize__wrapper { + .layout-single-column .scrollable > [tabindex="-1"]:first-child { margin-top: 10px; } -} -#mastodon .account-authorize__wrapper { - background: var(--elevated-color); - border-radius: var(--radius); - overflow: hidden; - flex-grow: 1; - margin-bottom: 10px; - display: flex; - flex-direction: column; -} -@media (max-width: 890px) { - #mastodon .account-authorize__wrapper { - margin-inline: 10px; + .layout-single-column .item-list > article:first-of-type { + margin-top: 10px; + } + .layout-single-column .load-more, + .layout-single-column .trends__item, + .layout-single-column .focusable, + .layout-single-column .entry, + .layout-single-column .statuses-grid__item .detailed-status, + .layout-single-column .story, + .layout-single-column .account-card, + .layout-single-column .scrollable :not(.focusable) > .account:not(.account--minimal), + .layout-single-column .timeline-hint { + margin-inline: 10px !important; + max-width: calc(100% - 20px); } } -.layout-multiple-columns #mastodon .account-authorize__wrapper { - margin-inline: 10px; +.scrollable { + padding-bottom: 40vh !important; } -#mastodon .account-authorize__wrapper .account-authorize { - padding: 20px 15px 10px; -} -#mastodon .account-authorize__wrapper .detailed-status__display-name { - margin-bottom: 10px !important; -} -#mastodon .account-authorize__wrapper .account--panel { - margin-top: auto; - border-bottom: 0; - padding-inline: 15px; - gap: 10px; +.empty-column-indicator, +.error-column { background: none; } -#mastodon .account-authorize__wrapper br { - display: block; -} -#mastodon .account-authorize__wrapper p { - margin-bottom: 0.2em; -} -#mastodon .account-authorize__wrapper .account--panel__button:first-child .icon-button:not(:hover):not(:focus) { - background: var(--elevated-color); -} -#mastodon .account-authorize__wrapper .icon-button { - width: 100% !important; - padding: 10px; - height: unset !important; -} -#mastodon .account-card { - display: flex; - flex-direction: column; - max-height: 360px; - margin: 0; - border-radius: var(--radius) !important; - background: var(--elevated-color); - box-shadow: none !important; - box-shadow: var(--shadow); -} -.explore__suggestions, -.directory__list { - padding: 15px; - display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); - gap: 10px; -} -.explore__suggestions.directory__list, -.directory__list.directory__list { - padding: 15px 10px; -} -.layout-multiple-columns .explore__suggestions, -.layout-multiple-columns .directory__list { - display: block; -} -.layout-multiple-columns .explore__suggestions > *, -.layout-multiple-columns .directory__list > * { - margin: 10px !important; -} -@media (max-width: 890px) { - .explore__suggestions, - .directory__list { - gap: 0 !important; - } - .explore__suggestions > *, - .directory__list > * { - margin: 10px !important; - } -} -#mastodon .account-card .account-card__header { - padding: 0 !important; -} -#mastodon .account-card .account-card__title { - margin-bottom: 10px; - margin-top: -24px; -} -#mastodon .account-card .account-card__title__avatar { - padding-inline-end: 10px; - padding-bottom: 0; -} -#mastodon .account-card .display-name { - padding-bottom: 0; -} -#mastodon .account-card .display-name__account { - font-size: 0.9em !important; -} -#mastodon .account-card .account-card__title__avatar .account__avatar, -#mastodon .account-card .account-card__title__avatar { - width: 64px !important; - height: 64px !important; - background-size: 64px 64px !important; -} -#mastodon .account-card .account-card__title__avatar .account__avatar img, -#mastodon .account-card .account-card__title__avatar img { - width: inherit; - height: inherit; -} -#mastodon .account-card .account-card__title { - padding-inline-end: 15px; -} -#mastodon .account-card .display-name bdi { - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; -} -#mastodon .account-card .account-card__bio { - margin-top: 0 !important; - max-height: unset; - mask: linear-gradient(#000 65px, rgba(0,0,0,0.5), transparent); - -webkit-mask: linear-gradient(#000 65px, rgba(0,0,0,0.5), transparent); - flex-grow: 1; - margin-bottom: -50px; - margin-bottom: -15px; - font-size: 1em; - padding-bottom: 60px; -} -#mastodon .account-card .account-card__bio::after { - content: unset !important; -} -#mastodon .account-card .account-card__bio br { - display: block; -} -#mastodon .account-card .account-card__actions { - margin-top: auto !important; - display: unset !important; -} -#mastodon .account-card .account-card__actions .account-card__counters { - display: flex; - gap: 1em; - padding-inline: 15px; -} -#mastodon .account-card .account-card__actions .account-card__counters__item { +.dismissable-banner { display: flex; align-items: center; - font-size: 1em; -} -#mastodon .account-card .account-card__actions .account-card__counters__item > small { - display: inline !important; - margin-inline-start: 0.4em; - font-size: 1em !important; -} -#mastodon .account-card .account-card__actions .account-card__actions__button { - position: absolute; - top: 10px; - right: 10px; - padding: 0; - background: rgba(0,0,0,0.4); - border-radius: var(--radius-round); - padding: 4px; - box-shadow: 0 4px 12px rgba(0,0,0,0.2); -} -#mastodon .account-card .account-card__actions .account-card__actions__button button, -#mastodon .account-card .account-card__actions .account-card__actions__button a { - border-radius: var(--radius-round) !important; - padding: 0.7em 1.7em; - min-height: unset; - font-size: 14px !important; - line-height: 1.2; - font-size: 1em !important; -} -#mastodon .account-card .account-card__actions .account-card__actions__button:empty { - display: none; -} -#mastodon .account-card::after { - content: unset !important; -} -#mastodon .account__wrapper { - gap: 15px; - flex-wrap: wrap; -} -#mastodon .account__wrapper .account__display-name { - flex-grow: 100; -} -#mastodon .account__wrapper .account__contents { - line-height: 1.4; - flex-basis: 70%; - width: 100px; - flex-grow: 100; - display: flex; - align-items: center; - justify-content: space-between; - gap: 0 20px; - flex-wrap: wrap; -} -#mastodon .account__wrapper .account__contents * { - line-height: unset !important; -} -#mastodon .account__wrapper .account__contents .display-name { - height: unset; + flex-direction: row-reverse; + gap: 20px; margin: 0; - width: 27ch !important; - flex-grow: 1; -} -#mastodon .account__wrapper .account__contents .display-name span { - display: block; -} -#mastodon .account__wrapper .account__contents .account__details { - flex-direction: column; - width: 25ch; -} -#mastodon .account__wrapper .account__contents .account__details span { - white-space: break-spaces !important; -} -#mastodon .account__wrapper .account__contents .account__details:has(.verified-badge) > span:first-child { - display: none; -} -#mastodon .account__wrapper .account__relationship { - display: flex !important; - flex-wrap: wrap; - justify-content: flex-end; - min-width: 10ch; - gap: 10px; - flex-grow: 1; -} -#mastodon .account__wrapper .account__relationship button { - background: var(--elevated-color); - color: inherit; -} -#mastodon .scrollable > div:first-child > [tabindex="-1"]:last-child .status__wrapper::after { - content: unset; -} -.focusable, -.entry, -.statuses-grid__item .detailed-status, -.trends__item, -.story, -.account-card, -.scrollable :not(.focusable) > .account, -.timeline-hint { - overflow: hidden; - transition: background 0.2s, box-shadow 0.4s, border 0.2s; - animation: fade 0.4s backwards cubic-bezier(0, 1, 1, 1); - position: relative; - border-radius: var(--radius); -} -.focusable.trends__item, -.entry.trends__item, -.statuses-grid__item .detailed-status.trends__item, -.trends__item.trends__item, -.story.trends__item, -.account-card.trends__item, -.scrollable :not(.focusable) > .account.trends__item, -.timeline-hint.trends__item, -.focusable.story, -.entry.story, -.statuses-grid__item .detailed-status.story, -.trends__item.story, -.story.story, -.account-card.story, -.scrollable :not(.focusable) > .account.story, -.timeline-hint.story, -.focusable.account-card, -.entry.account-card, -.statuses-grid__item .detailed-status.account-card, -.trends__item.account-card, -.story.account-card, -.account-card.account-card, -.scrollable :not(.focusable) > .account.account-card, -.timeline-hint.account-card { - animation: slideUpFade backwards 0.44s cubic-bezier(0, 1, 1, 1); -} -.focusable.trends__item:nth-child(1), -.entry.trends__item:nth-child(1), -.statuses-grid__item .detailed-status.trends__item:nth-child(1), -.trends__item.trends__item:nth-child(1), -.story.trends__item:nth-child(1), -.account-card.trends__item:nth-child(1), -.scrollable :not(.focusable) > .account.trends__item:nth-child(1), -.timeline-hint.trends__item:nth-child(1), -.focusable.story:nth-child(1), -.entry.story:nth-child(1), -.statuses-grid__item .detailed-status.story:nth-child(1), -.trends__item.story:nth-child(1), -.story.story:nth-child(1), -.account-card.story:nth-child(1), -.scrollable :not(.focusable) > .account.story:nth-child(1), -.timeline-hint.story:nth-child(1), -.focusable.account-card:nth-child(1), -.entry.account-card:nth-child(1), -.statuses-grid__item .detailed-status.account-card:nth-child(1), -.trends__item.account-card:nth-child(1), -.story.account-card:nth-child(1), -.account-card.account-card:nth-child(1), -.scrollable :not(.focusable) > .account.account-card:nth-child(1), -.timeline-hint.account-card:nth-child(1) { - animation-delay: 0.04s; -} -.focusable.trends__item:nth-child(2), -.entry.trends__item:nth-child(2), -.statuses-grid__item .detailed-status.trends__item:nth-child(2), -.trends__item.trends__item:nth-child(2), -.story.trends__item:nth-child(2), -.account-card.trends__item:nth-child(2), -.scrollable :not(.focusable) > .account.trends__item:nth-child(2), -.timeline-hint.trends__item:nth-child(2), -.focusable.story:nth-child(2), -.entry.story:nth-child(2), -.statuses-grid__item .detailed-status.story:nth-child(2), -.trends__item.story:nth-child(2), -.story.story:nth-child(2), -.account-card.story:nth-child(2), -.scrollable :not(.focusable) > .account.story:nth-child(2), -.timeline-hint.story:nth-child(2), -.focusable.account-card:nth-child(2), -.entry.account-card:nth-child(2), -.statuses-grid__item .detailed-status.account-card:nth-child(2), -.trends__item.account-card:nth-child(2), -.story.account-card:nth-child(2), -.account-card.account-card:nth-child(2), -.scrollable :not(.focusable) > .account.account-card:nth-child(2), -.timeline-hint.account-card:nth-child(2) { - animation-delay: 0.08s; -} -.focusable.trends__item:nth-child(3), -.entry.trends__item:nth-child(3), -.statuses-grid__item .detailed-status.trends__item:nth-child(3), -.trends__item.trends__item:nth-child(3), -.story.trends__item:nth-child(3), -.account-card.trends__item:nth-child(3), -.scrollable :not(.focusable) > .account.trends__item:nth-child(3), -.timeline-hint.trends__item:nth-child(3), -.focusable.story:nth-child(3), -.entry.story:nth-child(3), -.statuses-grid__item .detailed-status.story:nth-child(3), -.trends__item.story:nth-child(3), -.story.story:nth-child(3), -.account-card.story:nth-child(3), -.scrollable :not(.focusable) > .account.story:nth-child(3), -.timeline-hint.story:nth-child(3), -.focusable.account-card:nth-child(3), -.entry.account-card:nth-child(3), -.statuses-grid__item .detailed-status.account-card:nth-child(3), -.trends__item.account-card:nth-child(3), -.story.account-card:nth-child(3), -.account-card.account-card:nth-child(3), -.scrollable :not(.focusable) > .account.account-card:nth-child(3), -.timeline-hint.account-card:nth-child(3) { - animation-delay: 0.12s; -} -.focusable.trends__item:nth-child(4), -.entry.trends__item:nth-child(4), -.statuses-grid__item .detailed-status.trends__item:nth-child(4), -.trends__item.trends__item:nth-child(4), -.story.trends__item:nth-child(4), -.account-card.trends__item:nth-child(4), -.scrollable :not(.focusable) > .account.trends__item:nth-child(4), -.timeline-hint.trends__item:nth-child(4), -.focusable.story:nth-child(4), -.entry.story:nth-child(4), -.statuses-grid__item .detailed-status.story:nth-child(4), -.trends__item.story:nth-child(4), -.story.story:nth-child(4), -.account-card.story:nth-child(4), -.scrollable :not(.focusable) > .account.story:nth-child(4), -.timeline-hint.story:nth-child(4), -.focusable.account-card:nth-child(4), -.entry.account-card:nth-child(4), -.statuses-grid__item .detailed-status.account-card:nth-child(4), -.trends__item.account-card:nth-child(4), -.story.account-card:nth-child(4), -.account-card.account-card:nth-child(4), -.scrollable :not(.focusable) > .account.account-card:nth-child(4), -.timeline-hint.account-card:nth-child(4) { - animation-delay: 0.16s; -} -.focusable.trends__item:nth-child(5), -.entry.trends__item:nth-child(5), -.statuses-grid__item .detailed-status.trends__item:nth-child(5), -.trends__item.trends__item:nth-child(5), -.story.trends__item:nth-child(5), -.account-card.trends__item:nth-child(5), -.scrollable :not(.focusable) > .account.trends__item:nth-child(5), -.timeline-hint.trends__item:nth-child(5), -.focusable.story:nth-child(5), -.entry.story:nth-child(5), -.statuses-grid__item .detailed-status.story:nth-child(5), -.trends__item.story:nth-child(5), -.story.story:nth-child(5), -.account-card.story:nth-child(5), -.scrollable :not(.focusable) > .account.story:nth-child(5), -.timeline-hint.story:nth-child(5), -.focusable.account-card:nth-child(5), -.entry.account-card:nth-child(5), -.statuses-grid__item .detailed-status.account-card:nth-child(5), -.trends__item.account-card:nth-child(5), -.story.account-card:nth-child(5), -.account-card.account-card:nth-child(5), -.scrollable :not(.focusable) > .account.account-card:nth-child(5), -.timeline-hint.account-card:nth-child(5) { - animation-delay: 0.2s; -} -.focusable.trends__item:nth-child(6), -.entry.trends__item:nth-child(6), -.statuses-grid__item .detailed-status.trends__item:nth-child(6), -.trends__item.trends__item:nth-child(6), -.story.trends__item:nth-child(6), -.account-card.trends__item:nth-child(6), -.scrollable :not(.focusable) > .account.trends__item:nth-child(6), -.timeline-hint.trends__item:nth-child(6), -.focusable.story:nth-child(6), -.entry.story:nth-child(6), -.statuses-grid__item .detailed-status.story:nth-child(6), -.trends__item.story:nth-child(6), -.story.story:nth-child(6), -.account-card.story:nth-child(6), -.scrollable :not(.focusable) > .account.story:nth-child(6), -.timeline-hint.story:nth-child(6), -.focusable.account-card:nth-child(6), -.entry.account-card:nth-child(6), -.statuses-grid__item .detailed-status.account-card:nth-child(6), -.trends__item.account-card:nth-child(6), -.story.account-card:nth-child(6), -.account-card.account-card:nth-child(6), -.scrollable :not(.focusable) > .account.account-card:nth-child(6), -.timeline-hint.account-card:nth-child(6) { - animation-delay: 0.24s; -} -.focusable.trends__item:nth-child(7), -.entry.trends__item:nth-child(7), -.statuses-grid__item .detailed-status.trends__item:nth-child(7), -.trends__item.trends__item:nth-child(7), -.story.trends__item:nth-child(7), -.account-card.trends__item:nth-child(7), -.scrollable :not(.focusable) > .account.trends__item:nth-child(7), -.timeline-hint.trends__item:nth-child(7), -.focusable.story:nth-child(7), -.entry.story:nth-child(7), -.statuses-grid__item .detailed-status.story:nth-child(7), -.trends__item.story:nth-child(7), -.story.story:nth-child(7), -.account-card.story:nth-child(7), -.scrollable :not(.focusable) > .account.story:nth-child(7), -.timeline-hint.story:nth-child(7), -.focusable.account-card:nth-child(7), -.entry.account-card:nth-child(7), -.statuses-grid__item .detailed-status.account-card:nth-child(7), -.trends__item.account-card:nth-child(7), -.story.account-card:nth-child(7), -.account-card.account-card:nth-child(7), -.scrollable :not(.focusable) > .account.account-card:nth-child(7), -.timeline-hint.account-card:nth-child(7) { - animation-delay: 0.28s; -} -.focusable.trends__item:nth-child(8), -.entry.trends__item:nth-child(8), -.statuses-grid__item .detailed-status.trends__item:nth-child(8), -.trends__item.trends__item:nth-child(8), -.story.trends__item:nth-child(8), -.account-card.trends__item:nth-child(8), -.scrollable :not(.focusable) > .account.trends__item:nth-child(8), -.timeline-hint.trends__item:nth-child(8), -.focusable.story:nth-child(8), -.entry.story:nth-child(8), -.statuses-grid__item .detailed-status.story:nth-child(8), -.trends__item.story:nth-child(8), -.story.story:nth-child(8), -.account-card.story:nth-child(8), -.scrollable :not(.focusable) > .account.story:nth-child(8), -.timeline-hint.story:nth-child(8), -.focusable.account-card:nth-child(8), -.entry.account-card:nth-child(8), -.statuses-grid__item .detailed-status.account-card:nth-child(8), -.trends__item.account-card:nth-child(8), -.story.account-card:nth-child(8), -.account-card.account-card:nth-child(8), -.scrollable :not(.focusable) > .account.account-card:nth-child(8), -.timeline-hint.account-card:nth-child(8) { - animation-delay: 0.32s; -} -.focusable.trends__item:nth-child(9), -.entry.trends__item:nth-child(9), -.statuses-grid__item .detailed-status.trends__item:nth-child(9), -.trends__item.trends__item:nth-child(9), -.story.trends__item:nth-child(9), -.account-card.trends__item:nth-child(9), -.scrollable :not(.focusable) > .account.trends__item:nth-child(9), -.timeline-hint.trends__item:nth-child(9), -.focusable.story:nth-child(9), -.entry.story:nth-child(9), -.statuses-grid__item .detailed-status.story:nth-child(9), -.trends__item.story:nth-child(9), -.story.story:nth-child(9), -.account-card.story:nth-child(9), -.scrollable :not(.focusable) > .account.story:nth-child(9), -.timeline-hint.story:nth-child(9), -.focusable.account-card:nth-child(9), -.entry.account-card:nth-child(9), -.statuses-grid__item .detailed-status.account-card:nth-child(9), -.trends__item.account-card:nth-child(9), -.story.account-card:nth-child(9), -.account-card.account-card:nth-child(9), -.scrollable :not(.focusable) > .account.account-card:nth-child(9), -.timeline-hint.account-card:nth-child(9) { - animation-delay: 0.36s; -} -.focusable.trends__item:nth-child(10), -.entry.trends__item:nth-child(10), -.statuses-grid__item .detailed-status.trends__item:nth-child(10), -.trends__item.trends__item:nth-child(10), -.story.trends__item:nth-child(10), -.account-card.trends__item:nth-child(10), -.scrollable :not(.focusable) > .account.trends__item:nth-child(10), -.timeline-hint.trends__item:nth-child(10), -.focusable.story:nth-child(10), -.entry.story:nth-child(10), -.statuses-grid__item .detailed-status.story:nth-child(10), -.trends__item.story:nth-child(10), -.story.story:nth-child(10), -.account-card.story:nth-child(10), -.scrollable :not(.focusable) > .account.story:nth-child(10), -.timeline-hint.story:nth-child(10), -.focusable.account-card:nth-child(10), -.entry.account-card:nth-child(10), -.statuses-grid__item .detailed-status.account-card:nth-child(10), -.trends__item.account-card:nth-child(10), -.story.account-card:nth-child(10), -.account-card.account-card:nth-child(10), -.scrollable :not(.focusable) > .account.account-card:nth-child(10), -.timeline-hint.account-card:nth-child(10) { - animation-delay: 0.4s; -} -.focusable.trends__item:nth-child(11), -.entry.trends__item:nth-child(11), -.statuses-grid__item .detailed-status.trends__item:nth-child(11), -.trends__item.trends__item:nth-child(11), -.story.trends__item:nth-child(11), -.account-card.trends__item:nth-child(11), -.scrollable :not(.focusable) > .account.trends__item:nth-child(11), -.timeline-hint.trends__item:nth-child(11), -.focusable.story:nth-child(11), -.entry.story:nth-child(11), -.statuses-grid__item .detailed-status.story:nth-child(11), -.trends__item.story:nth-child(11), -.story.story:nth-child(11), -.account-card.story:nth-child(11), -.scrollable :not(.focusable) > .account.story:nth-child(11), -.timeline-hint.story:nth-child(11), -.focusable.account-card:nth-child(11), -.entry.account-card:nth-child(11), -.statuses-grid__item .detailed-status.account-card:nth-child(11), -.trends__item.account-card:nth-child(11), -.story.account-card:nth-child(11), -.account-card.account-card:nth-child(11), -.scrollable :not(.focusable) > .account.account-card:nth-child(11), -.timeline-hint.account-card:nth-child(11) { - animation-delay: 0.44s; -} -.focusable.focusable, -.entry.focusable, -.statuses-grid__item .detailed-status.focusable, -.trends__item.focusable, -.story.focusable, -.account-card.focusable, -.scrollable :not(.focusable) > .account.focusable, -.timeline-hint.focusable { - background: none; -} -.focusable.entry, -.entry.entry, -.statuses-grid__item .detailed-status.entry, -.trends__item.entry, -.story.entry, -.account-card.entry, -.scrollable :not(.focusable) > .account.entry, -.timeline-hint.entry { - margin-bottom: 10px; -} -.focusable:not(.detailed-status__wrapper)::before, -.entry:not(.detailed-status__wrapper)::before, -.statuses-grid__item .detailed-status:not(.detailed-status__wrapper)::before, -.trends__item:not(.detailed-status__wrapper)::before, -.story:not(.detailed-status__wrapper)::before, -.account-card:not(.detailed-status__wrapper)::before, -.scrollable :not(.focusable) > .account:not(.detailed-status__wrapper)::before, -.timeline-hint:not(.detailed-status__wrapper)::before { - content: ""; - position: absolute; - inset: 0px !important; - height: unset !important; - width: unset !important; - border-radius: var(--radius); - pointer-events: none; - transition: background-color 0.2s; -} -.focusable:not(.detailed-status__wrapper).unread::before, -.entry:not(.detailed-status__wrapper).unread::before, -.statuses-grid__item .detailed-status:not(.detailed-status__wrapper).unread::before, -.trends__item:not(.detailed-status__wrapper).unread::before, -.story:not(.detailed-status__wrapper).unread::before, -.account-card:not(.detailed-status__wrapper).unread::before, -.scrollable :not(.focusable) > .account:not(.detailed-status__wrapper).unread::before, -.timeline-hint:not(.detailed-status__wrapper).unread::before { - border-start-start-radius: 0 !important; - border-end-start-radius: 0 !important; -} -.focusable:not(.detailed-status__wrapper):hover::before, -.entry:not(.detailed-status__wrapper):hover::before, -.statuses-grid__item .detailed-status:not(.detailed-status__wrapper):hover::before, -.trends__item:not(.detailed-status__wrapper):hover::before, -.story:not(.detailed-status__wrapper):hover::before, -.account-card:not(.detailed-status__wrapper):hover::before, -.scrollable :not(.focusable) > .account:not(.detailed-status__wrapper):hover::before, -.timeline-hint:not(.detailed-status__wrapper):hover::before, -.focusable:not(.detailed-status__wrapper):focus-within::before, -.entry:not(.detailed-status__wrapper):focus-within::before, -.statuses-grid__item .detailed-status:not(.detailed-status__wrapper):focus-within::before, -.trends__item:not(.detailed-status__wrapper):focus-within::before, -.story:not(.detailed-status__wrapper):focus-within::before, -.account-card:not(.detailed-status__wrapper):focus-within::before, -.scrollable :not(.focusable) > .account:not(.detailed-status__wrapper):focus-within::before, -.timeline-hint:not(.detailed-status__wrapper):focus-within::before { - background-color: var(--hover-color); -} -.focusable:not(.detailed-status__wrapper):not(.status__wrapper), -.entry:not(.detailed-status__wrapper):not(.status__wrapper), -.statuses-grid__item .detailed-status:not(.detailed-status__wrapper):not(.status__wrapper), -.trends__item:not(.detailed-status__wrapper):not(.status__wrapper), -.story:not(.detailed-status__wrapper):not(.status__wrapper), -.account-card:not(.detailed-status__wrapper):not(.status__wrapper), -.scrollable :not(.focusable) > .account:not(.detailed-status__wrapper):not(.status__wrapper), -.timeline-hint:not(.detailed-status__wrapper):not(.status__wrapper) { - border-radius: var(--radius); + border-radius: 0; border: 0; + padding: 25px; } -.focusable:not(.detailed-status__wrapper):not(.status__wrapper):not(:last-child:not(:only-child))::after, -.entry:not(.detailed-status__wrapper):not(.status__wrapper):not(:last-child:not(:only-child))::after, -.statuses-grid__item .detailed-status:not(.detailed-status__wrapper):not(.status__wrapper):not(:last-child:not(:only-child))::after, -.trends__item:not(.detailed-status__wrapper):not(.status__wrapper):not(:last-child:not(:only-child))::after, -.story:not(.detailed-status__wrapper):not(.status__wrapper):not(:last-child:not(:only-child))::after, -.account-card:not(.detailed-status__wrapper):not(.status__wrapper):not(:last-child:not(:only-child))::after, -.scrollable :not(.focusable) > .account:not(.detailed-status__wrapper):not(.status__wrapper):not(:last-child:not(:only-child))::after, -.timeline-hint:not(.detailed-status__wrapper):not(.status__wrapper):not(:last-child:not(:only-child))::after { - content: ""; - position: absolute; - bottom: 0px; - inset-inline: var(--radius); - border-top: 1px solid var(--border-color); - pointer-events: none; +.dismissable-banner > div { + padding: 0; } -.status__wrapper-reply.status--in-thread::after { - top: 0; +.dismissable-banner button { + padding: 16px; + margin: -16px -14px; } -.status--in-thread.status__wrapper-reply:not(.status--first-in-thread)::after, -.status--in-thread:not(.status__wrapper-reply)::after { - border-top: 0 !important; -} -.explore__links { - padding: 10px !important; - display: flex; - flex-wrap: wrap; -} -.explore__links .trends__item { - margin: 7.5px; - padding: 25px !important; - box-shadow: var(--shadow-med); - width: 200px; - background: var(--elevated-color); -} -.explore__links .trends__item::after { - content: unset !important; - inset: 0 !important; - border-radius: var(--radius); - pointer-events: none; - border: 1px solid var(--border-color) !important; -} -.explore__links .trends__item a { - font-size: 1.4em; - line-height: 1.7em; -} -.explore__links .trends__item .trends__item__sparkline { - height: 100%; -} -.explore__links .trends__item .trends__item__sparkline svg { - height: 100%; - float: right; - overflow: visible !important; - position: relative; -} -.explore__links .trends__item .trends__item__sparkline svg path { - display: unset !important; - transition: transform 1s; -} -.explore__links .trends__item .trends__item__sparkline svg path:first-child { - transform-origin: center; -} -.explore__links .trends__item:hover svg path:first-child, -.explore__links .trends__item:focus-within svg path:first-child { - transform: scale(2); -} -.focusable.trends__item, -.entry.trends__item, -.statuses-grid__item .detailed-status.trends__item, -.trends__item.trends__item, -.story.trends__item, -.account-card.trends__item, -.scrollable :not(.focusable) > .account.trends__item, -.timeline-hint.trends__item, -.focusable.story, -.entry.story, -.statuses-grid__item .detailed-status.story, -.trends__item.story, -.story.story, -.account-card.story, -.scrollable :not(.focusable) > .account.story, -.timeline-hint.story { - padding: 10px; - flex-grow: 1; -} -.focusable.story, -.entry.story, -.statuses-grid__item .detailed-status.story, -.trends__item.story, -.story.story, -.account-card.story, -.scrollable :not(.focusable) > .account.story, -.timeline-hint.story { - padding: 15px; -} -.focusable.story .story__details, -.entry.story .story__details, -.statuses-grid__item .detailed-status.story .story__details, -.trends__item.story .story__details, -.story.story .story__details, -.account-card.story .story__details, -.scrollable :not(.focusable) > .account.story .story__details, -.timeline-hint.story .story__details { - padding-inline-start: 0 !important; -} -.focusable.story .story__thumbnail, -.entry.story .story__thumbnail, -.statuses-grid__item .detailed-status.story .story__thumbnail, -.trends__item.story .story__thumbnail, -.story.story .story__thumbnail, -.account-card.story .story__thumbnail, -.scrollable :not(.focusable) > .account.story .story__thumbnail, -.timeline-hint.story .story__thumbnail { - margin-inline-end: 0; - border-radius: var(--radius); - overflow: hidden; -} -.focusable.empty-column-indicator, -.entry.empty-column-indicator, -.statuses-grid__item .detailed-status.empty-column-indicator, -.trends__item.empty-column-indicator, -.story.empty-column-indicator, -.account-card.empty-column-indicator, -.scrollable :not(.focusable) > .account.empty-column-indicator, -.timeline-hint.empty-column-indicator { - display: block; -} -.focusable.timeline-hint, -.entry.timeline-hint, -.statuses-grid__item .detailed-status.timeline-hint, -.trends__item.timeline-hint, -.story.timeline-hint, -.account-card.timeline-hint, -.scrollable :not(.focusable) > .account.timeline-hint, -.timeline-hint.timeline-hint { - display: block; -} -.focusable.timeline-hint a::before, -.entry.timeline-hint a::before, -.statuses-grid__item .detailed-status.timeline-hint a::before, -.trends__item.timeline-hint a::before, -.story.timeline-hint a::before, -.account-card.timeline-hint a::before, -.scrollable :not(.focusable) > .account.timeline-hint a::before, -.timeline-hint.timeline-hint a::before { - content: ""; - position: absolute; - inset: 0; -} -#mastodon .status__wrapper { - background: none; -} -#mastodon .status, -#mastodon .scrollable .account { - padding-block: 15px; -} -#mastodon .status::before, -#mastodon .scrollable .account::before { - inset: -10px; - border-radius: var(--radius); -} -#mastodon .status.light { - overflow: hidden !important; -} -#mastodon .status.light .boost-modal__status-header { - display: flow-root !important; -} -#mastodon .account__relationship:empty { - display: none; -} -#mastodon .status__prepend { - white-space: nowrap; -} -#mastodon .status__prepend > span { - display: flex; - flex-grow: 1; - gap: 0.3em; - align-items: center; -} -#mastodon .status__prepend > span > a { - overflow: hidden; - text-overflow: ellipsis; -} -#mastodon .status__prepend + .status:not(.status-direct) { - padding-top: 0; -} -#mastodon .notification .status .status__info { - margin-top: 0px !important; -} -#mastodon .notification .status .status__content ~ .media-gallery, -#mastodon .notification .status .status__content ~ [style*="aspect-ratio:"] { - height: 60px; - width: 100px; - margin: 0 !important; - opacity: 0.5; - overflow: hidden; - border-radius: var(--radius); -} -#mastodon .notification .status .status__content ~ [style*="aspect-ratio:"] .video-player__controls { - display: none; -} -#mastodon .status__prepend, -#mastodon .notification__message { - display: flex; - padding-top: 15px !important; - padding-bottom: 0 !important; - margin: 0 !important; - z-index: 2; - position: relative; -} -#mastodon .status__prepend [class*="icon-wrapper"], -#mastodon .notification__message [class*="icon-wrapper"] { - display: flex; - align-items: center; -} -#mastodon .status__prepend a, -#mastodon .notification__message a { - white-space: nowrap; - font-weight: 700; - text-overflow: ellipsis; - overflow: hidden !important; -} -#mastodon .status__prepend bdi, -#mastodon .notification__message bdi { - text-overflow: ellipsis; - overflow: hidden; - max-width: 100%; -} -#mastodon .notification__message > span, -#mastodon .notification__message > span > span { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 0em 0.4em; - line-height: 1.4; - max-width: 100%; -} -#mastodon .notification-favourite .notification__message, -#mastodon .notification-reblog .notification__message { - margin-bottom: -10px !important; -} -#mastodon .notification-favourite .notification__message ~ div .status__info, -#mastodon .notification-reblog .notification__message ~ div .status__info, -#mastodon .notification-favourite .notification__message ~ div .status__action-bar, -#mastodon .notification-reblog .notification__message ~ div .status__action-bar { - display: none; -} -#mastodon .notification-favourite .notification__message ~ div .status, -#mastodon .notification-reblog .notification__message ~ div .status { - min-height: unset; -} -#mastodon .notification-favourite .notification__message ~ div .attachment-list, -#mastodon .notification-reblog .notification__message ~ div .attachment-list { - margin-top: 0; -} -#mastodon .notification-favourite .notification__message ~ div .status__content__text.status__content__text, -#mastodon .notification-reblog .notification__message ~ div .status__content__text.status__content__text { - max-height: 80px !important; - mask: linear-gradient(#000 60px, transparent); - -webkit-mask: linear-gradient(#000 60px, transparent); -} -#mastodon .notification-favourite .notification__message ~ div .status__content__text.status__content__text p, -#mastodon .notification-reblog .notification__message ~ div .status__content__text.status__content__text p { - margin: 0; -} -#mastodon .notification-favourite .notification__message ~ div .attachment-list__list, -#mastodon .notification-reblog .notification__message ~ div .attachment-list__list { - display: flex; - flex-direction: row; - justify-content: flex-start; - gap: 0 1em; - margin-top: 4px; - z-index: 2; - pointer-events: none; - max-width: 100%; -} -#mastodon .notification-favourite .notification__message ~ div .attachment-list__list li, -#mastodon .notification-reblog .notification__message ~ div .attachment-list__list li { - display: contents; -} -#mastodon .notification-favourite .notification__message ~ div .attachment-list__list a, -#mastodon .notification-reblog .notification__message ~ div .attachment-list__list a { - pointer-events: all; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; -} -#mastodon .status__line:not(.status__line--full) { - height: 20px; - filter: contrast(2); -} -#mastodon .status__line::before { - top: 20px; - height: 48px; -} -#mastodon .status__avatar { - min-width: 45px; -} -#mastodon .account__avatar-overlay-base { - width: 100%; - height: 100%; - background-size: cover; - border-radius: var(--radius); -} -#mastodon .account__avatar-overlay-base .account__avatar { - width: 90% !important; - height: 90% !important; -} -#mastodon .account__avatar-overlay-overlay { - border-radius: var(--radius-round); - overflow: hidden; -} -#mastodon .status__info { - margin-block: 5px 0; - padding: 0 !important; - align-items: flex-start; -} -#mastodon .status__info > * { - position: relative; - z-index: 2; -} -#mastodon .status__info .display-name { - color: unset !important; -} -#mastodon .status__info .display-name strong { - font-weight: 600; - overflow: hidden; - text-overflow: ellipsis; -} -#mastodon .status__info b { - unicode-bidi: isolate; -} -#mastodon .status__info .status__relative-time { - display: flex; - font-weight: 500; - font-size: 15px; -} -#mastodon .status__info .status__relative-time .status__visibility-icon { - order: 2; - margin-inline-start: 0.4em; -} -#mastodon .status__info .status__relative-time abbr { - margin-inline-start: 0.7em; -} -#mastodon .status__info .status__relative-time abbr::after { - content: "๏€"; - font: normal normal normal 14px/1 FontAwesome; -} -#mastodon .status__content { - padding-top: 2px; - text-align: unset !important; - line-height: 1.5; - margin-top: 10px; -} -#mastodon .status__content.status__content--with-spoiler { - overflow: visible; -} -#mastodon .status__content.status__content--with-spoiler > p { - margin-inline: -100px; - padding-inline: 100px; - overflow: hidden; -} -#mastodon .status__content.status__content--with-spoiler > p:first-child { - margin-bottom: 0; -} -#mastodon .status__content p:empty { - max-height: 0; -} -#mastodon .status__content .custom-emoji { - height: 2em; - min-width: 2em; - width: auto; -} -.custom-emoji { - transition: transform 1s cubic-bezier(0, 0.7, 0, 1); -} -.custom-emoji:hover { - transform: scale(1.7); - transition: transform 0.4s cubic-bezier(0, 0.7, 0, 1); -} -#mastodon .status__content ~ [style*="aspect-ratio"] { - max-height: 80vh; -} -#mastodon .status > .status__content .status__content__text:empty { - margin-top: -5px !important; -} -#mastodon .status__content__spoiler-link { - display: flex; - align-items: center; - position: relative; - padding: 0.4em 1.2em; - border-radius: var(--radius-round) !important; - color: inherit; - background: var(--elevated-color); - margin-block: 8px; -} -#mastodon .status__content__spoiler-link::before, -#mastodon .status__content__spoiler-link::after { - content: ""; - position: absolute; - inset: 0; - border-radius: var(--radius-round); - background-color: var(--hover-color); - opacity: 0; - transition: opacity 0.2s; -} -#mastodon .status__content__spoiler-link::after { - inset: -6px -9999px; -} -#mastodon .status__content__spoiler-link:hover::before, -#mastodon .status__content__spoiler-link:focus::before, -#mastodon .status__content__spoiler-link:hover::after, -#mastodon .status__content__spoiler-link:focus::after { - opacity: 1; -} -#mastodon .detailed-status__wrapper-direct .status__content, -#mastodon .status-direct .status__content, -#mastodon .status__wrapper-direct .status__content, -#mastodon .conversation .status__content { - position: relative !important; - background: var(--elevated-color); - padding: 8px 12px; - padding: 0.7em 0.9em !important; - border-radius: var(--radius-round); - border-top-left-radius: 6px; - box-sizing: border-box; - margin-top: 5px !important; - margin-bottom: 0; - overflow: hidden !important; - max-width: max-content; -} -#mastodon .detailed-status__wrapper-direct .status__content .media-gallery, -#mastodon .status-direct .status__content .media-gallery, -#mastodon .status__wrapper-direct .status__content .media-gallery, -#mastodon .conversation .status__content .media-gallery { - width: 999px; - max-width: 100% !important; -} -.detailed-status__wrapper-direct .status__content { - font-size: 17px; -} -#mastodon .status__wrapper-direct:not(.detailed-status__wrapper-direct) .status__prepend { - position: absolute; - font-size: 0; - opacity: 0; -} -#mastodon .status-direct .icon-at, -#mastodon .status-direct .status__visibility-icon { - color: var(--accent, #8c8dff); -} -#mastodon .status-direct .status__info .status__relative-time { - height: auto; - color: var(--accent, #8c8dff); -} -#mastodon .status-direct.status--in-thread .status__info { - align-items: center; - gap: 10px; -} -#mastodon .status-direct.status--in-thread .status__info > span { - width: 0; - flex-grow: 1; -} -#mastodon .status-direct.status--in-thread .status__info> span, -#mastodon .status-direct.status--in-thread .status__display-name { - overflow: visible !important; -} -#mastodon .status-direct.status--in-thread .status__display-name { - overflow: hidden; - width: 0; - flex-grow: 1; -} -#mastodon .status-direct.status--in-thread .status__avatar { - height: auto; - margin-bottom: -100px; -} -#mastodon .status-direct.status--in-thread .status__avatar .account__avatar { - position: absolute; - top: 0 !important; - height: 46px !important; - width: 46px !important; -} -#mastodon .status-direct.status--in-thread .display-name * { - display: inline; - margin-right: 0.2em; -} -#mastodon .media-gallery, -#mastodon .video-player, -#mastodon .status-card.horizontal.interactive, -#mastodon .status-card, -#mastodon .audio-player, -#mastodon .picture-in-picture-placeholder { - box-shadow: var(--shadow-low); - border-radius: var(--radius); - margin-top: 10px !important; - margin-bottom: 10px !important; - animation: scaleIn 0.4s; - max-width: unset !important; -} -#mastodon .status .media-gallery__item { - max-height: 80vh; -} -.status-card { - line-height: 1; -} -.status-card:not(.horizontal) { - border: 1px solid var(--border-color) !important; -} -.status-card__content { - padding: 12px !important; - margin-block: auto; -} -.status-card .status-card__image { - border-radius: 0; - width: 90px; - min-height: 100%; -} -.status-card .status-card__image img { - border-radius: 0; - height: 100%; -} -.status-card.compact:not(.interactive) .status-card__image { - position: relative; - aspect-ratio: unset !important; -} -.status-card.compact:not(.interactive) .status-card__image img { - position: absolute; - inset: 0; -} -.status-card__host { - font-size: 0.85em; - line-height: 1.5; - margin: 0; -} -.status-card__title { - font-size: 1em; - margin-top: 0.2em; - margin-bottom: 0; - line-height: 1.4; -} -.status-card__description { - line-height: 1.4 !important; - margin: 0 !important; -} -.status-card__author { - margin-top: 0.4em; - font-size: 0.85em; -} -.status-card:hover { - background-color: var(--hover-color); -} -.audio-player .video-player__seek { - margin: var(--radius); -} -#mastodon .hashtag-bar { - margin-top: 10px; -} -#mastodon .hashtag-bar a, -#mastodon .hashtag-bar button { - font-size: 0.9em; - padding: 0.2em 0.6em; - color: inherit; - opacity: 0.9; - color: var(--accent, #8c8dff); - transition: opacity 0.2s; -} -#mastodon .hashtag-bar a { - position: relative; - border-radius: var(--radius); - background: var(--elevated-color); -} -#mastodon .hashtag-bar a::after { - content: ""; - position: absolute; - inset: 0; - background: var(--elevated-color); - border-radius: inherit; - opacity: 0; - transition: opacity 0.2s; -} -#mastodon .hashtag-bar a:hover, -#mastodon .hashtag-bar a:focus { - opacity: 1; -} -#mastodon .hashtag-bar a:hover::after, -#mastodon .hashtag-bar a:focus::after { - opacity: 1; -} -#mastodon .hashtag-bar button { - padding-block: 0; -} -#mastodon .detailed-status__wrapper { - border-radius: var(--radius); - overflow: clip; -} -#mastodon .detailed-status { - border: 0 !important; - padding: 15px !important; - padding-bottom: 5px !important; -} -#mastodon .detailed-status .status__prepend { - padding-top: 0 !important; - margin-bottom: 1em !important; -} -#mastodon .detailed-status .detailed-status__display-name { - margin-bottom: 10px; -} -#mastodon div:empty + div > .detailed-status__wrapper { - margin-top: 0 !important; -} -#mastodon .detailed-status__wrapper, -#mastodon .detailed-status, -#mastodon .picture-in-picture { - box-shadow: var(--shadow); -} -#mastodon .detailed-status__wrapper .media-gallery, -#mastodon .detailed-status .media-gallery, -#mastodon .picture-in-picture .media-gallery, -#mastodon .detailed-status__wrapper .video-player, -#mastodon .detailed-status .video-player, -#mastodon .picture-in-picture .video-player, -#mastodon .detailed-status__wrapper .status-card.horizontal.interactive, -#mastodon .detailed-status .status-card.horizontal.interactive, -#mastodon .picture-in-picture .status-card.horizontal.interactive, -#mastodon .detailed-status__wrapper .status-card, -#mastodon .detailed-status .status-card, -#mastodon .picture-in-picture .status-card, -#mastodon .detailed-status__wrapper .audio-player, -#mastodon .detailed-status .audio-player, -#mastodon .picture-in-picture .audio-player, -#mastodon .detailed-status__wrapper .picture-in-picture-placeholder, -#mastodon .detailed-status .picture-in-picture-placeholder, -#mastodon .picture-in-picture .picture-in-picture-placeholder { - margin-inline: 0 !important; - max-height: unset !important; -} -#mastodon .detailed-status__wrapper .status__content, -#mastodon .detailed-status .status__content, -#mastodon .picture-in-picture .status__content { - min-height: unset !important; -} -#mastodon .detailed-status__wrapper { - isolation: isolate; -} -#mastodon .detailed-status__wrapper::before { - content: ""; - position: absolute; - inset: 0; - background: var(--elevated-tint); - pointer-events: none; - z-index: -1; -} -#mastodon .detailed-status__wrapper .detailed-status { - box-shadow: none; -} -#mastodon .picture-in-picture { - z-index: 101; -} -#mastodon .picture-in-picture .picture-in-picture__header { - border-radius: var(--radius) var(--radius) 0 0; -} -#mastodon .picture-in-picture .media-gallery, -#mastodon .picture-in-picture .video-player, -#mastodon .picture-in-picture .status-card.horizontal.interactive, -#mastodon .picture-in-picture .status-card, -#mastodon .picture-in-picture .audio-player, -#mastodon .picture-in-picture .picture-in-picture-placeholder { - --radius: 0; - margin: 0 !important; -} -#mastodon .picture-in-picture .picture-in-picture__footer { - border-radius: 0 0 var(--radius) var(--radius); -} -#mastodon .status__action-bar { - margin-top: 0.4em; - margin-bottom: -8px; -} -#mastodon .status__action-bar .icon-button { - padding: 0.25em 0.25em !important; - margin: 0; -} -#mastodon .status__action-bar .icon-button::before { - content: ""; - position: absolute; - inset: -0.5em; -} -#mastodon .status__action-bar, -#mastodon .detailed-status__action-bar, -#mastodon .picture-in-picture__footer { - position: relative; - z-index: 2; - pointer-events: none; - gap: 0 18px; - justify-content: unset; -} -#mastodon .status__action-bar :not(i), -#mastodon .detailed-status__action-bar :not(i), -#mastodon .picture-in-picture__footer :not(i) { - pointer-events: all; -} -#mastodon .status__action-bar > div, -#mastodon .detailed-status__action-bar > div, -#mastodon .picture-in-picture__footer > div { - all: unset; -} -#mastodon .status__action-bar .icon-button, -#mastodon .detailed-status__action-bar .icon-button, -#mastodon .picture-in-picture__footer .icon-button { - display: inline-flex; - align-items: center; - justify-content: center; - width: unset !important; - padding: 0.5em !important; - height: unset !important; - border-radius: var(--radius); - position: relative; -} -#mastodon .status__action-bar .icon-button:last-child, -#mastodon .detailed-status__action-bar .icon-button:last-child, -#mastodon .picture-in-picture__footer .icon-button:last-child { - margin: 0 !important; -} -#mastodon .status__action-bar .icon-button .icon-button__counter, -#mastodon .detailed-status__action-bar .icon-button .icon-button__counter, -#mastodon .picture-in-picture__footer .icon-button .icon-button__counter { - width: auto !important; -} -#mastodon .status__action-bar.detailed-status__action-bar, -#mastodon .detailed-status__action-bar.detailed-status__action-bar, -#mastodon .picture-in-picture__footer.detailed-status__action-bar, -#mastodon .status__action-bar.picture-in-picture__footer, -#mastodon .detailed-status__action-bar.picture-in-picture__footer, -#mastodon .picture-in-picture__footer.picture-in-picture__footer { - padding-inline: 15px !important; - gap: 0; -} -#mastodon .status__action-bar.detailed-status__action-bar .icon-button, -#mastodon .detailed-status__action-bar.detailed-status__action-bar .icon-button, -#mastodon .picture-in-picture__footer.detailed-status__action-bar .icon-button, -#mastodon .status__action-bar.picture-in-picture__footer .icon-button, -#mastodon .detailed-status__action-bar.picture-in-picture__footer .icon-button, -#mastodon .picture-in-picture__footer.picture-in-picture__footer .icon-button { - flex-grow: 1 !important; -} -#mastodon .status__action-bar.detailed-status__action-bar div, -#mastodon .detailed-status__action-bar.detailed-status__action-bar div, -#mastodon .picture-in-picture__footer.detailed-status__action-bar div, -#mastodon .status__action-bar.picture-in-picture__footer div, -#mastodon .detailed-status__action-bar.picture-in-picture__footer div, -#mastodon .picture-in-picture__footer.picture-in-picture__footer div, -#mastodon .status__action-bar.detailed-status__action-bar > div > span, -#mastodon .detailed-status__action-bar.detailed-status__action-bar > div > span, -#mastodon .picture-in-picture__footer.detailed-status__action-bar > div > span, -#mastodon .status__action-bar.picture-in-picture__footer > div > span, -#mastodon .detailed-status__action-bar.picture-in-picture__footer > div > span, -#mastodon .picture-in-picture__footer.picture-in-picture__footer > div > span { - display: flex; - justify-content: center; - flex-grow: 1; -} -#mastodon .status__action-bar.picture-in-picture__footer .icon-button::after, -#mastodon .detailed-status__action-bar.picture-in-picture__footer .icon-button::after, -#mastodon .picture-in-picture__footer.picture-in-picture__footer .icon-button::after { - content: unset !important; -} -.layout-single-column .tabs-bar__wrapper, -.layout-single-column .column-back-button--slim .column-back-button { +.tabs-bar__wrapper { grid-column: 2; border: 0 !important; + padding-top: 0; transition: margin 0.2s cubic-bezier(0, 0, 0, 1.1), top 0.4s; } -#mastodon .column-header, -#mastodon .column-inline-form { +@media (min-width: 890px) { + .tabs-bar__wrapper { + margin-top: -100vh; + } +} +.column-header, +.column-inline-form { font-weight: 600; border-bottom-left-radius: 0 !important; border-bottom-right-radius: 0 !important; } -#mastodon .column-header ~ .scrollable, -#mastodon .column-inline-form ~ .scrollable { +.column-header ~ .scrollable, +.column-inline-form ~ .scrollable { border-top-left-radius: 0 !important; border-top-right-radius: 0 !important; } -.layout-single-column .tabs-bar__wrapper .announcements, -.layout-single-column .column-back-button--slim .column-back-button .announcements, -.layout-single-column .tabs-bar__wrapper .column-header__collapsible:not(.collapsed), -.layout-single-column .column-back-button--slim .column-back-button .column-header__collapsible:not(.collapsed) { +.column-header__title { + display: inline; +} +.column-header__title svg { + vertical-align: -0.4em; +} +.announcements, +.column-header__collapsible:not(.collapsed) { flex-direction: column-reverse; align-items: flex-start; border: 0; - animation: slideDowFade 0.3s backwards cubic-bezier(0, 1, 0, 1.2); + animation: slideDownFade 0.3s backwards cubic-bezier(0, 1, 0, 1.2); } -.layout-single-column .tabs-bar__wrapper .column-header__collapsible, -.layout-single-column .column-back-button--slim .column-back-button .column-header__collapsible { +.column-header__collapsible { transition: none; - background: var(--modal-background-color); - backdrop-filter: var(--background-filter); + background: var(--surface-background-color); + overflow-y: auto !important; } -.layout-single-column .tabs-bar__wrapper .collapsed, -.layout-single-column .column-back-button--slim .column-back-button .collapsed { +.tabs-bar__wrapper .collapsed { display: none; } -.layout-single-column .tabs-bar__wrapper .announcements__container, -.layout-single-column .column-back-button--slim .column-back-button .announcements__container { +.announcements { + background: var(--surface-background-color); +} +.announcements__container { width: 100% !important; } -.layout-single-column .tabs-bar__wrapper .announcements__mastodon, -.layout-single-column .column-back-button--slim .column-back-button .announcements__mastodon { - display: block; +.announcements__mastodon { + display: block !important; z-index: -1; position: relative; } -.layout-single-column .tabs-bar__wrapper .announcements__pagination, -.layout-single-column .column-back-button--slim .column-back-button .announcements__pagination { +.announcements__pagination { bottom: unset; padding-block: 0; + display: flex; + align-items: center; +} +.column-header__wrapper > :not(.column-header):not(.collapsed) { + border-top: 2px solid var(--background-color) !important; +} +.column-header { + overflow: hidden; +} +.column-header > button { + z-index: 2; +} +.column-header__buttons { + isolation: isolate; +} +.column-header__buttons button { + transition: background 0.2s, transform 0.3s !important; + position: relative; + border-radius: 100px !important; + min-width: 40px; + margin: 5px; + margin-inline-start: 0; + font-size: 0.9em; + padding-inline: 10px; +} +.column-header__buttons button:not(.active) { + background: var(--elevated-color) !important; + z-index: 2; +} +.column-header__buttons button svg { + margin: 0; +} +.column-header__buttons button span { + display: none; +} +.column-header__buttons button::before { + content: ""; + position: absolute; + inset: -20px -800px; + transform: scale(0); + transform-origin: bottom center; + background: var(--surface-background-color); + z-index: -1; + border-radius: 100px; + pointer-events: none; + opacity: 0; + transition: transform 0.3s, opacity 0.3s; +} +@media (prefers-reduced-motion) { + .column-header__buttons button::before { + transition: none !important; + } +} +.column-header__buttons button.active::before { + transform: scale(1, 5); + opacity: 1; + transition: transform 0.3s, opacity 0.1s; } @media (min-width: 890px) { - .layout-single-column .tabs-bar__wrapper, - .layout-single-column .column-back-button--slim .column-back-button { - width: 285px; - top: 0 !important; - top: var(--top) !important; + .tabs-bar__wrapper { inset-inline: unset !important; height: 50px !important; - margin-top: -50px !important; - margin-inline-start: 10px; - margin-top: 30px; + top: 0; + top: var(--top) !important; + width: 285px; border-radius: var(--radius) var(--radius) !important; box-shadow: 0 12px 12px -12px rgba(0,0,0,0.1); + margin-inline-start: 20px; } - .layout-single-column .tabs-bar__wrapper:not(.column-back-button), - .layout-single-column .column-back-button--slim .column-back-button:not(.column-back-button) { - padding-top: 0; - } - .layout-single-column .tabs-bar__wrapper .column-header__wrapper, - .layout-single-column .column-back-button--slim .column-back-button .column-header__wrapper { - gap: 2px !important; + .tabs-bar__wrapper .column-header__wrapper { display: flex; flex-direction: column; border-radius: var(--radius); overflow: hidden; } - .layout-single-column .tabs-bar__wrapper .column-header, - .layout-single-column .column-back-button--slim .column-back-button .column-header { + .tabs-bar__wrapper .column-header { background: none !important; overflow: hidden; border: 0; } - .layout-single-column .tabs-bar__wrapper .column-header > button, - .layout-single-column .column-back-button--slim .column-back-button .column-header > button { - z-index: 2; - } - .layout-single-column .tabs-bar__wrapper .column-header__buttons button, - .layout-single-column .column-back-button--slim .column-back-button .column-header__buttons button { - transition: background 0.2s, transform 0.3s !important; - position: relative; - border-radius: 100px !important; - min-width: 40px; - margin: 5px; - margin-inline-start: 0; - font-size: 0.9em; - padding-inline: 10px; - } - .layout-single-column .tabs-bar__wrapper .column-header__buttons button .column-header__icon, - .layout-single-column .column-back-button--slim .column-back-button .column-header__buttons button .column-header__icon { - margin-inline-end: 0; - } - .layout-single-column .tabs-bar__wrapper .column-header__buttons button:not(.active), - .layout-single-column .column-back-button--slim .column-back-button .column-header__buttons button:not(.active) { - background: var(--elevated-color) !important; - } - .layout-single-column .tabs-bar__wrapper .column-header__buttons button svg, - .layout-single-column .column-back-button--slim .column-back-button .column-header__buttons button svg { - margin: 0; - } - .layout-single-column .tabs-bar__wrapper .column-header__buttons button span, - .layout-single-column .column-back-button--slim .column-back-button .column-header__buttons button span { - display: none; - } - .layout-single-column .tabs-bar__wrapper .column-header__buttons button::before, - .layout-single-column .column-back-button--slim .column-back-button .column-header__buttons button::before { - content: ""; - position: absolute; - inset: 0; - top: calc(100% + 5px); - bottom: -5px; - background: var(--modal-background-color); - backdrop-filter: var(--background-filter); - z-index: -1; - transition: inset 0.1s; - border-radius: 100px; - pointer-events: none; - } - .layout-single-column .tabs-bar__wrapper .column-header__buttons button.active::before, - .layout-single-column .column-back-button--slim .column-back-button .column-header__buttons button.active::before { - inset: -10px -300px; - } } -@media (min-width: 890px) and (min-width: 1320px) { - .layout-single-column .tabs-bar__wrapper, - .layout-single-column .column-back-button--slim .column-back-button { - margin-inline-start: 25px; - } -} -@media (min-width: 890px) and (max-width: 1174px) { - .layout-single-column .tabs-bar__wrapper, - .layout-single-column .column-back-button--slim .column-back-button { - width: 265px; - margin-top: -60px !important; - top: 10px !important; +@media (min-width: 890px) and (max-width: 1319px) { + .tabs-bar__wrapper { margin-inline-start: 10px; } } +@media (min-width: 890px) and (max-width: 1174px) { + .tabs-bar__wrapper { + width: 265px; + top: 10px !important; + } +} @media (min-width: 890px) { - .layout-single-column .column-back-button--slim { + .column-back-button--slim { margin-left: auto !important; order: -1; } - .layout-single-column .column-back-button--slim > .column-back-button { + .column-back-button--slim > .column-back-button { margin-top: 0 !important; top: unset !important; } } -@media (min-width: 890px) and (max-width: 1174px) { - .layout-single-column .column-back-button--slim > .column-back-button { - margin-top: -55px !important; - top: unset !important; - } -} -#mastodon .column-settings__row, -#mastodon .column-settings__hashtags { +.column-settings__row, +.column-settings__hashtags { gap: 0; } -#mastodon .column-settings__section { - margin-bottom: 4px; - padding-inline: 4px; +.column-settings h3 { + font-size: 1em; + margin-bottom: 8px; } -#mastodon .column-select__control { +.column-select__control { border-radius: var(--radius); } -#mastodon .setting-toggle, -#mastodon .app-form__toggle { +.local-settings__page__item, +.glitch-setting-text.glitch-setting-text, +.setting-toggle, +.app-form__toggle { display: flex; align-items: center; margin-bottom: 14px; position: relative; padding: 0.7em; background: var(--elevated-color); - margin-bottom: 2px !important; + margin-block: 0 2px !important; overflow: hidden; } -#mastodon .setting-toggle:first-child, -#mastodon .app-form__toggle:first-child { +.local-settings__page__item:first-of-type, +.glitch-setting-text.glitch-setting-text:first-of-type, +.setting-toggle:first-of-type, +.app-form__toggle:first-of-type { border-top-left-radius: var(--radius); border-top-right-radius: var(--radius); } -#mastodon .setting-toggle:last-child, -#mastodon .app-form__toggle:last-child { +.local-settings__page__item:last-of-type, +.glitch-setting-text.glitch-setting-text:last-of-type, +.setting-toggle:last-of-type, +.app-form__toggle:last-of-type { border-bottom-left-radius: var(--radius); border-bottom-right-radius: var(--radius); } -#mastodon .setting-toggle .react-toggle, -#mastodon .app-form__toggle .react-toggle { +.local-settings__page__item label, +.glitch-setting-text.glitch-setting-text label, +.setting-toggle label, +.app-form__toggle label, +.local-settings__page__item legend, +.glitch-setting-text.glitch-setting-text legend, +.setting-toggle legend, +.app-form__toggle legend { + padding-block: 2px !important; +} +.local-settings__page__item label span::before, +.glitch-setting-text.glitch-setting-text label span::before, +.setting-toggle label span::before, +.app-form__toggle label span::before { + content: ""; + position: absolute; + inset: -900px; +} +.local-settings__page__item .react-toggle, +.glitch-setting-text.glitch-setting-text .react-toggle, +.setting-toggle .react-toggle, +.app-form__toggle .react-toggle { order: 2; } -#mastodon .setting-toggle .setting-toggle__label, -#mastodon .app-form__toggle .setting-toggle__label { +.local-settings__page__item .setting-toggle__label, +.glitch-setting-text.glitch-setting-text .setting-toggle__label, +.setting-toggle .setting-toggle__label, +.app-form__toggle .setting-toggle__label { margin-bottom: 0 !important; flex-grow: 1; width: 0; } -#mastodon .setting-toggle::before, -#mastodon .app-form__toggle::before { +.local-settings__page__item::before, +.glitch-setting-text.glitch-setting-text::before, +.setting-toggle::before, +.app-form__toggle::before { content: ""; position: absolute; inset: 0; @@ -3022,13 +1017,23 @@ a:focus-visible, transition: opacity 0.2s; pointer-events: none; } -#mastodon .setting-toggle:hover::before, -#mastodon .app-form__toggle:hover::before, -#mastodon .setting-toggle:focus-within::before, -#mastodon .app-form__toggle:focus-within::before { +.local-settings__page__item:hover::before, +.glitch-setting-text.glitch-setting-text:hover::before, +.setting-toggle:hover::before, +.app-form__toggle:hover::before, +.local-settings__page__item:focus-within::before, +.glitch-setting-text.glitch-setting-text:focus-within::before, +.setting-toggle:focus-within::before, +.app-form__toggle:focus-within::before { opacity: 1; } -#mastodon .navigation-panel.navigation-panel { +@media (min-width: 890px) and (max-width: 1174px) { + .column-back-button--slim > .column-back-button { + margin-top: -55px !important; + top: unset !important; + } +} +.navigation-panel.navigation-panel { box-sizing: border-box; height: calc(100vh - var(--top) - 50px + var(--radius)); padding-bottom: 10px; @@ -3036,27 +1041,25 @@ a:focus-visible, border: 0; margin-top: calc(0px - var(--radius)); padding-top: calc(10px + var(--radius)); + overflow: hidden auto; } -#mastodon .navigation-panel.navigation-panel > hr { +.navigation-panel.navigation-panel > hr { display: none; } -#mastodon .navigation-panel.navigation-panel hr { - margin-inline: 15px; -} @media (min-width: 1175px) { - #mastodon .navigation-panel.navigation-panel { + .navigation-panel.navigation-panel { padding-top: calc(var(--radius) + 10px); margin-top: calc(50px - var(--radius)); } - #mastodon .navigation-panel.navigation-panel .navigation-panel__logo .column-link, - #mastodon .navigation-panel.navigation-panel .navigation-panel__logo hr { + .navigation-panel.navigation-panel .navigation-panel__logo .column-link, + .navigation-panel.navigation-panel .navigation-panel__logo hr { display: none !important; } - #mastodon .navigation-panel.navigation-panel .switch-to-advanced { + .navigation-panel.navigation-panel .switch-to-advanced { border-radius: var(--radius); margin-top: 0; } - #mastodon .navigation-panel.navigation-panel [href="/settings/preferences"] { + .navigation-panel.navigation-panel [href="/settings/preferences"] { display: none !important; } } @@ -3077,6 +1080,14 @@ a:focus-visible, top: -10px; box-sizing: border-box; } +.column-header__back-button, +.column-back-button > svg, +.column-link__icon, +.column-header > button .column-header__icon { + margin-inline-end: 0.7em; + font-size: 16px !important; + padding-right: 0 !important; +} @media (min-width: 890px) { .column-link { flex-grow: 100 !important; @@ -3093,11 +1104,6 @@ a:focus-visible, overflow: hidden; background: none !important; } - .column-link .column-link__icon, - .column-header > button .column-header__icon { - margin-inline-end: 0.7em !important; - font-size: 16px !important; - } .column-link::before { content: "" !important; position: absolute; @@ -3126,252 +1132,1567 @@ a:focus-visible, .column-link[href="/lists"] + div .column-link i { opacity: 0.2; } -} -@media (min-width: 890px) { - #mastodon .getting-started__trends { + .navigation-panel.navigation-panel .getting-started__trends { display: unset !important; } } -#mastodon .trends__item { - display: flex !important; +.navigation-panel.navigation-panel .trends__item { + margin: 0 !important; } -#mastodon .trends__item__name a::before { +.scrollable > div:first-child > [tabindex="-1"]:last-child .status__wrapper::after { + content: unset; +} +.focusable, +.entry, +.statuses-grid__item .detailed-status, +.trends__item, +.story, +.account-card, +.scrollable :not(.focusable) > .account:not(.account--minimal), +.timeline-hint { + overflow: hidden; + contain: paint inline-size; + position: relative; + border-radius: var(--panel-radius); + border: 0; +} +.focusable.focusable, +.entry.focusable, +.statuses-grid__item .detailed-status.focusable, +.trends__item.focusable, +.story.focusable, +.account-card.focusable, +.scrollable :not(.focusable) > .account:not(.account--minimal).focusable, +.timeline-hint.focusable { + background: none; +} +@media (pointer: fine) { + .focusable::before, + .entry::before, + .statuses-grid__item .detailed-status::before, + .trends__item::before, + .story::before, + .account-card::before, + .scrollable :not(.focusable) > .account:not(.account--minimal)::before, + .timeline-hint::before { + content: ""; + position: absolute; + inset: 0px !important; + height: unset !important; + width: unset !important; + pointer-events: none; + transition: background-color 0.2s; + } + .focusable:hover::before, + .entry:hover::before, + .statuses-grid__item .detailed-status:hover::before, + .trends__item:hover::before, + .story:hover::before, + .account-card:hover::before, + .scrollable :not(.focusable) > .account:not(.account--minimal):hover::before, + .timeline-hint:hover::before, + .focusable:focus-within::before, + .entry:focus-within::before, + .statuses-grid__item .detailed-status:focus-within::before, + .trends__item:focus-within::before, + .story:focus-within::before, + .account-card:focus-within::before, + .scrollable :not(.focusable) > .account:not(.account--minimal):focus-within::before, + .timeline-hint:focus-within::before { + background-color: var(--hover-color); + } +} +.focusable:not(:last-child)::after, +.entry:not(:last-child)::after, +.statuses-grid__item .detailed-status:not(:last-child)::after, +.trends__item:not(:last-child)::after, +.story:not(:last-child)::after, +.account-card:not(:last-child)::after, +.scrollable :not(.focusable) > .account:not(.account--minimal):not(:last-child)::after, +.timeline-hint:not(:last-child)::after { + content: ""; + position: absolute; + bottom: 0px; + inset-inline: var(--radius); + border-top: 1px solid var(--border-color); + pointer-events: none; +} +.status__wrapper-reply.status--in-thread::after { + top: 0; +} +.status--in-thread.status__wrapper-reply:not(.status--first-in-thread)::after, +.status--in-thread:not(.status__wrapper-reply)::after { + border-top: 0 !important; +} +.detailed-status, +.status { + padding: 16px; +} +.status__info .account__avatar, +.status__info .status__avatar { + max-width: var(--avatar-size) !important; + max-height: var(--avatar-size) !important; +} +.status__line { + left: calc(16px + (var(--avatar-size) / 2)); +} +.status__prepend + .status:not(.status-direct) { + padding-top: 0; +} +@media (max-width: 450px) { + .status--in-thread { + --avatar-size: 34px; + } + .status--in-thread .status__info ~ * { + margin-inline-start: calc(var(--avatar-size) + 10px); + width: calc(100% - (var(--avatar-size) + 10px)); + } +} +.status__content { + text-align: unset !important; + line-height: 1.5; +} +.status__content.status__content--with-spoiler { + overflow: visible; +} +.status__content.status__content--with-spoiler > p { + margin-inline: -100px; + padding-inline: 100px; + overflow: hidden; +} +.status__content.status__content--with-spoiler > p:first-child { + margin-bottom: 0; +} +.status__content p:empty { + max-height: 0; +} +.status__content picture { + display: contents; +} +.status__content .custom-emoji { + display: inline-block; + height: var(--emoji-size) !important; + min-width: var(--emoji-size) !important; + width: auto; + margin: -0.2ex 0 0.2ex; +} +@media (prefers-reduced-motion: no-preference) { + .custom-emoji { + transition: transform 1s cubic-bezier(0, 0.7, 0, 1); + } + .custom-emoji:hover { + transform: scale(1.7); + transition: transform 0.4s cubic-bezier(0, 0.7, 0, 1); + } +} +.status__content ~ [style*="aspect-ratio"] { + max-height: 80vh; +} +#mastodon .status__content__spoiler-link { + display: flex; + align-items: center; + position: relative; + padding: 0.4em 1.2em; + border-radius: var(--radius-round); + color: inherit; + background: var(--elevated-color); + margin-block: 8px; +} +#mastodon .status__content__spoiler-link::before, +#mastodon .status__content__spoiler-link::after { + content: ""; + position: absolute; + inset: 0; + border-radius: var(--radius-round); + background-color: var(--hover-color); + opacity: 0; + transition: opacity 0.2s; +} +#mastodon .status__content__spoiler-link::after { + inset: -6px -9999px; +} +#mastodon .status__content__spoiler-link:hover::before, +#mastodon .status__content__spoiler-link:focus::before, +#mastodon .status__content__spoiler-link:hover::after, +#mastodon .status__content__spoiler-link:focus::after { + opacity: 1; +} +.detailed-status__wrapper-direct .status__content, +.status-direct .status__content, +.status__wrapper-direct .status__content, +.conversation .status__content { + position: relative !important; + background: var(--elevated-color); + padding: 12px 14px; + border-radius: var(--radius-round); + border-top-left-radius: 6px; + box-sizing: border-box; + margin-bottom: 0; + overflow: hidden !important; + max-width: max-content; +} +.detailed-status__wrapper-direct .status__content .media-gallery, +.status-direct .status__content .media-gallery, +.status__wrapper-direct .status__content .media-gallery, +.conversation .status__content .media-gallery { + width: 999px; + max-width: 100% !important; +} +.status__wrapper-direct:not(.detailed-status__wrapper-direct) .status__prepend { + position: absolute; + contain: strict; +} +.detailed-status { + border: 0; + padding-bottom: 4px; +} +.detailed-status__wrapper, +.detailed-status { + box-shadow: var(--shadow); +} +.detailed-status__wrapper .status__content, +.detailed-status .status__content { + min-height: unset !important; +} +.detailed-status__wrapper { + isolation: isolate; + background: none; +} +.detailed-status__wrapper::before { + content: ""; + position: absolute; + inset: 0; + background: var(--elevated-tint) !important; + pointer-events: none; + z-index: -1; +} +.detailed-status__wrapper .detailed-status { + box-shadow: none; +} +.detailed-status__meta { + margin-top: 14px; + line-height: 2; +} +.detailed-status__meta > * { + display: inline-flex; + border: 0 !important; + padding: 0 !important; + margin-inline-end: 8px; +} +.detailed-status__meta > *:not(:last-child)::after { + content: "ยท"; +} +.media-gallery, +.video-player, +.status-card.horizontal.interactive, +.status-card, +.audio-player, +.picture-in-picture-placeholder { + box-shadow: var(--shadow-low); + border-radius: var(--radius) !important; + margin-top: 10px !important; + margin-bottom: 10px !important; + animation: scaleIn 0.4s; + max-width: unset !important; +} +.media-gallery:has(.spoiler-button:not(.spoiler-button--minified)) { + height: 150px !important; + aspect-ratio: unset !important; +} +.media-gallery__item { + border-radius: 0; +} +.spoiler-button--minified button { + padding: 6px !important; + background: rgba(0,0,0,0.2) !important; +} +.spoiler-button--minified button::after { + content: ""; + position: absolute; + inset: -50px; +} +.spoiler-button--minified button:hover { + background: rgba(0,0,0,0.4) !important; +} +.spoiler-button--minified .icon { + width: 18px; + height: 18px; +} +.status-card { + align-items: stretch; + gap: 0; +} +.status-card:not(.horizontal) { + border: 1px solid var(--border-color) !important; +} +.status-card:not(.expanded) .status-card__image { + overflow: hidden; +} +.status-card:not(.expanded) .status-card__image img { + border-radius: 0; +} +.status-card:not(.interactive) .status-card__image { + position: relative; + aspect-ratio: unset !important; +} +.status-card__content { + margin-block: auto; + padding: 15px; +} +.status-card__host { + font-size: 0.85em; + line-height: 1.5; + margin: 0; +} +.status-card__title { + font-size: 1em; + margin-top: 0.2em; + margin-bottom: 0; + line-height: 1.4; +} +.status-card__description { + line-height: 1.4 !important; + margin: 0; +} +@supports (-webkit-line-clamp: 8) { + .status-card__description { + display: -webkit-box; + -webkit-line-clamp: 8; + -webkit-box-orient: vertical; + white-space: unset; + } +} +.status-card__author { + margin-top: 0.4em; + font-size: 0.85em; +} +.status-card:hover { + background-color: var(--hover-color); +} +.more-from-author { + background: none; + border: 0; + padding-top: 0; + border-radius: var(--radius); +} +.audio-player .video-player__seek { + margin: var(--radius); +} +.hashtag-bar { + margin-top: 10px; +} +.hashtag-bar a, +.hashtag-bar button { + color: var(--accent, #8c8dff); + transition: opacity 0.2s; + padding: 5px 10px; +} +.hashtag-bar a { + position: relative; + border-radius: var(--radius-round); + background: var(--elevated-color); +} +.hashtag-bar a::after { + content: ""; + position: absolute; + inset: 0; + background: var(--elevated-color); + border-radius: inherit; + opacity: 0; + transition: opacity 0.2s; +} +.hashtag-bar a:hover, +.hashtag-bar a:focus { + opacity: 1; +} +.hashtag-bar a:hover::after, +.hashtag-bar a:focus::after { + opacity: 1; +} +.hashtag-bar button { + padding-block: 0; +} +.status__action-bar { + flex-wrap: wrap; + margin-top: 0.4em; + margin-bottom: -6px; + gap: 0; + margin-inline-start: -8px; + pointer-events: none; +} +.status__action-bar > * { + pointer-events: all; + flex-grow: 1; + max-width: 55px; + min-width: max-content; +} +.status__action-bar > * button.icon-button { + width: 100% !important; +} +.status__action-bar > :not(.icon-button) { + display: flex; + height: 100%; +} +.status__action-bar .icon-button { + margin: 0; +} +.status__action-bar .icon-button::before { + content: ""; + position: absolute; + inset: -0.5em; +} +.status__action-bar, +.detailed-status__action-bar, +.picture-in-picture__footer { + position: relative; + z-index: 2; + justify-content: unset; +} +.status__action-bar .icon-button, +.detailed-status__action-bar .icon-button, +.picture-in-picture__footer .icon-button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.5em 0.4em !important; + border-radius: var(--radius); + position: relative; +} +.status__action-bar .icon-button .icon-button__counter, +.detailed-status__action-bar .icon-button .icon-button__counter, +.picture-in-picture__footer .icon-button .icon-button__counter { + width: auto !important; +} +.status__action-bar .icon-button--with-counter, +.detailed-status__action-bar .icon-button--with-counter, +.picture-in-picture__footer .icon-button--with-counter { + padding-inline: 0.7em !important; +} +.detailed-status__action-bar, +.picture-in-picture__footer { + padding-inline: 15px !important; + gap: 0; +} +.detailed-status__action-bar .icon-button, +.picture-in-picture__footer .icon-button { + flex-grow: 1 !important; +} +.detailed-status__action-bar div, +.picture-in-picture__footer div { + display: flex; + justify-content: center; + flex-grow: 1; +} +.account__wrapper { + line-height: 1.5; +} +.account__contents { + display: flex; + flex-wrap: wrap; + flex-grow: 1; + gap: 0 10px; +} +.display-name { + margin-bottom: 1px !important; +} +.account:not(.account--minimal) .display-name__account { + display: block; + width: 300px; +} +.account__details { + font-size: 0.9em; + opacity: 0.8; + width: 100px; + flex-grow: 1; + text-align: end; + align-items: center; + line-height: 1.2; +} +.account__details:has(.verified-badge) > :not(.verified-badge) { + display: none; +} +.account__wrapper button:not(:hover):not(:focus) { + background: var(--elevated-color); + color: inherit; +} +.trends__item, +.story, +.account-card { + animation: slideUpFade backwards 0.4s 0.24s cubic-bezier(0, 1, 1, 1); + border-radius: var(--radius); +} +.trends__item:nth-child(1), +.story:nth-child(1), +.account-card:nth-child(1) { + animation-delay: 0.04s; +} +.trends__item:nth-child(2), +.story:nth-child(2), +.account-card:nth-child(2) { + animation-delay: 0.08s; +} +.trends__item:nth-child(3), +.story:nth-child(3), +.account-card:nth-child(3) { + animation-delay: 0.12s; +} +.trends__item:nth-child(4), +.story:nth-child(4), +.account-card:nth-child(4) { + animation-delay: 0.16s; +} +.trends__item:nth-child(5), +.story:nth-child(5), +.account-card:nth-child(5) { + animation-delay: 0.2s; +} +.trends__item:nth-child(6), +.story:nth-child(6), +.account-card:nth-child(6) { + animation-delay: 0.24s; +} +.explore__links { + padding: 10px; + display: flex; + flex-wrap: wrap; + align-content: flex-start; +} +.explore__links > .dismissable-banner { + margin: -10px; + margin-bottom: 10px; +} +.explore__links .story { + margin-inline: 0 !important; +} +.trends__item { + display: flex !important; + margin-inline: 0 !important; +} +.trends__item__name a::before { content: ""; position: absolute; inset: 0; } -#mastodon .trends__item__current { +.trends__item__current { display: none; } -#mastodon .trends__item__sparkline { +.trends__item__sparkline { overflow: visible !important; pointer-events: none; } -#mastodon .trends__item__sparkline svg { +.trends__item__sparkline svg { overflow: visible !important; } -#mastodon .trends__item__sparkline path:first-child { - filter: blur(10px); +.trends__item__sparkline path:first-child { + filter: blur(8px); } -#mastodon .trends__item__sparkline path:last-child { +.trends__item__sparkline path:last-child { mask: linear-gradient(to left, #000, #000, transparent); -webkit-mask: linear-gradient(to left, #000, #000, transparent); } -.rtl #mastodon .trends__item__sparkline { +.rtl .trends__item__sparkline { transform: scaleX(-1); } -.getting-started, -#mastodon .column[aria-labelledby="Getting-started"] > .scrollable.scrollable.scrollable { +.explore__links .trends__item { + margin: 7.5px !important; + padding: 25px !important; + box-shadow: var(--shadow-med); + width: 200px; + background: var(--elevated-color); + flex-grow: 1; +} +.explore__links .trends__item::after { + content: unset !important; +} +.explore__links .trends__item a { + font-size: 1.4em; + line-height: 1.7em; +} +.explore__links .trends__item a::before { + content: ""; + position: absolute; + inset: 0; +} +.explore__links .trends__item .trends__item__sparkline { + height: 100%; +} +.explore__links .trends__item .trends__item__sparkline svg { + height: 100%; + float: right; + overflow: visible !important; + position: relative; +} +.explore__links .trends__item .trends__item__sparkline svg path { + display: unset !important; + transition: transform 1s; +} +.explore__links .trends__item .trends__item__sparkline svg path:first-child { + transform-origin: center; +} +.explore__links .trends__item:hover svg path:first-child, +.explore__links .trends__item:focus-within svg path:first-child { + transform: scale(2); +} +.explore__links .story { + width: 100%; + margin: 0; +} +.account__header { + overflow: visible; +} +.follow-request-banner { + margin-bottom: -100px; + padding-bottom: 120px; +} +.account__header__image { + height: 0; + padding-bottom: 35%; + border-radius: var(--panel-radius) var(--panel-radius) 0 0; + position: sticky; + top: calc(0px - var(--panel-radius)); + overflow: clip; +} +.account__header__image img { + position: absolute; +} +.account__header__image .account__header__info { + position: absolute; + z-index: 2; + height: 100%; +} +.account__header__image .account__header__info > span { + position: sticky; + top: var(--radius); +} +.account__header__bar { + position: relative; + z-index: 2; + border: 0; + padding-inline: 20px; + border-radius: var(--radius) var(--radius) 0 0; + margin-top: calc(0px - var(--radius)) !important; + display: flex; + flex-direction: column; + background: var(--background-color); + isolation: isolate; +} +@media (max-width: 890px) { + .account__header__bar { + padding-inline: 15px; + } +} +.account__header__bar::before { + content: ""; + background: var(--elevated-tint); + position: absolute; + inset: 0; + pointer-events: none; +} +.account__header__bar::after { + content: ""; + position: absolute; + inset-inline: 0; + height: 95px; + background: inherit; + z-index: -1; + border-radius: var(--radius); + mask: linear-gradient(to bottom, transparent, #000); +} +.account__header__tabs { + overflow: visible !important; + align-items: flex-end; + padding: 0; + height: unset !important; + margin-top: -55px !important; +} +.account__header__tabs::before { + content: ""; + position: absolute; + top: -55px; + inset-inline: 0; + height: 150px; + backdrop-filter: blur(40px); + filter: brightness(1.1); + pointer-events: none; + opacity: 0.7; + z-index: -2; + clip-path: inset(55px 0 0 0 round var(--radius)); +} +.account__header__tabs ~ div { + z-index: 2; +} +.account__header__bar .avatar { + margin-inline-start: 0 !important; + overflow: visible !important; + position: relative; + margin-top: 20px; +} +.account__header__bar .avatar .account-role { + position: absolute; + bottom: 0; + left: 110%; + border-radius: var(--radius); +} +.account__header__bar .account__avatar { + border: 0; + box-shadow: var(--shadow); + background: none; + background-size: cover !important; +} +.account__header__tabs__name { + margin-bottom: 0; + padding: 0; + margin-top: 16px; +} +.account__header__tabs__name h1 { + display: flex; + flex-wrap: wrap; + white-space: unset; + gap: 0 0.4em; + font-weight: 600; +} +.account__header__extra { + margin-top: 8px; +} +.account__header__fields, +.account__header__account-note { + display: flex; + flex-wrap: wrap; + gap: 2px; + background: none; + border-radius: var(--radius) !important; + overflow: hidden; + max-width: max-content; + border: 0 !important; +} +.account__header__fields dl { + display: inline; + border-radius: 0; + overflow: hidden; + border: 0 !important; + padding: 8px 12px !important; + position: relative; + overflow: hidden; + flex-grow: 1; +} +.account__header__fields dl:not(.verified) { + background-color: var(--elevated-color); +} +.account__header__fields dl dt { + all: unset !important; + color: unset !important; + opacity: 0.9 !important; +} +.account__header__fields dl dd { + padding: 0; + white-space: unset; + max-height: unset; + text-align: unset; +} +.account__header__fields dl dd > span > a:first-child:last-child::after, +.account__header__fields dl dd .h-card:first-child:last-child a::after { + content: ""; + position: absolute; + inset: 0; + background-color: var(--hover-color); + opacity: 0; + transition: opacity 0.2s; +} +.account__header__fields dl dd > span > a:first-child:last-child:hover:after, +.account__header__fields dl dd .h-card:first-child:last-child a:hover:after, +.account__header__fields dl dd > span > a:first-child:last-child:focus:after, +.account__header__fields dl dd .h-card:first-child:last-child a:focus:after { + opacity: 1; +} +.account__header__fields dl dd.verified { + overflow: visible !important; + border: 0; + background: none; +} +.account__header__fields dl dd.verified a::before, +.account__header__fields dl dd.verified a::after { + content: ""; + position: absolute; + inset: 0; + background: currentcolor; + opacity: 0.2; +} +.account__header__fields dl dd.verified a::after { + background: linear-gradient(20deg, currentcolor, transparent) !important; + opacity: 0.2 !important; + z-index: -2; +} +.account__header__account-note { + position: relative; + font-size: 0.9em; + max-width: unset; + padding: 0.5em 10px !important; + margin-block: -5px 10px; + margin-inline: -10px !important; + border-radius: var(--radius) !important; +} +.account__header__account-note::after { + content: ""; + position: absolute; + bottom: 0; + inset-inline: 10px; + border-top: 1px solid var(--border-color); + transition: opacity 0.2s; +} +.account__header__account-note:focus-within::after { + opacity: 0; +} +.account__header__account-note label { + z-index: 2; + margin: 0; + pointer-events: none; + font-size: inherit; +} +.account__header__account-note textarea { + margin: -100px !important; + padding: 100px !important; + padding-inline-end: 0.7em !important; + margin-inline-end: -0.7em !important; + box-sizing: content-box; + width: 100%; + font-size: inherit; + transition: background 0.2s; +} +.account__header__account-note textarea::placeholder { + font-weight: 600; +} +.account-gallery__container { + border-radius: var(--radius); + overflow: clip; + padding: 0; + margin: 4px; + gap: 4px; + margin-bottom: calc(-40vh + 4px); +} +.account-gallery__item { + margin: 0; + flex: 1 1 calc(100px + 15%); + transition: flex 0.7s cubic-bezier(0, 0, 0, 1); + min-height: 180px !important; +} +.media-gallery__item-thumbnail { + transition: transform 0.2s; +} +.account-gallery__item:hover, +.account-gallery__item:focus-within { + flex-grow: 1.5; +} +.account-gallery__item:hover .media-gallery__item-thumbnail, +.account-gallery__item:focus-within .media-gallery__item-thumbnail { + transform: scale(1.02); +} +.account-gallery__container > button { + width: unset; + flex-grow: 1; + flex: 1 1 calc(100px + 15% - 24px); + margin: 12px; + font-size: 1.2em; + font-weight: 700; + background: var(--elevated-color); + color: inherit; +} +.account-gallery__container > button:hover:not(:focus) { + transform: scale(1.01); +} +.account-authorize__wrapper { + background: var(--elevated-color); + border-radius: var(--radius); + overflow: hidden; + flex-grow: 1; + margin: 10px; + display: flex; + flex-direction: column; +} +@media (max-width: 890px) { + .account-authorize__wrapper { + margin-inline: 10px; + } +} +.layout-multiple-columns .account-authorize__wrapper { + margin-inline: 10px; +} +.account-authorize__wrapper .account-authorize { + padding: 20px 15px 10px; +} +.account-authorize__wrapper .detailed-status__display-name { + margin-bottom: 10px !important; +} +.account-authorize__wrapper .account--panel { + margin-top: auto; + border-bottom: 0; + padding-inline: 15px; + gap: 10px; + background: none; +} +.account-authorize__wrapper br { + display: block; +} +.account-authorize__wrapper p { + margin-bottom: 0.2em; +} +.account-authorize__wrapper .account--panel__button:first-child .icon-button:not(:hover):not(:focus) { + background: var(--elevated-color); +} +.account-authorize__wrapper .icon-button { + width: 100% !important; + padding: 10px; + height: unset !important; +} +.about__meta { + border-radius: var(--radius); +} +.account--minimal { + max-width: 100%; +} +.about__section { + margin: 30px -20px; + padding-inline: 20px; + contain: inline-size paint; +} +.about__section.active .about__section__title { + margin-inline: -20px; + border-radius: 0; + border-inline: 0; + border-bottom: 0; +} +.about__section__title { + position: sticky; + top: -1px; + z-index: 2; + background: var(--background-color-tint); + border: 1px solid var(--border-color); + border-radius: var(--radius); + overflow: hidden; + transition: margin 0.2s cubic-bezier(0, 1, 0, 1), border-radius 0.2s; +} +.about__section__title::after { + content: ""; + position: absolute; + inset: 0; + background: var(--elevated-tint); + backdrop-filter: blur(10px); + z-index: -1; +} +.about__section__body { + border: 0; + padding: 0; + animation: slideDownFade 0.4s 0.1s backwards cubic-bezier(0, 1, 0, 1.2); +} +.explore__search-results { + border: 0; +} +.search-results__section__header { + margin: 0px 0px 10px; + color: unset; + background: none; + padding-inline: 25px; + font-weight: 600; +} +.search-results__section { + border: 0; + margin-bottom: 20px; +} +.sidebar-wrapper { + overflow: visible !important; + width: unset; +} +.sidebar-wrapper__inner { + position: sticky; + top: 0; + max-height: 100vh !important; + overflow-y: auto !important; + pointer-events: all !important; + z-index: 100; +} +.sidebar-wrapper__inner .fa { + margin-inline-end: 1em !important; +} +.sidebar-wrapper__inner .sidebar > ul > li { + overflow: hidden; + margin-inline: 15px !important; +} +.sidebar-wrapper__inner .sidebar > ul > li > a:not(.selected) { + background: none; +} +.sidebar-wrapper__inner .sidebar > ul > li a { + display: flex !important; + align-items: center; + white-space: unset; +} +.sidebar-wrapper__inner .sidebar > ul > li.selected { + margin: 6px; + border-radius: var(--radius); +} +.sidebar-wrapper__inner .sidebar > ul > li.selected > a.selected { + font-weight: 600; + color: unset; + position: relative; + overflow: visible; + border-radius: 0 !important; + border: 0; +} +.sidebar-wrapper__inner .sidebar > ul > li.selected > a.selected::after { + content: ""; + position: absolute; + top: 100%; + inset-inline: 0; + height: 9999px; + background: inherit; + z-index: -1; +} +.sidebar-wrapper__inner .sidebar > ul > li > ul { + border-radius: var(--radius) !important; + overflow: hidden !important; + gap: 2px !important; + margin: 8px; + margin-top: 0; + background: none; +} +.sidebar-wrapper__inner .sidebar > ul > li > ul > li { + border-radius: 8px; +} +.sidebar-wrapper__inner .sidebar > ul > li > ul > li:not(:first-child):not(:last-child) { + margin-block: 2px; +} +.sidebar-wrapper__inner .sidebar > ul > li > ul > li a { + padding: 14px 16px; + font-weight: 600; + border: 0; +} +.sidebar-wrapper__inner .sidebar > ul > li > ul > li:not(.selected) a { + background-color: rgba(150,150,250,0.1); +} +.admin-wrapper .content__heading { + margin-bottom: 2em; +} +.admin-wrapper h4 { + margin: 0; + border-bottom: 0; +} +.admin-wrapper form > h4 { + margin-top: 2em !important; + border-bottom: 0 !important; + margin-bottom: 0 !important; +} +.admin-wrapper .lead { + margin-bottom: 15px; +} +.admin-wrapper .flash-message, +.admin-wrapper .applications-list__item, +.admin-wrapper .filters-list__item { + border-radius: var(--radius); + border: 0; + overflow: clip; +} +.admin-wrapper .fields-row { + margin-inline: 0; + border-radius: var(--radius); + overflow: clip; + padding-top: 0; + gap: 2px; + display: flex; + flex-wrap: wrap; +} +.admin-wrapper .fields-group:not(.fields-row__column), +.admin-wrapper .fields-row { + margin-bottom: 1em !important; +} +.admin-wrapper .fields-row > .fields-row__column { + max-width: unset; + width: 300px; + border-radius: 0 !important; + display: flex; + flex-direction: column; + flex-grow: 1; + margin: 0 !important; +} +.admin-wrapper .fields-row > .fields-row__column .fields-group { + border-radius: 0 !important; + margin: 0 !important; +} +.admin-wrapper .fields-row > .fields-row__column .with_block_label { + display: flex; + flex-direction: column; + height: 100%; +} +.admin-wrapper .fields-row > .fields-row__column .with_block_label > .label_input { + display: flex; + flex-direction: column; + flex-grow: 1; +} +.admin-wrapper .fields-row > .fields-row__column .with_block_label > .label_input > textarea { + min-height: 300px; + flex-grow: 1; +} +.admin-wrapper .fields-row > .fields-row__column > :last-child { + flex-grow: 1; + align-items: flex-start; + border: 0; +} +.admin-wrapper .fields-row > .fields-row__column > :not(:first-child):not(:last-child) { + padding-block: 0.5em !important; + margin-block: -3px; +} +.admin-wrapper :not(.fields-row__column) > .fields-group, +.admin-wrapper .fields-row > *, +.admin-wrapper .label_input > ul, +.admin-wrapper .label_input__wrapper > ul, +.admin-wrapper .with_block_label.radio_buttons .label_input { + border-radius: var(--radius); + overflow: hidden; + padding: 0; + display: flex; + flex-direction: column; + gap: 2px; +} +.admin-wrapper :not(.fields-row__column) > .fields-group > *, +.admin-wrapper .fields-row > * > *, +.admin-wrapper .label_input > ul > *, +.admin-wrapper .label_input__wrapper > ul > *, +.admin-wrapper .with_block_label.radio_buttons .label_input > * { + background-color: var(--elevated-color); + padding: 0.8rem; + margin-block: 0px; + position: relative; + border-radius: 0 !important; + overflow: hidden; +} +.admin-wrapper :not(.fields-row__column) > .fields-group > *::after, +.admin-wrapper .fields-row > * > *::after, +.admin-wrapper .label_input > ul > *::after, +.admin-wrapper .label_input__wrapper > ul > *::after, +.admin-wrapper .with_block_label.radio_buttons .label_input > *::after { + content: ""; + position: absolute; + inset: 0; + background-color: var(--hover-color); + z-index: -1; + opacity: 0; + transition: opacity 0.2s; +} +.admin-wrapper :not(.fields-row__column) > .fields-group > *:hover::after, +.admin-wrapper .fields-row > * > *:hover::after, +.admin-wrapper .label_input > ul > *:hover::after, +.admin-wrapper .label_input__wrapper > ul > *:hover::after, +.admin-wrapper .with_block_label.radio_buttons .label_input > *:hover::after, +.admin-wrapper :not(.fields-row__column) > .fields-group > *:focus-within::after, +.admin-wrapper .fields-row > * > *:focus-within::after, +.admin-wrapper .label_input > ul > *:focus-within::after, +.admin-wrapper .label_input__wrapper > ul > *:focus-within::after, +.admin-wrapper .with_block_label.radio_buttons .label_input > *:focus-within::after { + opacity: 1; +} +.admin-wrapper :not(.fields-row__column) > .fields-group label::before, +.admin-wrapper .fields-row > * label::before, +.admin-wrapper .label_input > ul label::before, +.admin-wrapper .label_input__wrapper > ul label::before, +.admin-wrapper .with_block_label.radio_buttons .label_input label::before { + content: ""; + position: absolute; + inset: -900px; +} +.admin-wrapper .label_input__wrapper > :not([type="checkbox"]):not(label) { + margin-top: 4px; +} +.admin-wrapper .label_input { + position: relative; +} +.admin-wrapper label { + margin: 0 !important; + display: flex; + align-items: center; + padding: 0 !important; +} +.admin-wrapper label input { + margin: 0; + margin-inline-end: 10px !important; + position: static !important; +} +.admin-wrapper input { + border-radius: var(--radius) !important; +} +.admin-wrapper .radio { + flex-grow: 1; +} +.admin-wrapper .radio:not(:last-child) { + margin-bottom: 0 !important; +} +.admin-wrapper .hint:last-child { + margin-bottom: 0 !important; +} +.admin-wrapper .input.with_block_label > .row { + flex-wrap: wrap; + margin: 0; +} +.admin-wrapper .input.with_block_label > .row > .string { + padding: 0; + width: 100%; + margin: 0; +} +.admin-wrapper .input.with_block_label > .row > .string:first-child input { + border-radius: var(--radius) var(--radius) 0 0 !important; +} +.admin-wrapper .input.with_block_label > .row > .string:last-child input { + border-radius: 0 0 var(--radius) var(--radius) !important; +} +.admin-wrapper .input.with_block_label > .row:not(:last-child) { + margin-bottom: 8px; +} +.admin-wrapper li.checkbox { + flex-grow: 1; + overflow: hidden; +} +.admin-wrapper ul { + flex-direction: row !important; + flex-wrap: wrap; + gap: 2px; + flex-grow: 1; +} +.admin-wrapper li.checkbox { + flex-basis: 45%; +} +.admin-wrapper .spacer { + border-top: 1px solid var(--border-color) !important; +} +.batch-table label { + padding-inline-start: 20px !important; +} +.batch-table, +.table, +:not(.batch-table__row__content) > table { + overflow: clip; + border-radius: var(--radius); + border-spacing: 0 2px; + border-collapse: separate; +} +.batch-table__toolbar, +.batch-table__row, +.batch-table tr > *, +.table tr > *, +:not(.batch-table__row__content) > table tr > * { + border: 0; + margin-bottom: 2px !important; +} +.batch-table td, +.table td, +:not(.batch-table__row__content) > table td, +.batch-table th, +.table th, +:not(.batch-table__row__content) > table th, +.batch-table__row { + position: relative; +} +.batch-table tr > td > div > span, +.table tr > td > div > span, +:not(.batch-table__row__content) > table tr > td > div > span, +.batch-table tr > th > div > span, +.table tr > th > div > span, +:not(.batch-table__row__content) > table tr > th > div > span { + padding-inline: 0.7em; + display: inline-block; +} +.keyboard-shortcuts { + padding: 0; + margin-top: -4px; +} +.keyboard-shortcuts table { + width: 100%; + border-radius: 0; +} +.keyboard-shortcuts td { + padding: 0.7em; +} +.batch-table__row, +.batch-table th, +.table th, +:not(.batch-table__row__content) > table th, +.batch-table > tbody > tr > td, +.table > tbody > tr > td, +:not(.batch-table__row__content) > table > tbody > tr > td, +.batch-table tfoot td, +.table tfoot td, +:not(.batch-table__row__content) > table tfoot td { + background: var(--elevated-color) !important; + vertical-align: middle; +} +.batch-table__row::after, +.batch-table th::after, +.table th::after, +:not(.batch-table__row__content) > table th::after, +.batch-table > tbody > tr > td::after, +.table > tbody > tr > td::after, +:not(.batch-table__row__content) > table > tbody > tr > td::after, +.batch-table tfoot td::after, +.table tfoot td::after, +:not(.batch-table__row__content) > table tfoot td::after { + content: ""; + position: absolute; + inset: 0 0; + background: var(--hover-color); + opacity: 0; + transition: 0.2s; + pointer-events: none; +} +.batch-table__row:hover::after, +.batch-table th:hover::after, +.table th:hover::after, +:not(.batch-table__row__content) > table th:hover::after, +.batch-table > tbody > tr > td:hover::after, +.table > tbody > tr > td:hover::after, +:not(.batch-table__row__content) > table > tbody > tr > td:hover::after, +.batch-table tfoot td:hover::after, +.table tfoot td:hover::after, +:not(.batch-table__row__content) > table tfoot td:hover::after, +.batch-table__row:focus-within::after, +.batch-table th:focus-within::after, +.table th:focus-within::after, +:not(.batch-table__row__content) > table th:focus-within::after, +.batch-table > tbody > tr > td:focus-within::after, +.table > tbody > tr > td:focus-within::after, +:not(.batch-table__row__content) > table > tbody > tr > td:focus-within::after, +.batch-table tfoot td:focus-within::after, +.table tfoot td:focus-within::after, +:not(.batch-table__row__content) > table tfoot td:focus-within::after { + opacity: 1; +} +.batch-table__row > a:first-child:last-child, +.batch-table th > a:first-child:last-child, +.table th > a:first-child:last-child, +:not(.batch-table__row__content) > table th > a:first-child:last-child, +.batch-table > tbody > tr > td > a:first-child:last-child, +.table > tbody > tr > td > a:first-child:last-child, +:not(.batch-table__row__content) > table > tbody > tr > td > a:first-child:last-child, +.batch-table tfoot td > a:first-child:last-child, +.table tfoot td > a:first-child:last-child, +:not(.batch-table__row__content) > table tfoot td > a:first-child:last-child { + margin: 0; + width: 100%; + padding: 0.5em; +} +.batch-table th:hover td:not([rowspan])::after, +.table th:hover td:not([rowspan])::after, +:not(.batch-table__row__content) > table th:hover td:not([rowspan])::after, +.batch-table tr:hover td:not([rowspan])::after, +.table tr:hover td:not([rowspan])::after, +:not(.batch-table__row__content) > table tr:hover td:not([rowspan])::after, +.batch-table th:hover th:not([rowspan])::after, +.table th:hover th:not([rowspan])::after, +:not(.batch-table__row__content) > table th:hover th:not([rowspan])::after, +.batch-table tr:hover th:not([rowspan])::after, +.table tr:hover th:not([rowspan])::after, +:not(.batch-table__row__content) > table tr:hover th:not([rowspan])::after { + opacity: 1 !important; +} +.batch-table th [rowspan]:hover ~ td::after, +.table th [rowspan]:hover ~ td::after, +:not(.batch-table__row__content) > table th [rowspan]:hover ~ td::after, +.batch-table tr [rowspan]:hover ~ td::after, +.table tr [rowspan]:hover ~ td::after, +:not(.batch-table__row__content) > table tr [rowspan]:hover ~ td::after { + opacity: 0 !important; +} +.batch-table th [rowspan]::after, +.table th [rowspan]::after, +:not(.batch-table__row__content) > table th [rowspan]::after, +.batch-table tr [rowspan]::after, +.table tr [rowspan]::after, +:not(.batch-table__row__content) > table tr [rowspan]::after { + inset-inline: -900px; +} +.layout-multiple-columns.layout-multiple-columns { + --column-header-height: 45px; +} +.layout-multiple-columns.layout-multiple-columns .column-header, +.layout-multiple-columns.layout-multiple-columns .scrollable, +.layout-multiple-columns.layout-multiple-columns .column-back-button, +.layout-multiple-columns.layout-multiple-columns .account__header__image { + border-radius: 0 !important; + gap: 0 !important; +} +.layout-multiple-columns.layout-multiple-columns .columns-area { + background: none !important; + height: 100%; +} +.layout-multiple-columns.layout-multiple-columns .columns-area > div { + border: 0 !important; + padding: 0 !important; +} +.layout-multiple-columns.layout-multiple-columns .columns-area > div:not(.drawer):not(:last-child) { + margin-inline-end: 2px !important; +} +.layout-multiple-columns.layout-multiple-columns .columns-area > div.column { + flex-grow: 1; + max-width: 600px; +} +.layout-multiple-columns.layout-multiple-columns .columns-area > div:first-child { + margin-inline-start: auto !important; +} +.layout-multiple-columns.layout-multiple-columns .columns-area > div:last-child { + margin-inline-end: auto !important; +} +.layout-multiple-columns.layout-multiple-columns .drawer.drawer { + padding-top: 15px !important; + overflow: clip; + flex-grow: 1; + max-width: 350px; +} +.drawer__header { + border-radius: var(--radius-round); + background: var(--elevated-color); + margin-inline: 15px; + overflow: hidden; + border: 0 !important; +} +.drawer__header a { + border: 0; +} +.drawer__header a:first-child { + padding-inline-start: 15px !important; +} +.drawer__header a:last-child { + padding-inline-end: 15px !important; +} +.layout-multiple-columns.layout-multiple-columns .drawer.drawer .search { + z-index: 2; + margin-inline: 15px; + margin-bottom: 0; +} +.layout-multiple-columns.layout-multiple-columns .drawer.drawer > .drawer__pager { + border: 0; + overflow: visible !important; +} +.layout-multiple-columns.layout-multiple-columns .drawer.drawer .drawer__inner:not(.darker) { + margin-top: -20px; + padding-top: 30px; + height: unset; + bottom: 0; +} +.layout-multiple-columns.layout-multiple-columns .drawer.drawer .drawer__inner__mastodon { + margin-inline: -6px; + z-index: -1; +} +.layout-multiple-columns.layout-multiple-columns .compose-form { + margin-inline: 5px; +} +.layout-multiple-columns.layout-multiple-columns .drawer__inner:not(.darker), +.layout-multiple-columns.layout-multiple-columns .drawer__inner__mastodon { + background-color: transparent !important; +} +.layout-multiple-columns.layout-multiple-columns .darker { + background-color: var(--surface-background-color); + border-radius: var(--radius) var(--radius) 0 0; + top: 10px; + width: unset; + inset-inline: 2px; +} +.layout-multiple-columns.layout-multiple-columns .column { + background: none; +} +.layout-multiple-columns.layout-multiple-columns .column::after { + content: ""; + position: absolute; + inset: 0; + top: var(--column-header-height); + background: var(--background-color); + z-index: -1; +} +.layout-multiple-columns.layout-multiple-columns .column::before, +.layout-multiple-columns.layout-multiple-columns .column::after { + top: var(--column-header-height); + border-radius: var(--radius) var(--radius) 0 0; +} +.layout-multiple-columns.layout-multiple-columns .column-back-button, +.layout-multiple-columns.layout-multiple-columns .column-header__wrapper { + height: var(--column-header-height); +} +.layout-multiple-columns.layout-multiple-columns .column-back-button.active, +.layout-multiple-columns.layout-multiple-columns .column-header__wrapper.active { + box-shadow: none; +} +.layout-multiple-columns.layout-multiple-columns .column-back-button.active::before, +.layout-multiple-columns.layout-multiple-columns .column-header__wrapper.active::before { + inset-inline: var(--radius); +} +.layout-multiple-columns.layout-multiple-columns .column-back-button .column-header, +.layout-multiple-columns.layout-multiple-columns .column-header__wrapper .column-header { + border: 0 !important; + height: var(--column-header-height); +} +.layout-multiple-columns.layout-multiple-columns .column-back-button .column-header__buttons, +.layout-multiple-columns.layout-multiple-columns .column-header__wrapper .column-header__buttons { + height: 100%; +} +.layout-multiple-columns.layout-multiple-columns .column-back-button svg, +.layout-multiple-columns.layout-multiple-columns .column-header__wrapper svg { + height: 1.4em; +} +.layout-multiple-columns.layout-multiple-columns .column-back-button + .scrollable.scrollable, +.layout-multiple-columns.layout-multiple-columns .column-header__wrapper + .scrollable.scrollable { + border-radius: var(--radius) var(--radius) 0 0 !important; + overflow-y: scroll; +} +.layout-multiple-columns.layout-multiple-columns .getting-started__trends { + padding: 0px 20px; +} +.column[aria-labelledby="Misc"] > .scrollable, +.column[aria-labelledby="Getting-started"] > .scrollable, +.getting-started { position: relative; padding: 5px 10px !important; } -.getting-started .getting-started__wrapper, -#mastodon .column[aria-labelledby="Getting-started"] > .scrollable.scrollable.scrollable .getting-started__wrapper { +.column[aria-labelledby="Misc"] > .scrollable .getting-started__wrapper, +.column[aria-labelledby="Getting-started"] > .scrollable .getting-started__wrapper, +.getting-started .getting-started__wrapper { background: none; } -.getting-started .getting-started__wrapper a, -#mastodon .column[aria-labelledby="Getting-started"] > .scrollable.scrollable.scrollable .getting-started__wrapper a, -.getting-started .getting-started__wrapper .column-subheading, -#mastodon .column[aria-labelledby="Getting-started"] > .scrollable.scrollable.scrollable .getting-started__wrapper .column-subheading { +.column[aria-labelledby="Misc"] > .scrollable .column-link, +.column[aria-labelledby="Getting-started"] > .scrollable .column-link, +.getting-started .column-link, +.column[aria-labelledby="Misc"] > .scrollable .column-subheading, +.column[aria-labelledby="Getting-started"] > .scrollable .column-subheading, +.getting-started .column-subheading { border: 0 !important; - padding: 20px; + padding: 20px !important; background: none; } -.getting-started .getting-started__footer, -#mastodon .column[aria-labelledby="Getting-started"] > .scrollable.scrollable.scrollable .getting-started__footer { +.column[aria-labelledby="Misc"] > .scrollable .getting-started__footer, +.column[aria-labelledby="Getting-started"] > .scrollable .getting-started__footer, +.getting-started .getting-started__footer { padding-inline: 20px; } -.getting-started .getting-started__footer a span, -#mastodon .column[aria-labelledby="Getting-started"] > .scrollable.scrollable.scrollable .getting-started__footer a span { +.column[aria-labelledby="Misc"] > .scrollable .getting-started__footer a span, +.column[aria-labelledby="Getting-started"] > .scrollable .getting-started__footer a span, +.getting-started .getting-started__footer a span { font-size: 1.1em !important; line-height: 2; } -.about .account { - padding: 0 !important; - overflow: visible !important; -} -.about .account::after { - content: unset !important; -} -.about .about__meta { - border-radius: var(--radius); -} -.about .about__section__title { - position: sticky; - top: 0; - z-index: 2; - border-radius: 0 !important; -} -.about .about__section__body { - animation: slideDowFade 0.3s 0.1s backwards cubic-bezier(0, 1, 0, 1.2); -} -.about .about__section { - margin: 10px 0px !important; - margin-top: 20px; - border-radius: var(--radius); - overflow: clip; - transition: margin 0.2s cubic-bezier(0, 1, 0, 1), border-radius 0.2s; -} -.about .about__section:not(.active) { - border: 0 !important; -} -.about .about__section:not(.active) .about__section__title { - background: var(--elevated-color) !important; - border-radius: var(--radius); -} -#mastodon.modal-layout { - overflow: hidden; -} -#mastodon.modal-layout .container-alt { - background: inherit; - height: 100%; -} -#mastodon.modal-layout .container-alt .public-layout { - padding: 0 !important; -} -#mastodon.modal-layout .container-alt .form-container { - max-width: 500px !important; - padding: 0; - background: inherit; - display: flex; - flex-direction: column; - justify-content: center; - height: 100%; -} -#mastodon.modal-layout .container-alt .form-container h2 { - margin: 0; - padding: 20px; - font-weight: 600; -} -#mastodon.modal-layout .container-alt .form-container .follow-prompt { - margin: 0; - border-radius: 0 0 var(--radius) var(--radius); - overflow-y: auto; -} -#mastodon.modal-layout .container-alt .form-container .follow-prompt .activity-stream { - margin: 0 !important; -} -#mastodon.modal-layout .container-alt .entry { - border-radius: var(--radius) !important; -} -#mastodon.modal-layout #new_remote_follow { - position: sticky; - bottom: 0; - padding: 20px; - padding-bottom: 60px; - background: inherit; -} -@media (min-width: 890px) and (max-width: 1174px) { - .layout-single-column #mastodon .ui__header { +@media (min-width: 890px) and (max-width: 1175px) { + .ui__header { background: none !important; - border: 0; - margin-inline-end: 280px; - padding-top: 12px; - position: static; backdrop-filter: none; + position: relative; + margin-inline-end: 285px; + border: 0 !important; } - .layout-single-column #mastodon .columns-area__panels__main { - margin-inline: 10px !important; - margin-top: 10px; - } - .layout-single-column #mastodon .columns-area__panels__main .columns-area { - padding-bottom: 0 !important; - } - .layout-single-column #mastodon .dismissable-banner { - border-top-left-radius: 0 !important; - } - .layout-single-column #mastodon .navigation-panel { - background: none; - border: 0; - width: 265px !important; - padding-bottom: 10px; + .navigation-panel { + padding-inline: 10px; } } @media (max-width: 889px) { - #mastodon .scrollable:not(.scrollable--flex) { - padding: 0px !important; - padding-bottom: 40vh !important; - } - #mastodon .scrollable:not(.scrollable--flex)::before { - content: ""; - position: absolute; - inset: 0; - background-color: inherit; - z-index: -1; - } - #mastodon .scrollable:not(.scrollable--flex) .account-timeline__header, - #mastodon .scrollable:not(.scrollable--flex) .dismissable-banner { - margin: 0px !important; - } - #mastodon .focusable, - #mastodon .entry, - #mastodon .statuses-grid__item .detailed-status, - #mastodon .trends__item, - #mastodon .story, - #mastodon .account-card, - #mastodon .scrollable :not(.focusable) > .account, - #mastodon .timeline-hint { - border-radius: 0; - } - #mastodon .focusable::before, - #mastodon .entry::before, - #mastodon .statuses-grid__item .detailed-status::before, - #mastodon .trends__item::before, - #mastodon .story::before, - #mastodon .account-card::before, - #mastodon .scrollable :not(.focusable) > .account::before, - #mastodon .timeline-hint::before { - border-radius: 0 !important; - } - #mastodon .focusable::after, - #mastodon .entry::after, - #mastodon .statuses-grid__item .detailed-status::after, - #mastodon .trends__item::after, - #mastodon .story::after, - #mastodon .account-card::after, - #mastodon .scrollable :not(.focusable) > .account::after, - #mastodon .timeline-hint::after { - inset-inline: 0 !important; - } - #mastodon [class*="explore__"] > * { - border-radius: var(--radius); - } - #mastodon .detailed-status__wrapper { - margin: 0 !important; - } - #mastodon .status__action-bar { - margin-bottom: 0px; - gap: 0; - margin-inline-end: 0 !important; - } - #mastodon .status__action-bar :not(i):not(.status__action-bar-spacer) { - display: flex; - flex-grow: 9999; - justify-content: center !important; - max-width: 55px; - min-width: max-content; - } - #mastodon .status__action-bar > .icon-button:first-child { - margin-inline-start: -8px !important; - } - #mastodon .status__action-bar, - #mastodon .detailed-status__action-bar, - #mastodon .picture-in-picture__footer { - flex-wrap: wrap; - } - #mastodon .column-header { - border-inline: 0; - } .ui__header { border-bottom: 0; box-sizing: content-box; - min-height: 55px; height: auto; position: relative; box-sizing: border-box; gap: 5px 10px; flex-wrap: wrap; - padding-block: 8px; + padding-block: 12px; border: 0 !important; backdrop-filter: none !important; background: none !important; @@ -3389,7 +2710,7 @@ a:focus-visible, right: 0; } .ui__header [href="/search"] { - margin-inline-end: 5px; + display: none; } .ui__header [href="/publish"] { position: fixed; @@ -3454,11 +2775,11 @@ a:focus-visible, position: static !important; width: unset; } - #mastodon .tabs-bar__wrapper { + .tabs-bar__wrapper { top: 0; z-index: 200; } - #mastodon .tabs-bar__wrapper::after { + .tabs-bar__wrapper::after { content: ""; position: absolute; inset: 0; @@ -3466,103 +2787,73 @@ a:focus-visible, pointer-events: none; z-index: 2; } - #mastodon .column-back-button.column-back-button { - border-radius: 0 !important; - } @supports selector(.foxxo:has(.waf)) { - #mastodon [href="/publish"] { + [href="/publish"] { bottom: 70px !important; } - #mastodon .columns-area__panels { + .columns-area__panels { flex-direction: column; } - #mastodon .columns-area__panels__main { + .columns-area__panels__main { width: 100%; border-radius: var(--radius) !important; margin: 0 !important; border: 0 !important; overflow: clip !important; + flex-grow: 1; } - #mastodon .tabs-bar__wrapper { + .tabs-bar__wrapper { transition: none !important; } - #mastodon .columns-area__panels__pane--navigational { + .columns-area__panels__pane--navigational { display: contents; } - #mastodon .columns-area__panels__pane--navigational .columns-area__panels__pane__inner { - position: relative !important; - inset: unset !important; + .columns-area__panels__pane--navigational .columns-area__panels__pane__inner { + position: static !important; order: -1; width: unset; height: auto; white-space: nowrap; } - #mastodon .columns-area__panels__pane--navigational .navigation-panel { + .columns-area__panels__pane--navigational .navigation-panel { flex-direction: row; margin: 0; - padding: 0; - padding: 0 8px; - height: 5em; + padding: 12px; border-top: 1px solid; - overflow: scroll hidden; align-items: center; + overflow: visible; + height: auto; } - #mastodon .columns-area__panels__pane--navigational .navigation-panel::-webkit-scrollbar { + .columns-area__panels__pane--navigational .navigation-panel .flex-spacer { display: none; } - #mastodon .columns-area__panels__pane--navigational hr { + .columns-area__panels__pane--navigational hr { display: none; } - #mastodon .columns-area__panels__pane--navigational .trends__item__name > span, - #mastodon .columns-area__panels__pane--navigational .trends__item__sparkline { + .columns-area__panels__pane--navigational .trends__item__name > span, + .columns-area__panels__pane--navigational .trends__item__sparkline { display: none; } - #mastodon .columns-area__panels__pane--navigational .navigation-panel__legal, - #mastodon .columns-area__panels__pane--navigational h4, - #mastodon .columns-area__panels__pane--navigational .trends__item, - #mastodon .columns-area__panels__pane--navigational .trends__item__name { - all: unset; - display: contents !important; - } - #mastodon .columns-area__panels__pane--navigational .navigation-panel__legal::before, - #mastodon .columns-area__panels__pane--navigational h4::before, - #mastodon .columns-area__panels__pane--navigational .trends__item::before, - #mastodon .columns-area__panels__pane--navigational .trends__item__name::before, - #mastodon .columns-area__panels__pane--navigational .navigation-panel__legal::after, - #mastodon .columns-area__panels__pane--navigational h4::after, - #mastodon .columns-area__panels__pane--navigational .trends__item::after, - #mastodon .columns-area__panels__pane--navigational .trends__item__name::after { - content: unset; - } - #mastodon .columns-area__panels__pane--navigational h4 a span::after { + .columns-area__panels__pane--navigational h4 a span::after { content: ":" !important; } - #mastodon .columns-area__panels__pane--navigational:has(.getting-started__trends):has(.navigation-panel__sign-in-banner) .flex-spacer { - border-right: 1px solid var(--border-color); - height: 50%; - margin: 10px; + .columns-area__panels__pane--navigational .navigation-panel__legal { + display: contents; } - #mastodon .columns-area__panels__pane--navigational:has(.getting-started__trends):has(.navigation-panel__sign-in-banner) .getting-started__trends { - all: unset; - display: contents !important; - } - #mastodon .columns-area__panels__pane--navigational:has(.getting-started__trends) .trends__item:last-child a { - position: relative; - z-index: 3; - } - #mastodon .columns-area__panels__pane--navigational a, - #mastodon .columns-area__panels__pane--navigational h4 > span { + .columns-area__panels__pane--navigational a { position: relative; overflow: hidden; border-radius: var(--radius); font-weight: 600; font-size: 1.1em; + flex-grow: 1; text-align: center; min-width: max-content; + justify-content: center; transition: padding 0.2s; + padding-inline: 6px; } - #mastodon .columns-area__panels__pane--navigational a::before, - #mastodon .columns-area__panels__pane--navigational h4 > span::before { + .columns-area__panels__pane--navigational a::before { content: ""; position: absolute; inset: 0; @@ -3571,83 +2862,80 @@ a:focus-visible, border-radius: inherit; transition: inset 0.4s cubic-bezier(0, 0, 0, 1.2), opacity 0.2s; } - #mastodon .columns-area__panels__pane--navigational a svg, - #mastodon .columns-area__panels__pane--navigational h4 > span svg { + .columns-area__panels__pane--navigational a svg { width: 1em; height: 1em; + margin-inline-end: 0 !important; } - #mastodon .columns-area__panels__pane--navigational a.active, - #mastodon .columns-area__panels__pane--navigational h4 > span.active { - padding-inline: 1.2em; - margin-inline: 4px; + .columns-area__panels__pane--navigational a.active { position: relative; } - #mastodon .columns-area__panels__pane--navigational a.active::before, - #mastodon .columns-area__panels__pane--navigational h4 > span.active::before { + .columns-area__panels__pane--navigational a.active::before { inset: 0 !important; opacity: 0.1; } - #mastodon .columns-area__panels__pane--navigational span { - display: unset; + .columns-area__panels__pane--navigational span { + display: unset !important; + width: 0; + flex-grow: 1; + max-width: max-content; + overflow: hidden; + text-overflow: ellipsis; } } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .columns-area__panels__main { + :not(:has(.navigation-panel__sign-in-banner)) .columns-area__panels__main { margin-top: 2px !important; } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .ui__header { + :not(:has(.navigation-panel__sign-in-banner)) .ui__header { z-index: 199; } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .ui__header::before, - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .ui__header::after { + :not(:has(.navigation-panel__sign-in-banner)) .ui__header::before, + :not(:has(.navigation-panel__sign-in-banner)) .ui__header::after { all: unset; content: ""; position: fixed; bottom: 0; inset-inline: 0; - background: var(--background-color-tint, inherit); - border-top: 1px solid var(--divider); + background: var(--background-color); z-index: 200; - height: 61px; + height: 60px; visibility: visible; z-index: -1; - backdrop-filter: blur(10px); border-top: 1px solid var(--background-border-color); } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .ui__header::after { + :not(:has(.navigation-panel__sign-in-banner)) .ui__header::after { background: var(--elevated-tint); - height: 60px; + border: 0; } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .navigation-panel { + :not(:has(.navigation-panel__sign-in-banner)) .navigation-panel { + flex-wrap: wrap; border-top: none; - margin-top: -10px; + margin-bottom: 8px; + padding-block: 0; } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .navigation-panel a { - font-size: 1em; + :not(:has(.navigation-panel__sign-in-banner)) .navigation-panel a span { + display: none !important; } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .navigation-panel::after { - content: ""; - display: block; - position: sticky; - right: -20px; - min-width: 150px; - height: 100%; - margin-left: -50px; - background: inherit; - mask: linear-gradient(to right, transparent, #000); - -webkit-mask: linear-gradient(to right, transparent, #000); - pointer-events: none; - } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column { - min-height: 100vh; + :not(:has(.navigation-panel__sign-in-banner)) .navigation-panel [href="/settings/preferences"] { + position: absolute; + top: 12px; + inset-inline-end: 60px; + z-index: 900; + padding-block: 0; + height: 35px; + width: 35px; + box-sizing: border-box; + margin: 0; + border: 1px solid var(--border-color); } :root { --selector: '.column-link[href='/home'],.column-link[href='/notifications'],.column-link[href='/explore'],.column-link[href='/public/local'],.column-link[href='/lists']'; } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/home'], - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/notifications'], - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/explore'], - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/public/local'], - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/lists'] { + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/home'], + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/notifications'], + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/explore'], + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/public/local'], + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/lists'] { position: fixed !important; display: flex; flex-direction: column; @@ -3661,984 +2949,310 @@ a:focus-visible, justify-content: center; width: 19%; left: 0; - transform: translateX(10%); box-sizing: border-box; padding: 1px !important; } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/home']::before, - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/notifications']::before, - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/explore']::before, - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/public/local']::before, - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/lists']::before { + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/home']::before, + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/notifications']::before, + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/explore']::before, + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/public/local']::before, + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/lists']::before { inset-inline: 6px; } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/home'] span, - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/notifications'] span, - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/explore'] span, - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/public/local'] span, - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/lists'] span { + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/home'] span, + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/notifications'] span, + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/explore'] span, + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/public/local'] span, + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/lists'] span { margin: 0; font-size: 0; line-height: 1; opacity: 0; transition: font-size 0.4s, margin 0.7s, opacity 0.2s; } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/home'] svg, - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/notifications'] svg, - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/explore'] svg, - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/public/local'] svg, - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/lists'] svg { + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/home'] svg, + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/notifications'] svg, + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/explore'] svg, + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/public/local'] svg, + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/lists'] svg { width: 24px; height: 24px; transition: transform 0.1s; } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/home'] { - left: 0%; + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/home'] { + left: 2%; } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/notifications'] { - left: 19.25%; + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/notifications'] { + left: 21.25%; } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/explore'] { - left: 38.5%; + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/explore'] { + left: 40.5%; } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/public/local'] { - left: 57.75%; + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/public/local'] { + left: 59.75%; } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/lists'] { - left: 77%; - } - #mastodon:not(:has(.navigation-panel__sign-in-banner)):has(.column-link.active:not([href='/home']):not([href='/notifications']):not([href='/explore']):not([href='/public/local']):not([href='/lists']):not([href^="/lists"])) .tabs-bar__wrapper { - position: static; - height: auto; - z-index: unset; - margin: 0; - top: 0; - inset-inline: 0; - visibility: hidden; - } - #mastodon:not(:has(.navigation-panel__sign-in-banner)):has(.column-link.active:not([href='/home']):not([href='/notifications']):not([href='/explore']):not([href='/public/local']):not([href='/lists']):not([href^="/lists"])) .tabs-bar__wrapper #tabs-bar__portal, - #mastodon:not(:has(.navigation-panel__sign-in-banner)):has(.column-link.active:not([href='/home']):not([href='/notifications']):not([href='/explore']):not([href='/public/local']):not([href='/lists']):not([href^="/lists"])) .tabs-bar__wrapper .column-header__wrapper, - #mastodon:not(:has(.navigation-panel__sign-in-banner)):has(.column-link.active:not([href='/home']):not([href='/notifications']):not([href='/explore']):not([href='/public/local']):not([href='/lists']):not([href^="/lists"])) .tabs-bar__wrapper .column-header { - height: 100%; - } - #mastodon:not(:has(.navigation-panel__sign-in-banner)):has(.column-link.active:not([href='/home']):not([href='/notifications']):not([href='/explore']):not([href='/public/local']):not([href='/lists']):not([href^="/lists"])) .tabs-bar__wrapper .column-header__buttons { - visibility: visible; - height: auto; - width: 100%; - } - #mastodon:not(:has(.navigation-panel__sign-in-banner)):has(.column-link.active:not([href='/home']):not([href='/notifications']):not([href='/explore']):not([href='/public/local']):not([href='/lists']):not([href^="/lists"])) .tabs-bar__wrapper .column-header__buttons button { - height: 50px; - width: 100% !important; - text-align: left; - transform: none; - } - #mastodon:not(:has(.navigation-panel__sign-in-banner)):has(.column-link.active:not([href='/home']):not([href='/notifications']):not([href='/explore']):not([href='/public/local']):not([href='/lists']):not([href^="/lists"])) .tabs-bar__wrapper .column-header__buttons button::after { - content: attr(title); - margin-left: 1em; - font-weight: 600; - font-size: 0.9em; - } - #mastodon:not(:has(.navigation-panel__sign-in-banner)):has(.column-link.active:not([href='/home']):not([href='/notifications']):not([href='/explore']):not([href='/public/local']):not([href='/lists']):not([href^="/lists"])) .tabs-bar__wrapper .column-header__back-button, - #mastodon:not(:has(.navigation-panel__sign-in-banner)):has(.column-link.active:not([href='/home']):not([href='/notifications']):not([href='/explore']):not([href='/public/local']):not([href='/lists']):not([href^="/lists"])) .tabs-bar__wrapper .column-header > button { - display: none; - } - #mastodon:not(:has(.navigation-panel__sign-in-banner)):has(.column-link.active:not([href='/home']):not([href='/notifications']):not([href='/explore']):not([href='/public/local']):not([href='/lists']):not([href^="/lists"])) .tabs-bar__wrapper .column-header__collapsible { - visibility: visible; - } - #mastodon .columns-area__panels__main { - padding: 0 !important; - max-width: unset; - flex-grow: 1; - margin-top: 1px; - } - .is-composing .columns-area__panels__main { - padding-bottom: 40px !important; - } - #mastodon .columns-area__panels__main .scrollable, - #mastodon .columns-area__panels__main .account__header__image, - #mastodon .columns-area__panels__main > div { - border-radius: 0 !important; - } - #mastodon .dismissable-banner { - margin: 0; - padding-left: 4px; - } - #mastodon .status .status__avatar { - width: 42px !important; - min-width: 45px !important; - height: 45px !important; - background-size: 45px !important; - } - #mastodon .status .status__avatar > div { - width: 100% !important; - height: 100% !important; - background-size: cover !important; - } - #mastodon .status::before { - content: unset; - } - #mastodon .status__action-bar { - margin-bottom: -5px; - } - #mastodon .status__action-bar .icon-button { - margin: 0 !important; - justify-content: center; - } - #mastodon .icon-button:after { - content: unset !important; - } - #mastodon .navigation-panel { - margin-top: -200px; - padding-top: 200px; - height: calc(100vh + 200px - 55px); - border: 0; - border-inline-start: 1px solid; - padding-bottom: 90px; - } - #mastodon .columns-area { - padding-bottom: 0 !important; - overflow: hidden !important; - } - #mastodon .getting-started { - padding: 20px; - padding-bottom: 60px; - } - #mastodon .getting-started__wrapper { - flex-grow: 1; - overflow: visible !important; - } - #mastodon .compose-form { - padding: 10px; - margin-bottom: 100px; - } - #mastodon .compose-form::before { - content: ""; - position: absolute; - inset: 0; - background: var(--elevated-color); - z-index: -1; - } - #mastodon .about.about.about { - padding-inline: 10px !important; - } - #mastodon .about.about.about .account { - margin-top: 0; - } - #mastodon .about.about.about .about__header__hero, - #mastodon .about.about.about .about__section.active { - margin-inline: -11px !important; - width: unset; - border-radius: 0; - } - #mastodon .about.about.about .about__section.active { - margin-block: 20px !important; - } - #mastodon .about.about.about .about__section { - margin-bottom: 0; - border-bottom: 1px solid; + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/lists'] { + left: 79%; } } -.admin-wrapper .sidebar-wrapper { - overflow: visible !important; - width: unset; +#hover-card, +.dropdown-menu { + border-radius: var(--radius); + animation: scaleIn 0.2s cubic-bezier(0, 0, 0, 1.1); } -.admin-wrapper .sidebar-wrapper .sidebar-wrapper__inner { - position: sticky; - top: 0; - max-height: 100vh !important; - overflow-y: auto !important; - pointer-events: all !important; - z-index: 100; +.dropdown-menu__container__list { + overflow: hidden auto; + border-radius: var(--radius); + max-height: 70vh; } -.admin-wrapper .sidebar-wrapper .sidebar-wrapper__inner .fa { - margin-inline-end: 1em !important; -} -.admin-wrapper .sidebar-wrapper .sidebar-wrapper__inner .sidebar > ul > li { +.dropdown-menu__item { overflow: hidden; - margin-inline: 15px !important; } -.admin-wrapper .sidebar-wrapper .sidebar-wrapper__inner .sidebar > ul > li > a:not(.selected) { - background: none; +.dropdown-menu__item a { + padding: 0.7em 1em !important; + transition: background-color 0.2s, color 0.2s; + min-width: 150px; } -.admin-wrapper .sidebar-wrapper .sidebar-wrapper__inner .sidebar > ul > li a { - display: flex !important; - align-items: center; - white-space: unset; +.dropdown-menu__separator { + margin: 0 !important; } -.admin-wrapper .sidebar-wrapper .sidebar-wrapper__inner .sidebar > ul > li.selected { - margin: 6px; +.interaction-modal { border-radius: var(--radius); + overflow-y: auto; + box-sizing: border-box; + width: 700px; + text-align: center; } -.admin-wrapper .sidebar-wrapper .sidebar-wrapper__inner .sidebar > ul > li.selected > a.selected { - font-weight: 600; - color: unset; - position: relative; - overflow: visible; - border-radius: 0 !important; - border: 0; -} -.admin-wrapper .sidebar-wrapper .sidebar-wrapper__inner .sidebar > ul > li.selected > a.selected::after { - content: ""; - position: absolute; - top: 100%; - inset-inline: 0; - height: 9999px; - background: inherit; - z-index: -1; -} -.admin-wrapper .sidebar-wrapper .sidebar-wrapper__inner .sidebar > ul > li > ul { - border-radius: var(--radius) !important; - overflow: hidden !important; - gap: 2px !important; - margin: 8px; - margin-top: 0; - background: none; -} -.admin-wrapper .sidebar-wrapper .sidebar-wrapper__inner .sidebar > ul > li > ul > li { - border-radius: 8px; -} -.admin-wrapper .sidebar-wrapper .sidebar-wrapper__inner .sidebar > ul > li > ul > li:not(:first-child):not(:last-child) { - margin-block: 2px; -} -.admin-wrapper .sidebar-wrapper .sidebar-wrapper__inner .sidebar > ul > li > ul > li a { - padding: 14px 16px; - font-weight: 600; - border: 0; -} -.admin-wrapper .sidebar-wrapper .sidebar-wrapper__inner .sidebar > ul > li > ul > li:not(.selected) a { - background-color: rgba(150,150,250,0.1); -} -.admin-wrapper .content__heading { - margin-bottom: 2em; -} -.admin-wrapper h4 { - margin: 0; - border-bottom: 0; -} -.admin-wrapper form > h4 { - margin-top: 2em !important; - border-bottom: 0 !important; - margin-bottom: 0 !important; -} -.admin-wrapper .flash-message, -.admin-wrapper .applications-list__item, -.admin-wrapper .filters-list__item { - border-radius: var(--radius); - border: 0; - overflow: clip; -} -.admin-wrapper .fields-row { - margin-inline: 0; - border-radius: var(--radius); - overflow: clip; - padding-top: 0; - gap: 2px; +.interaction-modal__choices { + gap: 10px; display: flex; flex-wrap: wrap; } -.admin-wrapper .fields-group:not(.fields-row__column), -.admin-wrapper .fields-row { - margin-bottom: 1em !important; -} -.admin-wrapper .fields-row > .fields-row__column { - max-width: unset; - width: 300px; - border-radius: 0 !important; - display: flex; - flex-direction: column; - flex-grow: 1; - margin: 0 !important; -} -.admin-wrapper .fields-row > .fields-row__column .fields-group { - border-radius: 0 !important; - margin: 0 !important; -} -.admin-wrapper .fields-row > .fields-row__column .fields-group > .with_block_label { - display: flex; - flex-direction: column; - height: 100%; -} -.admin-wrapper .fields-row > .fields-row__column .fields-group > .with_block_label > .label_input { - flex-grow: 1; -} -.admin-wrapper .fields-row > .fields-row__column .fields-group > .with_block_label > .label_input > textarea { - min-height: 100%; -} -.admin-wrapper .fields-row > .fields-row__column > :last-child { - flex-grow: 1; - align-items: flex-start; - border: 0; -} -.admin-wrapper .fields-row > .fields-row__column > :not(:first-child):not(:last-child) { - padding-block: 0.5em !important; - margin-block: -3px; -} -.admin-wrapper :not(.fields-row__column) > .fields-group, -.admin-wrapper .fields-row > *, -.admin-wrapper .label_input > ul, -.admin-wrapper .label_input__wrapper > ul, -.admin-wrapper .radio_buttons > ul, -.admin-wrapper .with_block_label.radio_buttons .label_input { +.interaction-modal__choices .interaction-modal__choices__choice { + max-height: 50vh; + overflow-y: auto; + border: 1px solid var(--border-color); + padding: 24px; + margin: 0; border-radius: var(--radius); - overflow: clip; - padding: 0; - display: flex; - flex-direction: column; - gap: 2px; -} -.admin-wrapper :not(.fields-row__column) > .fields-group > *, -.admin-wrapper .fields-row > * > *, -.admin-wrapper .label_input > ul > *, -.admin-wrapper .label_input__wrapper > ul > *, -.admin-wrapper .radio_buttons > ul > *, -.admin-wrapper .with_block_label.radio_buttons .label_input > * { - background-color: var(--elevated-color); - padding: 0.8rem; - margin-block: 0px; + transition: background 0.2s; position: relative; - border-radius: 0 !important; } -.admin-wrapper :not(.fields-row__column) > .fields-group > *:not(p):not(h6):not(.input-copy)::after, -.admin-wrapper .fields-row > * > *:not(p):not(h6):not(.input-copy)::after, -.admin-wrapper .label_input > ul > *:not(p):not(h6):not(.input-copy)::after, -.admin-wrapper .label_input__wrapper > ul > *:not(p):not(h6):not(.input-copy)::after, -.admin-wrapper .radio_buttons > ul > *:not(p):not(h6):not(.input-copy)::after, -.admin-wrapper .with_block_label.radio_buttons .label_input > *:not(p):not(h6):not(.input-copy)::after { - content: ""; - position: absolute; - inset: 0; - background-color: var(--hover-color); - z-index: -1; - opacity: 0; - transition: opacity 0.2s; -} -.admin-wrapper :not(.fields-row__column) > .fields-group > *:not(p):not(h6):not(.input-copy):hover::after, -.admin-wrapper .fields-row > * > *:not(p):not(h6):not(.input-copy):hover::after, -.admin-wrapper .label_input > ul > *:not(p):not(h6):not(.input-copy):hover::after, -.admin-wrapper .label_input__wrapper > ul > *:not(p):not(h6):not(.input-copy):hover::after, -.admin-wrapper .radio_buttons > ul > *:not(p):not(h6):not(.input-copy):hover::after, -.admin-wrapper .with_block_label.radio_buttons .label_input > *:not(p):not(h6):not(.input-copy):hover::after, -.admin-wrapper :not(.fields-row__column) > .fields-group > *:not(p):not(h6):not(.input-copy):focus-within::after, -.admin-wrapper .fields-row > * > *:not(p):not(h6):not(.input-copy):focus-within::after, -.admin-wrapper .label_input > ul > *:not(p):not(h6):not(.input-copy):focus-within::after, -.admin-wrapper .label_input__wrapper > ul > *:not(p):not(h6):not(.input-copy):focus-within::after, -.admin-wrapper .radio_buttons > ul > *:not(p):not(h6):not(.input-copy):focus-within::after, -.admin-wrapper .with_block_label.radio_buttons .label_input > *:not(p):not(h6):not(.input-copy):focus-within::after { - opacity: 1; -} -.admin-wrapper :not(.fields-row__column) > .fields-group .input-copy__wrapper, -.admin-wrapper .fields-row > * .input-copy__wrapper, -.admin-wrapper .label_input > ul .input-copy__wrapper, -.admin-wrapper .label_input__wrapper > ul .input-copy__wrapper, -.admin-wrapper .radio_buttons > ul .input-copy__wrapper, -.admin-wrapper .with_block_label.radio_buttons .label_input .input-copy__wrapper { - border: 1px solid var(--border-color-2); - border-radius: var(--radius); -} -.admin-wrapper :not(.fields-row__column) > .fields-group > .input, -.admin-wrapper .fields-row > * > .input, -.admin-wrapper .label_input > ul > .input, -.admin-wrapper .label_input__wrapper > ul > .input, -.admin-wrapper .radio_buttons > ul > .input, -.admin-wrapper .with_block_label.radio_buttons .label_input > .input, -.admin-wrapper :not(.fields-row__column) > .fields-group .checkbox, -.admin-wrapper .fields-row > * .checkbox, -.admin-wrapper .label_input > ul .checkbox, -.admin-wrapper .label_input__wrapper > ul .checkbox, -.admin-wrapper .radio_buttons > ul .checkbox, -.admin-wrapper .with_block_label.radio_buttons .label_input .checkbox, -.admin-wrapper :not(.fields-row__column) > .fields-group .radio, -.admin-wrapper .fields-row > * .radio, -.admin-wrapper .label_input > ul .radio, -.admin-wrapper .label_input__wrapper > ul .radio, -.admin-wrapper .radio_buttons > ul .radio, -.admin-wrapper .with_block_label.radio_buttons .label_input .radio { - flex-grow: 1; -} -.admin-wrapper :not(.fields-row__column) > .fields-group > .input:not(:last-child), -.admin-wrapper .fields-row > * > .input:not(:last-child), -.admin-wrapper .label_input > ul > .input:not(:last-child), -.admin-wrapper .label_input__wrapper > ul > .input:not(:last-child), -.admin-wrapper .radio_buttons > ul > .input:not(:last-child), -.admin-wrapper .with_block_label.radio_buttons .label_input > .input:not(:last-child), -.admin-wrapper :not(.fields-row__column) > .fields-group .checkbox:not(:last-child), -.admin-wrapper .fields-row > * .checkbox:not(:last-child), -.admin-wrapper .label_input > ul .checkbox:not(:last-child), -.admin-wrapper .label_input__wrapper > ul .checkbox:not(:last-child), -.admin-wrapper .radio_buttons > ul .checkbox:not(:last-child), -.admin-wrapper .with_block_label.radio_buttons .label_input .checkbox:not(:last-child), -.admin-wrapper :not(.fields-row__column) > .fields-group .radio:not(:last-child), -.admin-wrapper .fields-row > * .radio:not(:last-child), -.admin-wrapper .label_input > ul .radio:not(:last-child), -.admin-wrapper .label_input__wrapper > ul .radio:not(:last-child), -.admin-wrapper .radio_buttons > ul .radio:not(:last-child), -.admin-wrapper .with_block_label.radio_buttons .label_input .radio:not(:last-child) { - margin-bottom: 2px; +.interaction-modal__choices .prose:last-child { margin-bottom: 0; } -.admin-wrapper :not(.fields-row__column) > .fields-group > .input.radio .hint, -.admin-wrapper .fields-row > * > .input.radio .hint, -.admin-wrapper .label_input > ul > .input.radio .hint, -.admin-wrapper .label_input__wrapper > ul > .input.radio .hint, -.admin-wrapper .radio_buttons > ul > .input.radio .hint, -.admin-wrapper .with_block_label.radio_buttons .label_input > .input.radio .hint, -.admin-wrapper :not(.fields-row__column) > .fields-group .checkbox.radio .hint, -.admin-wrapper .fields-row > * .checkbox.radio .hint, -.admin-wrapper .label_input > ul .checkbox.radio .hint, -.admin-wrapper .label_input__wrapper > ul .checkbox.radio .hint, -.admin-wrapper .radio_buttons > ul .checkbox.radio .hint, -.admin-wrapper .with_block_label.radio_buttons .label_input .checkbox.radio .hint, -.admin-wrapper :not(.fields-row__column) > .fields-group .radio.radio .hint, -.admin-wrapper .fields-row > * .radio.radio .hint, -.admin-wrapper .label_input > ul .radio.radio .hint, -.admin-wrapper .label_input__wrapper > ul .radio.radio .hint, -.admin-wrapper .radio_buttons > ul .radio.radio .hint, -.admin-wrapper .with_block_label.radio_buttons .label_input .radio.radio .hint, -.admin-wrapper :not(.fields-row__column) > .fields-group > .input.checkbox .hint, -.admin-wrapper .fields-row > * > .input.checkbox .hint, -.admin-wrapper .label_input > ul > .input.checkbox .hint, -.admin-wrapper .label_input__wrapper > ul > .input.checkbox .hint, -.admin-wrapper .radio_buttons > ul > .input.checkbox .hint, -.admin-wrapper .with_block_label.radio_buttons .label_input > .input.checkbox .hint, -.admin-wrapper :not(.fields-row__column) > .fields-group .checkbox.checkbox .hint, -.admin-wrapper .fields-row > * .checkbox.checkbox .hint, -.admin-wrapper .label_input > ul .checkbox.checkbox .hint, -.admin-wrapper .label_input__wrapper > ul .checkbox.checkbox .hint, -.admin-wrapper .radio_buttons > ul .checkbox.checkbox .hint, -.admin-wrapper .with_block_label.radio_buttons .label_input .checkbox.checkbox .hint, -.admin-wrapper :not(.fields-row__column) > .fields-group .radio.checkbox .hint, -.admin-wrapper .fields-row > * .radio.checkbox .hint, -.admin-wrapper .label_input > ul .radio.checkbox .hint, -.admin-wrapper .label_input__wrapper > ul .radio.checkbox .hint, -.admin-wrapper .radio_buttons > ul .radio.checkbox .hint, -.admin-wrapper .with_block_label.radio_buttons .label_input .radio.checkbox .hint, -.admin-wrapper :not(.fields-row__column) > .fields-group > .input.radio label, -.admin-wrapper .fields-row > * > .input.radio label, -.admin-wrapper .label_input > ul > .input.radio label, -.admin-wrapper .label_input__wrapper > ul > .input.radio label, -.admin-wrapper .radio_buttons > ul > .input.radio label, -.admin-wrapper .with_block_label.radio_buttons .label_input > .input.radio label, -.admin-wrapper :not(.fields-row__column) > .fields-group .checkbox.radio label, -.admin-wrapper .fields-row > * .checkbox.radio label, -.admin-wrapper .label_input > ul .checkbox.radio label, -.admin-wrapper .label_input__wrapper > ul .checkbox.radio label, -.admin-wrapper .radio_buttons > ul .checkbox.radio label, -.admin-wrapper .with_block_label.radio_buttons .label_input .checkbox.radio label, -.admin-wrapper :not(.fields-row__column) > .fields-group .radio.radio label, -.admin-wrapper .fields-row > * .radio.radio label, -.admin-wrapper .label_input > ul .radio.radio label, -.admin-wrapper .label_input__wrapper > ul .radio.radio label, -.admin-wrapper .radio_buttons > ul .radio.radio label, -.admin-wrapper .with_block_label.radio_buttons .label_input .radio.radio label, -.admin-wrapper :not(.fields-row__column) > .fields-group > .input.checkbox label, -.admin-wrapper .fields-row > * > .input.checkbox label, -.admin-wrapper .label_input > ul > .input.checkbox label, -.admin-wrapper .label_input__wrapper > ul > .input.checkbox label, -.admin-wrapper .radio_buttons > ul > .input.checkbox label, -.admin-wrapper .with_block_label.radio_buttons .label_input > .input.checkbox label, -.admin-wrapper :not(.fields-row__column) > .fields-group .checkbox.checkbox label, -.admin-wrapper .fields-row > * .checkbox.checkbox label, -.admin-wrapper .label_input > ul .checkbox.checkbox label, -.admin-wrapper .label_input__wrapper > ul .checkbox.checkbox label, -.admin-wrapper .radio_buttons > ul .checkbox.checkbox label, -.admin-wrapper .with_block_label.radio_buttons .label_input .checkbox.checkbox label, -.admin-wrapper :not(.fields-row__column) > .fields-group .radio.checkbox label, -.admin-wrapper .fields-row > * .radio.checkbox label, -.admin-wrapper .label_input > ul .radio.checkbox label, -.admin-wrapper .label_input__wrapper > ul .radio.checkbox label, -.admin-wrapper .radio_buttons > ul .radio.checkbox label, -.admin-wrapper .with_block_label.radio_buttons .label_input .radio.checkbox label { - margin-bottom: 0 !important; -} -.admin-wrapper :not(.fields-row__column) > .fields-group > .input .label_input, -.admin-wrapper .fields-row > * > .input .label_input, -.admin-wrapper .label_input > ul > .input .label_input, -.admin-wrapper .label_input__wrapper > ul > .input .label_input, -.admin-wrapper .radio_buttons > ul > .input .label_input, -.admin-wrapper .with_block_label.radio_buttons .label_input > .input .label_input, -.admin-wrapper :not(.fields-row__column) > .fields-group .checkbox .label_input, -.admin-wrapper .fields-row > * .checkbox .label_input, -.admin-wrapper .label_input > ul .checkbox .label_input, -.admin-wrapper .label_input__wrapper > ul .checkbox .label_input, -.admin-wrapper .radio_buttons > ul .checkbox .label_input, -.admin-wrapper .with_block_label.radio_buttons .label_input .checkbox .label_input, -.admin-wrapper :not(.fields-row__column) > .fields-group .radio .label_input, -.admin-wrapper .fields-row > * .radio .label_input, -.admin-wrapper .label_input > ul .radio .label_input, -.admin-wrapper .label_input__wrapper > ul .radio .label_input, -.admin-wrapper .radio_buttons > ul .radio .label_input, -.admin-wrapper .with_block_label.radio_buttons .label_input .radio .label_input { - flex-direction: column; -} -.admin-wrapper :not(.fields-row__column) > .fields-group > .input .label_input > label, -.admin-wrapper .fields-row > * > .input .label_input > label, -.admin-wrapper .label_input > ul > .input .label_input > label, -.admin-wrapper .label_input__wrapper > ul > .input .label_input > label, -.admin-wrapper .radio_buttons > ul > .input .label_input > label, -.admin-wrapper .with_block_label.radio_buttons .label_input > .input .label_input > label, -.admin-wrapper :not(.fields-row__column) > .fields-group .checkbox .label_input > label, -.admin-wrapper .fields-row > * .checkbox .label_input > label, -.admin-wrapper .label_input > ul .checkbox .label_input > label, -.admin-wrapper .label_input__wrapper > ul .checkbox .label_input > label, -.admin-wrapper .radio_buttons > ul .checkbox .label_input > label, -.admin-wrapper .with_block_label.radio_buttons .label_input .checkbox .label_input > label, -.admin-wrapper :not(.fields-row__column) > .fields-group .radio .label_input > label, -.admin-wrapper .fields-row > * .radio .label_input > label, -.admin-wrapper .label_input > ul .radio .label_input > label, -.admin-wrapper .label_input__wrapper > ul .radio .label_input > label, -.admin-wrapper .radio_buttons > ul .radio .label_input > label, -.admin-wrapper .with_block_label.radio_buttons .label_input .radio .label_input > label { - margin-bottom: 0; - padding-top: 0.1em; -} -.admin-wrapper :not(.fields-row__column) > .fields-group > .input .label_input__wrapper > :not(.checkbox), -.admin-wrapper .fields-row > * > .input .label_input__wrapper > :not(.checkbox), -.admin-wrapper .label_input > ul > .input .label_input__wrapper > :not(.checkbox), -.admin-wrapper .label_input__wrapper > ul > .input .label_input__wrapper > :not(.checkbox), -.admin-wrapper .radio_buttons > ul > .input .label_input__wrapper > :not(.checkbox), -.admin-wrapper .with_block_label.radio_buttons .label_input > .input .label_input__wrapper > :not(.checkbox), -.admin-wrapper :not(.fields-row__column) > .fields-group .checkbox .label_input__wrapper > :not(.checkbox), -.admin-wrapper .fields-row > * .checkbox .label_input__wrapper > :not(.checkbox), -.admin-wrapper .label_input > ul .checkbox .label_input__wrapper > :not(.checkbox), -.admin-wrapper .label_input__wrapper > ul .checkbox .label_input__wrapper > :not(.checkbox), -.admin-wrapper .radio_buttons > ul .checkbox .label_input__wrapper > :not(.checkbox), -.admin-wrapper .with_block_label.radio_buttons .label_input .checkbox .label_input__wrapper > :not(.checkbox), -.admin-wrapper :not(.fields-row__column) > .fields-group .radio .label_input__wrapper > :not(.checkbox), -.admin-wrapper .fields-row > * .radio .label_input__wrapper > :not(.checkbox), -.admin-wrapper .label_input > ul .radio .label_input__wrapper > :not(.checkbox), -.admin-wrapper .label_input__wrapper > ul .radio .label_input__wrapper > :not(.checkbox), -.admin-wrapper .radio_buttons > ul .radio .label_input__wrapper > :not(.checkbox), -.admin-wrapper .with_block_label.radio_buttons .label_input .radio .label_input__wrapper > :not(.checkbox) { - margin-top: 0.4em; -} -.admin-wrapper :not(.fields-row__column) > .fields-group > .input .checkbox, -.admin-wrapper .fields-row > * > .input .checkbox, -.admin-wrapper .label_input > ul > .input .checkbox, -.admin-wrapper .label_input__wrapper > ul > .input .checkbox, -.admin-wrapper .radio_buttons > ul > .input .checkbox, -.admin-wrapper .with_block_label.radio_buttons .label_input > .input .checkbox, -.admin-wrapper :not(.fields-row__column) > .fields-group .checkbox .checkbox, -.admin-wrapper .fields-row > * .checkbox .checkbox, -.admin-wrapper .label_input > ul .checkbox .checkbox, -.admin-wrapper .label_input__wrapper > ul .checkbox .checkbox, -.admin-wrapper .radio_buttons > ul .checkbox .checkbox, -.admin-wrapper .with_block_label.radio_buttons .label_input .checkbox .checkbox, -.admin-wrapper :not(.fields-row__column) > .fields-group .radio .checkbox, -.admin-wrapper .fields-row > * .radio .checkbox, -.admin-wrapper .label_input > ul .radio .checkbox, -.admin-wrapper .label_input__wrapper > ul .radio .checkbox, -.admin-wrapper .radio_buttons > ul .radio .checkbox, -.admin-wrapper .with_block_label.radio_buttons .label_input .radio .checkbox { - inset: 0; - padding: 1em !important; -} -.admin-wrapper :not(.fields-row__column) > .fields-group li.checkbox, -.admin-wrapper .fields-row > * li.checkbox, -.admin-wrapper .label_input > ul li.checkbox, -.admin-wrapper .label_input__wrapper > ul li.checkbox, -.admin-wrapper .radio_buttons > ul li.checkbox, -.admin-wrapper .with_block_label.radio_buttons .label_input li.checkbox { - width: calc(50% - 27px); -} -.admin-wrapper :not(.fields-row__column) > .fields-group li.checkbox label, -.admin-wrapper .fields-row > * li.checkbox label, -.admin-wrapper .label_input > ul li.checkbox label, -.admin-wrapper .label_input__wrapper > ul li.checkbox label, -.admin-wrapper .radio_buttons > ul li.checkbox label, -.admin-wrapper .with_block_label.radio_buttons .label_input li.checkbox label { - position: static; - padding-top: 0; -} -.admin-wrapper :not(.fields-row__column) > .fields-group li.checkbox label::before, -.admin-wrapper .fields-row > * li.checkbox label::before, -.admin-wrapper .label_input > ul li.checkbox label::before, -.admin-wrapper .label_input__wrapper > ul li.checkbox label::before, -.admin-wrapper .radio_buttons > ul li.checkbox label::before, -.admin-wrapper .with_block_label.radio_buttons .label_input li.checkbox label::before { - content: ""; - position: absolute; - inset: 0; -} -.admin-wrapper :not(.fields-row__column) > .fields-group li.checkbox label input, -.admin-wrapper .fields-row > * li.checkbox label input, -.admin-wrapper .label_input > ul li.checkbox label input, -.admin-wrapper .label_input__wrapper > ul li.checkbox label input, -.admin-wrapper .radio_buttons > ul li.checkbox label input, -.admin-wrapper .with_block_label.radio_buttons .label_input li.checkbox label input { - inset: 1em !important; -} -.admin-wrapper :not(.fields-row__column) > .fields-group > h6, -.admin-wrapper .fields-row > * > h6, -.admin-wrapper .label_input > ul > h6, -.admin-wrapper .label_input__wrapper > ul > h6, -.admin-wrapper .radio_buttons > ul > h6, -.admin-wrapper .with_block_label.radio_buttons .label_input > h6, -.admin-wrapper :not(.fields-row__column) > .fields-group > p, -.admin-wrapper .fields-row > * > p, -.admin-wrapper .label_input > ul > p, -.admin-wrapper .label_input__wrapper > ul > p, -.admin-wrapper .radio_buttons > ul > p, -.admin-wrapper .with_block_label.radio_buttons .label_input > p { - margin: 0; -} -.admin-wrapper :not(.fields-row__column) > .fields-group > h6:not(:last-child), -.admin-wrapper .fields-row > * > h6:not(:last-child), -.admin-wrapper .label_input > ul > h6:not(:last-child), -.admin-wrapper .label_input__wrapper > ul > h6:not(:last-child), -.admin-wrapper .radio_buttons > ul > h6:not(:last-child), -.admin-wrapper .with_block_label.radio_buttons .label_input > h6:not(:last-child), -.admin-wrapper :not(.fields-row__column) > .fields-group > p:not(:last-child), -.admin-wrapper .fields-row > * > p:not(:last-child), -.admin-wrapper .label_input > ul > p:not(:last-child), -.admin-wrapper .label_input__wrapper > ul > p:not(:last-child), -.admin-wrapper .radio_buttons > ul > p:not(:last-child), -.admin-wrapper .with_block_label.radio_buttons .label_input > p:not(:last-child) { - padding-bottom: 0; -} -.admin-wrapper ul { - flex-direction: row !important; - flex-wrap: wrap; - gap: 2px; - margin-top: 0.4em; -} -.admin-wrapper .spacer { - border-top: 1px solid var(--border-color) !important; -} -.batch-table, -.table, -:not(.batch-table__row__content) > table { - overflow: clip; - border-radius: var(--radius); - border-spacing: 0 2px; - border-collapse: separate; -} -.batch-table .batch-table__toolbar, -.table .batch-table__toolbar, -:not(.batch-table__row__content) > table .batch-table__toolbar, -.batch-table .batch-table__row, -.table .batch-table__row, -:not(.batch-table__row__content) > table .batch-table__row, -.batch-table tr > *, -.table tr > *, -:not(.batch-table__row__content) > table tr > * { - border: 0; - margin-bottom: 2px !important; -} -.batch-table td, -.table td, -:not(.batch-table__row__content) > table td, -.batch-table th, -.table th, -:not(.batch-table__row__content) > table th, -.batch-table .batch-table__row, -.table .batch-table__row, -:not(.batch-table__row__content) > table .batch-table__row { - position: relative; -} -.batch-table tr > td > div > span, -.table tr > td > div > span, -:not(.batch-table__row__content) > table tr > td > div > span, -.batch-table tr > th > div > span, -.table tr > th > div > span, -:not(.batch-table__row__content) > table tr > th > div > span { - padding-inline: 0.7em; - display: inline-block; -} -.keyboard-shortcuts td { - padding: 0.7em; -} -.batch-table .batch-table__row, -.table .batch-table__row, -:not(.batch-table__row__content) > table .batch-table__row, -.batch-table th, -.table th, -:not(.batch-table__row__content) > table th, -.batch-table > tbody > tr > td, -.table > tbody > tr > td, -:not(.batch-table__row__content) > table > tbody > tr > td, -.batch-table tfoot td, -.table tfoot td, -:not(.batch-table__row__content) > table tfoot td { - background: var(--elevated-color) !important; - vertical-align: middle; -} -.batch-table .batch-table__row::after, -.table .batch-table__row::after, -:not(.batch-table__row__content) > table .batch-table__row::after, -.batch-table th::after, -.table th::after, -:not(.batch-table__row__content) > table th::after, -.batch-table > tbody > tr > td::after, -.table > tbody > tr > td::after, -:not(.batch-table__row__content) > table > tbody > tr > td::after, -.batch-table tfoot td::after, -.table tfoot td::after, -:not(.batch-table__row__content) > table tfoot td::after { - content: ""; - position: absolute; - inset: 0 0; - background: var(--hover-color); - opacity: 0; - transition: 0.2s; - pointer-events: none; -} -.batch-table .batch-table__row:hover::after, -.table .batch-table__row:hover::after, -:not(.batch-table__row__content) > table .batch-table__row:hover::after, -.batch-table th:hover::after, -.table th:hover::after, -:not(.batch-table__row__content) > table th:hover::after, -.batch-table > tbody > tr > td:hover::after, -.table > tbody > tr > td:hover::after, -:not(.batch-table__row__content) > table > tbody > tr > td:hover::after, -.batch-table tfoot td:hover::after, -.table tfoot td:hover::after, -:not(.batch-table__row__content) > table tfoot td:hover::after, -.batch-table .batch-table__row:focus-within::after, -.table .batch-table__row:focus-within::after, -:not(.batch-table__row__content) > table .batch-table__row:focus-within::after, -.batch-table th:focus-within::after, -.table th:focus-within::after, -:not(.batch-table__row__content) > table th:focus-within::after, -.batch-table > tbody > tr > td:focus-within::after, -.table > tbody > tr > td:focus-within::after, -:not(.batch-table__row__content) > table > tbody > tr > td:focus-within::after, -.batch-table tfoot td:focus-within::after, -.table tfoot td:focus-within::after, -:not(.batch-table__row__content) > table tfoot td:focus-within::after { - opacity: 1; -} -.batch-table .batch-table__row > a:first-child:last-child, -.table .batch-table__row > a:first-child:last-child, -:not(.batch-table__row__content) > table .batch-table__row > a:first-child:last-child, -.batch-table th > a:first-child:last-child, -.table th > a:first-child:last-child, -:not(.batch-table__row__content) > table th > a:first-child:last-child, -.batch-table > tbody > tr > td > a:first-child:last-child, -.table > tbody > tr > td > a:first-child:last-child, -:not(.batch-table__row__content) > table > tbody > tr > td > a:first-child:last-child, -.batch-table tfoot td > a:first-child:last-child, -.table tfoot td > a:first-child:last-child, -:not(.batch-table__row__content) > table tfoot td > a:first-child:last-child { - margin: 0; - width: 100%; - padding: 0.5em; -} -.batch-table th:hover td:not([rowspan])::after, -.table th:hover td:not([rowspan])::after, -:not(.batch-table__row__content) > table th:hover td:not([rowspan])::after, -.batch-table tr:hover td:not([rowspan])::after, -.table tr:hover td:not([rowspan])::after, -:not(.batch-table__row__content) > table tr:hover td:not([rowspan])::after, -.batch-table th:hover th:not([rowspan])::after, -.table th:hover th:not([rowspan])::after, -:not(.batch-table__row__content) > table th:hover th:not([rowspan])::after, -.batch-table tr:hover th:not([rowspan])::after, -.table tr:hover th:not([rowspan])::after, -:not(.batch-table__row__content) > table tr:hover th:not([rowspan])::after { - opacity: 1 !important; -} -.batch-table th [rowspan]:hover ~ td::after, -.table th [rowspan]:hover ~ td::after, -:not(.batch-table__row__content) > table th [rowspan]:hover ~ td::after, -.batch-table tr [rowspan]:hover ~ td::after, -.table tr [rowspan]:hover ~ td::after, -:not(.batch-table__row__content) > table tr [rowspan]:hover ~ td::after { - opacity: 0 !important; -} -.batch-table th [rowspan]::after, -.table th [rowspan]::after, -:not(.batch-table__row__content) > table th [rowspan]::after, -.batch-table tr [rowspan]::after, -.table tr [rowspan]::after, -:not(.batch-table__row__content) > table tr [rowspan]::after { - inset-inline: -900px; -} -.layout-multiple-columns #mastodon .columns-area { - overflow: auto hidden !important; - padding: 0; -} -.layout-multiple-columns #mastodon .columns-area .scrollable:not(.scrollable--flex) { - padding: 0px !important; - padding-bottom: 40vh !important; -} -.layout-multiple-columns #mastodon .columns-area .scrollable:not(.scrollable--flex)::before { - content: ""; - position: absolute; - inset: 0; - background-color: inherit; - z-index: -1; -} -.layout-multiple-columns #mastodon .columns-area .scrollable:not(.scrollable--flex) .account-timeline__header, -.layout-multiple-columns #mastodon .columns-area .scrollable:not(.scrollable--flex) .dismissable-banner { - margin: 0px !important; -} -.layout-multiple-columns #mastodon .columns-area .focusable, -.layout-multiple-columns #mastodon .columns-area .entry, -.layout-multiple-columns #mastodon .columns-area .statuses-grid__item .detailed-status, -.layout-multiple-columns #mastodon .columns-area .trends__item, -.layout-multiple-columns #mastodon .columns-area .story, -.layout-multiple-columns #mastodon .columns-area .account-card, -.layout-multiple-columns #mastodon .columns-area .scrollable :not(.focusable) > .account, -.layout-multiple-columns #mastodon .columns-area .timeline-hint { - border-radius: 0; -} -.layout-multiple-columns #mastodon .columns-area .focusable::before, -.layout-multiple-columns #mastodon .columns-area .entry::before, -.layout-multiple-columns #mastodon .columns-area .statuses-grid__item .detailed-status::before, -.layout-multiple-columns #mastodon .columns-area .trends__item::before, -.layout-multiple-columns #mastodon .columns-area .story::before, -.layout-multiple-columns #mastodon .columns-area .account-card::before, -.layout-multiple-columns #mastodon .columns-area .scrollable :not(.focusable) > .account::before, -.layout-multiple-columns #mastodon .columns-area .timeline-hint::before { - border-radius: 0 !important; -} -.layout-multiple-columns #mastodon .columns-area .focusable::after, -.layout-multiple-columns #mastodon .columns-area .entry::after, -.layout-multiple-columns #mastodon .columns-area .statuses-grid__item .detailed-status::after, -.layout-multiple-columns #mastodon .columns-area .trends__item::after, -.layout-multiple-columns #mastodon .columns-area .story::after, -.layout-multiple-columns #mastodon .columns-area .account-card::after, -.layout-multiple-columns #mastodon .columns-area .scrollable :not(.focusable) > .account::after, -.layout-multiple-columns #mastodon .columns-area .timeline-hint::after { - inset-inline: 0 !important; -} -.layout-multiple-columns #mastodon .columns-area [class*="explore__"] > * { - border-radius: var(--radius); -} -.layout-multiple-columns #mastodon .columns-area .detailed-status__wrapper { - margin: 0 !important; -} -.layout-multiple-columns #mastodon .columns-area .status__action-bar { - margin-bottom: 0px; - gap: 0; - margin-inline-end: 0 !important; -} -.layout-multiple-columns #mastodon .columns-area .status__action-bar :not(i):not(.status__action-bar-spacer) { - display: flex; - flex-grow: 9999; - justify-content: center !important; - max-width: 55px; - min-width: max-content; -} -.layout-multiple-columns #mastodon .columns-area .status__action-bar > .icon-button:first-child { - margin-inline-start: -8px !important; -} -.layout-multiple-columns #mastodon .columns-area .status__action-bar, -.layout-multiple-columns #mastodon .columns-area .detailed-status__action-bar, -.layout-multiple-columns #mastodon .columns-area .picture-in-picture__footer { - flex-wrap: wrap; -} -.layout-multiple-columns #mastodon .columns-area .follow_requests-unlocked_explanation { - margin: 0 !important; -} -.layout-multiple-columns #mastodon .columns-area .column-header, -.layout-multiple-columns #mastodon .columns-area .scrollable, -.layout-multiple-columns #mastodon .columns-area .column-back-button, -.layout-multiple-columns #mastodon .columns-area .account__header__image { - border-radius: 0 !important; -} -.layout-multiple-columns #mastodon .columns-area .icon-button:after { - content: unset !important; -} -.layout-multiple-columns #mastodon .columns-area > div { - border: 0 !important; - padding: 0 !important; -} -.layout-multiple-columns #mastodon .columns-area > div:not(.drawer):not(:last-child) { - margin-inline-end: 2px !important; -} -.layout-multiple-columns #mastodon .columns-area > div.column { - flex-grow: 1; - max-width: 600px; -} -.layout-multiple-columns #mastodon .columns-area > div:first-child { - margin-inline-start: auto !important; -} -.layout-multiple-columns #mastodon .columns-area > div:last-child { - margin-inline-end: auto !important; -} -.layout-multiple-columns #mastodon .columns-area .drawer { - padding-inline: 6px !important; - padding-top: 20px !important; - overflow: clip; -} -.layout-multiple-columns #mastodon .columns-area .drawer .drawer__header { - border-radius: var(--radius-round); - background: var(--elevated-tint); - margin-inline: 10px; - overflow: hidden; - border: 0 !important; -} -.layout-multiple-columns #mastodon .columns-area .drawer .drawer__header a { - border: 0; -} -.layout-multiple-columns #mastodon .columns-area .drawer .drawer__header a:first-child { - padding-inline-start: 15px !important; -} -.layout-multiple-columns #mastodon .columns-area .drawer .drawer__header a:last-child { - padding-inline-end: 15px !important; -} -.layout-multiple-columns #mastodon .columns-area .drawer .search { - z-index: 2; -} -.layout-multiple-columns #mastodon .columns-area .drawer > .drawer__pager { - border: 0; - overflow: visible !important; -} -.layout-multiple-columns #mastodon .columns-area .drawer > .drawer__pager > .drawer__inner:not(.darker) { - top: -20px; - margin-inline-start: -6px; - margin-inline-end: -4px; - width: calc(100% + 10px); - padding-inline-start: 6px; - padding-inline-end: 4px; - height: calc(100% + 20px); -} -.layout-multiple-columns #mastodon .columns-area .drawer .drawer__inner__mastodon { - margin-inline: -6px; - margin-inline-end: -4px; - z-index: -1; -} -.layout-multiple-columns #mastodon .columns-area .search { - margin-inline: 10px; -} -.layout-multiple-columns #mastodon .columns-area .compose-form { - display: flex; - flex-direction: column; -} -.layout-multiple-columns #mastodon .columns-area .drawer__inner:not(.darker), -.layout-multiple-columns #mastodon .columns-area .drawer__inner__mastodon { - background-color: transparent; - border: 0 !important; - background-color: transparent !important; -} -.layout-multiple-columns #mastodon .columns-area .drawer__inner.darker { - padding: 0 !important; - border-radius: var(--radius-round) var(--radius-round) 0 0; -} -.layout-multiple-columns #mastodon .columns-area .drawer__inner.darker::before { - content: ""; - position: absolute; - inset: 0; - background: var(--elevated-tint); - pointer-events: none; -} -.layout-multiple-columns #mastodon .columns-area .getting-started__trends { - padding: 0px 20px; -} -.layout-multiple-columns #mastodon .columns-area .column-header { - border-top: 0; -} -.layout-multiple-columns #mastodon .columns-area .column-header__title { - padding-block: 0; -} -.layout-multiple-columns #mastodon .columns-area .status { - padding-bottom: 10px !important; -} -.layout-multiple-columns #mastodon .columns-area .detailed-status .status__content { - font-size: 1.3em; +.interaction-modal__choices h3 { + margin-bottom: 10px; } .modal-root__container { animation: bounceIn 0.7s; } +@media (max-width: 890px) { + .modal-root__modal { + margin-top: auto; + max-width: 100%; + border-radius: var(--radius) var(--radius) 0 0; + } +} +.picture-in-picture { + z-index: 101; +} +.picture-in-picture .picture-in-picture__header { + border-radius: var(--radius) var(--radius) 0 0; +} +.picture-in-picture .media-gallery, +.picture-in-picture .video-player, +.picture-in-picture .status-card.horizontal.interactive, +.picture-in-picture .status-card, +.picture-in-picture .audio-player, +.picture-in-picture .picture-in-picture-placeholder { + --radius: 0; + margin: 0 !important; +} +.picture-in-picture .picture-in-picture__footer { + border-radius: 0 0 var(--radius) var(--radius); +} +.report-modal[style="max-width: 960px;"] { + background: var(--background-color); +} +.report-modal[style="max-width: 960px;"], +.report-modal[style="max-width: 960px;"] * { + color: inherit !important; +} +.report-modal[style="max-width: 960px;"] .report-modal__comment { + min-width: unset; + width: 370px; + max-width: unset; + flex: none; + padding: 20px; + height: 100%; + overflow-y: auto; +} +.report-modal[style="max-width: 960px;"] .setting-text__wrapper { + border-radius: var(--radius); + overflow: hidden; + background: none; +} +.report-modal[style="max-width: 960px;"] .setting-text__wrapper textarea { + border: 0; + max-height: unset !important; + background: none; +} +.report-modal[style="max-width: 960px;"] .focal-point-modal__content { + position: sticky; + top: 0; + max-height: 100vh; + flex-grow: 0 !important; + max-width: 100%; +} +.report-modal[style="max-width: 960px;"] .focal-point { + width: 100%; + height: 100%; +} +.report-modal[style="max-width: 960px;"] .audio-player, +.report-modal[style="max-width: 960px;"] .focal-point img { + width: unset !important; + height: unset !important; + max-height: 100% !important; + max-width: 100% !important; +} +.report-modal[style="max-width: 960px;"] .audio-player { + margin: 10px !important; + width: 600px !important; + max-width: calc(100% - 20px) !important; +} +.report-modal[style="max-width: 960px;"] .focal-point__reticle { + box-shadow: 0 0 300px 200px rgba(0,0,0,0.2); +} +@media not all and (max-width: 900px) { + .report-modal[style="max-width: 960px;"] { + max-width: max-content !important; + max-height: 98vh; + border: 0; + box-shadow: var(--shadow); + overflow: hidden; + border-radius: var(--radius); + width: 98vw; + } + .report-modal[style="max-width: 960px;"] .report-modal__container { + flex-wrap: nowrap; + border: 0; + max-width: max-content; + max-height: 100%; + } + .report-modal[style="max-width: 960px;"] .report-modal__target { + position: absolute; + padding: 24px 20px 12px; + font-weight: bold; + width: 348px; + box-sizing: border-box; + text-align: start; + background: inherit; + } + .report-modal[style="max-width: 960px;"] .report-modal__close { + position: fixed !important; + right: 12px; + top: 12px; + order: 2; + color: #fff; + background: var(--modal-background-color); + padding: 12px; + } + .report-modal[style="max-width: 960px;"] .report-modal__comment { + padding-top: calc(30px + 2em); + padding-bottom: 160px; + } + .report-modal[style="max-width: 960px;"] .focal-point-modal__content, + .report-modal[style="max-width: 960px;"] .focal-point { + overflow: visible; + } + .report-modal[style="max-width: 960px;"] .focal-point img { + min-width: 500px; + } + .report-modal[style="max-width: 960px;"] .focal-point__preview { + inset-inline-start: -220px; + right: unset; + bottom: 20px; + pointer-events: none; + text-align: end; + } + .report-modal[style="max-width: 960px;"] .focal-point__preview strong { + color: inherit; + } + .report-modal[style="max-width: 960px;"] .focal-point__preview div { + border-radius: var(--radius); + box-shadow: none; + } +} +@media (max-width: 900px) { + .report-modal[style="max-width: 960px;"] { + height: auto; + width: 100vw; + max-width: unset !important; + border-radius: 0; + } + .report-modal[style="max-width: 960px;"] .report-modal__container { + height: auto; + min-height: 0; + } + .report-modal[style="max-width: 960px;"] .report-modal__container { + flex-direction: column-reverse; + flex-wrap: nowrap; + flex-grow: 1; + } + .report-modal[style="max-width: 960px;"] .report-modal__comment { + width: 100%; + border: 0; + max-height: 70%; + flex: 0 0 auto; + height: unset; + order: unset; + } +} +.emoji-picker-dropdown__menu { + border-radius: var(--radius); + overflow: hidden; + resize: both; + width: 400px; +} +.emoji-mart { + display: flex !important; + flex-direction: column !important; + width: 100% !important; + height: 100% !important; +} +.emoji-mart-scroll { + flex-grow: 1; + max-height: unset !important; +} +.emoji-mart-bar { + order: 2; +} +.emoji-mart-category-list { + overflow: visible !important; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(calc(20px + 6%), 1fr)); +} +.emoji-mart-category-list li { + display: contents; +} +.emoji-mart-category-list button { + position: relative; + padding: 0 !important; + padding-top: 100% !important; +} +.emoji-mart-category-list button img, +.emoji-mart-category-list button span { + height: calc(100% - 10px) !important; + width: calc(100% - 10px) !important; + inset: 5px; + position: absolute !important; + transition: transform 0.1s cubic-bezier(0, 0, 0, 1); +} +.emoji-mart-category-list button:hover img, +.emoji-mart-category-list button:hover span { + transform: scale(1.2); +} +.emoji-picker-dropdown__modifiers { + top: 16px; +} \ No newline at end of file diff --git a/app/javascript/flavours/glitch/styles/variables.scss b/app/javascript/flavours/glitch/styles/variables.scss index 54dc54ac02..3a12809483 100644 --- a/app/javascript/flavours/glitch/styles/variables.scss +++ b/app/javascript/flavours/glitch/styles/variables.scss @@ -112,4 +112,6 @@ $dismiss-overlay-width: 4rem; --background-color: #{darken($ui-base-color, 8%)}; --background-color-tint: #{rgba(darken($ui-base-color, 8%), 0.9)}; --surface-background-color: #{darken($ui-base-color, 4%)}; + --surface-variant-background-color: #{$ui-base-color}; + --surface-variant-active-background-color: #{lighten($ui-base-color, 4%)}; } diff --git a/app/javascript/flavours/glitch/test_helpers.tsx b/app/javascript/flavours/glitch/test_helpers.tsx index 69d57b95a0..09efda1e73 100644 --- a/app/javascript/flavours/glitch/test_helpers.tsx +++ b/app/javascript/flavours/glitch/test_helpers.tsx @@ -17,7 +17,6 @@ class FakeIdentityWrapper extends Component< signedIn: PropTypes.bool.isRequired, accountId: PropTypes.string, disabledAccountId: PropTypes.string, - accessToken: PropTypes.string, }).isRequired, }; @@ -26,7 +25,6 @@ class FakeIdentityWrapper extends Component< identity: { signedIn: this.props.signedIn, accountId: '123', - accessToken: 'test-access-token', }, }; } diff --git a/app/javascript/flavours/glitch/theme.yml b/app/javascript/flavours/glitch/theme.yml index ca18fd7b86..976e7cb506 100644 --- a/app/javascript/flavours/glitch/theme.yml +++ b/app/javascript/flavours/glitch/theme.yml @@ -1,5 +1,5 @@ # (REQUIRED) The directory which contains the entry point files. -pack_directory: app/javascript/flavours/glitch/packs +pack_directory: app/javascript/flavours/glitch/entrypoints # (OPTIONAL) Define files to be preloaded when a logged-in user is # visiting the main web app. diff --git a/app/javascript/flavours/vanilla/theme.yml b/app/javascript/flavours/vanilla/theme.yml index d155324beb..0f531a3fe7 100644 --- a/app/javascript/flavours/vanilla/theme.yml +++ b/app/javascript/flavours/vanilla/theme.yml @@ -1,5 +1,5 @@ # (REQUIRED) The directory which contains the pack files. -pack_directory: app/javascript/packs +pack_directory: app/javascript/entrypoints # (OPTIONAL) Define files to be preloaded when a logged-in user is # visiting the main web app. diff --git a/app/javascript/hooks/useLinks.ts b/app/javascript/hooks/useLinks.ts new file mode 100644 index 0000000000..f08b9500da --- /dev/null +++ b/app/javascript/hooks/useLinks.ts @@ -0,0 +1,61 @@ +import { useCallback } from 'react'; + +import { useHistory } from 'react-router-dom'; + +import { openURL } from 'mastodon/actions/search'; +import { useAppDispatch } from 'mastodon/store'; + +const isMentionClick = (element: HTMLAnchorElement) => + element.classList.contains('mention'); + +const isHashtagClick = (element: HTMLAnchorElement) => + element.textContent?.[0] === '#' || + element.previousSibling?.textContent?.endsWith('#'); + +export const useLinks = () => { + const history = useHistory(); + const dispatch = useAppDispatch(); + + const handleHashtagClick = useCallback( + (element: HTMLAnchorElement) => { + const { textContent } = element; + + if (!textContent) return; + + history.push(`/tags/${textContent.replace(/^#/, '')}`); + }, + [history], + ); + + const handleMentionClick = useCallback( + (element: HTMLAnchorElement) => { + dispatch( + openURL(element.href, history, () => { + window.location.href = element.href; + }), + ); + }, + [dispatch, history], + ); + + const handleClick = useCallback( + (e: React.MouseEvent) => { + const target = (e.target as HTMLElement).closest('a'); + + if (!target || e.button !== 0 || e.ctrlKey || e.metaKey) { + return; + } + + if (isMentionClick(target)) { + e.preventDefault(); + handleMentionClick(target); + } else if (isHashtagClick(target)) { + e.preventDefault(); + handleHashtagClick(target); + } + }, + [handleMentionClick, handleHashtagClick], + ); + + return handleClick; +}; diff --git a/app/javascript/hooks/useTimeout.ts b/app/javascript/hooks/useTimeout.ts new file mode 100644 index 0000000000..bb1e8848dd --- /dev/null +++ b/app/javascript/hooks/useTimeout.ts @@ -0,0 +1,44 @@ +import { useRef, useCallback, useEffect } from 'react'; + +export const useTimeout = () => { + const timeoutRef = useRef>(); + const callbackRef = useRef<() => void>(); + + const set = useCallback((callback: () => void, delay: number) => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + + callbackRef.current = callback; + timeoutRef.current = setTimeout(callback, delay); + }, []); + + const delay = useCallback((delay: number) => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + + if (!callbackRef.current) { + return; + } + + timeoutRef.current = setTimeout(callbackRef.current, delay); + }, []); + + const cancel = useCallback(() => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + timeoutRef.current = undefined; + callbackRef.current = undefined; + } + }, []); + + useEffect( + () => () => { + cancel(); + }, + [cancel], + ); + + return [set, cancel, delay] as const; +}; diff --git a/app/javascript/icons/android-chrome-144x144.png b/app/javascript/icons/android-chrome-144x144.png index eb550caf81..698fb4a260 100644 Binary files a/app/javascript/icons/android-chrome-144x144.png and b/app/javascript/icons/android-chrome-144x144.png differ diff --git a/app/javascript/icons/android-chrome-192x192.png b/app/javascript/icons/android-chrome-192x192.png index 44960b7bcd..2b6b632648 100644 Binary files a/app/javascript/icons/android-chrome-192x192.png and b/app/javascript/icons/android-chrome-192x192.png differ diff --git a/app/javascript/icons/android-chrome-256x256.png b/app/javascript/icons/android-chrome-256x256.png index eb6d3810e8..51e3849a26 100644 Binary files a/app/javascript/icons/android-chrome-256x256.png and b/app/javascript/icons/android-chrome-256x256.png differ diff --git a/app/javascript/icons/android-chrome-36x36.png b/app/javascript/icons/android-chrome-36x36.png index 83890c2d6f..925f69c4fc 100644 Binary files a/app/javascript/icons/android-chrome-36x36.png and b/app/javascript/icons/android-chrome-36x36.png differ diff --git a/app/javascript/icons/android-chrome-384x384.png b/app/javascript/icons/android-chrome-384x384.png index add9cb06b9..9d256a83cb 100644 Binary files a/app/javascript/icons/android-chrome-384x384.png and b/app/javascript/icons/android-chrome-384x384.png differ diff --git a/app/javascript/icons/android-chrome-48x48.png b/app/javascript/icons/android-chrome-48x48.png index 69e818fa4e..bcfe7475d0 100644 Binary files a/app/javascript/icons/android-chrome-48x48.png and b/app/javascript/icons/android-chrome-48x48.png differ diff --git a/app/javascript/icons/android-chrome-512x512.png b/app/javascript/icons/android-chrome-512x512.png index 073250b3d0..bffacfb699 100644 Binary files a/app/javascript/icons/android-chrome-512x512.png and b/app/javascript/icons/android-chrome-512x512.png differ diff --git a/app/javascript/icons/android-chrome-72x72.png b/app/javascript/icons/android-chrome-72x72.png index 2f4711875b..16679d5731 100644 Binary files a/app/javascript/icons/android-chrome-72x72.png and b/app/javascript/icons/android-chrome-72x72.png differ diff --git a/app/javascript/icons/android-chrome-96x96.png b/app/javascript/icons/android-chrome-96x96.png index dee0f6d69e..9ade87cf32 100644 Binary files a/app/javascript/icons/android-chrome-96x96.png and b/app/javascript/icons/android-chrome-96x96.png differ diff --git a/app/javascript/icons/favicon-16x16.png b/app/javascript/icons/favicon-16x16.png index bdf2f913ee..eed8e0035c 100644 Binary files a/app/javascript/icons/favicon-16x16.png and b/app/javascript/icons/favicon-16x16.png differ diff --git a/app/javascript/icons/favicon-32x32.png b/app/javascript/icons/favicon-32x32.png index 6c68991f5a..9165746bcf 100644 Binary files a/app/javascript/icons/favicon-32x32.png and b/app/javascript/icons/favicon-32x32.png differ diff --git a/app/javascript/icons/favicon-48x48.png b/app/javascript/icons/favicon-48x48.png index 0aac1ee5b8..259676c0a9 100644 Binary files a/app/javascript/icons/favicon-48x48.png and b/app/javascript/icons/favicon-48x48.png differ diff --git a/app/javascript/mastodon/actions/account_notes.ts b/app/javascript/mastodon/actions/account_notes.ts index e524e5235b..c2ebaf54a4 100644 --- a/app/javascript/mastodon/actions/account_notes.ts +++ b/app/javascript/mastodon/actions/account_notes.ts @@ -1,18 +1,10 @@ -import type { ApiRelationshipJSON } from 'mastodon/api_types/relationships'; -import { createAppAsyncThunk } from 'mastodon/store/typed_functions'; +import { apiSubmitAccountNote } from 'mastodon/api/accounts'; +import { createDataLoadingThunk } from 'mastodon/store/typed_functions'; -import api from '../api'; - -export const submitAccountNote = createAppAsyncThunk( +export const submitAccountNote = createDataLoadingThunk( 'account_note/submit', - async (args: { id: string; value: string }, { getState }) => { - const response = await api(getState).post( - `/api/v1/accounts/${args.id}/note`, - { - comment: args.value, - }, - ); - - return { relationship: response.data }; - }, + ({ accountId, note }: { accountId: string; note: string }) => + apiSubmitAccountNote(accountId, note), + (relationship) => ({ relationship }), + { skipLoading: true }, ); diff --git a/app/javascript/mastodon/actions/accounts.js b/app/javascript/mastodon/actions/accounts.js index 9f3bbba033..cea915e5f1 100644 --- a/app/javascript/mastodon/actions/accounts.js +++ b/app/javascript/mastodon/actions/accounts.js @@ -76,11 +76,11 @@ export const ACCOUNT_REVEAL = 'ACCOUNT_REVEAL'; export * from './accounts_typed'; export function fetchAccount(id) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(fetchRelationships([id])); dispatch(fetchAccountRequest(id)); - api(getState).get(`/api/v1/accounts/${id}`).then(response => { + api().get(`/api/v1/accounts/${id}`).then(response => { dispatch(importFetchedAccount(response.data)); dispatch(fetchAccountSuccess()); }).catch(error => { @@ -89,10 +89,10 @@ export function fetchAccount(id) { }; } -export const lookupAccount = acct => (dispatch, getState) => { +export const lookupAccount = acct => (dispatch) => { dispatch(lookupAccountRequest(acct)); - api(getState).get('/api/v1/accounts/lookup', { params: { acct } }).then(response => { + api().get('/api/v1/accounts/lookup', { params: { acct } }).then(response => { dispatch(fetchRelationships([response.data.id])); dispatch(importFetchedAccount(response.data)); dispatch(lookupAccountSuccess()); @@ -146,7 +146,7 @@ export function followAccount(id, options = { reblogs: true }) { dispatch(followAccountRequest({ id, locked })); - api(getState).post(`/api/v1/accounts/${id}/follow`, options).then(response => { + api().post(`/api/v1/accounts/${id}/follow`, options).then(response => { dispatch(followAccountSuccess({relationship: response.data, alreadyFollowing})); }).catch(error => { dispatch(followAccountFail({ id, error, locked })); @@ -158,7 +158,7 @@ export function unfollowAccount(id) { return (dispatch, getState) => { dispatch(unfollowAccountRequest(id)); - api(getState).post(`/api/v1/accounts/${id}/unfollow`).then(response => { + api().post(`/api/v1/accounts/${id}/unfollow`).then(response => { dispatch(unfollowAccountSuccess({relationship: response.data, statuses: getState().get('statuses')})); }).catch(error => { dispatch(unfollowAccountFail({ id, error })); @@ -170,7 +170,7 @@ export function blockAccount(id) { return (dispatch, getState) => { dispatch(blockAccountRequest(id)); - api(getState).post(`/api/v1/accounts/${id}/block`).then(response => { + api().post(`/api/v1/accounts/${id}/block`).then(response => { // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers dispatch(blockAccountSuccess({ relationship: response.data, statuses: getState().get('statuses') })); }).catch(error => { @@ -180,10 +180,10 @@ export function blockAccount(id) { } export function unblockAccount(id) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(unblockAccountRequest(id)); - api(getState).post(`/api/v1/accounts/${id}/unblock`).then(response => { + api().post(`/api/v1/accounts/${id}/unblock`).then(response => { dispatch(unblockAccountSuccess({ relationship: response.data })); }).catch(error => { dispatch(unblockAccountFail({ id, error })); @@ -223,7 +223,7 @@ export function muteAccount(id, notifications, duration=0) { return (dispatch, getState) => { dispatch(muteAccountRequest(id)); - api(getState).post(`/api/v1/accounts/${id}/mute`, { notifications, duration }).then(response => { + api().post(`/api/v1/accounts/${id}/mute`, { notifications, duration }).then(response => { // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers dispatch(muteAccountSuccess({ relationship: response.data, statuses: getState().get('statuses') })); }).catch(error => { @@ -233,10 +233,10 @@ export function muteAccount(id, notifications, duration=0) { } export function unmuteAccount(id) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(unmuteAccountRequest(id)); - api(getState).post(`/api/v1/accounts/${id}/unmute`).then(response => { + api().post(`/api/v1/accounts/${id}/unmute`).then(response => { dispatch(unmuteAccountSuccess({ relationship: response.data })); }).catch(error => { dispatch(unmuteAccountFail({ id, error })); @@ -274,10 +274,10 @@ export function unmuteAccountFail(error) { export function fetchFollowers(id) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(fetchFollowersRequest(id)); - api(getState).get(`/api/v1/accounts/${id}/followers`).then(response => { + api().get(`/api/v1/accounts/${id}/followers`).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); @@ -324,7 +324,7 @@ export function expandFollowers(id) { dispatch(expandFollowersRequest(id)); - api(getState).get(url).then(response => { + api().get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); @@ -361,10 +361,10 @@ export function expandFollowersFail(id, error) { } export function fetchFollowing(id) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(fetchFollowingRequest(id)); - api(getState).get(`/api/v1/accounts/${id}/following`).then(response => { + api().get(`/api/v1/accounts/${id}/following`).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); @@ -411,7 +411,7 @@ export function expandFollowing(id) { dispatch(expandFollowingRequest(id)); - api(getState).get(url).then(response => { + api().get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); @@ -460,7 +460,7 @@ export function fetchRelationships(accountIds) { dispatch(fetchRelationshipsRequest(newAccountIds)); - api(getState).get(`/api/v1/accounts/relationships?with_suspended=true&${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => { + api().get(`/api/v1/accounts/relationships?with_suspended=true&${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => { dispatch(fetchRelationshipsSuccess({ relationships: response.data })); }).catch(error => { dispatch(fetchRelationshipsFail(error)); @@ -486,10 +486,10 @@ export function fetchRelationshipsFail(error) { } export function fetchFollowRequests() { - return (dispatch, getState) => { + return (dispatch) => { dispatch(fetchFollowRequestsRequest()); - api(getState).get('/api/v1/follow_requests').then(response => { + api().get('/api/v1/follow_requests').then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); dispatch(fetchFollowRequestsSuccess(response.data, next ? next.uri : null)); @@ -528,7 +528,7 @@ export function expandFollowRequests() { dispatch(expandFollowRequestsRequest()); - api(getState).get(url).then(response => { + api().get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); dispatch(expandFollowRequestsSuccess(response.data, next ? next.uri : null)); @@ -558,10 +558,10 @@ export function expandFollowRequestsFail(error) { } export function authorizeFollowRequest(id) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(authorizeFollowRequestRequest(id)); - api(getState) + api() .post(`/api/v1/follow_requests/${id}/authorize`) .then(() => dispatch(authorizeFollowRequestSuccess({ id }))) .catch(error => dispatch(authorizeFollowRequestFail(id, error))); @@ -585,10 +585,10 @@ export function authorizeFollowRequestFail(id, error) { export function rejectFollowRequest(id) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(rejectFollowRequestRequest(id)); - api(getState) + api() .post(`/api/v1/follow_requests/${id}/reject`) .then(() => dispatch(rejectFollowRequestSuccess({ id }))) .catch(error => dispatch(rejectFollowRequestFail(id, error))); @@ -611,10 +611,10 @@ export function rejectFollowRequestFail(id, error) { } export function pinAccount(id) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(pinAccountRequest(id)); - api(getState).post(`/api/v1/accounts/${id}/pin`).then(response => { + api().post(`/api/v1/accounts/${id}/pin`).then(response => { dispatch(pinAccountSuccess({ relationship: response.data })); }).catch(error => { dispatch(pinAccountFail(error)); @@ -623,10 +623,10 @@ export function pinAccount(id) { } export function unpinAccount(id) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(unpinAccountRequest(id)); - api(getState).post(`/api/v1/accounts/${id}/unpin`).then(response => { + api().post(`/api/v1/accounts/${id}/unpin`).then(response => { dispatch(unpinAccountSuccess({ relationship: response.data })); }).catch(error => { dispatch(unpinAccountFail(error)); @@ -662,7 +662,7 @@ export function unpinAccountFail(error) { }; } -export const updateAccount = ({ displayName, note, avatar, header, discoverable, indexable }) => (dispatch, getState) => { +export const updateAccount = ({ displayName, note, avatar, header, discoverable, indexable }) => (dispatch) => { const data = new FormData(); data.append('display_name', displayName); @@ -672,7 +672,7 @@ export const updateAccount = ({ displayName, note, avatar, header, discoverable, data.append('discoverable', discoverable); data.append('indexable', indexable); - return api(getState).patch('/api/v1/accounts/update_credentials', data).then(response => { + return api().patch('/api/v1/accounts/update_credentials', data).then(response => { dispatch(importFetchedAccount(response.data)); }); }; diff --git a/app/javascript/mastodon/actions/announcements.js b/app/javascript/mastodon/actions/announcements.js index 339c5f3adc..7657b05dc4 100644 --- a/app/javascript/mastodon/actions/announcements.js +++ b/app/javascript/mastodon/actions/announcements.js @@ -26,10 +26,10 @@ export const ANNOUNCEMENTS_TOGGLE_SHOW = 'ANNOUNCEMENTS_TOGGLE_SHOW'; const noOp = () => {}; -export const fetchAnnouncements = (done = noOp) => (dispatch, getState) => { +export const fetchAnnouncements = (done = noOp) => (dispatch) => { dispatch(fetchAnnouncementsRequest()); - api(getState).get('/api/v1/announcements').then(response => { + api().get('/api/v1/announcements').then(response => { dispatch(fetchAnnouncementsSuccess(response.data.map(x => normalizeAnnouncement(x)))); }).catch(error => { dispatch(fetchAnnouncementsFail(error)); @@ -61,10 +61,10 @@ export const updateAnnouncements = announcement => ({ announcement: normalizeAnnouncement(announcement), }); -export const dismissAnnouncement = announcementId => (dispatch, getState) => { +export const dismissAnnouncement = announcementId => (dispatch) => { dispatch(dismissAnnouncementRequest(announcementId)); - api(getState).post(`/api/v1/announcements/${announcementId}/dismiss`).then(() => { + api().post(`/api/v1/announcements/${announcementId}/dismiss`).then(() => { dispatch(dismissAnnouncementSuccess(announcementId)); }).catch(error => { dispatch(dismissAnnouncementFail(announcementId, error)); @@ -103,7 +103,7 @@ export const addReaction = (announcementId, name) => (dispatch, getState) => { dispatch(addReactionRequest(announcementId, name, alreadyAdded)); } - api(getState).put(`/api/v1/announcements/${announcementId}/reactions/${encodeURIComponent(name)}`).then(() => { + api().put(`/api/v1/announcements/${announcementId}/reactions/${encodeURIComponent(name)}`).then(() => { dispatch(addReactionSuccess(announcementId, name, alreadyAdded)); }).catch(err => { if (!alreadyAdded) { @@ -134,10 +134,10 @@ export const addReactionFail = (announcementId, name, error) => ({ skipLoading: true, }); -export const removeReaction = (announcementId, name) => (dispatch, getState) => { +export const removeReaction = (announcementId, name) => (dispatch) => { dispatch(removeReactionRequest(announcementId, name)); - api(getState).delete(`/api/v1/announcements/${announcementId}/reactions/${encodeURIComponent(name)}`).then(() => { + api().delete(`/api/v1/announcements/${announcementId}/reactions/${encodeURIComponent(name)}`).then(() => { dispatch(removeReactionSuccess(announcementId, name)); }).catch(err => { dispatch(removeReactionFail(announcementId, name, err)); diff --git a/app/javascript/mastodon/actions/blocks.js b/app/javascript/mastodon/actions/blocks.js index 54296d0905..5c66e27bec 100644 --- a/app/javascript/mastodon/actions/blocks.js +++ b/app/javascript/mastodon/actions/blocks.js @@ -13,10 +13,10 @@ export const BLOCKS_EXPAND_SUCCESS = 'BLOCKS_EXPAND_SUCCESS'; export const BLOCKS_EXPAND_FAIL = 'BLOCKS_EXPAND_FAIL'; export function fetchBlocks() { - return (dispatch, getState) => { + return (dispatch) => { dispatch(fetchBlocksRequest()); - api(getState).get('/api/v1/blocks').then(response => { + api().get('/api/v1/blocks').then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); dispatch(fetchBlocksSuccess(response.data, next ? next.uri : null)); @@ -56,7 +56,7 @@ export function expandBlocks() { dispatch(expandBlocksRequest()); - api(getState).get(url).then(response => { + api().get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); dispatch(expandBlocksSuccess(response.data, next ? next.uri : null)); diff --git a/app/javascript/mastodon/actions/bookmarks.js b/app/javascript/mastodon/actions/bookmarks.js index 0b16f61e63..89716b224c 100644 --- a/app/javascript/mastodon/actions/bookmarks.js +++ b/app/javascript/mastodon/actions/bookmarks.js @@ -18,7 +18,7 @@ export function fetchBookmarkedStatuses() { dispatch(fetchBookmarkedStatusesRequest()); - api(getState).get('/api/v1/bookmarks').then(response => { + api().get('/api/v1/bookmarks').then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedStatuses(response.data)); dispatch(fetchBookmarkedStatusesSuccess(response.data, next ? next.uri : null)); @@ -59,7 +59,7 @@ export function expandBookmarkedStatuses() { dispatch(expandBookmarkedStatusesRequest()); - api(getState).get(url).then(response => { + api().get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedStatuses(response.data)); dispatch(expandBookmarkedStatusesSuccess(response.data, next ? next.uri : null)); diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 013050e6f1..648f35ef08 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -196,7 +196,7 @@ export function submitCompose(routerHistory) { }); } - api(getState).request({ + api().request({ url: statusId === null ? '/api/v1/statuses' : `/api/v1/statuses/${statusId}`, method: statusId === null ? 'post' : 'put', data: { @@ -281,7 +281,7 @@ export function submitComposeFail(error) { export function uploadCompose(files) { return function (dispatch, getState) { - const uploadLimit = 4; + const uploadLimit = getState().getIn(['server', 'server', 'configuration', 'statuses', 'max_media_attachments']); const media = getState().getIn(['compose', 'media_attachments']); const pending = getState().getIn(['compose', 'pending_media_attachments']); const progress = new Array(files.length).fill(0); @@ -301,12 +301,12 @@ export function uploadCompose(files) { dispatch(uploadComposeRequest()); for (const [i, file] of Array.from(files).entries()) { - if (media.size + i > 3) break; + if (media.size + i > (uploadLimit - 1)) break; const data = new FormData(); data.append('file', file); - api(getState).post('/api/v2/media', data, { + api().post('/api/v2/media', data, { onUploadProgress: function({ loaded }){ progress[i] = loaded; dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total)); @@ -323,7 +323,7 @@ export function uploadCompose(files) { let tryCount = 1; const poll = () => { - api(getState).get(`/api/v1/media/${data.id}`).then(response => { + api().get(`/api/v1/media/${data.id}`).then(response => { if (response.status === 200) { dispatch(uploadComposeSuccess(response.data, file)); } else if (response.status === 206) { @@ -345,7 +345,7 @@ export const uploadComposeProcessing = () => ({ type: COMPOSE_UPLOAD_PROCESSING, }); -export const uploadThumbnail = (id, file) => (dispatch, getState) => { +export const uploadThumbnail = (id, file) => (dispatch) => { dispatch(uploadThumbnailRequest()); const total = file.size; @@ -353,7 +353,7 @@ export const uploadThumbnail = (id, file) => (dispatch, getState) => { data.append('thumbnail', file); - api(getState).put(`/api/v1/media/${id}`, data, { + api().put(`/api/v1/media/${id}`, data, { onUploadProgress: ({ loaded }) => { dispatch(uploadThumbnailProgress(loaded, total)); }, @@ -436,7 +436,7 @@ export function changeUploadCompose(id, params) { dispatch(changeUploadComposeSuccess(data, true)); } else { - api(getState).put(`/api/v1/media/${id}`, params).then(response => { + api().put(`/api/v1/media/${id}`, params).then(response => { dispatch(changeUploadComposeSuccess(response.data, false)); }).catch(error => { dispatch(changeUploadComposeFail(id, error)); @@ -524,7 +524,7 @@ const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) => fetchComposeSuggestionsAccountsController = new AbortController(); - api(getState).get('/api/v1/accounts/search', { + api().get('/api/v1/accounts/search', { signal: fetchComposeSuggestionsAccountsController.signal, params: { @@ -558,7 +558,7 @@ const fetchComposeSuggestionsTags = throttle((dispatch, getState, token) => { fetchComposeSuggestionsTagsController = new AbortController(); - api(getState).get('/api/v2/search', { + api().get('/api/v2/search', { signal: fetchComposeSuggestionsTagsController.signal, params: { diff --git a/app/javascript/mastodon/actions/conversations.js b/app/javascript/mastodon/actions/conversations.js index 8c4c4529fb..03174c485d 100644 --- a/app/javascript/mastodon/actions/conversations.js +++ b/app/javascript/mastodon/actions/conversations.js @@ -28,13 +28,13 @@ export const unmountConversations = () => ({ type: CONVERSATIONS_UNMOUNT, }); -export const markConversationRead = conversationId => (dispatch, getState) => { +export const markConversationRead = conversationId => (dispatch) => { dispatch({ type: CONVERSATIONS_READ, id: conversationId, }); - api(getState).post(`/api/v1/conversations/${conversationId}/read`); + api().post(`/api/v1/conversations/${conversationId}/read`); }; export const expandConversations = ({ maxId } = {}) => (dispatch, getState) => { @@ -48,7 +48,7 @@ export const expandConversations = ({ maxId } = {}) => (dispatch, getState) => { const isLoadingRecent = !!params.since_id; - api(getState).get('/api/v1/conversations', { params }) + api().get('/api/v1/conversations', { params }) .then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); @@ -88,10 +88,10 @@ export const updateConversations = conversation => dispatch => { }); }; -export const deleteConversation = conversationId => (dispatch, getState) => { +export const deleteConversation = conversationId => (dispatch) => { dispatch(deleteConversationRequest(conversationId)); - api(getState).delete(`/api/v1/conversations/${conversationId}`) + api().delete(`/api/v1/conversations/${conversationId}`) .then(() => dispatch(deleteConversationSuccess(conversationId))) .catch(error => dispatch(deleteConversationFail(conversationId, error))); }; diff --git a/app/javascript/mastodon/actions/custom_emojis.js b/app/javascript/mastodon/actions/custom_emojis.js index 9ec8156b17..fb65f072dc 100644 --- a/app/javascript/mastodon/actions/custom_emojis.js +++ b/app/javascript/mastodon/actions/custom_emojis.js @@ -5,10 +5,10 @@ export const CUSTOM_EMOJIS_FETCH_SUCCESS = 'CUSTOM_EMOJIS_FETCH_SUCCESS'; export const CUSTOM_EMOJIS_FETCH_FAIL = 'CUSTOM_EMOJIS_FETCH_FAIL'; export function fetchCustomEmojis() { - return (dispatch, getState) => { + return (dispatch) => { dispatch(fetchCustomEmojisRequest()); - api(getState).get('/api/v1/custom_emojis').then(response => { + api().get('/api/v1/custom_emojis').then(response => { dispatch(fetchCustomEmojisSuccess(response.data)); }).catch(error => { dispatch(fetchCustomEmojisFail(error)); diff --git a/app/javascript/mastodon/actions/directory.js b/app/javascript/mastodon/actions/directory.js deleted file mode 100644 index cda63f2b5a..0000000000 --- a/app/javascript/mastodon/actions/directory.js +++ /dev/null @@ -1,62 +0,0 @@ -import api from '../api'; - -import { fetchRelationships } from './accounts'; -import { importFetchedAccounts } from './importer'; - -export const DIRECTORY_FETCH_REQUEST = 'DIRECTORY_FETCH_REQUEST'; -export const DIRECTORY_FETCH_SUCCESS = 'DIRECTORY_FETCH_SUCCESS'; -export const DIRECTORY_FETCH_FAIL = 'DIRECTORY_FETCH_FAIL'; - -export const DIRECTORY_EXPAND_REQUEST = 'DIRECTORY_EXPAND_REQUEST'; -export const DIRECTORY_EXPAND_SUCCESS = 'DIRECTORY_EXPAND_SUCCESS'; -export const DIRECTORY_EXPAND_FAIL = 'DIRECTORY_EXPAND_FAIL'; - -export const fetchDirectory = params => (dispatch, getState) => { - dispatch(fetchDirectoryRequest()); - - api(getState).get('/api/v1/directory', { params: { ...params, limit: 20 } }).then(({ data }) => { - dispatch(importFetchedAccounts(data)); - dispatch(fetchDirectorySuccess(data)); - dispatch(fetchRelationships(data.map(x => x.id))); - }).catch(error => dispatch(fetchDirectoryFail(error))); -}; - -export const fetchDirectoryRequest = () => ({ - type: DIRECTORY_FETCH_REQUEST, -}); - -export const fetchDirectorySuccess = accounts => ({ - type: DIRECTORY_FETCH_SUCCESS, - accounts, -}); - -export const fetchDirectoryFail = error => ({ - type: DIRECTORY_FETCH_FAIL, - error, -}); - -export const expandDirectory = params => (dispatch, getState) => { - dispatch(expandDirectoryRequest()); - - const loadedItems = getState().getIn(['user_lists', 'directory', 'items']).size; - - api(getState).get('/api/v1/directory', { params: { ...params, offset: loadedItems, limit: 20 } }).then(({ data }) => { - dispatch(importFetchedAccounts(data)); - dispatch(expandDirectorySuccess(data)); - dispatch(fetchRelationships(data.map(x => x.id))); - }).catch(error => dispatch(expandDirectoryFail(error))); -}; - -export const expandDirectoryRequest = () => ({ - type: DIRECTORY_EXPAND_REQUEST, -}); - -export const expandDirectorySuccess = accounts => ({ - type: DIRECTORY_EXPAND_SUCCESS, - accounts, -}); - -export const expandDirectoryFail = error => ({ - type: DIRECTORY_EXPAND_FAIL, - error, -}); diff --git a/app/javascript/mastodon/actions/directory.ts b/app/javascript/mastodon/actions/directory.ts new file mode 100644 index 0000000000..34ac309c66 --- /dev/null +++ b/app/javascript/mastodon/actions/directory.ts @@ -0,0 +1,37 @@ +import type { List as ImmutableList } from 'immutable'; + +import { apiGetDirectory } from 'mastodon/api/directory'; +import { createDataLoadingThunk } from 'mastodon/store/typed_functions'; + +import { fetchRelationships } from './accounts'; +import { importFetchedAccounts } from './importer'; + +export const fetchDirectory = createDataLoadingThunk( + 'directory/fetch', + async (params: Parameters[0]) => + apiGetDirectory(params), + (data, { dispatch }) => { + dispatch(importFetchedAccounts(data)); + dispatch(fetchRelationships(data.map((x) => x.id))); + + return { accounts: data }; + }, +); + +export const expandDirectory = createDataLoadingThunk( + 'directory/expand', + async (params: Parameters[0], { getState }) => { + const loadedItems = getState().user_lists.getIn([ + 'directory', + 'items', + ]) as ImmutableList; + + return apiGetDirectory({ ...params, offset: loadedItems.size }, 20); + }, + (data, { dispatch }) => { + dispatch(importFetchedAccounts(data)); + dispatch(fetchRelationships(data.map((x) => x.id))); + + return { accounts: data }; + }, +); diff --git a/app/javascript/mastodon/actions/domain_blocks.js b/app/javascript/mastodon/actions/domain_blocks.js index 55c0a6ce9d..727f800af3 100644 --- a/app/javascript/mastodon/actions/domain_blocks.js +++ b/app/javascript/mastodon/actions/domain_blocks.js @@ -24,7 +24,7 @@ export function blockDomain(domain) { return (dispatch, getState) => { dispatch(blockDomainRequest(domain)); - api(getState).post('/api/v1/domain_blocks', { domain }).then(() => { + api().post('/api/v1/domain_blocks', { domain }).then(() => { const at_domain = '@' + domain; const accounts = getState().get('accounts').filter(item => item.get('acct').endsWith(at_domain)).valueSeq().map(item => item.get('id')); @@ -54,7 +54,7 @@ export function unblockDomain(domain) { return (dispatch, getState) => { dispatch(unblockDomainRequest(domain)); - api(getState).delete('/api/v1/domain_blocks', { params: { domain } }).then(() => { + api().delete('/api/v1/domain_blocks', { params: { domain } }).then(() => { const at_domain = '@' + domain; const accounts = getState().get('accounts').filter(item => item.get('acct').endsWith(at_domain)).valueSeq().map(item => item.get('id')); dispatch(unblockDomainSuccess({ domain, accounts })); @@ -80,10 +80,10 @@ export function unblockDomainFail(domain, error) { } export function fetchDomainBlocks() { - return (dispatch, getState) => { + return (dispatch) => { dispatch(fetchDomainBlocksRequest()); - api(getState).get('/api/v1/domain_blocks').then(response => { + api().get('/api/v1/domain_blocks').then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(fetchDomainBlocksSuccess(response.data, next ? next.uri : null)); }).catch(err => { @@ -123,7 +123,7 @@ export function expandDomainBlocks() { dispatch(expandDomainBlocksRequest()); - api(getState).get(url).then(response => { + api().get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(expandDomainBlocksSuccess(response.data, next ? next.uri : null)); }).catch(err => { diff --git a/app/javascript/mastodon/actions/favourites.js b/app/javascript/mastodon/actions/favourites.js index 2d4d4e6206..ff475c82be 100644 --- a/app/javascript/mastodon/actions/favourites.js +++ b/app/javascript/mastodon/actions/favourites.js @@ -18,7 +18,7 @@ export function fetchFavouritedStatuses() { dispatch(fetchFavouritedStatusesRequest()); - api(getState).get('/api/v1/favourites').then(response => { + api().get('/api/v1/favourites').then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedStatuses(response.data)); dispatch(fetchFavouritedStatusesSuccess(response.data, next ? next.uri : null)); @@ -62,7 +62,7 @@ export function expandFavouritedStatuses() { dispatch(expandFavouritedStatusesRequest()); - api(getState).get(url).then(response => { + api().get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedStatuses(response.data)); dispatch(expandFavouritedStatusesSuccess(response.data, next ? next.uri : null)); diff --git a/app/javascript/mastodon/actions/featured_tags.js b/app/javascript/mastodon/actions/featured_tags.js index 18bb615394..6ee4dee2bc 100644 --- a/app/javascript/mastodon/actions/featured_tags.js +++ b/app/javascript/mastodon/actions/featured_tags.js @@ -11,7 +11,7 @@ export const fetchFeaturedTags = (id) => (dispatch, getState) => { dispatch(fetchFeaturedTagsRequest(id)); - api(getState).get(`/api/v1/accounts/${id}/featured_tags`) + api().get(`/api/v1/accounts/${id}/featured_tags`) .then(({ data }) => dispatch(fetchFeaturedTagsSuccess(id, data))) .catch(err => dispatch(fetchFeaturedTagsFail(id, err))); }; diff --git a/app/javascript/mastodon/actions/filters.js b/app/javascript/mastodon/actions/filters.js index a11956ac56..588e390f0a 100644 --- a/app/javascript/mastodon/actions/filters.js +++ b/app/javascript/mastodon/actions/filters.js @@ -23,13 +23,13 @@ export const initAddFilter = (status, { contextType }) => dispatch => }, })); -export const fetchFilters = () => (dispatch, getState) => { +export const fetchFilters = () => (dispatch) => { dispatch({ type: FILTERS_FETCH_REQUEST, skipLoading: true, }); - api(getState) + api() .get('/api/v2/filters') .then(({ data }) => dispatch({ type: FILTERS_FETCH_SUCCESS, @@ -44,10 +44,10 @@ export const fetchFilters = () => (dispatch, getState) => { })); }; -export const createFilterStatus = (params, onSuccess, onFail) => (dispatch, getState) => { +export const createFilterStatus = (params, onSuccess, onFail) => (dispatch) => { dispatch(createFilterStatusRequest()); - api(getState).post(`/api/v2/filters/${params.filter_id}/statuses`, params).then(response => { + api().post(`/api/v2/filters/${params.filter_id}/statuses`, params).then(response => { dispatch(createFilterStatusSuccess(response.data)); if (onSuccess) onSuccess(); }).catch(error => { @@ -70,10 +70,10 @@ export const createFilterStatusFail = error => ({ error, }); -export const createFilter = (params, onSuccess, onFail) => (dispatch, getState) => { +export const createFilter = (params, onSuccess, onFail) => (dispatch) => { dispatch(createFilterRequest()); - api(getState).post('/api/v2/filters', params).then(response => { + api().post('/api/v2/filters', params).then(response => { dispatch(createFilterSuccess(response.data)); if (onSuccess) onSuccess(response.data); }).catch(error => { diff --git a/app/javascript/mastodon/actions/history.js b/app/javascript/mastodon/actions/history.js index 52401b7dce..07732ea187 100644 --- a/app/javascript/mastodon/actions/history.js +++ b/app/javascript/mastodon/actions/history.js @@ -15,7 +15,7 @@ export const fetchHistory = statusId => (dispatch, getState) => { dispatch(fetchHistoryRequest(statusId)); - api(getState).get(`/api/v1/statuses/${statusId}/history`).then(({ data }) => { + api().get(`/api/v1/statuses/${statusId}/history`).then(({ data }) => { dispatch(importFetchedAccounts(data.map(x => x.account))); dispatch(fetchHistorySuccess(statusId, data)); }).catch(error => dispatch(fetchHistoryFail(error))); diff --git a/app/javascript/mastodon/actions/importer/index.js b/app/javascript/mastodon/actions/importer/index.js index 16f191b584..516a7a7973 100644 --- a/app/javascript/mastodon/actions/importer/index.js +++ b/app/javascript/mastodon/actions/importer/index.js @@ -68,13 +68,17 @@ export function importFetchedStatuses(statuses) { status.filtered.forEach(result => pushUnique(filters, result.filter)); } - if (status.reblog && status.reblog.id) { + if (status.reblog?.id) { processStatus(status.reblog); } - if (status.poll && status.poll.id) { + if (status.poll?.id) { pushUnique(polls, normalizePoll(status.poll, getState().getIn(['polls', status.poll.id]))); } + + if (status.card) { + status.card.authors.forEach(author => author.account && pushUnique(accounts, author.account)); + } } statuses.forEach(processStatus); diff --git a/app/javascript/mastodon/actions/importer/normalizer.js b/app/javascript/mastodon/actions/importer/normalizer.js index b5a30343e4..c09a3f442c 100644 --- a/app/javascript/mastodon/actions/importer/normalizer.js +++ b/app/javascript/mastodon/actions/importer/normalizer.js @@ -36,6 +36,17 @@ export function normalizeStatus(status, normalOldStatus) { normalStatus.poll = status.poll.id; } + if (status.card) { + normalStatus.card = { + ...status.card, + authors: status.card.authors.map(author => ({ + ...author, + accountId: author.account?.id, + account: undefined, + })), + }; + } + if (status.filtered) { normalStatus.filtered = status.filtered.map(normalizeFilterResult); } diff --git a/app/javascript/mastodon/actions/interactions.js b/app/javascript/mastodon/actions/interactions.js index 7d0144438a..57f2459c01 100644 --- a/app/javascript/mastodon/actions/interactions.js +++ b/app/javascript/mastodon/actions/interactions.js @@ -3,10 +3,6 @@ import api, { getLinks } from '../api'; import { fetchRelationships } from './accounts'; import { importFetchedAccounts, importFetchedStatus } from './importer'; -export const REBLOG_REQUEST = 'REBLOG_REQUEST'; -export const REBLOG_SUCCESS = 'REBLOG_SUCCESS'; -export const REBLOG_FAIL = 'REBLOG_FAIL'; - export const REBLOGS_EXPAND_REQUEST = 'REBLOGS_EXPAND_REQUEST'; export const REBLOGS_EXPAND_SUCCESS = 'REBLOGS_EXPAND_SUCCESS'; export const REBLOGS_EXPAND_FAIL = 'REBLOGS_EXPAND_FAIL'; @@ -15,10 +11,6 @@ export const FAVOURITE_REQUEST = 'FAVOURITE_REQUEST'; export const FAVOURITE_SUCCESS = 'FAVOURITE_SUCCESS'; export const FAVOURITE_FAIL = 'FAVOURITE_FAIL'; -export const UNREBLOG_REQUEST = 'UNREBLOG_REQUEST'; -export const UNREBLOG_SUCCESS = 'UNREBLOG_SUCCESS'; -export const UNREBLOG_FAIL = 'UNREBLOG_FAIL'; - export const UNFAVOURITE_REQUEST = 'UNFAVOURITE_REQUEST'; export const UNFAVOURITE_SUCCESS = 'UNFAVOURITE_SUCCESS'; export const UNFAVOURITE_FAIL = 'UNFAVOURITE_FAIL'; @@ -51,89 +43,13 @@ export const UNBOOKMARK_REQUEST = 'UNBOOKMARKED_REQUEST'; export const UNBOOKMARK_SUCCESS = 'UNBOOKMARKED_SUCCESS'; export const UNBOOKMARK_FAIL = 'UNBOOKMARKED_FAIL'; -export function reblog(status, visibility) { - return function (dispatch, getState) { - dispatch(reblogRequest(status)); - - api(getState).post(`/api/v1/statuses/${status.get('id')}/reblog`, { visibility }).then(function (response) { - // The reblog API method returns a new status wrapped around the original. In this case we are only - // interested in how the original is modified, hence passing it skipping the wrapper - dispatch(importFetchedStatus(response.data.reblog)); - dispatch(reblogSuccess(status)); - }).catch(function (error) { - dispatch(reblogFail(status, error)); - }); - }; -} - -export function unreblog(status) { - return (dispatch, getState) => { - dispatch(unreblogRequest(status)); - - api(getState).post(`/api/v1/statuses/${status.get('id')}/unreblog`).then(response => { - dispatch(importFetchedStatus(response.data)); - dispatch(unreblogSuccess(status)); - }).catch(error => { - dispatch(unreblogFail(status, error)); - }); - }; -} - -export function reblogRequest(status) { - return { - type: REBLOG_REQUEST, - status: status, - skipLoading: true, - }; -} - -export function reblogSuccess(status) { - return { - type: REBLOG_SUCCESS, - status: status, - skipLoading: true, - }; -} - -export function reblogFail(status, error) { - return { - type: REBLOG_FAIL, - status: status, - error: error, - skipLoading: true, - }; -} - -export function unreblogRequest(status) { - return { - type: UNREBLOG_REQUEST, - status: status, - skipLoading: true, - }; -} - -export function unreblogSuccess(status) { - return { - type: UNREBLOG_SUCCESS, - status: status, - skipLoading: true, - }; -} - -export function unreblogFail(status, error) { - return { - type: UNREBLOG_FAIL, - status: status, - error: error, - skipLoading: true, - }; -} +export * from "./interactions_typed"; export function favourite(status) { - return function (dispatch, getState) { + return function (dispatch) { dispatch(favouriteRequest(status)); - api(getState).post(`/api/v1/statuses/${status.get('id')}/favourite`).then(function (response) { + api().post(`/api/v1/statuses/${status.get('id')}/favourite`).then(function (response) { dispatch(importFetchedStatus(response.data)); dispatch(favouriteSuccess(status)); }).catch(function (error) { @@ -143,10 +59,10 @@ export function favourite(status) { } export function unfavourite(status) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(unfavouriteRequest(status)); - api(getState).post(`/api/v1/statuses/${status.get('id')}/unfavourite`).then(response => { + api().post(`/api/v1/statuses/${status.get('id')}/unfavourite`).then(response => { dispatch(importFetchedStatus(response.data)); dispatch(unfavouriteSuccess(status)); }).catch(error => { @@ -206,10 +122,10 @@ export function unfavouriteFail(status, error) { } export function bookmark(status) { - return function (dispatch, getState) { + return function (dispatch) { dispatch(bookmarkRequest(status)); - api(getState).post(`/api/v1/statuses/${status.get('id')}/bookmark`).then(function (response) { + api().post(`/api/v1/statuses/${status.get('id')}/bookmark`).then(function (response) { dispatch(importFetchedStatus(response.data)); dispatch(bookmarkSuccess(status, response.data)); }).catch(function (error) { @@ -219,10 +135,10 @@ export function bookmark(status) { } export function unbookmark(status) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(unbookmarkRequest(status)); - api(getState).post(`/api/v1/statuses/${status.get('id')}/unbookmark`).then(response => { + api().post(`/api/v1/statuses/${status.get('id')}/unbookmark`).then(response => { dispatch(importFetchedStatus(response.data)); dispatch(unbookmarkSuccess(status, response.data)); }).catch(error => { @@ -278,10 +194,10 @@ export function unbookmarkFail(status, error) { } export function fetchReblogs(id) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(fetchReblogsRequest(id)); - api(getState).get(`/api/v1/statuses/${id}/reblogged_by`).then(response => { + api().get(`/api/v1/statuses/${id}/reblogged_by`).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); dispatch(fetchReblogsSuccess(id, response.data, next ? next.uri : null)); @@ -325,7 +241,7 @@ export function expandReblogs(id) { dispatch(expandReblogsRequest(id)); - api(getState).get(url).then(response => { + api().get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); @@ -360,10 +276,10 @@ export function expandReblogsFail(id, error) { } export function fetchFavourites(id) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(fetchFavouritesRequest(id)); - api(getState).get(`/api/v1/statuses/${id}/favourited_by`).then(response => { + api().get(`/api/v1/statuses/${id}/favourited_by`).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); dispatch(fetchFavouritesSuccess(id, response.data, next ? next.uri : null)); @@ -407,7 +323,7 @@ export function expandFavourites(id) { dispatch(expandFavouritesRequest(id)); - api(getState).get(url).then(response => { + api().get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); @@ -442,10 +358,10 @@ export function expandFavouritesFail(id, error) { } export function pin(status) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(pinRequest(status)); - api(getState).post(`/api/v1/statuses/${status.get('id')}/pin`).then(response => { + api().post(`/api/v1/statuses/${status.get('id')}/pin`).then(response => { dispatch(importFetchedStatus(response.data)); dispatch(pinSuccess(status)); }).catch(error => { @@ -480,10 +396,10 @@ export function pinFail(status, error) { } export function unpin (status) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(unpinRequest(status)); - api(getState).post(`/api/v1/statuses/${status.get('id')}/unpin`).then(response => { + api().post(`/api/v1/statuses/${status.get('id')}/unpin`).then(response => { dispatch(importFetchedStatus(response.data)); dispatch(unpinSuccess(status)); }).catch(error => { diff --git a/app/javascript/mastodon/actions/interactions_typed.ts b/app/javascript/mastodon/actions/interactions_typed.ts new file mode 100644 index 0000000000..f58faffa86 --- /dev/null +++ b/app/javascript/mastodon/actions/interactions_typed.ts @@ -0,0 +1,35 @@ +import { apiReblog, apiUnreblog } from 'mastodon/api/interactions'; +import type { StatusVisibility } from 'mastodon/models/status'; +import { createDataLoadingThunk } from 'mastodon/store/typed_functions'; + +import { importFetchedStatus } from './importer'; + +export const reblog = createDataLoadingThunk( + 'status/reblog', + ({ + statusId, + visibility, + }: { + statusId: string; + visibility: StatusVisibility; + }) => apiReblog(statusId, visibility), + (data, { dispatch, discardLoadData }) => { + // The reblog API method returns a new status wrapped around the original. In this case we are only + // interested in how the original is modified, hence passing it skipping the wrapper + dispatch(importFetchedStatus(data.reblog)); + + // The payload is not used in any actions + return discardLoadData; + }, +); + +export const unreblog = createDataLoadingThunk( + 'status/unreblog', + ({ statusId }: { statusId: string }) => apiUnreblog(statusId), + (data, { dispatch, discardLoadData }) => { + dispatch(importFetchedStatus(data)); + + // The payload is not used in any actions + return discardLoadData; + }, +); diff --git a/app/javascript/mastodon/actions/lists.js b/app/javascript/mastodon/actions/lists.js index b0789cd426..9956059387 100644 --- a/app/javascript/mastodon/actions/lists.js +++ b/app/javascript/mastodon/actions/lists.js @@ -57,7 +57,7 @@ export const fetchList = id => (dispatch, getState) => { dispatch(fetchListRequest(id)); - api(getState).get(`/api/v1/lists/${id}`) + api().get(`/api/v1/lists/${id}`) .then(({ data }) => dispatch(fetchListSuccess(data))) .catch(err => dispatch(fetchListFail(id, err))); }; @@ -78,10 +78,10 @@ export const fetchListFail = (id, error) => ({ error, }); -export const fetchLists = () => (dispatch, getState) => { +export const fetchLists = () => (dispatch) => { dispatch(fetchListsRequest()); - api(getState).get('/api/v1/lists') + api().get('/api/v1/lists') .then(({ data }) => dispatch(fetchListsSuccess(data))) .catch(err => dispatch(fetchListsFail(err))); }; @@ -125,10 +125,10 @@ export const changeListEditorTitle = value => ({ value, }); -export const createList = (title, shouldReset) => (dispatch, getState) => { +export const createList = (title, shouldReset) => (dispatch) => { dispatch(createListRequest()); - api(getState).post('/api/v1/lists', { title }).then(({ data }) => { + api().post('/api/v1/lists', { title }).then(({ data }) => { dispatch(createListSuccess(data)); if (shouldReset) { @@ -151,10 +151,10 @@ export const createListFail = error => ({ error, }); -export const updateList = (id, title, shouldReset, isExclusive, replies_policy) => (dispatch, getState) => { +export const updateList = (id, title, shouldReset, isExclusive, replies_policy) => (dispatch) => { dispatch(updateListRequest(id)); - api(getState).put(`/api/v1/lists/${id}`, { title, replies_policy, exclusive: typeof isExclusive === 'undefined' ? undefined : !!isExclusive }).then(({ data }) => { + api().put(`/api/v1/lists/${id}`, { title, replies_policy, exclusive: typeof isExclusive === 'undefined' ? undefined : !!isExclusive }).then(({ data }) => { dispatch(updateListSuccess(data)); if (shouldReset) { @@ -183,10 +183,10 @@ export const resetListEditor = () => ({ type: LIST_EDITOR_RESET, }); -export const deleteList = id => (dispatch, getState) => { +export const deleteList = id => (dispatch) => { dispatch(deleteListRequest(id)); - api(getState).delete(`/api/v1/lists/${id}`) + api().delete(`/api/v1/lists/${id}`) .then(() => dispatch(deleteListSuccess(id))) .catch(err => dispatch(deleteListFail(id, err))); }; @@ -207,10 +207,10 @@ export const deleteListFail = (id, error) => ({ error, }); -export const fetchListAccounts = listId => (dispatch, getState) => { +export const fetchListAccounts = listId => (dispatch) => { dispatch(fetchListAccountsRequest(listId)); - api(getState).get(`/api/v1/lists/${listId}/accounts`, { params: { limit: 0 } }).then(({ data }) => { + api().get(`/api/v1/lists/${listId}/accounts`, { params: { limit: 0 } }).then(({ data }) => { dispatch(importFetchedAccounts(data)); dispatch(fetchListAccountsSuccess(listId, data)); }).catch(err => dispatch(fetchListAccountsFail(listId, err))); @@ -234,7 +234,7 @@ export const fetchListAccountsFail = (id, error) => ({ error, }); -export const fetchListSuggestions = q => (dispatch, getState) => { +export const fetchListSuggestions = q => (dispatch) => { const params = { q, resolve: false, @@ -242,7 +242,7 @@ export const fetchListSuggestions = q => (dispatch, getState) => { following: true, }; - api(getState).get('/api/v1/accounts/search', { params }).then(({ data }) => { + api().get('/api/v1/accounts/search', { params }).then(({ data }) => { dispatch(importFetchedAccounts(data)); dispatch(fetchListSuggestionsReady(q, data)); }).catch(error => dispatch(showAlertForError(error))); @@ -267,10 +267,10 @@ export const addToListEditor = accountId => (dispatch, getState) => { dispatch(addToList(getState().getIn(['listEditor', 'listId']), accountId)); }; -export const addToList = (listId, accountId) => (dispatch, getState) => { +export const addToList = (listId, accountId) => (dispatch) => { dispatch(addToListRequest(listId, accountId)); - api(getState).post(`/api/v1/lists/${listId}/accounts`, { account_ids: [accountId] }) + api().post(`/api/v1/lists/${listId}/accounts`, { account_ids: [accountId] }) .then(() => dispatch(addToListSuccess(listId, accountId))) .catch(err => dispatch(addToListFail(listId, accountId, err))); }; @@ -298,10 +298,10 @@ export const removeFromListEditor = accountId => (dispatch, getState) => { dispatch(removeFromList(getState().getIn(['listEditor', 'listId']), accountId)); }; -export const removeFromList = (listId, accountId) => (dispatch, getState) => { +export const removeFromList = (listId, accountId) => (dispatch) => { dispatch(removeFromListRequest(listId, accountId)); - api(getState).delete(`/api/v1/lists/${listId}/accounts`, { params: { account_ids: [accountId] } }) + api().delete(`/api/v1/lists/${listId}/accounts`, { params: { account_ids: [accountId] } }) .then(() => dispatch(removeFromListSuccess(listId, accountId))) .catch(err => dispatch(removeFromListFail(listId, accountId, err))); }; @@ -338,10 +338,10 @@ export const setupListAdder = accountId => (dispatch, getState) => { dispatch(fetchAccountLists(accountId)); }; -export const fetchAccountLists = accountId => (dispatch, getState) => { +export const fetchAccountLists = accountId => (dispatch) => { dispatch(fetchAccountListsRequest(accountId)); - api(getState).get(`/api/v1/accounts/${accountId}/lists`) + api().get(`/api/v1/accounts/${accountId}/lists`) .then(({ data }) => dispatch(fetchAccountListsSuccess(accountId, data))) .catch(err => dispatch(fetchAccountListsFail(accountId, err))); }; @@ -370,4 +370,3 @@ export const addToListAdder = listId => (dispatch, getState) => { export const removeFromListAdder = listId => (dispatch, getState) => { dispatch(removeFromList(listId, getState().getIn(['listAdder', 'accountId']))); }; - diff --git a/app/javascript/mastodon/actions/markers.ts b/app/javascript/mastodon/actions/markers.ts index 84e5b33bcc..03f577c540 100644 --- a/app/javascript/mastodon/actions/markers.ts +++ b/app/javascript/mastodon/actions/markers.ts @@ -1,21 +1,24 @@ -import { List as ImmutableList } from 'immutable'; - import { debounce } from 'lodash'; import type { MarkerJSON } from 'mastodon/api_types/markers'; -import type { RootState } from 'mastodon/store'; +import { getAccessToken } from 'mastodon/initial_state'; +import type { AppDispatch, RootState } from 'mastodon/store'; import { createAppAsyncThunk } from 'mastodon/store/typed_functions'; -import api, { authorizationTokenFromState } from '../api'; +import api from '../api'; import { compareId } from '../compare_id'; export const synchronouslySubmitMarkers = createAppAsyncThunk( 'markers/submit', async (_args, { getState }) => { - const accessToken = authorizationTokenFromState(getState); + const accessToken = getAccessToken(); const params = buildPostMarkersParams(getState()); - if (Object.keys(params).length === 0 || !accessToken) { + if ( + Object.keys(params).length === 0 || + !accessToken || + accessToken === '' + ) { return; } @@ -71,34 +74,17 @@ interface MarkerParam { last_read_id?: string; } -function getLastHomeId(state: RootState): string | undefined { - /* eslint-disable @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */ - return ( - state - // @ts-expect-error state.timelines is not yet typed - .getIn(['timelines', 'home', 'items'], ImmutableList()) - // @ts-expect-error state.timelines is not yet typed - .find((item) => item !== null) - ); -} - function getLastNotificationId(state: RootState): string | undefined { // @ts-expect-error state.notifications is not yet typed + // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call return state.getIn(['notifications', 'lastReadId']); } const buildPostMarkersParams = (state: RootState) => { const params = {} as { home?: MarkerParam; notifications?: MarkerParam }; - const lastHomeId = getLastHomeId(state); const lastNotificationId = getLastNotificationId(state); - if (lastHomeId && compareId(lastHomeId, state.markers.home) > 0) { - params.home = { - last_read_id: lastHomeId, - }; - } - if ( lastNotificationId && compareId(lastNotificationId, state.markers.notifications) > 0 @@ -115,14 +101,14 @@ export const submitMarkersAction = createAppAsyncThunk<{ home: string | undefined; notifications: string | undefined; }>('markers/submitAction', async (_args, { getState }) => { - const accessToken = authorizationTokenFromState(getState); + const accessToken = getAccessToken(); const params = buildPostMarkersParams(getState()); - if (Object.keys(params).length === 0 || accessToken === '') { + if (Object.keys(params).length === 0 || !accessToken || accessToken === '') { return { home: undefined, notifications: undefined }; } - await api(getState).post('/api/v1/markers', params); + await api().post('/api/v1/markers', params); return { home: params.home?.last_read_id, @@ -131,8 +117,8 @@ export const submitMarkersAction = createAppAsyncThunk<{ }); const debouncedSubmitMarkers = debounce( - (dispatch) => { - dispatch(submitMarkersAction()); + (dispatch: AppDispatch) => { + void dispatch(submitMarkersAction()); }, 300000, { @@ -152,14 +138,11 @@ export const submitMarkers = createAppAsyncThunk( }, ); -export const fetchMarkers = createAppAsyncThunk( - 'markers/fetch', - async (_args, { getState }) => { - const response = await api(getState).get>( - `/api/v1/markers`, - { params: { timeline: ['notifications'] } }, - ); +export const fetchMarkers = createAppAsyncThunk('markers/fetch', async () => { + const response = await api().get>( + `/api/v1/markers`, + { params: { timeline: ['notifications'] } }, + ); - return { markers: response.data }; - }, -); + return { markers: response.data }; +}); diff --git a/app/javascript/mastodon/actions/mutes.js b/app/javascript/mastodon/actions/mutes.js index 99c113f414..3676748cf3 100644 --- a/app/javascript/mastodon/actions/mutes.js +++ b/app/javascript/mastodon/actions/mutes.js @@ -13,10 +13,10 @@ export const MUTES_EXPAND_SUCCESS = 'MUTES_EXPAND_SUCCESS'; export const MUTES_EXPAND_FAIL = 'MUTES_EXPAND_FAIL'; export function fetchMutes() { - return (dispatch, getState) => { + return (dispatch) => { dispatch(fetchMutesRequest()); - api(getState).get('/api/v1/mutes').then(response => { + api().get('/api/v1/mutes').then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); dispatch(fetchMutesSuccess(response.data, next ? next.uri : null)); @@ -56,7 +56,7 @@ export function expandMutes() { dispatch(expandMutesRequest()); - api(getState).get(url).then(response => { + api().get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data)); dispatch(expandMutesSuccess(response.data, next ? next.uri : null)); diff --git a/app/javascript/mastodon/actions/notification_policies.ts b/app/javascript/mastodon/actions/notification_policies.ts new file mode 100644 index 0000000000..fcc9919c49 --- /dev/null +++ b/app/javascript/mastodon/actions/notification_policies.ts @@ -0,0 +1,16 @@ +import { + apiGetNotificationPolicy, + apiUpdateNotificationsPolicy, +} from 'mastodon/api/notification_policies'; +import type { NotificationPolicy } from 'mastodon/models/notification_policy'; +import { createDataLoadingThunk } from 'mastodon/store/typed_functions'; + +export const fetchNotificationPolicy = createDataLoadingThunk( + 'notificationPolicy/fetch', + () => apiGetNotificationPolicy(), +); + +export const updateNotificationsPolicy = createDataLoadingThunk( + 'notificationPolicy/update', + (policy: Partial) => apiUpdateNotificationsPolicy(policy), +); diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js index b54cbe27b9..6a59d5624e 100644 --- a/app/javascript/mastodon/actions/notifications.js +++ b/app/javascript/mastodon/actions/notifications.js @@ -44,10 +44,6 @@ export const NOTIFICATIONS_MARK_AS_READ = 'NOTIFICATIONS_MARK_AS_READ'; export const NOTIFICATIONS_SET_BROWSER_SUPPORT = 'NOTIFICATIONS_SET_BROWSER_SUPPORT'; export const NOTIFICATIONS_SET_BROWSER_PERMISSION = 'NOTIFICATIONS_SET_BROWSER_PERMISSION'; -export const NOTIFICATION_POLICY_FETCH_REQUEST = 'NOTIFICATION_POLICY_FETCH_REQUEST'; -export const NOTIFICATION_POLICY_FETCH_SUCCESS = 'NOTIFICATION_POLICY_FETCH_SUCCESS'; -export const NOTIFICATION_POLICY_FETCH_FAIL = 'NOTIFICATION_POLICY_FETCH_FAIL'; - export const NOTIFICATION_REQUESTS_FETCH_REQUEST = 'NOTIFICATION_REQUESTS_FETCH_REQUEST'; export const NOTIFICATION_REQUESTS_FETCH_SUCCESS = 'NOTIFICATION_REQUESTS_FETCH_SUCCESS'; export const NOTIFICATION_REQUESTS_FETCH_FAIL = 'NOTIFICATION_REQUESTS_FETCH_FAIL'; @@ -216,7 +212,7 @@ export function expandNotifications({ maxId, forceLoad } = {}, done = noOp) { dispatch(expandNotificationsRequest(isLoadingMore)); - api(getState).get('/api/v1/notifications', { params, signal: expandNotificationsController.signal }).then(response => { + api().get('/api/v1/notifications', { params, signal: expandNotificationsController.signal }).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data.map(item => item.account))); @@ -262,12 +258,12 @@ export function expandNotificationsFail(error, isLoadingMore) { } export function clearNotifications() { - return (dispatch, getState) => { + return (dispatch) => { dispatch({ type: NOTIFICATIONS_CLEAR, }); - api(getState).post('/api/v1/notifications/clear'); + api().post('/api/v1/notifications/clear'); }; } @@ -346,40 +342,6 @@ export function setBrowserPermission (value) { }; } -export const fetchNotificationPolicy = () => (dispatch, getState) => { - dispatch(fetchNotificationPolicyRequest()); - - api(getState).get('/api/v1/notifications/policy').then(({ data }) => { - dispatch(fetchNotificationPolicySuccess(data)); - }).catch(err => { - dispatch(fetchNotificationPolicyFail(err)); - }); -}; - -export const fetchNotificationPolicyRequest = () => ({ - type: NOTIFICATION_POLICY_FETCH_REQUEST, -}); - -export const fetchNotificationPolicySuccess = policy => ({ - type: NOTIFICATION_POLICY_FETCH_SUCCESS, - policy, -}); - -export const fetchNotificationPolicyFail = error => ({ - type: NOTIFICATION_POLICY_FETCH_FAIL, - error, -}); - -export const updateNotificationsPolicy = params => (dispatch, getState) => { - dispatch(fetchNotificationPolicyRequest()); - - api(getState).put('/api/v1/notifications/policy', params).then(({ data }) => { - dispatch(fetchNotificationPolicySuccess(data)); - }).catch(err => { - dispatch(fetchNotificationPolicyFail(err)); - }); -}; - export const fetchNotificationRequests = () => (dispatch, getState) => { const params = {}; @@ -393,7 +355,7 @@ export const fetchNotificationRequests = () => (dispatch, getState) => { dispatch(fetchNotificationRequestsRequest()); - api(getState).get('/api/v1/notifications/requests', { params }).then(response => { + api().get('/api/v1/notifications/requests', { params }).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data.map(x => x.account))); dispatch(fetchNotificationRequestsSuccess(response.data, next ? next.uri : null)); @@ -426,7 +388,7 @@ export const expandNotificationRequests = () => (dispatch, getState) => { dispatch(expandNotificationRequestsRequest()); - api(getState).get(url).then(response => { + api().get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data.map(x => x.account))); dispatch(expandNotificationRequestsSuccess(response.data, next?.uri)); @@ -459,7 +421,7 @@ export const fetchNotificationRequest = id => (dispatch, getState) => { dispatch(fetchNotificationRequestRequest(id)); - api(getState).get(`/api/v1/notifications/requests/${id}`).then(({ data }) => { + api().get(`/api/v1/notifications/requests/${id}`).then(({ data }) => { dispatch(fetchNotificationRequestSuccess(data)); }).catch(err => { dispatch(fetchNotificationRequestFail(id, err)); @@ -482,10 +444,10 @@ export const fetchNotificationRequestFail = (id, error) => ({ error, }); -export const acceptNotificationRequest = id => (dispatch, getState) => { +export const acceptNotificationRequest = id => (dispatch) => { dispatch(acceptNotificationRequestRequest(id)); - api(getState).post(`/api/v1/notifications/requests/${id}/accept`).then(() => { + api().post(`/api/v1/notifications/requests/${id}/accept`).then(() => { dispatch(acceptNotificationRequestSuccess(id)); }).catch(err => { dispatch(acceptNotificationRequestFail(id, err)); @@ -508,10 +470,10 @@ export const acceptNotificationRequestFail = (id, error) => ({ error, }); -export const dismissNotificationRequest = id => (dispatch, getState) => { +export const dismissNotificationRequest = id => (dispatch) => { dispatch(dismissNotificationRequestRequest(id)); - api(getState).post(`/api/v1/notifications/requests/${id}/dismiss`).then(() =>{ + api().post(`/api/v1/notifications/requests/${id}/dismiss`).then(() =>{ dispatch(dismissNotificationRequestSuccess(id)); }).catch(err => { dispatch(dismissNotificationRequestFail(id, err)); @@ -550,7 +512,7 @@ export const fetchNotificationsForRequest = accountId => (dispatch, getState) => dispatch(fetchNotificationsForRequestRequest()); - api(getState).get('/api/v1/notifications', { params }).then(response => { + api().get('/api/v1/notifications', { params }).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data.map(item => item.account))); dispatch(importFetchedStatuses(response.data.map(item => item.status).filter(status => !!status))); @@ -586,7 +548,7 @@ export const expandNotificationsForRequest = () => (dispatch, getState) => { dispatch(expandNotificationsForRequestRequest()); - api(getState).get(url).then(response => { + api().get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedAccounts(response.data.map(item => item.account))); dispatch(importFetchedStatuses(response.data.map(item => item.status).filter(status => !!status))); diff --git a/app/javascript/mastodon/actions/pin_statuses.js b/app/javascript/mastodon/actions/pin_statuses.js index baa10d1562..d583eab573 100644 --- a/app/javascript/mastodon/actions/pin_statuses.js +++ b/app/javascript/mastodon/actions/pin_statuses.js @@ -8,10 +8,10 @@ export const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS'; export const PINNED_STATUSES_FETCH_FAIL = 'PINNED_STATUSES_FETCH_FAIL'; export function fetchPinnedStatuses() { - return (dispatch, getState) => { + return (dispatch) => { dispatch(fetchPinnedStatusesRequest()); - api(getState).get(`/api/v1/accounts/${me}/statuses`, { params: { pinned: true } }).then(response => { + api().get(`/api/v1/accounts/${me}/statuses`, { params: { pinned: true } }).then(response => { dispatch(importFetchedStatuses(response.data)); dispatch(fetchPinnedStatusesSuccess(response.data, null)); }).catch(error => { diff --git a/app/javascript/mastodon/actions/polls.js b/app/javascript/mastodon/actions/polls.js index a37410dc90..aa49341444 100644 --- a/app/javascript/mastodon/actions/polls.js +++ b/app/javascript/mastodon/actions/polls.js @@ -10,10 +10,10 @@ export const POLL_FETCH_REQUEST = 'POLL_FETCH_REQUEST'; export const POLL_FETCH_SUCCESS = 'POLL_FETCH_SUCCESS'; export const POLL_FETCH_FAIL = 'POLL_FETCH_FAIL'; -export const vote = (pollId, choices) => (dispatch, getState) => { +export const vote = (pollId, choices) => (dispatch) => { dispatch(voteRequest()); - api(getState).post(`/api/v1/polls/${pollId}/votes`, { choices }) + api().post(`/api/v1/polls/${pollId}/votes`, { choices }) .then(({ data }) => { dispatch(importFetchedPoll(data)); dispatch(voteSuccess(data)); @@ -21,10 +21,10 @@ export const vote = (pollId, choices) => (dispatch, getState) => { .catch(err => dispatch(voteFail(err))); }; -export const fetchPoll = pollId => (dispatch, getState) => { +export const fetchPoll = pollId => (dispatch) => { dispatch(fetchPollRequest()); - api(getState).get(`/api/v1/polls/${pollId}`) + api().get(`/api/v1/polls/${pollId}`) .then(({ data }) => { dispatch(importFetchedPoll(data)); dispatch(fetchPollSuccess(data)); diff --git a/app/javascript/mastodon/actions/reports.js b/app/javascript/mastodon/actions/reports.js index 756b8cd05e..49b89b0d13 100644 --- a/app/javascript/mastodon/actions/reports.js +++ b/app/javascript/mastodon/actions/reports.js @@ -15,10 +15,10 @@ export const initReport = (account, status) => dispatch => }, })); -export const submitReport = (params, onSuccess, onFail) => (dispatch, getState) => { +export const submitReport = (params, onSuccess, onFail) => (dispatch) => { dispatch(submitReportRequest()); - api(getState).post('/api/v1/reports', params).then(response => { + api().post('/api/v1/reports', params).then(response => { dispatch(submitReportSuccess(response.data)); if (onSuccess) onSuccess(); }).catch(error => { diff --git a/app/javascript/mastodon/actions/search.js b/app/javascript/mastodon/actions/search.js index a34a490e76..bde17ae0db 100644 --- a/app/javascript/mastodon/actions/search.js +++ b/app/javascript/mastodon/actions/search.js @@ -46,7 +46,7 @@ export function submitSearch(type) { dispatch(fetchSearchRequest(type)); - api(getState).get('/api/v2/search', { + api().get('/api/v2/search', { params: { q: value, resolve: signedIn, @@ -99,7 +99,7 @@ export const expandSearch = type => (dispatch, getState) => { dispatch(expandSearchRequest(type)); - api(getState).get('/api/v2/search', { + api().get('/api/v2/search', { params: { q: value, type, @@ -156,7 +156,7 @@ export const openURL = (value, history, onFailure) => (dispatch, getState) => { dispatch(fetchSearchRequest()); - api(getState).get('/api/v2/search', { params: { q: value, resolve: true } }).then(response => { + api().get('/api/v2/search', { params: { q: value, resolve: true } }).then(response => { if (response.data.accounts?.length > 0) { dispatch(importFetchedAccounts(response.data.accounts)); history.push(`/@${response.data.accounts[0].acct}`); diff --git a/app/javascript/mastodon/actions/server.js b/app/javascript/mastodon/actions/server.js index 65f3efc3a7..32ee093afa 100644 --- a/app/javascript/mastodon/actions/server.js +++ b/app/javascript/mastodon/actions/server.js @@ -25,7 +25,7 @@ export const fetchServer = () => (dispatch, getState) => { dispatch(fetchServerRequest()); - api(getState) + api() .get('/api/v2/instance').then(({ data }) => { if (data.contact.account) dispatch(importFetchedAccount(data.contact.account)); dispatch(fetchServerSuccess(data)); @@ -46,10 +46,10 @@ const fetchServerFail = error => ({ error, }); -export const fetchServerTranslationLanguages = () => (dispatch, getState) => { +export const fetchServerTranslationLanguages = () => (dispatch) => { dispatch(fetchServerTranslationLanguagesRequest()); - api(getState) + api() .get('/api/v1/instance/translation_languages').then(({ data }) => { dispatch(fetchServerTranslationLanguagesSuccess(data)); }).catch(err => dispatch(fetchServerTranslationLanguagesFail(err))); @@ -76,7 +76,7 @@ export const fetchExtendedDescription = () => (dispatch, getState) => { dispatch(fetchExtendedDescriptionRequest()); - api(getState) + api() .get('/api/v1/instance/extended_description') .then(({ data }) => dispatch(fetchExtendedDescriptionSuccess(data))) .catch(err => dispatch(fetchExtendedDescriptionFail(err))); @@ -103,7 +103,7 @@ export const fetchDomainBlocks = () => (dispatch, getState) => { dispatch(fetchDomainBlocksRequest()); - api(getState) + api() .get('/api/v1/instance/domain_blocks') .then(({ data }) => dispatch(fetchDomainBlocksSuccess(true, data))) .catch(err => { diff --git a/app/javascript/mastodon/actions/settings.js b/app/javascript/mastodon/actions/settings.js index 3685b0684e..fbd89f9d4b 100644 --- a/app/javascript/mastodon/actions/settings.js +++ b/app/javascript/mastodon/actions/settings.js @@ -20,7 +20,7 @@ export function changeSetting(path, value) { } const debouncedSave = debounce((dispatch, getState) => { - if (getState().getIn(['settings', 'saved'])) { + if (getState().getIn(['settings', 'saved']) || !getState().getIn(['meta', 'me'])) { return; } diff --git a/app/javascript/mastodon/actions/statuses.js b/app/javascript/mastodon/actions/statuses.js index 3aed807358..a60b80dc2c 100644 --- a/app/javascript/mastodon/actions/statuses.js +++ b/app/javascript/mastodon/actions/statuses.js @@ -59,7 +59,7 @@ export function fetchStatus(id, forceFetch = false) { dispatch(fetchStatusRequest(id, skipLoading)); - api(getState).get(`/api/v1/statuses/${id}`).then(response => { + api().get(`/api/v1/statuses/${id}`).then(response => { dispatch(importFetchedStatus(response.data)); dispatch(fetchStatusSuccess(skipLoading)); }).catch(error => { @@ -102,7 +102,7 @@ export const editStatus = (id, routerHistory) => (dispatch, getState) => { dispatch(fetchStatusSourceRequest()); - api(getState).get(`/api/v1/statuses/${id}/source`).then(response => { + api().get(`/api/v1/statuses/${id}/source`).then(response => { dispatch(fetchStatusSourceSuccess()); ensureComposeIsVisible(getState, routerHistory); dispatch(setComposeToStatus(status, response.data.text, response.data.spoiler_text)); @@ -134,7 +134,7 @@ export function deleteStatus(id, routerHistory, withRedraft = false) { dispatch(deleteStatusRequest(id)); - api(getState).delete(`/api/v1/statuses/${id}`).then(response => { + api().delete(`/api/v1/statuses/${id}`).then(response => { dispatch(deleteStatusSuccess(id)); dispatch(deleteFromTimelines(id)); dispatch(importFetchedAccount(response.data.account)); @@ -175,10 +175,10 @@ export const updateStatus = status => dispatch => dispatch(importFetchedStatus(status)); export function fetchContext(id) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(fetchContextRequest(id)); - api(getState).get(`/api/v1/statuses/${id}/context`).then(response => { + api().get(`/api/v1/statuses/${id}/context`).then(response => { dispatch(importFetchedStatuses(response.data.ancestors.concat(response.data.descendants))); dispatch(fetchContextSuccess(id, response.data.ancestors, response.data.descendants)); @@ -219,10 +219,10 @@ export function fetchContextFail(id, error) { } export function muteStatus(id) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(muteStatusRequest(id)); - api(getState).post(`/api/v1/statuses/${id}/mute`).then(() => { + api().post(`/api/v1/statuses/${id}/mute`).then(() => { dispatch(muteStatusSuccess(id)); }).catch(error => { dispatch(muteStatusFail(id, error)); @@ -253,10 +253,10 @@ export function muteStatusFail(id, error) { } export function unmuteStatus(id) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(unmuteStatusRequest(id)); - api(getState).post(`/api/v1/statuses/${id}/unmute`).then(() => { + api().post(`/api/v1/statuses/${id}/unmute`).then(() => { dispatch(unmuteStatusSuccess(id)); }).catch(error => { dispatch(unmuteStatusFail(id, error)); @@ -316,10 +316,10 @@ export function toggleStatusCollapse(id, isCollapsed) { }; } -export const translateStatus = id => (dispatch, getState) => { +export const translateStatus = id => (dispatch) => { dispatch(translateStatusRequest(id)); - api(getState).post(`/api/v1/statuses/${id}/translate`).then(response => { + api().post(`/api/v1/statuses/${id}/translate`).then(response => { dispatch(translateStatusSuccess(id, response.data)); }).catch(error => { dispatch(translateStatusFail(id, error)); diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js index 9daeb3c60f..e7fe1c53ed 100644 --- a/app/javascript/mastodon/actions/streaming.js +++ b/app/javascript/mastodon/actions/streaming.js @@ -77,7 +77,7 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti }, onDisconnect() { - dispatch(disconnectTimeline(timelineId)); + dispatch(disconnectTimeline({ timeline: timelineId })); if (options.fallback) { // @ts-expect-error diff --git a/app/javascript/mastodon/actions/suggestions.js b/app/javascript/mastodon/actions/suggestions.js index 8eafe38b21..258ffa901d 100644 --- a/app/javascript/mastodon/actions/suggestions.js +++ b/app/javascript/mastodon/actions/suggestions.js @@ -10,10 +10,10 @@ export const SUGGESTIONS_FETCH_FAIL = 'SUGGESTIONS_FETCH_FAIL'; export const SUGGESTIONS_DISMISS = 'SUGGESTIONS_DISMISS'; export function fetchSuggestions(withRelationships = false) { - return (dispatch, getState) => { + return (dispatch) => { dispatch(fetchSuggestionsRequest()); - api(getState).get('/api/v2/suggestions', { params: { limit: 20 } }).then(response => { + api().get('/api/v2/suggestions', { params: { limit: 20 } }).then(response => { dispatch(importFetchedAccounts(response.data.map(x => x.account))); dispatch(fetchSuggestionsSuccess(response.data)); @@ -48,11 +48,11 @@ export function fetchSuggestionsFail(error) { }; } -export const dismissSuggestion = accountId => (dispatch, getState) => { +export const dismissSuggestion = accountId => (dispatch) => { dispatch({ type: SUGGESTIONS_DISMISS, id: accountId, }); - api(getState).delete(`/api/v1/suggestions/${accountId}`).catch(() => {}); + api().delete(`/api/v1/suggestions/${accountId}`).catch(() => {}); }; diff --git a/app/javascript/mastodon/actions/tags.js b/app/javascript/mastodon/actions/tags.js index dda8c924bb..d18d7e514f 100644 --- a/app/javascript/mastodon/actions/tags.js +++ b/app/javascript/mastodon/actions/tags.js @@ -20,10 +20,10 @@ export const HASHTAG_UNFOLLOW_REQUEST = 'HASHTAG_UNFOLLOW_REQUEST'; export const HASHTAG_UNFOLLOW_SUCCESS = 'HASHTAG_UNFOLLOW_SUCCESS'; export const HASHTAG_UNFOLLOW_FAIL = 'HASHTAG_UNFOLLOW_FAIL'; -export const fetchHashtag = name => (dispatch, getState) => { +export const fetchHashtag = name => (dispatch) => { dispatch(fetchHashtagRequest()); - api(getState).get(`/api/v1/tags/${name}`).then(({ data }) => { + api().get(`/api/v1/tags/${name}`).then(({ data }) => { dispatch(fetchHashtagSuccess(name, data)); }).catch(err => { dispatch(fetchHashtagFail(err)); @@ -45,10 +45,10 @@ export const fetchHashtagFail = error => ({ error, }); -export const fetchFollowedHashtags = () => (dispatch, getState) => { +export const fetchFollowedHashtags = () => (dispatch) => { dispatch(fetchFollowedHashtagsRequest()); - api(getState).get('/api/v1/followed_tags').then(response => { + api().get('/api/v1/followed_tags').then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(fetchFollowedHashtagsSuccess(response.data, next ? next.uri : null)); }).catch(err => { @@ -87,7 +87,7 @@ export function expandFollowedHashtags() { dispatch(expandFollowedHashtagsRequest()); - api(getState).get(url).then(response => { + api().get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(expandFollowedHashtagsSuccess(response.data, next ? next.uri : null)); }).catch(error => { @@ -117,10 +117,10 @@ export function expandFollowedHashtagsFail(error) { }; } -export const followHashtag = name => (dispatch, getState) => { +export const followHashtag = name => (dispatch) => { dispatch(followHashtagRequest(name)); - api(getState).post(`/api/v1/tags/${name}/follow`).then(({ data }) => { + api().post(`/api/v1/tags/${name}/follow`).then(({ data }) => { dispatch(followHashtagSuccess(name, data)); }).catch(err => { dispatch(followHashtagFail(name, err)); @@ -144,10 +144,10 @@ export const followHashtagFail = (name, error) => ({ error, }); -export const unfollowHashtag = name => (dispatch, getState) => { +export const unfollowHashtag = name => (dispatch) => { dispatch(unfollowHashtagRequest(name)); - api(getState).post(`/api/v1/tags/${name}/unfollow`).then(({ data }) => { + api().post(`/api/v1/tags/${name}/unfollow`).then(({ data }) => { dispatch(unfollowHashtagSuccess(name, data)); }).catch(err => { dispatch(unfollowHashtagFail(name, err)); diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js index 4ce7c3cf84..f0ea46118e 100644 --- a/app/javascript/mastodon/actions/timelines.js +++ b/app/javascript/mastodon/actions/timelines.js @@ -6,9 +6,11 @@ import { usePendingItems as preferPendingItems } from 'mastodon/initial_state'; import { importFetchedStatus, importFetchedStatuses } from './importer'; import { submitMarkers } from './markers'; +import {timelineDelete} from './timelines_typed'; + +export { disconnectTimeline } from './timelines_typed'; export const TIMELINE_UPDATE = 'TIMELINE_UPDATE'; -export const TIMELINE_DELETE = 'TIMELINE_DELETE'; export const TIMELINE_CLEAR = 'TIMELINE_CLEAR'; export const TIMELINE_EXPAND_REQUEST = 'TIMELINE_EXPAND_REQUEST'; @@ -17,7 +19,6 @@ export const TIMELINE_EXPAND_FAIL = 'TIMELINE_EXPAND_FAIL'; export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP'; export const TIMELINE_LOAD_PENDING = 'TIMELINE_LOAD_PENDING'; -export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT'; export const TIMELINE_CONNECT = 'TIMELINE_CONNECT'; export const TIMELINE_MARK_AS_PARTIAL = 'TIMELINE_MARK_AS_PARTIAL'; @@ -62,16 +63,10 @@ export function updateTimeline(timeline, status, accept) { export function deleteFromTimelines(id) { return (dispatch, getState) => { const accountId = getState().getIn(['statuses', id, 'account']); - const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => status.get('id')); + const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => status.get('id')).valueSeq().toJSON(); const reblogOf = getState().getIn(['statuses', id, 'reblog'], null); - dispatch({ - type: TIMELINE_DELETE, - id, - accountId, - references, - reblogOf, - }); + dispatch(timelineDelete({ statusId: id, accountId, references, reblogOf })); }; } @@ -114,7 +109,7 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) { dispatch(expandTimelineRequest(timelineId, isLoadingMore)); - api(getState).get(path, { params }).then(response => { + api().get(path, { params }).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedStatuses(response.data)); @@ -163,6 +158,7 @@ export const expandAccountTimeline = (accountId, { maxId, withReplies, t export const expandAccountFeaturedTimeline = (accountId, { tagged } = {}) => expandTimeline(`account:${accountId}:pinned${tagged ? `:${tagged}` : ''}`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true, tagged }); export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true, limit: 40 }); export const expandListTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done); +export const expandLinkTimeline = (url, { maxId } = {}, done = noOp) => expandTimeline(`link:${url}`, `/api/v1/timelines/link`, { url, max_id: maxId }, done); export const expandHashtagTimeline = (hashtag, { maxId, tags, local } = {}, done = noOp) => { return expandTimeline(`hashtag:${hashtag}${local ? ':local' : ''}`, `/api/v1/timelines/tag/${hashtag}`, { max_id: maxId, @@ -225,12 +221,6 @@ export function connectTimeline(timeline) { }; } -export const disconnectTimeline = timeline => ({ - type: TIMELINE_DISCONNECT, - timeline, - usePendingItems: preferPendingItems, -}); - export const markAsPartial = timeline => ({ type: TIMELINE_MARK_AS_PARTIAL, timeline, diff --git a/app/javascript/mastodon/actions/timelines_typed.ts b/app/javascript/mastodon/actions/timelines_typed.ts new file mode 100644 index 0000000000..07d82b2f01 --- /dev/null +++ b/app/javascript/mastodon/actions/timelines_typed.ts @@ -0,0 +1,20 @@ +import { createAction } from '@reduxjs/toolkit'; + +import { usePendingItems as preferPendingItems } from 'mastodon/initial_state'; + +export const disconnectTimeline = createAction( + 'timeline/disconnect', + ({ timeline }: { timeline: string }) => ({ + payload: { + timeline, + usePendingItems: preferPendingItems, + }, + }), +); + +export const timelineDelete = createAction<{ + statusId: string; + accountId: string; + references: string[]; + reblogOf: string | null; +}>('timelines/delete'); diff --git a/app/javascript/mastodon/actions/trends.js b/app/javascript/mastodon/actions/trends.js index d314423884..0bdf17a5d2 100644 --- a/app/javascript/mastodon/actions/trends.js +++ b/app/javascript/mastodon/actions/trends.js @@ -1,6 +1,6 @@ import api, { getLinks } from '../api'; -import { importFetchedStatuses } from './importer'; +import { importFetchedStatuses, importFetchedAccounts } from './importer'; export const TRENDS_TAGS_FETCH_REQUEST = 'TRENDS_TAGS_FETCH_REQUEST'; export const TRENDS_TAGS_FETCH_SUCCESS = 'TRENDS_TAGS_FETCH_SUCCESS'; @@ -18,10 +18,10 @@ export const TRENDS_STATUSES_EXPAND_REQUEST = 'TRENDS_STATUSES_EXPAND_REQUEST'; export const TRENDS_STATUSES_EXPAND_SUCCESS = 'TRENDS_STATUSES_EXPAND_SUCCESS'; export const TRENDS_STATUSES_EXPAND_FAIL = 'TRENDS_STATUSES_EXPAND_FAIL'; -export const fetchTrendingHashtags = () => (dispatch, getState) => { +export const fetchTrendingHashtags = () => (dispatch) => { dispatch(fetchTrendingHashtagsRequest()); - api(getState) + api() .get('/api/v1/trends/tags') .then(({ data }) => dispatch(fetchTrendingHashtagsSuccess(data))) .catch(err => dispatch(fetchTrendingHashtagsFail(err))); @@ -45,12 +45,15 @@ export const fetchTrendingHashtagsFail = error => ({ skipAlert: true, }); -export const fetchTrendingLinks = () => (dispatch, getState) => { +export const fetchTrendingLinks = () => (dispatch) => { dispatch(fetchTrendingLinksRequest()); - api(getState) - .get('/api/v1/trends/links') - .then(({ data }) => dispatch(fetchTrendingLinksSuccess(data))) + api() + .get('/api/v1/trends/links', { params: { limit: 20 } }) + .then(({ data }) => { + dispatch(importFetchedAccounts(data.flatMap(link => link.authors.map(author => author.account)).filter(account => !!account))); + dispatch(fetchTrendingLinksSuccess(data)); + }) .catch(err => dispatch(fetchTrendingLinksFail(err))); }; @@ -79,7 +82,7 @@ export const fetchTrendingStatuses = () => (dispatch, getState) => { dispatch(fetchTrendingStatusesRequest()); - api(getState).get('/api/v1/trends/statuses').then(response => { + api().get('/api/v1/trends/statuses').then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedStatuses(response.data)); dispatch(fetchTrendingStatusesSuccess(response.data, next ? next.uri : null)); @@ -115,7 +118,7 @@ export const expandTrendingStatuses = () => (dispatch, getState) => { dispatch(expandTrendingStatusesRequest()); - api(getState).get(url).then(response => { + api().get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedStatuses(response.data)); dispatch(expandTrendingStatusesSuccess(response.data, next ? next.uri : null)); diff --git a/app/javascript/mastodon/api.ts b/app/javascript/mastodon/api.ts index de597a3e3b..24672290c7 100644 --- a/app/javascript/mastodon/api.ts +++ b/app/javascript/mastodon/api.ts @@ -1,9 +1,9 @@ -import type { AxiosResponse, RawAxiosRequestHeaders } from 'axios'; +import type { AxiosResponse, Method, RawAxiosRequestHeaders } from 'axios'; import axios from 'axios'; import LinkHeader from 'http-link-header'; +import { getAccessToken } from './initial_state'; import ready from './ready'; -import type { GetState } from './store'; export const getLinks = (response: AxiosResponse) => { const value = response.headers.link as string | undefined; @@ -29,30 +29,22 @@ const setCSRFHeader = () => { void ready(setCSRFHeader); -export const authorizationTokenFromState = (getState?: GetState) => { - return ( - getState && (getState().meta.get('access_token', '') as string | false) - ); -}; +const authorizationTokenFromInitialState = (): RawAxiosRequestHeaders => { + const accessToken = getAccessToken(); -const authorizationHeaderFromState = (getState?: GetState) => { - const accessToken = authorizationTokenFromState(getState); - - if (!accessToken) { - return {}; - } + if (!accessToken) return {}; return { Authorization: `Bearer ${accessToken}`, - } as RawAxiosRequestHeaders; + }; }; // eslint-disable-next-line import/no-default-export -export default function api(getState: GetState) { +export default function api(withAuthorization = true) { return axios.create({ headers: { ...csrfHeader, - ...authorizationHeaderFromState(getState), + ...(withAuthorization ? authorizationTokenFromInitialState() : {}), }, transformResponse: [ @@ -66,3 +58,50 @@ export default function api(getState: GetState) { ], }); } + +type RequestParamsOrData = Record; + +export async function apiRequest( + method: Method, + url: string, + args: { + params?: RequestParamsOrData; + data?: RequestParamsOrData; + } = {}, +) { + const { data } = await api().request({ + method, + url: '/api/' + url, + ...args, + }); + + return data; +} + +export async function apiRequestGet( + url: string, + params?: RequestParamsOrData, +) { + return apiRequest('GET', url, { params }); +} + +export async function apiRequestPost( + url: string, + data?: RequestParamsOrData, +) { + return apiRequest('POST', url, { data }); +} + +export async function apiRequestPut( + url: string, + data?: RequestParamsOrData, +) { + return apiRequest('PUT', url, { data }); +} + +export async function apiRequestDelete( + url: string, + params?: RequestParamsOrData, +) { + return apiRequest('DELETE', url, { params }); +} diff --git a/app/javascript/mastodon/api/accounts.ts b/app/javascript/mastodon/api/accounts.ts new file mode 100644 index 0000000000..bd1757e827 --- /dev/null +++ b/app/javascript/mastodon/api/accounts.ts @@ -0,0 +1,7 @@ +import { apiRequestPost } from 'mastodon/api'; +import type { ApiRelationshipJSON } from 'mastodon/api_types/relationships'; + +export const apiSubmitAccountNote = (id: string, value: string) => + apiRequestPost(`v1/accounts/${id}/note`, { + comment: value, + }); diff --git a/app/javascript/mastodon/api/directory.ts b/app/javascript/mastodon/api/directory.ts new file mode 100644 index 0000000000..cd39f8f269 --- /dev/null +++ b/app/javascript/mastodon/api/directory.ts @@ -0,0 +1,15 @@ +import { apiRequestGet } from 'mastodon/api'; +import type { ApiAccountJSON } from 'mastodon/api_types/accounts'; + +export const apiGetDirectory = ( + params: { + order: string; + local: boolean; + offset?: number; + }, + limit = 20, +) => + apiRequestGet('v1/directory', { + ...params, + limit, + }); diff --git a/app/javascript/mastodon/api/interactions.ts b/app/javascript/mastodon/api/interactions.ts new file mode 100644 index 0000000000..118b5f06d2 --- /dev/null +++ b/app/javascript/mastodon/api/interactions.ts @@ -0,0 +1,10 @@ +import { apiRequestPost } from 'mastodon/api'; +import type { Status, StatusVisibility } from 'mastodon/models/status'; + +export const apiReblog = (statusId: string, visibility: StatusVisibility) => + apiRequestPost<{ reblog: Status }>(`v1/statuses/${statusId}/reblog`, { + visibility, + }); + +export const apiUnreblog = (statusId: string) => + apiRequestPost(`v1/statuses/${statusId}/unreblog`); diff --git a/app/javascript/mastodon/api/notification_policies.ts b/app/javascript/mastodon/api/notification_policies.ts new file mode 100644 index 0000000000..4032134fb5 --- /dev/null +++ b/app/javascript/mastodon/api/notification_policies.ts @@ -0,0 +1,9 @@ +import { apiRequestGet, apiRequestPut } from 'mastodon/api'; +import type { NotificationPolicyJSON } from 'mastodon/api_types/notification_policies'; + +export const apiGetNotificationPolicy = () => + apiRequestGet('/v1/notifications/policy'); + +export const apiUpdateNotificationsPolicy = ( + policy: Partial, +) => apiRequestPut('/v1/notifications/policy', policy); diff --git a/app/javascript/mastodon/api_types/notification_policies.ts b/app/javascript/mastodon/api_types/notification_policies.ts new file mode 100644 index 0000000000..0f4a2d132e --- /dev/null +++ b/app/javascript/mastodon/api_types/notification_policies.ts @@ -0,0 +1,12 @@ +// See app/serializers/rest/notification_policy_serializer.rb + +export interface NotificationPolicyJSON { + filter_not_following: boolean; + filter_not_followers: boolean; + filter_new_accounts: boolean; + filter_private_mentions: boolean; + summary: { + pending_requests_count: number; + pending_notifications_count: number; + }; +} diff --git a/app/javascript/mastodon/api_types/statuses.ts b/app/javascript/mastodon/api_types/statuses.ts index c7dd33b5da..a934faeb7a 100644 --- a/app/javascript/mastodon/api_types/statuses.ts +++ b/app/javascript/mastodon/api_types/statuses.ts @@ -30,6 +30,12 @@ export interface ApiMentionJSON { acct: string; } +export interface ApiPreviewCardAuthorJSON { + name: string; + url: string; + account?: ApiAccountJSON; +} + export interface ApiPreviewCardJSON { url: string; title: string; @@ -38,6 +44,7 @@ export interface ApiPreviewCardJSON { type: string; author_name: string; author_url: string; + author_account?: ApiAccountJSON; provider_name: string; provider_url: string; html: string; @@ -48,6 +55,7 @@ export interface ApiPreviewCardJSON { embed_url: string; blurhash: string; published_at: string; + authors: ApiPreviewCardAuthorJSON[]; } export interface ApiStatusJSON { diff --git a/app/javascript/mastodon/components/account.jsx b/app/javascript/mastodon/components/account.jsx index 4a99dd0bbf..2a748e67ff 100644 --- a/app/javascript/mastodon/components/account.jsx +++ b/app/javascript/mastodon/components/account.jsx @@ -1,17 +1,19 @@ import PropTypes from 'prop-types'; +import { useCallback } from 'react'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import { Link } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; +import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react'; import { EmptyAccount } from 'mastodon/components/empty_account'; import { ShortNumber } from 'mastodon/components/short_number'; import { VerifiedBadge } from 'mastodon/components/verified_badge'; +import DropdownMenuContainer from '../containers/dropdown_menu_container'; import { me } from '../initial_state'; import { Avatar } from './avatar'; @@ -30,151 +32,150 @@ const messages = defineMessages({ unmute_notifications: { id: 'account.unmute_notifications_short', defaultMessage: 'Unmute notifications' }, mute: { id: 'account.mute_short', defaultMessage: 'Mute' }, block: { id: 'account.block_short', defaultMessage: 'Block' }, + more: { id: 'status.more', defaultMessage: 'More' }, }); -class Account extends ImmutablePureComponent { +const Account = ({ size = 46, account, onFollow, onBlock, onMute, onMuteNotifications, hidden, minimal, defaultAction, withBio }) => { + const intl = useIntl(); - static propTypes = { - size: PropTypes.number, - account: ImmutablePropTypes.record, - onFollow: PropTypes.func, - onBlock: PropTypes.func, - onMute: PropTypes.func, - onMuteNotifications: PropTypes.func, - intl: PropTypes.object.isRequired, - hidden: PropTypes.bool, - minimal: PropTypes.bool, - defaultAction: PropTypes.string, - withBio: PropTypes.bool, - }; + const handleFollow = useCallback(() => { + onFollow(account); + }, [onFollow, account]); - static defaultProps = { - size: 46, - }; + const handleBlock = useCallback(() => { + onBlock(account); + }, [onBlock, account]); - handleFollow = () => { - this.props.onFollow(this.props.account); - }; + const handleMute = useCallback(() => { + onMute(account); + }, [onMute, account]); - handleBlock = () => { - this.props.onBlock(this.props.account); - }; + const handleMuteNotifications = useCallback(() => { + onMuteNotifications(account, true); + }, [onMuteNotifications, account]); - handleMute = () => { - this.props.onMute(this.props.account); - }; + const handleUnmuteNotifications = useCallback(() => { + onMuteNotifications(account, false); + }, [onMuteNotifications, account]); - handleMuteNotifications = () => { - this.props.onMuteNotifications(this.props.account, true); - }; - - handleUnmuteNotifications = () => { - this.props.onMuteNotifications(this.props.account, false); - }; - - render () { - const { account, intl, hidden, withBio, defaultAction, size, minimal } = this.props; - - if (!account) { - return ; - } - - if (hidden) { - return ( - <> - {account.get('display_name')} - {account.get('username')} - - ); - } - - let buttons; - - if (account.get('id') !== me && account.get('relationship', null) !== null) { - const following = account.getIn(['relationship', 'following']); - const requested = account.getIn(['relationship', 'requested']); - const blocking = account.getIn(['relationship', 'blocking']); - const muting = account.getIn(['relationship', 'muting']); - - if (requested) { - buttons = - ); -}; - -BackButton.propTypes = { - onlyIcon: PropTypes.bool, -}; - -class ColumnHeader extends PureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - - static propTypes = { - intl: PropTypes.object.isRequired, - title: PropTypes.node, - icon: PropTypes.string, - iconComponent: PropTypes.func, - active: PropTypes.bool, - multiColumn: PropTypes.bool, - extraButton: PropTypes.node, - showBackButton: PropTypes.bool, - children: PropTypes.node, - pinned: PropTypes.bool, - placeholder: PropTypes.bool, - onPin: PropTypes.func, - onMove: PropTypes.func, - onClick: PropTypes.func, - appendContent: PropTypes.node, - collapseIssues: PropTypes.bool, - ...WithRouterPropTypes, - }; - - state = { - collapsed: true, - animating: false, - }; - - handleToggleClick = (e) => { - e.stopPropagation(); - this.setState({ collapsed: !this.state.collapsed, animating: true }); - }; - - handleTitleClick = () => { - this.props.onClick?.(); - }; - - handleMoveLeft = () => { - this.props.onMove(-1); - }; - - handleMoveRight = () => { - this.props.onMove(1); - }; - - handleTransitionEnd = () => { - this.setState({ animating: false }); - }; - - handlePin = () => { - if (!this.props.pinned) { - this.props.history.replace('/'); - } - - this.props.onPin(); - }; - - render () { - const { title, icon, iconComponent, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues, history } = this.props; - const { collapsed, animating } = this.state; - - const wrapperClassName = classNames('column-header__wrapper', { - 'active': active, - }); - - const buttonClassName = classNames('column-header', { - 'active': active, - }); - - const collapsibleClassName = classNames('column-header__collapsible', { - 'collapsed': collapsed, - 'animating': animating, - }); - - const collapsibleButtonClassName = classNames('column-header__button', { - 'active': !collapsed, - }); - - let extraContent, pinButton, moveButtons, backButton, collapseButton; - - if (children) { - extraContent = ( -
- {children} -
- ); - } - - if (multiColumn && pinned) { - pinButton = ; - - moveButtons = ( -
- - -
- ); - } else if (multiColumn && this.props.onPin) { - pinButton = ; - } - - if (history && !pinned && ((multiColumn && history.location?.state?.fromMastodon) || showBackButton)) { - backButton = ; - } - - const collapsedContent = [ - extraContent, - ]; - - if (multiColumn) { - collapsedContent.push( -
- {pinButton} - {moveButtons} -
- ); - } - - if (this.context.identity.signedIn && (children || (multiColumn && this.props.onPin))) { - collapseButton = ( - - ); - } - - const hasTitle = (icon || iconComponent) && title; - - const component = ( -
-

- {hasTitle && ( - <> - {backButton} - - - - )} - - {!hasTitle && backButton} - -
- {extraButton} - {collapseButton} -
-

- -
-
- {(!collapsed || animating) && collapsedContent} -
-
- - {appendContent} -
- ); - - if (placeholder) { - return component; - } else { - return ( - {component} - ); - } - } - -} - -export default injectIntl(withRouter(ColumnHeader)); diff --git a/app/javascript/mastodon/components/column_header.tsx b/app/javascript/mastodon/components/column_header.tsx new file mode 100644 index 0000000000..ec946cab3e --- /dev/null +++ b/app/javascript/mastodon/components/column_header.tsx @@ -0,0 +1,301 @@ +import { useCallback, useState } from 'react'; + +import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; + +import classNames from 'classnames'; + +import AddIcon from '@/material-icons/400-24px/add.svg?react'; +import ArrowBackIcon from '@/material-icons/400-24px/arrow_back.svg?react'; +import ChevronLeftIcon from '@/material-icons/400-24px/chevron_left.svg?react'; +import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; +import CloseIcon from '@/material-icons/400-24px/close.svg?react'; +import SettingsIcon from '@/material-icons/400-24px/settings.svg?react'; +import type { IconProp } from 'mastodon/components/icon'; +import { Icon } from 'mastodon/components/icon'; +import { ButtonInTabsBar } from 'mastodon/features/ui/util/columns_context'; +import { useIdentity } from 'mastodon/identity_context'; + +import { useAppHistory } from './router'; + +const messages = defineMessages({ + show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' }, + hide: { id: 'column_header.hide_settings', defaultMessage: 'Hide settings' }, + moveLeft: { + id: 'column_header.moveLeft_settings', + defaultMessage: 'Move column to the left', + }, + moveRight: { + id: 'column_header.moveRight_settings', + defaultMessage: 'Move column to the right', + }, + back: { id: 'column_back_button.label', defaultMessage: 'Back' }, +}); + +const BackButton: React.FC<{ + onlyIcon: boolean; +}> = ({ onlyIcon }) => { + const history = useAppHistory(); + const intl = useIntl(); + + const handleBackClick = useCallback(() => { + if (history.location.state?.fromMastodon) { + history.goBack(); + } else { + history.push('/'); + } + }, [history]); + + return ( + + ); +}; + +export interface Props { + title?: string; + icon?: string; + iconComponent?: IconProp; + active?: boolean; + children?: React.ReactNode; + pinned?: boolean; + multiColumn?: boolean; + extraButton?: React.ReactNode; + showBackButton?: boolean; + placeholder?: boolean; + appendContent?: React.ReactNode; + collapseIssues?: boolean; + onClick?: () => void; + onMove?: (arg0: number) => void; + onPin?: () => void; +} + +export const ColumnHeader: React.FC = ({ + title, + icon, + iconComponent, + active, + children, + pinned, + multiColumn, + extraButton, + showBackButton, + placeholder, + appendContent, + collapseIssues, + onClick, + onMove, + onPin, +}) => { + const intl = useIntl(); + const { signedIn } = useIdentity(); + const history = useAppHistory(); + const [collapsed, setCollapsed] = useState(true); + const [animating, setAnimating] = useState(false); + + const handleToggleClick = useCallback( + (e: React.MouseEvent) => { + e.stopPropagation(); + setCollapsed((value) => !value); + setAnimating(true); + }, + [setCollapsed, setAnimating], + ); + + const handleTitleClick = useCallback(() => { + onClick?.(); + }, [onClick]); + + const handleMoveLeft = useCallback(() => { + onMove?.(-1); + }, [onMove]); + + const handleMoveRight = useCallback(() => { + onMove?.(1); + }, [onMove]); + + const handleTransitionEnd = useCallback(() => { + setAnimating(false); + }, [setAnimating]); + + const handlePin = useCallback(() => { + if (!pinned) { + history.replace('/'); + } + + onPin?.(); + }, [history, pinned, onPin]); + + const wrapperClassName = classNames('column-header__wrapper', { + active, + }); + + const buttonClassName = classNames('column-header', { + active, + }); + + const collapsibleClassName = classNames('column-header__collapsible', { + collapsed, + animating, + }); + + const collapsibleButtonClassName = classNames('column-header__button', { + active: !collapsed, + }); + + let extraContent, pinButton, moveButtons, backButton, collapseButton; + + if (children) { + extraContent = ( +
+ {children} +
+ ); + } + + if (multiColumn && pinned) { + pinButton = ( + + ); + + moveButtons = ( +
+ + +
+ ); + } else if (multiColumn && onPin) { + pinButton = ( + + ); + } + + if ( + !pinned && + ((multiColumn && history.location.state?.fromMastodon) || showBackButton) + ) { + backButton = ; + } + + const collapsedContent = [extraContent]; + + if (multiColumn) { + collapsedContent.push( +
+ {pinButton} + {moveButtons} +
, + ); + } + + if (signedIn && (children || (multiColumn && onPin))) { + collapseButton = ( + + ); + } + + const hasIcon = icon && iconComponent; + const hasTitle = hasIcon && title; + + const component = ( +
+

+ {hasTitle && ( + <> + {backButton} + + + + )} + + {!hasTitle && backButton} + +
+ {extraButton} + {collapseButton} +
+

+ +
+
+ {(!collapsed || animating) && collapsedContent} +
+
+ + {appendContent} +
+ ); + + if (placeholder) { + return component; + } else { + return {component}; + } +}; + +// eslint-disable-next-line import/no-default-export +export default ColumnHeader; diff --git a/app/javascript/mastodon/components/follow_button.tsx b/app/javascript/mastodon/components/follow_button.tsx new file mode 100644 index 0000000000..ecc4e1ee17 --- /dev/null +++ b/app/javascript/mastodon/components/follow_button.tsx @@ -0,0 +1,128 @@ +import { useCallback, useEffect } from 'react'; + +import { useIntl, defineMessages, FormattedMessage } from 'react-intl'; + +import { useIdentity } from '@/mastodon/identity_context'; +import { + fetchRelationships, + followAccount, + unfollowAccount, +} from 'mastodon/actions/accounts'; +import { openModal } from 'mastodon/actions/modal'; +import { Button } from 'mastodon/components/button'; +import { LoadingIndicator } from 'mastodon/components/loading_indicator'; +import { me } from 'mastodon/initial_state'; +import { useAppDispatch, useAppSelector } from 'mastodon/store'; + +const messages = defineMessages({ + unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, + follow: { id: 'account.follow', defaultMessage: 'Follow' }, + followBack: { id: 'account.follow_back', defaultMessage: 'Follow back' }, + mutual: { id: 'account.mutual', defaultMessage: 'Mutual' }, + edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' }, +}); + +export const FollowButton: React.FC<{ + accountId?: string; +}> = ({ accountId }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + const { signedIn } = useIdentity(); + const account = useAppSelector((state) => + accountId ? state.accounts.get(accountId) : undefined, + ); + const relationship = useAppSelector((state) => + accountId ? state.relationships.get(accountId) : undefined, + ); + const following = relationship?.following || relationship?.requested; + + useEffect(() => { + if (accountId && signedIn) { + dispatch(fetchRelationships([accountId])); + } + }, [dispatch, accountId, signedIn]); + + const handleClick = useCallback(() => { + if (!signedIn) { + dispatch( + openModal({ + modalType: 'INTERACTION', + modalProps: { + type: 'follow', + accountId: accountId, + url: account?.url, + }, + }), + ); + } + + if (!relationship) return; + + if (accountId === me) { + return; + } else if (relationship.following || relationship.requested) { + dispatch( + openModal({ + modalType: 'CONFIRM', + modalProps: { + message: ( + @{account?.acct} }} + /> + ), + confirm: intl.formatMessage(messages.unfollow), + onConfirm: () => { + dispatch(unfollowAccount(accountId)); + }, + }, + }), + ); + } else { + dispatch(followAccount(accountId)); + } + }, [dispatch, intl, accountId, relationship, account, signedIn]); + + let label; + + if (!signedIn) { + label = intl.formatMessage(messages.follow); + } else if (accountId === me) { + label = intl.formatMessage(messages.edit_profile); + } else if (!relationship) { + label = ; + } else if (relationship.following && relationship.followed_by) { + label = intl.formatMessage(messages.mutual); + } else if (!relationship.following && relationship.followed_by) { + label = intl.formatMessage(messages.followBack); + } else if (relationship.following || relationship.requested) { + label = intl.formatMessage(messages.unfollow); + } else { + label = intl.formatMessage(messages.follow); + } + + if (accountId === me) { + return ( + + {label} + + ); + } + + return ( + + ); +}; diff --git a/app/javascript/mastodon/components/hashtag_bar.tsx b/app/javascript/mastodon/components/hashtag_bar.tsx index ed5de7d3a5..1642ba6504 100644 --- a/app/javascript/mastodon/components/hashtag_bar.tsx +++ b/app/javascript/mastodon/components/hashtag_bar.tsx @@ -52,7 +52,10 @@ function uniqueHashtagsWithCaseHandling(hashtags: string[]) { ); return Object.values(groups).map((tags) => { - if (tags.length === 1) return tags[0]; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- we know that the array has at least one element + const firstTag = tags[0]!; + + if (tags.length === 1) return firstTag; // The best match is the one where we have the less difference between upper and lower case letter count const best = minBy(tags, (tag) => { @@ -66,7 +69,7 @@ function uniqueHashtagsWithCaseHandling(hashtags: string[]) { return Math.abs(lowerCase - upperCase); }); - return best ?? tags[0]; + return best ?? firstTag; }); } diff --git a/app/javascript/mastodon/components/hover_card_account.tsx b/app/javascript/mastodon/components/hover_card_account.tsx new file mode 100644 index 0000000000..8933e14a98 --- /dev/null +++ b/app/javascript/mastodon/components/hover_card_account.tsx @@ -0,0 +1,74 @@ +import { useEffect, forwardRef } from 'react'; + +import classNames from 'classnames'; +import { Link } from 'react-router-dom'; + +import { fetchAccount } from 'mastodon/actions/accounts'; +import { AccountBio } from 'mastodon/components/account_bio'; +import { AccountFields } from 'mastodon/components/account_fields'; +import { Avatar } from 'mastodon/components/avatar'; +import { FollowersCounter } from 'mastodon/components/counters'; +import { DisplayName } from 'mastodon/components/display_name'; +import { FollowButton } from 'mastodon/components/follow_button'; +import { LoadingIndicator } from 'mastodon/components/loading_indicator'; +import { ShortNumber } from 'mastodon/components/short_number'; +import { domain } from 'mastodon/initial_state'; +import { useAppSelector, useAppDispatch } from 'mastodon/store'; + +export const HoverCardAccount = forwardRef< + HTMLDivElement, + { accountId?: string } +>(({ accountId }, ref) => { + const dispatch = useAppDispatch(); + + const account = useAppSelector((state) => + accountId ? state.accounts.get(accountId) : undefined, + ); + + useEffect(() => { + if (accountId && !account) { + dispatch(fetchAccount(accountId)); + } + }, [dispatch, accountId, account]); + + return ( + + ); +}); + +HoverCardAccount.displayName = 'HoverCardAccount'; diff --git a/app/javascript/mastodon/components/hover_card_controller.tsx b/app/javascript/mastodon/components/hover_card_controller.tsx new file mode 100644 index 0000000000..057ef1aaed --- /dev/null +++ b/app/javascript/mastodon/components/hover_card_controller.tsx @@ -0,0 +1,189 @@ +import { useEffect, useRef, useState, useCallback } from 'react'; + +import { useLocation } from 'react-router-dom'; + +import Overlay from 'react-overlays/Overlay'; +import type { + OffsetValue, + UsePopperOptions, +} from 'react-overlays/esm/usePopper'; + +import { useTimeout } from 'mastodon/../hooks/useTimeout'; +import { HoverCardAccount } from 'mastodon/components/hover_card_account'; + +const offset = [-12, 4] as OffsetValue; +const enterDelay = 750; +const leaveDelay = 150; +const popperConfig = { strategy: 'fixed' } as UsePopperOptions; + +const isHoverCardAnchor = (element: HTMLElement) => + element.matches('[data-hover-card-account]'); + +export const HoverCardController: React.FC = () => { + const [open, setOpen] = useState(false); + const [accountId, setAccountId] = useState(); + const [anchor, setAnchor] = useState(null); + const cardRef = useRef(null); + const [setLeaveTimeout, cancelLeaveTimeout] = useTimeout(); + const [setEnterTimeout, cancelEnterTimeout, delayEnterTimeout] = useTimeout(); + const [setScrollTimeout] = useTimeout(); + const location = useLocation(); + + const handleClose = useCallback(() => { + cancelEnterTimeout(); + cancelLeaveTimeout(); + setOpen(false); + setAnchor(null); + }, [cancelEnterTimeout, cancelLeaveTimeout, setOpen, setAnchor]); + + useEffect(() => { + handleClose(); + }, [handleClose, location]); + + useEffect(() => { + let isScrolling = false; + let currentAnchor: HTMLElement | null = null; + let currentTitle: string | null = null; + + const open = (target: HTMLElement) => { + target.setAttribute('aria-describedby', 'hover-card'); + setOpen(true); + setAnchor(target); + setAccountId(target.getAttribute('data-hover-card-account') ?? undefined); + }; + + const close = () => { + currentAnchor?.removeAttribute('aria-describedby'); + currentAnchor = null; + setOpen(false); + setAnchor(null); + setAccountId(undefined); + }; + + const handleMouseEnter = (e: MouseEvent) => { + const { target } = e; + + // We've exited the window + if (!(target instanceof HTMLElement)) { + close(); + return; + } + + // We've entered an anchor + if (!isScrolling && isHoverCardAnchor(target)) { + cancelLeaveTimeout(); + + currentAnchor?.removeAttribute('aria-describedby'); + currentAnchor = target; + + currentTitle = target.getAttribute('title'); + target.removeAttribute('title'); + + setEnterTimeout(() => { + open(target); + }, enterDelay); + } + + // We've entered the hover card + if ( + !isScrolling && + (target === currentAnchor || target === cardRef.current) + ) { + cancelLeaveTimeout(); + } + }; + + const handleMouseLeave = (e: MouseEvent) => { + const { target } = e; + + if (!currentAnchor) { + return; + } + + if ( + currentTitle && + target instanceof HTMLElement && + target === currentAnchor + ) + target.setAttribute('title', currentTitle); + + if (target === currentAnchor || target === cardRef.current) { + cancelEnterTimeout(); + + setLeaveTimeout(() => { + close(); + }, leaveDelay); + } + }; + + const handleScrollEnd = () => { + isScrolling = false; + }; + + const handleScroll = () => { + isScrolling = true; + cancelEnterTimeout(); + setScrollTimeout(handleScrollEnd, 100); + }; + + const handleMouseMove = () => { + delayEnterTimeout(enterDelay); + }; + + document.body.addEventListener('mouseenter', handleMouseEnter, { + passive: true, + capture: true, + }); + + document.body.addEventListener('mousemove', handleMouseMove, { + passive: true, + capture: false, + }); + + document.body.addEventListener('mouseleave', handleMouseLeave, { + passive: true, + capture: true, + }); + + document.addEventListener('scroll', handleScroll, { + passive: true, + capture: true, + }); + + return () => { + document.body.removeEventListener('mouseenter', handleMouseEnter); + document.body.removeEventListener('mousemove', handleMouseMove); + document.body.removeEventListener('mouseleave', handleMouseLeave); + document.removeEventListener('scroll', handleScroll); + }; + }, [ + setEnterTimeout, + setLeaveTimeout, + setScrollTimeout, + cancelEnterTimeout, + cancelLeaveTimeout, + delayEnterTimeout, + setOpen, + setAccountId, + setAnchor, + ]); + + return ( + + {({ props }) => ( +
+ +
+ )} +
+ ); +}; diff --git a/app/javascript/mastodon/components/media_gallery.jsx b/app/javascript/mastodon/components/media_gallery.jsx index 91459a1285..ed4805b05d 100644 --- a/app/javascript/mastodon/components/media_gallery.jsx +++ b/app/javascript/mastodon/components/media_gallery.jsx @@ -305,13 +305,13 @@ class MediaGallery extends PureComponent { style.aspectRatio = '3 / 2'; } - const size = media.take(4).size; + const size = media.size; const uncached = media.every(attachment => attachment.get('type') === 'unknown'); if (this.isFullSizeEligible()) { children = ; } else { - children = media.take(4).map((attachment, i) => ); + children = media.map((attachment, i) => ); } if (uncached) { diff --git a/app/javascript/mastodon/components/more_from_author.jsx b/app/javascript/mastodon/components/more_from_author.jsx new file mode 100644 index 0000000000..c20e76ac45 --- /dev/null +++ b/app/javascript/mastodon/components/more_from_author.jsx @@ -0,0 +1,19 @@ +import PropTypes from 'prop-types'; + +import { FormattedMessage } from 'react-intl'; + +import { AuthorLink } from 'mastodon/features/explore/components/author_link'; + +export const MoreFromAuthor = ({ accountId }) => ( +
+ + + + + }} /> +
+); + +MoreFromAuthor.propTypes = { + accountId: PropTypes.string.isRequired, +}; diff --git a/app/javascript/mastodon/components/poll.jsx b/app/javascript/mastodon/components/poll.jsx index c7036d111b..7b836f00b1 100644 --- a/app/javascript/mastodon/components/poll.jsx +++ b/app/javascript/mastodon/components/poll.jsx @@ -14,6 +14,7 @@ import CheckIcon from '@/material-icons/400-24px/check.svg?react'; import { Icon } from 'mastodon/components/icon'; import emojify from 'mastodon/features/emoji/emoji'; import Motion from 'mastodon/features/ui/util/optional_motion'; +import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; import { RelativeTimestamp } from './relative_timestamp'; @@ -38,12 +39,8 @@ const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => { }, {}); class Poll extends ImmutablePureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - static propTypes = { + identity: identityContextPropShape, poll: ImmutablePropTypes.map, lang: PropTypes.string, intl: PropTypes.object.isRequired, @@ -235,7 +232,7 @@ class Poll extends ImmutablePureComponent {
- {!showResults && } + {!showResults && } {!showResults && <> ยท } {showResults && !this.props.disabled && <> ยท } {votesCount} @@ -247,4 +244,4 @@ class Poll extends ImmutablePureComponent { } -export default injectIntl(Poll); +export default injectIntl(withIdentity(Poll)); diff --git a/app/javascript/mastodon/components/server_banner.jsx b/app/javascript/mastodon/components/server_banner.jsx index 63eec53492..baa220af5e 100644 --- a/app/javascript/mastodon/components/server_banner.jsx +++ b/app/javascript/mastodon/components/server_banner.jsx @@ -42,10 +42,12 @@ class ServerBanner extends PureComponent { return (
- {domain}, mastodon: Mastodon }} /> + {domain}, mastodon: Mastodon }} />
- + + +
{isLoading ? ( @@ -84,10 +86,6 @@ class ServerBanner extends PureComponent { )}
- -
- -
); } diff --git a/app/javascript/mastodon/components/short_number.tsx b/app/javascript/mastodon/components/short_number.tsx index 74c3c5d75e..a0b523aaad 100644 --- a/app/javascript/mastodon/components/short_number.tsx +++ b/app/javascript/mastodon/components/short_number.tsx @@ -48,7 +48,7 @@ const ShortNumberCounter: React.FC = ({ value }) => { const count = ( ); diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx index 8582d235bc..310b4fd7af 100644 --- a/app/javascript/mastodon/components/status.jsx +++ b/app/javascript/mastodon/components/status.jsx @@ -425,7 +425,7 @@ class Status extends ImmutablePureComponent { prepend = (
- }} /> + }} />
); @@ -446,7 +446,7 @@ class Status extends ImmutablePureComponent { prepend = (
- }} /> + }} />
); } @@ -562,7 +562,7 @@ class Status extends ImmutablePureComponent { {status.get('edited_at') && *} - +
{statusAvatar}
diff --git a/app/javascript/mastodon/components/status_action_bar.jsx b/app/javascript/mastodon/components/status_action_bar.jsx index 6def49fdbb..c79eae8460 100644 --- a/app/javascript/mastodon/components/status_action_bar.jsx +++ b/app/javascript/mastodon/components/status_action_bar.jsx @@ -22,6 +22,7 @@ import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react'; import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react'; import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react'; import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react'; +import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions'; import { WithRouterPropTypes } from 'mastodon/utils/react_router'; @@ -74,12 +75,8 @@ const mapStateToProps = (state, { status }) => ({ }); class StatusActionBar extends ImmutablePureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - static propTypes = { + identity: identityContextPropShape, status: ImmutablePropTypes.map.isRequired, relationship: ImmutablePropTypes.record, onReply: PropTypes.func, @@ -118,7 +115,7 @@ class StatusActionBar extends ImmutablePureComponent { ]; handleReplyClick = () => { - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; if (signedIn) { this.props.onReply(this.props.status, this.props.history); @@ -136,7 +133,7 @@ class StatusActionBar extends ImmutablePureComponent { }; handleFavouriteClick = () => { - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; if (signedIn) { this.props.onFavourite(this.props.status); @@ -146,7 +143,7 @@ class StatusActionBar extends ImmutablePureComponent { }; handleReblogClick = e => { - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; if (signedIn) { this.props.onReblog(this.props.status, e); @@ -250,7 +247,7 @@ class StatusActionBar extends ImmutablePureComponent { render () { const { status, relationship, intl, withDismiss, withCounters, scrollKey } = this.props; - const { signedIn, permissions } = this.context.identity; + const { signedIn, permissions } = this.props.identity; const publicStatus = ['public', 'unlisted'].includes(status.get('visibility')); const pinnableStatus = ['public', 'unlisted', 'private'].includes(status.get('visibility')); @@ -410,4 +407,4 @@ class StatusActionBar extends ImmutablePureComponent { } -export default withRouter(connect(mapStateToProps)(injectIntl(StatusActionBar))); +export default withRouter(withIdentity(connect(mapStateToProps)(injectIntl(StatusActionBar)))); diff --git a/app/javascript/mastodon/components/status_content.jsx b/app/javascript/mastodon/components/status_content.jsx index 4a7ba941eb..96452374dc 100644 --- a/app/javascript/mastodon/components/status_content.jsx +++ b/app/javascript/mastodon/components/status_content.jsx @@ -12,8 +12,10 @@ import { connect } from 'react-redux'; import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'; import { Icon } from 'mastodon/components/icon'; import PollContainer from 'mastodon/containers/poll_container'; +import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; import { autoPlayGif, languages as preloadedLanguages } from 'mastodon/initial_state'; + const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top) /** @@ -67,12 +69,8 @@ const mapStateToProps = state => ({ }); class StatusContent extends PureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - static propTypes = { + identity: identityContextPropShape, status: ImmutablePropTypes.map.isRequired, statusContent: PropTypes.string, expanded: PropTypes.bool, @@ -120,6 +118,7 @@ class StatusContent extends PureComponent { link.addEventListener('click', this.onMentionClick.bind(this, mention), false); link.setAttribute('title', `@${mention.get('acct')}`); link.setAttribute('href', `/@${mention.get('acct')}`); + link.setAttribute('data-hover-card-account', mention.get('id')); } else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) { link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false); link.setAttribute('href', `/tags/${link.text.replace(/^#/, '')}`); @@ -245,7 +244,7 @@ class StatusContent extends PureComponent { const renderReadMore = this.props.onClick && status.get('collapsed'); const contentLocale = intl.locale.replace(/[_-].*/, ''); const targetLanguages = this.props.languages?.get(status.get('language') || 'und'); - const renderTranslate = this.props.onTranslate && this.context.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('search_index').trim().length > 0 && targetLanguages?.includes(contentLocale); + const renderTranslate = this.props.onTranslate && this.props.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('search_index').trim().length > 0 && targetLanguages?.includes(contentLocale); const content = { __html: statusContent ?? getStatusContent(status) }; const spoilerContent = { __html: status.getIn(['translation', 'spoilerHtml']) || status.get('spoilerHtml') }; @@ -328,4 +327,4 @@ class StatusContent extends PureComponent { } -export default withRouter(connect(mapStateToProps)(injectIntl(StatusContent))); +export default withRouter(withIdentity(connect(mapStateToProps)(injectIntl(StatusContent)))); diff --git a/app/javascript/mastodon/components/status_list.jsx b/app/javascript/mastodon/components/status_list.jsx index 3ed20f65eb..fee6675faa 100644 --- a/app/javascript/mastodon/components/status_list.jsx +++ b/app/javascript/mastodon/components/status_list.jsx @@ -33,6 +33,7 @@ export default class StatusList extends ImmutablePureComponent { withCounters: PropTypes.bool, timelineId: PropTypes.string, lastId: PropTypes.string, + bindToDocument: PropTypes.bool, }; static defaultProps = { diff --git a/app/javascript/mastodon/containers/mastodon.jsx b/app/javascript/mastodon/containers/mastodon.jsx index 87708da191..0b1255c336 100644 --- a/app/javascript/mastodon/containers/mastodon.jsx +++ b/app/javascript/mastodon/containers/mastodon.jsx @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import { PureComponent } from 'react'; import { Helmet } from 'react-helmet'; @@ -14,6 +13,7 @@ import { connectUserStream } from 'mastodon/actions/streaming'; import ErrorBoundary from 'mastodon/components/error_boundary'; import { Router } from 'mastodon/components/router'; import UI from 'mastodon/features/ui'; +import { IdentityContext, createIdentityContext } from 'mastodon/identity_context'; import initialState, { title as siteTitle } from 'mastodon/initial_state'; import { IntlProvider } from 'mastodon/locales'; import { store } from 'mastodon/store'; @@ -28,33 +28,9 @@ if (initialState.meta.me) { store.dispatch(fetchCustomEmojis()); } -const createIdentityContext = state => ({ - signedIn: !!state.meta.me, - accountId: state.meta.me, - disabledAccountId: state.meta.disabled_account_id, - accessToken: state.meta.access_token, - permissions: state.role ? state.role.permissions : 0, -}); - export default class Mastodon extends PureComponent { - - static childContextTypes = { - identity: PropTypes.shape({ - signedIn: PropTypes.bool.isRequired, - accountId: PropTypes.string, - disabledAccountId: PropTypes.string, - accessToken: PropTypes.string, - }).isRequired, - }; - identity = createIdentityContext(initialState); - getChildContext() { - return { - identity: this.identity, - }; - } - componentDidMount() { if (this.identity.signedIn) { this.disconnect = store.dispatch(connectUserStream()); @@ -74,19 +50,21 @@ export default class Mastodon extends PureComponent { render () { return ( - - - - - - - - + + + + + + + + + - - - - + + + + + ); } diff --git a/app/javascript/mastodon/containers/status_container.jsx b/app/javascript/mastodon/containers/status_container.jsx index c6842e8df8..4a9b525777 100644 --- a/app/javascript/mastodon/containers/status_container.jsx +++ b/app/javascript/mastodon/containers/status_container.jsx @@ -96,9 +96,9 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({ onModalReblog (status, privacy) { if (status.get('reblogged')) { - dispatch(unreblog(status)); + dispatch(unreblog({ statusId: status.get('id') })); } else { - dispatch(reblog(status, privacy)); + dispatch(reblog({ statusId: status.get('id'), visibility: privacy })); } }, diff --git a/app/javascript/mastodon/features/account/components/header.jsx b/app/javascript/mastodon/features/account/components/header.jsx index e9d6071a21..1326874e50 100644 --- a/app/javascript/mastodon/features/account/components/header.jsx +++ b/app/javascript/mastodon/features/account/components/header.jsx @@ -25,6 +25,7 @@ import { IconButton } from 'mastodon/components/icon_button'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import { ShortNumber } from 'mastodon/components/short_number'; import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container'; +import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; import { autoPlayGif, me, domain as localDomain } from 'mastodon/initial_state'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions'; import { WithRouterPropTypes } from 'mastodon/utils/react_router'; @@ -93,7 +94,7 @@ const messageForFollowButton = relationship => { return messages.mutual; } else if (!relationship.get('following') && relationship.get('followed_by')) { return messages.followBack; - } else if (relationship.get('following')) { + } else if (relationship.get('following') || relationship.get('requested')) { return messages.unfollow; } else { return messages.follow; @@ -111,6 +112,7 @@ const dateFormatOptions = { class Header extends ImmutablePureComponent { static propTypes = { + identity: identityContextPropShape, account: ImmutablePropTypes.record, identity_props: ImmutablePropTypes.list, onFollow: PropTypes.func.isRequired, @@ -136,10 +138,6 @@ class Header extends ImmutablePureComponent { ...WithRouterPropTypes, }; - static contextTypes = { - identity: PropTypes.object, - }; - setRef = c => { this.node = c; }; @@ -255,7 +253,7 @@ class Header extends ImmutablePureComponent { render () { const { account, hidden, intl } = this.props; - const { signedIn, permissions } = this.context.identity; + const { signedIn, permissions } = this.props.identity; if (!account) { return null; @@ -293,10 +291,8 @@ class Header extends ImmutablePureComponent { if (me !== account.get('id')) { if (signedIn && !account.get('relationship')) { // Wait until the relationship is loaded actionBtn = ; - } else if (account.getIn(['relationship', 'requested'])) { - actionBtn =
); }; diff --git a/app/javascript/mastodon/features/home_timeline/index.jsx b/app/javascript/mastodon/features/home_timeline/index.jsx index 6e7dc2b6c8..00b5835a16 100644 --- a/app/javascript/mastodon/features/home_timeline/index.jsx +++ b/app/javascript/mastodon/features/home_timeline/index.jsx @@ -14,6 +14,7 @@ import { fetchAnnouncements, toggleShowAnnouncements } from 'mastodon/actions/an import { IconWithBadge } from 'mastodon/components/icon_with_badge'; import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator'; import AnnouncementsContainer from 'mastodon/features/getting_started/containers/announcements_container'; +import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; import { criticalUpdatesPending } from 'mastodon/initial_state'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; @@ -40,12 +41,8 @@ const mapStateToProps = state => ({ }); class HomeTimeline extends PureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - static propTypes = { + identity: identityContextPropShape, dispatch: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, hasUnread: PropTypes.bool, @@ -126,7 +123,7 @@ class HomeTimeline extends PureComponent { render () { const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props; const pinned = !!columnId; - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; const banners = []; let announcementsButton; @@ -190,4 +187,4 @@ class HomeTimeline extends PureComponent { } -export default connect(mapStateToProps)(injectIntl(HomeTimeline)); +export default connect(mapStateToProps)(withIdentity(injectIntl(HomeTimeline))); diff --git a/app/javascript/mastodon/features/link_timeline/index.tsx b/app/javascript/mastodon/features/link_timeline/index.tsx new file mode 100644 index 0000000000..dd726dac1a --- /dev/null +++ b/app/javascript/mastodon/features/link_timeline/index.tsx @@ -0,0 +1,76 @@ +import { useRef, useEffect, useCallback } from 'react'; + +import { Helmet } from 'react-helmet'; +import { useParams } from 'react-router-dom'; + +import ExploreIcon from '@/material-icons/400-24px/explore.svg?react'; +import { expandLinkTimeline } from 'mastodon/actions/timelines'; +import Column from 'mastodon/components/column'; +import { ColumnHeader } from 'mastodon/components/column_header'; +import StatusListContainer from 'mastodon/features/ui/containers/status_list_container'; +import type { Card } from 'mastodon/models/status'; +import { useAppDispatch, useAppSelector } from 'mastodon/store'; + +export const LinkTimeline: React.FC<{ + multiColumn: boolean; +}> = ({ multiColumn }) => { + const { url } = useParams<{ url: string }>(); + const decodedUrl = url ? decodeURIComponent(url) : undefined; + const dispatch = useAppDispatch(); + const columnRef = useRef(null); + const firstStatusId = useAppSelector((state) => + decodedUrl + ? // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access + (state.timelines.getIn([`link:${decodedUrl}`, 'items', 0]) as string) + : undefined, + ); + const story = useAppSelector((state) => + firstStatusId + ? (state.statuses.getIn([firstStatusId, 'card']) as Card) + : undefined, + ); + + const handleHeaderClick = useCallback(() => { + columnRef.current?.scrollTop(); + }, []); + + const handleLoadMore = useCallback( + (maxId: string) => { + dispatch(expandLinkTimeline(decodedUrl, { maxId })); + }, + [dispatch, decodedUrl], + ); + + useEffect(() => { + dispatch(expandLinkTimeline(decodedUrl)); + }, [dispatch, decodedUrl]); + + return ( + + + + + + + {story?.title} + + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export default LinkTimeline; diff --git a/app/javascript/mastodon/features/notifications/components/column_settings.jsx b/app/javascript/mastodon/features/notifications/components/column_settings.jsx index fc737c0fe2..39e394e449 100644 --- a/app/javascript/mastodon/features/notifications/components/column_settings.jsx +++ b/app/javascript/mastodon/features/notifications/components/column_settings.jsx @@ -5,6 +5,7 @@ import { FormattedMessage } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_REPORTS } from 'mastodon/permissions'; import { CheckboxWithLabel } from './checkbox_with_label'; @@ -12,13 +13,9 @@ import ClearColumnButton from './clear_column_button'; import GrantPermissionButton from './grant_permission_button'; import SettingToggle from './setting_toggle'; -export default class ColumnSettings extends PureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - +class ColumnSettings extends PureComponent { static propTypes = { + identity: identityContextPropShape, settings: ImmutablePropTypes.map.isRequired, pushSettings: ImmutablePropTypes.map.isRequired, onChange: PropTypes.func.isRequired, @@ -27,7 +24,7 @@ export default class ColumnSettings extends PureComponent { alertsEnabled: PropTypes.bool, browserSupport: PropTypes.bool, browserPermission: PropTypes.string, - notificationPolicy: ImmutablePropTypes.map, + notificationPolicy: PropTypes.object.isRequired, onChangePolicy: PropTypes.func.isRequired, }; @@ -85,22 +82,22 @@ export default class ColumnSettings extends PureComponent {

- + - + - + - + @@ -215,7 +212,7 @@ export default class ColumnSettings extends PureComponent {
- {((this.context.identity.permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) && ( + {((this.props.identity.permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) && (

@@ -228,7 +225,7 @@ export default class ColumnSettings extends PureComponent {
)} - {((this.context.identity.permissions & PERMISSION_MANAGE_REPORTS) === PERMISSION_MANAGE_REPORTS) && ( + {((this.props.identity.permissions & PERMISSION_MANAGE_REPORTS) === PERMISSION_MANAGE_REPORTS) && (

@@ -245,3 +242,5 @@ export default class ColumnSettings extends PureComponent { } } + +export default withIdentity(ColumnSettings); diff --git a/app/javascript/mastodon/features/notifications/components/filtered_notifications_banner.jsx b/app/javascript/mastodon/features/notifications/components/filtered_notifications_banner.jsx deleted file mode 100644 index 56da7ba626..0000000000 --- a/app/javascript/mastodon/features/notifications/components/filtered_notifications_banner.jsx +++ /dev/null @@ -1,49 +0,0 @@ -import { useEffect } from 'react'; - -import { FormattedMessage } from 'react-intl'; - -import { Link } from 'react-router-dom'; - -import { useDispatch, useSelector } from 'react-redux'; - -import InventoryIcon from '@/material-icons/400-24px/inventory_2.svg?react'; -import { fetchNotificationPolicy } from 'mastodon/actions/notifications'; -import { Icon } from 'mastodon/components/icon'; -import { toCappedNumber } from 'mastodon/utils/numbers'; - -export const FilteredNotificationsBanner = () => { - const dispatch = useDispatch(); - const policy = useSelector(state => state.get('notificationPolicy')); - - useEffect(() => { - dispatch(fetchNotificationPolicy()); - - const interval = setInterval(() => { - dispatch(fetchNotificationPolicy()); - }, 120000); - - return () => { - clearInterval(interval); - }; - }, [dispatch]); - - if (policy === null || policy.getIn(['summary', 'pending_notifications_count']) === 0) { - return null; - } - - return ( - - - -
- - -
- -
-
{toCappedNumber(policy.getIn(['summary', 'pending_notifications_count']))}
- -
- - ); -}; diff --git a/app/javascript/mastodon/features/notifications/components/filtered_notifications_banner.tsx b/app/javascript/mastodon/features/notifications/components/filtered_notifications_banner.tsx new file mode 100644 index 0000000000..2c4b3b9717 --- /dev/null +++ b/app/javascript/mastodon/features/notifications/components/filtered_notifications_banner.tsx @@ -0,0 +1,68 @@ +import { useEffect } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { Link } from 'react-router-dom'; + +import InventoryIcon from '@/material-icons/400-24px/inventory_2.svg?react'; +import { fetchNotificationPolicy } from 'mastodon/actions/notification_policies'; +import { Icon } from 'mastodon/components/icon'; +import { useAppSelector, useAppDispatch } from 'mastodon/store'; +import { toCappedNumber } from 'mastodon/utils/numbers'; + +export const FilteredNotificationsBanner: React.FC = () => { + const dispatch = useAppDispatch(); + const policy = useAppSelector((state) => state.notificationPolicy); + + useEffect(() => { + void dispatch(fetchNotificationPolicy()); + + const interval = setInterval(() => { + void dispatch(fetchNotificationPolicy()); + }, 120000); + + return () => { + clearInterval(interval); + }; + }, [dispatch]); + + if (policy === null || policy.summary.pending_notifications_count === 0) { + return null; + } + + return ( + + + +
+ + + + + + +
+ +
+
+ {toCappedNumber(policy.summary.pending_notifications_count)} +
+ +
+ + ); +}; diff --git a/app/javascript/mastodon/features/notifications/components/moderation_warning.tsx b/app/javascript/mastodon/features/notifications/components/moderation_warning.tsx new file mode 100644 index 0000000000..2c1683e218 --- /dev/null +++ b/app/javascript/mastodon/features/notifications/components/moderation_warning.tsx @@ -0,0 +1,78 @@ +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; + +import GavelIcon from '@/material-icons/400-24px/gavel.svg?react'; +import { Icon } from 'mastodon/components/icon'; + +// This needs to be kept in sync with app/models/account_warning.rb +const messages = defineMessages({ + none: { + id: 'notification.moderation_warning.action_none', + defaultMessage: 'Your account has received a moderation warning.', + }, + disable: { + id: 'notification.moderation_warning.action_disable', + defaultMessage: 'Your account has been disabled.', + }, + mark_statuses_as_sensitive: { + id: 'notification.moderation_warning.action_mark_statuses_as_sensitive', + defaultMessage: 'Some of your posts have been marked as sensitive.', + }, + delete_statuses: { + id: 'notification.moderation_warning.action_delete_statuses', + defaultMessage: 'Some of your posts have been removed.', + }, + sensitive: { + id: 'notification.moderation_warning.action_sensitive', + defaultMessage: 'Your posts will be marked as sensitive from now on.', + }, + silence: { + id: 'notification.moderation_warning.action_silence', + defaultMessage: 'Your account has been limited.', + }, + suspend: { + id: 'notification.moderation_warning.action_suspend', + defaultMessage: 'Your account has been suspended.', + }, +}); + +interface Props { + action: + | 'none' + | 'disable' + | 'mark_statuses_as_sensitive' + | 'delete_statuses' + | 'sensitive' + | 'silence' + | 'suspend'; + id: string; + hidden: boolean; +} + +export const ModerationWarning: React.FC = ({ action, id, hidden }) => { + const intl = useIntl(); + + if (hidden) { + return null; + } + + return ( + + + +
+

{intl.formatMessage(messages[action])}

+ + + +
+
+ ); +}; diff --git a/app/javascript/mastodon/features/notifications/components/notification.jsx b/app/javascript/mastodon/features/notifications/components/notification.jsx index c091554628..986628fdc8 100644 --- a/app/javascript/mastodon/features/notifications/components/notification.jsx +++ b/app/javascript/mastodon/features/notifications/components/notification.jsx @@ -26,6 +26,7 @@ import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import FollowRequestContainer from '../containers/follow_request_container'; +import { ModerationWarning } from './moderation_warning'; import { RelationshipsSeveranceEvent } from './relationships_severance_event'; import Report from './report'; @@ -40,6 +41,7 @@ const messages = defineMessages({ adminSignUp: { id: 'notification.admin.sign_up', defaultMessage: '{name} signed up' }, adminReport: { id: 'notification.admin.report', defaultMessage: '{name} reported {target}' }, relationshipsSevered: { id: 'notification.relationships_severance_event', defaultMessage: 'Lost connections with {name}' }, + moderationWarning: { id: 'notification.moderation_warning', defaultMessage: 'You have received a moderation warning' }, }); const notificationForScreenReader = (intl, message, timestamp) => { @@ -383,6 +385,27 @@ class Notification extends ImmutablePureComponent { ); } + renderModerationWarning (notification) { + const { intl, unread, hidden } = this.props; + const warning = notification.get('moderation_warning'); + + if (!warning) { + return null; + } + + return ( + +
+
+
+ ); + } + renderAdminSignUp (notification, account, link) { const { intl, unread } = this.props; @@ -412,7 +435,7 @@ class Notification extends ImmutablePureComponent { const targetAccount = report.get('target_account'); const targetDisplayNameHtml = { __html: targetAccount.get('display_name_html') }; - const targetLink = ; + const targetLink = ; return ( @@ -435,7 +458,7 @@ class Notification extends ImmutablePureComponent { const { notification } = this.props; const account = notification.get('account'); const displayNameHtml = { __html: account.get('display_name_html') }; - const link = ; + const link = ; switch(notification.get('type')) { case 'follow': @@ -456,6 +479,8 @@ class Notification extends ImmutablePureComponent { return this.renderPoll(notification, account); case 'severed_relationships': return this.renderRelationshipsSevered(notification); + case 'moderation_warning': + return this.renderModerationWarning(notification); case 'admin.sign_up': return this.renderAdminSignUp(notification, account, link); case 'admin.report': diff --git a/app/javascript/mastodon/features/notifications/containers/column_settings_container.js b/app/javascript/mastodon/features/notifications/containers/column_settings_container.js index de266160f8..94383d0bb5 100644 --- a/app/javascript/mastodon/features/notifications/containers/column_settings_container.js +++ b/app/javascript/mastodon/features/notifications/containers/column_settings_container.js @@ -4,7 +4,8 @@ import { connect } from 'react-redux'; import { showAlert } from '../../../actions/alerts'; import { openModal } from '../../../actions/modal'; -import { setFilter, clearNotifications, requestBrowserPermission, updateNotificationsPolicy } from '../../../actions/notifications'; +import { updateNotificationsPolicy } from '../../../actions/notification_policies'; +import { setFilter, clearNotifications, requestBrowserPermission } from '../../../actions/notifications'; import { changeAlerts as changePushNotifications } from '../../../actions/push_notifications'; import { changeSetting } from '../../../actions/settings'; import ColumnSettings from '../components/column_settings'; @@ -15,13 +16,16 @@ const messages = defineMessages({ permissionDenied: { id: 'notifications.permission_denied_alert', defaultMessage: 'Desktop notifications can\'t be enabled, as browser permission has been denied before' }, }); +/** + * @param {import('mastodon/store').RootState} state + */ const mapStateToProps = state => ({ settings: state.getIn(['settings', 'notifications']), pushSettings: state.get('push_notifications'), alertsEnabled: state.getIn(['settings', 'notifications', 'alerts']).includes(true), browserSupport: state.getIn(['notifications', 'browserSupport']), browserPermission: state.getIn(['notifications', 'browserPermission']), - notificationPolicy: state.get('notificationPolicy'), + notificationPolicy: state.notificationPolicy, }); const mapDispatchToProps = (dispatch, { intl }) => ({ diff --git a/app/javascript/mastodon/features/notifications/containers/notification_container.js b/app/javascript/mastodon/features/notifications/containers/notification_container.js index de450cd1ab..650acf4ccd 100644 --- a/app/javascript/mastodon/features/notifications/containers/notification_container.js +++ b/app/javascript/mastodon/features/notifications/containers/notification_container.js @@ -39,12 +39,12 @@ const mapDispatchToProps = dispatch => ({ }, onModalReblog (status, privacy) { - dispatch(reblog(status, privacy)); + dispatch(reblog({ statusId: status.get('id'), visibility: privacy })); }, onReblog (status, e) { if (status.get('reblogged')) { - dispatch(unreblog(status)); + dispatch(unreblog({ statusId: status.get('id') })); } else { if (e.shiftKey || !boostModal) { this.onModalReblog(status); diff --git a/app/javascript/mastodon/features/notifications/index.jsx b/app/javascript/mastodon/features/notifications/index.jsx index e062957ff8..d45f517152 100644 --- a/app/javascript/mastodon/features/notifications/index.jsx +++ b/app/javascript/mastodon/features/notifications/index.jsx @@ -17,6 +17,7 @@ import NotificationsIcon from '@/material-icons/400-24px/notifications-fill.svg? import { compareId } from 'mastodon/compare_id'; import { Icon } from 'mastodon/components/icon'; import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator'; +import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { submitMarkers } from '../../actions/markers'; @@ -77,12 +78,8 @@ const mapStateToProps = state => ({ }); class Notifications extends PureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - static propTypes = { + identity: identityContextPropShape, columnId: PropTypes.string, notifications: ImmutablePropTypes.list.isRequired, dispatch: PropTypes.func.isRequired, @@ -190,7 +187,7 @@ class Notifications extends PureComponent { const { intl, notifications, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, lastReadId, canMarkAsRead, needsNotificationPermission } = this.props; const pinned = !!columnId; const emptyMessage = ; - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; let scrollableContent = null; @@ -299,4 +296,4 @@ class Notifications extends PureComponent { } -export default connect(mapStateToProps)(injectIntl(Notifications)); +export default connect(mapStateToProps)(withIdentity(injectIntl(Notifications))); diff --git a/app/javascript/mastodon/features/picture_in_picture/components/footer.jsx b/app/javascript/mastodon/features/picture_in_picture/components/footer.jsx index 7a163a8825..ba0642da28 100644 --- a/app/javascript/mastodon/features/picture_in_picture/components/footer.jsx +++ b/app/javascript/mastodon/features/picture_in_picture/components/footer.jsx @@ -18,6 +18,7 @@ import { replyCompose } from 'mastodon/actions/compose'; import { reblog, favourite, unreblog, unfavourite } from 'mastodon/actions/interactions'; import { openModal } from 'mastodon/actions/modal'; import { IconButton } from 'mastodon/components/icon_button'; +import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; import { me, boostModal } from 'mastodon/initial_state'; import { makeGetStatus } from 'mastodon/selectors'; import { WithRouterPropTypes } from 'mastodon/utils/react_router'; @@ -47,12 +48,8 @@ const makeMapStateToProps = () => { }; class Footer extends ImmutablePureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - static propTypes = { + identity: identityContextPropShape, statusId: PropTypes.string.isRequired, status: ImmutablePropTypes.map.isRequired, intl: PropTypes.object.isRequired, @@ -75,7 +72,7 @@ class Footer extends ImmutablePureComponent { handleReplyClick = () => { const { dispatch, askReplyConfirmation, status, intl } = this.props; - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; if (signedIn) { if (askReplyConfirmation) { @@ -104,7 +101,7 @@ class Footer extends ImmutablePureComponent { handleFavouriteClick = () => { const { dispatch, status } = this.props; - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; if (signedIn) { if (status.get('favourited')) { @@ -126,16 +123,16 @@ class Footer extends ImmutablePureComponent { _performReblog = (status, privacy) => { const { dispatch } = this.props; - dispatch(reblog(status, privacy)); + dispatch(reblog({ statusId: status.get('id'), visibility: privacy })); }; handleReblogClick = e => { const { dispatch, status } = this.props; - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; if (signedIn) { if (status.get('reblogged')) { - dispatch(unreblog(status)); + dispatch(unreblog({ statusId: status.get('id') })); } else if ((e && e.shiftKey) || !boostModal) { this._performReblog(status); } else { @@ -209,4 +206,4 @@ class Footer extends ImmutablePureComponent { } -export default connect(makeMapStateToProps)(withRouter(injectIntl(Footer))); +export default connect(makeMapStateToProps)(withIdentity(withRouter(injectIntl(Footer)))); diff --git a/app/javascript/mastodon/features/public_timeline/index.jsx b/app/javascript/mastodon/features/public_timeline/index.jsx index 3601dfeae8..91351901f5 100644 --- a/app/javascript/mastodon/features/public_timeline/index.jsx +++ b/app/javascript/mastodon/features/public_timeline/index.jsx @@ -9,6 +9,7 @@ import { connect } from 'react-redux'; import PublicIcon from '@/material-icons/400-24px/public.svg?react'; import { DismissableBanner } from 'mastodon/components/dismissable_banner'; +import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; import { domain } from 'mastodon/initial_state'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; @@ -40,16 +41,12 @@ const mapStateToProps = (state, { columnId }) => { }; class PublicTimeline extends PureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - static defaultProps = { onlyMedia: false, }; static propTypes = { + identity: identityContextPropShape, dispatch: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, columnId: PropTypes.string, @@ -80,7 +77,7 @@ class PublicTimeline extends PureComponent { componentDidMount () { const { dispatch, onlyMedia, onlyRemote } = this.props; - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; dispatch(expandPublicTimeline({ onlyMedia, onlyRemote })); @@ -90,7 +87,7 @@ class PublicTimeline extends PureComponent { } componentDidUpdate (prevProps) { - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; if (prevProps.onlyMedia !== this.props.onlyMedia || prevProps.onlyRemote !== this.props.onlyRemote) { const { dispatch, onlyMedia, onlyRemote } = this.props; @@ -164,4 +161,4 @@ class PublicTimeline extends PureComponent { } -export default connect(mapStateToProps)(injectIntl(PublicTimeline)); +export default connect(mapStateToProps)(withIdentity(injectIntl(PublicTimeline))); diff --git a/app/javascript/mastodon/features/status/components/action_bar.jsx b/app/javascript/mastodon/features/status/components/action_bar.jsx index 69209e8bd0..d610539987 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.jsx +++ b/app/javascript/mastodon/features/status/components/action_bar.jsx @@ -21,6 +21,7 @@ import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react'; import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react'; import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react'; import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react'; +import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions'; import { WithRouterPropTypes } from 'mastodon/utils/react_router'; @@ -67,12 +68,8 @@ const mapStateToProps = (state, { status }) => ({ }); class ActionBar extends PureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - static propTypes = { + identity: identityContextPropShape, status: ImmutablePropTypes.map.isRequired, relationship: ImmutablePropTypes.record, onReply: PropTypes.func.isRequired, @@ -198,7 +195,7 @@ class ActionBar extends PureComponent { render () { const { status, relationship, intl } = this.props; - const { signedIn, permissions } = this.context.identity; + const { signedIn, permissions } = this.props.identity; const publicStatus = ['public', 'unlisted'].includes(status.get('visibility')); const pinnableStatus = ['public', 'unlisted', 'private'].includes(status.get('visibility')); @@ -326,4 +323,4 @@ class ActionBar extends PureComponent { } -export default withRouter(connect(mapStateToProps)(injectIntl(ActionBar))); +export default withRouter(connect(mapStateToProps)(withIdentity(injectIntl(ActionBar)))); diff --git a/app/javascript/mastodon/features/status/components/card.jsx b/app/javascript/mastodon/features/status/components/card.jsx index f47861f663..ee1fbe0f8f 100644 --- a/app/javascript/mastodon/features/status/components/card.jsx +++ b/app/javascript/mastodon/features/status/components/card.jsx @@ -7,6 +7,7 @@ import { FormattedMessage } from 'react-intl'; import classNames from 'classnames'; + import Immutable from 'immutable'; import ImmutablePropTypes from 'react-immutable-proptypes'; @@ -15,6 +16,7 @@ import OpenInNewIcon from '@/material-icons/400-24px/open_in_new.svg?react'; import PlayArrowIcon from '@/material-icons/400-24px/play_arrow-fill.svg?react'; import { Blurhash } from 'mastodon/components/blurhash'; import { Icon } from 'mastodon/components/icon'; +import { MoreFromAuthor } from 'mastodon/components/more_from_author'; import { RelativeTimestamp } from 'mastodon/components/relative_timestamp'; import { useBlurhash } from 'mastodon/initial_state'; @@ -136,9 +138,10 @@ export default class Card extends PureComponent { const interactive = card.get('type') === 'video'; const language = card.get('language') || ''; const largeImage = (card.get('image')?.length > 0 && card.get('width') > card.get('height')) || interactive; + const showAuthor = !!card.getIn(['authors', 0, 'accountId']); const description = ( -
+
{provider} {card.get('published_at') && <> ยท } @@ -146,7 +149,7 @@ export default class Card extends PureComponent { {card.get('title')} - {card.get('author_name').length > 0 ? {card.get('author_name')} }} /> : {card.get('description')}} + {!showAuthor && (card.get('author_name').length > 0 ? {card.get('author_name')} }} /> : {card.get('description')})}
); @@ -235,10 +238,14 @@ export default class Card extends PureComponent { } return ( - - {embed} - {description} - + <> + + {embed} + {description} + + + {showAuthor && } + ); } diff --git a/app/javascript/mastodon/features/status/components/detailed_status.jsx b/app/javascript/mastodon/features/status/components/detailed_status.jsx index 8843619bc9..bc81fd2dfb 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.jsx +++ b/app/javascript/mastodon/features/status/components/detailed_status.jsx @@ -272,7 +272,7 @@ class DetailedStatus extends ImmutablePureComponent {
)} - +
diff --git a/app/javascript/mastodon/features/status/containers/detailed_status_container.js b/app/javascript/mastodon/features/status/containers/detailed_status_container.js index 1c650f544f..c3d4fec4db 100644 --- a/app/javascript/mastodon/features/status/containers/detailed_status_container.js +++ b/app/javascript/mastodon/features/status/containers/detailed_status_container.js @@ -74,12 +74,12 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ }, onModalReblog (status, privacy) { - dispatch(reblog(status, privacy)); + dispatch(reblog({ statusId: status.get('id'), visibility: privacy })); }, onReblog (status, e) { if (status.get('reblogged')) { - dispatch(unreblog(status)); + dispatch(unreblog({ statusId: status.get('id') })); } else { if (e.shiftKey || !boostModal) { this.onModalReblog(status); diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx index 3914759725..7f37cb50d2 100644 --- a/app/javascript/mastodon/features/status/index.jsx +++ b/app/javascript/mastodon/features/status/index.jsx @@ -20,6 +20,7 @@ import { Icon } from 'mastodon/components/icon'; import { LoadingIndicator } from 'mastodon/components/loading_indicator'; import ScrollContainer from 'mastodon/containers/scroll_container'; import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error'; +import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { @@ -189,12 +190,8 @@ const titleFromStatus = (intl, status) => { }; class Status extends ImmutablePureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - static propTypes = { + identity: identityContextPropShape, params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, status: ImmutablePropTypes.map, @@ -244,7 +241,7 @@ class Status extends ImmutablePureComponent { handleFavouriteClick = (status) => { const { dispatch } = this.props; - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; if (signedIn) { if (status.get('favourited')) { @@ -274,7 +271,7 @@ class Status extends ImmutablePureComponent { handleReplyClick = (status) => { const { askReplyConfirmation, dispatch, intl } = this.props; - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; if (signedIn) { if (askReplyConfirmation) { @@ -302,16 +299,16 @@ class Status extends ImmutablePureComponent { }; handleModalReblog = (status, privacy) => { - this.props.dispatch(reblog(status, privacy)); + this.props.dispatch(reblog({ statusId: status.get('id'), visibility: privacy })); }; handleReblogClick = (status, e) => { const { dispatch } = this.props; - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; if (signedIn) { if (status.get('reblogged')) { - dispatch(unreblog(status)); + dispatch(unreblog({ statusId: status.get('id') })); } else { if ((e && e.shiftKey) || !boostModal) { this.handleModalReblog(status); @@ -745,4 +742,4 @@ class Status extends ImmutablePureComponent { } -export default withRouter(injectIntl(connect(makeMapStateToProps)(Status))); +export default withRouter(injectIntl(connect(makeMapStateToProps)(withIdentity(Status)))); diff --git a/app/javascript/mastodon/features/ui/components/column_loading.tsx b/app/javascript/mastodon/features/ui/components/column_loading.tsx index 42174838cf..d9563dda7a 100644 --- a/app/javascript/mastodon/features/ui/components/column_loading.tsx +++ b/app/javascript/mastodon/features/ui/components/column_loading.tsx @@ -1,11 +1,8 @@ -import Column from '../../../components/column'; -import ColumnHeader from '../../../components/column_header'; +import Column from 'mastodon/components/column'; +import { ColumnHeader } from 'mastodon/components/column_header'; +import type { Props as ColumnHeaderProps } from 'mastodon/components/column_header'; -interface Props { - multiColumn?: boolean; -} - -export const ColumnLoading: React.FC = (otherProps) => ( +export const ColumnLoading: React.FC = (otherProps) => (
diff --git a/app/javascript/mastodon/features/ui/components/compose_panel.jsx b/app/javascript/mastodon/features/ui/components/compose_panel.jsx index e6ac79bdd9..18321cbe63 100644 --- a/app/javascript/mastodon/features/ui/components/compose_panel.jsx +++ b/app/javascript/mastodon/features/ui/components/compose_panel.jsx @@ -7,16 +7,13 @@ import { changeComposing, mountCompose, unmountCompose } from 'mastodon/actions/ import ServerBanner from 'mastodon/components/server_banner'; import ComposeFormContainer from 'mastodon/features/compose/containers/compose_form_container'; import SearchContainer from 'mastodon/features/compose/containers/search_container'; +import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; import LinkFooter from './link_footer'; class ComposePanel extends PureComponent { - - static contextTypes = { - identity: PropTypes.object.isRequired, - }; - static propTypes = { + identity: identityContextPropShape, dispatch: PropTypes.func.isRequired, }; @@ -41,7 +38,7 @@ class ComposePanel extends PureComponent { } render() { - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; return (
@@ -65,4 +62,4 @@ class ComposePanel extends PureComponent { } -export default connect()(ComposePanel); +export default connect()(withIdentity(ComposePanel)); diff --git a/app/javascript/mastodon/features/ui/components/header.jsx b/app/javascript/mastodon/features/ui/components/header.jsx index 2f8636b12a..19c76c722b 100644 --- a/app/javascript/mastodon/features/ui/components/header.jsx +++ b/app/javascript/mastodon/features/ui/components/header.jsx @@ -13,6 +13,7 @@ import { fetchServer } from 'mastodon/actions/server'; import { Avatar } from 'mastodon/components/avatar'; import { Icon } from 'mastodon/components/icon'; import { WordmarkLogo, SymbolLogo } from 'mastodon/components/logo'; +import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; import { registrationsOpen, me, sso_redirect } from 'mastodon/initial_state'; const Account = connect(state => ({ @@ -41,12 +42,8 @@ const mapDispatchToProps = (dispatch) => ({ }); class Header extends PureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - static propTypes = { + identity: identityContextPropShape, openClosedRegistrationsModal: PropTypes.func, location: PropTypes.object, signupUrl: PropTypes.string.isRequired, @@ -60,7 +57,7 @@ class Header extends PureComponent { } render () { - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; const { location, openClosedRegistrationsModal, signupUrl, intl } = this.props; let content; @@ -121,4 +118,4 @@ class Header extends PureComponent { } -export default injectIntl(withRouter(connect(mapStateToProps, mapDispatchToProps)(Header))); +export default injectIntl(withRouter(withIdentity(connect(mapStateToProps, mapDispatchToProps)(Header)))); diff --git a/app/javascript/mastodon/features/ui/components/link_footer.jsx b/app/javascript/mastodon/features/ui/components/link_footer.jsx index 6b1555243b..08af6fa444 100644 --- a/app/javascript/mastodon/features/ui/components/link_footer.jsx +++ b/app/javascript/mastodon/features/ui/components/link_footer.jsx @@ -8,6 +8,7 @@ import { Link } from 'react-router-dom'; import { connect } from 'react-redux'; import { openModal } from 'mastodon/actions/modal'; +import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; import { domain, version, source_url, statusPageUrl, profile_directory as profileDirectory } from 'mastodon/initial_state'; import { PERMISSION_INVITE_USERS } from 'mastodon/permissions'; import { logOut } from 'mastodon/utils/log_out'; @@ -32,12 +33,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ }); class LinkFooter extends PureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - static propTypes = { + identity: identityContextPropShape, multiColumn: PropTypes.bool, onLogout: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, @@ -53,7 +50,7 @@ class LinkFooter extends PureComponent { }; render () { - const { signedIn, permissions } = this.context.identity; + const { signedIn, permissions } = this.props.identity; const { multiColumn } = this.props; const canInvite = signedIn && ((permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS); @@ -108,4 +105,4 @@ class LinkFooter extends PureComponent { } -export default injectIntl(connect(null, mapDispatchToProps)(LinkFooter)); +export default injectIntl(withIdentity(connect(null, mapDispatchToProps)(LinkFooter))); diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx index 14a1933436..ff90eef359 100644 --- a/app/javascript/mastodon/features/ui/components/navigation_panel.jsx +++ b/app/javascript/mastodon/features/ui/components/navigation_panel.jsx @@ -31,6 +31,7 @@ import { fetchFollowRequests } from 'mastodon/actions/accounts'; import { IconWithBadge } from 'mastodon/components/icon_with_badge'; import { WordmarkLogo } from 'mastodon/components/logo'; import { NavigationPortal } from 'mastodon/components/navigation_portal'; +import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; import { timelinePreview, trendsEnabled } from 'mastodon/initial_state'; import { transientSingleColumn } from 'mastodon/is_mobile'; @@ -97,12 +98,8 @@ const FollowRequestsLink = () => { }; class NavigationPanel extends Component { - - static contextTypes = { - identity: PropTypes.object.isRequired, - }; - static propTypes = { + identity: identityContextPropShape, intl: PropTypes.object.isRequired, }; @@ -112,7 +109,7 @@ class NavigationPanel extends Component { render () { const { intl } = this.props; - const { signedIn, disabledAccountId } = this.context.identity; + const { signedIn, disabledAccountId } = this.props.identity; let banner = undefined; @@ -189,4 +186,4 @@ class NavigationPanel extends Component { } -export default injectIntl(NavigationPanel); +export default injectIntl(withIdentity(NavigationPanel)); diff --git a/app/javascript/mastodon/features/ui/components/sign_in_banner.jsx b/app/javascript/mastodon/features/ui/components/sign_in_banner.jsx index 4216f3da38..74a8fdb841 100644 --- a/app/javascript/mastodon/features/ui/components/sign_in_banner.jsx +++ b/app/javascript/mastodon/features/ui/components/sign_in_banner.jsx @@ -22,7 +22,8 @@ const SignInBanner = () => { if (sso_redirect) { return (
-

+

+

); @@ -44,7 +45,8 @@ const SignInBanner = () => { return (
-

+

+

{signupButton}
diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx index c84a2c51a4..d9f609620c 100644 --- a/app/javascript/mastodon/features/ui/index.jsx +++ b/app/javascript/mastodon/features/ui/index.jsx @@ -14,7 +14,9 @@ import { HotKeys } from 'react-hotkeys'; import { focusApp, unfocusApp, changeLayout } from 'mastodon/actions/app'; import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodon/actions/markers'; import { INTRODUCTION_VERSION } from 'mastodon/actions/onboarding'; +import { HoverCardController } from 'mastodon/components/hover_card_controller'; import { PictureInPicture } from 'mastodon/features/picture_in_picture'; +import { identityContextPropShape, withIdentity } from 'mastodon/identity_context'; import { layoutFromWindow } from 'mastodon/is_mobile'; import { WithRouterPropTypes } from 'mastodon/utils/react_router'; @@ -23,7 +25,7 @@ import { clearHeight } from '../../actions/height_cache'; import { expandNotifications } from '../../actions/notifications'; import { fetchServer, fetchServerTranslationLanguages } from '../../actions/server'; import { expandHomeTimeline } from '../../actions/timelines'; -import initialState, { me, owner, singleUserMode, trendsEnabled, trendsAsLanding } from '../../initial_state'; +import initialState, { me, owner, singleUserMode, trendsEnabled, trendsAsLanding, disableHoverCards } from '../../initial_state'; import BundleColumnError from './components/bundle_column_error'; import Header from './components/header'; @@ -54,6 +56,7 @@ import { FavouritedStatuses, BookmarkedStatuses, FollowedTags, + LinkTimeline, ListTimeline, Blocks, DomainBlocks, @@ -120,12 +123,8 @@ const keyMap = { }; class SwitchingColumnsArea extends PureComponent { - - static contextTypes = { - identity: PropTypes.object, - }; - static propTypes = { + identity: identityContextPropShape, children: PropTypes.node, location: PropTypes.object, singleColumn: PropTypes.bool, @@ -160,7 +159,7 @@ class SwitchingColumnsArea extends PureComponent { render () { const { children, singleColumn } = this.props; - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; const pathName = this.props.location.pathname; let redirect; @@ -204,6 +203,7 @@ class SwitchingColumnsArea extends PureComponent { + @@ -252,12 +252,8 @@ class SwitchingColumnsArea extends PureComponent { } class UI extends PureComponent { - - static contextTypes = { - identity: PropTypes.object.isRequired, - }; - static propTypes = { + identity: identityContextPropShape, dispatch: PropTypes.func.isRequired, children: PropTypes.node, isComposing: PropTypes.bool, @@ -309,7 +305,7 @@ class UI extends PureComponent { this.dragTargets.push(e.target); } - if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files') && this.props.canUploadMore && this.context.identity.signedIn) { + if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files') && this.props.canUploadMore && this.props.identity.signedIn) { this.setState({ draggingOver: true }); } }; @@ -337,7 +333,7 @@ class UI extends PureComponent { this.setState({ draggingOver: false }); this.dragTargets = []; - if (e.dataTransfer && e.dataTransfer.files.length >= 1 && this.props.canUploadMore && this.context.identity.signedIn) { + if (e.dataTransfer && e.dataTransfer.files.length >= 1 && this.props.canUploadMore && this.props.identity.signedIn) { this.props.dispatch(uploadCompose(e.dataTransfer.files)); } }; @@ -389,7 +385,7 @@ class UI extends PureComponent { }; componentDidMount () { - const { signedIn } = this.context.identity; + const { signedIn } = this.props.identity; window.addEventListener('focus', this.handleWindowFocus, false); window.addEventListener('blur', this.handleWindowBlur, false); @@ -586,12 +582,13 @@ class UI extends PureComponent {
- + {children} {layout !== 'mobile' && } + {!disableHoverCards && } @@ -602,4 +599,4 @@ class UI extends PureComponent { } -export default connect(mapStateToProps)(injectIntl(withRouter(UI))); +export default connect(mapStateToProps)(injectIntl(withRouter(withIdentity(UI)))); diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index e1f5bfdaf6..b8a2359d92 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -201,3 +201,7 @@ export function NotificationRequests () { export function NotificationRequest () { return import(/*webpackChunkName: "features/notifications/request" */'../../notifications/request'); } + +export function LinkTimeline () { + return import(/*webpackChunkName: "features/link_timeline" */'../../link_timeline'); +} diff --git a/app/javascript/mastodon/identity_context.tsx b/app/javascript/mastodon/identity_context.tsx new file mode 100644 index 0000000000..7f28ab77a3 --- /dev/null +++ b/app/javascript/mastodon/identity_context.tsx @@ -0,0 +1,70 @@ +import PropTypes from 'prop-types'; +import { createContext, useContext } from 'react'; + +import hoistStatics from 'hoist-non-react-statics'; + +import type { InitialState } from 'mastodon/initial_state'; + +export interface IdentityContextType { + signedIn: boolean; + accountId: string | undefined; + disabledAccountId: string | undefined; + permissions: number; +} + +export const identityContextPropShape = PropTypes.shape({ + signedIn: PropTypes.bool.isRequired, + accountId: PropTypes.string, + disabledAccountId: PropTypes.string, +}).isRequired; + +export const createIdentityContext = (state: InitialState) => ({ + signedIn: !!state.meta.me, + accountId: state.meta.me, + disabledAccountId: state.meta.disabled_account_id, + permissions: state.role?.permissions ?? 0, +}); + +export const IdentityContext = createContext({ + signedIn: false, + permissions: 0, + accountId: undefined, + disabledAccountId: undefined, +}); + +export const useIdentity = () => useContext(IdentityContext); + +export interface IdentityProps { + ref?: unknown; + wrappedComponentRef?: unknown; +} + +/* Injects an `identity` props into the wrapped component to be able to use the new context in class components */ +export function withIdentity< + ComponentType extends React.ComponentType, +>(Component: ComponentType) { + const displayName = `withIdentity(${Component.displayName ?? Component.name})`; + const C = (props: React.ComponentProps) => { + const { wrappedComponentRef, ...remainingProps } = props; + + return ( + + {(context) => { + return ( + // @ts-expect-error - Dynamic covariant generic components are tough to type. + + ); + }} + + ); + }; + + C.displayName = displayName; + C.WrappedComponent = Component; + + return hoistStatics(C, Component); +} diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js index d8c57a2a0c..60b35cb31a 100644 --- a/app/javascript/mastodon/initial_state.js +++ b/app/javascript/mastodon/initial_state.js @@ -15,6 +15,7 @@ * @property {boolean=} boost_modal * @property {boolean=} delete_modal * @property {boolean=} disable_swiping + * @property {boolean=} disable_hover_cards * @property {string=} disabled_account_id * @property {string} display_media * @property {string} domain @@ -44,12 +45,22 @@ * @property {string} sso_redirect */ +/** + * @typedef Role + * @property {string} id + * @property {string} name + * @property {string} permissions + * @property {string} color + * @property {boolean} highlighted + */ + /** * @typedef InitialState * @property {Record} accounts * @property {InitialStateLanguage[]} languages * @property {boolean=} critical_updates_pending * @property {InitialStateMeta} meta + * @property {Role?} role */ const element = document.getElementById('initial-state'); @@ -76,6 +87,7 @@ export const autoPlayGif = getMeta('auto_play_gif'); export const boostModal = getMeta('boost_modal'); export const deleteModal = getMeta('delete_modal'); export const disableSwiping = getMeta('disable_swiping'); +export const disableHoverCards = getMeta('disable_hover_cards'); export const disabledAccountId = getMeta('disabled_account_id'); export const displayMedia = getMeta('display_media'); export const domain = getMeta('domain'); @@ -107,4 +119,11 @@ export const criticalUpdatesPending = initialState?.critical_updates_pending; export const statusPageUrl = getMeta('status_page_url'); export const sso_redirect = getMeta('sso_redirect'); +/** + * @returns {string | undefined} + */ +export function getAccessToken() { + return getMeta('access_token'); +} + export default initialState; diff --git a/app/javascript/mastodon/locales/af.json b/app/javascript/mastodon/locales/af.json index e4f7f12b0e..77e15eb2c6 100644 --- a/app/javascript/mastodon/locales/af.json +++ b/app/javascript/mastodon/locales/af.json @@ -50,7 +50,6 @@ "account.requested_follow": "{name} het versoek om jou te volg", "account.share": "Deel @{name} se profiel", "account.show_reblogs": "Wys aangestuurde plasings van @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Plaas} other {{counter} Plasings}}", "account.unblock": "Deblokkeer @{name}", "account.unblock_domain": "Deblokkeer domein {domain}", "account.unblock_short": "Deblokkeer", diff --git a/app/javascript/mastodon/locales/an.json b/app/javascript/mastodon/locales/an.json index 3f1fd376ff..752b6c3564 100644 --- a/app/javascript/mastodon/locales/an.json +++ b/app/javascript/mastodon/locales/an.json @@ -31,9 +31,7 @@ "account.follow": "Seguir", "account.followers": "Seguidores", "account.followers.empty": "Encara no sigue dengรบn a este usuario.", - "account.followers_counter": "{count, plural, one {{counter} Seguidor} other {{counter} Seguidores}}", "account.following": "Seguindo", - "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Seguindo}}", "account.follows.empty": "Este usuario encara no sigue a dengรบn.", "account.go_to_profile": "Ir ta lo perfil", "account.hide_reblogs": "Amagar retutz de @{name}", @@ -54,7 +52,6 @@ "account.requested_follow": "{name} ha demandau seguir-te", "account.share": "Compartir lo perfil de @{name}", "account.show_reblogs": "Amostrar retutz de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Publicaciรณn} other {{counter} Publicaciones}}", "account.unblock": "Desblocar a @{name}", "account.unblock_domain": "Amostrar a {domain}", "account.unblock_short": "Desblocar", @@ -476,8 +473,6 @@ "server_banner.about_active_users": "Usuarios activos en o servidor entre los zaguers 30 dรญas (Usuarios Activos Mensuals)", "server_banner.active_users": "usuarios activos", "server_banner.administered_by": "Administrau per:", - "server_banner.introduction": "{domain} ye parte d'o ret social descentralizau liderada per {mastodon}.", - "server_banner.learn_more": "Saber mas", "server_banner.server_stats": "Estatisticas d'o servidor:", "sign_in_banner.create_account": "Creyar cuenta", "sign_in_banner.sign_in": "Iniciar sesiรณn", diff --git a/app/javascript/mastodon/locales/ar.json b/app/javascript/mastodon/locales/ar.json index dd13f10aa3..115f6335b6 100644 --- a/app/javascript/mastodon/locales/ar.json +++ b/app/javascript/mastodon/locales/ar.json @@ -4,8 +4,8 @@ "about.disclaimer": "ู…ุงุณุชุฏูˆู† ุจุฑู†ุงู…ุฌ ุญุฑ ูˆู…ูุชูˆุญ ุงู„ู…ุตุฏุฑ ูˆุนู„ุงู…ุฉ ุชุฌุงุฑูŠุฉ ู„ู€ Mastodon GmbH.", "about.domain_blocks.no_reason_available": "ุงู„ุณุจุจ ุบูŠุฑ ู…ุชูˆูุฑ", "about.domain_blocks.preamble": "ูŠุณู…ุญ ู„ูƒ ู…ุงุณุชุฏูˆู† ุนู…ูˆู…ุงู‹ ุจุนุฑุถ ุงู„ู…ุญุชูˆู‰ ู…ู† ุงู„ู…ุณุชุฎุฏู…ูŠู† ู…ู† ุฃูŠ ุฎุงุฏู… ุขุฎุฑ ููŠ ุงู„ูุฏุฑุงู„ูŠุฉ ูˆุงู„ุชูุงุนู„ ู…ุนู‡ู…. ูˆู‡ุฐู‡ ู‡ูŠ ุงู„ุงุณุชุซู†ุงุกุงุช ุงู„ุชูŠ ูˆุถุนุช ุนู„ู‰ ู‡ุฐุง ุงู„ุฎุงุฏู… ุจุงู„ุฐุงุช.", - "about.domain_blocks.silenced.explanation": "ุนู…ูˆู…ุงู‹ุŒ ู„ู† ุชุฑู‰ ู…ู„ูุงุช ุงู„ุชุนุฑูŠู ูˆุงู„ู…ุญุชูˆู‰ ู…ู† ู‡ุฐุง ุงู„ุฎุงุฏู…ุŒ ุฅู„ุง ุฅุฐุง ูƒู†ุช ุชุจุญุซ ุนู†ู‡ ุจุดูƒู„ ุตุฑูŠุญ ุฃูˆ ุชุฎุชุงุฑ ุฃู† ุชุชุงุจุนู‡.", - "about.domain_blocks.silenced.title": "ุชู… ูƒุชู…ู‡", + "about.domain_blocks.silenced.explanation": "ู„ู† ุชุธู‡ุฑ ู„ูƒ ู…ู„ูุงุช ุงู„ุชุนุฑูŠู ุงู„ุดุฎุตูŠุฉ ูˆุงู„ู…ุญุชูˆู‰ ู…ู† ู‡ุฐุง ุงู„ุฎุงุฏูˆู…ุŒ ุฅู„ุง ุฅู† ุจุญุซุช ุนู†ู‡ ุนู…ุฏู‹ุง ุฃูˆ ุชุงุจุนุชู‡.", + "about.domain_blocks.silenced.title": "ู…ุญุฏูˆุฏ", "about.domain_blocks.suspended.explanation": "ู„ู† ูŠุชู… ู…ุนุงู„ุฌุฉ ุฃูŠ ุจูŠุงู†ุงุช ู…ู† ู‡ุฐุง ุงู„ุฎุงุฏู… ุฃูˆ ุชุฎุฒูŠู†ู‡ุง ุฃูˆ ุชุจุงุฏู„ู‡ุงุŒ ู…ู…ุง ูŠุฌุนู„ ุฃูŠ ุชูุงุนู„ ุฃูˆ ุงุชุตุงู„ ู…ุน ุงู„ู…ุณุชุฎุฏู…ูŠู† ู…ู† ู‡ุฐุง ุงู„ุฎุงุฏู… ู…ุณุชุญูŠู„ุง.", "about.domain_blocks.suspended.title": "ู…ูุนู„ู‘ู‚", "about.not_available": "ู„ู… ูŠุชู… ุชูˆููŠุฑ ู‡ุฐู‡ ุงู„ู…ุนู„ูˆู…ุงุช ุนู„ู‰ ู‡ุฐุง ุงู„ุฎุงุฏู….", @@ -32,12 +32,10 @@ "account.featured_tags.last_status_never": "ู„ุง ุชูˆุฌุฏ ุฑุณุงุฆู„", "account.featured_tags.title": "ูˆุณูˆู… {name} ุงู„ู…ู…ูŠู‘ูŽุฒุฉ", "account.follow": "ู…ุชุงุจุนุฉ", - "account.follow_back": "ุชุงุจุนู‡ ุจุฏูˆุฑูƒ", + "account.follow_back": "ุชุงุจุนู‡ู… ุจุงู„ู…ุซู„", "account.followers": "ู…ูุชุงุจูุนูˆู†", "account.followers.empty": "ู„ุง ุฃุญุฏูŽ ูŠูุชุงุจุน ู‡ุฐุง ุงู„ู…ูุณุชุฎุฏู… ุฅู„ู‰ ุญุฏ ุงู„ุขู†.", - "account.followers_counter": "{count, plural, zero{ู„ุง ู…ูุชุงุจุน} one {ู…ูุชุงุจุนูŒ ูˆุงุญูุฏ} two {ู…ูุชุงุจุนุงู†ู ุงูุซู†ุงู†} few {{counter} ู…ูุชุงุจูุนูŠู†} many {{counter} ู…ูุชุงุจูุนู‹ุง} other {{counter} ู…ูุชุงุจุน}}", "account.following": "ุงู„ุงุดุชุฑุงูƒุงุช", - "account.following_counter": "{count, plural, zero{ู„ุง ูŠูุชุงุจูุน ุฃุญุฏู‹ุง} one {ูŠูุชุงุจูุนู ูˆุงุญุฏ} two{ูŠูุชุงุจูุนู ุงูุซู†ุงู†} few{ูŠูุชุงุจูุนู {counter}} many{ูŠูุชุงุจูุนู {counter}} other {ูŠูุชุงุจูุนู {counter}}}", "account.follows.empty": "ู„ุง ูŠูุชุงุจุน ู‡ุฐุง ุงู„ู…ูุณุชุฎุฏู…ู ุฃูŠู‘ูŽ ุฃุญุฏู ุญุชู‰ ุงู„ุขู†.", "account.go_to_profile": "ุงุฐู‡ุจ ุฅู„ู‰ ุงู„ู…ู„ู ุงู„ุดุฎุตูŠ", "account.hide_reblogs": "ุฅุฎูุงุก ุงู„ู…ุนุงุฏ ู†ุดุฑู‡ุง ู…ูู† @{name}", @@ -53,7 +51,7 @@ "account.mute_notifications_short": "ูƒุชู… ุงู„ุฅุดุนุงุฑุงุช", "account.mute_short": "ุงูƒุชู…", "account.muted": "ู…ูŽูƒุชูˆู…", - "account.mutual": "ู…ุชุจุงุฏู„", + "account.mutual": "ู…ุชุจุงุฏู„ุฉ", "account.no_bio": "ู„ู… ูŠุชู… ุชู‚ุฏูŠู… ูˆุตู.", "account.open_original_page": "ุงูุชุญ ุงู„ุตูุญุฉ ุงู„ุฃุตู„ูŠุฉ", "account.posts": "ู…ู†ุดูˆุฑุงุช", @@ -63,7 +61,6 @@ "account.requested_follow": "ู„ู‚ุฏ ุทู„ุจ {name} ู…ุชุงุจุนุชูƒ", "account.share": "ุดุงุฑููƒ ุงู„ู…ู„ู ุงู„ุชุนุฑูŠููŠ ู„ู€ @{name}", "account.show_reblogs": "ุงุนุฑุถ ุฅุนุงุฏุงุช ู†ุดุฑ @{name}", - "account.statuses_counter": "{count, plural, zero {ู„ูŽุง ู…ู†ุดูˆุฑุงุช} one {ู…ู†ุดูˆุฑ ูˆุงุญุฏ} two {ู…ู†ุดูˆุฑุงู† ุฅุซู†ุงู†} few {{counter} ู…ู†ุดูˆุฑุงุช} many {{counter} ู…ู†ุดูˆุฑู‹ุง} other {{counter} ู…ู†ุดูˆุฑ}}", "account.unblock": "ุฅู„ุบุงุก ุงู„ุญูŽุธุฑ ุนู† @{name}", "account.unblock_domain": "ุฅู„ุบุงุก ุงู„ุญูŽุธุฑ ุนู† ุงู„ู†ู‘ูุทุงู‚ {domain}", "account.unblock_short": "ุฃู„ุบ ุงู„ุญุฌุจ", @@ -73,8 +70,8 @@ "account.unmute_notifications_short": "ุฅู„ุบุงุก ูƒูŽุชู… ุงู„ุฅุดุนุงุฑุงุช", "account.unmute_short": "ุฅู„ุบุงุก ุงู„ูƒุชู…", "account_note.placeholder": "ุงุถุบุท ู„ุฅุถุงูุฉ ู…ูู„ุงุญุธุฉ", - "admin.dashboard.daily_retention": "ู…ุนุฏู„ ุงู„ุงุญุชูุงุธ ุจุงู„ู…ุณุชุฎุฏู… ุจุนุฏ ุงู„ุชุณุฌูŠู„ ุจูŠูˆู…", - "admin.dashboard.monthly_retention": "ู…ุนุฏู„ ุงู„ุงุญุชูุงุธ ุจุงู„ู…ุณุชุฎุฏู… ุจุนุฏ ุงู„ุชุณุฌูŠู„ ุจุงู„ุดู‡ูˆุฑ", + "admin.dashboard.daily_retention": "ู…ุนุฏู‘ู„ ุจู‚ุงุก ุงู„ู…ุณุชุฎุฏู…ูŠู† ุจุนุฏ ุฅู†ุดุงุก ุงู„ุญุณุงุจุงุชุŒ ุจุงู„ุฃูŠุงู…", + "admin.dashboard.monthly_retention": "ู…ุนุฏู‘ู„ ุจู‚ุงุก ุงู„ู…ุณุชุฎุฏู…ูŠู† ุจุนุฏ ุฅู†ุดุงุก ุงู„ุญุณุงุจุงุชุŒ ุจุงู„ุดู‡ูˆุฑ", "admin.dashboard.retention.average": "ุงู„ู…ุนุฏู„", "admin.dashboard.retention.cohort": "ุดู‡ุฑ ุงู„ุชุณุฌูŠู„", "admin.dashboard.retention.cohort_size": "ุงู„ู…ุณุชุฎุฏู…ูˆู† ุงู„ุฌุฏุฏ", @@ -89,13 +86,13 @@ "announcement.announcement": "ุฅุนู„ุงู†", "attachments_list.unprocessed": "(ุบูŠุฑ ู…ุนุงู„ูŽุฌ)", "audio.hide": "ุฅุฎูุงุก ุงู„ู…ู‚ุทุน ุงู„ุตูˆุชูŠ", - "block_modal.remote_users_caveat": "Do tโ€™i kรซrkojmรซ shรซrbyesit {domain} tรซ respektojรซ vendimin tuaj. Por, pajtimi sโ€™รซshtรซ i garantuar, ngaqรซ disa shรซrbyes mund tโ€™i trajtojnรซ ndryshe bllokimet. Psotimet publike mundet tรซ jenรซ ende tรซ dukshme pรซr pรซrdorues pa bรซrรซ hyrje nรซ llogari.", - "block_modal.show_less": "ุงุนุฑุถ ุฃู‚ู„ู‘", - "block_modal.show_more": "ุฃุธู‡ุฑ ุงู„ู…ุฒูŠุฏ", + "block_modal.remote_users_caveat": "ุณูˆู ู†ุทู„ุจ ู…ู† ุงู„ุฎุงุฏู… {domain} ุฃู† ูŠุญุชุฑู… ู‚ุฑุงุฑูƒุŒ ู„ูƒู† ุงู„ุงู„ุชุฒุงู… ุบูŠุฑ ู…ุถู…ูˆู† ู„ุฃู† ุจุนุถ ุงู„ุฎูˆุงุฏูŠู… ู‚ุฏ ุชุชุนุงู…ู„ ู…ุน ู†ุตูˆุต ุงู„ูƒุชู„ ุจุดูƒู„ ู…ุฎุชู„ู. ู‚ุฏ ุชุธู„ ุงู„ู…ู†ุดูˆุฑุงุช ุงู„ุนุงู…ุฉ ู…ุฑุฆูŠุฉ ู„ู„ู…ุณุชุฎุฏู…ูŠู† ุบูŠุฑ ุงู„ู…ุณุฌู„ูŠู† ุงู„ุฏุฎูˆู„.", + "block_modal.show_less": "ุชูุงุตูŠู„ ุฃู‚ู„ู‘", + "block_modal.show_more": "ุชูุงุตูŠู„ ุฃูƒุซุฑ", "block_modal.they_cant_mention": "ู„ู† ูŠุณุชุทูŠุน ุฐููƒุฑูƒ ุฃูˆ ู…ุชุงุจุนุชูƒ.", - "block_modal.they_cant_see_posts": "ู„ู† ูŠุณุชุทูŠุน ุฑุคูŠุฉ ู…ู†ุดูˆุฑุงุชูƒ ูˆู„ู† ุชุฑู‰ ู…ู†ุดูˆุฑุงุชู‡.", - "block_modal.they_will_know": "ูŠู…ูƒู†ู‡ ุฃู† ูŠุฑู‰ ุฃู†ู‡ ู‚ุฏ ุชู… ุญุฌุจู‡.", - "block_modal.title": "ุฃุชุฑูŠุฏ ุญุธุฑ ุงู„ู…ุณุชุฎุฏู…ุŸ", + "block_modal.they_cant_see_posts": "ู„ู† ูŠุณุชุทูŠุน ู…ุทุงู„ุนุฉ ู…ู†ุดูˆุฑุงุชูƒ ูˆู„ู† ุชุทุงู„ุน ู…ู†ุดูˆุฑุงุชู‡.", + "block_modal.they_will_know": "ุณูŠุนู„ู… ุฃู†ู‡ ู‚ุฏ ุญูุธูุฑ.", + "block_modal.title": "ุฃุชุฑูŠุฏ ุญุธุฑ ู‡ุฐุง ุงู„ู…ุณุชุฎุฏู…ุŸ", "block_modal.you_wont_see_mentions": "ู„ู† ุชุฑ ุงู„ู…ู†ุดูˆุฑุงุช ุงู„ุชูŠ ูŠูุดุงุฑ ููŠู‡ู… ุฅู„ูŠู‡.", "boost_modal.combo": "ูŠูู…ูƒู†ูƒ ุงู„ุถู‘ุบุท ุนู„ู‰ {combo} ู„ุชุฎุทูŠ ู‡ุฐุง ููŠ ุงู„ู…ุฑุฉ ุงู„ู…ูู‚ุจู„ุฉ", "bundle_column_error.copy_stacktrace": "ุงู†ุณุฎ ุชู‚ุฑูŠุฑ ุงู„ุฎุทุฃ", @@ -159,7 +156,7 @@ "compose_form.poll.single": "ุงุฎุชุฑ ูˆุงุญุฏุง", "compose_form.poll.switch_to_multiple": "ุชุบูŠููŠุฑ ุงู„ุงุณุชุทู„ุงุน ู„ู„ุณู…ุงุญ ุจุงูุฎูŠุงุฑุงุช ู…ูุชุนุฏู‘ูุฏุฉ", "compose_form.poll.switch_to_single": "ุชุบูŠููŠุฑ ุงู„ุงุณุชุทู„ุงุน ู„ู„ุณู…ุงุญ ุจุงูุฎูŠุงุฑ ูˆุงุญุฏ ูู‚ุท", - "compose_form.poll.type": "ุงู„ุฃุณู„ูˆุจ", + "compose_form.poll.type": "ุงู„ุทุฑุงุฒ", "compose_form.publish": "ู†ุดุฑ", "compose_form.publish_form": "ู…ู†ุดูˆุฑ ุฌุฏูŠุฏ", "compose_form.reply": "ุฑุฏู‘", @@ -220,12 +217,16 @@ "domain_pill.activitypub_lets_connect": "ูŠุชูŠุญ ู„ูƒ ุงู„ุชูˆุงุตู„ ูˆุงู„ุชูุงุนู„ ู…ุน ุงู„ู†ุงุณ ู„ูŠุณ ูู‚ุท ุนู„ู‰ ู…ุงุณุชุฏูˆู†ุŒ ูˆู„ูƒู† ุนุจุฑ ุชุทุจูŠู‚ุงุช ุงุฌุชู…ุงุนูŠุฉ ู…ุฎุชู„ูุฉ ุฃูŠุถุง.", "domain_pill.activitypub_like_language": "ุฅู†ู‘ ActivityPub ู…ุซู„ ู„ุบุฉ ู…ุงุณุชุฏูˆู† ุงู„ุชูŠ ูŠุชุญุฏุซ ุจู‡ุง ู…ุน ุดุจูƒุงุช ุงุฌุชู…ุงุนูŠุฉ ุฃุฎุฑู‰.", "domain_pill.server": "ุงู„ุฎุงุฏูู…", - "domain_pill.their_handle": "ู…ูุนุฑู‘ูููู‡:", + "domain_pill.their_handle": "ู…ูุนุฑูู‡:", "domain_pill.their_server": "ุจูŠุชู‡ู… ุงู„ุฑู‚ู…ูŠุŒ ุญูŠุซ ุชูุณุชุถุงู ูƒุงูุฉ ู…ู†ุดูˆุฑุงุชู‡ู….", "domain_pill.their_username": "ู…ูุนุฑู‘ููู‡ู… ุงู„ูุฑูŠุฏ ุนู„ู‰ ุงู„ุฎุงุฏู…. ู…ู† ุงู„ู…ู…ูƒู† ุงู„ุนุซูˆุฑ ุนู„ู‰ ู…ุณุชุฎุฏู…ูŠู† ุจู†ูุณ ุงุณู… ุงู„ู…ุณุชุฎุฏู… ุนู„ู‰ ุฎูˆุงุฏู… ู…ุฎุชู„ูุฉ.", "domain_pill.username": "ุงุณู… ุงู„ู…ุณุชุฎุฏู…", "domain_pill.whats_in_a_handle": "ู…ุง ุงู„ู…ู‚ุตูˆุฏ ุจุงู„ู…ูุนุฑู‘ููุŸ", + "domain_pill.who_they_are": "ุจู…ุง ุฃู† ุงู„ู…ุนุงู„ุฌุงุช ุชู‚ูˆู„ ู…ู† ู‡ูˆ ุงู„ุดุฎุต ูˆู…ูƒุงู† ูˆุฌูˆุฏู‡ุŒ ูŠู…ูƒู†ูƒ ุงู„ุชูุงุนู„ ู…ุน ุงู„ู†ุงุณ ุนุจุฑ ุงู„ุดุจูƒุฉ ุงู„ุงุฌุชู…ุงุนูŠุฉ ู„ู€ .", + "domain_pill.who_you_are": "ู„ุฃู† ู…ุนุงู„ุฌุชูƒ ุชู‚ูˆู„ ู…ู† ุฃู†ุช ูˆู…ูƒุงู† ูˆุฌูˆุฏูƒุŒ ูŠู…ูƒู† ุงู„ู†ุงุณ ุงู„ุชูุงุนู„ ู…ุนูƒ ุนุจุฑ ุงู„ุดุจูƒุฉ ุงู„ุงุฌุชู…ุงุนูŠุฉ ู„ู€ .", "domain_pill.your_handle": "ุนู†ูˆุงู†ูƒ ุงู„ูƒุงู…ู„:", + "domain_pill.your_server": "ู…ู†ุฒู„ูƒ ุงู„ุฑู‚ู…ูŠุŒ ุญูŠุซ ุชุนูŠุด ุฌู…ูŠุน ู…ุดุงุฑูƒุงุชูƒ. ู„ุง ุชุญุจ ู‡ุฐุงุŸ ุฅู†ู‚ู„ ุงู„ุฎูˆุงุฏู… ููŠ ุฃูŠ ูˆู‚ุช ูˆุงุฎุถุฑ ู…ุชุงุจุนูŠู†ูƒ ุฃูŠุถู‹ุง.", + "domain_pill.your_username": "ู…ุนุฑููƒ ุงู„ูุฑูŠุฏ ุนู„ู‰ ู‡ุฐุง ุงู„ุฎุงุฏู…. ู…ู† ุงู„ู…ู…ูƒู† ุงู„ุนุซูˆุฑ ุนู„ู‰ ู…ุณุชุฎุฏู…ูŠู† ุจู†ูุณ ุฅุณู… ุงู„ู…ุณุชุฎุฏู… ุนู„ู‰ ุฎูˆุงุฏู… ู…ุฎุชู„ูุฉ.", "embed.instructions": "ูŠู…ูƒู†ูƒู… ุฅุฏู…ุงุฌ ู‡ุฐุง ุงู„ู…ู†ุดูˆุฑ ุนู„ู‰ ู…ูˆู‚ุนูƒู… ุงู„ุฅู„ูƒุชุฑูˆู†ูŠ ุนู† ุทุฑูŠู‚ ู†ุณุฎ ุงู„ุดูุฑุฉ ุฃุฏู†ุงู‡.", "embed.preview": "ุฅู„ูŠูƒ ู…ุง ุณูŠุจุฏูˆ ุนู„ูŠู‡:", "emoji_button.activity": "ุงู„ุฃู†ุดุทุฉ", @@ -262,6 +263,7 @@ "empty_column.list": "ู‡ุฐู‡ ุงู„ู‚ุงุฆู…ุฉ ูุงุฑุบุฉ ู…ุคู‚ุชุง ูˆ ู„ูƒู† ุณูˆู ุชู…ุชู„ุฆ ุชุฏุฑูŠุฌูŠุง ุนู†ุฏู…ุง ูŠุจุฏุฃ ุงู„ุฃุนุถุงุก ุงู„ู…ูู†ุชูŽู…ูŠู† ุฅู„ูŠู‡ุง ุจู†ุดุฑ ู…ู†ุดูˆุฑุงุช.", "empty_column.lists": "ู„ูŠุณ ุนู†ุฏูƒ ุฃูŠุฉ ู‚ุงุฆู…ุฉ ุจุนุฏ. ุณูˆู ุชุธู‡ุฑ ู‚ูˆุงุฆู…ูƒ ู‡ู†ุง ุฅู† ู‚ู…ุช ุจุฅู†ุดุงุก ูˆุงุญุฏุฉ.", "empty_column.mutes": "ู„ู… ุชู‚ู… ุจูƒุชู… ุฃูŠ ู…ุณุชุฎุฏู… ุจุนุฏ.", + "empty_column.notification_requests": "ู„ุง ูŠูˆุฌุฏ ุดูŠุก ู‡ู†ุง. ุนู†ุฏู…ุง ุชุชู„ู‚ู‰ ุฅุดุนุงุฑุงุช ุฌุฏูŠุฏุฉุŒ ุณูˆู ุชุธู‡ุฑ ู‡ู†ุง ูˆูู‚ู‹ุง ู„ุฅุนุฏุงุฏุงุชูƒ.", "empty_column.notifications": "ู„ู… ุชุชู„ู‚ ุฃูŠ ุฅุดุนุงุฑ ุจุนุฏู. ุชูุงุนู„ ู…ุน ุงู„ู…ุณุชุฎุฏู…ูŠู† ุงู„ุขุฎุฑูŠู† ู„ุฅู†ุดุงุก ู…ุญุงุฏุซุฉ.", "empty_column.public": "ู„ุง ูŠูˆุฌุฏ ุฃูŠ ุดูŠุก ู‡ู†ุง! ู‚ู… ุจู†ุดุฑ ุดูŠุก ู…ุง ู„ู„ุนุงู…ุฉุŒ ุฃูˆ ุงุชุจุน ุงู„ู…ุณุชุฎุฏู…ูŠู† ุงู„ุขุฎุฑูŠู† ุงู„ู…ุชูˆุงุฌุฏูŠู† ุนู„ู‰ ุงู„ุฎูˆุงุฏู… ุงู„ุฃุฎุฑู‰ ู„ู…ู„ุก ุฎูŠุท ุงู„ู…ุญุงุฏุซุงุช", "error.unexpected_crash.explanation": "ู†ุธุฑุง ู„ูˆุฌูˆุฏ ุฎุทุฃ ููŠ ุงู„ุชุนู„ูŠู…ุงุช ุงู„ุจุฑู…ุฌูŠุฉ ุฃูˆ ู…ุดูƒู„ุฉ ุชูˆุงูู‚ ู…ุน ุงู„ู…ุชุตูู‘ุญุŒ ุชุนุฐุฑ ุนุฑุถ ู‡ุฐู‡ ุงู„ุตูุญุฉ ุจุดูƒู„ ุตุญูŠุญ.", @@ -292,6 +294,8 @@ "filter_modal.select_filter.subtitle": "ุงุณุชุฎุฏู… ูุฆุฉ ู…ูˆุฌูˆุฏุฉ ุฃูˆ ู‚ู… ุจุฅู†ุดุงุก ูุฆุฉ ุฌุฏูŠุฏุฉ", "filter_modal.select_filter.title": "ุชุตููŠุฉ ู‡ุฐุง ุงู„ู…ู†ุดูˆุฑ", "filter_modal.title.status": "ุชุตููŠุฉ ู…ู†ุดูˆุฑ", + "filtered_notifications_banner.mentions": "{count, plural, one {ุฅุดุงุฑุฉ} two {ุฅุดุงุฑุชูŠู†} few {# ุฅุดุงุฑุงุช} other {# ุฅุดุงุฑุฉ}}", + "filtered_notifications_banner.pending_requests": "ุฅุดุนุงุฑุงุช ู…ู† {count, plural, zero {}=0 {ู„ุง ุฃุญุฏ} one {ุดุฎุต ูˆุงุญุฏ ู‚ุฏ ุชุนุฑูู‡} two {ุดุฎุตูŠู† ู‚ุฏ ุชุนุฑูู‡ู…ุง} few {# ุฃุดุฎุงุต ู‚ุฏ ุชุนุฑูู‡ู…} many {# ุดุฎุต ู‚ุฏ ุชุนุฑูู‡ู…} other {# ุดุฎุต ู‚ุฏ ุชุนุฑูู‡ู…}}", "filtered_notifications_banner.title": "ุงู„ุฅุดุนุงุฑุงุช ุงู„ู…ุตูุงุฉ", "firehose.all": "ุงู„ูƒู„", "firehose.local": "ู‡ุฐุง ุงู„ุฎุงุฏู…", @@ -301,6 +305,8 @@ "follow_requests.unlocked_explanation": "ุญุชู‰ ูˆุฅู† ูƒุงู† ุญุณุงุจูƒ ุบูŠุฑ ู…ู‚ูู„ุŒ ูŠุนุชู‚ุฏ ูุฑูŠู‚ {domain} ุฃู†ูƒ ู‚ุฏ ุชุฑุบุจ ููŠ ู…ุฑุงุฌุนุฉ ุทู„ุจุงุช ุงู„ู…ุชุงุจุนุฉ ู…ู† ู‡ุฐู‡ ุงู„ุญุณุงุจุงุช ูŠุฏูˆูŠุงู‹.", "follow_suggestions.curated_suggestion": "ุงุฎุชูŠุงุฑ ุงู„ู…ูˆุธููŠู†", "follow_suggestions.dismiss": "ู„ุง ุชูุธู‡ุฑู‡ุง ู…ุฌุฏู‘ุฏู‹ุง", + "follow_suggestions.featured_longer": "ู…ุฎุชุงุฑ ูŠุฏูˆูŠุงู‹ ู…ู† ู‚ูุจู„ ูุฑูŠู‚ {domain}", + "follow_suggestions.friends_of_friends_longer": "ู…ุดู‡ูˆุฑ ุจูŠู† ุงู„ุฃุดุฎุงุต ุงู„ุฐูŠู† ุชุชุงุจุนู‡ู…", "follow_suggestions.hints.featured": "ุชู… ุงุฎุชูŠุงุฑ ู‡ุฐุง ุงู„ู…ู„ู ุงู„ุดุฎุตูŠ ูŠุฏูˆูŠุงู‹ ู…ู† ู‚ุจู„ ูุฑูŠู‚ {domain}.", "follow_suggestions.hints.friends_of_friends": "ู‡ุฐุง ุงู„ู…ู„ู ุงู„ุดุฎุตูŠ ู…ุดู‡ูˆุฑ ุจูŠู† ุงู„ุฃุดุฎุงุต ุงู„ุฐูŠู† ุชุชุงุจุนู‡ู….", "follow_suggestions.hints.most_followed": "ู‡ุฐุง ุงู„ู…ู„ู ุงู„ุดุฎุตูŠ ู‡ูˆ ูˆุงุญุฏ ู…ู† ุงู„ุฃูƒุซุฑ ู…ุชุงุจุนุฉ ุนู„ู‰ {domain}.", @@ -308,6 +314,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "ู‡ุฐุง ุงู„ู…ู„ู ุงู„ุดุฎุตูŠ ู…ุดุงุจู‡ ู„ู„ู…ู„ูุงุช ุงู„ุดุฎุตูŠุฉ ุงู„ุชูŠ ุชุงุจุนุชู‡ุง ู…ุคุฎุฑุง.", "follow_suggestions.personalized_suggestion": "ุชูˆุตูŠุฉ ู…ุฎุตุตุฉ", "follow_suggestions.popular_suggestion": "ุชูˆุตูŠุฉ ุฑุงุฆุฌุฉ", + "follow_suggestions.popular_suggestion_longer": "ุฑุงุฆุฌ ุนู„ู‰ {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "ู…ุดุงุจู‡ุฉ ู„ู…ูˆุงุตูุงุช ุงู„ู…ู„ูุงุช ุงู„ุดุฎุตูŠุฉ ุงู„ุชูŠ ุชุงุจุนุชูŽู‡ุง ุญุฏูŠุซู‹ุง", "follow_suggestions.view_all": "ุนุฑุถ ุงู„ูƒู„", "follow_suggestions.who_to_follow": "ุญุณุงุจุงุช ู„ู„ู…ูุชุงุจูŽุนุฉ", "followed_tags": "ุงู„ูˆุณูˆู… ุงู„ู…ุชุงุจูŽุนุฉ", @@ -360,8 +368,8 @@ "interaction_modal.title.reply": "ุงู„ุฑุฏ ุนู„ู‰ ู…ู†ุดูˆุฑ {name}", "intervals.full.days": "{number, plural, one {# ูŠูˆู…} other {# ุฃูŠุงู…}}", "intervals.full.hours": "{number, plural, one {# ุณุงุนุฉ} other {# ุณุงุนุงุช}}", - "intervals.full.minutes": "{number, plural, one {# ุฏู‚ูŠู‚ุฉ} other {# ุฏู‚ุงุฆู‚}}", - "keyboard_shortcuts.back": "ู„ู„ุนูˆุฏุฉ", + "intervals.full.minutes": "{number, plural, one {ุฏู‚ูŠู‚ุฉ ูˆุงุญุฏุฉ}two {ุฏู‚ูŠู‚ุชุงู†} other {# ุฏู‚ุงุฆู‚}}", + "keyboard_shortcuts.back": "ู„ู„ุฑุฌูˆุน", "keyboard_shortcuts.blocked": "ู„ูุชุญ ู‚ุงุฆู…ุฉ ุงู„ู…ุณุชุฎุฏู…ูŠู† ุงู„ู…ุญุธูˆุฑูŠู†", "keyboard_shortcuts.boost": "ู„ุฅุนุงุฏุฉ ุงู„ู†ุดุฑ", "keyboard_shortcuts.column": "ู„ู„ุชุฑูƒูŠุฒ ุนู„ู‰ ู…ู†ุดูˆุฑ ุนู„ู‰ ุฃุญุฏ ุงู„ุฃุนู…ุฏุฉ", @@ -403,6 +411,7 @@ "limited_account_hint.action": "ุฅุธู‡ุงุฑ ุงู„ู…ู„ู ุงู„ุชุนุฑูŠููŠ ุนู„ู‰ ุฃูŠ ุญุงู„", "limited_account_hint.title": "ุชู… ุฅุฎูุงุก ู‡ุฐุง ุงู„ู…ู„ู ุงู„ุดุฎุตูŠ ู…ู† ู‚ุจู„ ู…ุดุฑููŠ {domain}.", "link_preview.author": "ู…ูู† {name}", + "link_preview.more_from_author": "ุงู„ู…ุฒูŠุฏ ู…ู† {name}", "lists.account.add": "ุฃุถู ุฅู„ู‰ ุงู„ู‚ุงุฆู…ุฉ", "lists.account.remove": "ุงุญุฐู ู…ู† ุงู„ู‚ุงุฆู…ุฉ", "lists.delete": "ุงุญุฐู ุงู„ู‚ุงุฆู…ุฉ", @@ -421,7 +430,9 @@ "loading_indicator.label": "ุฌุงุฑูŠ ุงู„ุชุญู…ูŠู„โ€ฆ", "media_gallery.toggle_visible": "{number, plural, zero {} one {ุงุฎู ุงู„ุตูˆุฑุฉ} two {ุงุฎู ุงู„ุตูˆุฑุชูŠู†} few {ุงุฎู ุงู„ุตูˆุฑ} many {ุงุฎู ุงู„ุตูˆุฑ} other {ุงุฎู ุงู„ุตูˆุฑ}}", "moved_to_account_banner.text": "ุญุณุงุจูƒ {disabledAccount} ู…ุนุทู„ ุญุงู„ูŠู‹ุง ู„ุฃู†ูƒ ุงู†ุชู‚ู„ุช ุฅู„ู‰ {movedToAccount}.", + "mute_modal.hide_from_notifications": "ุฅุฎูุงุก ู…ู† ู‚ุงุฆู…ุฉ ุงู„ุฅุดุนุงุฑุงุช", "mute_modal.hide_options": "ุฅุฎูุงุก ุงู„ุฎูŠุงุฑุงุช", + "mute_modal.indefinite": "ุฅู„ู‰ ุฃู† ุฃูุณุฎ ูƒุชู…ู‡ุง", "mute_modal.show_options": "ุฅุธู‡ุงุฑ ุงู„ุฎูŠุงุฑุงุช", "mute_modal.they_can_mention_and_follow": "ุณูŠูƒูˆู† ุจุฅู…ูƒุงู†ู‡ ุงู„ุฅุดุงุฑุฉ ุฅู„ูŠูƒ ูˆู…ุชุงุจุนุชูƒุŒ ู„ูƒู†ูƒ ู„ู† ุชุฑู‡.", "mute_modal.they_wont_know": "ู„ู† ูŠูŽุนุฑู ุฃู†ู‡ ู‚ุฏ ุชู… ูƒุชู…ู‡.", @@ -460,10 +471,23 @@ "notification.follow": "ูŠุชุงุจุนูƒ {name}", "notification.follow_request": "ู„ู‚ุฏ ุทู„ุจ {name} ู…ุชุงุจุนุชูƒ", "notification.mention": "{name} ุฐูƒุฑูƒ", + "notification.moderation-warning.learn_more": "ุงุนุฑู ุงู„ู…ุฒูŠุฏ", + "notification.moderation_warning": "ู„ู‚ุฏ ุชู„ู‚ูŠุช ุชุญุฐูŠุฑู‹ุง ุจุงู„ุฅุดุฑุงู", + "notification.moderation_warning.action_delete_statuses": "ุชู… ุฅุฒุงู„ุฉ ุจุนุถ ู…ุดุงุฑูƒุงุชูƒ.", + "notification.moderation_warning.action_disable": "ุชู… ุชุนุทูŠู„ ุญุณุงุจูƒ.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "ุจุนุถ ู…ู† ู…ู†ุดูˆุฑุงุชูƒ ุชู… ุชุตู†ูŠูู‡ุง ุนู„ู‰ ุฃู†ู‡ุง ุญุณุงุณุฉ.", + "notification.moderation_warning.action_none": "ู„ู‚ุฏ ุชู„ู‚ู‰ ุญุณุงุจูƒ ุชุญุฐูŠุฑุง ุจุงู„ุฅุดุฑุงู.", + "notification.moderation_warning.action_sensitive": "ุณูŠุชู… ูˆุถุน ุนู„ุงู…ุฉ ุนู„ู‰ ู…ู†ุดูˆุฑุงุชูƒ ุนู„ู‰ ุฃู†ู‡ุง ุญุณุงุณุฉ ู…ู† ุงู„ุขู† ูุตุงุนุฏุง.", + "notification.moderation_warning.action_silence": "ู„ู‚ุฏ ุชู… ุชู‚ูŠูŠุฏ ุญุณุงุจูƒ.", + "notification.moderation_warning.action_suspend": "ู„ู‚ุฏ ุชู… ุชุนู„ูŠู‚ ุญุณุงุจูƒ.", "notification.own_poll": "ุงู†ุชู‡ู‰ ุงุณุชุทู„ุงุนูƒ ู„ู„ุฑุฃูŠ", "notification.poll": "ู„ู‚ุฏ ุงู†ุชู‡ู‰ ุงุณุชุทู„ุงุน ุฑุฃูŠ ุดุงุฑูƒุชูŽ ููŠู‡", "notification.reblog": "ู‚ุงู… {name} ุจู…ุดุงุฑูƒุฉ ู…ู†ุดูˆุฑูƒ", + "notification.relationships_severance_event": "ูู‚ุฏุช ุงู„ุงุชุตุงู„ุงุช ู…ุน {name}", + "notification.relationships_severance_event.account_suspension": "ู‚ุงู… ู…ุดุฑู ู…ู† {from} ุจุชุนู„ูŠู‚ {target}ุŒ ู…ู…ุง ูŠุนู†ูŠ ุฃู†ูƒ ู„ู… ูŠุนุฏ ุจุฅู…ูƒุงู†ูƒ ุชู„ู‚ูŠ ุงู„ุชุญุฏูŠุซุงุช ู…ู†ู‡ู… ุฃูˆ ุงู„ุชูุงุนู„ ู…ุนู‡ู….", + "notification.relationships_severance_event.domain_block": "ู‚ุงู… ู…ุดุฑู ู…ู† {from} ุจุญุธุฑ {target}ุŒ ุจู…ุง ููŠ ุฐู„ูƒ {followersCount} ู…ู† ู…ุชุงุจุนูŠู†ูƒ ูˆ {followingCount, plural, one {# ุญุณุงุจ} other {# ุญุณุงุจุงุช}} ุชุชุงุจุนู‡ุง.", "notification.relationships_severance_event.learn_more": "ุงุนุฑู ุงู„ู…ุฒูŠุฏ", + "notification.relationships_severance_event.user_domain_block": "ู„ู‚ุฏ ู‚ู…ุช ุจุญุธุฑ {target}ุŒ ู…ู…ุง ุฃุฏู‰ ุฅู„ู‰ ุฅุฒุงู„ุฉ {followersCount} ู…ู† ู…ุชุงุจุนูŠู†ูƒ ูˆ {followingCount, plural, one {# ุญุณุงุจ} other {# ุญุณุงุจุงุช}} ุชุชุงุจุนู‡ุง.", "notification.status": "{name} ู†ุดุฑ ู„ู„ุชูˆ", "notification.update": "ุนุฏู‘ู„ูŽ {name} ู…ู†ุดูˆุฑู‹ุง", "notification_requests.accept": "ู…ูˆุงูู‚ุฉ", @@ -503,10 +527,15 @@ "notifications.permission_denied": "ุชู†ุจูŠู‡ุงุช ุณุทุญ ุงู„ู…ูƒุชุจ ุบูŠุฑ ู…ุชูˆูุฑุฉ ุจุณุจุจ ุฑูุถ ุฃุฐูˆู†ุงุช ุงู„ู…ุชุตูุญ ู…ุณุจู‚ุงู‹", "notifications.permission_denied_alert": "ู„ุง ูŠู…ูƒู† ุชูุนูŠู„ ุฅุดุนุงุฑุงุช ุณุทุญ ุงู„ู…ูƒุชุจุŒ ู„ุฃู† ุฅุฐู† ุงู„ู…ุชุตูุญ ู‚ุฏ ุชู… ุฑูุถู‡ ุณุงุจู‚ุงู‹", "notifications.permission_required": "ุฅุดุนุงุฑุงุช ุณุทุญ ุงู„ู…ูƒุชุจ ุบูŠุฑ ู…ุชูˆูุฑุฉ ู„ุฃู†ู‡ ู„ู… ูŠุชู… ู…ู†ุญ ุงู„ุฅุฐู† ุงู„ู…ุทู„ูˆุจ.", + "notifications.policy.filter_new_accounts.hint": "ุชู… ุฅู†ุดุงุคู‡ุง ู…ู†ุฐ {days, plural, zero {}one {ูŠูˆู… ูˆุงุญุฏ} two {ูŠูˆู…ุงู†} few {# ุฃูŠุงู…} many {# ุฃูŠุงู…} other {# ุฃูŠุงู…}}", "notifications.policy.filter_new_accounts_title": "ุญุณุงุจุงุช ุฌุฏูŠุฏุฉ", + "notifications.policy.filter_not_followers_hint": "ุจู…ุง ููŠ ุฐู„ูƒ ุงู„ุฃุดุฎุงุต ุงู„ุฐูŠู† ูŠุชุงุจุนูˆู†ูƒ ุฃู‚ู„ ู…ู† {days, plural, zero {}one {ูŠูˆู… ูˆุงุญุฏ} two {ูŠูˆู…ุงู†} few {# ุฃูŠุงู…} many {# ุฃูŠุงู…} other {# ุฃูŠุงู…}}", "notifications.policy.filter_not_followers_title": "ุฃุดุฎุงุต ู„ุง ูŠุชุงุจุนูˆู†ูƒ", "notifications.policy.filter_not_following_hint": "ุญุชู‰ ุชูˆุงูู‚ ุนู„ูŠู‡ู… ูŠุฏูˆูŠุง", "notifications.policy.filter_not_following_title": "ุฃุดุฎุงุต ู„ุง ุชุชุงุจุนู‡ู…", + "notifications.policy.filter_private_mentions_hint": "ุชู…ุช ุชุตููŠุชู‡ ุฅู„ุง ุฅุฐุง ุฃู† ูŠูƒูˆู† ุฑุฏู‹ุง ุนู„ู‰ ุฐูƒุฑูƒ ุฃูˆ ุฅุฐุง ูƒู†ุช ุชุชุงุจุน ุงู„ุญุณุงุจ", + "notifications.policy.filter_private_mentions_title": "ุฅุดุงุฑุงุช ุฎุงุตุฉ ุบูŠุฑ ู…ุฑุบูˆุจ ููŠู‡ุง", + "notifications.policy.title": "ุชุตููŠุฉ ุงู„ุฅุดุนุงุฑุงุช ู…ู†โ€ฆ", "notifications_permission_banner.enable": "ุชูุนูŠู„ ุฅุดุนุงุฑุงุช ุณุทุญ ุงู„ู…ูƒุชุจ", "notifications_permission_banner.how_to_control": "ู„ุชู„ู‚ูŠ ุงู„ุฅุดุนุงุฑุงุช ุนู†ุฏู…ุง ู„ุง ูŠูƒูˆู† ู…ุงุณุชุฏูˆู† ู…ูุชูˆุญุŒ ู‚ู… ุจุชูุนูŠู„ ุฅุดุนุงุฑุงุช ุณุทุญ ุงู„ู…ูƒุชุจุŒ ูŠู…ูƒู†ูƒ ุงู„ุชุญูƒู… ุจุฏู‚ุฉ ููŠ ุฃู†ูˆุงุน ุงู„ุชูุงุนู„ุงุช ุงู„ุชูŠ ุชูˆู„ุฏ ุฅุดุนุงุฑุงุช ุณุทุญ ุงู„ู…ูƒุชุจ ู…ู† ุฎู„ุงู„ ุฒุฑ ุงู„ู€{icon} ุฃุนู„ุงู‡ ุจู…ุฌุฑุฏ ุชูุนูŠู„ู‡ุง.", "notifications_permission_banner.title": "ู„ุง ุชููˆุช ุดูŠุฆุงู‹ ุฃุจุฏุงู‹", @@ -663,13 +692,10 @@ "server_banner.about_active_users": "ุงู„ุฃุดุฎุงุต ุงู„ุฐูŠู† ูŠุณุชุฎุฏู…ูˆู† ู‡ุฐุง ุงู„ุฎุงุฏู… ุฎู„ุงู„ ุงู„ุฃูŠุงู… ุงู„ุซู„ุงุซูŠู† ุงู„ุฃุฎูŠุฑุฉ (ุงู„ู…ุณุชุฎุฏู…ูˆู† ุงู„ู†ุดุทูˆู† ุดู‡ุฑูŠู‹ุง)", "server_banner.active_users": "ู…ุณุชุฎุฏู… ู†ุดุท", "server_banner.administered_by": "ูŠูุฏูŠุฑู‡:", - "server_banner.introduction": "{domain} ู‡ูˆ ุฌุฒุก ู…ู† ุงู„ุดุจูƒุฉ ุงู„ุงุฌุชู…ุงุนูŠุฉ ุงู„ู„ุงู…ุฑูƒุฒูŠุฉ ุงู„ุชูŠ ุชุนู…ู„ ุจูˆุงุณุทุฉ {mastodon}.", - "server_banner.learn_more": "ุชุนู„ู… ุงู„ู…ุฒูŠุฏ", "server_banner.server_stats": "ุฅุญุตุงุฆูŠุงุช ุงู„ุฎุงุฏู…:", "sign_in_banner.create_account": "ุฃู†ุดุฆ ุญุณุงุจู‹ุง", "sign_in_banner.sign_in": "ุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„", "sign_in_banner.sso_redirect": "ุชุณุฌูŠู„ ุงู„ุฏุฎูˆู„ ุฃูˆ ุฅู†ุดุงุก ุญุณุงุจ", - "sign_in_banner.text": "ู‚ู… ุจุงู„ูˆู„ูˆุฌ ุจุญุณุงุจูƒ ู„ู…ุชุงุจุนุฉ ุงู„ุตูุญุงุช ุงู„ุดุฎุตูŠุฉ ุฃูˆ ุงู„ูˆุณูˆู…ุŒ ุฃูˆ ู„ุฅุถุงูุฉ ุงู„ู…ู†ุดูˆุฑุงุช ุฅู„ู‰ ุงู„ู…ูุถู„ุฉ ูˆู…ุดุงุฑูƒุชู‡ุง ูˆุงู„ุฑุฏ ุนู„ูŠู‡ุง ุฃูˆ ุงู„ุชูุงุนู„ ุจูˆุงุณุทุฉ ุญุณุงุจูƒ ุงู„ู…ุชูˆุงุฌุฏ ุนู„ู‰ ุฎุงุฏู… ู…ุฎุชู„ู.", "status.admin_account": "ุงูุชุญ ุงู„ูˆุงุฌู‡ุฉ ุงู„ุฅุฏุงุฑูŠุฉ ู„ู€ @{name}", "status.admin_domain": "ูุชุญ ูˆุงุฌู‡ุฉ ุงู„ุฅุดุฑุงู ู„ู€ {domain}", "status.admin_status": "ุงูุชุญ ู‡ุฐุง ุงู„ู…ู†ุดูˆุฑ ุนู„ู‰ ูˆุงุฌู‡ุฉ ุงู„ุฅุดุฑุงู", @@ -687,6 +713,7 @@ "status.edited_x_times": "ุนูุฏู‘ู„ {count, plural, zero {} one {ู…ุฑุฉู‹ ูˆุงุญุฏุฉ} two {ู…ุฑู‘ุชุงู†} few {{count} ู…ุฑุงุช} many {{count} ู…ุฑุฉ} other {{count} ู…ุฑุฉ}}", "status.embed": "ุฅุฏู…ุงุฌ", "status.favourite": "ูุถู‘ู„", + "status.favourites": "{count, plural, zero {}one {ู…ูุถู„ุฉ ูˆุงุญุฏุฉ} two {ู…ูุถู„ุชุงู†} few {# ู…ูุถู„ุงุช} many {# ู…ูุถู„ุงุช} other {# ู…ูุถู„ุงุช}}", "status.filter": "ุชุตููŠุฉ ู‡ุฐู‡ ุงู„ุฑุณุงู„ุฉ", "status.filtered": "ู…ูุตูู‘ู‰", "status.hide": "ุฅุฎูุงุก ุงู„ู…ู†ุดูˆุฑ", @@ -707,6 +734,7 @@ "status.reblog": "ุฅุนุงุฏุฉ ุงู„ู†ุดุฑ", "status.reblog_private": "ุฅุนุงุฏุฉ ุงู„ู†ุดุฑ ุฅู„ู‰ ุงู„ุฌู…ู‡ูˆุฑ ุงู„ุฃุตู„ูŠ", "status.reblogged_by": "ุดุงุฑูŽูƒูŽู‡ {name}", + "status.reblogs": "{count, plural, one {ุชุนุฒูŠุฒ ูˆุงุญุฏ} two {ุชุนุฒูŠุฒุชุงู†} few {# ุชุนุฒูŠุฒุงุช} many {# ุชุนุฒูŠุฒุงุช} other {# ุชุนุฒูŠุฒุงุช}}", "status.reblogs.empty": "ู„ู… ูŠู‚ู… ุฃูŠ ุฃุญุฏ ุจู…ุดุงุฑูƒุฉ ู‡ุฐุง ุงู„ู…ู†ุดูˆุฑ ุจุนุฏ. ุนู†ุฏู…ุง ูŠู‚ูˆู… ุฃุญุฏู‡ู… ุจุฐู„ูƒ ุณูˆู ูŠุธู‡ุฑ ู‡ู†ุง.", "status.redraft": "ุฅุฒุงู„ุฉ ูˆุฅุนุงุฏุฉ ุงู„ุตูŠุงุบุฉ", "status.remove_bookmark": "ุงุญุฐูู‡ ู…ูู† ุงู„ููˆุงุตู„ ุงู„ู…ุฑุฌุนูŠุฉ", diff --git a/app/javascript/mastodon/locales/ast.json b/app/javascript/mastodon/locales/ast.json index b5015c75d8..3f32a8bf15 100644 --- a/app/javascript/mastodon/locales/ast.json +++ b/app/javascript/mastodon/locales/ast.json @@ -32,7 +32,6 @@ "account.followers": "Siguidores", "account.followers.empty": "Naide sigue a esti perfil.", "account.following": "Siguiendo", - "account.following_counter": "{count, plural,one {Sigue a {counter}} other {Sigue a {counter}}}", "account.follows.empty": "Esti perfil nun sigue a naide.", "account.go_to_profile": "Dir al perfil", "account.hide_reblogs": "Anubrir los artรญculos compartรญos de @{name}", @@ -49,7 +48,6 @@ "account.report": "Informar de: @{name}", "account.requested_follow": "{name} solicitรณ siguite", "account.show_reblogs": "Amosar los artรญculos compartรญos de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} artรญculu} other {{counter} artรญculos}}", "account.unblock": "Desbloquiar a @{name}", "account.unblock_domain": "Desbloquiar el dominiu ยซ{domain}ยป", "account.unblock_short": "Desbloquiar", @@ -409,8 +407,6 @@ "search_results.see_all": "Ver too", "search_results.statuses": "Artรญculos", "search_results.title": "Busca de: {q}", - "server_banner.introduction": "{domain} ye parte de la rede social descentralizada que tien la teunoloxรญa de {mastodon}.", - "server_banner.learn_more": "Saber mรกs", "server_banner.server_stats": "Estadรญstiques del sirvidor:", "sign_in_banner.create_account": "Crear una cuenta", "sign_in_banner.sso_redirect": "Aniciar la sesiรณn o rexistrase", diff --git a/app/javascript/mastodon/locales/be.json b/app/javascript/mastodon/locales/be.json index 2b7673312f..df29fbd418 100644 --- a/app/javascript/mastodon/locales/be.json +++ b/app/javascript/mastodon/locales/be.json @@ -308,6 +308,8 @@ "follow_requests.unlocked_explanation": "ะ’ะฐัˆ ะฐะบะฐัžะฝั‚ ะฝะต ัั…ะฐะฒะฐะฝั‹, ะฐะดะฝะฐะบ ะฟั€ะฐะดัั‚ะฐัžะฝั–ะบั– {domain} ะฟะฐะปั–ั‡ั‹ะปั–, ัˆั‚ะพ ะฒั‹ ะผะพะถะฐั†ะต ะทะฐั…ะฐั†ะตั†ัŒ ะฟั€ะฐะณะปัะดะทะตั†ัŒ ะทะฐะฟั‹ั‚ั‹ ะฝะฐ ะฟะฐะดะฟั–ัะบัƒ ะท ะณัั‚ั‹ั… ะฟั€ะพั„ั–ะปััž ัƒั€ัƒั‡ะฝัƒัŽ.", "follow_suggestions.curated_suggestion": "ะ’ั‹ะฑะฐั€ ะฐะดะผั–ะฝั–ัั‚ั€ะฐั†ั‹ั–", "follow_suggestions.dismiss": "ะะต ะฟะฐะบะฐะทะฒะฐั†ัŒ ะทะฝะพัž", + "follow_suggestions.featured_longer": "ะะดะฐะฑั€ะฐะฝั‹ั ะบะฐะผะฐะฝะดะฐะน {domain} ัƒั€ัƒั‡ะฝัƒัŽ", + "follow_suggestions.friends_of_friends_longer": "ะŸะฐะฟัƒะปัั€ะฝะฐะต ััั€ะพะด ะปัŽะดะทะตะน, ะฝะฐ ัะบั–ั… ะ’ั‹ ะฟะฐะดะฟั–ัะฐะฝั‹", "follow_suggestions.hints.featured": "ะ“ัั‚ั‹ ะฟั€ะพั„ั–ะปัŒ ะฑั‹ัž ะฒั‹ะฑั€ะฐะฝั‹ ัžั€ัƒั‡ะฝัƒัŽ ะบะฐะผะฐะฝะดะฐะน {domain}.", "follow_suggestions.hints.friends_of_friends": "ะ“ัั‚ั‹ ะฟั€ะพั„ั–ะปัŒ ะฟะฐะฟัƒะปัั€ะฝั‹ ััั€ะพะด ะปัŽะดะทะตะน, ะฝะฐ ัะบั–ั… ะฒั‹ ะฟะฐะดะฟั–ัะฐะปั–ัั.", "follow_suggestions.hints.most_followed": "ะ“ัั‚ั‹ ะฟั€ะพั„ั–ะปัŒ - ะฐะดะทั–ะฝ ะท ะฟั€ะพั„ั–ะปััž ะท ัะฐะผะฐะน ะฒัะปั–ะบะฐะน ะบะพะปัŒะบะฐัั†ัŽ ะฟะฐะดะฟั–ัะฐะบ ะฝะฐ {domain}.", @@ -315,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "ะ“ัั‚ั‹ ะฟั€ะพั„ั–ะปัŒ ะฟะฐะดะพะฑะฝั‹ ะฝะฐ ะฟั€ะพั„ั–ะปั–, ะฝะฐ ัะบั–ั ะฒั‹ ะฝัะดะฐัžะฝะฐ ะฟะฐะดะฟั–ัะฐะปั–ัั.", "follow_suggestions.personalized_suggestion": "ะŸะตั€ัะฐะฝะฐะปั–ะทะฐะฒะฐะฝะฐั ะฟั€ะฐะฟะฐะฝะพะฒะฐ", "follow_suggestions.popular_suggestion": "ะŸะฐะฟัƒะปัั€ะฝะฐั ะฟั€ะฐะฟะฐะฝะพะฒะฐ", + "follow_suggestions.popular_suggestion_longer": "ะŸะฐะฟัƒะปัั€ะฝะฐะต ะฝะฐ {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "ะŸะฐะดะพะฑะฝั‹ั ะฟั€ะพั„ั–ะปั–, ะทะฐ ัะบั–ะผั– ะฒั‹ ะฝัะดะฐัžะฝะฐ ัะฐั‡ั‹ะปั–", "follow_suggestions.view_all": "ะŸั€ะฐะณะปัะดะทะตั†ัŒ ัƒัั‘", "follow_suggestions.who_to_follow": "ะะฐ ะบะฐะณะพ ะฟะฐะดะฟั–ัะฐั†ั†ะฐ", "followed_tags": "ะŸะฐะดะฟั–ัะบั–", @@ -410,6 +414,8 @@ "limited_account_hint.action": "ะฃัะต ั€ะพัžะฝะฐ ะฟะฐะบะฐะทะฒะฐั†ัŒ ะฟั€ะพั„ั–ะปัŒ", "limited_account_hint.title": "ะ“ัั‚ั‹ ะฟั€ะพั„ั–ะปัŒ ะฑั‹ัž ัั…ะฐะฒะฐะฝั‹ ะผะฐะดัั€ะฐั‚ะฐั€ะฐะผั–", "link_preview.author": "ะะด {name}", + "link_preview.more_from_author": "ะ‘ะพะปัŒัˆ ะฐะด {name}", + "link_preview.shares": "{count, plural, one {{counter} ะดะพะฟั–ั} few {{counter} ะดะพะฟั–ัั‹} many {{counter} ะดะพะฟั–ัะฐัž} other {{counter} ะดะพะฟั–ััƒ}}", "lists.account.add": "ะ”ะฐะดะฐั†ัŒ ะดะฐ ัะฟั–ััƒ", "lists.account.remove": "ะ’ั‹ะดะฐะปั–ั†ัŒ ัะฐ ัะฟั–ััƒ", "lists.delete": "ะ’ั‹ะดะฐะปั–ั†ัŒ ัะฟั–ั", @@ -439,7 +445,7 @@ "mute_modal.you_wont_see_posts": "ะšะฐั€ั‹ัั‚ะฐะปัŒะฝั–ะบ ะฟะฐ-ั€ะฐะฝะตะนัˆะฐะผัƒ ะฑัƒะดะทะต ะฑะฐั‡ั‹ั†ัŒ ะฒะฐัˆั‹ั ะฟะฐะฒะตะดะฐะผะปะตะฝะฝั–, ะฐะปะต ะฒั‹ ะฝะต ะฑัƒะดะทะตั†ะต ะฟะฐะฒะตะดะฐะผะปะตะฝะฝั– ะบะฐั€ั‹ัั‚ะฐะปัŒะฝั–ะบะฐ.", "navigation_bar.about": "ะŸั€ะฐ ะฝะฐั", "navigation_bar.advanced_interface": "ะะดะบั€ั‹ั†ัŒ ัƒ ะฟะฐัˆั‹ั€ะฐะฝั‹ะผ ะฒัะฑ-ั–ะฝั‚ัั€ั„ะตะนัะต", - "navigation_bar.blocks": "ะ—ะฐะฑะปะฐะบะฐะฒะฐะฝั‹ั ะบะฐั€ั‹ัั‚ะฐะปัŒะฝั–ะบั–", + "navigation_bar.blocks": "ะ—ะฐะฑะปะฐะบั–ั€ะฐะฒะฐะฝั‹ั ะบะฐั€ั‹ัั‚ะฐะปัŒะฝั–ะบั–", "navigation_bar.bookmarks": "ะ—ะฐะบะปะฐะดะบั–", "navigation_bar.community_timeline": "ะ›ะฐะบะฐะปัŒะฝะฐั ัั‚ัƒะถะบะฐ", "navigation_bar.compose": "ะกั‚ะฒะฐั€ั‹ั†ัŒ ะฝะพะฒั‹ ะดะพะฟั–ั", @@ -458,7 +464,7 @@ "navigation_bar.opened_in_classic_interface": "ะ”ะพะฟั–ัั‹, ัƒะปั–ะบะพะฒั‹ั ะทะฐะฟั–ัั‹ ั– ั–ะฝัˆั‹ั ัะฟะตั†ั‹ั„ั–ั‡ะฝั‹ั ัั‚ะฐั€ะพะฝะบั– ะฟะฐ ะทะผะพัžั‡ะฐะฝะฝั– ะฐะดั‡ั‹ะฝััŽั†ั†ะฐ ัž ะบะปะฐัั–ั‡ะฝั‹ะผ ะฒัะฑ-ั–ะฝั‚ัั€ั„ะตะนัะต.", "navigation_bar.personal": "ะัะฐะฑั–ัั‚ะฐะต", "navigation_bar.pins": "ะ—ะฐะผะฐั†ะฐะฒะฐะฝั‹ั ะดะพะฟั–ัั‹", - "navigation_bar.preferences": "ะŸะฐั€ะฐะผะตั‚ั€ั‹", + "navigation_bar.preferences": "ะะฐะปะฐะดั‹", "navigation_bar.public_timeline": "ะ“ะปะฐะฑะฐะปัŒะฝะฐั ัั‚ัƒะถะบะฐ", "navigation_bar.search": "ะŸะพัˆัƒะบ", "navigation_bar.security": "ะ‘ััะฟะตะบะฐ", @@ -469,10 +475,23 @@ "notification.follow": "{name} ะฟะฐะดะฟั–ัะฐัžัั ะฝะฐ ะฒะฐั", "notification.follow_request": "{name} ะฐะดะฟั€ะฐะฒั–ัž ะทะฐะฟั‹ั‚ ะฝะฐ ะฟะฐะดะฟั–ัะบัƒ", "notification.mention": "{name} ะทะณะฐะดะฐัž ะฒะฐั", + "notification.moderation-warning.learn_more": "ะ”ะฐะฒะตะดะฐั†ั†ะฐ ะฑะพะปัŒัˆ", + "notification.moderation_warning": "ะ’ั‹ ะฐั‚ั€ั‹ะผะฐะปั– ะฟะฐะฟัั€ัะดะถะฐะฝะฝะต ะฐะฑ ะผะฐะดัั€ะฐั†ั‹ั–", + "notification.moderation_warning.action_delete_statuses": "ะะตะบะฐั‚ะพั€ั‹ั ะฒะฐัˆั‹ั ะดะพะฟั–ัั‹ ะฑั‹ะปั– ะฒั‹ะดะฐะปะตะฝั‹ั.", + "notification.moderation_warning.action_disable": "ะ’ะฐัˆ ัƒะปั–ะบะพะฒั‹ ะทะฐะฟั–ั ะฑั‹ัž ะฐะดะบะปัŽั‡ะฐะฝั‹.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "ะะตะบะฐั‚ะพั€ั‹ั ะท ะฒะฐัˆั‹ั… ะดะพะฟั–ัะฐัž ะฑั‹ะปั– ะฟะฐะทะฝะฐั‡ะฐะฝั‹ั ัะบ ะดะฐะปั–ะบะฐั‚ะฝั‹ั.", + "notification.moderation_warning.action_none": "ะ’ะฐัˆ ัƒะปั–ะบะพะฒั‹ ะทะฐะฟั–ั ะฐั‚ั€ั‹ะผะฐัž ะฟะฐะฟัั€ัะดะถะฐะฝะฝะต ะฐะด ะผะฐะดัั€ะฐั‚ะฐั€ะฐัž.", + "notification.moderation_warning.action_sensitive": "ะ— ะณัั‚ะฐะณะฐ ะผะพะผะฐะฝั‚ัƒ ะฒะฐัˆั‹ั ะดะพะฟั–ัั‹ ะฑัƒะดัƒั†ัŒ ะฟะฐะทะฝะฐั‡ะฐะฝั‹ั ัะบ ะดะฐะปั–ะบะฐั‚ะฝั‹ั.", + "notification.moderation_warning.action_silence": "ะ’ะฐัˆ ัƒะปั–ะบะพะฒั‹ ะทะฐะฟั–ั ะฑั‹ัž ะฐะฑะผะตะถะฐะฒะฐะฝั‹.", + "notification.moderation_warning.action_suspend": "ะ’ะฐัˆ ัƒะปั–ะบะพะฒั‹ ะทะฐะฟั–ั ะฑั‹ัž ะฟั€ั‹ะฟั‹ะฝะตะฝั‹.", "notification.own_poll": "ะ’ะฐัˆะฐ ะฐะฟั‹ั‚ะฐะฝะฝะต ัะบะพะฝั‡ั‹ะปะฐัั", "notification.poll": "ะะฟั‹ั‚ะฐะฝะฝะต, ะดะทะต ะฒั‹ ะฟั€ั‹ะฝัะปั– ัžะดะทะตะป, ัะบะพะฝั‡ั‹ะปะฐัั", "notification.reblog": "{name} ะฟะฐัˆั‹ั€ั‹ัž ะฒะฐัˆ ะดะพะฟั–ั", + "notification.relationships_severance_event": "ะกั‚ั€ะฐั†ั–ัž ััƒะฒัะทัŒ ะท {name}", + "notification.relationships_severance_event.account_suspension": "ะะดะผั–ะฝั–ัั‚ั€ะฐั‚ะฐั€ ะท {from} ะฟั€ั‹ะฟั‹ะฝั–ัž ะฟั€ะฐั†ัƒ {target}, ัˆั‚ะพ ะฐะทะฝะฐั‡ะฐะต, ัˆั‚ะพ ะฒั‹ ะฑะพะปัŒัˆ ะฝะต ะผะพะถะฐั†ะต ะฐั‚ั€ั‹ะผะปั–ะฒะฐั†ัŒ ะฐะด ั–ั… ะฐะฑะฝะฐัžะปะตะฝะฝั ั†ั– ัžะทะฐะตะผะฐะดะทะตะนะฝั–ั‡ะฐั†ัŒ ะท ั–ะผั–.", + "notification.relationships_severance_event.domain_block": "ะะดะผั–ะฝั–ัั‚ั€ะฐั‚ะฐั€ ะท {from} ะทะฐะฑะปะฐะบั–ั€ะฐะฒะฐัž {target}, ัƒ ั‚ั‹ะผ ะปั–ะบัƒ {followersCount} ะฒะฐัˆั‹ั… ะฟะฐะดะฟั–ัั‡ั‹ะบะฐ(-ะฐัž) ั– {followingCount, plural, one {# ัƒะปั–ะบะพะฒั‹ ะทะฐะฟั–ั} few {# ัƒะปั–ะบะพะฒั‹ั ะทะฐะฟั–ัั‹} many {# ัƒะปั–ะบะพะฒั‹ั… ะทะฐะฟั–ัะฐัž} other {# ัƒะปั–ะบะพะฒั‹ั… ะทะฐะฟั–ัะฐัž}}.", "notification.relationships_severance_event.learn_more": "ะ”ะฐะฒะตะดะฐั†ั†ะฐ ะฑะพะปัŒัˆ", + "notification.relationships_severance_event.user_domain_block": "ะ’ั‹ ะทะฐะฑะปะฐะบั–ั€ะฐะฒะฐะปั– {target} ะฒั‹ะดะฐะปั–ัžัˆั‹ {followersCount} ัะฒะฐั–ั… ะฟะฐะดะฟั–ัั‡ั‹ะบะฐัž ั– {followingCount, plural, one {# ัƒะปั–ะบะพะฒั‹ ะทะฐะฟั–ั} few {# ัƒะปั–ะบะพะฒั‹ั ะทะฐะฟั–ัั‹} many {# ัƒะปั–ะบะพะฒั‹ั… ะทะฐะฟั–ัะฐัž} other {# ัƒะปั–ะบะพะฒั‹ั… ะทะฐะฟั–ัะฐัž}}, ะทะฐ ัะบั–ะผั– ะฒั‹ ัะพั‡ั‹ั†ะต.", "notification.status": "ะะพะฒั‹ ะดะพะฟั–ั ะฐะด {name}", "notification.update": "ะ”ะพะฟั–ั {name} ะฐะดั€ัะดะฐะณะฐะฒะฐะฝั‹", "notification_requests.accept": "ะŸั€ั‹ะฝัั†ัŒ", @@ -677,13 +696,10 @@ "server_banner.about_active_users": "ะ›ัŽะดะทั–, ัะบั–ั ะบะฐั€ั‹ัั‚ะฐัŽั†ั†ะฐ ะณัั‚ั‹ะผ ัะตั€ะฒะตั€ะฐ ะฝะฐ ะฟั€ะฐั†ัะณัƒ ะฐะฟะพัˆะฝั–ั… 30 ะดะทั‘ะฝ (ะจั‚ะพะผะตััั‡ะฝะฐ ะะบั‚ั‹ัžะฝั‹ั ะšะฐั€ั‹ัั‚ะฐะปัŒะฝั–ะบั–)", "server_banner.active_users": "ะฐะบั‚ั‹ัžะฝั‹ั ะบะฐั€ั‹ัั‚ะฐะปัŒะฝั–ะบั–", "server_banner.administered_by": "ะะดะผั–ะฝั–ัั‚ั€ะฐั‚ะฐั€:", - "server_banner.introduction": "{domain} ั‘ัั†ัŒ ั‡ะฐัั‚ะบะฐะน ะดัั†ัะฝั‚ั€ะฐะปั–ะทะฐะฒะฐะฝะฐะน ัะฐั†ั‹ัะปัŒะฝะฐะน ัะตั‚ะบั– ะฐะด {mastodon}.", - "server_banner.learn_more": "ะ”ะฐะฒะตะดะฐั†ั†ะฐ ะฑะพะปัŒัˆ", "server_banner.server_stats": "ะกั‚ะฐั‚ั‹ัั‚ั‹ะบะฐ ัะตั€ะฒะตั€ะฐ:", "sign_in_banner.create_account": "ะกั‚ะฒะฐั€ั‹ั†ัŒ ัƒะปั–ะบะพะฒั‹ ะทะฐะฟั–ั", "sign_in_banner.sign_in": "ะฃะฒะฐะนัั†ั–", "sign_in_banner.sso_redirect": "ะฃะฒะฐั…ะพะด ั†ั– ั€ัะณั–ัั‚ั€ะฐั†ั‹ั", - "sign_in_banner.text": "ะฃะฒะฐะนะดะทั–ั†ะต, ะบะฐะฑ ะฟะฐะดะฟั–ัะฐั†ั†ะฐ ะฝะฐ ะปัŽะดะทะตะน ั– ั‚ัะณั–, ะบะฐะฑ ะฐะดะบะฐะทะฒะฐั†ัŒ ะฝะฐ ะดะพะฟั–ัั‹, ะดะทัะปั–ั†ั†ะฐ ั–ะผั– ั– ะฟะฐะดะฐะฑะฐั†ัŒ ั–ั…, ะฐะปัŒะฑะพ ะบะฐะฝั‚ะฐะบั‚ะฐะฒะฐั†ัŒ ะท ะฒะฐัˆะฐะณะฐ ัžะปั–ะบะพะฒะฐะณะฐ ะทะฐะฟั–ััƒ ะฝะฐ ั–ะฝัˆั‹ะผ ัะตั€ะฒะตั€ั‹.", "status.admin_account": "ะะดะบั€ั‹ั†ัŒ ั–ะฝั‚ัั€ั„ะตะนั ะผะฐะดัั€ะฐั‚ะฐั€ะฐ ะดะปั @{name}", "status.admin_domain": "ะะดะบั€ั‹ั†ัŒ ั–ะฝั‚ัั€ั„ะตะนั ะผะฐะดัั€ะฐั‚ะฐั€ะฐ ะดะปั {domain}", "status.admin_status": "ะะดะบั€ั‹ั†ัŒ ะณัั‚ั‹ ะดะพะฟั–ั ัƒ ั–ะฝั‚ัั€ั„ะตะนัะต ะผะฐะดัั€ะฐั†ั‹ั–", diff --git a/app/javascript/mastodon/locales/bg.json b/app/javascript/mastodon/locales/bg.json index 910d6cb06e..98e84c45d7 100644 --- a/app/javascript/mastodon/locales/bg.json +++ b/app/javascript/mastodon/locales/bg.json @@ -297,6 +297,7 @@ "filter_modal.select_filter.subtitle": "ะ˜ะทะฑะตั€ะตั‚ะต ััŠั‰ะตัั‚ะฒัƒะฒะฐั‰ะฐ ะบะฐั‚ะตะณะพั€ะธั ะธะปะธ ััŠะทะดะฐะนั‚ะต ะฝะพะฒะฐ", "filter_modal.select_filter.title": "ะคะธะปั‚ั€ะธั€ะฐะฝะต ะฝะฐ ะฟัƒะฑะป.", "filter_modal.title.status": "ะคะธะปั‚ั€ะธั€ะฐะฝะต ะฝะฐ ะฟัƒะฑะป.", + "filtered_notifications_banner.mentions": "{count, plural, one {ัะฟะพะผะตะฝะฐะฒะฐะฝะต} other {ัะฟะพะผะตะฝะฐะฒะฐะฝะธั}}", "filtered_notifications_banner.pending_requests": "ะ˜ะทะฒะตัั‚ะธัั‚ะฐ ะพั‚ {count, plural, =0 {ะฝะธะบะพะณะพ, ะบะพะณะพั‚ะพ ะผะพะถะต ะดะฐ ะฟะพะทะฝะฐะฒะฐั‚ะต} one {ะตะดะฝะพ ะปะธั†ะต, ะบะพะตั‚ะพ ะผะพะถะต ะดะฐ ะฟะพะทะฝะฐะฒะฐั‚ะต} other {# ะดัƒัˆะธ, ะบะพะธั‚ะพ ะผะพะถะต ะดะฐ ะฟะพะทะฝะฐะฒะฐั‚ะต}}", "filtered_notifications_banner.title": "ะคะธะปั‚ั€ะธั€ะฐะฝะธ ะธะทะฒะตัั‚ะธั", "firehose.all": "ะ’ัะธั‡ะบะพ", @@ -307,6 +308,8 @@ "follow_requests.unlocked_explanation": "ะ’ัŠะฟั€ะตะบะธ ั‡ะต ะฐะบะฐัƒะฝั‚ัŠั‚ ะฒะธ ะฝะต ะต ะทะฐะบะปัŽั‡ะตะฝ, ัะปัƒะถะธั‚ะตะปะธั‚ะต ะฝะฐ {domain} ะฟะพะผะธัะปะธั…ะฐ, ั‡ะต ะผะพะถะต ะดะฐ ะธัะบะฐั‚ะต ะดะฐ ะฟั€ะตะณะปะตะถะดะฐั‚ะต ั€ัŠั‡ะฝะพ ะทะฐัะฒะบะธั‚ะต ะทะฐ ะฟะพัะปะตะดะฒะฐะฝะต ะฝะฐ ั‚ะตะทะธ ะฟั€ะพั„ะธะปะธ.", "follow_suggestions.curated_suggestion": "ะ˜ะทะฑะพั€ ะฝะฐ ะฟะตั€ัะพะฝะฐะป", "follow_suggestions.dismiss": "ะ‘ะตะท ะฝะพะฒะพ ะฟะพะบะฐะทะฒะฐะฝะต", + "follow_suggestions.featured_longer": "ะ ัŠั‡ะฝะพ ะธะทะฑั€ะฐะฝะพ ะพั‚ ะพั‚ะฑะพั€ะฐ ะฝะฐ {domain}", + "follow_suggestions.friends_of_friends_longer": "ะŸะพะฟัƒะปัั€ะฝะพ ะธะทะผะตะถะดัƒ ั…ะพั€ะฐั‚ะฐ, ะบะพะธั‚ะพ ัะปะตะดะฒะฐั‚ะต", "follow_suggestions.hints.featured": "ะขะพะทะธ ะฟั€ะพั„ะธะป ะต ั€ัŠั‡ะฝะพ ะฟะพะดะฑั€ะฐะฝ ะพั‚ ะพั‚ะฑะพั€ะฐ ะฝะฐ {domain}.", "follow_suggestions.hints.friends_of_friends": "ะขะพะทะธ ะฟั€ะพั„ะธะป ะต ะฟะพะฟัƒะปัั€ะตะฝ ะธะทะผะตะถะดัƒ ั…ะพั€ะฐั‚ะฐ, ะบะพะธั‚ะพ ัะปะตะดะฒะฐั‚ะต.", "follow_suggestions.hints.most_followed": "ะขะพะทะธ ะฟั€ะพั„ะธะป ะต ะตะดะธะฝ ะพั‚ ะฝะฐะน-ัะปะตะดะฒะฐะฝะธั‚ะต ะฟั€ะธ {domain}.", @@ -314,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "ะขะพะทะธ ะฟั€ะพั„ะธะป ะต ะฟะพะดะพะฑะตะฝ ะฝะฐ ะฟั€ะพั„ะธะปะธั‚ะต, ะบะพะธั‚ะพ ัั‚ะต ะฟะพัะปะตะดะฒะฐะปะธ ะฝะฐัะบะพั€ะพ.", "follow_suggestions.personalized_suggestion": "ะŸะตั€ัะพะฝะฐะปะธะทะธั€ะฐะฝะพ ะฟั€ะตะดะปะพะถะตะฝะธะต", "follow_suggestions.popular_suggestion": "ะŸะพะฟัƒะปัั€ะฝะพ ะฟั€ะตะดะปะพะถะตะฝะธะต", + "follow_suggestions.popular_suggestion_longer": "ะŸะพะฟัƒะปัั€ะฝะพ ะธะท {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "ะŸะพะดะพะฑะฝะธ ะฝะฐ ะฟั€ะพั„ะธะปะธั‚ะต, ะบะพะธั‚ะพ ะฝะฐัะบะพั€ะพ ัั‚ะต ะฟะพัะปะตะดะฒะฐะปะธ", "follow_suggestions.view_all": "ะŸั€ะตะณะปะตะด ะฝะฐ ะฒัะธั‡ะบะธ", "follow_suggestions.who_to_follow": "ะšะพะณะพ ะดะฐ ัะต ัะปะตะดะฒะฐ", "followed_tags": "ะŸะพัะปะตะดะฒะฐะฝะธ ั…ะฐัˆั‚ะฐะณะพะฒะต", @@ -409,6 +414,8 @@ "limited_account_hint.action": "ะŸะพะบะฐะทะฒะฐะฝะต ะฝะฐ ะฟั€ะพั„ะธะปะฐ ะฒัŠะฟั€ะตะบะธ ั‚ะพะฒะฐ", "limited_account_hint.title": "ะขะพะทะธ ะฟั€ะพั„ะธะป ะต ะฑะธะป ัะบั€ะธั‚ ะพั‚ ะผะพะดะตั€ะฐั‚ะพั€ะธั‚ะต ะฝะฐ {domain}.", "link_preview.author": "ะžั‚ {name}", + "link_preview.more_from_author": "ะžั‰ะต ะพั‚ {name}", + "link_preview.shares": "{count, plural, one {{counter} ะฟัƒะฑะปะธะบะฐั†ะธั} other {{counter} ะฟัƒะฑะปะธะบะฐั†ะธะธ}}", "lists.account.add": "ะ”ะพะฑะฐะฒัะฝะต ะบัŠะผ ัะฟะธััŠะบ", "lists.account.remove": "ะŸั€ะตะผะฐั…ะฒะฐะฝะต ะพั‚ ัะฟะธััŠะบะฐ", "lists.delete": "ะ˜ะทั‚ั€ะธะฒะฐะฝะต ะฝะฐ ัะฟะธััŠะบะฐ", @@ -468,6 +475,15 @@ "notification.follow": "{name} ะฒะธ ะฟะพัะปะตะดะฒะฐ", "notification.follow_request": "{name} ะฟะพะธัะบะฐ ะดะฐ ะฒะธ ะฟะพัะปะตะดะฒะฐ", "notification.mention": "{name} ะฒะธ ัะฟะพะผะตะฝะฐ", + "notification.moderation-warning.learn_more": "ะะฐัƒั‡ะตั‚ะต ะฟะพะฒะตั‡ะต", + "notification.moderation_warning": "ะŸะพะปัƒั‡ะธั…ั‚ะต ะฟั€ะตะดัƒะฟั€ะตะถะดะตะฝะธะต ะทะฐ ะผะพะดะตั€ะธั€ะฐะฝะต", + "notification.moderation_warning.action_delete_statuses": "ะัะบะพะธ ะพั‚ ะฟัƒะฑะปะธะบะฐั†ะธะธั‚ะต ะฒะธ ัะฐ ะฟั€ะตะผะฐั…ะฝะฐั‚ะธ.", + "notification.moderation_warning.action_disable": "ะ’ะฐัˆะธัั‚ ะฐะบะฐัƒะฝั‚ ะต ะธะทะบะปัŽั‡ะตะฝ.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "ะัะบะพะธ ะพั‚ ะฟัƒะฑะปะธะบะฐั†ะธะธั‚ะต ะฒะธ ัะฐ ะพะทะฝะฐั‡ะตะฝะธ ะบะฐั‚ะพ ะดะตะปะธะบะฐั‚ะฝะธ.", + "notification.moderation_warning.action_none": "ะะบะฐัƒะฝั‚ัŠั‚ ะฒะธ ะฟะพะปัƒั‡ะธ ะฟั€ะตะดัƒะฟั€ะตะถะดะตะฝะธะต ะทะฐ ะผะพะดะตั€ะธั€ะฐะฝะต.", + "notification.moderation_warning.action_sensitive": "ะŸัƒะฑะปะธะบะฐั†ะธะธั‚ะต ะฒะธ ั‰ะต ัะต ะพะทะฝะฐั‡ะฐะฒะฐั‚ ะบะฐั‚ะพ ะดะตะปะธะบะฐั‚ะฝะธ ะพั‚ ัะตะณะฐ ะฝะฐั‚ะฐั‚ัŠะบ.", + "notification.moderation_warning.action_silence": "ะ’ะฐัˆะธัั‚ ะฐะบะฐัƒะฝั‚ ะต ะพะณั€ะฐะฝะธั‡ะตะฝ.", + "notification.moderation_warning.action_suspend": "ะ’ะฐัˆะธัั‚ ะฐะบะฐัƒะฝั‚ ะต ัะฟั€ัะฝ.", "notification.own_poll": "ะะฝะบะตั‚ะฐั‚ะฐ ะฒะธ ะฟั€ะธะบะปัŽั‡ะธ", "notification.poll": "ะะฝะบะตั‚ะฐ, ะฒ ะบะพัั‚ะพ ะณะปะฐััƒะฒะฐั…ั‚ะต, ะฟั€ะธะบะปัŽั‡ะธ", "notification.reblog": "{name} ะฟะพะดัะธะปะธ ะฒะฐัˆะฐ ะฟัƒะฑะปะธะบะฐั†ะธั", @@ -680,13 +696,13 @@ "server_banner.about_active_users": "ะŸะพะปะทะฒะฐั‰ะธั‚ะต ััŠั€ะฒัŠั€ะฐ ะฟั€ะตะท ะฟะพัะปะตะดะฝะธั‚ะต 30 ะดะฝะธ (ะดะตะนะฝะธั‚ะต ะผะตัะตั‡ะฝะพ ะฟะพั‚ั€ะตะฑะธั‚ะตะปะธ)", "server_banner.active_users": "ะดะตะนะฝะธ ะฟะพั‚ั€ะตะฑะธั‚ะตะปะธ", "server_banner.administered_by": "ะะดะผะธะฝะธัั‚ั€ะธั€ะฐ ัะต ะพั‚:", - "server_banner.introduction": "{domain} ะต ั‡ะฐัั‚ ะพั‚ ะดะตั†ะตะฝั‚ั€ะฐะปะธะทะธั€ะฐะฝะฐั‚ะฐ ัะพั†ะธะฐะปะฝะฐ ะผั€ะตะถะฐ, ะฟะพะดะดัŠั€ะถะฐะฝะฐ ะพั‚ {mastodon}.", - "server_banner.learn_more": "ะะฐัƒั‡ะตั‚ะต ะฟะพะฒะตั‡ะต", + "server_banner.is_one_of_many": "{domain} ะต ะตะดะธะฝ ะพั‚ ะผะฝะพะณะพั‚ะพ ะฝะตะทะฐะฒะธัะธะผะธ ััŠั€ะฒัŠั€ะธ ะฝะฐ Mastodon, ะบะพะธั‚ะพ ะผะพะถะต ะดะฐ ัƒะฟะพั‚ั€ะตะฑัะฒะฐั‚ะต, ะทะฐ ะดะฐ ัƒั‡ะฐัั‚ะฒะฐั‚ะต ะฒัŠะฒ ั„ะตะดะธะฒัะตะปะตะฝะฐั‚ะฐ.", "server_banner.server_stats": "ะกั‚ะฐั‚ะธัั‚ะธะบะฐ ะฝะฐ ััŠั€ะฒัŠั€ะฐ:", "sign_in_banner.create_account": "ะกัŠะทะดะฐะฒะฐะฝะต ะฝะฐ ะฐะบะฐัƒะฝั‚", + "sign_in_banner.follow_anyone": "ะŸะพัะปะตะดะฒะฐะนั‚ะต ะฝัะบะพะณะพ ะฟั€ะตะท ั„ะตะดะธะฒัะตะปะตะฝะฐั‚ะฐ ะธ ะฒะธะถั‚ะต ะฒัะธั‡ะบะพ ะฒ ั…ั€ะพะฝะพะปะพะณะธั‡ะตะฝ ั€ะตะด. ะ‘ะตะท ะฐะปะณะพั€ะธั‚ะผะธ, ั€ะตะบะปะฐะผะธ, ะธะปะธ ะฟั€ะธะผะฐะผะฒะฐั‰ะธ ะฒั€ัŠะทะบะธ ะฒ ะฟะพะปะตะทั€ะตะฝะธะตั‚ะพ.", + "sign_in_banner.mastodon_is": "Mastodon ะต ะฝะฐะน-ะดะพะฑั€ะธั ะฝะฐั‡ะธะฝ ะดะฐ ะฑัŠะดะตั‚ะต ะฒ ะบั€ะฐะบ ััŠั ัะปัƒั‡ะฒะฐั‰ะพั‚ะพ ัะต.", "sign_in_banner.sign_in": "ะ’ั…ะพะด", "sign_in_banner.sso_redirect": "ะ’ะปะธะทะฐะฝะต ะธะปะธ ั€ะตะณะธัั‚ั€ะธั€ะฐะฝะต", - "sign_in_banner.text": "ะ’ะปะตะทั‚ะต, ะทะฐ ะดะฐ ะฟะพัะปะตะดะฒะฐั‚ะต ะฟั€ะพั„ะธะปะธ ะธะปะธ ั…ะฐัˆั‚ะฐะณะพะฒะต, ะพั‚ะฑะตะปัะทะฒะฐั‚ะต ะบะฐั‚ะพ ะปัŽะฑะธะผะธ, ัะฟะพะดะตะปัั‚ะต ะธ ะพั‚ะณะพะฒะฐั€ัั‚ะต ะฝะฐ ะฟัƒะฑะปะธะบะฐั†ะธะธ. ะœะพะถะต ััŠั‰ะพ ั‚ะฐะบะฐ ะดะฐ ะฒะทะฐะธะผะพะดะตะนัั‚ะฒะฐั‚ะต ะพั‚ ะฐะบะฐัƒะฝั‚ะฐ ัะธ ะฝะฐ ะดั€ัƒะณ ััŠั€ะฒัŠั€.", "status.admin_account": "ะžั‚ะฒะฐั€ัะฝะต ะฝะฐ ะธะฝั‚ะตั€ั„ะตะนั ะทะฐ ะผะพะดะตั€ะธั€ะฐะฝะต ะทะฐ @{name}", "status.admin_domain": "ะžั‚ะฒะฐั€ัะฝะต ะฝะฐ ะผะพะดะตั€ะธั€ะฐั‰ะธั ะธะฝั‚ะตั€ั„ะตะนั ะทะฐ {domain}", "status.admin_status": "ะžั‚ะฒะฐั€ัะฝะต ะฝะฐ ะฟัƒะฑะปะธะบะฐั†ะธัั‚ะฐ ะฒ ะผะพะดะตั€ะธั€ะฐั‰ะธั ะธะฝั‚ะตั€ั„ะตะนั", @@ -727,7 +743,7 @@ "status.reblogged_by": "{name} ะฟะพะดัะธะปะธ", "status.reblogs": "{count, plural, one {ะฟะพะดัะธะปะฒะฐะฝะต} other {ะฟะพะดัะธะปะฒะฐะฝะธั}}", "status.reblogs.empty": "ะžั‰ะต ะฝะธะบะพะณะพ ะฝะต ะต ะฟะพะดัะธะปะฒะฐะป ะฟัƒะฑะปะธะบะฐั†ะธัั‚ะฐ. ะŸะพะดัะธะปะฒะฐั‰ะธัั‚ ั‰ะต ัะต ะฟะพะบะฐะถะต ั‚ัƒะบ.", - "status.redraft": "ะ˜ะทั‚ั€ะธะฒะฐะฝะต ะธ ะฟั€ะตะฝะฐั‡ะตั€ั‚ะฐะฒะฐะฝะต", + "status.redraft": "ะ˜ะทั‚ั€ะธะฒะฐะฝะต ะธ ะฟั€ะตั€ะฐะฑะพั‚ะฒะฐะฝะต", "status.remove_bookmark": "ะŸั€ะตะผะฐั…ะฒะฐะฝะต ะฝะฐ ะพั‚ะผะตั‚ะบะฐั‚ะฐ", "status.replied_to": "ะ’ ะพั‚ะณะพะฒะพั€ ะดะพ {name}", "status.reply": "ะžั‚ะณะพะฒะพั€", diff --git a/app/javascript/mastodon/locales/bn.json b/app/javascript/mastodon/locales/bn.json index 797b93e243..a203c43f03 100644 --- a/app/javascript/mastodon/locales/bn.json +++ b/app/javascript/mastodon/locales/bn.json @@ -33,9 +33,7 @@ "account.follow": "เฆ…เฆจเงเฆธเฆฐเฆฃ", "account.followers": "เฆ…เฆจเงเฆธเฆฐเฆฃเฆ•เฆพเฆฐเง€", "account.followers.empty": "เฆเฆ‡ เฆฌเงเฆฏเฆ•เงเฆคเฆฟเฆ•เง‡ เฆเฆ–เฆจเง‹ เฆ•เง‡เฆ‰ เฆ…เฆจเงเฆธเฆฐเฆฃ เฆ•เฆฐเง‡ เฆจเฆพ.", - "account.followers_counter": "{count, plural,one {{counter} เฆœเฆจ เฆ…เฆจเงเฆธเฆฐเฆฃเฆ•เฆพเฆฐเง€ } other {{counter} เฆœเฆจ เฆ…เฆจเงเฆธเฆฐเฆฃเฆ•เฆพเฆฐเง€}}", "account.following": "เฆ…เฆจเงเฆธเฆฐเฆฃ เฆ•เฆฐเฆพ เฆนเฆšเงเฆ›เง‡", - "account.following_counter": "{count, plural,one {{counter} เฆœเฆจเฆ•เง‡ เฆ…เฆจเงเฆธเฆฐเฆฃ} other {{counter} เฆœเฆจเฆ•เง‡ เฆ…เฆจเงเฆธเฆฐเฆฃ}}", "account.follows.empty": "เฆเฆ‡ เฆธเฆฆเฆธเงเฆฏ เฆ•เฆพเฆ‰เฆ•เง‡ เฆเฆ–เฆจเง‹ เฆซเฆฒเง‹ เฆ•เฆฐเง‡เฆจ เฆจเฆพ.", "account.go_to_profile": "เฆชเงเฆฐเง‹เฆซเฆพเฆ‡เฆฒเง‡ เฆฏเฆพเฆจ", "account.hide_reblogs": "@{name}'เฆฐ เฆธเฆฎเฆฐเงเฆฅเฆจเฆ—เงเฆฒเฆฟ เฆฒเงเฆ•เฆฟเงŸเง‡ เฆซเง‡เฆฒเงเฆจ", @@ -60,7 +58,6 @@ "account.requested_follow": "{name} เฆ†เฆชเฆจเฆพเฆ•เง‡ เฆ…เฆจเงเฆธเฆฐเฆฃ เฆ•เฆฐเฆพเฆฐ เฆœเฆจเงเฆฏ เฆ…เฆจเงเฆฐเง‹เฆง เฆ•เฆฐเง‡เฆ›เง‡", "account.share": "@{name} เฆฐ เฆชเงเฆฐเง‹เฆซเฆพเฆ‡เฆฒ เฆ…เฆจเงเฆฏเฆฆเง‡เฆฐ เฆฆเง‡เฆ–เฆพเฆจ", "account.show_reblogs": "@{name} เฆฐ เฆธเฆฎเฆฐเงเฆฅเฆจเฆ—เงเฆฒเง‹ เฆฆเง‡เฆ–เฆพเฆจ", - "account.statuses_counter": "{count, plural,one {{counter} เฆŸเงเฆŸ} other {{counter} เฆŸเงเฆŸ}}", "account.unblock": "@{name} เฆฐ เฆ•เฆพเฆฐเงเฆฏเฆ•เฆฒเฆพเฆช เฆฆเง‡เฆ–เงเฆจ", "account.unblock_domain": "{domain} เฆ•เง‡ เฆ†เฆฌเฆพเฆฐ เฆฆเง‡เฆ–เงเฆจ", "account.unblock_short": "เฆ†เฆจเฆฌเงเฆฒเฆ• เฆ•เฆฐเงเฆจ", @@ -407,7 +404,6 @@ "search_results.all": "เฆธเฆฌ", "search_results.hashtags": "เฆนเงเฆฏเฆพเฆถเฆŸเงเฆฏเฆพเฆ—เฆ—เงเฆฒเฆฟ", "search_results.statuses": "เฆŸเงเฆŸ", - "server_banner.learn_more": "เฆ†เฆฐเง‹ เฆœเฆพเฆจเง‹", "sign_in_banner.sign_in": "Sign in", "status.admin_account": "@{name} เฆฐ เฆœเฆจเงเฆฏ เฆชเฆฐเฆฟเฆšเฆพเฆฒเฆจเฆพเฆฐ เฆ‡เฆจเงเฆŸเฆพเฆฐเฆซเง‡เฆธเง‡ เฆขเงเฆ•เงเฆจ", "status.admin_status": "เฆฏเฆพเงŸ เฆฒเง‡เฆ–เฆพเฆŸเฆฟ เฆชเฆฐเฆฟเฆšเฆพเฆฒเฆจเฆพเฆฐ เฆ‡เฆจเงเฆŸเฆพเฆฐเฆซเง‡เฆธเง‡ เฆ–เงเฆฒเงเฆจ", diff --git a/app/javascript/mastodon/locales/br.json b/app/javascript/mastodon/locales/br.json index f51121bcdc..a150fb4902 100644 --- a/app/javascript/mastodon/locales/br.json +++ b/app/javascript/mastodon/locales/br.json @@ -35,9 +35,8 @@ "account.follow_back": "Heuliaรฑ d'ho tro", "account.followers": "Tud koumanantet", "account.followers.empty": "Den na heul an implijerยทez-maรฑ c'hoazh.", - "account.followers_counter": "{count, plural, other{{counter} Heulierยทez}}", + "account.followers_counter": "{count, plural, one {{counter} heulier} two {{counter} heulier} few {{counter} heulier} many {{counter} heulier} other {{counter} heulier}}", "account.following": "Koumanantoรน", - "account.following_counter": "{count, plural, one{{counter} C'houmanant} two{{counter} Goumanant} other {{counter} a Goumanant}}", "account.follows.empty": "An implijerยทez-maรฑ na heul den ebet.", "account.go_to_profile": "Gwelet ar profil", "account.hide_reblogs": "Kuzh skignadennoรน gant @{name}", @@ -62,7 +61,7 @@ "account.requested_follow": "Gant {name} eo bet goulennet ho heuliaรฑ", "account.share": "Skignaรฑ profil @{name}", "account.show_reblogs": "Diskouez skignadennoรน @{name}", - "account.statuses_counter": "{count, plural, one {{counter} C'hannad} two {{counter} Gannad} other {{counter} a Gannadoรน}}", + "account.statuses_counter": "{count, plural, one {{counter} embannadur} two {{counter} embannadur} few {{counter} embannadur} many {{counter} embannadur} other {{counter} embannadur}}", "account.unblock": "Diverzaรฑ @{name}", "account.unblock_domain": "Diverzaรฑ an domani {domain}", "account.unblock_short": "Distankaรฑ", @@ -263,6 +262,8 @@ "follow_request.authorize": "Aotren", "follow_request.reject": "Nac'haรฑ", "follow_requests.unlocked_explanation": "Daoust ma n'eo ket ho kont prennet, skipailh {domain} a soรฑj e fellfe deoc'h gwiriekaat pedadennoรน heuliaรฑ deus ar c'hontoรน-se diwar-zorn.", + "follow_suggestions.friends_of_friends_longer": "Diouzh ar c'hiz e-touez an dud heuliet ganeoc'h", + "follow_suggestions.popular_suggestion_longer": "Diouzh ar c'hiz war {domain}", "follow_suggestions.view_all": "Gwelet pep tra", "followed_tags": "Hashtagoรน o heuliaรฑ", "footer.about": "Diwar-benn", @@ -395,6 +396,7 @@ "notification.follow": "heuliaรฑ a ra {name} ac'hanoc'h", "notification.follow_request": "Gant {name} eo bet goulennet ho heuliaรฑ", "notification.mention": "Gant {name} oc'h bet meneget", + "notification.moderation-warning.learn_more": "Gouzout hiroc'h", "notification.own_poll": "Echu eo ho sontadeg", "notification.poll": "Ur sontadeg ho deus mouezhet warnaรฑ a zo echuet", "notification.reblog": "Gant {name} eo bet skignet ho toud", @@ -563,7 +565,6 @@ "search_results.title": "Klask {q}", "server_banner.active_users": "implijerienยทezed oberiant", "server_banner.administered_by": "Meret gant :", - "server_banner.learn_more": "Gouzout hiroc'h", "server_banner.server_stats": "Stadegoรน ar servijer :", "sign_in_banner.create_account": "Krouiรฑ ur gont", "sign_in_banner.sign_in": "Kevreaรฑ", diff --git a/app/javascript/mastodon/locales/ca.json b/app/javascript/mastodon/locales/ca.json index 40d9771df1..3123e29d8d 100644 --- a/app/javascript/mastodon/locales/ca.json +++ b/app/javascript/mastodon/locales/ca.json @@ -35,9 +35,9 @@ "account.follow_back": "Segueix tu tambรฉ", "account.followers": "Seguidors", "account.followers.empty": "A aquest usuari encara no el segueix ningรบ.", - "account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} Seguidors}}", + "account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidors}}", "account.following": "Seguint", - "account.following_counter": "{count, plural, other {{counter} Seguint-ne}}", + "account.following_counter": "{count, plural, other {Seguint-ne {counter}}}", "account.follows.empty": "Aquest usuari encara no segueix ningรบ.", "account.go_to_profile": "Vรฉs al perfil", "account.hide_reblogs": "Amaga els impulsos de @{name}", @@ -63,7 +63,7 @@ "account.requested_follow": "{name} ha demanat de seguir-te", "account.share": "Comparteix el perfil de @{name}", "account.show_reblogs": "Mostra els impulsos de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Tut} other {{counter} Tuts}}", + "account.statuses_counter": "{count, plural, one {{counter} publicaciรณ} other {{counter} publicacions}}", "account.unblock": "Desbloca @{name}", "account.unblock_domain": "Desbloca el domini {domain}", "account.unblock_short": "Desbloca", @@ -308,6 +308,8 @@ "follow_requests.unlocked_explanation": "Tot i que el teu compte no estร  blocat, el personal de {domain} ha pensat que รฉs possible que vulguis revisar manualment les solยทlicituds de seguiment dโ€™aquests comptes.", "follow_suggestions.curated_suggestion": "Tria de l'equip", "follow_suggestions.dismiss": "No ho tornis a mostrar", + "follow_suggestions.featured_longer": "Triat personalment per l'equip de {domain}", + "follow_suggestions.friends_of_friends_longer": "Popular entre la gent que segueixes", "follow_suggestions.hints.featured": "L'equip de {domain} ha seleccionat aquest perfil.", "follow_suggestions.hints.friends_of_friends": "Aquest perfil รฉs popular entre la gent que seguiu.", "follow_suggestions.hints.most_followed": "Aquest perfil รฉs un dels mรฉs seguits a {domain}.", @@ -315,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Aquest perfil รฉs similar a d'altres que heu seguit recentment.", "follow_suggestions.personalized_suggestion": "Suggeriment personalitzat", "follow_suggestions.popular_suggestion": "Suggeriment popular", + "follow_suggestions.popular_suggestion_longer": "Popular a {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Semblant a perfils que seguiu de fa poc", "follow_suggestions.view_all": "Mostra-ho tot", "follow_suggestions.who_to_follow": "A qui seguir", "followed_tags": "Etiquetes seguides", @@ -410,6 +414,8 @@ "limited_account_hint.action": "Mostra el perfil de totes maneres", "limited_account_hint.title": "Aquest perfil l'han amagat els moderadors de {domain}.", "link_preview.author": "Per {name}", + "link_preview.more_from_author": "Mรฉs de {name}", + "link_preview.shares": "{count, plural, one {{counter} publicaciรณ} other {{counter} publicacions}}", "lists.account.add": "Afegeix a la llista", "lists.account.remove": "Elimina de la llista", "lists.delete": "Elimina la llista", @@ -469,6 +475,15 @@ "notification.follow": "{name} et segueix", "notification.follow_request": "{name} ha solยทlicitat de seguir-te", "notification.mention": "{name} t'ha esmentat", + "notification.moderation-warning.learn_more": "Per a saber-ne mรฉs", + "notification.moderation_warning": "Heu rebut un avรญs de moderaciรณ", + "notification.moderation_warning.action_delete_statuses": "S'han eliminat algunes de les vostres publicacions.", + "notification.moderation_warning.action_disable": "S'ha desactivat el vostre compte.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "S'ha marcat com a sensibles algunes de les vostres publicacions.", + "notification.moderation_warning.action_none": "El vostre compte ha rebut un avรญs de moderaciรณ.", + "notification.moderation_warning.action_sensitive": "A partir d'ara les vostres publicacions es marcaran com sensibles.", + "notification.moderation_warning.action_silence": "S'ha limitat el vostre compte.", + "notification.moderation_warning.action_suspend": "S'ha suspรจs el vostre compte.", "notification.own_poll": "La teva enquesta ha finalitzat", "notification.poll": "Ha finalitzat una enquesta en quรจ has votat", "notification.reblog": "{name} t'ha impulsat", @@ -681,13 +696,13 @@ "server_banner.about_active_users": "Gent que ha fet servir aquest servidor en els darrers 30 dies (Usuaris Actius Mensuals)", "server_banner.active_users": "usuaris actius", "server_banner.administered_by": "Administrat per:", - "server_banner.introduction": "{domain} รฉs part de la xarxa social descentralitzada impulsada per {mastodon}.", - "server_banner.learn_more": "Mรฉs informaciรณ", + "server_banner.is_one_of_many": "{domain} รฉs un dels molts servidors de Mastodon que pots fer servir per a participar en el fedivers.", "server_banner.server_stats": "Estadรญstiques del servidor:", "sign_in_banner.create_account": "Crea un compte", + "sign_in_banner.follow_anyone": "Segueix qui sigui al fedivers i ho veurร s tot en ordre cronolรฒgic. Sense algorismes, anuncis o pescaclics.", + "sign_in_banner.mastodon_is": "Mastodon รฉs la millor manera de seguir al moment quรจ passa.", "sign_in_banner.sign_in": "Inici de sessiรณ", "sign_in_banner.sso_redirect": "Inici de sessiรณ o Registre", - "sign_in_banner.text": "Inicia la sessiรณ per a seguir perfils o etiquetes, afavorir, compartir i respondre tuts. Tambรฉ pots interactuar des del teu compte a un servidor diferent.", "status.admin_account": "Obre la interfรญcie de moderaciรณ per a @{name}", "status.admin_domain": "Obre la interfรญcie de moderaciรณ per a @{domain}", "status.admin_status": "Obre aquest tut a la interfรญcie de moderaciรณ", diff --git a/app/javascript/mastodon/locales/ckb.json b/app/javascript/mastodon/locales/ckb.json index c3c365b3a1..3ebf9391d2 100644 --- a/app/javascript/mastodon/locales/ckb.json +++ b/app/javascript/mastodon/locales/ckb.json @@ -35,9 +35,7 @@ "account.follow_back": "ู†ฺต†ูˆ ุจฺฉ•ู†•ูˆ•", "account.followers": "ุดูˆŽู†ฺฉ•ูˆุชูˆูˆุงู†", "account.followers.empty": "ฺฉ•ุณŽฺฉ ุดูˆŽู† ุฆ•ู… ุจ•ฺฉุงุฑู‡Žู†•ุฑ• ู†•ฺฉ•ูˆุชูˆูˆ•", - "account.followers_counter": "{count, plural, one {{counter} ุดูˆŽู†ฺฉ•ูˆุชูˆูˆ} other {{counter} ุดูˆŽู†ฺฉ•ูˆุชูˆูˆ}}", "account.following": "ุจ•ุฏูˆุงุฏุง", - "account.following_counter": "{count, plural, one {{counter} ุดูˆŽู†ฺฉ•ูˆุชูˆูˆ} other {{counter} ุดูˆŽู†ฺฉ•ูˆุชูˆูˆ}}", "account.follows.empty": "ุฆ•ู… ุจ•ฺฉุงุฑู‡Žู†•ุฑ• ุชุง ุฆŽุณุชุง ุดูˆŽู† ฺฉ•ุณ ู†•ฺฉ•ูˆุชูˆูˆ•.", "account.go_to_profile": "ุจฺ•† ุจ† ูพฺ•†ูุงŒู„Œ", "account.hide_reblogs": "ุฏุงุดุงุฑุฏู†Œ ุจูˆูˆุณุช•ฺฉุงู† ู„• @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} ุฏุงูˆุงŒ ฺฉุฑุฏูˆูˆ• ุดูˆŽู†ุช ุจฺฉ•ูˆŽุช", "account.share": "ูพุฑ†ูุงŒู„Œ @{name} ู‡ุงูˆุจ•ุด ุจฺฉ•", "account.show_reblogs": "ูพŒุดุงู†ุฏุงู†Œ ุจ•ุฑุฒฺฉุฑุฏู†•ูˆ•ฺฉุงู† ู„• @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}", "account.unblock": "@{name} ู„ุงุจุจ•", "account.unblock_domain": "ฺฉุฑุฏู†•ูˆ•Œ ุฏ†ู…•Œู†Œ {domain}", "account.unblock_short": "ู„ุงุจุฑุฏู†Œ ุจ•ุฑุจ•ุณุช", @@ -533,8 +530,6 @@ "server_banner.about_active_users": "ุฆ•ูˆ ฺฉ•ุณุงู†•Œ ู„• ู…ุงูˆ•Œ ูฃู  ฺ•†ฺ˜Œ ฺ•ุงุจุฑุฏูˆูˆุฏุง ุฆ•ู… ุณŽุฑฺค•ุฑ• ุจ•ฺฉุงุฑุฏ•ู‡Žู†ู† (ุจ•ฺฉุงุฑู‡Žู†•ุฑุงู†Œ ฺ†ุงู„ุงฺฉ ู…ุงู†ฺฏุงู†•)", "server_banner.active_users": "ุจ•ฺฉุงุฑู‡Žู†•ุฑุงู†Œ ฺ†ุงู„ุงฺฉ", "server_banner.administered_by": "ุจ•ฺ•Žูˆ•ุจุฑุฏู† ู„•ู„ุงŒ•ู†:", - "server_banner.introduction": "{domain} ุจ•ุดŽฺฉ• ู„•ูˆ ุช†ฺ•• ฺฉ†ู…•ฺตุงŒ•ุชŒŒ• ู„ุงู…•ุฑฺฉ•ุฒŒŒ•Œ ฺฉ• ู„•ู„ุงŒ•ู† {mastodon}•ูˆ• ุจ•ู‡Žุฒ ุฏ•ฺฉุฑŽุช.", - "server_banner.learn_more": "ุฒŒุงุชุฑ ูŽุฑุจู‡", "server_banner.server_stats": "ุฏ†ุฎŒ ฺ•ุงฺ˜•ฺฉุงุฑ:", "sign_in_banner.create_account": "ู‡•ฺ˜ู…ุงุฑ ุฏุฑูˆุณุชุจฺฉ•", "sign_in_banner.sign_in": "ุจฺ†† ฺ˜ูˆูˆุฑ•ูˆ•", diff --git a/app/javascript/mastodon/locales/co.json b/app/javascript/mastodon/locales/co.json index be4cce2692..78f8e6fd78 100644 --- a/app/javascript/mastodon/locales/co.json +++ b/app/javascript/mastodon/locales/co.json @@ -16,8 +16,6 @@ "account.follow": "Siguitร ", "account.followers": "Abbunati", "account.followers.empty": "Nisunu hรจ abbunatu ร  st'utilizatore.", - "account.followers_counter": "{count, plural, one {{counter} Abbunatu} other {{counter} Abbunati}}", - "account.following_counter": "{count, plural, one {{counter} Abbunamentu} other {{counter} Abbunamenti}}", "account.follows.empty": "St'utilizatore รนn seguita nisunu.", "account.hide_reblogs": "Piattร  spartere da @{name}", "account.link_verified_on": "A prupietร  di stu ligame hรจ stata verificata u {date}", @@ -32,7 +30,6 @@ "account.requested": "In attesa d'apprubazione. Cliccate per annullร  a dumanda", "account.share": "Sparte u prufile di @{name}", "account.show_reblogs": "Vede spartere da @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Statutu} other {{counter} Statuti}}", "account.unblock": "Sbluccร  @{name}", "account.unblock_domain": "ร™n piattร  piรน {domain}", "account.unendorse": "ร™n fร  figurร  nant'ร  u prufilu", diff --git a/app/javascript/mastodon/locales/cs.json b/app/javascript/mastodon/locales/cs.json index 2fc01f3acc..12de5d5ecd 100644 --- a/app/javascript/mastodon/locales/cs.json +++ b/app/javascript/mastodon/locales/cs.json @@ -20,7 +20,7 @@ "account.block_short": "Zablokovat", "account.blocked": "Blokovanรฝ", "account.browse_more_on_origin_server": "Vรญce na pลฏvodnรญm profilu", - "account.cancel_follow_request": "Zruลกit ลพรกdost o sledovรกnรญ", + "account.cancel_follow_request": "Zruลกit sledovรกnรญ", "account.copy": "Kopรญrovat odkaz na profil", "account.direct": "Soukromฤ› zmรญnit @{name}", "account.disable_notifications": "Pล™estat mฤ› upozorลˆovat, kdyลพ @{name} zveล™ejnรญ pล™รญspฤ›vek", @@ -35,9 +35,9 @@ "account.follow_back": "Takรฉ sledovat", "account.followers": "Sledujรญcรญ", "account.followers.empty": "Tohoto uลพivatele zatรญm nikdo nesleduje.", - "account.followers_counter": "{count, plural, one {{counter} Sledujรญcรญ} few {{counter} Sledujรญcรญ} many {{counter} Sledujรญcรญch} other {{counter} Sledujรญcรญch}}", + "account.followers_counter": "{count, plural, one {{counter} sledujรญcรญ} few {{counter} sledujรญcรญ} many {{counter} sledujรญcรญch} other {{counter} sledujรญcรญch}}", "account.following": "Sledujete", - "account.following_counter": "{count, plural, one {{counter} Sledovanรฝ} few {{counter} Sledovanรญ} many {{counter} Sledovanรฝch} other {{counter} Sledovanรฝch}}", + "account.following_counter": "{count, plural, one {{counter} sledovanรฝ} few {{counter} sledovanรญ} many {{counter} sledovanรฝch} other {{counter} sledovanรฝch}}", "account.follows.empty": "Tento uลพivatel zatรญm nikoho nesleduje.", "account.go_to_profile": "Pล™ejรญt na profil", "account.hide_reblogs": "Skrรฝt boosty od @{name}", @@ -63,7 +63,7 @@ "account.requested_follow": "{name} tฤ› poลพรกdal o sledovรกnรญ", "account.share": "Sdรญlet profil @{name}", "account.show_reblogs": "Zobrazit boosty od @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Pล™รญspฤ›vek} few {{counter} Pล™รญspฤ›vky} many {{counter} Pล™รญspฤ›vkลฏ} other {{counter} Pล™รญspฤ›vkลฏ}}", + "account.statuses_counter": "{count, plural, one {{counter} pล™รญspฤ›vek} few {{counter} pล™รญspฤ›vky} many {{counter} pล™รญspฤ›vkลฏ} other {{counter} pล™รญspฤ›vkลฏ}}", "account.unblock": "Odblokovat @{name}", "account.unblock_domain": "Odblokovat domรฉnu {domain}", "account.unblock_short": "Odblokovat", @@ -78,9 +78,9 @@ "admin.dashboard.retention.average": "Prลฏmฤ›r", "admin.dashboard.retention.cohort": "Mฤ›sรญc registrace", "admin.dashboard.retention.cohort_size": "Novรญ uลพivatelรฉ", - "admin.impact_report.instance_accounts": "Profily รบฤtลฏ, kterรฉ by odstranily", - "admin.impact_report.instance_followers": "Sledovatelรฉ, o kterรฉ by naลกi uลพivatelรฉ pล™iลกli", - "admin.impact_report.instance_follows": "Nรกsledovnรญci jejich uลพivatelรฉ by ztratili", + "admin.impact_report.instance_accounts": "Profily รบฤtลฏ, kterรฉ by byli odstanฤ›ny", + "admin.impact_report.instance_followers": "Sledujรญcรญ, o kterรฉ by naลกi uลพivatelรฉ pล™iลกli", + "admin.impact_report.instance_follows": "Sledujรญcรญ, o kterรฉ by naลกi uลพivatelรฉ pล™iลกli", "admin.impact_report.title": "Shrnutรญ dopadu", "alert.rate_limited.message": "Zkuste to prosรญm znovu po {retry_time, time, medium}.", "alert.rate_limited.title": "Spojenรญ omezena", @@ -89,7 +89,7 @@ "announcement.announcement": "Oznรกmenรญ", "attachments_list.unprocessed": "(nezpracovรกno)", "audio.hide": "Skrรฝt zvuk", - "block_modal.remote_users_caveat": "Poลพรกdรกme server {domain}, aby respektoval vaลกe rozhodnutรญ. รšplnรฉ dodrลพovรกnรญ nastavenรญ vลกak nenรญ zaruฤeno, protoลพe nฤ›kterรฉ servery mohou ล™eลกit blokovรกnรญ rลฏznฤ›. Veล™ejnรฉ pล™รญspฤ›vky mohou bรฝt stรกle viditelnรฉ pro nepล™ihlรกลกenรฉ uลพivatele.", + "block_modal.remote_users_caveat": "Poลพรกdรกme server {domain}, aby respektoval vaลกe rozhodnutรญ. รšplnรฉ dodrลพovรกnรญ nastavenรญ vลกak nenรญ zaruฤeno, protoลพe nฤ›kterรฉ servery mohou ล™eลกit blokovรกnรญ rลฏznฤ›. Veล™ejnรฉ pล™รญspฤ›vky mohou stรกle bรฝt viditelnรฉ pro nepล™ihlรกลกenรฉ uลพivatele.", "block_modal.show_less": "Zobrazit mรฉnฤ›", "block_modal.show_more": "Zobrazit vรญce", "block_modal.they_cant_mention": "Nemลฏลพe vรกs zmiลˆovat ani sledovat.", @@ -197,7 +197,7 @@ "copy_icon_button.copied": "Zkopรญrovรกno do schrรกnky", "copypaste.copied": "Zkopรญrovรกno", "copypaste.copy_to_clipboard": "Zkopรญrovat do schrรกnky", - "directory.federated": "Ze znรกmรฉho fedivesmรญru", + "directory.federated": "Ze znรกmรฉho fediversu", "directory.local": "Pouze z {domain}", "directory.new_arrivals": "Novฤ› pล™รญchozรญ", "directory.recently_active": "Nedรกvno aktivnรญ", @@ -213,7 +213,7 @@ "domain_block_modal.block_account_instead": "Radฤ›ji blokovat @{name}", "domain_block_modal.they_can_interact_with_old_posts": "Lidรฉ z tohoto serveru mohou interagovat s vaลกimi starรฝmi pล™รญspฤ›vky.", "domain_block_modal.they_cant_follow": "Nikdo z tohoto serveru vรกs nemลฏลพe sledovat.", - "domain_block_modal.they_wont_know": "Nebude vฤ›dฤ›t, ลพe je zablokovรกn.", + "domain_block_modal.they_wont_know": "Nebude vฤ›dฤ›t, ลพe je zablokovรกn*a.", "domain_block_modal.title": "Blokovat domรฉnu?", "domain_block_modal.you_will_lose_followers": "Vลกichni vaลกi sledujรญcรญ z tohoto serveru budou odstranฤ›ni.", "domain_block_modal.you_wont_see_posts": "Neuvidรญte pล™รญspฤ›vky ani upozornฤ›nรญ od uลพivatelลฏ z tohoto serveru.", @@ -308,6 +308,8 @@ "follow_requests.unlocked_explanation": "Pล™estoลพe vรกลก รบฤet nenรญ uzamฤen, personรกl {domain} usoudil, ลพe byste mohli chtรญt tyto poลพadavky na sledovรกnรญ zkontrolovat ruฤnฤ›.", "follow_suggestions.curated_suggestion": "Vรฝbฤ›r personรกlลฏ", "follow_suggestions.dismiss": "Znovu nezobrazovat", + "follow_suggestions.featured_longer": "Ruฤnฤ› vybrรกno tรฝmem {domain}", + "follow_suggestions.friends_of_friends_longer": "Populรกrnรญ mezi lidmi, kterรฉ sledujete", "follow_suggestions.hints.featured": "Tento profil byl ruฤnฤ› vybrรกn tรฝmem {domain}.", "follow_suggestions.hints.friends_of_friends": "Tento profil je populรกrnรญ mezi lidmi, kterรฉ sledujete.", "follow_suggestions.hints.most_followed": "Tento profil je jednรญm z nejvรญce sledovanรฝch na {domain}.", @@ -315,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Tento profil je podobnรฝ profilลฏm, kterรฉ jste nedรกvno sledovali.", "follow_suggestions.personalized_suggestion": "Pล™izpลฏsobenรฝ nรกvrh", "follow_suggestions.popular_suggestion": "Populรกrnรญ nรกvrh", + "follow_suggestions.popular_suggestion_longer": "Populรกrnรญ na {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Podobnรฉ profilลฏm, kterรฉ jste nedรกvno sledovali", "follow_suggestions.view_all": "Zobrazit vลกe", "follow_suggestions.who_to_follow": "Koho sledovat", "followed_tags": "Sledovanรฉ hashtagy", @@ -337,7 +341,7 @@ "hashtag.column_settings.tag_mode.any": "Jakรฝkoliv z tฤ›chto", "hashtag.column_settings.tag_mode.none": "ลฝรกdnรฝ z tฤ›chto", "hashtag.column_settings.tag_toggle": "Zahrnout v tomto sloupci dalลกรญ ลกtรญtky", - "hashtag.counter_by_accounts": "{count, plural, one {{counter} รบฤastnรญk} few {{counter} รบฤastnรญci} other {{counter} รบฤastnรญkลฏ}}", + "hashtag.counter_by_accounts": "{count, plural, one {{counter} รบฤastnรญk*ice} few {{counter} รบฤastnรญci} other {{counter} รบฤastnรญkลฏ}}", "hashtag.counter_by_uses": "{count, plural, one {{counter} pล™รญspฤ›vek} few {{counter} pล™รญspฤ›vky} other {{counter} pล™รญspฤ›vkลฏ}}", "hashtag.counter_by_uses_today": "Dnes {count, plural, one {{counter} pล™รญspฤ›vek} few {{counter} pล™รญspฤ›vky} other {{counter} pล™รญspฤ›vkลฏ}}", "hashtag.follow": "Sledovat hashtag", @@ -410,6 +414,8 @@ "limited_account_hint.action": "Pล™esto profil zobrazit", "limited_account_hint.title": "Tento profil byl skryt moderรกtory {domain}.", "link_preview.author": "Podle {name}", + "link_preview.more_from_author": "Vรญce od {name}", + "link_preview.shares": "{count, plural, one {{counter} pล™รญspฤ›vek} few {{counter} pล™รญspฤ›vky} many {{counter} pล™รญspฤ›vkลฏ} other {{counter} pล™รญspฤ›vkลฏ}}", "lists.account.add": "Pล™idat do seznamu", "lists.account.remove": "Odebrat ze seznamu", "lists.delete": "Smazat seznam", @@ -434,7 +440,7 @@ "mute_modal.show_options": "Zobrazit moลพnosti", "mute_modal.they_can_mention_and_follow": "Mohou vรกs zmรญnit a sledovat, ale neuvidรญte je.", "mute_modal.they_wont_know": "Nebudou vฤ›dฤ›t, ลพe byli skryti.", - "mute_modal.title": "Ztlumit uลพivatele?", + "mute_modal.title": "Ztlumit uลพivatele*ku?", "mute_modal.you_wont_see_mentions": "Neuvidรญte pล™รญspฤ›vky, kterรฉ je zmiลˆujรญ.", "mute_modal.you_wont_see_posts": "Stรกle budou moci vidฤ›t vaลกe pล™รญspฤ›vky, ale vy jejich neuvidรญte.", "navigation_bar.about": "O aplikaci", @@ -469,6 +475,15 @@ "notification.follow": "Uลพivatel {name} vรกs zaฤal sledovat", "notification.follow_request": "Uลพivatel {name} poลพรกdal o povolenรญ vรกs sledovat", "notification.mention": "Uลพivatel {name} vรกs zmรญnil", + "notification.moderation-warning.learn_more": "Zjistit vรญce", + "notification.moderation_warning": "Obdrลพeli jste moderaฤnรญ varovรกnรญ", + "notification.moderation_warning.action_delete_statuses": "Nฤ›kterรฉ z vaลกich pล™รญspฤ›vkลฏ byly odstranฤ›ny.", + "notification.moderation_warning.action_disable": "Vรกลก รบฤet je zablokovรกn.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Nฤ›kterรฉ z vaลกich pล™รญspฤ›vkลฏ byly oznaฤeny jako citlivรฉ.", + "notification.moderation_warning.action_none": "Vรกลก รบฤet obdrลพel moderaฤnรญ varovรกnรญ.", + "notification.moderation_warning.action_sensitive": "Vaลกe pล™รญspฤ›vky budou od nynฤ›jลกka oznaฤeny jako citlivรฉ.", + "notification.moderation_warning.action_silence": "Vรกลก รบฤet byl omezen.", + "notification.moderation_warning.action_suspend": "Vรกลก รบฤet byl pozastaven.", "notification.own_poll": "Vaลกe anketa skonฤila", "notification.poll": "Anketa, ve kterรฉ jste hlasovali, skonฤila", "notification.reblog": "Uลพivatel {name} boostnul vรกลก pล™รญspฤ›vek", @@ -551,8 +566,8 @@ "onboarding.share.message": "Jsem {username} na #Mastodonu! Pojฤ mฤ› sledovat na {url}", "onboarding.share.next_steps": "Moลพnรฉ dalลกรญ kroky:", "onboarding.share.title": "Sdรญlejte svลฏj profil", - "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:", - "onboarding.start.skip": "Want to skip right ahead?", + "onboarding.start.lead": "Nynรญ jste souฤรกstรญ Mastodonu, unikรกtnรญ sociรกlnรญ sรญtฤ›, kde vy - ne algoritmus - vytvรกล™รญ vaลกe vlastnรญ proลพitky. Zaฤnฤ›te na tรฉto novรฉ sociรกlnรญ platformฤ›:", + "onboarding.start.skip": "Nepotล™ebujete pomoci zaฤรญt?", "onboarding.start.title": "Dokรกzali jste to!", "onboarding.steps.follow_people.body": "Mastodon je o sledovรกnรญ zajimavรฝch lidรญ.", "onboarding.steps.follow_people.title": "Pล™ispลฏsobit vlastnรญ domovskรฝ kanรกl", @@ -566,7 +581,7 @@ "onboarding.tips.accounts_from_other_servers": "Vรญte, ลพe? Protoลพe je Mastodon decentralizovanรฝ, nฤ›kterรฉ profily, na kterรฉ narazรญte, budou hostovรกny na jinรฝch serverech, neลพ je ten vรกลก. A pล™esto s nimi mลฏลพete bezproblรฉmovฤ› komunikovat! Jejich server se nachรกzรญ v druhรฉ polovinฤ› uลพivatelskรฉho jmรฉna!", "onboarding.tips.migration": "Vรญte, ลพe? Pokud mรกte pocit, ลพe {domain} pro vรกs v budoucnu nenรญ vhodnou volbou, mลฏลพete se pล™esunout na jinรฝ Mastodon server, aniลพ byste pล™iลกli o svรฉ sledujรญcรญ. Mลฏลพete dokonce hostovat svลฏj vlastnรญ server!", "onboarding.tips.verification": "Vรญte, ลพe? Svลฏj รบฤet mลฏลพete ovฤ›ล™it tak, ลพe na svรฉ webovรฉ strรกnky umรญstรญte odkaz na vรกลก Mastodon profil a odkaz na strรกnku pล™idรกte do svรฉho profilu. Nejsou k tomu potล™eba ลพรกdnรฉ poplatky ani dokumenty!", - "password_confirmation.exceeds_maxlength": "Potvrzenรญ hesla pล™ekraฤuje maximรกlnรญ dรฉlku hesla", + "password_confirmation.exceeds_maxlength": "Potvrzenรญ hesla pล™ekraฤuje maximรกlnรญ povolenou dรฉlku hesla", "password_confirmation.mismatching": "Zadanรก hesla se neshodujรญ", "picture_in_picture.restore": "Vrรกtit zpฤ›t", "poll.closed": "Uzavล™eno", @@ -650,7 +665,7 @@ "report.unfollow": "Pล™estat sledovat @{name}", "report.unfollow_explanation": "Tento รบฤet sledujete. Abyste uลพ nevidฤ›li jeho pล™รญspฤ›vky ve svรฉ domovskรฉ ฤasovรฉ ose, pล™estaลˆte jej sledovat.", "report_notification.attached_statuses": "{count, plural, one {{count} pล™ipojenรฝ pล™รญspฤ›vek} few {{count} pล™ipojenรฉ pล™รญspฤ›vky} many {{count} pล™ipojenรฝch pล™รญspฤ›vkลฏ} other {{count} pล™ipojenรฝch pล™รญspฤ›vkลฏ}}", - "report_notification.categories.legal": "Zรกkonnรฉ", + "report_notification.categories.legal": "Prรกvnรญ ustanovenรญ", "report_notification.categories.other": "Ostatnรญ", "report_notification.categories.spam": "Spam", "report_notification.categories.violation": "Poruลกenรญ pravidla", @@ -681,13 +696,13 @@ "server_banner.about_active_users": "Lidรฉ pouลพรญvajรญcรญ tento server bฤ›hem poslednรญch 30 dnรญ (mฤ›sรญฤnรญ aktivnรญ uลพivatelรฉ)", "server_banner.active_users": "aktivnรญ uลพivatelรฉ", "server_banner.administered_by": "Spravovรกno:", - "server_banner.introduction": "{domain} je souฤรกstรญ decentralizovanรฉ sociรกlnรญ sรญtฤ› bฤ›ลพรญcรญ na {mastodon}.", - "server_banner.learn_more": "Zjistit vรญce", + "server_banner.is_one_of_many": "{domain} je jednรญm z mnoha Mastodon serverลฏ, kterรฉ mลฏลพete pouลพรญt k รบฤasti na fediversu.", "server_banner.server_stats": "Statistiky serveru:", "sign_in_banner.create_account": "Vytvoล™it รบฤet", + "sign_in_banner.follow_anyone": "Sledujte kohokoli napล™รญฤ fediversem a uvidรญte vลกe v chronologickรฉm poล™adรญ. Bez algoritmลฏ, reklam a clickbaitu.", + "sign_in_banner.mastodon_is": "Mastodon je ten nejlepลกรญ zpลฏsob, jak udrลพet krok s tรญm, co se prรกvฤ› dฤ›je.", "sign_in_banner.sign_in": "Pล™ihlรกsit se", "sign_in_banner.sso_redirect": "Pล™ihlรกลกenรญ nebo Registrace", - "sign_in_banner.text": "Pล™ihlaste se pro sledovรกnรญ profilลฏ nebo hashtagลฏ, oblรญbenรญ, sdรญlenรญ a odpovรญdรกnรญ na pล™รญspฤ›vky. Svลฏj รบฤet mลฏลพete takรฉ pouลพรญvat k interagovรกnรญ i na jinรฉm serveru.", "status.admin_account": "Otevล™รญt moderรกtorskรฉ rozhranรญ pro @{name}", "status.admin_domain": "Otevล™รญt moderรกtorskรฉ rozhranรญ pro {domain}", "status.admin_status": "Otevล™รญt tento pล™รญspฤ›vek v moderรกtorskรฉm rozhranรญ", diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json index d2731b6294..1c7e61832c 100644 --- a/app/javascript/mastodon/locales/cy.json +++ b/app/javascript/mastodon/locales/cy.json @@ -35,9 +35,7 @@ "account.follow_back": "Dilyn yn รดl", "account.followers": "Dilynwyr", "account.followers.empty": "Does neb yn dilyn y defnyddiwr hwn eto.", - "account.followers_counter": "{count, plural, one {Dilynwr: {counter}} other {Dilynwyr: {counter}}}", "account.following": "Yn dilyn", - "account.following_counter": "{count, plural, one {Yn dilyn: {counter}} other {Yn dilyn: {counter}}}", "account.follows.empty": "Nid yw'r defnyddiwr hwn yn dilyn unrhyw un eto.", "account.go_to_profile": "Mynd i'r proffil", "account.hide_reblogs": "Cuddio hybiau gan @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "Mae {name} wedi gwneud cais i'ch dilyn", "account.share": "Rhannwch broffil @{name}", "account.show_reblogs": "Dangos hybiau gan @{name}", - "account.statuses_counter": "{count, plural, one {Postiad: {counter}} other {Postiad: {counter}}}", "account.unblock": "Dadflocio @{name}", "account.unblock_domain": "Dadflocio parth {domain}", "account.unblock_short": "Dadflocio", @@ -89,6 +86,14 @@ "announcement.announcement": "Cyhoeddiad", "attachments_list.unprocessed": "(heb eu prosesu)", "audio.hide": "Cuddio sain", + "block_modal.remote_users_caveat": "Byddwn yn gofyn i'r gweinydd {domain} barchu eich penderfyniad. Fodd bynnag, nid yw cydymffurfiad wedi'i warantu gan y gall rhai gweinyddwyr drin rhwystro mewn ffyrdd gwahanol. Mae'n bosibl y bydd postiadau cyhoeddus yn dal i fod yn weladwy i ddefnyddwyr nad ydynt wedi mewngofnodi.", + "block_modal.show_less": "Dangos llai", + "block_modal.show_more": "Dangos mwy", + "block_modal.they_cant_mention": "Nid ydynt yn gallu eich crybwyll na'ch dilyn.", + "block_modal.they_cant_see_posts": "Nid ydynt yn gallu gweld eich postiadau ac ni fyddwch yn gweld eu rhai hwy.", + "block_modal.they_will_know": "Gallant weld eu bod wedi'u rhwystro.", + "block_modal.title": "Rhwystro defnyddiwr?", + "block_modal.you_wont_see_mentions": "Ni welwch bostiadau sy'n sรดn amdanynt.", "boost_modal.combo": "Mae modd pwyso {combo} er mwyn hepgor hyn tro nesa", "bundle_column_error.copy_stacktrace": "Copรฏo'r adroddiad gwall", "bundle_column_error.error.body": "Nid oedd modd cynhyrchu'r dudalen honno. Gall fod oherwydd gwall yn ein cod neu fater cydnawsedd porwr.", @@ -169,6 +174,7 @@ "confirmations.delete_list.message": "Ydych chi'n siลตr eich bod eisiau dileu'r rhestr hwn am byth?", "confirmations.discard_edit_media.confirm": "Dileu", "confirmations.discard_edit_media.message": "Mae gennych newidiadau heb eu cadw i'r disgrifiad cyfryngau neu'r rhagolwg - eu dileu beth bynnag?", + "confirmations.domain_block.confirm": "Rhwystro gweinydd", "confirmations.domain_block.message": "Ydych chi wir, wir eisiau blocio'r holl {domain}? Fel arfer, mae blocio neu dewi pobl penodol yn broses mwy effeithiol. Fyddwch chi ddim yn gweld cynnwys o'r parth hwnnw mewn ffrydiau cyhoeddus neu yn eich hysbysiadau. Bydd eich dilynwyr o'r parth hwnnw yn cael eu ddileu.", "confirmations.edit.confirm": "Golygu", "confirmations.edit.message": "Bydd golygu nawr yn trosysgrifennu'r neges rydych yn ei ysgrifennu ar hyn o bryd. Ydych chi'n siลตr eich bod eisiau gwneud hyn?", @@ -200,6 +206,27 @@ "dismissable_banner.explore_statuses": "Mae'r rhain yn bostiadau o bob rhan o'r we gymdeithasol sydd ar gynnydd heddiw. Mae postiadau mwy diweddar sydd รข mwy o hybiau a ffefrynu'n cael eu graddio'n uwch.", "dismissable_banner.explore_tags": "Mae'r rhain yn hashnodau sydd ar gynnydd ar y we gymdeithasol heddiw. Mae hashnodau sy'n cael eu defnyddio gan fwy o unigolion gwahanol yn cael eu graddio'n uwch.", "dismissable_banner.public_timeline": "Dyma'r postiadau cyhoeddus diweddaraf gan bobl ar y we gymdeithasol y mae pobl ar {domain} yn eu dilyn.", + "domain_block_modal.block": "Rhwystro gweinydd", + "domain_block_modal.block_account_instead": "Rhwystro @{name} yn lle hynny", + "domain_block_modal.they_can_interact_with_old_posts": "Gall pobl o'r gweinydd hwn ryngweithio รข'ch hen bostiadau.", + "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 rhwystro.", + "domain_block_modal.title": "Rhwystro parth?", + "domain_block_modal.you_will_lose_followers": "Bydd eich holl ddilynwyr o'r gweinydd hwn yn cael eu tynnu.", + "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_like_language": "Mae ActivityPub fel yr iaith y mae Mastodon yn ei siarad รข rhwydweithiau cymdeithasol eraill.", + "domain_pill.server": "Gweinydd", + "domain_pill.their_handle": "Eu handlen:", + "domain_pill.their_server": "Eu cartref digidol, lle mae eu holl negeseuon yn byw.", + "domain_pill.their_username": "Eu dynodwr unigryw ar eu gweinydd. Mae'n bosibl dod o hyd i ddefnyddwyr gyda'r un enw defnyddiwr ar wahanol weinyddion.", + "domain_pill.username": "Enw Defnyddiwr", + "domain_pill.whats_in_a_handle": "Beth sydd mewn handlen?", + "domain_pill.who_they_are": "Gan fod handlen yn dweud pwy yw rhywun a ble maen nhw, gallwch chi ryngweithio รข phobl ar draws gwe gymdeithasol .", + "domain_pill.who_you_are": "Oherwydd bod eich handlen yn dweud pwy ydych chi a ble rydych chi, gall pobl ryngweithio รข chi ar draws gwe gymdeithasol .", + "domain_pill.your_handle": "Eich handlen:", + "domain_pill.your_server": "Eich cartref digidol, lle mae'ch holl bostiadau'n byw. Ddim yn hoffi'r un hon? Trosglwyddwch weinyddion ar unrhyw adeg a dewch รข'ch dilynwyr hefyd.", + "domain_pill.your_username": "Eich dynodwr unigryw ar y gweinydd hwn. Mae'n bosibl dod o hyd i ddefnyddwyr gyda'r un enw defnyddiwr ar wahanol weinyddion.", "embed.instructions": "Gosodwch y post hwn ar eich gwefan drwy gopรฏo'r cรดd isod.", "embed.preview": "Dyma sut olwg fydd arno:", "emoji_button.activity": "Gweithgarwch", @@ -267,6 +294,8 @@ "filter_modal.select_filter.subtitle": "Defnyddiwch gategori sy'n bodoli eisoes neu crรซu un newydd", "filter_modal.select_filter.title": "Hidlo'r postiad hwn", "filter_modal.title.status": "Hidlo postiad", + "filtered_notifications_banner.mentions": "{count, plural, one {crybwylliad} other {crybwylliad}}", + "filtered_notifications_banner.pending_requests": "Hysbysiadau gan {count, plural, =0 {neb} one {un person} other {# person}} efallai y gwyddoch amdanyn nhw", "filtered_notifications_banner.title": "Hysbysiadau wedi'u hidlo", "firehose.all": "Popeth", "firehose.local": "Gweinydd hwn", @@ -276,6 +305,8 @@ "follow_requests.unlocked_explanation": "Er nid yw eich cyfrif wedi'i gloi, roedd y staff {domain} yn meddwl efallai hoffech adolygu ceisiadau dilyn o'r cyfrifau rhain wrth law.", "follow_suggestions.curated_suggestion": "Dewis staff", "follow_suggestions.dismiss": "Peidio รข dangos hwn eto", + "follow_suggestions.featured_longer": "Wedi'i ddewis รข llaw gan dรฎm {domain}", + "follow_suggestions.friends_of_friends_longer": "Yn boblogaidd ymhlith y bobl rydych chi'n eu dilyn", "follow_suggestions.hints.featured": "Mae'r proffil hwn wedi'i ddewis yn arbennig gan dรฎm {domain}.", "follow_suggestions.hints.friends_of_friends": "Mae'r proffil hwn yn boblogaidd ymhlith y bobl rydych chi'n eu dilyn.", "follow_suggestions.hints.most_followed": "Mae'r proffil hwn yn un o'r rhai sy'n cael ei ddilyn fwyaf ar {domain}.", @@ -283,6 +314,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Mae'r proffil hwn yn debyg i'r proffiliau rydych chi wedi'u dilyn yn fwyaf diweddar.", "follow_suggestions.personalized_suggestion": "Awgrym personol", "follow_suggestions.popular_suggestion": "Awgrym poblogaidd", + "follow_suggestions.popular_suggestion_longer": "Yn boblogaidd ar {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Yn debyg i broffiliau y gwnaethoch chi eu dilyn yn ddiweddar", "follow_suggestions.view_all": "Gweld y cyfan", "follow_suggestions.who_to_follow": "Pwy i ddilyn", "followed_tags": "Hashnodau rydych yn eu dilyn", @@ -396,6 +429,15 @@ "loading_indicator.label": "Yn llwythoโ€ฆ", "media_gallery.toggle_visible": "{number, plural, one {Cuddio delwedd} other {Cuddio delwedd}}", "moved_to_account_banner.text": "Ar hyn y bryd, mae eich cyfrif {disabledAccount} wedi ei analluogi am i chi symud i {movedToAccount}.", + "mute_modal.hide_from_notifications": "Cuddio rhag hysbysiadau", + "mute_modal.hide_options": "Cuddio'r dewis", + "mute_modal.indefinite": "Nes i mi eu dad-dewi", + "mute_modal.show_options": "Dangos y dewis", + "mute_modal.they_can_mention_and_follow": "Gallan nhw eich crybwyll a'ch dilyn, ond fyddwch chi ddim yn eu gweld.", + "mute_modal.they_wont_know": "Fyddan nhw ddim yn gwybod eu bod wedi cael eu tawelu.", + "mute_modal.title": "Tewi defnyddiwr?", + "mute_modal.you_wont_see_mentions": "Welwch chi ddim postiadau sy'n sรดn amdanyn nhw.", + "mute_modal.you_wont_see_posts": "Gallan nhw weld eich postiadau o hyd, ond fyddwch chi ddim yn gweld eu rhai hwy.", "navigation_bar.about": "Ynghylch", "navigation_bar.advanced_interface": "Agor mewn rhyngwyneb gwe uwch", "navigation_bar.blocks": "Defnyddwyr wedi eu blocio", @@ -428,9 +470,23 @@ "notification.follow": "Dilynodd {name} chi", "notification.follow_request": "Mae {name} wedi gwneud cais i'ch dilyn", "notification.mention": "Crybwyllodd {name} amdanoch chi", + "notification.moderation-warning.learn_more": "Dysgu mwy", + "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_disable": "Mae eich cyfrif wedi'i analluogi.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Mae rhai o'ch postiadau wedi'u marcio'n sensitif.", + "notification.moderation_warning.action_none": "Mae eich cyfrif wedi derbyn rhybudd cymedroli.", + "notification.moderation_warning.action_sensitive": "Bydd eich postiadau'n cael eu marcio'n sensitif o hyn ymlaen.", + "notification.moderation_warning.action_silence": "Mae eich cyfrif wedi'i gyfyngu.", + "notification.moderation_warning.action_suspend": "Mae eich cyfrif wedi'i hatal.", "notification.own_poll": "Mae eich pleidlais wedi dod i ben", "notification.poll": "Mae pleidlais rydych wedi pleidleisio ynddi wedi dod i ben", "notification.reblog": "Hybodd {name} eich post", + "notification.relationships_severance_event": "Wedi colli cysylltiad รข {name}", + "notification.relationships_severance_event.account_suspension": "Mae gweinyddwr o {from} wedi atal {target}, sy'n golygu na allwch dderbyn diweddariadau ganddynt mwyach na rhyngweithio รข nhw.", + "notification.relationships_severance_event.domain_block": "Mae gweinyddwr o {from} wedi rhwystro {target}, gan gynnwys {followersCount} o'ch dilynwyr a {followingCount, plural, one {# cyfrif} other {# cyfrif}} arall rydych chi'n ei ddilyn.", + "notification.relationships_severance_event.learn_more": "Dysgu mwy", + "notification.relationships_severance_event.user_domain_block": "Rydych wedi rhwystro {target}, gan ddileu {followersCount} o'ch dilynwyr a {followingCount, plural, one {# cyfrif} other {#cyfrifon}} arall rydych yn ei ddilyn.", "notification.status": "{name} newydd ei bostio", "notification.update": "Golygodd {name} bostiad", "notification_requests.accept": "Derbyn", @@ -443,6 +499,8 @@ "notifications.column_settings.admin.sign_up": "Cofrestriadau newydd:", "notifications.column_settings.alert": "Hysbysiadau bwrdd gwaith", "notifications.column_settings.favourite": "Ffefrynnau:", + "notifications.column_settings.filter_bar.advanced": "Dangos pob categori", + "notifications.column_settings.filter_bar.category": "Bar hidlo cyflym", "notifications.column_settings.follow": "Dilynwyr newydd:", "notifications.column_settings.follow_request": "Ceisiadau dilyn newydd:", "notifications.column_settings.mention": "Crybwylliadau:", @@ -633,13 +691,10 @@ "server_banner.about_active_users": "Pobl sy'n defnyddio'r gweinydd hwn yn ystod y 30 diwrnod diwethaf (Defnyddwyr Gweithredol Misol)", "server_banner.active_users": "defnyddwyr gweithredol", "server_banner.administered_by": "Gweinyddir gan:", - "server_banner.introduction": "Mae {domain} yn rhan o'r rhwydwaith cymdeithasol datganoledig sy'n cael ei bweru gan {mastodon}.", - "server_banner.learn_more": "Dysgu mwy", "server_banner.server_stats": "Ystadegau'r gweinydd:", "sign_in_banner.create_account": "Creu cyfrif", "sign_in_banner.sign_in": "Mewngofnodi", "sign_in_banner.sso_redirect": "Mewngofnodi neu Gofrestru", - "sign_in_banner.text": "Mewngofnodwch i ddilyn proffiliau neu hashnodau, ffefrynnau, rhannu ac ymateb i bostiadau. Gallwch hefyd ryngweithio o'ch cyfrif ar weinyddion gwahanol.", "status.admin_account": "Agor rhyngwyneb cymedroli ar gyfer @{name}", "status.admin_domain": "Agor rhyngwyneb cymedroli {domain}", "status.admin_status": "Agor y postiad hwn yn y rhyngwyneb cymedroli", @@ -653,9 +708,11 @@ "status.direct": "Crybwyll yn breifat @{name}", "status.direct_indicator": "Crybwyll preifat", "status.edit": "Golygu", + "status.edited": "Golygwyd ddiwethaf {date}", "status.edited_x_times": "Golygwyd {count, plural, one {count} two {count} other {{count} gwaith}}", "status.embed": "Mewnblannu", "status.favourite": "Hoffi", + "status.favourites": "{count, plural, one {ffefryn} other {ffefryn}}", "status.filter": "Hidlo'r postiad hwn", "status.filtered": "Wedi'i hidlo", "status.hide": "Cuddio'r postiad", @@ -676,6 +733,7 @@ "status.reblog": "Hybu", "status.reblog_private": "Hybu i'r gynulleidfa wreiddiol", "status.reblogged_by": "Hybodd {name}", + "status.reblogs": "{count, plural, one {hwb} other {hwb}}", "status.reblogs.empty": "Does neb wedi hybio'r post yma eto. Pan y bydd rhywun yn gwneud, byddent yn ymddangos yma.", "status.redraft": "Dileu ac ailddrafftio", "status.remove_bookmark": "Tynnu nod tudalen", diff --git a/app/javascript/mastodon/locales/da.json b/app/javascript/mastodon/locales/da.json index a7c8a049ea..d8c178d295 100644 --- a/app/javascript/mastodon/locales/da.json +++ b/app/javascript/mastodon/locales/da.json @@ -35,9 +35,8 @@ "account.follow_back": "Fรธlg tilbage", "account.followers": "Fรธlgere", "account.followers.empty": "Ingen fรธlger denne bruger endnu.", - "account.followers_counter": "{count, plural, one {{counter} Fรธlger} other {{counter} Fรธlgere}}", + "account.followers_counter": "{count, plural, one {{counter} fรธlger} other {{counter} fรธlgere}}", "account.following": "Fรธlger", - "account.following_counter": "{count, plural, one {{counter} Fรธlges} other {{counter} Fรธlges}}", "account.follows.empty": "Denne bruger fรธlger ikke nogen endnu.", "account.go_to_profile": "Gรฅ til profil", "account.hide_reblogs": "Skjul boosts fra @{name}", @@ -63,7 +62,6 @@ "account.requested_follow": "{name} har anmodet om at fรธlge dig", "account.share": "Del @{name}s profil", "account.show_reblogs": "Vis fremhรฆvelser fra @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Indlรฆg} other {{counter} Indlรฆg}}", "account.unblock": "Afblokรฉr @{name}", "account.unblock_domain": "Afblokรฉr domรฆnet {domain}", "account.unblock_short": "Afblokรฉr", @@ -221,6 +219,8 @@ "domain_pill.activitypub_like_language": "ActivityPub er \"sproget\", Mastodon taler med andre sociale netvรฆrk.", "domain_pill.server": "Server", "domain_pill.their_handle": "Vedkommendes handle:", + "domain_pill.their_server": "Det digitale hjem, hvor alle indlรฆggene findes.", + "domain_pill.their_username": "Entydig identifikator pรฅ denne server. Det er muligt at finde brugere med samme brugernavn pรฅ forskellige servere.", "domain_pill.username": "Brugernavn", "domain_pill.whats_in_a_handle": "Hvad er der i et handle (@brugernavn)?", "domain_pill.who_they_are": "Da et handle fortรฆller, hvem nogen er, og hvor de er, kan man interagere med folk pรฅ tvรฆrs af det sociale net af .", @@ -306,6 +306,8 @@ "follow_requests.unlocked_explanation": "Selvom din konto ikke er lรฅst, synes {domain}-personalet, du mรฅske bรธr gennemgรฅ disse anmodninger manuelt.", "follow_suggestions.curated_suggestion": "Personaleudvalgt", "follow_suggestions.dismiss": "Vis ikke igen", + "follow_suggestions.featured_longer": "Hรฅndplukket af {domain}-teamet", + "follow_suggestions.friends_of_friends_longer": "Populรฆrt blandt personer, som fรธlges", "follow_suggestions.hints.featured": "Denne profil er hรฅndplukket af {domain}-teamet.", "follow_suggestions.hints.friends_of_friends": "Denne profil er populรฆr blandt de personer, som fรธlges.", "follow_suggestions.hints.most_followed": "Denne profil er en af de mest fulgte pรฅ {domain}.", @@ -313,6 +315,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Denne profil svarer til de profiler, som senest er blevet fulgt.", "follow_suggestions.personalized_suggestion": "Personligt forslag", "follow_suggestions.popular_suggestion": "Populรฆrt forslag", + "follow_suggestions.popular_suggestion_longer": "Populรฆrt pรฅ {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Svarende til profiler, som for nylig er fulgt", "follow_suggestions.view_all": "Vis alle", "follow_suggestions.who_to_follow": "Hvem, som skal fรธlges", "followed_tags": "Hashtag, som fรธlges", @@ -408,6 +412,8 @@ "limited_account_hint.action": "Vis profil alligevel", "limited_account_hint.title": "Denne profil er blevet skjult af {domain}-moderatorerne.", "link_preview.author": "Af {name}", + "link_preview.more_from_author": "Mere fra {name}", + "link_preview.shares": "{count, plural, one {{counter} indlรฆg} other {{counter} indlรฆg}}", "lists.account.add": "Fรธj til liste", "lists.account.remove": "Fjern fra liste", "lists.delete": "Slet liste", @@ -467,6 +473,15 @@ "notification.follow": "{name} begyndte at fรธlge dig", "notification.follow_request": "{name} har anmodet om at fรธlge dig", "notification.mention": "{name} nรฆvnte dig", + "notification.moderation-warning.learn_more": "Lรฆs mere", + "notification.moderation_warning": "Du er tildelt en moderationsadvarsel", + "notification.moderation_warning.action_delete_statuses": "Nogle af dine indlรฆg er blevet fjernet.", + "notification.moderation_warning.action_disable": "Din konto er blevet deaktiveret.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Nogle af dine indlรฆg er blevet markeret som sensitive.", + "notification.moderation_warning.action_none": "Din konto er tildelt en moderationsadvarsel.", + "notification.moderation_warning.action_sensitive": "Dine indlรฆg markeres fra nu af som sensitive.", + "notification.moderation_warning.action_silence": "Din konto er blevet begrรฆnset.", + "notification.moderation_warning.action_suspend": "Din konto er suspenderet.", "notification.own_poll": "Din afstemning er afsluttet", "notification.poll": "En afstemning, hvori du stemte, er slut", "notification.reblog": "{name} boostede dit indlรฆg", @@ -679,13 +694,10 @@ "server_banner.about_active_users": "Folk, som brugte denne server de seneste 30 dage (mรฅnedlige aktive brugere)", "server_banner.active_users": "aktive brugere", "server_banner.administered_by": "Hรฅndteres af:", - "server_banner.introduction": "{domain} er en del af det decentraliserede, sociale netvรฆrk drevet af {mastodon}.", - "server_banner.learn_more": "Lรฆs mere", "server_banner.server_stats": "Serverstatstik:", "sign_in_banner.create_account": "Opret konto", "sign_in_banner.sign_in": "Log ind", "sign_in_banner.sso_redirect": "Log ind eller Tilmeld", - "sign_in_banner.text": "Log ind for at fรธlge profiler eller hashtags, markere som favorit, dele og besvare indlรฆg eller interagere fra din konto pรฅ en anden server.", "status.admin_account": "ร…bn modereringsbrugerflade for @{name}", "status.admin_domain": "ร…bn modereringsbrugerflade for {domain}", "status.admin_status": "ร…bn dette indlรฆg i modereringsbrugerfladen", diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json index a1ba25fede..86438757a3 100644 --- a/app/javascript/mastodon/locales/de.json +++ b/app/javascript/mastodon/locales/de.json @@ -308,6 +308,8 @@ "follow_requests.unlocked_explanation": "Auch wenn dein Konto รถffentlich bzw. nicht geschรผtzt ist, haben die Moderator*innen von {domain} gedacht, dass du diesen Follower lieber manuell bestรคtigen solltest.", "follow_suggestions.curated_suggestion": "Vom Server-Team empfohlen", "follow_suggestions.dismiss": "Nicht mehr anzeigen", + "follow_suggestions.featured_longer": "Vom {domain}-Team ausgewรคhlt", + "follow_suggestions.friends_of_friends_longer": "Beliebt bei Leuten, denen du folgst", "follow_suggestions.hints.featured": "Dieses Profil wurde vom {domain}-Team ausgewรคhlt.", "follow_suggestions.hints.friends_of_friends": "Dieses Profil ist bei deinen Followern beliebt.", "follow_suggestions.hints.most_followed": "Dieses Profil ist eines der am meisten gefolgten auf {domain}.", @@ -315,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Dieses Profil รคhnelt den Profilen, denen du in letzter Zeit gefolgt hast.", "follow_suggestions.personalized_suggestion": "Persรถnliche Empfehlung", "follow_suggestions.popular_suggestion": "Beliebte Empfehlung", + "follow_suggestions.popular_suggestion_longer": "Beliebt auf {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "ร„hnlich zu Profilen, denen du seit kurzem folgst", "follow_suggestions.view_all": "Alle anzeigen", "follow_suggestions.who_to_follow": "Empfohlene Profile", "followed_tags": "Gefolgte Hashtags", @@ -327,7 +331,7 @@ "footer.source_code": "Quellcode anzeigen", "footer.status": "Status", "generic.saved": "Gespeichert", - "getting_started.heading": "Auf gehtโ€™s!", + "getting_started.heading": "Auf gehts!", "hashtag.column_header.tag_mode.all": "und {additional}", "hashtag.column_header.tag_mode.any": "oder {additional}", "hashtag.column_header.tag_mode.none": "ohne {additional}", @@ -396,7 +400,7 @@ "keyboard_shortcuts.requests": "Liste der Follower-Anfragen aufrufen", "keyboard_shortcuts.search": "Suchleiste fokussieren", "keyboard_shortcuts.spoilers": "Feld fรผr Inhaltswarnung anzeigen/ausblenden", - "keyboard_shortcuts.start": "โ€žAuf gehtโ€™s!โ€œ รถffnen", + "keyboard_shortcuts.start": "โ€žAuf gehts!โ€œ รถffnen", "keyboard_shortcuts.toggle_hidden": "Beitragstext hinter der Inhaltswarnung anzeigen/ausblenden", "keyboard_shortcuts.toggle_sensitivity": "Medien anzeigen/ausblenden", "keyboard_shortcuts.toot": "Neuen Beitrag erstellen", @@ -410,6 +414,8 @@ "limited_account_hint.action": "Profil trotzdem anzeigen", "limited_account_hint.title": "Dieses Profil wurde von den Moderator*innen von {domain} ausgeblendet.", "link_preview.author": "Von {name}", + "link_preview.more_from_author": "Mehr von {name}", + "link_preview.shares": "{count, plural, one {{counter} Beitrag} other {{counter} Beitrรคge}}", "lists.account.add": "Zur Liste hinzufรผgen", "lists.account.remove": "Von der Liste entfernen", "lists.delete": "Liste lรถschen", @@ -469,6 +475,15 @@ "notification.follow": "{name} folgt dir", "notification.follow_request": "{name} mรถchte dir folgen", "notification.mention": "{name} erwรคhnte dich", + "notification.moderation-warning.learn_more": "Mehr erfahren", + "notification.moderation_warning": "Du wurdest von den Moderator*innen verwarnt", + "notification.moderation_warning.action_delete_statuses": "Einige deiner Beitrรคge sind entfernt worden.", + "notification.moderation_warning.action_disable": "Dein Konto wurde deaktiviert.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Einige deiner Beitrรคge wurden mit einer Inhaltswarnung versehen.", + "notification.moderation_warning.action_none": "Dein Konto ist von den Moderator*innen verwarnt worden.", + "notification.moderation_warning.action_sensitive": "Deine zukรผnftigen Beitrรคge werden mit einer Inhaltswarnung versehen.", + "notification.moderation_warning.action_silence": "Dein Konto wurde eingeschrรคnkt.", + "notification.moderation_warning.action_suspend": "Dein Konto wurde gesperrt.", "notification.own_poll": "Deine Umfrage ist beendet", "notification.poll": "Eine Umfrage, an der du teilgenommen hast, ist beendet", "notification.reblog": "{name} teilte deinen Beitrag", @@ -536,11 +551,11 @@ "onboarding.follows.empty": "Bedauerlicherweise kรถnnen aktuell keine Ergebnisse angezeigt werden. Du kannst die Suche verwenden oder den Reiter โ€žEntdeckenโ€œ auswรคhlen, um neue Leute zum Folgen zu finden โ€“ oder du versuchst es spรคter erneut.", "onboarding.follows.lead": "Deine Startseite ist der primรคre Anlaufpunkt, um Mastodon zu erleben. Je mehr Profilen du folgst, umso aktiver und interessanter wird sie. Damit du direkt loslegen kannst, gibt es hier ein paar Vorschlรคge:", "onboarding.follows.title": "Personalisiere deine Startseite", - "onboarding.profile.discoverable": "Mein Profil auffindbar machen", + "onboarding.profile.discoverable": "Mein Profil darf entdeckt werden", "onboarding.profile.discoverable_hint": "Wenn du entdeckt werden mรถchtest, dann kรถnnen deine Beitrรคge in Suchergebnissen und Trends erscheinen. Dein Profil kann ebenfalls anderen mit รคhnlichen Interessen vorgeschlagen werden.", "onboarding.profile.display_name": "Anzeigename", "onboarding.profile.display_name_hint": "Dein richtiger Name oder dein Fantasienameย โ€ฆ", - "onboarding.profile.lead": "Du kannst das spรคter in den Einstellungen vervollstรคndigen, wo noch mehr Anpassungsmรถglichkeiten zur Verfรผgung stehen.", + "onboarding.profile.lead": "Du kannst dein Profil spรคter in den Einstellungen vervollstรคndigen. Dort stehen weitere Anpassungsmรถglichkeiten zur Verfรผgung.", "onboarding.profile.note": "รœber mich", "onboarding.profile.note_hint": "Du kannst andere @Profile erwรคhnen oder #Hashtags verwendenย โ€ฆ", "onboarding.profile.save_and_continue": "Speichern und fortfahren", @@ -556,16 +571,16 @@ "onboarding.start.title": "Du hast es geschafft!", "onboarding.steps.follow_people.body": "Interessanten Profilen zu folgen ist das, was Mastodon ausmacht.", "onboarding.steps.follow_people.title": "Personalisiere deine Startseite", - "onboarding.steps.publish_status.body": "BegrรผรŸe die Welt mit Text, Fotos, Videos oder Umfragen {emoji}", + "onboarding.steps.publish_status.body": "BegrรผรŸe die Welt mit Text, Fotos, Videos oder Umfragen. {emoji}", "onboarding.steps.publish_status.title": "Erstelle deinen ersten Beitrag", "onboarding.steps.setup_profile.body": "Mit einem vollstรคndigen Profil interagieren andere eher mit dir.", "onboarding.steps.setup_profile.title": "Personalisiere dein Profil", - "onboarding.steps.share_profile.body": "Lass deine Freund*innen wissen, wie sie dich auf Mastodon finden kรถnnen", + "onboarding.steps.share_profile.body": "Lass deine Freund*innen wissen, wie sie dich auf Mastodon finden kรถnnen.", "onboarding.steps.share_profile.title": "Teile dein Mastodon-Profil", "onboarding.tips.2fa": "Wusstest du schon? Du kannst die Sicherheit deines Kontos erhรถhen, indem du die Zwei-Faktor-Authentisierung in deinen Kontoeinstellungen aktivierst. Dafรผr ist keine Telefonnummer notwendig und es funktioniert jede beliebige TOTP-App!", "onboarding.tips.accounts_from_other_servers": "Wusstest du schon? Da Mastodon dezentralisiert ist, werden einige Profile, denen du begegnest, auf anderen Servern als deinem bereitgestellt. Und trotzdem kannst du uneingeschrรคnkt mit ihnen interagieren! Der Servername befindet sich in der zweiten Hรคlfte ihres Profilnamens!", "onboarding.tips.migration": "Wusstest du schon? Wenn du das Gefรผhl hast, dass {domain} in Zukunft nicht die richtige Serverwahl fรผr dich ist, kannst du auf einen anderen Mastodon-Server umziehen, ohne deine Follower zu verlieren. Du kannst sogar deinen eigenen Server betreiben!", - "onboarding.tips.verification": "Wusstest du schon? Du kannst dein Konto verifizieren, indem du auf deiner Website auf dein Mastodon-Profil verlinkst und den Link deiner Website zu deinem Profil hinzufรผgst. Keine Gebรผhren oder Dokumente erforderlich!", + "onboarding.tips.verification": "Wusstest du schon? Du kannst dein Konto verifizieren, indem du auf deiner Website auf dein Mastodon-Profil verlinkst und den Link deiner Website zu deinem Profil hinzufรผgst. Vรถllig kostenlos und ohne Dokumente einsenden zu mรผssen!", "password_confirmation.exceeds_maxlength": "Passwortbestรคtigung รผberschreitet die maximal erlaubte Zeichenanzahl", "password_confirmation.mismatching": "Passwortbestรคtigung stimmt nicht รผberein", "picture_in_picture.restore": "Zurรผcksetzen", @@ -681,13 +696,13 @@ "server_banner.about_active_users": "Personen, die diesen Server in den vergangenen 30 Tagen verwendet haben (monatlich aktive Nutzer*innen)", "server_banner.active_users": "aktive Profile", "server_banner.administered_by": "Verwaltet von:", - "server_banner.introduction": "{domain} ist Teil eines dezentralisierten sozialen Netzwerks, angetrieben von {mastodon}.", - "server_banner.learn_more": "Mehr erfahren", + "server_banner.is_one_of_many": "{domain} ist einer von vielen unabhรคngigen Mastodon-Servern, mit dem du dich im Fediverse beteiligen kannst.", "server_banner.server_stats": "Serverstatistik:", "sign_in_banner.create_account": "Konto erstellen", + "sign_in_banner.follow_anyone": "Du kannst jedem im Fediverse folgen und alles in chronologischer Reihenfolge sehen. Keine Algorithmen, Werbung oder Clickbaits vorhanden.", + "sign_in_banner.mastodon_is": "Mastodon ist der beste Zugang, um auf dem Laufenden zu bleiben.", "sign_in_banner.sign_in": "Anmelden", "sign_in_banner.sso_redirect": "Anmelden oder registrieren", - "sign_in_banner.text": "Melde dich an, um Profilen oder Hashtags zu folgen, Beitrรคge zu favorisieren, zu teilen und auf sie zu antworten. Du kannst auch von deinem Konto aus auf einem anderen Server interagieren.", "status.admin_account": "@{name} moderieren", "status.admin_domain": "{domain} moderieren", "status.admin_status": "Beitrag moderieren", diff --git a/app/javascript/mastodon/locales/el.json b/app/javascript/mastodon/locales/el.json index 937bb5d027..5442624b36 100644 --- a/app/javascript/mastodon/locales/el.json +++ b/app/javascript/mastodon/locales/el.json @@ -35,9 +35,7 @@ "account.follow_back": "ฮ‘ฮบฮฟฮปฮฟฯฮธฮทฯƒฮต ฮบฮฑฮน ฮตฯƒฯ", "account.followers": "ฮ‘ฮบฯŒฮปฮฟฯ…ฮธฮฟฮน", "account.followers.empty": "ฮšฮฑฮฝฮตฮฏฯ‚ ฮดฮตฮฝ ฮฑฮบฮฟฮปฮฟฯ…ฮธฮตฮฏ ฮฑฯ…ฯ„ฯŒฮฝ ฯ„ฮฟฮฝ ฯ‡ฯฮฎฯƒฯ„ฮท ฮฑฮบฯŒฮผฮฑ.", - "account.followers_counter": "{count, plural, one {{counter} ฮ‘ฮบฯŒฮปฮฟฯ…ฮธฮฟฯ‚} other {{counter} ฮ‘ฮบฯŒฮปฮฟฯ…ฮธฮฟฮน}}", "account.following": "ฮ‘ฮบฮฟฮปฮฟฯ…ฮธฮตฮฏฯ„ฮต", - "account.following_counter": "{count, plural, one {{counter} ฮ‘ฮบฮฟฮปฮฟฯ…ฮธฮตฮฏ} other {{counter} ฮ‘ฮบฮฟฮปฮฟฯ…ฮธฮฟฯฮฝ}}", "account.follows.empty": "ฮ‘ฯ…ฯ„ฯŒฯ‚ ฮฟ ฯ‡ฯฮฎฯƒฯ„ฮทฯ‚ ฮดฮตฮฝ ฮฑฮบฮฟฮปฮฟฯ…ฮธฮตฮฏ ฮบฮฑฮฝฮญฮฝฮฑฮฝ ฮฑฮบฯŒฮผฮฑ.", "account.go_to_profile": "ฮœฮตฯ„ฮฌฮฒฮฑฯƒฮท ฯƒฯ„ฮฟ ฯ€ฯฮฟฯ†ฮฏฮป", "account.hide_reblogs": "ฮ‘ฯ€ฯŒฮบฯฯ…ฯˆฮท ฮตฮฝฮนฯƒฯ‡ฯฯƒฮตฯ‰ฮฝ ฮฑฯ€ฯŒ @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "ฮŸ/ฮ— {name} ฮฑฮนฯ„ฮฎฮธฮทฮบฮต ฮฝฮฑ ฯƒฮต ฮฑฮบฮฟฮปฮฟฯ…ฮธฮฎฯƒฮตฮน", "account.share": "ฮšฮฟฮนฮฝฮฟฯ€ฮฟฮฏฮทฯƒฮท ฯ„ฮฟฯ… ฯ€ฯฮฟฯ†ฮฏฮป @{name}", "account.show_reblogs": "ฮ•ฮผฯ†ฮฌฮฝฮนฯƒฮท ฮตฮฝฮนฯƒฯ‡ฯฯƒฮตฯ‰ฮฝ ฮฑฯ€ฯŒ @{name}", - "account.statuses_counter": "{count, plural, one {{counter} ฮ‘ฮฝฮฌฯฯ„ฮทฯƒฮท} other {{counter} ฮ‘ฮฝฮฑฯฯ„ฮฎฯƒฮตฮนฯ‚}}", "account.unblock": "ฮ†ฯฯƒฮท ฮฑฯ€ฮฟฮบฮปฮตฮนฯƒฮผฮฟฯ @{name}", "account.unblock_domain": "ฮ†ฯฯƒฮท ฮฑฯ€ฮฟฮบฮปฮตฮนฯƒฮผฮฟฯ ฯ„ฮฟฯ… ฯ„ฮฟฮผฮญฮฑ {domain}", "account.unblock_short": "ฮ†ฯฯƒฮท ฮฑฯ€ฮฟฮบฮปฮตฮนฯƒฮผฮฟฯ", @@ -558,13 +555,10 @@ "server_banner.about_active_users": "ฮ†ฯ„ฮฟฮผฮฑ ฯ€ฮฟฯ… ฯ‡ฯฮทฯƒฮนฮผฮฟฯ€ฮฟฮนฮฟฯฮฝ ฮฑฯ…ฯ„ฯŒฮฝ ฯ„ฮฟฮฝ ฮดฮนฮฑฮบฮฟฮผฮนฯƒฯ„ฮฎ ฮบฮฑฯ„ฮฌ ฯ„ฮนฯ‚ ฯ„ฮตฮปฮตฯ…ฯ„ฮฑฮฏฮตฯ‚ 30 ฮทฮผฮญฯฮตฯ‚ (ฮœฮทฮฝฮนฮฑฮฏฮฑ ฮ•ฮฝฮตฯฮณฮฟฮฏ ฮงฯฮฎฯƒฯ„ฮตฯ‚)", "server_banner.active_users": "ฮตฮฝฮตฯฮณฮฟฮฏ ฯ‡ฯฮฎฯƒฯ„ฮตฯ‚", "server_banner.administered_by": "ฮ”ฮนฮฑฯ‡ฮตฮนฯฮนฯƒฯ„ฮฎฯ‚:", - "server_banner.introduction": "ฮŸ {domain} ฮตฮฏฮฝฮฑฮน ฮผฮญฯฮฟฯ‚ ฯ„ฮฟฯ… ฮฑฯ€ฮฟฮบฮตฮฝฯ„ฯฯ‰ฮผฮญฮฝฮฟฯ… ฮบฮฟฮนฮฝฯ‰ฮฝฮนฮบฮฟฯ ฮดฮนฮบฯ„ฯฮฟฯ… ฯ€ฮฟฯ… ฯ€ฮฑฯฮญฯ‡ฮตฯ„ฮฑฮน ฮฑฯ€ฯŒ {mastodon}.", - "server_banner.learn_more": "ฮœฮฌฮธฮต ฯ€ฮตฯฮนฯƒฯƒฯŒฯ„ฮตฯฮฑ", "server_banner.server_stats": "ฮฃฯ„ฮฑฯ„ฮนฯƒฯ„ฮนฮบฮฌ ฮดฮนฮฑฮบฮฟฮผฮนฯƒฯ„ฮฎ:", "sign_in_banner.create_account": "ฮ”ฮทฮผฮนฮฟฯ…ฯฮณฮฏฮฑ ฮปฮฟฮณฮฑฯฮนฮฑฯƒฮผฮฟฯ", "sign_in_banner.sign_in": "ฮฃฯฮฝฮดฮตฯƒฮท", "sign_in_banner.sso_redirect": "ฮฃฯ…ฮฝฮดฮตฮธฮตฮฏฯ„ฮต ฮฎ ฮ•ฮณฮณฯฮฑฯ†ฮตฮฏฯ„ฮต", - "sign_in_banner.text": "ฮฃฯ…ฮฝฮดฮตฮธฮตฮฏฯ„ฮต ฮณฮนฮฑ ฮฝฮฑ ฮฑฮบฮฟฮปฮฟฯ…ฮธฮฎฯƒฮตฯ„ฮต ฯ€ฯฮฟฯ†ฮฏฮป ฮฎ ฮตฯ„ฮนฮบฮญฯ„ฮตฯ‚, ฮฑฮณฮฑฯ€ฮฎฯƒฯ„ฮต, ฮผฮฟฮนฯฮฑฯƒฯ„ฮตฮฏฯ„ฮต ฮบฮฑฮน ฮฑฯ€ฮฑฮฝฯ„ฮฎฯƒฯ„ฮต ฯƒฮต ฮดฮทฮผฮฟฯƒฮนฮตฯฯƒฮตฮนฯ‚. ฮœฯ€ฮฟฯฮตฮฏฯ„ฮต ฮตฯ€ฮฏฯƒฮทฯ‚ ฮฝฮฑ ฮฑฮปฮปฮทฮปฮตฯ€ฮนฮดฯฮฌฯƒฮตฯ„ฮต ฮฑฯ€ฯŒ ฯ„ฮฟฮฝ ฮปฮฟฮณฮฑฯฮนฮฑฯƒฮผฯŒ ฯƒฮฑฯ‚ ฯƒฮต ฮดฮนฮฑฯ†ฮฟฯฮตฯ„ฮนฮบฯŒ ฮดฮนฮฑฮบฮฟฮผฮนฯƒฯ„ฮฎ.", "status.admin_account": "ฮ†ฮฝฮฟฮนฮณฮผฮฑ ฮดฮนฮตฯ€ฮฑฯ†ฮฎฯ‚ ฯƒฯ…ฮฝฯ„ฮฟฮฝฮนฯƒฮผฮฟฯ ฮณฮนฮฑ ฯ„ฮฟฮฝ/ฯ„ฮทฮฝ @{name}", "status.admin_domain": "ฮ†ฮฝฮฟฮนฮณฮผฮฑ ฮปฮตฮนฯ„ฮฟฯ…ฯฮณฮฏฮฑฯ‚ ฮดฮนฮฑฮผฮตฯƒฮฟฮปฮฌฮฒฮทฯƒฮทฯ‚ ฮณฮนฮฑ {domain}", "status.admin_status": "ฮ†ฮฝฮฟฮนฮณฮผฮฑ ฮฑฯ…ฯ„ฮฎฯ‚ ฯ„ฮทฯ‚ ฮฑฮฝฮฌฯฯ„ฮทฯƒฮทฯ‚ ฯƒฮต ฮดฮนฮตฯ€ฮฑฯ†ฮฎ ฯƒฯ…ฮฝฯ„ฮฟฮฝฮนฯƒฮผฮฟฯ", diff --git a/app/javascript/mastodon/locales/en-GB.json b/app/javascript/mastodon/locales/en-GB.json index 8e11e838c7..c25d9c90af 100644 --- a/app/javascript/mastodon/locales/en-GB.json +++ b/app/javascript/mastodon/locales/en-GB.json @@ -35,9 +35,9 @@ "account.follow_back": "Follow back", "account.followers": "Followers", "account.followers.empty": "No one follows this user yet.", - "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}", + "account.followers_counter": "{count, plural, one {{counter} follower} other {{counter} followers}}", "account.following": "Following", - "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}", + "account.following_counter": "{count, plural, one {{counter} following} other {{counter} following}}", "account.follows.empty": "This user doesn't follow anyone yet.", "account.go_to_profile": "Go to profile", "account.hide_reblogs": "Hide boosts from @{name}", @@ -63,7 +63,7 @@ "account.requested_follow": "{name} has requested to follow you", "account.share": "Share @{name}'s profile", "account.show_reblogs": "Show boosts from @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Post} other {{counter} Posts}}", + "account.statuses_counter": "{count, plural, one {{counter} post} other {{counter} posts}}", "account.unblock": "Unblock @{name}", "account.unblock_domain": "Unblock domain {domain}", "account.unblock_short": "Unblock", @@ -308,6 +308,8 @@ "follow_requests.unlocked_explanation": "Even though your account is not locked, the {domain} staff thought you might want to review follow requests from these accounts manually.", "follow_suggestions.curated_suggestion": "Staff pick", "follow_suggestions.dismiss": "Don't show again", + "follow_suggestions.featured_longer": "Hand-picked by the {domain} team", + "follow_suggestions.friends_of_friends_longer": "Popular among people you follow", "follow_suggestions.hints.featured": "This profile has been hand-picked by the {domain} team.", "follow_suggestions.hints.friends_of_friends": "This profile is popular among the people you follow.", "follow_suggestions.hints.most_followed": "This profile is one of the most followed on {domain}.", @@ -315,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "This profile is similar to the profiles you have most recently followed.", "follow_suggestions.personalized_suggestion": "Personalised suggestion", "follow_suggestions.popular_suggestion": "Popular suggestion", + "follow_suggestions.popular_suggestion_longer": "Popular on {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Similar to profiles you recently followed", "follow_suggestions.view_all": "View all", "follow_suggestions.who_to_follow": "Who to follow", "followed_tags": "Followed hashtags", @@ -410,6 +414,8 @@ "limited_account_hint.action": "Show profile anyway", "limited_account_hint.title": "This profile has been hidden by the moderators of {domain}.", "link_preview.author": "By {name}", + "link_preview.more_from_author": "More from {name}", + "link_preview.shares": "{count, plural, one {{counter} post} other {{counter} posts}}", "lists.account.add": "Add to list", "lists.account.remove": "Remove from list", "lists.delete": "Delete list", @@ -469,6 +475,15 @@ "notification.follow": "{name} followed you", "notification.follow_request": "{name} has requested to follow you", "notification.mention": "{name} mentioned you", + "notification.moderation-warning.learn_more": "Learn more", + "notification.moderation_warning": "You have received a moderation warning", + "notification.moderation_warning.action_delete_statuses": "Some of your posts have been removed.", + "notification.moderation_warning.action_disable": "Your account has been disabled.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Some of your posts have been marked as sensitive.", + "notification.moderation_warning.action_none": "Your account has received a moderation warning.", + "notification.moderation_warning.action_sensitive": "Your posts will be marked as sensitive from now on.", + "notification.moderation_warning.action_silence": "Your account has been limited.", + "notification.moderation_warning.action_suspend": "Your account has been suspended.", "notification.own_poll": "Your poll has ended", "notification.poll": "A poll you have voted in has ended", "notification.reblog": "{name} boosted your status", @@ -681,13 +696,13 @@ "server_banner.about_active_users": "People using this server during the last 30 days (Monthly Active Users)", "server_banner.active_users": "active users", "server_banner.administered_by": "Administered by:", - "server_banner.introduction": "{domain} is part of the decentralised social network powered by {mastodon}.", - "server_banner.learn_more": "Learn more", + "server_banner.is_one_of_many": "{domain} is one of the many independent Mastodon servers you can use to participate in the fediverse.", "server_banner.server_stats": "Server stats:", "sign_in_banner.create_account": "Create account", + "sign_in_banner.follow_anyone": "Follow anyone across the fediverse and see it all in chronological order. No algorithms, ads, or clickbait in sight.", + "sign_in_banner.mastodon_is": "Mastodon is the best way to keep up with what's happening.", "sign_in_banner.sign_in": "Sign in", "sign_in_banner.sso_redirect": "Login or Register", - "sign_in_banner.text": "Login to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.", "status.admin_account": "Open moderation interface for @{name}", "status.admin_domain": "Open moderation interface for {domain}", "status.admin_status": "Open this post in the moderation interface", diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index a5f9285c2f..69f3c6e1a5 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -35,9 +35,9 @@ "account.follow_back": "Follow back", "account.followers": "Followers", "account.followers.empty": "No one follows this user yet.", - "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Followers}}", + "account.followers_counter": "{count, plural, one {{counter} follower} other {{counter} followers}}", "account.following": "Following", - "account.following_counter": "{count, plural, one {{counter} Following} other {{counter} Following}}", + "account.following_counter": "{count, plural, one {{counter} following} other {{counter} following}}", "account.follows.empty": "This user doesn't follow anyone yet.", "account.go_to_profile": "Go to profile", "account.hide_reblogs": "Hide boosts from @{name}", @@ -63,7 +63,7 @@ "account.requested_follow": "{name} has requested to follow you", "account.share": "Share @{name}'s profile", "account.show_reblogs": "Show boosts from @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Post} other {{counter} Posts}}", + "account.statuses_counter": "{count, plural, one {{counter} post} other {{counter} posts}}", "account.unblock": "Unblock @{name}", "account.unblock_domain": "Unblock domain {domain}", "account.unblock_short": "Unblock", @@ -308,6 +308,8 @@ "follow_requests.unlocked_explanation": "Even though your account is not locked, the {domain} staff thought you might want to review follow requests from these accounts manually.", "follow_suggestions.curated_suggestion": "Staff pick", "follow_suggestions.dismiss": "Don't show again", + "follow_suggestions.featured_longer": "Hand-picked by the {domain} team", + "follow_suggestions.friends_of_friends_longer": "Popular among people you follow", "follow_suggestions.hints.featured": "This profile has been hand-picked by the {domain} team.", "follow_suggestions.hints.friends_of_friends": "This profile is popular among the people you follow.", "follow_suggestions.hints.most_followed": "This profile is one of the most followed on {domain}.", @@ -315,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "This profile is similar to the profiles you have most recently followed.", "follow_suggestions.personalized_suggestion": "Personalized suggestion", "follow_suggestions.popular_suggestion": "Popular suggestion", + "follow_suggestions.popular_suggestion_longer": "Popular on {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Similar to profiles you recently followed", "follow_suggestions.view_all": "View all", "follow_suggestions.who_to_follow": "Who to follow", "followed_tags": "Followed hashtags", @@ -410,6 +414,8 @@ "limited_account_hint.action": "Show profile anyway", "limited_account_hint.title": "This profile has been hidden by the moderators of {domain}.", "link_preview.author": "By {name}", + "link_preview.more_from_author": "More from {name}", + "link_preview.shares": "{count, plural, one {{counter} post} other {{counter} posts}}", "lists.account.add": "Add to list", "lists.account.remove": "Remove from list", "lists.delete": "Delete list", @@ -469,6 +475,15 @@ "notification.follow": "{name} followed you", "notification.follow_request": "{name} has requested to follow you", "notification.mention": "{name} mentioned you", + "notification.moderation-warning.learn_more": "Learn more", + "notification.moderation_warning": "You have received a moderation warning", + "notification.moderation_warning.action_delete_statuses": "Some of your posts have been removed.", + "notification.moderation_warning.action_disable": "Your account has been disabled.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Some of your posts have been marked as sensitive.", + "notification.moderation_warning.action_none": "Your account has received a moderation warning.", + "notification.moderation_warning.action_sensitive": "Your posts will be marked as sensitive from now on.", + "notification.moderation_warning.action_silence": "Your account has been limited.", + "notification.moderation_warning.action_suspend": "Your account has been suspended.", "notification.own_poll": "Your poll has ended", "notification.poll": "A poll you have voted in has ended", "notification.reblog": "{name} boosted your post", @@ -681,13 +696,13 @@ "server_banner.about_active_users": "People using this server during the last 30 days (Monthly Active Users)", "server_banner.active_users": "active users", "server_banner.administered_by": "Administered by:", - "server_banner.introduction": "{domain} is part of the decentralized social network powered by {mastodon}.", - "server_banner.learn_more": "Learn more", + "server_banner.is_one_of_many": "{domain} is one of the many independent Mastodon servers you can use to participate in the fediverse.", "server_banner.server_stats": "Server stats:", "sign_in_banner.create_account": "Create account", + "sign_in_banner.follow_anyone": "Follow anyone across the fediverse and see it all in chronological order. No algorithms, ads, or clickbait in sight.", + "sign_in_banner.mastodon_is": "Mastodon is the best way to keep up with what's happening.", "sign_in_banner.sign_in": "Login", "sign_in_banner.sso_redirect": "Login or Register", - "sign_in_banner.text": "Login to follow profiles or hashtags, favorite, share and reply to posts. You can also interact from your account on a different server.", "status.admin_account": "Open moderation interface for @{name}", "status.admin_domain": "Open moderation interface for {domain}", "status.admin_status": "Open this post in the moderation interface", diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json index 6e7885f485..e7cfc03468 100644 --- a/app/javascript/mastodon/locales/eo.json +++ b/app/javascript/mastodon/locales/eo.json @@ -35,9 +35,7 @@ "account.follow_back": "Sekvu reen", "account.followers": "Sekvantoj", "account.followers.empty": "Ankoraลญ neniu sekvas ฤ‰i tiun uzanton.", - "account.followers_counter": "{count, plural, one{{counter} Sekvanto} other {{counter} Sekvantoj}}", "account.following": "Sekvatoj", - "account.following_counter": "{count, plural, one {{counter} Sekvato} other {{counter} Sekvatoj}}", "account.follows.empty": "La uzanto ankoraลญ ne sekvas iun ajn.", "account.go_to_profile": "Iri al profilo", "account.hide_reblogs": "Kaลi diskonigojn de @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} petis sekvi vin", "account.share": "Diskonigi la profilon de @{name}", "account.show_reblogs": "Montri diskonigojn de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Afiลo} other {{counter} Afiลoj}}", "account.unblock": "Malbloki @{name}", "account.unblock_domain": "Malbloki la domajnon {domain}", "account.unblock_short": "Malbloki", @@ -498,7 +495,14 @@ "poll_button.add_poll": "Aldoni balotenketon", "poll_button.remove_poll": "Forigi balotenketon", "privacy.change": "Agordi mesaฤan privatecon", + "privacy.direct.long": "ฤˆiuj menciitaj en la afiลo", + "privacy.direct.short": "Specifaj homoj", + "privacy.private.long": "Nur viaj sekvantoj", + "privacy.private.short": "Sekvantoj", + "privacy.public.long": "ฤˆiujn ajn ฤ‰e kaj ekster Mastodon", "privacy.public.short": "Publika", + "privacy.unlisted.long": "Malpli algoritmaj fanfaroj", + "privacy.unlisted.short": "Diskrete publika", "privacy_policy.last_updated": "Laste ฤisdatigita en {date}", "privacy_policy.title": "Politiko de privateco", "recommended": "Rekomendita", @@ -589,13 +593,10 @@ "server_banner.about_active_users": "Personoj uzantaj ฤ‰i tiun servilon dum la lastaj 30 tagoj (Aktivaj Uzantoj Monate)", "server_banner.active_users": "aktivaj uzantoj", "server_banner.administered_by": "Administrata de:", - "server_banner.introduction": "{domain} apartenas al la malcentra socia retejo povigita de {mastodon}.", - "server_banner.learn_more": "Lernu pli", "server_banner.server_stats": "Statistikoj de la servilo:", "sign_in_banner.create_account": "Krei konton", "sign_in_banner.sign_in": "Saluti", "sign_in_banner.sso_redirect": "Ensalutu aลญ Registriฤi", - "sign_in_banner.text": "Ensalutu por sekvi profilojn aลญ haลetikedojn, ลatatajn, dividi kaj respondi afiลojn. Vi ankaลญ povas interagi de via konto sur alia servilo.", "status.admin_account": "Malfermi fasadon de moderigado por @{name}", "status.admin_domain": "Malfermu moderigan interfacon por {domain}", "status.admin_status": "Malfermi ฤ‰i tiun mesaฤon en la kontrola interfaco", diff --git a/app/javascript/mastodon/locales/es-AR.json b/app/javascript/mastodon/locales/es-AR.json index 2cf4198625..28e8de9237 100644 --- a/app/javascript/mastodon/locales/es-AR.json +++ b/app/javascript/mastodon/locales/es-AR.json @@ -37,7 +37,7 @@ "account.followers.empty": "Todavรญa nadie sigue a este usuario.", "account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}", "account.following": "Siguiendo", - "account.following_counter": "{count, plural, other {Siguiendo a {counter}}}", + "account.following_counter": "{count, plural, one {siguiendo a {counter}} other {siguiendo a {counter}}}", "account.follows.empty": "Todavรญa este usuario no sigue a nadie.", "account.go_to_profile": "Ir al perfil", "account.hide_reblogs": "Ocultar adhesiones de @{name}", @@ -241,7 +241,7 @@ "emoji_button.nature": "Naturaleza", "emoji_button.not_found": "No se encontraron emojis coincidentes", "emoji_button.objects": "Objetos", - "emoji_button.people": "Gente", + "emoji_button.people": "Cuentas", "emoji_button.recent": "Usados frecuentemente", "emoji_button.search": "Buscar...", "emoji_button.search_results": "Resultados de bรบsqueda", @@ -308,6 +308,8 @@ "follow_requests.unlocked_explanation": "A pesar de que tu cuenta no es privada, el equipo de {domain} pensรณ que podrรญas querer revisar manualmente las solicitudes de seguimiento de estas cuentas.", "follow_suggestions.curated_suggestion": "Selecciรณn del equipo", "follow_suggestions.dismiss": "No mostrar de nuevo", + "follow_suggestions.featured_longer": "Seleccionada a mano por el equipo de {domain}", + "follow_suggestions.friends_of_friends_longer": "Populares entre las cuentas que seguรญs", "follow_suggestions.hints.featured": "Este perfil fue seleccionado a mano por el equipo de {domain}.", "follow_suggestions.hints.friends_of_friends": "Este perfil es popular entre las cuentas que seguรญs.", "follow_suggestions.hints.most_followed": "Este perfil es uno de los mรกs seguidos en {domain}.", @@ -315,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Este perfil es similar a los que comenzaste a seguir.", "follow_suggestions.personalized_suggestion": "Sugerencia personalizada", "follow_suggestions.popular_suggestion": "Sugerencia popular", + "follow_suggestions.popular_suggestion_longer": "Popular en {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Similares a perfiles que comenzaste a seguir recientemente", "follow_suggestions.view_all": "Ver todo", "follow_suggestions.who_to_follow": "A quiรฉn seguir", "followed_tags": "Etiquetas seguidas", @@ -410,6 +414,8 @@ "limited_account_hint.action": "Mostrar perfil de todos modos", "limited_account_hint.title": "Este perfil fue ocultado por los moderadores de {domain}.", "link_preview.author": "Por {name}", + "link_preview.more_from_author": "Mรกs de {name}", + "link_preview.shares": "{count, plural, one {{counter} mensaje} other {{counter} mensajes}}", "lists.account.add": "Agregar a lista", "lists.account.remove": "Quitar de lista", "lists.delete": "Eliminar lista", @@ -469,6 +475,15 @@ "notification.follow": "{name} te empezรณ a seguir", "notification.follow_request": "{name} solicitรณ seguirte", "notification.mention": "{name} te mencionรณ", + "notification.moderation-warning.learn_more": "Aprendรฉ mรกs", + "notification.moderation_warning": "Recibiste una advertencia de moderaciรณn", + "notification.moderation_warning.action_delete_statuses": "Se eliminaron algunos de tus mensajes.", + "notification.moderation_warning.action_disable": "Se deshabilitรณ tu cuenta.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Se marcaron como sensibles a algunos de tus mensajes.", + "notification.moderation_warning.action_none": "Tu cuenta recibiรณ una advertencia de moderaciรณn.", + "notification.moderation_warning.action_sensitive": "A partir de ahora, tus mensajes serรกn marcados como sensibles.", + "notification.moderation_warning.action_silence": "Tu cuenta fue limitada.", + "notification.moderation_warning.action_suspend": "Tu cuenta fue suspendida.", "notification.own_poll": "Tu encuesta finalizรณ", "notification.poll": "Finalizรณ una encuesta en la que votaste", "notification.reblog": "{name} adhiriรณ a tu mensaje", @@ -681,13 +696,13 @@ "server_banner.about_active_users": "Personas usando este servidor durante los รบltimos 30 dรญas (Usuarios Activos Mensuales)", "server_banner.active_users": "usuarios activos", "server_banner.administered_by": "Administrado por:", - "server_banner.introduction": "{domain} es parte de la red social descentralizada con la tecnologรญa de {mastodon}.", - "server_banner.learn_more": "Aprendรฉ mรกs", + "server_banner.is_one_of_many": "{domain} es uno de los muchos servidores de Mastodon independientes que podรฉs usar para participar en el Fediverso.", "server_banner.server_stats": "Estadรญsticas del servidor:", "sign_in_banner.create_account": "Crear cuenta", + "sign_in_banner.follow_anyone": "Seguรญ a cualquiera cuenta a travรฉs del Fediverso y leรฉ todo en orden cronolรณgico. Nada de algoritmos, publicidad o titulares engaรฑosos.", + "sign_in_banner.mastodon_is": "Mastodon es la mejor manera de mantenerse al dรญa sobre lo que estรก sucediendo.", "sign_in_banner.sign_in": "Iniciar sesiรณn", "sign_in_banner.sso_redirect": "Iniciรก sesiรณn o registrate", - "sign_in_banner.text": "Iniciรก sesiรณn para seguir cuentas o etiquetas, marcar mensajes como favoritos, compartirlos y responderlos. Tambiรฉn podรฉs interactuar desde tu cuenta en un servidor diferente.", "status.admin_account": "Abrir interface de moderaciรณn para @{name}", "status.admin_domain": "Abrir interface de moderaciรณn para {domain}", "status.admin_status": "Abrir este mensaje en la interface de moderaciรณn", diff --git a/app/javascript/mastodon/locales/es-MX.json b/app/javascript/mastodon/locales/es-MX.json index 1d8d4cedf8..c10a161015 100644 --- a/app/javascript/mastodon/locales/es-MX.json +++ b/app/javascript/mastodon/locales/es-MX.json @@ -35,9 +35,9 @@ "account.follow_back": "Seguir tambiรฉn", "account.followers": "Seguidores", "account.followers.empty": "Todavรญa nadie sigue a este usuario.", - "account.followers_counter": "{count, plural, one {{counter} Seguidor} other {{counter} Seguidores}}", + "account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}", "account.following": "Siguiendo", - "account.following_counter": "{count, plural, other {{counter} Siguiendo}}", + "account.following_counter": "{count, plural, one {{counter} siguiendo} other {{counter} siguiendo}}", "account.follows.empty": "Este usuario todavรญa no sigue a nadie.", "account.go_to_profile": "Ir al perfil", "account.hide_reblogs": "Ocultar retoots de @{name}", @@ -63,7 +63,7 @@ "account.requested_follow": "{name} ha solicitado seguirte", "account.share": "Compartir el perfil de @{name}", "account.show_reblogs": "Mostrar retoots de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", + "account.statuses_counter": "{count, plural, one {{counter} publicaciรณn} other {{counter} publicaciones}}", "account.unblock": "Desbloquear a @{name}", "account.unblock_domain": "Mostrar a {domain}", "account.unblock_short": "Desbloquear", @@ -308,6 +308,8 @@ "follow_requests.unlocked_explanation": "A pesar de que tu cuenta no es privada, el personal de {domain} ha pensado que quizรกs deberรญas revisar manualmente las solicitudes de seguimiento de estas cuentas.", "follow_suggestions.curated_suggestion": "Recomendaciones del equipo", "follow_suggestions.dismiss": "No mostrar de nuevo", + "follow_suggestions.featured_longer": "Escogidos por el equipo de {domain}", + "follow_suggestions.friends_of_friends_longer": "Populares entre las personas a las que sigues", "follow_suggestions.hints.featured": "Este perfil ha sido seleccionado a mano por el equipo de {domain}.", "follow_suggestions.hints.friends_of_friends": "Este perfil es popular entre las personas que sigues.", "follow_suggestions.hints.most_followed": "Este perfil es uno de los mรกs seguidos en {domain}.", @@ -315,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Este perfil es similar a los perfiles que has seguido recientemente.", "follow_suggestions.personalized_suggestion": "Sugerencia personalizada", "follow_suggestions.popular_suggestion": "Sugerencia popular", + "follow_suggestions.popular_suggestion_longer": "Populares en {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Similares a los perfiles que has seguido recientemente", "follow_suggestions.view_all": "Ver todo", "follow_suggestions.who_to_follow": "Recomendamos seguir", "followed_tags": "Hashtags seguidos", @@ -410,6 +414,8 @@ "limited_account_hint.action": "Mostrar perfil de todos modos", "limited_account_hint.title": "Este perfil ha sido ocultado por los moderadores de {domain}.", "link_preview.author": "Por {name}", + "link_preview.more_from_author": "Mรกs de {name}", + "link_preview.shares": "{count, plural, one {{counter} publicaciรณn} other {{counter} publicaciones}}", "lists.account.add": "Aรฑadir a lista", "lists.account.remove": "Quitar de lista", "lists.delete": "Borrar lista", @@ -469,6 +475,15 @@ "notification.follow": "{name} te empezรณ a seguir", "notification.follow_request": "{name} ha solicitado seguirte", "notification.mention": "{name} te ha mencionado", + "notification.moderation-warning.learn_more": "Saber mรกs", + "notification.moderation_warning": "Has recibido una advertencia de moderaciรณn", + "notification.moderation_warning.action_delete_statuses": "Se han eliminado algunas de tus publicaciones.", + "notification.moderation_warning.action_disable": "Tu cuenta ha sido desactivada.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Se han marcado como sensibles algunas de tus publicaciones.", + "notification.moderation_warning.action_none": "Tu cuenta ha recibido un aviso de moderaciรณn.", + "notification.moderation_warning.action_sensitive": "De ahora en adelante, todas tus publicaciones se marcarรกn como sensibles.", + "notification.moderation_warning.action_silence": "Tu cuenta ha sido limitada.", + "notification.moderation_warning.action_suspend": "Tu cuenta ha sido suspendida.", "notification.own_poll": "Tu encuesta ha terminado", "notification.poll": "Una encuesta en la que has votado ha terminado", "notification.reblog": "{name} ha retooteado tu estado", @@ -681,13 +696,13 @@ "server_banner.about_active_users": "Personas utilizando este servidor durante los รบltimos 30 dรญas (Usuarios Activos Mensuales)", "server_banner.active_users": "usuarios activos", "server_banner.administered_by": "Administrado por:", - "server_banner.introduction": "{domain} es parte de la red social descentralizada gestionada por {mastodon}.", - "server_banner.learn_more": "Saber mรกs", + "server_banner.is_one_of_many": "{domain} es uno de los varios servidores independientes de Mastodon que puedes usar para participar en el fediverso.", "server_banner.server_stats": "Estadรญsticas del servidor:", "sign_in_banner.create_account": "Crear cuenta", + "sign_in_banner.follow_anyone": "Sigue a cualquier persona en el fediverso y velo todo en orden cronolรณgico. Sin algoritmos, sin anuncios o titulares engaรฑosos.", + "sign_in_banner.mastodon_is": "Mastodon es el mejor modo de mantenerse al dรญa sobre quรฉ estรก ocurriendo.", "sign_in_banner.sign_in": "Iniciar sesiรณn", "sign_in_banner.sso_redirect": "Iniciar sesiรณn o Registrarse", - "sign_in_banner.text": "Inicia sesiรณn para seguir perfiles o etiquetas, asรญ como marcar como favoritas, compartir y responder a publicaciones. Tambiรฉn puedes interactuar desde tu cuenta en un servidor diferente.", "status.admin_account": "Abrir interfaz de moderaciรณn para @{name}", "status.admin_domain": "Abrir interfaz de moderaciรณn para {domain}", "status.admin_status": "Abrir este estado en la interfaz de moderaciรณn", diff --git a/app/javascript/mastodon/locales/es.json b/app/javascript/mastodon/locales/es.json index 149a37d74e..259fc1795b 100644 --- a/app/javascript/mastodon/locales/es.json +++ b/app/javascript/mastodon/locales/es.json @@ -35,9 +35,9 @@ "account.follow_back": "Seguir tambiรฉn", "account.followers": "Seguidores", "account.followers.empty": "Todavรญa nadie sigue a este usuario.", - "account.followers_counter": "{count, plural, one {{counter} Seguidor} other {{counter} Seguidores}}", + "account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}", "account.following": "Siguiendo", - "account.following_counter": "{count, plural, other {Siguiendo a {counter}}}", + "account.following_counter": "{count, plural, one {{counter} siguiendo} other {{counter} siguiendo}}", "account.follows.empty": "Este usuario todavรญa no sigue a nadie.", "account.go_to_profile": "Ir al perfil", "account.hide_reblogs": "Ocultar impulsos de @{name}", @@ -63,7 +63,7 @@ "account.requested_follow": "{name} ha solicitado seguirte", "account.share": "Compartir el perfil de @{name}", "account.show_reblogs": "Mostrar impulsos de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Publicaciรณn} other {{counter} Publicaciones}}", + "account.statuses_counter": "{count, plural, one {{counter} publicaciรณn} other {{counter} publicaciones}}", "account.unblock": "Desbloquear a @{name}", "account.unblock_domain": "Desbloquear dominio {domain}", "account.unblock_short": "Desbloquear", @@ -308,6 +308,8 @@ "follow_requests.unlocked_explanation": "A pesar de que tu cuenta no es privada, el personal de {domain} ha pensado que quizรกs deberรญas revisar manualmente las solicitudes de seguimiento de estas cuentas.", "follow_suggestions.curated_suggestion": "Recomendaciones del equipo", "follow_suggestions.dismiss": "No mostrar de nuevo", + "follow_suggestions.featured_longer": "Escogidos por el equipo de {domain}", + "follow_suggestions.friends_of_friends_longer": "Populares entre las personas a las que sigues", "follow_suggestions.hints.featured": "Este perfil ha sido elegido a mano por el equipo de {domain}.", "follow_suggestions.hints.friends_of_friends": "Este perfil es popular entre las personas que sigues.", "follow_suggestions.hints.most_followed": "Este perfil es uno de los mรกs seguidos en {domain}.", @@ -315,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Este perfil es similar a los perfiles que has seguido recientemente.", "follow_suggestions.personalized_suggestion": "Sugerencia personalizada", "follow_suggestions.popular_suggestion": "Sugerencia popular", + "follow_suggestions.popular_suggestion_longer": "Populares en {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Similares a los perfiles que has seguido recientemente", "follow_suggestions.view_all": "Ver todo", "follow_suggestions.who_to_follow": "A quiรฉn seguir", "followed_tags": "Etiquetas seguidas", @@ -410,6 +414,8 @@ "limited_account_hint.action": "Mostrar perfil de todos modos", "limited_account_hint.title": "Este perfil ha sido ocultado por los moderadores de {domain}.", "link_preview.author": "Por {name}", + "link_preview.more_from_author": "Mรกs de {name}", + "link_preview.shares": "{count, plural, one {{counter} publicaciรณn} other {{counter} publicaciones}}", "lists.account.add": "Aรฑadir a lista", "lists.account.remove": "Quitar de lista", "lists.delete": "Borrar lista", @@ -469,6 +475,15 @@ "notification.follow": "{name} te empezรณ a seguir", "notification.follow_request": "{name} ha solicitado seguirte", "notification.mention": "{name} te ha mencionado", + "notification.moderation-warning.learn_more": "Saber mรกs", + "notification.moderation_warning": "Has recibido una advertencia de moderaciรณn", + "notification.moderation_warning.action_delete_statuses": "Se han eliminado algunas de tus publicaciones.", + "notification.moderation_warning.action_disable": "Tu cuenta ha sido desactivada.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Se han marcado como sensibles algunas de tus publicaciones.", + "notification.moderation_warning.action_none": "Tu cuenta ha recibido un aviso de moderaciรณn.", + "notification.moderation_warning.action_sensitive": "De ahora en adelante, todas tus publicaciones se marcarรกn como sensibles.", + "notification.moderation_warning.action_silence": "Tu cuenta ha sido limitada.", + "notification.moderation_warning.action_suspend": "Tu cuenta ha sido suspendida.", "notification.own_poll": "Tu encuesta ha terminado", "notification.poll": "Una encuesta en la que has votado ha terminado", "notification.reblog": "{name} ha impulsado tu publicaciรณn", @@ -681,13 +696,13 @@ "server_banner.about_active_users": "Usuarios activos en el servidor durante los รบltimos 30 dรญas (Usuarios Activos Mensuales)", "server_banner.active_users": "usuarios activos", "server_banner.administered_by": "Administrado por:", - "server_banner.introduction": "{domain} es parte de la red social descentralizada liderada por {mastodon}.", - "server_banner.learn_more": "Saber mรกs", + "server_banner.is_one_of_many": "{domain} es uno de los varios servidores independientes de Mastodon que puedes usar para participar en el fediverso.", "server_banner.server_stats": "Estadรญsticas del servidor:", "sign_in_banner.create_account": "Crear cuenta", + "sign_in_banner.follow_anyone": "Sigue a cualquier persona en el fediverso y velo todo en orden cronolรณgico. Sin algoritmos, sin anuncios o titulares engaรฑosos.", + "sign_in_banner.mastodon_is": "Mastodon es el mejor modo de mantenerse al dรญa sobre quรฉ estรก ocurriendo.", "sign_in_banner.sign_in": "Iniciar sesiรณn", "sign_in_banner.sso_redirect": "Iniciar sesiรณn o Registrarse", - "sign_in_banner.text": "Inicia sesiรณn para seguir perfiles o etiquetas, asรญ como marcar como favoritas, compartir y responder a publicaciones. Tambiรฉn puedes interactuar desde tu cuenta en un servidor diferente.", "status.admin_account": "Abrir interfaz de moderaciรณn para @{name}", "status.admin_domain": "Abrir interfaz de moderaciรณn para {domain}", "status.admin_status": "Abrir esta publicaciรณn en la interfaz de moderaciรณn", diff --git a/app/javascript/mastodon/locales/et.json b/app/javascript/mastodon/locales/et.json index b2759d66c6..94f5ef5d9e 100644 --- a/app/javascript/mastodon/locales/et.json +++ b/app/javascript/mastodon/locales/et.json @@ -35,9 +35,7 @@ "account.follow_back": "Jรคlgi vastu", "account.followers": "Jรคlgijad", "account.followers.empty": "Keegi ei jรคlgi veel seda kasutajat.", - "account.followers_counter": "{count, plural, one {{counter} jรคlgija} other {{counter} jรคlgijat}}", "account.following": "Jรคlgib", - "account.following_counter": "{count, plural, one {{counter} jรคlgitav} other {{counter} jรคlgitavat}}", "account.follows.empty": "See kasutaja ei jรคlgi veel kedagi.", "account.go_to_profile": "Mine profiilile", "account.hide_reblogs": "Peida @{name} jagamised", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} on taodelnud sinu jรคlgimist", "account.share": "Jaga @{name} profiili", "account.show_reblogs": "Nรคita @{name} jagamisi", - "account.statuses_counter": "{count, plural, one {{counter} postitus} other {{counter} postitust}}", "account.unblock": "Eemalda blokeering @{name}", "account.unblock_domain": "Tee {domain} nรคhtavaks", "account.unblock_short": "Eemalda blokeering", @@ -147,14 +144,14 @@ "compose.published.body": "Postitus avaldatud.", "compose.published.open": "Ava", "compose.saved.body": "Postitus salvestatud.", - "compose_form.direct_message_warning_learn_more": "Vaata tรคpsemalt", + "compose_form.direct_message_warning_learn_more": "Vaata lisa", "compose_form.encryption_warning": "Postitused Mastodonis ei ole otsast-otsani krรผpteeritud. ร„ra jaga mingeid delikaatseid andmeid Mastodoni kaudu.", "compose_form.hashtag_warning": "See postitus ei ilmu รผhegi mรคrksรตna all, kuna pole avalik. Vaid avalikud postitused on mรคrksรตnade kaudu leitavad.", "compose_form.lock_disclaimer": "Su konto ei ole {locked}. Igaรผks saab sind jรคlgida, et nรคha su ainult-jรคlgijatele postitusi.", "compose_form.lock_disclaimer.lock": "lukus", "compose_form.placeholder": "Millest mรตtled?", "compose_form.poll.duration": "Kรผsitluse kestus", - "compose_form.poll.multiple": "Valikvastustega", + "compose_form.poll.multiple": "Mitu vastust", "compose_form.poll.option_placeholder": "Valik {number}", "compose_form.poll.single": "Vali รผks", "compose_form.poll.switch_to_multiple": "Muuda kรผsitlust mitmikvaliku lubamiseks", @@ -297,6 +294,7 @@ "filter_modal.select_filter.subtitle": "Kasuta olemasolevat kategooriat vรตi loo uus", "filter_modal.select_filter.title": "Filtreeri seda postitust", "filter_modal.title.status": "Postituse filtreerimine", + "filtered_notifications_banner.mentions": "{count, plural, one {mainimine} other {mainimist}}", "filtered_notifications_banner.pending_requests": "Teateid {count, plural, =0 {mitte รผheltki} one {รผhelt} other {#}} inimeselt, keda vรตid teada", "filtered_notifications_banner.title": "Filtreeritud teavitused", "firehose.all": "Kรตik", @@ -305,15 +303,19 @@ "follow_request.authorize": "Autoriseeri", "follow_request.reject": "Hรผlga", "follow_requests.unlocked_explanation": "Kuigi su konto pole lukustatud, soovitab {domain} personal siiski nende kontode jรคlgimistaotlused kรคsitsi รผle vaadata.", - "follow_suggestions.curated_suggestion": "Teiste valitud", + "follow_suggestions.curated_suggestion": "Meeskonna valitud", "follow_suggestions.dismiss": "ร„ra enam nรคita", - "follow_suggestions.hints.featured": "Selle kasutajaprofiili on soovitanud {domain} kasutajad.", - "follow_suggestions.hints.friends_of_friends": "See kasutajaprofiil on jรคlgitavate seas populaarne.", + "follow_suggestions.featured_longer": "Kรคsitsi valitud {domain} meeskonna poolt", + "follow_suggestions.friends_of_friends_longer": "Populaarne inimeste hulgas, keda jรคlgid", + "follow_suggestions.hints.featured": "Selle kasutajaprofiili on soovitanud {domain} meeskond.", + "follow_suggestions.hints.friends_of_friends": "See kasutajaprofiil on sinu jรคlgitavate seas populaarne.", "follow_suggestions.hints.most_followed": "See on {domain} enim jรคlgitud kasutajaprofiil.", - "follow_suggestions.hints.most_interactions": "See on {domain} viimasel ajal enim tรคhelepanu saanud kasutajaprofiil.", + "follow_suggestions.hints.most_interactions": "See kasutajaprofiil on viimasel ajal {domain} saanud palju tรคhelepanu.", "follow_suggestions.hints.similar_to_recently_followed": "See kasutajaprofiil sarnaneb neile, mida oled hiljuti jรคlgima asunud.", "follow_suggestions.personalized_suggestion": "Isikupรคrastatud soovitus", "follow_suggestions.popular_suggestion": "Popuplaarne soovitus", + "follow_suggestions.popular_suggestion_longer": "Populaarne kohas {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Sarnane profiilile, mida hiljuti jรคlgima hakkasid", "follow_suggestions.view_all": "Vaata kรตiki", "follow_suggestions.who_to_follow": "Keda jรคlgida", "followed_tags": "Jรคlgitavad mรคrksรตnad", @@ -409,6 +411,8 @@ "limited_account_hint.action": "Nรคita profilli sellegipoolest", "limited_account_hint.title": "See profiil on peidetud {domain} moderaatorite poolt.", "link_preview.author": "{name} poolt", + "link_preview.more_from_author": "Veel kasutajalt {name}", + "link_preview.shares": "{count, plural, one {{counter} postitus} other {{counter} postitust}}", "lists.account.add": "Lisa nimekirja", "lists.account.remove": "Eemalda nimekirjast", "lists.delete": "Kustuta nimekiri", @@ -468,13 +472,22 @@ "notification.follow": "{name} alustas su jรคlgimist", "notification.follow_request": "{name} soovib sind jรคlgida", "notification.mention": "{name} mainis sind", + "notification.moderation-warning.learn_more": "Vaata lisa", + "notification.moderation_warning": "Said modereerimise hoiatuse", + "notification.moderation_warning.action_delete_statuses": "Mรตni su postitus on eemaldatud.", + "notification.moderation_warning.action_disable": "Su konto on keelatud.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Mรตni su postitustest on mรคrgitud kui tundlik.", + "notification.moderation_warning.action_none": "Su konto on saanud modereerimise hoiatuse.", + "notification.moderation_warning.action_sensitive": "Su postitused mรคrgitakse nรผรผdsest tundlikuks.", + "notification.moderation_warning.action_silence": "Su kontole pandi piirang.", + "notification.moderation_warning.action_suspend": "Su konto on peatatud.", "notification.own_poll": "Su kรผsitlus on lรตppenud", "notification.poll": "Kรผsitlus, milles osalesid, on lรตppenud", "notification.reblog": "{name} jagas edasi postitust", "notification.relationships_severance_event": "Kadunud รผhendus kasutajaga {name}", "notification.relationships_severance_event.account_suspension": "{from} admin on kustutanud {target}, mis tรคhendab, et sa ei saa enam neilt uuendusi vรตi suhelda nendega.", "notification.relationships_severance_event.domain_block": "{from} admin on blokeerinud {target}, sealhulgas {followersCount} sinu jรคlgijat ja {followingCount, plural, one {# konto} other {# kontot}}, mida jรคlgid.", - "notification.relationships_severance_event.learn_more": "Saa rohkem teada", + "notification.relationships_severance_event.learn_more": "Vaata lisa", "notification.relationships_severance_event.user_domain_block": "Blokeerisid {target}, eemaldades oma jรคlgijate hulgast {followersCount} ja jรคlgitavate hulgast {followingCount, plural, one {# konto} other {# kontot}}.", "notification.status": "{name} just postitas", "notification.update": "{name} muutis postitust", @@ -680,13 +693,13 @@ "server_banner.about_active_users": "Inimesed, kes kasutavad seda serverit viimase 30 pรคeva jooksul (kuu aktiivsed kasutajad)", "server_banner.active_users": "aktiivsed kasutajad", "server_banner.administered_by": "Administraator:", - "server_banner.introduction": "{domain} on osa detsentraliseeritud sotsiaalvรตrgustikust, mida vรตimaldab {mastodon}.", - "server_banner.learn_more": "Vaata tรคpsemalt", + "server_banner.is_one_of_many": "{domain} on รผks paljudest sรตltumatutest Mastodoni serveritest, mida saab fediversumis osalemiseks kasutada.", "server_banner.server_stats": "Serveri statistika:", "sign_in_banner.create_account": "Loo konto", + "sign_in_banner.follow_anyone": "Jรคlgi รผkskรตik keda kogu fediversumist ja nรคe kรตike ajalises jรคrjestuses. Ei mingeid algoritme, reklaame vรตi klikipรผรผdjaid segamas.", + "sign_in_banner.mastodon_is": "Mastodon on parim viis olemaks kursis sellega, mis toimub.", "sign_in_banner.sign_in": "Logi sisse", "sign_in_banner.sso_redirect": "Sisene vรตi registreeru", - "sign_in_banner.text": "Logi sisse, et jรคlgida profiile vรตi silte, mรคrkida lemmikuks, jagada ja vastata postitustele. Vรตid suhelda ka mรตne teise serveri konto kaudu.", "status.admin_account": "Ava @{name} moderaatorivaates", "status.admin_domain": "Ava {domain} modeereerimisliides", "status.admin_status": "Ava postitus moderaatorivaates", diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json index 7e2ecf69cd..97c4250d22 100644 --- a/app/javascript/mastodon/locales/eu.json +++ b/app/javascript/mastodon/locales/eu.json @@ -35,9 +35,7 @@ "account.follow_back": "Jarraitu bueltan", "account.followers": "Jarraitzaileak", "account.followers.empty": "Ez du inork erabiltzaile hau jarraitzen oraindik.", - "account.followers_counter": "{count, plural, one {Jarraitzaile {counter}} other {{counter} jarraitzaile}}", "account.following": "Jarraitzen", - "account.following_counter": "{count, plural, one {{counter} jarraitzen} other {{counter} jarraitzen}}", "account.follows.empty": "Erabiltzaile honek ez du inor jarraitzen oraindik.", "account.go_to_profile": "Joan profilera", "account.hide_reblogs": "Ezkutatu @{name} erabiltzailearen bultzadak", @@ -63,7 +61,6 @@ "account.requested_follow": "{name}-(e)k zu jarraitzeko eskaera egin du", "account.share": "Partekatu @{name} erabiltzailearen profila", "account.show_reblogs": "Erakutsi @{name} erabiltzailearen bultzadak", - "account.statuses_counter": "{count, plural, one {Bidalketa {counter}} other {{counter} bidalketa}}", "account.unblock": "Desblokeatu @{name}", "account.unblock_domain": "Berriz erakutsi {domain}", "account.unblock_short": "Desblokeatu", @@ -308,6 +305,8 @@ "follow_requests.unlocked_explanation": "Zure kontua blokeatuta ez badago ere, {domain} domeinuko arduradunek uste dute kontu hauetako jarraipen eskaerak agian eskuz begiratu nahiko dituzula.", "follow_suggestions.curated_suggestion": "Domeinuaren iradokizuna", "follow_suggestions.dismiss": "Ez erakutsi berriro", + "follow_suggestions.featured_longer": "{domain} domeinuko taldeak hautaturikoak", + "follow_suggestions.friends_of_friends_longer": "Jarraitzen duzun jendearen artean ezagunak direnak", "follow_suggestions.hints.featured": "Profil hau {domain} domeinuko taldeak eskuz aukeratu du.", "follow_suggestions.hints.friends_of_friends": "Profil hau ezaguna da jarraitzen duzun jendearen artean.", "follow_suggestions.hints.most_followed": "Profil hau {domain} domeinuan gehien jarraitzen den profiletako bat da.", @@ -315,6 +314,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Profil hau duela gutxi jarraitu dituzun profil askoren antzekoa da.", "follow_suggestions.personalized_suggestion": "Iradokizun pertsonalizatua", "follow_suggestions.popular_suggestion": "Iradokizun ezaguna", + "follow_suggestions.popular_suggestion_longer": "{domain} domeinuan ezagunak direnak", + "follow_suggestions.similar_to_recently_followed_longer": "Berriki jarraitu dituzun profilen antzekoa", "follow_suggestions.view_all": "Ikusi denak", "follow_suggestions.who_to_follow": "Zein jarraitu", "followed_tags": "Jarraitutako traolak", @@ -469,6 +470,15 @@ "notification.follow": "{name}(e)k jarraitzen dizu", "notification.follow_request": "{name}(e)k zu jarraitzeko eskaera egin du", "notification.mention": "{name}(e)k aipatu zaitu", + "notification.moderation-warning.learn_more": "Informazio gehiago", + "notification.moderation_warning": "Moderazio-abisu bat jaso duzu", + "notification.moderation_warning.action_delete_statuses": "Argitalpen batzuk kendu dira.", + "notification.moderation_warning.action_disable": "Zure kontua desaktibatua izan da.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Argitalpen batzuk hunkigarri gisa ezarri dira.", + "notification.moderation_warning.action_none": "Kontuak moderazio-abisu bat jaso du.", + "notification.moderation_warning.action_sensitive": "Argitalpenak hunkigarri gisa markatuko dira hemendik aurrera.", + "notification.moderation_warning.action_silence": "Kontua murriztu egin da.", + "notification.moderation_warning.action_suspend": "Kontua itxi da.", "notification.own_poll": "Zure inkesta amaitu da", "notification.poll": "Zuk erantzun duzun inkesta bat bukatu da", "notification.reblog": "{name}(e)k bultzada eman dio zure bidalketari", @@ -679,13 +689,10 @@ "server_banner.about_active_users": "Azken 30 egunetan zerbitzari hau erabili duen jendea (hilabeteko erabiltzaile aktiboak)", "server_banner.active_users": "erabiltzaile aktibo", "server_banner.administered_by": "Administratzailea(k):", - "server_banner.introduction": "{domain} zerbitzaria {mastodon} erabiltzen duen sare sozial deszentralizatuko parte da.", - "server_banner.learn_more": "Ikasi gehiago", "server_banner.server_stats": "Zerbitzariaren estatistikak:", "sign_in_banner.create_account": "Sortu kontua", "sign_in_banner.sign_in": "Hasi saioa", "sign_in_banner.sso_redirect": "Hasi saioa edo izena eman", - "sign_in_banner.text": "Hasi saioa profilak edo traolak jarraitzeko, bidalketak gogokoetara gehitzeko, partekatzeko edo erantzuteko. Zure kontutik ere komunika zaitezke beste zerbitzari ezberdin batean.", "status.admin_account": "Ireki @{name} erabiltzailearen moderazio interfazea", "status.admin_domain": "{domain}-(r)en moderazio-interfazea ireki", "status.admin_status": "Ireki bidalketa hau moderazio interfazean", diff --git a/app/javascript/mastodon/locales/fa.json b/app/javascript/mastodon/locales/fa.json index 6d6b7d612c..18f6466d48 100644 --- a/app/javascript/mastodon/locales/fa.json +++ b/app/javascript/mastodon/locales/fa.json @@ -35,9 +35,7 @@ "account.follow_back": "ุฏู†ุจุงู„ ฺฉุฑุฏู† ู…ุชู‚ุงุจู„", "account.followers": "ูพŒโ€ŒฺฏŒุฑู†ุฏฺฏุงู†", "account.followers.empty": "ู‡ู†ูˆุฒ ฺฉุณŒ ูพŒโ€ŒฺฏŒุฑ ุงŒู† ฺฉุงุฑุจุฑ ู†Œุณุช.", - "account.followers_counter": "{count, plural, one {{counter} ูพŒโ€ŒฺฏŒุฑู†ุฏู‡} other {{counter} ูพŒโ€ŒฺฏŒุฑู†ุฏู‡}}", "account.following": "ูพŒ ู…Œโ€ŒฺฏŒุฑŒุฏ", - "account.following_counter": "{count, plural, one {{counter} ูพŒโ€Œฺฏุฑูุชู‡} other {{counter} ูพŒโ€Œฺฏุฑูุชู‡}}", "account.follows.empty": "ุงŒู† ฺฉุงุฑุจุฑ ู‡ู†ูˆุฒ ูพŒโ€ŒฺฏŒุฑ ฺฉุณŒ ู†Œุณุช.", "account.go_to_profile": "ุฑูุชู† ุจู‡ ู†ู…ุงŒู‡", "account.hide_reblogs": "ู†ู‡ูุชู† ุชู‚ูˆŒุชโ€Œู‡ุงŒ โ€Ž@{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} ุฏุฑุฎูˆุงุณุช ูพŒโ€ŒฺฏŒุฑŒุชุงู† ุฑุง ุฏุงุฏ", "account.share": "ู‡ู…โ€Œุฑุณุงู†Œ ู†ู…ุงŒู‡ู” โ€Ž@{name}", "account.show_reblogs": "ู†ู…ุงŒุด ุชู‚ูˆŒุชโ€Œู‡ุงŒ โ€Ž@{name}", - "account.statuses_counter": "{count, plural, one {{counter} ูุฑุณุชู‡} other {{counter} ูุฑุณุชู‡}}", "account.unblock": "ุฑูุน ู…ุณุฏูˆุฏŒุช โ€Ž@{name}", "account.unblock_domain": "ุฑูุน ู…ุณุฏูˆุฏŒุช ุฏุงู…ู†ู‡ู” {domain}", "account.unblock_short": "ุฑูุน ู…ุณุฏูˆุฏŒุช", @@ -620,13 +617,10 @@ "server_banner.about_active_users": "ุงูุฑุงุฏŒ ฺฉู‡ ุฏุฑ ณฐ ุฑูˆุฒ ฺฏุฐุดุชู‡ ุงุฒ ุงŒู† ฺฉุงุฑุณุงุฒ ุงุณุชูุงุฏู‡ ฺฉุฑุฏู‡โ€Œุงู†ุฏ (ฺฉุงุฑุจุฑุงู† ูุนู‘ุงู„ ู…ุงู‡ุงู†ู‡)", "server_banner.active_users": "ฺฉุงุฑุจุฑ ูุนู‘ุงู„", "server_banner.administered_by": "ุจู‡ ู…ุฏŒุฑŒุช:", - "server_banner.introduction": "{domain} ุจุฎุดŒ ุงุฒ ุดุจฺฉู‡ู” ุงุฌุชู…ุงุนŒ ู†ุงู…ุชู…ุฑฺฉุฒŒุณุช ฺฉู‡ ุงุฒ {mastodon} ู†Œุฑูˆ ฺฏุฑูุชู‡.", - "server_banner.learn_more": "ุจŒุดโ€Œุชุฑ ุจŒุงู…ูˆุฒŒุฏ", "server_banner.server_stats": "ุขู…ุงุฑ ฺฉุงุฑุณุงุฒ:", "sign_in_banner.create_account": "ุงŒุฌุงุฏ ุญุณุงุจ", "sign_in_banner.sign_in": "ูˆุฑูˆุฏ", "sign_in_banner.sso_redirect": "ูˆุฑูˆุฏ Œุง ุซุจุช ู†ุงู…", - "sign_in_banner.text": "ุจุฑุงŒ ูพŒโ€ŒฺฏŒุฑŒ ู†ู…ุงŒู‡โ€Œู‡ุง Œุง ุจุฑฺ†ุณุจโ€Œู‡ุงุŒ ูพุณู†ุฏŒุฏู†ุŒ ู‡ู…โ€Œุฑุณุงู†Œ ูˆ Œุง ูพุงุณุฎ ุจู‡ ูุฑุณุชู‡โ€Œู‡ุง ูˆุงุฑุฏ ุดูˆŒุฏ. ู‡ู…ฺ†ู†Œู† ู…Œโ€Œุชูˆุงู†Œุฏ ุงŒู† ฺฉุงุฑู‡ุง ุฑุง ุจุง ุญุณุงุจุชุงู† ุฏุฑ ฺฉุงุฑุณุงุฒŒ ุฏŒฺฏุฑ ุงู†ุฌุงู… ุฏู‡Œุฏ.", "status.admin_account": "ฺฏุดูˆุฏู† ูˆุงุณุท ู…ุฏŒุฑŒุช ุจุฑุงŒ โ€Ž@{name}", "status.admin_domain": "ฺฏุดูˆุฏู† ูˆุงุณุท ู…ุฏŒุฑŒุช ุจุฑุงŒ โ€Ž{domain}", "status.admin_status": "ฺฏุดูˆุฏู† ุงŒู† ูุฑุณุชู‡ ุฏุฑ ูˆุงุณุท ู…ุฏŒุฑŒุช", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 246bb56fcc..0767dd5e37 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -96,7 +96,7 @@ "block_modal.they_cant_see_posts": "Hรคn ei voi enรครค nรคhdรค julkaisujasi, etkรค sinรค voi nรคhdรค hรคnen.", "block_modal.they_will_know": "Hรคn voi nรคhdรค, ettรค hรคnet on estetty.", "block_modal.title": "Estetรครคnkรถ kรคyttรคjรค?", - "block_modal.you_wont_see_mentions": "Et enรครค nรคe hรคnen julkaisujaan etkรค voi seurata hรคntรค.", + "block_modal.you_wont_see_mentions": "Et tule enรครค nรคkemรครคn julkaisuja, joissa hรคnet mainitaan.", "boost_modal.combo": "Ensi kerralla voit ohittaa tรคmรคn painamalla {combo}", "bundle_column_error.copy_stacktrace": "Kopioi virheraportti", "bundle_column_error.error.body": "Pyydettyรค sivua ei voitu hahmontaa. Se voi johtua virheestรค koodissamme tai selaimen yhteensopivuudessa.", @@ -213,7 +213,7 @@ "domain_block_modal.block_account_instead": "Estรค sen sijaan @{name}", "domain_block_modal.they_can_interact_with_old_posts": "Ihmiset tรคltรค palvelimelta eivรคt voi olla vuorovaikutuksessa vanhojen julkaisujesi kanssa.", "domain_block_modal.they_cant_follow": "Kukaan tรคltรค palvelimelta ei voi seurata sinua.", - "domain_block_modal.they_wont_know": "Hรคn ei saa tietรครค, ettรค hรคnet on estetty.", + "domain_block_modal.they_wont_know": "Hรคn ei saa ilmoitusta tulleensa estetyksi.", "domain_block_modal.title": "Estetรครคnkรถ verkkotunnus?", "domain_block_modal.you_will_lose_followers": "Kaikki seuraajasi tรคltรค palvelimelta poistetaan.", "domain_block_modal.you_wont_see_posts": "Et enรครค nรคe julkaisuja etkรค ilmoituksia tรคmรคn palvelimen kรคyttรคjiltรค.", @@ -266,7 +266,7 @@ "empty_column.list": "Tรคllรค listalla ei ole vielรค mitรครคn. Kun tรคmรคn listan jรคsenet lรคhettรคvรคt uusia julkaisuja, ne nรคkyvรคt tรคssรค.", "empty_column.lists": "Sinulla ei ole vielรค yhtรครคn listaa. Kun luot sellaisen, nรคkyy se tรคssรค.", "empty_column.mutes": "Et ole mykistรคnyt vielรค yhtรครคn kรคyttรคjรครค.", - "empty_column.notification_requests": "Kaikki kunnossa! Tรครคllรค ei ole mitรครคn. Kun saat uusia ilmoituksia, ne nรคkyvรคt tรครคllรค asetustesi mukaisesti.", + "empty_column.notification_requests": "Olet ajan tasalla! Tรครคllรค ei ole mitรครคn uutta kerrottavaa. Kun saat uusia ilmoituksia, ne nรคkyvรคt tรครคllรค asetustesi mukaisesti.", "empty_column.notifications": "Sinulla ei ole vielรค ilmoituksia. Kun keskustelet muille, nรคet sen tรครคllรค.", "empty_column.public": "Tรครคllรค ei ole mitรครคn! Kirjoita jotain julkisesti. Voit myรถs seurata muiden palvelimien kรคyttรคjiรค", "error.unexpected_crash.explanation": "Sivua ei voida nรคyttรครค oikein ohjelmointivirheen tai selaimen yhteensopivuusvajeen vuoksi.", @@ -298,7 +298,7 @@ "filter_modal.select_filter.title": "Suodata tรคmรค julkaisu", "filter_modal.title.status": "Suodata julkaisu", "filtered_notifications_banner.mentions": "{count, plural, one {maininta} other {mainintaa}}", - "filtered_notifications_banner.pending_requests": "Sinulle on ilmoituksia mahdollisesti tuntemiltasi henkilรถiltรค seuraavasti: {count, plural, =0 {Ei keltรครคn} one {Yhdeltรค henkilรถltรค} other {# henkilรถltรค}}", + "filtered_notifications_banner.pending_requests": "Ilmoituksia {count, plural, =0 {ei ole} one {1 henkilรถltรค} other {# henkilรถltรค}}, jonka saatat tuntea", "filtered_notifications_banner.title": "Suodatetut ilmoitukset", "firehose.all": "Kaikki", "firehose.local": "Tรคmรค palvelin", @@ -308,6 +308,8 @@ "follow_requests.unlocked_explanation": "Vaikkei tiliรคsi ole lukittu, palvelimen {domain} yllรคpito on arvioinut, ettรค saatat olla halukas tarkistamaan nรคmรค seuraamispyynnรถt erikseen.", "follow_suggestions.curated_suggestion": "Ehdotus yllรคpidolta", "follow_suggestions.dismiss": "ร„lรค nรคytรค uudelleen", + "follow_suggestions.featured_longer": "Palvelimen {domain} tiimin poimintoja", + "follow_suggestions.friends_of_friends_longer": "Suosittu seuraamiesi ihmisten keskuudessa", "follow_suggestions.hints.featured": "Tรคmรคn profiilin on valinnut palvelimen {domain} tiimi.", "follow_suggestions.hints.friends_of_friends": "Seuraamasi kรคyttรคjรคt suosivat tรคtรค profiilia.", "follow_suggestions.hints.most_followed": "Tรคmรค profiili on palvelimen {domain} seuratuimpia.", @@ -315,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Tรคmรค profiili on samankaltainen kuin profiilit, joita olet viimeksi seurannut.", "follow_suggestions.personalized_suggestion": "Mukautettu ehdotus", "follow_suggestions.popular_suggestion": "Suosittu ehdotus", + "follow_suggestions.popular_suggestion_longer": "Suosittu palvelimella {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Samankaltainen kuin รคskettรคin seuraamasi profiilit", "follow_suggestions.view_all": "Nรคytรค kaikki", "follow_suggestions.who_to_follow": "Ehdotuksia seurattavaksi", "followed_tags": "Seuratut aihetunnisteet", @@ -410,6 +414,8 @@ "limited_account_hint.action": "Nรคytรค profiili joka tapauksessa", "limited_account_hint.title": "Palvelimen {domain} valvojat ovat piilottaneet tรคmรคn kรคyttรคjรคtilin.", "link_preview.author": "Julkaissut {name}", + "link_preview.more_from_author": "Lisรครค kรคyttรคjรคltรค {name}", + "link_preview.shares": "{count, plural, one {{counter} julkaisu} other {{counter} julkaisua}}", "lists.account.add": "Lisรครค listalle", "lists.account.remove": "Poista listalta", "lists.delete": "Poista lista", @@ -429,13 +435,13 @@ "media_gallery.toggle_visible": "{number, plural, one {Piilota kuva} other {Piilota kuvat}}", "moved_to_account_banner.text": "Tilisi {disabledAccount} on tรคllรค hetkellรค poissa kรคytรถstรค, koska teit siirron tiliin {movedToAccount}.", "mute_modal.hide_from_notifications": "Piilota ilmoituksista", - "mute_modal.hide_options": "Piilota valinnat", - "mute_modal.indefinite": "Kunnes poistan mykistyksen hรคneltรค", - "mute_modal.show_options": "Nรคytรค valinnat", + "mute_modal.hide_options": "Piilota vaihtoehdot", + "mute_modal.indefinite": "Kunnes perun hรคntรค koskevan mykistyksen", + "mute_modal.show_options": "Nรคytรค vaihtoehdot", "mute_modal.they_can_mention_and_follow": "Hรคn voi mainita sinut ja seurata sinua, mutta sinรค et nรคe hรคntรค.", - "mute_modal.they_wont_know": "Hรคn ei saa tietรครค, ettรค hรคnet on mykistetty.", + "mute_modal.they_wont_know": "Hรคn ei saa ilmoitusta tulleensa mykistetyksi.", "mute_modal.title": "Mykistetรครคnkรถ kรคyttรคjรค?", - "mute_modal.you_wont_see_mentions": "Et enรครค nรคe julkaisuja, joissa hรคnet mainitaan.", + "mute_modal.you_wont_see_mentions": "Et tule enรครค nรคkemรครคn julkaisuja, joissa hรคnet mainitaan.", "mute_modal.you_wont_see_posts": "Hรคn voi yhรค nรคhdรค julkaisusi, mutta sinรค et nรคe hรคnen.", "navigation_bar.about": "Tietoja", "navigation_bar.advanced_interface": "Avaa edistyneessรค selainkรคyttรถliittymรคssรค", @@ -469,6 +475,15 @@ "notification.follow": "{name} seurasi sinua", "notification.follow_request": "{name} on pyytรคnyt lupaa saada seurata sinua", "notification.mention": "{name} mainitsi sinut", + "notification.moderation-warning.learn_more": "Lue lisรครค", + "notification.moderation_warning": "Olet saanut moderointivaroituksen", + "notification.moderation_warning.action_delete_statuses": "Jotkin julkaisusi on poistettu.", + "notification.moderation_warning.action_disable": "Tilisi on poistettu kรคytรถstรค.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Jotkin julkaisusi on merkitty arkaluonteisiksi.", + "notification.moderation_warning.action_none": "Tilisi on saanut moderointivaroituksen.", + "notification.moderation_warning.action_sensitive": "Tรคstรค lรคhtien julkaisusi merkitรครคn arkaluonteisiksi.", + "notification.moderation_warning.action_silence": "Tiliรคsi on rajoitettu.", + "notification.moderation_warning.action_suspend": "Tilisi on jรครคdytetty.", "notification.own_poll": "ร„รคnestyksesi on pรครคttynyt", "notification.poll": "Kysely, johon osallistuit, on pรครคttynyt", "notification.reblog": "{name} tehosti julkaisuasi", @@ -516,11 +531,11 @@ "notifications.permission_denied": "Tyรถpรถytรคilmoitukset eivรคt ole kรคytettรคvissรค, koska selaimen kรคyttรถoikeuspyyntรถ on aiemmin evรคtty", "notifications.permission_denied_alert": "Tyรถpรถytรคilmoituksia ei voi ottaa kรคyttรถรถn, koska selaimen kรคyttรถoikeus on aiemmin estetty", "notifications.permission_required": "Tyรถpรถytรคilmoitukset eivรคt ole kรคytettรคvissรค, koska siihen tarvittavaa lupaa ei ole myรถnnetty.", - "notifications.policy.filter_new_accounts.hint": "Luotu {days, plural, one {viime pรคivรคnรค} other {viimeisenรค # pรคivรคnรค}}", + "notifications.policy.filter_new_accounts.hint": "Luotu {days, plural, one {viimeisimmรคn pรคivรคn aikana} other {# viime pรคivรคn aikana}}", "notifications.policy.filter_new_accounts_title": "Uudet tilit", - "notifications.policy.filter_not_followers_hint": "Mukaan lukien ne, jotka ovat seuranneet sinua vรคhemmรคn kuin {days, plural, one {pรคivรคn} other {# pรคivรครค}}", + "notifications.policy.filter_not_followers_hint": "Mukaan lukien alle {days, plural, one {pรคivรคn} other {# pรคivรคn}} verran sinua seuranneet", "notifications.policy.filter_not_followers_title": "Henkilรถt, jotka eivรคt seuraa sinua", - "notifications.policy.filter_not_following_hint": "Kunnes hyvรคksyt ne manuaalisesti", + "notifications.policy.filter_not_following_hint": "Kunnes hyvรคksyt ne omin kรคsin", "notifications.policy.filter_not_following_title": "Henkilรถt, joita et seuraa", "notifications.policy.filter_private_mentions_hint": "Suodatetaan, ellei se vastaa omaan mainintaasi tai ellet seuraa lรคhettรคjรครค", "notifications.policy.filter_private_mentions_title": "Ei-toivotut yksityismaininnat", @@ -681,13 +696,13 @@ "server_banner.about_active_users": "Palvelimen kรคyttรคjรคt viimeisten 30 pรคivรคn ajalta (kuukauden aktiiviset kรคyttรคjรคt)", "server_banner.active_users": "aktiivista kรคyttรคjรครค", "server_banner.administered_by": "Yllรคpitรคjรค:", - "server_banner.introduction": "{domain} kuuluu hajautettuun sosiaaliseen verkostoon, jonka voimanlรคhde on {mastodon}.", - "server_banner.learn_more": "Lue lisรครค", + "server_banner.is_one_of_many": "{domain} on yksi monista itsenรคisistรค Mastodon-palvelimista, joiden vรคlityksellรค voit toimia fediversumissa.", "server_banner.server_stats": "Palvelimen tilastot:", "sign_in_banner.create_account": "Luo tili", + "sign_in_banner.follow_anyone": "Seuraa kenen tahansa julkaisuja fediversumissa ja nรคe ne kaikki aikajรคrjestyksessรค. Ei algoritmeja, mainoksia tai klikkausten kalastelua.", + "sign_in_banner.mastodon_is": "Mastodon on paras tapa pysyรค ajan tasalla siitรค, mitรค ympรคrillรค tapahtuu.", "sign_in_banner.sign_in": "Kirjaudu", "sign_in_banner.sso_redirect": "Kirjaudu tai rekisterรถidy", - "sign_in_banner.text": "Kirjaudu sisรครคn, niin voit seurata profiileja tai aihetunnisteita, lisรคtรค julkaisuja suosikkeihin, jakaa julkaisuja ja vastata niihin. Voit olla vuorovaikutuksessa myรถs eri palvelimella olevalta tililtรคsi.", "status.admin_account": "Avaa tilin @{name} valvontanรคkymรค", "status.admin_domain": "Avaa palvelimen {domain} valvontanรคkymรค", "status.admin_status": "Avaa julkaisu valvontanรคkymรคssรค", diff --git a/app/javascript/mastodon/locales/fil.json b/app/javascript/mastodon/locales/fil.json index 894d73c8cc..b8a2987ef0 100644 --- a/app/javascript/mastodon/locales/fil.json +++ b/app/javascript/mastodon/locales/fil.json @@ -50,6 +50,8 @@ "admin.dashboard.retention.cohort_size": "Mga bagong tagagamit", "alert.rate_limited.message": "Mangyaring subukan muli pagkatapos ng {retry_time, time, medium}.", "audio.hide": "Itago ang tunog", + "block_modal.show_less": "Magpakita ng mas kaunti", + "block_modal.show_more": "Magpakita ng higit pa", "block_modal.title": "Harangan ang tagagamit?", "bundle_column_error.error.title": "Naku!", "bundle_column_error.network.body": "Nagkaroon ng kamalian habang sinusubukang i-karga ang pahinang ito. Maaaring dahil ito sa pansamantalang problema ng iyong koneksyon sa internet o ang server na ito.", @@ -102,6 +104,7 @@ "compose_form.encryption_warning": "Ang mga post sa Mastodon ay hindi naka-encrypt nang dulo-dulo. Huwag magbahagi ng anumang sensitibong impormasyon sa Mastodon.", "compose_form.hashtag_warning": "Hindi maililista ang post na ito sa anumang hashtag dahil hindi ito nakapubliko. Mga nakapublikong post lamang ang mahahanap ayon sa hashtag.", "compose_form.placeholder": "Anong nangyari?", + "compose_form.poll.duration": "Tagal ng botohan", "compose_form.poll.multiple": "Maraming pagpipilian", "compose_form.poll.single": "Piliin ang isa", "compose_form.reply": "Tumugon", @@ -115,6 +118,7 @@ "confirmations.delete_list.confirm": "Tanggalin", "confirmations.delete_list.message": "Sigurado ka bang gusto mong burahin ang listahang ito?", "confirmations.discard_edit_media.confirm": "Ipagpaliban", + "confirmations.domain_block.confirm": "Harangan ang serbiro", "confirmations.edit.confirm": "Baguhin", "confirmations.reply.confirm": "Tumugon", "conversation.mark_as_read": "Markahan bilang nabasa na", @@ -173,6 +177,7 @@ "empty_column.list": "Wala pang laman ang listahang ito. Kapag naglathala ng mga bagong post ang mga miyembro ng listahang ito, makikita iyon dito.", "empty_column.lists": "Wala ka pang mga listahan. Kapag gumawa ka ng isa, makikita yun dito.", "explore.search_results": "Mga resulta ng paghahanap", + "explore.suggested_follows": "Mga tao", "explore.title": "Tuklasin", "explore.trending_links": "Mga balita", "filter_modal.select_filter.search": "Hanapin o gumawa", @@ -182,13 +187,19 @@ "follow_request.authorize": "Tanggapin", "follow_request.reject": "Tanggihan", "follow_suggestions.dismiss": "Huwag nang ipakita muli", + "follow_suggestions.popular_suggestion_longer": "Sikat sa {domain}", "follow_suggestions.view_all": "Tingnan lahat", "follow_suggestions.who_to_follow": "Sinong maaaring sundan", "footer.about": "Tungkol dito", "footer.get_app": "Kunin ang app", + "footer.status": "Katayuan", "generic.saved": "Nakaimbak", "hashtag.column_header.tag_mode.all": "at {additional}", "hashtag.column_header.tag_mode.any": "o {additional}", + "hashtag.column_settings.tag_mode.all": "Lahat ng nandito", + "hashtag.column_settings.tag_mode.any": "Ilan dito", + "hashtag.column_settings.tag_mode.none": "Wala dito", + "hashtags.and_other": "โ€ฆat {count, plural, one {# iba pa} other {# na iba pa}}", "home.column_settings.show_replies": "Ipakita ang mga tugon", "home.pending_critical_update.body": "Mangyaring i-update ang iyong serbiro ng Mastodon sa lalong madaling panahon!", "interaction_modal.login.action": "Iuwi mo ako", @@ -199,6 +210,7 @@ "intervals.full.days": "{number, plural, one {# araw} other {# na araw}}", "intervals.full.hours": "{number, plural, one {# oras} other {# na oras}}", "intervals.full.minutes": "{number, plural, one {# minuto} other {# na minuto}}", + "keyboard_shortcuts.blocked": "Buksan ang talaan ng mga nakaharang na mga tagagamit", "keyboard_shortcuts.description": "Paglalarawan", "keyboard_shortcuts.down": "Ilipat pababa sa talaan", "keyboard_shortcuts.mention": "Banggitin ang may-akda", @@ -210,22 +222,29 @@ "link_preview.author": "Ni/ng {name}", "lists.account.add": "Idagdag sa talaan", "lists.account.remove": "Tanggalin mula sa talaan", + "lists.delete": "Burahin ang talaan", "lists.new.create": "Idagdag sa talaan", "lists.new.title_placeholder": "Bagong pangalan ng talaan", "lists.replies_policy.title": "Ipakita ang mga tugon sa:", "lists.subheading": "Iyong mga talaan", "loading_indicator.label": "Kumakargaโ€ฆ", + "mute_modal.hide_from_notifications": "Itago mula sa mga abiso", "navigation_bar.about": "Tungkol dito", "navigation_bar.blocks": "Nakaharang na mga tagagamit", "navigation_bar.direct": "Mga palihim na banggit", + "navigation_bar.discover": "Tuklasin", + "navigation_bar.explore": "Tuklasin", "navigation_bar.favourites": "Mga paborito", + "navigation_bar.follow_requests": "Mga hiling sa pagsunod", "navigation_bar.follows_and_followers": "Mga sinusundan at tagasunod", "navigation_bar.lists": "Mga listahan", + "navigation_bar.public_timeline": "Pinagsamang timeline", "navigation_bar.search": "Maghanap", "notification.admin.report": "Iniulat ni {name} si {target}", "notification.follow": "Sinundan ka ni {name}", "notification.follow_request": "Hinihiling ni {name} na sundan ka", "notification.mention": "Binanggit ka ni {name}", + "notification.moderation_warning": "Mayroong kang natanggap na babala sa pagtitimpi", "notification.relationships_severance_event.learn_more": "Matuto nang higit pa", "notification_requests.accept": "Tanggapin", "notification_requests.notifications_from": "Mga abiso mula kay/sa {name}", @@ -234,10 +253,12 @@ "notifications.column_settings.alert": "Mga abiso sa Desktop", "notifications.column_settings.favourite": "Mga paborito:", "notifications.column_settings.follow": "Mga bagong tagasunod:", + "notifications.column_settings.poll": "Resulta ng botohan:", "notifications.column_settings.unread_notifications.category": "Hindi Nabasang mga Abiso", "notifications.column_settings.update": "Mga pagbago:", "notifications.filter.all": "Lahat", "notifications.filter.favourites": "Mga paborito", + "notifications.filter.polls": "Resulta ng botohan", "notifications.mark_as_read": "Markahan lahat ng abiso bilang nabasa na", "notifications.policy.filter_not_followers_title": "Mga taong hindi ka susundan", "notifications.policy.filter_not_following_title": "Mga taong hindi mo sinusundan", @@ -246,6 +267,7 @@ "onboarding.profile.note_hint": "Maaari mong @bangitin ang ibang mga tao o mga #hashtagโ€ฆ", "onboarding.profile.save_and_continue": "Iimbak at magpatuloy", "onboarding.share.next_steps": "Mga posibleng susunod na hakbang:", + "picture_in_picture.restore": "Ilagay ito pabalik", "poll.closed": "Sarado", "poll.reveal": "Ipakita ang mga resulta", "poll.voted": "Binoto mo para sa sagot na ito", @@ -268,9 +290,13 @@ "reply_indicator.cancel": "Ipagpaliban", "report.block": "Harangan", "report.categories.other": "Iba pa", + "report.categories.violation": "Lumalabag ang nilalaman sa isa o higit pang mga patakaran ng serbiro", + "report.category.subtitle": "Piliin ang pinakamahusay na tugma", "report.category.title": "Sabihin mo sa amin kung anong nangyari sa {type} na ito", "report.close": "Tapos na", "report.next": "Sunod", + "report.placeholder": "Mga Karagdagang Puna", + "report.reasons.dislike": "Hindi ko gusto ito", "report.reasons.violation": "Lumalabag ito sa mga panuntunan ng serbiro", "report.reasons.violation_description": "Alam mo na lumalabag ito sa mga partikular na panuntunan", "report.rules.title": "Aling mga patakaran ang nilabag?", @@ -280,13 +306,15 @@ "report.thanks.title": "Ayaw mo bang makita ito?", "report.thanks.title_actionable": "Salamat sa pag-uulat, titingnan namin ito.", "report_notification.categories.other": "Iba pa", + "report_notification.categories.violation": "Paglabag sa patakaran", + "report_notification.open": "Buksan ang ulat", "search.quick_action.open_url": "Buksan ang URL sa Mastodon", "search.search_or_paste": "Maghanap o ilagay ang URL", "search_popout.full_text_search_disabled_message": "Hindi magagamit sa {domain}.", "search_popout.full_text_search_logged_out_message": "Magagamit lamang kapag naka-log in.", + "search_popout.recent": "Kamakailang mga paghahanap", "search_results.all": "Lahat", "search_results.see_all": "Ipakita lahat", - "server_banner.learn_more": "Matuto nang higit pa", "server_banner.server_stats": "Katayuan ng serbiro:", "status.block": "Harangan si @{name}", "status.delete": "Tanggalin", diff --git a/app/javascript/mastodon/locales/fo.json b/app/javascript/mastodon/locales/fo.json index 68faf6d81f..c27ffe0aa7 100644 --- a/app/javascript/mastodon/locales/fo.json +++ b/app/javascript/mastodon/locales/fo.json @@ -35,7 +35,7 @@ "account.follow_back": "Fylg aftur", "account.followers": "Fylgjarar", "account.followers.empty": "Ongar fylgjarar enn.", - "account.followers_counter": "{count, plural, one {{counter} Fylgjari} other {{counter} Fylgjarar}}", + "account.followers_counter": "{count, plural, one {{counter} fylgjari} other {{counter} fylgjarar}}", "account.following": "Fylgir", "account.following_counter": "{count, plural, one {{counter} fylgir} other {{counter} fylgja}}", "account.follows.empty": "Hesin brรบkari fylgir ongum enn.", @@ -308,6 +308,8 @@ "follow_requests.unlocked_explanation": "Sjรกlvt um konta tรญn ikki er lรฆst, so hugsa {domain} starvsfรณlkini, at tรบ kanska hevur hug at kanna umbรธnir um at fylgja frรก hesum kontum viรฐ hond.", "follow_suggestions.curated_suggestion": "Val hjรก รกbyrgdarfรณlki", "follow_suggestions.dismiss": "Lat vera viรฐ at vรญsa", + "follow_suggestions.featured_longer": "Vald burturรบr av {domain} toyminum", + "follow_suggestions.friends_of_friends_longer": "Vรฆlumtรณkt millum fรณlk, sum tรบ fylgir", "follow_suggestions.hints.featured": "Hesin vangin er รบrvaldur av toyminum handan {domain}.", "follow_suggestions.hints.friends_of_friends": "Hesin vangin er vรฆlumtรณktur millum tey, tรบ fylgir.", "follow_suggestions.hints.most_followed": "Hesin vangin er ein av teimum, sum er mest fylgdur รก {domain}.", @@ -315,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Hesin vangin lรญkist teimum, sum tรบ nรฝliga hevur fylgt.", "follow_suggestions.personalized_suggestion": "Persรณnligt uppskot", "follow_suggestions.popular_suggestion": "Vรฆlumtรณkt uppskot", + "follow_suggestions.popular_suggestion_longer": "Vรฆlumtรณkt รก {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Lรญkist vangum, sum tรบ nรฝliga hevur fylgt", "follow_suggestions.view_all": "Vรญs รธll", "follow_suggestions.who_to_follow": "Hvรธrji tรบ รกtti at fylgt", "followed_tags": "Fylgd frรกmerki", @@ -410,6 +414,8 @@ "limited_account_hint.action": "Vรญs vangamynd kortini", "limited_account_hint.title": "Hesin vangin er fjaldur av kjakleiรฐarunum รก {domain}.", "link_preview.author": "Av {name}", + "link_preview.more_from_author": "Meira frรก {name}", + "link_preview.shares": "{count, plural, one {{counter} postur} other {{counter} postar}}", "lists.account.add": "Legg afturat lista", "lists.account.remove": "Tak av lista", "lists.delete": "Strika lista", @@ -469,6 +475,15 @@ "notification.follow": "{name} fylgdi tรฆr", "notification.follow_request": "{name} biรฐur um at fylgja tรฆr", "notification.mention": "{name} nevndi teg", + "notification.moderation-warning.learn_more": "Lรฆr meira", + "notification.moderation_warning": "Tรบ hevur mรณttikiรฐ eina umsjรณnarรกvaring", + "notification.moderation_warning.action_delete_statuses": "Onkrir av tรญnum postum eru strikaรฐir.", + "notification.moderation_warning.action_disable": "Konta tรญn er gjรธrd รณvirkin.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Nakrir av postum tรญnum eru merktir sum viรฐkvรฆmir.", + "notification.moderation_warning.action_none": "Konta tรญn hevur mรณttikiรฐ eina umsjรณnarรกvaring.", + "notification.moderation_warning.action_sensitive": "Postar tรญnir verรฐa merktir sum viรฐkvรฆmir frรก nรบ av.", + "notification.moderation_warning.action_silence": "Konta tรญn er avmarkaรฐ.", + "notification.moderation_warning.action_suspend": "Konta tรญn er รณgildaรฐ.", "notification.own_poll": "Tรญn atkvรธรฐugreiรฐsla er endaรฐ", "notification.poll": "Ein atkvรธรฐugreiรฐsla, har tรบ hevur atkvรธtt, er endaรฐ", "notification.reblog": "{name} lyfti tรญn post", @@ -681,13 +696,13 @@ "server_banner.about_active_users": "Fรณlk, sum hava brรบkt hendan ambรฆtaran seinastu 30 dagarnar (mรกnaรฐarligir virknir brรบkarar)", "server_banner.active_users": "virknir brรบkarar", "server_banner.administered_by": "Umsitari:", - "server_banner.introduction": "{domain} er partur av desentrala sosiala netverkinum, sum er driviรฐ av {mastodon}.", - "server_banner.learn_more": "Lรฆr meira", + "server_banner.is_one_of_many": "{domain} er ein av nรณgvum รณheftum Mastodon ambรฆtarum, sum tรบ kanst brรบka at luttaka รญ fediversinum.", "server_banner.server_stats": "Ambรฆtarahagtรธl:", "sign_in_banner.create_account": "Stovna kontu", + "sign_in_banner.follow_anyone": "Fylg ein og hvรธnn รญ fediversinum og sรญggj alt รญ tรญรฐarrรธรฐ. Ongar algoritmur, ongar lรฝsingar og einki klikkbeit รญ eygsjรณn.", + "sign_in_banner.mastodon_is": "Mastodon er best mรกtin at fylgja viรฐ รญ tรญ, sum hendir.", "sign_in_banner.sign_in": "Rita inn", "sign_in_banner.sso_redirect": "Rita inn ella Skrรกset teg", - "sign_in_banner.text": "Innrita fyri at fylgja vangum og frรกmerkjum, dรกma, deila og svara postum. Tรบ kanst eisini brรบka kontuna til at samvirka รก einum รธรฐrum ambรฆtara.", "status.admin_account": "Lat kjakleiรฐaramarkamรณt upp fyri @{name}", "status.admin_domain": "Lat umsjรณnarmarkamรณt upp fyri {domain}", "status.admin_status": "Lat hendan postin upp รญ kjakleiรฐaramarkamรณtinum", diff --git a/app/javascript/mastodon/locales/fr-CA.json b/app/javascript/mastodon/locales/fr-CA.json index 9e29852904..4324855003 100644 --- a/app/javascript/mastodon/locales/fr-CA.json +++ b/app/javascript/mastodon/locales/fr-CA.json @@ -35,9 +35,7 @@ "account.follow_back": "S'abonner en retour", "account.followers": "abonnรฉยทeยทs", "account.followers.empty": "Personne ne suit ce compte pour l'instant.", - "account.followers_counter": "{count, plural, one {{counter} Abonnรฉยทe} other {{counter} Abonnรฉยทeยทs}}", "account.following": "Abonnรฉยทe", - "account.following_counter": "{count, plural, one {{counter} Abonnement} other {{counter} Abonnements}}", "account.follows.empty": "Ce compte ne suit personne prรฉsentement.", "account.go_to_profile": "Voir ce profil", "account.hide_reblogs": "Masquer les boosts de @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} a demandรฉ ร  vous suivre", "account.share": "Partager le profil de @{name}", "account.show_reblogs": "Afficher les boosts de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Publication} other {{counter} Publications}}", "account.unblock": "Dรฉbloquer @{name}", "account.unblock_domain": "Dรฉbloquer le domaine {domain}", "account.unblock_short": "Dรฉbloquer", @@ -156,7 +153,7 @@ "compose_form.poll.duration": "Durรฉe du sondage", "compose_form.poll.multiple": "Choix multiple", "compose_form.poll.option_placeholder": "Option {number}", - "compose_form.poll.single": "Choisissez-en un", + "compose_form.poll.single": "Choix unique", "compose_form.poll.switch_to_multiple": "Changer le sondage pour autoriser plusieurs choix", "compose_form.poll.switch_to_single": "Changer le sondage pour n'autoriser qu'un seul choix", "compose_form.poll.type": "Style", @@ -585,9 +582,9 @@ "privacy.private.short": "Abonnรฉs", "privacy.public.long": "Tout le monde sur et en dehors de Mastodon", "privacy.public.short": "Public", - "privacy.unlisted.additional": "Cette option se comporte exactement comme l'option publique, sauf que le message n'apparaรฎtra pas dans les flux en direct, les hashtags, l'exploration ou la recherche Mastodon, mรชme si vous avez optรฉ pour l'option publique pour l'ensemble de votre compte.", + "privacy.unlisted.additional": "Se comporte exactement comme ยซย publicย ยป, sauf que le message n'apparaรฎtra pas dans les flux en direct, les hashtags, explorer ou la recherche Mastodon, mรชme si vous les avez activรฉ au niveau de votre compte.", "privacy.unlisted.long": "Moins de fanfares algorithmiques", - "privacy.unlisted.short": "Public calme", + "privacy.unlisted.short": "Public discret", "privacy_policy.last_updated": "Derniรจre mise ร  jour {date}", "privacy_policy.title": "Politique de confidentialitรฉ", "recommended": "Recommandรฉ", @@ -680,13 +677,10 @@ "server_banner.about_active_users": "Personnes utilisant ce serveur au cours des 30 derniers jours (Comptes actifs mensuellement)", "server_banner.active_users": "comptes actifs", "server_banner.administered_by": "Administrรฉ par:", - "server_banner.introduction": "{domain} fait partie du rรฉseau social dรฉcentralisรฉ propulsรฉ par {mastodon}.", - "server_banner.learn_more": "En savoir plus", "server_banner.server_stats": "Statistiques du serveur:", "sign_in_banner.create_account": "Crรฉer un compte", "sign_in_banner.sign_in": "Se connecter", "sign_in_banner.sso_redirect": "Se connecter ou sโ€™inscrire", - "sign_in_banner.text": "Identifiez-vous pour suivre des profils ou des hashtags, ajouter des favoris, partager et rรฉpondre ร  des publications. Vous pouvez รฉgalement interagir depuis votre compte sur un autre serveur.", "status.admin_account": "Ouvrir lโ€™interface de modรฉration pour @{name}", "status.admin_domain": "Ouvrir lโ€™interface de modรฉration pour {domain}", "status.admin_status": "Ouvrir ce message dans lโ€™interface de modรฉration", diff --git a/app/javascript/mastodon/locales/fr.json b/app/javascript/mastodon/locales/fr.json index 1a5803623f..cd67cda539 100644 --- a/app/javascript/mastodon/locales/fr.json +++ b/app/javascript/mastodon/locales/fr.json @@ -35,9 +35,7 @@ "account.follow_back": "S'abonner en retour", "account.followers": "Abonnรฉยทeยทs", "account.followers.empty": "Personne ne suit cetยทte utilisateurยทrice pour lโ€™instant.", - "account.followers_counter": "{count, plural, one {{counter} Abonnรฉยทe} other {{counter} Abonnรฉยทeยทs}}", "account.following": "Abonnements", - "account.following_counter": "{count, plural, one {{counter} Abonnement} other {{counter} Abonnements}}", "account.follows.empty": "Cetยทte utilisateurยทrice ne suit personne pour lโ€™instant.", "account.go_to_profile": "Aller au profil", "account.hide_reblogs": "Masquer les partages de @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} a demandรฉ ร  vous suivre", "account.share": "Partager le profil de @{name}", "account.show_reblogs": "Afficher les partages de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Message} other {{counter} Messages}}", "account.unblock": "Dรฉbloquer @{name}", "account.unblock_domain": "Dรฉbloquer le domaine {domain}", "account.unblock_short": "Dรฉbloquer", @@ -156,7 +153,7 @@ "compose_form.poll.duration": "Durรฉe du sondage", "compose_form.poll.multiple": "Choix multiple", "compose_form.poll.option_placeholder": "Option {number}", - "compose_form.poll.single": "Choisissez-en un", + "compose_form.poll.single": "Choix unique", "compose_form.poll.switch_to_multiple": "Changer le sondage pour autoriser plusieurs choix", "compose_form.poll.switch_to_single": "Modifier le sondage pour autoriser qu'un seul choix", "compose_form.poll.type": "Style", @@ -585,9 +582,9 @@ "privacy.private.short": "Abonnรฉs", "privacy.public.long": "Tout le monde sur et en dehors de Mastodon", "privacy.public.short": "Public", - "privacy.unlisted.additional": "Cette option se comporte exactement comme l'option publique, sauf que le message n'apparaรฎtra pas dans les flux en direct, les hashtags, l'exploration ou la recherche Mastodon, mรชme si vous avez optรฉ pour l'option publique pour l'ensemble de votre compte.", + "privacy.unlisted.additional": "Se comporte exactement comme ยซย publicย ยป, sauf que le message n'apparaรฎtra pas dans les flux en direct, les hashtags, explorer ou la recherche Mastodon, mรชme si vous les avez activรฉ au niveau de votre compte.", "privacy.unlisted.long": "Moins de fanfares algorithmiques", - "privacy.unlisted.short": "Public calme", + "privacy.unlisted.short": "Public discret", "privacy_policy.last_updated": "Derniรจre mise ร  jour {date}", "privacy_policy.title": "Politique de confidentialitรฉ", "recommended": "Recommandรฉ", @@ -680,13 +677,10 @@ "server_banner.about_active_users": "Personnes utilisant ce serveur au cours des 30 derniers jours (Comptes actifs mensuellement)", "server_banner.active_users": "comptes actifs", "server_banner.administered_by": "Administrรฉ par :", - "server_banner.introduction": "{domain} fait partie du rรฉseau social dรฉcentralisรฉ propulsรฉ par {mastodon}.", - "server_banner.learn_more": "En savoir plus", "server_banner.server_stats": "Statistiques du serveur :", "sign_in_banner.create_account": "Crรฉer un compte", "sign_in_banner.sign_in": "Se connecter", "sign_in_banner.sso_redirect": "Se connecter ou sโ€™inscrire", - "sign_in_banner.text": "Identifiez-vous pour suivre des profils ou des hashtags, ajouter des favoris, partager et rรฉpondre ร  des messages. Vous pouvez รฉgalement interagir depuis votre compte sur un autre serveur.", "status.admin_account": "Ouvrir lโ€™interface de modรฉration pour @{name}", "status.admin_domain": "Ouvrir lโ€™interface de modรฉration pour {domain}", "status.admin_status": "Ouvrir ce message dans lโ€™interface de modรฉration", diff --git a/app/javascript/mastodon/locales/fy.json b/app/javascript/mastodon/locales/fy.json index a22f4767a6..d787c16bf3 100644 --- a/app/javascript/mastodon/locales/fy.json +++ b/app/javascript/mastodon/locales/fy.json @@ -35,9 +35,7 @@ "account.follow_back": "Weromfolgje", "account.followers": "Folgers", "account.followers.empty": "Noch net ien folget dizze brรปker.", - "account.followers_counter": "{count, plural, one {{counter} folger} other {{counter} folgers}}", "account.following": "Folgjend", - "account.following_counter": "{count, plural, one {{counter} folgjend} other {{counter} folgjend}}", "account.follows.empty": "Dizze brรปker folget noch net ien.", "account.go_to_profile": "Gean nei profyl", "account.hide_reblogs": "Boosts fan @{name} ferstopje", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} hat dy in folchfersyk stjoerd", "account.share": "Profyl fan @{name} diele", "account.show_reblogs": "Boosts fan @{name} toane", - "account.statuses_counter": "{count, plural, one {{counter} berjocht} other {{counter} berjochten}}", "account.unblock": "@{name} deblokkearje", "account.unblock_domain": "Domein {domain} deblokkearje", "account.unblock_short": "Deblokkearje", @@ -89,9 +86,12 @@ "announcement.announcement": "Oankundiging", "attachments_list.unprocessed": "(net ferwurke)", "audio.hide": "Audio ferstopje", + "block_modal.remote_users_caveat": "Wy freegje de server {domain} om jo beslรบt te respektearjen. It neilibben hjirfan is echter net garandearre, omdat guon servers blokkaden oars ynterpretearje kinne. Iepenbiere berjochten binne mooglik noch hieltyd sichtber foar net-oanmelde brรปkers.", "block_modal.show_less": "Minder toane", "block_modal.show_more": "Mear toane", "block_modal.they_cant_mention": "Sy kinne jo net fermelde of folgje.", + "block_modal.they_cant_see_posts": "De persoan kin jo berjochten net sjen en jo ek net harren berjochten.", + "block_modal.they_will_know": "De persoan kin sjen dat dy blokkearre wurdt.", "block_modal.title": "Brรปker blokkearje?", "block_modal.you_wont_see_mentions": "Jo sjogge gjin berjochten mear dyโ€™t dizze account fermelde.", "boost_modal.combo": "Jo kinne op {combo} drukke om dit de folgjende kear oer te slaan", @@ -214,11 +214,15 @@ "domain_block_modal.title": "Domein blokkearje?", "domain_block_modal.you_will_lose_followers": "Al jo folgers fan dizze server wurde รปntfolge.", "domain_block_modal.you_wont_see_posts": "Jo sjogge gjin berjochten of meldingen mear fan brรปkers op dizze server.", + "domain_pill.activitypub_lets_connect": "It soarget derfoar dat jo net allinnich mar ferbine en kommunisearje kinne mei minsken op Mastodon, mar ek mei oare sosjale apps.", + "domain_pill.activitypub_like_language": "ActivityPub is de taal dyโ€™t Mastodon mei oare sosjale netwurken sprekt.", "domain_pill.server": "Server", "domain_pill.their_handle": "Harren fediverse-adres:", "domain_pill.their_server": "Harren digitale thรบs, werโ€™t al harren berjochten binne.", + "domain_pill.their_username": "Harren unike identifikaasje-adres op harren server. It is mooglik dat der brรปkers mei deselde brรปkersnamme op ferskate servers te finen binne.", "domain_pill.username": "Brรปkersnamme", "domain_pill.whats_in_a_handle": "Wat is in fediverse-adres?", + "domain_pill.who_they_are": "Omdat jo oan in fediverse-adres sjen kinne hoeโ€™t ien hjit en op hokker server dy sit, kinne jo mei minsken op it troch sosjale web (fediverse) kommunisearje.", "domain_pill.your_handle": "Jo fediverse-adres:", "embed.instructions": "Embed this status on your website by copying the code below.", "embed.preview": "Sa komt it der รบt te sjen:", @@ -648,13 +652,10 @@ "server_banner.about_active_users": "Oantal brรปkers yn de รดfrรปne 30 dagen (MAU)", "server_banner.active_users": "warbere brรปkers", "server_banner.administered_by": "Beheard troch:", - "server_banner.introduction": "{domain} is รปnderdiel fan it desintralisearre sosjale netwurk {mastodon}.", - "server_banner.learn_more": "Mear ynfo", "server_banner.server_stats": "Serverstatistiken:", "sign_in_banner.create_account": "Account registrearje", "sign_in_banner.sign_in": "Oanmelde", "sign_in_banner.sso_redirect": "Oanmelde of Registrearje", - "sign_in_banner.text": "Meld jo oan, om profilen of hashtags te folgjen, berjochten favoryt te meitsjen, te dielen en te beรคntwurdzjen of om fan jo account รบt op in oare server mei oaren ynteraksje te hawwen.", "status.admin_account": "Moderaasje-omjouwing fan @{name} iepenje", "status.admin_domain": "Moderaasje-omjouwing fan {domain} iepenje", "status.admin_status": "Open this status in the moderation interface", diff --git a/app/javascript/mastodon/locales/ga.json b/app/javascript/mastodon/locales/ga.json index c71effe06d..edf7618148 100644 --- a/app/javascript/mastodon/locales/ga.json +++ b/app/javascript/mastodon/locales/ga.json @@ -31,9 +31,7 @@ "account.follow": "Lean", "account.followers": "Leantรณirรญ", "account.followers.empty": "Nรญ leanann รฉinne an t-รบsรกideoir seo fรณs.", - "account.followers_counter": "{count, plural, one {Leantรณir amhรกin} other {{counter} Leantรณir}}", "account.following": "Ag leanรบint", - "account.following_counter": "{count, plural, one {Ag leanรบint cรบntas amhรกin} other {Ag leanรบint {counter} cรบntas}}", "account.follows.empty": "Nรญ leanann an t-รบsรกideoir seo duine ar bith fรณs.", "account.go_to_profile": "Tรฉigh go dtรญ prรณifรญl", "account.hide_reblogs": "Folaigh moltaรญ รณ @{name}", @@ -55,7 +53,6 @@ "account.requested_follow": "D'iarr {name} ort do chuntas a leanรบint", "account.share": "Roinn prรณifรญl @{name}", "account.show_reblogs": "Taispeรกin moltaรญ รณ @{name}", - "account.statuses_counter": "{count, plural, one {Postรกil amhรกin} other {{counter} Postรกil}}", "account.unblock": "Bain bac de @{name}", "account.unblock_domain": "Bain bac den ainm fearainn {domain}", "account.unblock_short": "Bain bac de", @@ -438,7 +435,6 @@ "search_results.statuses": "Postรกlacha", "search_results.title": "Cuardaigh ar thรณir {q}", "server_banner.active_users": "รบsรกideoirรญ gnรญomhacha", - "server_banner.learn_more": "Tuilleadh eolais", "server_banner.server_stats": "Staitisticรญ freastalaรญ:", "sign_in_banner.create_account": "Cruthaigh cuntas", "sign_in_banner.sign_in": "Sinigh isteach", diff --git a/app/javascript/mastodon/locales/gd.json b/app/javascript/mastodon/locales/gd.json index 8ab515e1a8..fec025045c 100644 --- a/app/javascript/mastodon/locales/gd.json +++ b/app/javascript/mastodon/locales/gd.json @@ -35,9 +35,7 @@ "account.follow_back": "Lean air ais", "account.followers": "Luchd-leantainn", "account.followers.empty": "Chan eil neach sam bith aโ€™ leantainn air aโ€™ chleachdaiche seo fhathast.", - "account.followers_counter": "{count, plural, one {{counter} neach-leantainn} two {{counter} neach-leantainn} few {{counter} luchd-leantainn} other {{counter} luchd-leantainn}}", "account.following": "Aโ€™ leantainn", - "account.following_counter": "{count, plural, one {Aโ€™ leantainn {counter}} two {Aโ€™ leantainn {counter}} few {Aโ€™ leantainn {counter}} other {Aโ€™ leantainn {counter}}}", "account.follows.empty": "Chan eil an cleachdaiche seo aโ€™ leantainn neach sam bith fhathast.", "account.go_to_profile": "Tadhail air aโ€™ phrรฒifil", "account.hide_reblogs": "Falaich na brosnachaidhean o @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "Dhโ€™iarr {name} โ€™gad leantainn", "account.share": "Co-roinn aโ€™ phrรฒifil aig @{name}", "account.show_reblogs": "Seall na brosnachaidhean o @{name}", - "account.statuses_counter": "{count, plural, one {{counter} phost} two {{counter} phost} few {{counter} postaichean} other {{counter} post}}", "account.unblock": "Dรฌ-bhac @{name}", "account.unblock_domain": "Dรฌ-bhac an ร rainn {domain}", "account.unblock_short": "Dรฌ-bhac", @@ -297,6 +294,7 @@ "filter_modal.select_filter.subtitle": "Cleachd roinn-seรฒrsa a tha ann no cruthaich tรจ รนr", "filter_modal.select_filter.title": "Criathraich am post seo", "filter_modal.title.status": "Criathraich post", + "filtered_notifications_banner.mentions": "{count, plural, one {iomradh} two {iomradh} few {iomraidhean} other {iomradh}}", "filtered_notifications_banner.pending_requests": "{count, plural, =0 {Chan eil brath ann o dhaoine} one {Tha brathan ann o # neach} two {Tha brathan ann o # neach} few {Tha brathan ann o # daoine} other {Tha brathan ann o # duine}} air a bheil thu eรฒlach โ€™s dรฒcha", "filtered_notifications_banner.title": "Brathan criathraichte", "firehose.all": "Na h-uile", @@ -307,6 +305,7 @@ "follow_requests.unlocked_explanation": "Ged nach eil an cunntas agad glaiste, tha sgioba {domain} dhen bheachd gum bโ€™ fheร irrde thu lรจirmheas a dhรจanamh air na h-iarrtasan leantainn o na cunntasan seo a lร imh.", "follow_suggestions.curated_suggestion": "Roghainn an sgioba", "follow_suggestions.dismiss": "Na seall seo a-rithist", + "follow_suggestions.friends_of_friends_longer": "Fรจill mhรฒr am measg nan daoine a leanas tu", "follow_suggestions.hints.featured": "Chaidh aโ€™ phrรฒifil seo a thaghadh le sgioba {domain} a lร imh.", "follow_suggestions.hints.friends_of_friends": "Tha fรจill mhรฒr air aโ€™ phrรฒifil seo am measg nan daoine a leanas tu.", "follow_suggestions.hints.most_followed": "Tha aโ€™ phrรฒifil seo am measg an fheadhainn a leanar as trice air {domain}.", @@ -314,6 +313,7 @@ "follow_suggestions.hints.similar_to_recently_followed": "Tha aโ€™ phrรฒifil seo coltach ris na prรฒifilean air an lean thu o chionn goirid.", "follow_suggestions.personalized_suggestion": "Moladh pearsanaichte", "follow_suggestions.popular_suggestion": "Moladh air a bheil fรจill mhรฒr", + "follow_suggestions.popular_suggestion_longer": "Fรจill mhรฒr air {domain}", "follow_suggestions.view_all": "Seall na h-uile", "follow_suggestions.who_to_follow": "Molaidhean leantainn", "followed_tags": "Tagaichean hais โ€™gan leantainn", @@ -468,6 +468,7 @@ "notification.follow": "Tha {name} โ€™gad leantainn a-nis", "notification.follow_request": "Dhโ€™iarr {name} โ€™gad leantainn", "notification.mention": "Thug {name} iomradh ort", + "notification.moderation-warning.learn_more": "Barrachd fiosrachaidh", "notification.own_poll": "Thร inig an cunntas-bheachd agad gu crรฌoch", "notification.poll": "Thร inig cunntas-bheachd sa bhรฒt thu gu crรฌoch", "notification.reblog": "Bhrosnaich {name} am post agad", @@ -680,13 +681,10 @@ "server_banner.about_active_users": "Daoine a chleachd am frithealaiche seo rรจ an 30 latha mu dheireadh (Cleachdaichean gnรฌomhach gach mรฌos)", "server_banner.active_users": "cleachdaichean gnรฌomhach", "server_banner.administered_by": "Rianachd le:", - "server_banner.introduction": "Tha {domain} am measg an lรฌonraidh shรฒisealta sgaoilte le cumhachd {mastodon}.", - "server_banner.learn_more": "Barrachd fiosrachaidh", "server_banner.server_stats": "Stadastaireachd an fhrithealaiche:", "sign_in_banner.create_account": "Cruthaich cunntas", "sign_in_banner.sign_in": "Clร raich a-steach", "sign_in_banner.sso_redirect": "Clร raich a-steach no clร raich leinn", - "sign_in_banner.text": "Clร raich a-steach a leantainn phrรฒifilean no thagaichean hais, aโ€™ cur postaichean ris na h-annsachdan โ€™s โ€™gan co-roinneadh is freagairt dhaibh. โ€™S urrainn dhut gnรฌomh a ghabhail le cunntas o fhrithealaiche eile cuideachd.", "status.admin_account": "Fosgail eadar-aghaidh na maorsainneachd dha @{name}", "status.admin_domain": "Fosgail eadar-aghaidh na maorsainneachd dha {domain}", "status.admin_status": "Fosgail am post seo ann an eadar-aghaidh na maorsainneachd", diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json index 901bca7498..03287c7e52 100644 --- a/app/javascript/mastodon/locales/gl.json +++ b/app/javascript/mastodon/locales/gl.json @@ -2,7 +2,7 @@ "about.blocks": "Servidores suxeitos a moderaciรณn", "about.contact": "Contacto:", "about.disclaimer": "Mastodon รฉ software libre, de cรณdigo aberto, e unha marca comercial de Mastodon gGmbH.", - "about.domain_blocks.no_reason_available": "Motivo non indicado. ", + "about.domain_blocks.no_reason_available": "Motivo non indicado", "about.domain_blocks.preamble": "Mastodon de xeito xeral permรญteche ver contidos doutros servidores do fediverso e interactuar coas sรบas usuarias. Estas son as excepciรณns que se estabeleceron neste servidor en particular.", "about.domain_blocks.silenced.explanation": "Por defecto non verรกs perfรญs e contido desde este servidor, a menos que mires de xeito explรญcito ou optes por seguir ese contido ou usuaria.", "about.domain_blocks.silenced.title": "Limitado", @@ -35,9 +35,9 @@ "account.follow_back": "Seguir tamรฉn", "account.followers": "Seguidoras", "account.followers.empty": "Aรญnda ninguรฉn segue esta usuaria.", - "account.followers_counter": "{count, plural, one {{counter} Seguidora} other {{counter} Seguidoras}}", + "account.followers_counter": "{count, plural, one {{counter} seguidora} other {{counter} seguidoras}}", "account.following": "Seguindo", - "account.following_counter": "{count, plural, one {{counter} Seguindo} other {{counter} Seguindo}}", + "account.following_counter": "{count, plural, one {{counter} seguimento} other {{counter} seguimentos}}", "account.follows.empty": "Esta usuaria aรญnda non segue a ninguรฉn.", "account.go_to_profile": "Ir ao perfil", "account.hide_reblogs": "Agochar promociรณns de @{name}", @@ -63,7 +63,7 @@ "account.requested_follow": "{name} solicitou seguirte", "account.share": "Compartir o perfil de @{name}", "account.show_reblogs": "Amosar compartidos de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Publicaciรณn} other {{counter} Publicaciรณns}}", + "account.statuses_counter": "{count, plural, one {{counter} publicaciรณn} other {{counter} publicaciรณns}}", "account.unblock": "Desbloquear @{name}", "account.unblock_domain": "Amosar {domain}", "account.unblock_short": "Desbloquear", @@ -92,7 +92,7 @@ "block_modal.remote_users_caveat": "รmoslle pedir ao servidor {domain} que respecte a tรบa decisiรณn. Emporiso, non hai garantรญa de que atenda a peticiรณn xa que os servidores xestionan os bloqueos de formas diferentes. As publicaciรณns pรบblicas poderรญan aรญnda ser visibles para usuarias que non iniciaron sesiรณn.", "block_modal.show_less": "Mostrar menos", "block_modal.show_more": "Mostrar mรกis", - "block_modal.they_cant_mention": "Non te pode seguir nin mencionar.", + "block_modal.they_cant_mention": "Non te poden seguir nin mencionar.", "block_modal.they_cant_see_posts": "Non pode ver as tรบas publicaciรณns nin ti as de ela.", "block_modal.they_will_know": "Pode ver que a bloqueaches.", "block_modal.title": "Bloquear usuaria?", @@ -115,7 +115,7 @@ "closed_registrations_modal.find_another_server": "Atopa outro servidor", "closed_registrations_modal.preamble": "Mastodon รฉ descentralizado, asรญ que non importa onde crees a conta, poderรกs seguir e interactuar con calquera conta deste servidor. Incluso podes ter o teu servidor!", "closed_registrations_modal.title": "Crear conta en Mastodon", - "column.about": "Acerca de", + "column.about": "Sobre", "column.blocks": "Usuarias bloqueadas", "column.bookmarks": "Marcadores", "column.community": "Cronoloxรญa local", @@ -224,7 +224,7 @@ "domain_pill.their_server": "O seu fogar dixital, onde estรกn as sรบas publicaciรณns.", "domain_pill.their_username": "O seu identificador รบnico no seu servidor. ร‰ posible atopar usuarias co mesmo nome de usuaria en diferentes servidores.", "domain_pill.username": "Nome de usuaria", - "domain_pill.whats_in_a_handle": "Que รฉ o alcume?", + "domain_pill.whats_in_a_handle": "As partes do alcume?", "domain_pill.who_they_are": "O alcume dinos quen รฉ esa persoa e onde estรก, para que poidas interactuar con ela en toda a web social de .", "domain_pill.who_you_are": "Como o teu alcume informa de quen es e onde estรกs, as persoas poden interactuar contigo desde toda a web social de .", "domain_pill.your_handle": "O teu alcume:", @@ -308,6 +308,8 @@ "follow_requests.unlocked_explanation": "Malia que a tรบa conta non รฉ privada, a administraciรณn de {domain} pensou que quizabes terรญas que revisar de xeito manual as solicitudes de seguiminto.", "follow_suggestions.curated_suggestion": "Suxestiรณns do Servidor", "follow_suggestions.dismiss": "Non mostrar mรกis", + "follow_suggestions.featured_longer": "Elecciรณn persoal do equipo de {domain}", + "follow_suggestions.friends_of_friends_longer": "Popular entre as persoas que sigues", "follow_suggestions.hints.featured": "Este perfil foi escollido pola administraciรณn de {domain}.", "follow_suggestions.hints.friends_of_friends": "Este perfil รฉ popular entre as persoas que segues.", "follow_suggestions.hints.most_followed": "Este perfil รฉ un dos mรกis seguidos en {domain}.", @@ -315,10 +317,12 @@ "follow_suggestions.hints.similar_to_recently_followed": "Este perfil ten semellanzas cos perfรญs que ti seguiches รบltimamente.", "follow_suggestions.personalized_suggestion": "Suxestiรณn personalizada", "follow_suggestions.popular_suggestion": "Suxestiรณn popular", + "follow_suggestions.popular_suggestion_longer": "Popular en {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Perfรญs semellantes aos que seguiches recentemente", "follow_suggestions.view_all": "Ver todas", "follow_suggestions.who_to_follow": "A quen seguir", "followed_tags": "Cancelos seguidos", - "footer.about": "Acerca de", + "footer.about": "Sobre", "footer.directory": "Directorio de perfรญs", "footer.get_app": "Descarga a app", "footer.invite": "Convidar persoas", @@ -410,6 +414,8 @@ "limited_account_hint.action": "Mostrar perfil igualmente", "limited_account_hint.title": "Este perfil foi agochado pola moderaciรณn de {domain}.", "link_preview.author": "Por {name}", + "link_preview.more_from_author": "Mรกis de {name}", + "link_preview.shares": "{count, plural, one {{counter} publicaciรณn} other {{counter} publicaciรณns}}", "lists.account.add": "Engadir รก listaxe", "lists.account.remove": "Eliminar da listaxe", "lists.delete": "Eliminar listaxe", @@ -437,7 +443,7 @@ "mute_modal.title": "Acalar usuaria?", "mute_modal.you_wont_see_mentions": "Non verรกs as publicaciรณns que a mencionen.", "mute_modal.you_wont_see_posts": "Seguirรก podendo ler as tรบas publicaciรณns, pero non verรกs as sรบas.", - "navigation_bar.about": "Acerca de", + "navigation_bar.about": "Sobre", "navigation_bar.advanced_interface": "Abrir coa interface web avanzada", "navigation_bar.blocks": "Usuarias bloqueadas", "navigation_bar.bookmarks": "Marcadores", @@ -469,6 +475,15 @@ "notification.follow": "{name} comezou a seguirte", "notification.follow_request": "{name} solicitou seguirte", "notification.mention": "{name} mencionoute", + "notification.moderation-warning.learn_more": "Saber mรกis", + "notification.moderation_warning": "Recibiches unha advertencia da moderaciรณn", + "notification.moderation_warning.action_delete_statuses": "Algunha das tรบas publicaciรณns foron eliminadas.", + "notification.moderation_warning.action_disable": "A tรบa conta foi desactivada.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Algunha das tรบas publicaciรณns foron marcadas como sensibles.", + "notification.moderation_warning.action_none": "A tรบa conta recibeu unha advertencia da moderaciรณn.", + "notification.moderation_warning.action_sensitive": "De agora en diante as tรบas publicaciรณns van ser marcadas como sensibles.", + "notification.moderation_warning.action_silence": "A tรบa conta foi limitada.", + "notification.moderation_warning.action_suspend": "A tรบa conta foi suspendida.", "notification.own_poll": "A tรบa enquisa rematou", "notification.poll": "Rematou a enquisa na que votaches", "notification.reblog": "{name} compartiu a tรบa publicaciรณn", @@ -681,13 +696,13 @@ "server_banner.about_active_users": "Persoas que usaron este servidor nos รบltimos 30 dรญas (Usuarias Activas Mensuais)", "server_banner.active_users": "usuarias activas", "server_banner.administered_by": "Administrada por:", - "server_banner.introduction": "{domain} รฉ parte da rede social descentralizada que funciona grazas a {mastodon}.", - "server_banner.learn_more": "Saber mรกis", + "server_banner.is_one_of_many": "{domain} รฉ un dos moitos servidores Mastodon independentes que podes usar para participar do Fediverso.", "server_banner.server_stats": "Estatรญsticas do servidor:", "sign_in_banner.create_account": "Crear conta", + "sign_in_banner.follow_anyone": "Sigue a quen queiras no Fediverso e le as publicaciรณns en orde cronolรณxica. Sen algoritmos, publicidade nin titulares engaรฑosos.", + "sign_in_banner.mastodon_is": "Mastodon รฉ o mellor xeito de estar ao dรญa do que acontece.", "sign_in_banner.sign_in": "Iniciar sesiรณn", "sign_in_banner.sso_redirect": "Acceder ou Crear conta", - "sign_in_banner.text": "Inicia sesiรณn para seguir perfรญs ou cancelos, marcar como favorita e responder a publicaciรณns. Tamรฉn podes interactuar coa tรบa conta noutro servidor.", "status.admin_account": "Abrir interface de moderaciรณn para @{name}", "status.admin_domain": "Abrir interface de moderaciรณn para {domain}", "status.admin_status": "Abrir esta publicaciรณn na interface de moderaciรณn", diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json index d696186bf5..8111a56e89 100644 --- a/app/javascript/mastodon/locales/he.json +++ b/app/javascript/mastodon/locales/he.json @@ -35,9 +35,9 @@ "account.follow_back": "ืœืขืงื•ื‘ ื‘ื—ื–ืจื”", "account.followers": "ืขื•ืงื‘ื™ื", "account.followers.empty": "ืืฃ ืื—ื“ ืœื ืขื•ืงื‘ ืื—ืจ ื”ืžืฉืชืžืฉ ื”ื–ื” ืขื“ื™ื™ืŸ.", - "account.followers_counter": "{count, plural,one {ืขื•ืงื‘ ืื—ื“} other {{counter} ืขื•ืงื‘ื™ื}}", + "account.followers_counter": "{count, plural,one {ืขื•ืงื‘ ืื—ื“} other {{count} ืขื•ืงื‘ื™ื}}", "account.following": "ื ืขืงื‘ื™ื", - "account.following_counter": "{count, plural,one {ืขื•ืงื‘ ืื—ืจื™ {counter}}other {ืขื•ืงื‘ ืื—ืจื™ {counter}}}", + "account.following_counter": "{count, plural,one {ืขื•ืงื‘ ืื—ืจื™ {count}}other {ืขื•ืงื‘ ืื—ืจื™ {count}}}", "account.follows.empty": "ืžืฉืชืžืฉ ื–ื” ืขื“ื™ื™ืŸ ืœื ืขื•ืงื‘ ืื—ืจื™ ืืฃ ืื—ื“.", "account.go_to_profile": "ืžืขื‘ืจ ืœืคืจื•ืคื™ืœ", "account.hide_reblogs": "ืœื”ืกืชื™ืจ ื”ื™ื“ื”ื•ื“ื™ื ืžืืช @{name}", @@ -63,7 +63,7 @@ "account.requested_follow": "{name} ื‘ื™ืงืฉื• ืœืขืงื•ื‘ ืื—ืจื™ืš", "account.share": "ืฉืชืฃ ืืช ื”ืคืจื•ืคื™ืœ ืฉืœ @{name}", "account.show_reblogs": "ื”ืฆื’ ื”ื“ื”ื•ื“ื™ื ืžืืช @{name}", - "account.statuses_counter": "{count, plural, one {ื”ื•ื“ืขื”} two {ื”ื•ื“ืขื•ืชื™ื™ื} many {{count} ื”ื•ื“ืขื•ืช} other {{count} ื”ื•ื“ืขื•ืช}}", + "account.statuses_counter": "{count, plural, one {ื”ื•ื“ืขื” ืื—ืช} two {ื”ื•ื“ืขื•ืชื™ื™ื} many {{count} ื”ื•ื“ืขื•ืช} other {{count} ื”ื•ื“ืขื•ืช}}", "account.unblock": "ืœื”ืกื™ืจ ื—ืกื™ืžื” ืœ- @{name}", "account.unblock_domain": "ื”ืกื™ืจื™ ืืช ื”ื—ืกื™ืžื” ืฉืœ ืงื”ื™ืœืช {domain}", "account.unblock_short": "ื”ืกืจ ื—ืกื™ืžื”", @@ -308,6 +308,8 @@ "follow_requests.unlocked_explanation": "ืœืžืจื•ืช ืฉื—ืฉื‘ื•ื ืš ืื™ื ื• ื ืขื•ืœ, ืฆื•ื•ืช {domain} ื—ื•ืฉื‘ ืฉืื•ืœื™ ื›ื“ืื™ ืœื•ื•ื“ื ืืช ื‘ืงืฉื•ืช ื”ืžืขืงื‘ ื”ืืœื” ื™ื“ื ื™ืช.", "follow_suggestions.curated_suggestion": "ื‘ื—ื™ืจืช ื”ืฆื•ื•ืช", "follow_suggestions.dismiss": "ืœื ืœื”ืฆื™ื’ ืฉื•ื‘", + "follow_suggestions.featured_longer": "ื ื‘ื—ืจ ื™ื“ื ื™ืช ืขืœ ื™ื“ื™ ื”ืฆื•ื•ืช ืฉืœ {domain}", + "follow_suggestions.friends_of_friends_longer": "ืคื•ืคื•ืœืจื™ ื‘ื™ืŸ ื”ื ืขืงื‘ื™ื ืฉืœืš", "follow_suggestions.hints.featured": "ื”ื—ืฉื‘ื•ืŸ ื”ื–ื” ื ื‘ื—ืจ ืื™ืฉื™ืช ืขืœ ื™ื“ื™ ืฆื•ื•ืช {domain}.", "follow_suggestions.hints.friends_of_friends": "ื—ืฉื‘ื•ืŸ ื–ื” ืคื•ืคื•ืœืจื™ ื‘ื™ืŸ ื”ื ืขืงื‘ื™ื ืฉืœืš.", "follow_suggestions.hints.most_followed": "ื—ืฉื‘ื•ืŸ ื–ื” ื”ื•ื ืžื‘ื™ืŸ ื”ื ืขืงื‘ื™ื ื‘ื™ื•ืชืจ ื‘ืฉืจืช {domain}.", @@ -315,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "ื—ืฉื‘ื•ืŸ ื–ื” ื“ื•ืžื” ืœื—ืฉื‘ื•ื ื•ืช ืื—ืจื™ื ืฉืื—ืจื™ื”ื ื”ืชื—ืœืช ืœืขืงื•ื‘ ืœืื—ืจื•ื ื”.", "follow_suggestions.personalized_suggestion": "ื”ืฆืขื•ืช ืžื•ืชืืžื•ืช ืื™ืฉื™ืช", "follow_suggestions.popular_suggestion": "ื”ืฆืขื” ืคื•ืคื•ืœืจื™ืช", + "follow_suggestions.popular_suggestion_longer": "ืคื•ืคื•ืœืจื™ ื‘ืงื”ื™ืœืช {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "ื“ื•ืžื” ืœืžืฉืชืžืฉื•ืช.ื™ื ืฉืขืงื‘ืช ืื—ืจื™ื”ืŸ.ื ืœืื—ืจื•ื ื”", "follow_suggestions.view_all": "ืฆืคื™ื” ื‘ื›ืœ", "follow_suggestions.who_to_follow": "ืื—ืจื™ ืžื™ ืœืขืงื•ื‘", "followed_tags": "ื”ืชื’ื™ื•ืช ืฉื”ื—ืฉื‘ื•ืŸ ืฉืœืš ืขื•ืงื‘ ืื—ืจื™ื”ืŸ", @@ -410,6 +414,8 @@ "limited_account_hint.action": "ื”ืฆื’ ื—ืฉื‘ื•ืŸ ื‘ื›ืœ ื–ืืช", "limited_account_hint.title": "ืคืจื•ืคื™ืœ ื”ืžืฉืชืžืฉ ื”ื–ื” ื”ื•ืกืชืจ ืขืœ ื™ื“ื™ ื”ืžื ื—ื™ื ืฉืœ {domain}.", "link_preview.author": "ืžืืช {name}", + "link_preview.more_from_author": "ืขื•ื“ ืžืืช {name}", + "link_preview.shares": "{count, plural, one {ื”ื•ื“ืขื” ืื—ืช} two {ื”ื•ื“ืขื•ืชื™ื™ื} many {{count} ื”ื•ื“ืขื•ืช} other {{count} ื”ื•ื“ืขื•ืช}}", "lists.account.add": "ื”ื•ืกืฃ ืœืจืฉื™ืžื”", "lists.account.remove": "ื”ืกืจ ืžืจืฉื™ืžื”", "lists.delete": "ืžื—ื™ืงืช ืจืฉื™ืžื”", @@ -469,6 +475,15 @@ "notification.follow": "{name} ื‘ืžืขืงื‘ ืื—ืจื™ื™ืš", "notification.follow_request": "{name} ื‘ื™ืงืฉื• ืœืขืงื•ื‘ ืื—ืจื™ืš", "notification.mention": "ืื•ื–ื›ืจืช ืขืœ ื™ื“ื™ {name}", + "notification.moderation-warning.learn_more": "ืœืžื™ื“ืข ื ื•ืกืฃ", + "notification.moderation_warning": "ืงื™ื‘ืœืช ืื–ื”ืจื” ืžืฆื•ื•ืช ื ื™ื”ื•ืœ ื”ืชื•ื›ืŸ", + "notification.moderation_warning.action_delete_statuses": "ื—ืœืง ืžื”ื•ื“ืขื•ืชื™ืš ื”ื•ืกืจื•.", + "notification.moderation_warning.action_disable": "ื—ืฉื‘ื•ื ืš ื”ื•ืฉื‘ืช.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "ื—ืœืง ืžื”ื•ื“ืขื•ืชื™ืš ืกื•ืžื ื• ื›ืจื’ื™ืฉื•ืช.", + "notification.moderation_warning.action_none": "ื—ืฉื‘ื•ื ืš ืงื™ื‘ืœ ืื–ื”ืจื” ืžืฆื•ื•ืช ื ื™ื”ื•ืœ ื”ืชื•ื›ืŸ.", + "notification.moderation_warning.action_sensitive": "ื”ื•ื“ืขื•ืชื™ืš ื™ืกื•ืžื ื• ื›ืจื’ื™ืฉื•ืช ืžืขืชื” ื•ืื™ืœืš.", + "notification.moderation_warning.action_silence": "ื—ืฉื‘ื•ื ืš ื”ื•ื’ื‘ืœ.", + "notification.moderation_warning.action_suspend": "ื—ืฉื‘ื•ื ืš ื”ื•ืฉืขื”.", "notification.own_poll": "ื”ืกืงืจ ืฉืœืš ื”ืกืชื™ื™ื", "notification.poll": "ืกืงืจ ืฉื”ืฆื‘ืขืช ื‘ื• ื”ืกืชื™ื™ื", "notification.reblog": "ื”ื•ื“ืขืชืš ื”ื•ื“ื”ื“ื” ืขืœ ื™ื“ื™ {name}", @@ -681,13 +696,13 @@ "server_banner.about_active_users": "ืžืฉืชืžืฉื™ื ืคืขื™ืœื™ื ื‘ืฉืจืช ื‘ึพ30 ื”ื™ืžื™ื ื”ืื—ืจื•ื ื™ื (ืžืฉืชืžืฉื™ื ืคืขื™ืœื™ื ื—ื•ื“ืฉื™ื™ื)", "server_banner.active_users": "ืžืฉืชืžืฉื™ื ืคืขื™ืœื™ื", "server_banner.administered_by": "ืžื ื•ื”ืœ ืข\"ื™:", - "server_banner.introduction": "{domain} ื”ื•ื ืฉืจืช ื‘ืจืฉืช ื”ืžื‘ื•ื–ืจืช {mastodon}.", - "server_banner.learn_more": "ืžื™ื“ืข ื ื•ืกืฃ", + "server_banner.is_one_of_many": "{domain} ื”ื•ื ืฉืจืช ืื—ื“ ืžืฉืจืชื™ ืžืกื˜ื•ื“ื•ืŸ ืขืฆืžืื™ื™ื ืจื‘ื™ื ืฉื“ืจื’ื ืชื•ื›ืœื• ืœื”ืฉืชืชืฃ ื‘ืคื“ื™ื•ื•ืจืก (ืจืฉืช ื—ื‘ืจืชื™ืช ืžื‘ื•ื–ืจืช).", "server_banner.server_stats": "ืกื˜ื˜ื™ืกื˜ื™ืงื•ืช ืฉืจืช:", "sign_in_banner.create_account": "ื™ืฆื™ืจืช ื—ืฉื‘ื•ืŸ", + "sign_in_banner.follow_anyone": "ืชื•ื›ืœื• ืœืขืงื•ื‘ ืื—ืจื™ ื›ืœ ืžืฉืžืชืžืฉ ื‘ืคื“ื™ื•ื•ืจืก ื•ืœืงืจื•ื ื”ื›ืœ ืœืคื™ ืกื“ืจ ื”ืคืจืกื•ื ื‘ืฆื™ืจ ื”ื–ืžืŸ. ืื™ืŸ ืืœื’ื•ืจื™ืชืžื™ื, ืคืจืกื•ืžื•ืช, ืื• ืงืœื™ืงื‘ื™ื™ื˜ ืžื˜ืขื ื‘ืขืœื™ ื”ืจืฉืช.", + "sign_in_banner.mastodon_is": "ืžืกื˜ื•ื“ื•ืŸ ื”ื•ื ื”ื“ืจืš ื”ื˜ื•ื‘ื” ื‘ื™ื•ืชืจ ืœืขืงื•ื‘ ืื—ืจื™ ืžื” ืฉืงื•ืจื”.", "sign_in_banner.sign_in": "ื”ืชื—ื‘ืจื•ืช", "sign_in_banner.sso_redirect": "ื”ืชื—ื‘ืจื•ืช/ื”ืจืฉืžื”", - "sign_in_banner.text": "ื™ืฉ ืœื”ืชื—ื‘ืจ ื›ื“ื™ ืœืขืงื•ื‘ ืื—ืจื™ ืžืฉืชืžืฉื™ื ืื• ืชื’ื™ื•ืช, ืœื—ื‘ื‘, ืœืฉืชืฃ ื•ืœืขื ื•ืช ืœื”ื•ื“ืขื•ืช. ื ื™ืชืŸ ื’ื ืœืชืงืฉืจ ืžื”ื—ืฉื‘ื•ืŸ ืฉืœืš ืขื ืฉืจืช ืื—ืจ.", "status.admin_account": "ืคืชื—/ื™ ืžืžืฉืง ื ื™ื”ื•ืœ ืขื‘ื•ืจ @{name}", "status.admin_domain": "ืคืชื™ื—ืช ืžืžืฉืง ื ื™ื”ื•ืœ ืขื‘ื•ืจ {domain}", "status.admin_status": "Open this status in the moderation interface", @@ -762,7 +777,7 @@ "timeline_hint.resources.followers": "ืขื•ืงื‘ื™ื", "timeline_hint.resources.follows": "ื ืขืงื‘ื™ื", "timeline_hint.resources.statuses": "ื”ื•ื“ืขื•ืช ื™ืฉื ื•ืช ื™ื•ืชืจ", - "trends.counter_by_accounts": "{count, plural, one {ืื“ื {count}} other {{count} ื.ื ืฉื™ื}} {days, plural, one {ืžืื– ืืชืžื•ืœ} two {ื‘ื™ื•ืžื™ื™ื ื”ืื—ืจื•ื ื™ื} other {ื‘ืžืฉืš {days} ื”ื™ืžื™ื ื”ืื—ืจื•ื ื™ื}}", + "trends.counter_by_accounts": "{count, plural, one {ืื“ื ืื—ื“} other {{count} ื.ื ืฉื™ื}} {days, plural, one {ืžืื– ืืชืžื•ืœ} two {ื‘ื™ื•ืžื™ื™ื ื”ืื—ืจื•ื ื™ื} other {ื‘ืžืฉืš {days} ื”ื™ืžื™ื ื”ืื—ืจื•ื ื™ื}}", "trends.trending_now": "ื ื•ืฉืื™ื ื—ืžื™ื", "ui.beforeunload": "ื”ื˜ื™ื•ื˜ื ืชืื‘ื“ ืื ืชืขื–ื‘ื• ืืช ืžืกื˜ื•ื“ื•ืŸ.", "units.short.billion": "{count} ืžืœื™ืืจื“", diff --git a/app/javascript/mastodon/locales/hi.json b/app/javascript/mastodon/locales/hi.json index 372eb09fa7..89c71207f0 100644 --- a/app/javascript/mastodon/locales/hi.json +++ b/app/javascript/mastodon/locales/hi.json @@ -35,9 +35,7 @@ "account.follow_back": "เคซเฅ‰เคฒเฅ‹ เค•เคฐเฅ‡เค‚", "account.followers": "เคซเฅ‰เคฒเฅ‹เคตเคฐ", "account.followers.empty": "เค•เฅ‹เคˆ เคญเฅ€ เค‡เคธ เคฏเฅ‚เฅ›เคฐเฅ เค•เฅ‹ เฅžเฅ‰เคฒเฅ‹ เคจเคนเฅ€เค‚ เค•เคฐเคคเคพ เคนเฅˆ", - "account.followers_counter": "{count, plural, one {{counter} เค…เคจเฅเค—เคพเคฎเฅ€} other {{counter} เคธเคฎเคฐเฅเคฅเค•}}", "account.following": "เคซเฅ‰เคฒเฅ‹เค‡เค‚เค—", - "account.following_counter": "{count, plural, one {{counter} เคจเคฟเคฎเฅเคจเคฒเคฟเค–เคฟเคค} other {{counter} เคจเคฟเคฎเฅเคจเคฒเคฟเค–เคฟเคค}}", "account.follows.empty": "เคฏเคน เคฏเฅ‚เฅ›เคฐเฅ เค…เคญเฅ€ เคคเค• เค•เคฟเคธเฅ€ เค•เฅ‹ เคซเฅ‰เคฒเฅ‹ เคจเคนเฅ€เค‚ เค•เคฐเคคเคพ เคนเฅˆเฅค", "account.go_to_profile": "เคชเฅเคฐเฅ‹เคซเคพเค‡เคฒ เคฎเฅ‡เค‚ เคœเคพเคเค", "account.hide_reblogs": "@{name} เค•เฅ‡ เคฌเฅ‚เคธเฅเคŸ เค›เฅเคชเคพเคเค‚", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} เคจเฅ‡ เค†เคชเค•เฅ‹ เคซเฅ‰เคฒเฅ‹ เค•เคฐเคจเฅ‡ เค•เฅ‡ เคฒเคฟเค เค…เคจเฅเคฐเฅ‹เคง เค•เคฟเคฏเคพ เคนเฅˆ", "account.share": "@{name} เค•เฅ€ เคชเฅเคฐเฅ‹เคซเคพเค‡เคฒ เคถเฅ‡เคฏเคฐ เค•เคฐเฅ‡", "account.show_reblogs": "@{name} เค•เฅ‡ เคฌเฅ‚เคธเฅเคŸ เคฆเคฟเค–เคพเค", - "account.statuses_counter": "{count, plural, one {{counter} เคญเฅ‹เค‚เคชเฅ‚} other {{counter} เคญเฅ‹เค‚เคชเฅ‚}}", "account.unblock": "@{name} เค•เฅ‹ เค…เคจเคฌเฅเคฒเฅ‰เค• เค•เคฐเฅ‡เค‚", "account.unblock_domain": "{domain} เคฆเคฟเค–เคพเค", "account.unblock_short": "เค…เคจเคฌเฅเคฒเฅ‰เค•", @@ -80,6 +77,7 @@ "admin.dashboard.retention.cohort_size": "เคจเคฏเฅ‡ เค‰เคชเคฏเฅ‹เค—เค•เคฐเฅเคคเคพ", "admin.impact_report.instance_accounts": "เคฏเฅ‡ เค…เค•เคพเค‰เค‚เคŸ เคชเฅเคฐเฅ‹เคซเคพเค‡เคฒ เคฎเคฟเคŸเคพ เคฆเฅ‡เค—เคพ", "admin.impact_report.instance_followers": "เคนเคฎเคพเคฐเฅ‡ เคฏเฅ‚เคœเคฐเฅเคธ เค‡เคจ เคซเฅ‰เคฒเฅ‹เค…เคฐเฅเคธ เค•เฅ‹ เค–เฅ‹ เคฆเฅ‡เค‚เค—เฅ‡", + "admin.impact_report.instance_follows": "เค‰เคจเค•เฅ‡ เค‰เคชเคฏเฅ‹เค—เค•เคฐเฅเคคเคพ เค‡เคคเคจเฅ‡ เฅžเฅ‰เคฒเฅ‹เค…เคฐ เค–เฅ‹ เคฆเฅ‡เค‚เค—เฅ‡", "admin.impact_report.title": "เคชเฅเคฐเคญเคพเคตเค•เคพเค‚ เคธเคพเคฐเคพเค‚เคถ", "alert.rate_limited.message": "เค•เฅƒเคชเฅเคฏเคพ {retry_time, time, medium} เค•เฅ‡ เคฌเคพเคฆ เคฆเฅเคฌเคพเคฐเคพ เค•เฅ‹เคถเคฟเคถ เค•เคฐเฅ‡เค‚", "alert.rate_limited.title": "เคธเฅ€เคฎเคฟเคค เคฆเคฐ", @@ -88,6 +86,7 @@ "announcement.announcement": "เค˜เฅ‹เคทเคฃเคพ", "attachments_list.unprocessed": "(เค…เคธเค‚เคธเคพเคงเคฟเคค)", "audio.hide": "เคนเคพเคˆเคก เค‘เคกเคฟเคฏเฅ‹", + "block_modal.remote_users_caveat": "เคนเคฎ {domain} เค•เฅ‹ เค†เคชเค•เฅ‡ เคจเคฟเคฐเฅเคฃเคฏ เค•เคพ เคธเคฎเฅเคฎเคพเคจ เค•เคฐเคจเฅ‡ เค•เฅ‹ เค•เคนเฅ‡เค‚เค—เฅ‡เฅคเคนเคพเคฒเคพเค•เคฟ เค‡เคธเค•เฅ€ เค†เคชเฅ‚เคฐเฅเคคเคฟ เค•เคฟ เคชเฅเคฐเคคเฅเคฏเคพเคญเฅ‚เคคเคฟ เคจเคนเฅ€เค‚ เคนเฅ‡เฅค เค•เฅเคฏเฅ‹เค‚เค•เคฟ เค•เฅเค› เคธเคฐเฅเคตเคฐ เคฌเฅเคฒเฅ‰เค• เค•เฅ‹ เค…เคฒเค— เคคเคฐเคน เคธเฅ‡ เคจเคฟเคญเคพ เคธเค•เคคเฅ‡ เคนเฅ‡เฅค เค…เคญเฅ€ เคญเฅ€ เคธเคพเคฐเฅเคตเคœเคพเคจเคฟเค• เคชเฅ‹เคธเฅเคŸ เคฒเฅ‹เค—- เค‡เคจ เคฌเค—เฅˆเคฐ เค•เฅ‡ เค‰เคชเคฏเฅ‹เค—เค•เคฐเฅเคคเคพเค“เค‚ เค•เฅ‹ เคฆเคฟเค– เคธเค•เคคเฅ€ เคนเฅˆเค‚เฅค", "block_modal.show_less": "เค•เคฎ เคฆเคฟเค–เคพเคเค‚", "block_modal.show_more": "เค”เคฐ เคฆเคฟเค–เคพเคเค", "block_modal.they_cant_mention": "เคตเฅ‡ เค†เคชเค•เฅ‹ เคฎเฅ‡เค‚เคถเคจ เคฏเคพ เคซเฅ‰เคฒเฅ‹ เคจเคนเฅ€เค‚ เค•เคฐ เคธเค•เคคเฅ‡", @@ -205,6 +204,15 @@ "dismissable_banner.dismiss": "เคกเคฟเคธเคฎเคฟเคธ", "dismissable_banner.explore_links": "เค‡เคจ เคธเคฎเคพเคšเคพเคฐเฅ‹เค‚ เค•เฅ‡ เคฌเคพเคฐเฅ‡ เคฎเฅ‡เค‚ เคฒเฅ‹เค—เฅ‹เค‚ เคฆเฅเคตเคพเคฐเคพ เค‡เคธ เคชเคฐ เค”เคฐ เคกเฅ‡เคธเฅ‡เค‚เคŸเฅเคฐเคฒเฅ€เคธเฅ‡เคก เคจเฅ‡เคŸเคตเคฐเฅเค• เค•เฅ‡ เค…เคจเฅเคฏ เคธเคฐเฅเคตเคฐเฅ‹เค‚ เคชเคฐ เค…เคญเฅ€ เคฌเคพเคค เค•เฅ€ เคœเคพ เคฐเคนเฅ€ เคนเฅˆเฅค", "dismissable_banner.explore_tags": "เคฏเฅ‡ เคนเฅˆเคถเคŸเฅˆเค— เค…เคญเฅ€ เค‡เคธ เคชเคฐ เค”เคฐ เคกเฅ‡เคธเฅ‡เค‚เคŸเฅเคฐเคฒเฅ€เคธเฅ‡เคก เคจเฅ‡เคŸเคตเคฐเฅเค• เค•เฅ‡ เค…เคจเฅเคฏ เคธเคฐเฅเคตเคฐเฅ‹เค‚ เคชเคฐ เคฒเฅ‹เค—เฅ‹เค‚ เค•เฅ‡ เคฌเฅ€เคš เค•เคฐเฅเคทเคฃ เคชเฅเคฐเคพเคชเฅเคค เค•เคฐ เคฐเคนเฅ‡ เคนเฅˆเค‚เฅค", + "dismissable_banner.public_timeline": "เคฏเคน เคคเคพเคœเคพ เคธเคพเคฐเฅเคตเคœเคจเคฟเค• เคชเฅ‹เคธเฅเคŸ เคนเฅˆ เคœเคฟเคธเค•เคพ เคธเคพเคฎเคพเคœเคฟเค• เคตเฅ‡เคฌ {domain} เค•เฅ‡ เคฒเฅ‹เค—เฅ‹ เคฆเฅเคตเคพเคฐเคพ เค…เคจเฅเคธเคฐเคฃ เคนเฅ‹ เคฐเคนเคพ เคนเฅˆเค‚เฅค", + "domain_block_modal.block": "เคธเคฐเฅเคตเคฐ เคฌเฅเคฒเฅ‰เค• เค•เคฐเฅ‡เค‚", + "domain_block_modal.block_account_instead": "เค‡เคธเค•เฅ€ เคœเค—เคน เคฏเคน @{name} เคฐเค–เฅ‡เค‚", + "domain_block_modal.they_can_interact_with_old_posts": "เค‡เคธ เคธเคฐเฅเคตเคฐ เค•เฅ€ เคฒเฅ‹เค— เค†เคชเค•เฅ€ เคชเฅ‚เคฐเคพเคจเฅ€ เคชเฅ‹เคธเฅเคŸเฅเคธ เค•เคพ เค…เคจเฅเคธเคฐเคฃ เค•เคฟเคฏเคพ เคœเคพ sakta เคนเฅˆเฅค", + "domain_block_modal.they_cant_follow": "เค‡เคธ เคธเคฐเฅเคตเคฐ เคฎเฅ‡เคธเฅ‡ เค•เฅ‹เคˆ เคญเฅ€ เค†เคชเค•เคพ เค…เคจเฅเคธเคฐเคฃ เคจเคนเฅ€เค‚ เค•เคฐ เคธเค•เคคเคพเฅค", + "domain_block_modal.they_wont_know": "เค‰เคจเค•เฅ‹ เคชเคคเคพ เคจเคนเฅ€เค‚ เคšเคฒเฅ‡เค—เคพ เค•เคฟ เคตเฅ‡ เค…เคตเคฐเฅ‹เคงเคฟเคค เค•เคฟเค เค—เค เคนเฅˆเฅค", + "domain_block_modal.title": "เคกเฅ‹เคฎเฅ‡เคจ เคฌเฅเคฒเฅ‰เค• เค•เคฐเฅ‡เค‚", + "domain_pill.server": "เคธเคฐเฅเคตเคฐ", + "domain_pill.username": "เคฏเฅ‚เคœเคผเคฐเคจเฅ‡เคฎ", "embed.instructions": "เค…เคชเคจเฅ‡ เคตเฅ‡เคฌเคธเคพเค‡เคŸ เคชเคฐ, เคจเคฟเคšเฅ‡ เคฆเคฟเค เค•เฅ‹เคก เค•เฅ‹ เค•เฅ‰เคชเฅ€ เค•เคฐเค•เฅ‡, เค‡เคธ เคธเฅเคŸเฅ‡เคŸเคธ เค•เฅ‹ เคเคฎเฅเคฌเฅ‡เคก เค•เคฐเฅ‡เค‚", "embed.preview": "เคฏเคน เคเคธเคพ เคฆเคฟเค–เฅ‡เค—เคพ :", "emoji_button.activity": "เค—เคคเคฟเคตเคฟเคงเคฟ", @@ -274,6 +282,7 @@ "follow_request.authorize": "เค…เคงเคฟเค•เคพเคฐ เคฆเฅ‡เค‚", "follow_request.reject": "เค…เคธเฅเคตเฅ€เค•เคพเคฐ เค•เคฐเฅ‡เค‚", "follow_requests.unlocked_explanation": "เคนเคพเคฒเคพเคเค•เคฟ เค†เคชเค•เคพ เค–เคพเคคเคพ เคฒเฅ‰เค• เคจเคนเฅ€เค‚ เคนเฅˆ, เคซเคฟเคฐ เคญเฅ€ {domain} เคกเฅ‹เคฎเฅ‡เคจ เคธเฅเคŸเคพเคซ เคจเฅ‡ เคธเฅ‹เคšเคพ เค•เคฟ เค†เคช เค‡เคจ เค–เคพเคคเฅ‹เค‚ เค•เฅ‡ เคฎเฅˆเคจเฅเคฏเฅเค…เคฒ เค…เคจเฅเคฐเฅ‹เคงเฅ‹เค‚ เค•เฅ€ เคธเคฎเฅ€เค•เฅเคทเคพ เค•เคฐเคจเคพ เคšเคพเคนเคคเฅ‡ เคนเฅˆเค‚เฅค", + "follow_suggestions.dismiss": "เคฆเฅ‹เคฌเคพเคฐเคพ เคจ เคฆเคฟเค–เคพเคเค‚", "followed_tags": "เคซเฅ‰เคฒเฅ‹ เค•เคฟเค เค—เค เคนเฅˆเคถเคŸเฅˆเค—เฅเคธ", "footer.about": "เค…เคฌเคพเค‰เคŸ", "footer.directory": "เคชเฅเคฐเฅ‹เคซเคพเค‡เคฒเฅเคธ เคกเคพเคฏเคฐเฅ‡เค•เฅเคŸเคฐเฅ€", diff --git a/app/javascript/mastodon/locales/hr.json b/app/javascript/mastodon/locales/hr.json index ec8d62dbba..c8f6f01862 100644 --- a/app/javascript/mastodon/locales/hr.json +++ b/app/javascript/mastodon/locales/hr.json @@ -35,9 +35,7 @@ "account.follow_back": "Slijedi natrag", "account.followers": "Pratitelji", "account.followers.empty": "Nitko joลก ne prati korisnika/cu.", - "account.followers_counter": "{count, plural, one {{counter} pratitelj} other {{counter} pratitelja}}", "account.following": "Pratim", - "account.following_counter": "{count, plural, one {{counter} praฤ‡eni} few{{counter} praฤ‡ena} other {{counter} praฤ‡enih}}", "account.follows.empty": "Korisnik/ca joลก ne prati nikoga.", "account.go_to_profile": "Idi na profil", "account.hide_reblogs": "Sakrij boostove od @{name}", @@ -62,7 +60,6 @@ "account.requested_follow": "{name} zatraลพio/la je praฤ‡enje", "account.share": "Podijeli profil @{name}", "account.show_reblogs": "Prikaลพi boostove od @{name}", - "account.statuses_counter": "{count, plural, one {{counter} toot} other {{counter} toota}}", "account.unblock": "Deblokiraj @{name}", "account.unblock_domain": "Deblokiraj domenu {domain}", "account.unblock_short": "Deblokiraj", @@ -455,8 +452,6 @@ "server_banner.about_active_users": "Popis aktivnih korisnika proลกli mjesec", "server_banner.active_users": "aktivni korisnici", "server_banner.administered_by": "Administrator je:", - "server_banner.introduction": "{domain} je dio decentralizirane socijalne mreลพe koju pokreฤ‡e {mastodon}.", - "server_banner.learn_more": "Saznaj viลกe", "server_banner.server_stats": "Statistike posluลพitelja:", "sign_in_banner.create_account": "Stvori raฤun", "sign_in_banner.sign_in": "Prijavi se", diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json index 16dcea642d..1fcadc8f9c 100644 --- a/app/javascript/mastodon/locales/hu.json +++ b/app/javascript/mastodon/locales/hu.json @@ -35,9 +35,9 @@ "account.follow_back": "Viszontkรถvetรฉs", "account.followers": "Kรถvetล‘", "account.followers.empty": "Ezt a felhasznรกlรณt mรฉg senki sem kรถveti.", - "account.followers_counter": "{count, plural, one {{counter} Kรถvetล‘} other {{counter} Kรถvetล‘}}", + "account.followers_counter": "{count, plural, one {{counter} kรถvetล‘} other {{counter} kรถvetล‘}}", "account.following": "Kรถvetve", - "account.following_counter": "{count, plural, one {{counter} Kรถvetett} other {{counter} Kรถvetett}}", + "account.following_counter": "{count, plural, one {{counter} kรถvetett} other {{counter} kรถvetett}}", "account.follows.empty": "Ez a felhasznรกlรณ mรฉg senkit sem kรถvet.", "account.go_to_profile": "Ugrรกs a profilhoz", "account.hide_reblogs": "@{name} megtolรกsainak elrejtรฉse", @@ -63,7 +63,7 @@ "account.requested_follow": "{name} kรฉrte, hogy kรถvethessen", "account.share": "@{name} profiljรกnak megosztรกsa", "account.show_reblogs": "@{name} megtolรกsainak mutatรกsa", - "account.statuses_counter": "{count, plural, one {{counter} Bejegyzรฉs} other {{counter} Bejegyzรฉs}}", + "account.statuses_counter": "{count, plural, one {{counter} bejegyzรฉs} other {{counter} bejegyzรฉs}}", "account.unblock": "@{name} letiltรกsรกnak feloldรกsa", "account.unblock_domain": "{domain} domain tiltรกsรกnak feloldรกsa", "account.unblock_short": "Tiltรกs feloldรกsa", @@ -308,6 +308,8 @@ "follow_requests.unlocked_explanation": "Bรกr a fiรณkod nincs zรกrolva, a(z) {domain} csapata รบgy gondolta, hogy talรกn kรฉzzel szeretnรฉd ellenล‘rizni ezen fiรณkok kรถvetรฉsi kรฉrรฉseit.", "follow_suggestions.curated_suggestion": "A stรกb vรกlasztรกsa", "follow_suggestions.dismiss": "Ne jelenjen meg รบjra", + "follow_suggestions.featured_longer": "A {domain} csapata รกltal kรฉzzel kivรกlasztott", + "follow_suggestions.friends_of_friends_longer": "Nรฉpszerลฑ az รกltalad kรถvetett emberek kรถrรฉben", "follow_suggestions.hints.featured": "Ezt a profilt a(z) {domain} csapata vรกlasztotta ki.", "follow_suggestions.hints.friends_of_friends": "Ez a profil nรฉpszerลฑ az รกltalad kรถvetett emberek kรถrรฉben.", "follow_suggestions.hints.most_followed": "Ez a profil a leginkรกbb kรถvetett a(z) {domain} oldalon.", @@ -315,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Ez a profil hasonlรณ azokhoz a profilokhoz, melyeket nemrรฉg kezdtรฉl el kรถvetni.", "follow_suggestions.personalized_suggestion": "Szemรฉlyre szabott javaslat", "follow_suggestions.popular_suggestion": "Nรฉpszerลฑ javaslat", + "follow_suggestions.popular_suggestion_longer": "Nรฉpszerลฑ itt: {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Hasonlรณ azokhoz a profilokhoz, melyeket nemrรฉg kรถvettรฉl be", "follow_suggestions.view_all": "ร–sszes megtekintรฉse", "follow_suggestions.who_to_follow": "Kit รฉrdemes kรถvetni", "followed_tags": "Kรถvetett hashtagek", @@ -410,6 +414,8 @@ "limited_account_hint.action": "Profil megjelenรญtรฉse mindenkรฉppen", "limited_account_hint.title": "Ezt a profilt {domain} moderรกtorai elrejtettรฉk.", "link_preview.author": "{name} szerint", + "link_preview.more_from_author": "Tรถbb tล‘le: {name}", + "link_preview.shares": "{count, plural, one {{counter} bejegyzรฉs} other {{counter} bejegyzรฉs}}", "lists.account.add": "Hozzรกadรกs a listรกhoz", "lists.account.remove": "Eltรกvolรญtรกs a listรกbรณl", "lists.delete": "Lista tรถrlรฉse", @@ -469,6 +475,15 @@ "notification.follow": "{name} kรถvet tรฉged", "notification.follow_request": "{name} kรถvetni szeretne tรฉged", "notification.mention": "{name} megemlรญtett", + "notification.moderation-warning.learn_more": "Tovรกbbi informรกciรณ", + "notification.moderation_warning": "Moderรกciรณs figyelmeztetรฉst kaptรกl", + "notification.moderation_warning.action_delete_statuses": "Nรฉhรกny bejegyzรฉsedet eltรกvolรญtottรกk.", + "notification.moderation_warning.action_disable": "A fiรณkod le van tiltva.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Nรฉhรกny bejegyzรฉsedet kรฉnyesnek jelรถltรฉk.", + "notification.moderation_warning.action_none": "A fiรณkod moderรกciรณs figyelmeztetรฉst kapott.", + "notification.moderation_warning.action_sensitive": "A bejegyzรฉseid mostantรณl รฉrzรฉkenynek lesznek jelรถlve.", + "notification.moderation_warning.action_silence": "A fiรณkod korlรกtozรกsra kerรผlt.", + "notification.moderation_warning.action_suspend": "A fiรณkod felfรผggesztรฉsre kerรผlt.", "notification.own_poll": "A szavazรกsod vรฉget รฉrt", "notification.poll": "Egy szavazรกs, melyben rรฉszt vettรฉl, vรฉget รฉrt", "notification.reblog": "{name} megtolta a bejegyzรฉsedet", @@ -681,13 +696,13 @@ "server_banner.about_active_users": "Az elmรบlt 30 napban ezt a kiszolgรกlรณt hasznรกlรณk szรกma (Havi aktรญv felhasznรกlรณk)", "server_banner.active_users": "aktรญv felhasznรกlรณ", "server_banner.administered_by": "Adminisztrรกtor:", - "server_banner.introduction": "{domain} rรฉsze egy decentralizรกlt kรถzรถssรฉgi hรกlรณnak, melyet a {mastodon} hajt meg.", - "server_banner.learn_more": "Tudj meg tรถbbet", + "server_banner.is_one_of_many": "{domain} egy a jelentล‘s, fรผggetlen Mastodon kiszolgรกlรณk kรถzรผl, melyet a fediverzumban valรณ rรฉszvรฉtelre hasznรกlhatsz.", "server_banner.server_stats": "Kiszolgรกlรณstatisztika:", "sign_in_banner.create_account": "Fiรณk lรฉtrehozรกsa", + "sign_in_banner.follow_anyone": "Kรถvess bรกrkit a fediverzumon keresztรผl, รฉs lรกss mindent idล‘rendi sorrendben. Algoritmusok, hirdetรฉsek, kattintรกsvadรกszat nรฉlkรผl.", + "sign_in_banner.mastodon_is": "A Mastodon a legjobb mรณdja annak, hogy a tรถrtรฉnรฉsekkel kapcsolatban naprakรฉsz maradj.", "sign_in_banner.sign_in": "Bejelentkezรฉs", "sign_in_banner.sso_redirect": "Bejelentkezรฉs vagy regisztrรกciรณ", - "sign_in_banner.text": "Jelentkezz be profilok vagy hashtagek kรถvetรฉsรฉhez, kedvencnek jelรถlรฉsรฉhez, bejegyzรฉsek megosztรกsรกhoz, megvรกlaszolรกsรกhoz. A fiรณkodbรณl mรกs kiszolgรกlรณkon is kommunikรกlhatsz.", "status.admin_account": "Moderรกciรณs felรผlet megnyitรกsa @{name} fiรณkhoz", "status.admin_domain": "Moderรกciรณs felรผlet megnyitรกsa {domain} esetรฉben", "status.admin_status": "Bejegyzรฉs megnyitรกsa a moderรกciรณs felรผleten", diff --git a/app/javascript/mastodon/locales/hy.json b/app/javascript/mastodon/locales/hy.json index 7310104bf9..b4abe9bf09 100644 --- a/app/javascript/mastodon/locales/hy.json +++ b/app/javascript/mastodon/locales/hy.json @@ -28,9 +28,7 @@ "account.follow": "ี€ีฅีฟีฅึ‚ีฅีฌ", "account.followers": "ี€ีฅีฟีฅึ‚ีธีฒีถีฅึ€", "account.followers.empty": "ิฑีตีฝ ึ…ีฃีฟีกีฟีซึ€ีธีปีจ ีคีฅีผ ีธีน ีดีงีฏ ีนีซ ีฐีฅีฟีฅึ‚ีธึ‚ีดึ‰", - "account.followers_counter": "{count, plural, one {{counter} ี€ีฅีฟีฅึ‚ีธึ€ีค} other {{counter} ี€ีฅีฟีฅึ‚ีธึ€ีค}}", "account.following": "ี€ีฅีฟีฅึ‚ีกีฎ", - "account.following_counter": "{count, plural, one {{counter} ี€ีฅีฟีฅึ‚ีกีฎ} other {{counter} ี€ีฅีฟีฅึ‚ีกีฎ}}", "account.follows.empty": "ิฑีตีฝ ึ…ีฃีฟีกีฟีงึ€ีจ ีคีฅีผ ีธีน ีดีงีฏีซ ีนีซ ีฐีฅีฟีฅึ‚ีธึ‚ีดึ‰", "account.go_to_profile": "ิณีถีกีฌ ีกีถีฑีถีกีฏีกีถ ีฐีกีทีซึ‚", "account.hide_reblogs": "ินีกึ„ึีถีฅีฌ @{name}ึŠีซ ีฟีกึ€ีกีฎีกีฎีถีฅึ€ีจ", @@ -52,7 +50,6 @@ "account.requested_follow": "{name}-ีจ ึีกีถีฏีกีถีธึ‚ีด ีง ีฐีฅีฟีฅึ‚ีฅีฌ ึ„ีฅีฆ", "account.share": "ิฟีซีฝีธึ‚ีฅีฌ @{name}ึŠีซ ีงีปีธีพ", "account.show_reblogs": "ี‘ีธึ‚ึีกีคึ€ีฅีฌ @{name}ึŠีซ ีฟีกึ€ีกีฎีกีฎีถีฅึ€ีจ", - "account.statuses_counter": "{count, plural, one {{counter} ิณึ€ีกีผีธึ‚ีด} other {{counter} ิณึ€ีกีผีธึ‚ีดีถีฅึ€}}", "account.unblock": "ิฑีบีกีกึ€ีฃีฅีฌีกึƒีกีฏีฅีฌ @{name}ึŠีซีถ", "account.unblock_domain": "ี‘ีธึ‚ึีกีคึ€ีฅีฌ {domain} ีฉีกึ„ึีธึ‚ีกีฎ ีฟีซึ€ีธีตีฉีซ ีฃึ€ีกีผีธึ‚ีดีถีฅึ€ีจ", "account.unblock_short": "ิฑึ€ีฃีฅีฌีกีขีกึีฅีฌ", @@ -441,8 +438,6 @@ "search_results.title": "ีˆึ€ีธีถีฅีฌ {q}-ีถ", "server_banner.active_users": "ีกีฏีฟีซึ‚ ีดีกึ€ีคีซีฏ", "server_banner.administered_by": "ิฟีกีผีกึ‚ีกึ€ีธีฒ", - "server_banner.introduction": "{domain}-ีจ ีฐีกีถีคีซีกีฝีถีธึ‚ีด ีง ีกีบีกีฏีฅีถีฟึ€ีธีถ ีฝีธึ. ึีกีถึีซ ีดีกีฝ, ีฝีฟีฅีฒีฎีธึ‚ีกีฎ {mastodon}-ีธีพึ‰\n", - "server_banner.learn_more": "ิปีดีกีถีกีฌ ีกึ‚ีฅีฌีซีถ", "server_banner.server_stats": "ีีฅึ€ีธึ‚ีฅึ€ีซ ีพีซีณีกีฏีจ", "sign_in_banner.create_account": "ีีฟีฅีฒีฎีฅีฌ ีฐีกีทีซึ‚", "sign_in_banner.sign_in": "ี„ีธึ‚ีฟึ„", diff --git a/app/javascript/mastodon/locales/ia.json b/app/javascript/mastodon/locales/ia.json index 73634b99c1..ace6402ee1 100644 --- a/app/javascript/mastodon/locales/ia.json +++ b/app/javascript/mastodon/locales/ia.json @@ -19,7 +19,7 @@ "account.block_domain": "Blocar dominio {domain}", "account.block_short": "Blocar", "account.blocked": "Blocate", - "account.browse_more_on_origin_server": "Navigar plus sur le profilo original", + "account.browse_more_on_origin_server": "Explorar plus sur le profilo original", "account.cancel_follow_request": "Cancellar sequimento", "account.copy": "Copiar ligamine a profilo", "account.direct": "Mentionar privatemente @{name}", @@ -58,7 +58,7 @@ "account.open_original_page": "Aperir le pagina original", "account.posts": "Messages", "account.posts_with_replies": "Messages e responsas", - "account.report": "Signalar @{name}", + "account.report": "Reportar @{name}", "account.requested": "Attendente le approbation. Clicca pro cancellar le requesta de sequer", "account.requested_follow": "{name} ha requestate de sequer te", "account.share": "Compartir profilo de @{name}", @@ -111,7 +111,7 @@ "bundle_modal_error.message": "Un error ha occurrite durante le cargamento de iste componente.", "bundle_modal_error.retry": "Tentar novemente", "closed_registrations.other_server_instructions": "Perque Mastodon es decentralisate, tu pote crear un conto sur un altere servitor e totevia interager con iste servitor.", - "closed_registrations_modal.description": "Crear un conto in {domain} actualmente non es possibile, ma considera que non es necessari haber un conto specificamente sur {domain} pro usar Mastodon.", + "closed_registrations_modal.description": "Crear un conto sur {domain} non es actualmente possibile, ma considera que non es necessari haber un conto specificamente sur {domain} pro usar Mastodon.", "closed_registrations_modal.find_another_server": "Cercar un altere servitor", "closed_registrations_modal.preamble": "Mastodon es decentralisate, dunque, non importa ubi tu crea tu conto, tu pote sequer e communicar con omne persona sur iste servitor. Tu pote mesmo hospitar tu proprie servitor!", "closed_registrations_modal.title": "Crear un conto sur Mastodon", @@ -122,7 +122,7 @@ "column.direct": "Mentiones private", "column.directory": "Navigar profilos", "column.domain_blocks": "Dominios blocate", - "column.favourites": "Favoritos", + "column.favourites": "Favorites", "column.firehose": "Fluxos in directo", "column.follow_requests": "Requestas de sequimento", "column.home": "Initio", @@ -204,7 +204,7 @@ "disabled_account_banner.account_settings": "Parametros de conto", "disabled_account_banner.text": "Tu conto {disabledAccount} es actualmente disactivate.", "dismissable_banner.community_timeline": "Ecce le messages public le plus recente del personas con contos sur {domain}.", - "dismissable_banner.dismiss": "Dimitter", + "dismissable_banner.dismiss": "Clauder", "dismissable_banner.explore_links": "Istes es le articulos de novas que se condivide le plus sur le rete social hodie. Le articulos de novas le plus recente, publicate per plus personas differente, se classifica plus in alto.", "dismissable_banner.explore_statuses": "Ecce le messages de tote le rete social que gania popularitate hodie. Le messages plus nove con plus impulsos e favorites se classifica plus in alto.", "dismissable_banner.explore_tags": "Ecce le hashtags que gania popularitate sur le rete social hodie. Le hashtags usate per plus personas differente se classifica plus in alto.", @@ -212,11 +212,11 @@ "domain_block_modal.block": "Blocar le servitor", "domain_block_modal.block_account_instead": "Blocar @{name} in su loco", "domain_block_modal.they_can_interact_with_old_posts": "Le personas de iste servitor pote interager con tu messages ancian.", - "domain_block_modal.they_cant_follow": "Nulle persona ab iste servitor pote sequer te.", - "domain_block_modal.they_wont_know": "Illes non sapera que illes ha essite blocate.", + "domain_block_modal.they_cant_follow": "Necuno de iste servitor pote sequer te.", + "domain_block_modal.they_wont_know": "Ille non sapera que ille ha essite blocate.", "domain_block_modal.title": "Blocar dominio?", - "domain_block_modal.you_will_lose_followers": "Omne sequitores ab iste servitor essera removite.", - "domain_block_modal.you_wont_see_posts": "Tu non videra messages e notificationes ab usatores sur iste servitor.", + "domain_block_modal.you_will_lose_followers": "Tote tu sequitores de iste servitor essera removite.", + "domain_block_modal.you_wont_see_posts": "Tu non videra messages e notificationes de usatores sur iste servitor.", "domain_pill.activitypub_lets_connect": "Illo te permitte connecter e interager con personas non solmente sur Mastodon, ma tamben sur altere applicationes social.", "domain_pill.activitypub_like_language": "ActivityPub es como le linguage commun que Mastodon parla con altere retes social.", "domain_pill.server": "Servitor", @@ -274,7 +274,7 @@ "error.unexpected_crash.next_steps": "Tenta refrescar le pagina. Si isto non remedia le problema, es possibile que tu pote totevia usar Mastodon per medio de un altere navigator o application native.", "error.unexpected_crash.next_steps_addons": "Tenta disactivar istes e refrescar le pagina. Si isto non remedia le problema, es possibile que tu pote totevia usar Mastodon per medio de un altere navigator o application native.", "errors.unexpected_crash.copy_stacktrace": "Copiar le traciamento del pila al area de transferentia", - "errors.unexpected_crash.report_issue": "Signalar un defecto", + "errors.unexpected_crash.report_issue": "Reportar problema", "explore.search_results": "Resultatos de recerca", "explore.suggested_follows": "Personas", "explore.title": "Explorar", @@ -307,7 +307,9 @@ "follow_request.reject": "Rejectar", "follow_requests.unlocked_explanation": "Benque tu conto non es serrate, le personal de {domain} pensa que es un bon idea que tu revide manualmente le sequente requestas de iste contos.", "follow_suggestions.curated_suggestion": "Selection del equipa", - "follow_suggestions.dismiss": "Non monstrar novemente", + "follow_suggestions.dismiss": "Non monstrar de novo", + "follow_suggestions.featured_longer": "Seligite con cura per le equipa de {domain}", + "follow_suggestions.friends_of_friends_longer": "Popular inter le gente que tu seque", "follow_suggestions.hints.featured": "Iste profilo ha essite seligite manualmente per le equipa de {domain}.", "follow_suggestions.hints.friends_of_friends": "Iste profilo es popular inter le gente que tu seque.", "follow_suggestions.hints.most_followed": "Iste profilo es un del plus sequites sur {domain}.", @@ -315,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Iste profilo es similar al profilos que tu ha recentemente sequite.", "follow_suggestions.personalized_suggestion": "Suggestion personalisate", "follow_suggestions.popular_suggestion": "Suggestion personalisate", + "follow_suggestions.popular_suggestion_longer": "Popular sur {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Similar al profilos que tu ha sequite recentemente", "follow_suggestions.view_all": "Vider toto", "follow_suggestions.who_to_follow": "Qui sequer", "followed_tags": "Hashtags sequite", @@ -350,7 +354,7 @@ "home.pending_critical_update.link": "Vider actualisationes", "home.pending_critical_update.title": "Actualisation de securitate critic disponibile!", "home.show_announcements": "Monstrar annuncios", - "interaction_modal.description.favourite": "Con un conto sur Mastodon, tu pote marcar iste message como favorite pro informar le autor que tu lo apprecia e salveguarda pro plus tarde.", + "interaction_modal.description.favourite": "Con un conto sur Mastodon, tu pote marcar iste message como favorite pro informar le autor que tu lo apprecia e lo salva pro plus tarde.", "interaction_modal.description.follow": "Con un conto sur Mastodon, tu pote sequer {name} e reciper su messages in tu fluxo de initio.", "interaction_modal.description.reblog": "Con un conto sur Mastodon, tu pote impulsar iste message pro condivider lo con tu proprie sequitores.", "interaction_modal.description.reply": "Con un conto sur Mastodon, tu pote responder a iste message.", @@ -385,7 +389,7 @@ "keyboard_shortcuts.hotkey": "Clave accelerator", "keyboard_shortcuts.legend": "Monstrar iste legenda", "keyboard_shortcuts.local": "Aperir le chronologia local", - "keyboard_shortcuts.mention": "Mentionar le author", + "keyboard_shortcuts.mention": "Mentionar le autor", "keyboard_shortcuts.muted": "Aperir lista de usatores silentiate", "keyboard_shortcuts.my_profile": "Aperir tu profilo", "keyboard_shortcuts.notifications": "Aperir columna de notificationes", @@ -408,8 +412,10 @@ "lightbox.next": "Sequente", "lightbox.previous": "Precedente", "limited_account_hint.action": "Monstrar profilo in omne caso", - "limited_account_hint.title": "Iste profilo esseva celate per le moderatores de {domain}.", + "limited_account_hint.title": "Iste profilo ha essite celate per le moderatores de {domain}.", "link_preview.author": "Per {name}", + "link_preview.more_from_author": "Plus de {name}", + "link_preview.shares": "{count, plural, one {{counter} message} other {{counter} messages}}", "lists.account.add": "Adder al lista", "lists.account.remove": "Remover del lista", "lists.delete": "Deler lista", @@ -428,12 +434,12 @@ "loading_indicator.label": "Carganteโ€ฆ", "media_gallery.toggle_visible": "{number, plural, one {Celar imagine} other {Celar imagines}}", "moved_to_account_banner.text": "Tu conto {disabledAccount} es actualmente disactivate perque tu ha cambiate de conto a {movedToAccount}.", - "mute_modal.hide_from_notifications": "Celar ab notificationes", + "mute_modal.hide_from_notifications": "Celar in notificationes", "mute_modal.hide_options": "Celar optiones", "mute_modal.indefinite": "Usque io dissilentia iste persona", "mute_modal.show_options": "Monstrar optiones", - "mute_modal.they_can_mention_and_follow": "Illes pote mentionar te e sequer te, ma tu non potera vider los.", - "mute_modal.they_wont_know": "Illes non sapera que illes ha essite silentiate.", + "mute_modal.they_can_mention_and_follow": "Ille pote mentionar te e sequer te, ma tu non potera vider le.", + "mute_modal.they_wont_know": "Ille non sapera que ille ha essite silentiate.", "mute_modal.title": "Silentiar le usator?", "mute_modal.you_wont_see_mentions": "Tu non videra le messages que mentiona iste persona.", "mute_modal.you_wont_see_posts": "Iste persona pote totevia vider tu messages, ma tu non videra le sues.", @@ -447,13 +453,13 @@ "navigation_bar.discover": "Discoperir", "navigation_bar.domain_blocks": "Dominios blocate", "navigation_bar.explore": "Explorar", - "navigation_bar.favourites": "Favoritos", + "navigation_bar.favourites": "Favorites", "navigation_bar.filters": "Parolas silentiate", "navigation_bar.follow_requests": "Requestas de sequimento", "navigation_bar.followed_tags": "Hashtags sequite", "navigation_bar.follows_and_followers": "Sequites e sequitores", "navigation_bar.lists": "Listas", - "navigation_bar.logout": "Clauder le session", + "navigation_bar.logout": "Clauder session", "navigation_bar.mutes": "Usatores silentiate", "navigation_bar.opened_in_classic_interface": "Messages, contos e altere paginas specific es aperite per predefinition in le interfacie web classic.", "navigation_bar.personal": "Personal", @@ -463,12 +469,21 @@ "navigation_bar.search": "Cercar", "navigation_bar.security": "Securitate", "not_signed_in_indicator.not_signed_in": "Es necessari aperir session pro acceder a iste ressource.", - "notification.admin.report": "{name} ha signalate {target}", + "notification.admin.report": "{name} ha reportate {target}", "notification.admin.sign_up": "{name} se ha inscribite", "notification.favourite": "{name} ha marcate tu message como favorite", "notification.follow": "{name} te ha sequite", "notification.follow_request": "{name} ha requestate de sequer te", "notification.mention": "{name} te ha mentionate", + "notification.moderation-warning.learn_more": "Apprender plus", + "notification.moderation_warning": "Tu ha recipite un advertimento de moderation", + "notification.moderation_warning.action_delete_statuses": "Alcunes de tu messages ha essite removite.", + "notification.moderation_warning.action_disable": "Tu conto ha essite disactivate.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Alcunes de tu messages ha essite marcate como sensibile.", + "notification.moderation_warning.action_none": "Tu conto ha recipite un advertimento de moderation.", + "notification.moderation_warning.action_sensitive": "Tu messages essera marcate como sensibile a partir de ora.", + "notification.moderation_warning.action_silence": "Tu conto ha essite limitate.", + "notification.moderation_warning.action_suspend": "Tu conto ha essite suspendite.", "notification.own_poll": "Tu sondage ha finite", "notification.poll": "Un sondage in le qual tu ha votate ha finite", "notification.reblog": "{name} ha impulsate tu message", @@ -480,15 +495,15 @@ "notification.status": "{name} ha justo ora publicate", "notification.update": "{name} ha modificate un message", "notification_requests.accept": "Acceptar", - "notification_requests.dismiss": "Dimitter", + "notification_requests.dismiss": "Clauder", "notification_requests.notifications_from": "Notificationes de {name}", "notification_requests.title": "Notificationes filtrate", "notifications.clear": "Rader notificationes", "notifications.clear_confirmation": "Es tu secur que tu vole rader permanentemente tote tu notificationes?", - "notifications.column_settings.admin.report": "Nove signalationes:", + "notifications.column_settings.admin.report": "Nove reportos:", "notifications.column_settings.admin.sign_up": "Nove inscriptiones:", "notifications.column_settings.alert": "Notificationes de scriptorio", - "notifications.column_settings.favourite": "Favoritos:", + "notifications.column_settings.favourite": "Favorites:", "notifications.column_settings.filter_bar.advanced": "Monstrar tote le categorias", "notifications.column_settings.filter_bar.category": "Barra de filtro rapide", "notifications.column_settings.follow": "Nove sequitores:", @@ -505,7 +520,7 @@ "notifications.column_settings.update": "Modificationes:", "notifications.filter.all": "Toto", "notifications.filter.boosts": "Impulsos", - "notifications.filter.favourites": "Favoritos", + "notifications.filter.favourites": "Favorites", "notifications.filter.follows": "Sequites", "notifications.filter.mentions": "Mentiones", "notifications.filter.polls": "Resultatos del sondage", @@ -608,7 +623,7 @@ "relative_time.today": "hodie", "reply_indicator.attachments": "{count, plural, one {# annexo} other {# annexos}}", "reply_indicator.cancel": "Cancellar", - "reply_indicator.poll": "Inquesta", + "reply_indicator.poll": "Sondage", "report.block": "Blocar", "report.block_explanation": "Tu non videra le messages de iste persona. Ille non potera vider tu messages o sequer te. Ille potera saper de esser blocate.", "report.categories.legal": "Juridic", @@ -622,7 +637,7 @@ "report.close": "Facite", "report.comment.title": "Ha il altere cosas que nos deberea saper?", "report.forward": "Reinviar a {target}", - "report.forward_hint": "Le conto es de un altere servitor. Inviar un copia anonymisate del signalation a illo tamben?", + "report.forward_hint": "Le conto es de un altere servitor. Inviar un copia anonymisate del reporto a illo tamben?", "report.mute": "Silentiar", "report.mute_explanation": "Tu non videra le messages de iste persona. Ille pote totevia sequer te e vider tu messages e non sapera de esser silentiate.", "report.next": "Sequente", @@ -642,11 +657,11 @@ "report.statuses.subtitle": "Selige tote le responsas appropriate", "report.statuses.title": "Existe alcun messages que appoia iste reporto?", "report.submit": "Submitter", - "report.target": "Signalamento de {target}", + "report.target": "Reportage de {target}", "report.thanks.take_action": "Ecce tu optiones pro controlar lo que tu vide sur Mastodon:", "report.thanks.take_action_actionable": "Durante que nos revide isto, tu pote prender mesuras contra @{name}:", "report.thanks.title": "Non vole vider isto?", - "report.thanks.title_actionable": "Gratias pro signalar, nos investigara isto.", + "report.thanks.title_actionable": "Gratias pro reportar, nos investigara isto.", "report.unfollow": "Cessar de sequer @{name}", "report.unfollow_explanation": "Tu seque iste conto. Pro non plus vider su messages in tu fluxo de initio, cessa de sequer lo.", "report_notification.attached_statuses": "{count, plural, one {{count} message} other {{count} messages}} annexate", @@ -664,7 +679,7 @@ "search.quick_action.status_search": "Messages correspondente a {x}", "search.search_or_paste": "Cerca o colla un URL", "search_popout.full_text_search_disabled_message": "Non disponibile sur {domain}.", - "search_popout.full_text_search_logged_out_message": "Solmente disponibile al initiar le session.", + "search_popout.full_text_search_logged_out_message": "Solmente disponibile post aperir session.", "search_popout.language_code": "Codice de lingua ISO", "search_popout.options": "Optiones de recerca", "search_popout.quick_actions": "Actiones rapide", @@ -681,13 +696,13 @@ "server_banner.about_active_users": "Personas que ha usate iste servitor in le ultime 30 dies (usatores active per mense)", "server_banner.active_users": "usatores active", "server_banner.administered_by": "Administrate per:", - "server_banner.introduction": "{domain} face parte del rete social decentralisate actionate per {mastodon}.", - "server_banner.learn_more": "Apprender plus", + "server_banner.is_one_of_many": "{domain} es un de multe servitores independente de Mastodon que tu pote usar pro participar in le fediverso.", "server_banner.server_stats": "Statos del servitor:", "sign_in_banner.create_account": "Crear un conto", + "sign_in_banner.follow_anyone": "Seque quicunque in le fediverso, e tu videra toto in ordine chronologic. Sin algorithmo, sin publicitate, sin titulos de esca.", + "sign_in_banner.mastodon_is": "Mastodon es le melior maniera de sequer lo que passa.", "sign_in_banner.sign_in": "Aperir session", "sign_in_banner.sso_redirect": "Aperir session o crear conto", - "sign_in_banner.text": "Aperi session pro sequer profilos o hashtags, marcar messages como favorite, e condivider e responder a messages. Tu pote etiam interager desde tu conto sur un altere servitor.", "status.admin_account": "Aperir le interfacie de moderation pro @{name}", "status.admin_domain": "Aperir le interfacie de moderation pro {domain}", "status.admin_status": "Aperir iste message in le interfacie de moderation", @@ -704,7 +719,7 @@ "status.edited": "Ultime modification le {date}", "status.edited_x_times": "Modificate {count, plural, one {{count} vice} other {{count} vices}}", "status.embed": "Incastrar", - "status.favourite": "Adder al favoritos", + "status.favourite": "Adder al favorites", "status.favourites": "{count, plural, one {favorite} other {favorites}}", "status.filter": "Filtrar iste message", "status.filtered": "Filtrate", @@ -733,7 +748,7 @@ "status.replied_to": "Respondite a {name}", "status.reply": "Responder", "status.replyAll": "Responder al discussion", - "status.report": "Signalar @{name}", + "status.report": "Reportar @{name}", "status.sensitive_warning": "Contento sensibile", "status.share": "Compartir", "status.show_filter_reason": "Monstrar in omne caso", @@ -744,12 +759,12 @@ "status.show_original": "Monstrar original", "status.title.with_attachments": "{user} ha publicate {attachmentCount, plural, one {un annexo} other {{attachmentCount} annexos}}", "status.translate": "Traducer", - "status.translated_from_with": "Traducite ab {lang} usante {provider}", + "status.translated_from_with": "Traducite de {lang} usante {provider}", "status.uncached_media_warning": "Previsualisation non disponibile", "status.unmute_conversation": "Non plus silentiar conversation", "status.unpin": "Disfixar del profilo", "subscribed_languages.lead": "Solmente le messages in le linguas seligite apparera in tu chronologias de initio e de listas post le cambiamento. Selige necun pro reciper messages in tote le linguas.", - "subscribed_languages.save": "Salveguardar le cambiamentos", + "subscribed_languages.save": "Salvar le cambiamentos", "subscribed_languages.target": "Cambiar le linguas subscribite pro {target}", "tabs_bar.home": "Initio", "tabs_bar.notifications": "Notificationes", @@ -783,7 +798,7 @@ "upload_modal.choose_image": "Seliger un imagine", "upload_modal.description_placeholder": "Cinque expertos del zoo jam bibeva whisky frigide", "upload_modal.detect_text": "Deteger texto de un imagine", - "upload_modal.edit_media": "Modificar le medio", + "upload_modal.edit_media": "Modificar multimedia", "upload_modal.hint": "Clicca o trahe le circulo sur le previsualisation pro eliger le puncto focal que essera sempre visibile sur tote le miniaturas.", "upload_modal.preparing_ocr": "Preparation del OCRโ€ฆ", "upload_modal.preview_label": "Previsualisation ({ratio})", diff --git a/app/javascript/mastodon/locales/id.json b/app/javascript/mastodon/locales/id.json index 90a48b6343..f4e5e1baea 100644 --- a/app/javascript/mastodon/locales/id.json +++ b/app/javascript/mastodon/locales/id.json @@ -35,9 +35,7 @@ "account.follow_back": "Ikuti balik", "account.followers": "Pengikut", "account.followers.empty": "Pengguna ini belum ada pengikut.", - "account.followers_counter": "{count, plural, other {{counter} Pengikut}}", "account.following": "Mengikuti", - "account.following_counter": "{count, plural, other {{counter} Mengikuti}}", "account.follows.empty": "Pengguna ini belum mengikuti siapa pun.", "account.go_to_profile": "Buka profil", "account.hide_reblogs": "Sembunyikan boosts dari @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} ingin mengikuti Anda", "account.share": "Bagikan profil @{name}", "account.show_reblogs": "Tampilkan boost dari @{name}", - "account.statuses_counter": "{count, plural, other {{counter} Kiriman}}", "account.unblock": "Buka blokir @{name}", "account.unblock_domain": "Buka blokir domain {domain}", "account.unblock_short": "Buka blokir", @@ -123,6 +120,7 @@ "column.directory": "Jelajahi profil", "column.domain_blocks": "Domain tersembunyi", "column.favourites": "Favorit", + "column.firehose": "Feed yang sedang berlangsung", "column.follow_requests": "Permintaan mengikuti", "column.home": "Beranda", "column.lists": "List", @@ -143,7 +141,9 @@ "community.column_settings.remote_only": "Hanya jarak jauh", "compose.language.change": "Ganti bahasa", "compose.language.search": "Telusuri bahasa...", + "compose.published.body": "Postingan diterbitkan.", "compose.published.open": "Buka", + "compose.saved.body": "Postingan tersimpan.", "compose_form.direct_message_warning_learn_more": "Pelajari lebih lanjut", "compose_form.encryption_warning": "Kiriman di Mastodon tidak dienkripsi secara end-to-end. Jangan bagikan informasi sensitif melalui Mastodon.", "compose_form.hashtag_warning": "Kiriman ini tidak akan didaftarkan di bawah tagar apapun selama tidak diatur ke publik. Hanya kiriman publik yang dapat dicari dengan tagar.", @@ -151,11 +151,19 @@ "compose_form.lock_disclaimer.lock": "terkunci", "compose_form.placeholder": "Apa yang ada di pikiran Anda?", "compose_form.poll.duration": "Durasi japat", + "compose_form.poll.multiple": "Pilihan ganda", + "compose_form.poll.option_placeholder": "Opsi {number}", + "compose_form.poll.single": "Pilih Satu", "compose_form.poll.switch_to_multiple": "Ubah japat menjadi pilihan ganda", "compose_form.poll.switch_to_single": "Ubah japat menjadi pilihan tunggal", + "compose_form.poll.type": "Gaya", + "compose_form.publish": "Postingan", "compose_form.publish_form": "Terbitkan", + "compose_form.reply": "Balas", + "compose_form.save_changes": "Perbarui", "compose_form.spoiler.marked": "Hapus peringatan tentang isi konten", "compose_form.spoiler.unmarked": "Tambahkan peringatan tentang isi konten", + "compose_form.spoiler_placeholder": "Peringatan konten (opsional)", "confirmation_modal.cancel": "Batal", "confirmations.block.confirm": "Blokir", "confirmations.cancel_follow_request.confirm": "Batalkan permintaan", @@ -166,12 +174,15 @@ "confirmations.delete_list.message": "Apakah Anda yakin untuk menghapus daftar ini secara permanen?", "confirmations.discard_edit_media.confirm": "Buang", "confirmations.discard_edit_media.message": "Anda belum menyimpan perubahan deskripsi atau pratinjau media, buang saja?", + "confirmations.domain_block.confirm": "Blokir server", "confirmations.domain_block.message": "Apakah Anda benar-benar yakin untuk memblokir keseluruhan {domain}? Dalam kasus tertentu beberapa pemblokiran atau penyembunyian lebih baik.", "confirmations.edit.confirm": "Ubah", + "confirmations.edit.message": "Mengubah akan menimpa pesan yang sedang anda tulis. Apakah anda yakin ingin melanjutkan?", "confirmations.logout.confirm": "Keluar", "confirmations.logout.message": "Apakah Anda yakin ingin keluar?", "confirmations.mute.confirm": "Bisukan", "confirmations.redraft.confirm": "Hapus dan susun ulang", + "confirmations.redraft.message": "Apakah anda yakin ingin menghapus postingan ini dan menyusun ulang postingan ini? Favorit dan peningkatan akan hilang, dan balasan ke postingan asli tidak akan terhubung ke postingan manapun.", "confirmations.reply.confirm": "Balas", "confirmations.reply.message": "Membalas sekarang akan menimpa pesan yang sedang Anda buat. Anda yakin ingin melanjutkan?", "confirmations.unfollow.confirm": "Berhenti mengikuti", @@ -180,6 +191,7 @@ "conversation.mark_as_read": "Tandai sudah dibaca", "conversation.open": "Lihat percakapan", "conversation.with": "Dengan {names}", + "copy_icon_button.copied": "Disalin ke clipboard", "copypaste.copied": "Disalin", "copypaste.copy_to_clipboard": "Salin ke clipboard", "directory.federated": "Dari fediverse yang dikenal", @@ -191,7 +203,27 @@ "dismissable_banner.community_timeline": "Ini adalah kiriman publik terkini dari orang yang akunnya berada di {domain}.", "dismissable_banner.dismiss": "Abaikan", "dismissable_banner.explore_links": "Cerita berita ini sekarang sedang dibicarakan oleh orang di server ini dan lainnya dalam jaringan terdesentralisasi.", + "dismissable_banner.explore_statuses": "Ini adalah postingan dari seluruh web sosial yang mendapatkan daya tarik saat ini. Postingan baru dengan lebih banyak peningkatan dan favorit memiliki peringkat lebih tinggi.", "dismissable_banner.explore_tags": "Tagar ini sekarang sedang tren di antara orang di server ini dan lainnya dalam jaringan terdesentralisasi.", + "dismissable_banner.public_timeline": "Ini adalah postingan publik dari orang-orang di web sosial yang diikuti oleh {domain}.", + "domain_block_modal.block": "Blokir server", + "domain_block_modal.block_account_instead": "Blokir @{name} saja", + "domain_block_modal.they_can_interact_with_old_posts": "Orang-orang dari server ini dapat berinteraksi dengan kiriman lama anda.", + "domain_block_modal.they_cant_follow": "Tidak ada seorangpun dari server ini yang dapat mengikuti anda.", + "domain_block_modal.they_wont_know": "Mereka tidak akan tahu bahwa mereka diblokir.", + "domain_block_modal.title": "Blokir domain?", + "domain_block_modal.you_will_lose_followers": "Semua pengikut anda dari server ini akan dihapus.", + "domain_block_modal.you_wont_see_posts": "Anda tidak akan melihat postingan atau notifikasi dari pengguna di server ini.", + "domain_pill.activitypub_lets_connect": "Ini memungkinkan anda terhubung dan berinteraksi dengan orang-orang tidak hanya di Mastodon, tetapi juga di berbagai aplikasi sosial.", + "domain_pill.activitypub_like_language": "ActivityPub seperti bahasa yang digunakan Mastodon dengan jejaring sosial lainnya.", + "domain_pill.server": "Server", + "domain_pill.their_handle": "Nama penggunanya:", + "domain_pill.their_server": "Rumah digital mereka, di mana semua postingan mereka tersedia.", + "domain_pill.their_username": "Pengenal unik mereka di server tersebut. Itu memungkinkan dapat mencari pengguna dengan nama yang sama di server lain.", + "domain_pill.username": "Nama pengguna", + "domain_pill.whats_in_a_handle": "Apa itu nama pengguna?", + "domain_pill.who_they_are": "Karena nama pengguna menunjukkan siapa seseorang dan di mana server mereka berada, anda dapat berinteraksi dengan orang-orang di seluruh web sosial .", + "domain_pill.your_handle": "Nama pengguna anda:", "embed.instructions": "Sematkan kiriman ini di situs web Anda dengan menyalin kode di bawah ini.", "embed.preview": "Tampilan akan seperti ini nantinya:", "emoji_button.activity": "Aktivitas", @@ -260,6 +292,15 @@ "follow_request.authorize": "Izinkan", "follow_request.reject": "Tolak", "follow_requests.unlocked_explanation": "Meskipun akun Anda tidak dikunci, staf {domain} menyarankan Anda untuk meninjau permintaan mengikuti dari akun-akun ini secara manual.", + "follow_suggestions.curated_suggestion": "Pilihan staf", + "follow_suggestions.dismiss": "Jangan tampilkan lagi", + "follow_suggestions.hints.featured": "Profil ini telah dipilih sendiri oleh tim {domain}.", + "follow_suggestions.hints.friends_of_friends": "Profil ini populer di kalangan orang yang anda ikuti.", + "follow_suggestions.personalized_suggestion": "Saran yang dipersonalisasi", + "follow_suggestions.popular_suggestion": "Saran populer", + "follow_suggestions.popular_suggestion_longer": "Populer di {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Serupa dengan profil yang baru Anda ikuti", + "follow_suggestions.view_all": "Lihat semua", "followed_tags": "Tagar yang diikuti", "footer.about": "Tentang", "footer.directory": "Direktori profil", @@ -285,6 +326,7 @@ "home.column_settings.show_reblogs": "Tampilkan boost", "home.column_settings.show_replies": "Tampilkan balasan", "home.hide_announcements": "Sembunyikan pengumuman", + "home.pending_critical_update.link": "Lihat pembaruan", "home.show_announcements": "Tampilkan pengumuman", "interaction_modal.description.follow": "Dengan sebuah akun di Mastodon, Anda bisa mengikuti {name} untuk menerima kirimannya di beranda Anda.", "interaction_modal.description.reblog": "Dengan sebuah akun di Mastodon, Anda bisa mem-boost kiriman ini untuk membagikannya ke pengikut Anda sendiri.", @@ -336,6 +378,7 @@ "lightbox.previous": "Sebelumnya", "limited_account_hint.action": "Tetap tampilkan profil", "limited_account_hint.title": "Profil ini telah disembunyikan oleh moderator {domain}.", + "link_preview.author": "Oleh {name}", "lists.account.add": "Tambah ke daftar", "lists.account.remove": "Hapus dari daftar", "lists.delete": "Hapus daftar", @@ -350,8 +393,11 @@ "lists.search": "Cari di antara orang yang Anda ikuti", "lists.subheading": "Daftar Anda", "load_pending": "{count, plural, other {# item baru}}", + "loading_indicator.label": "Memuatโ€ฆ", "media_gallery.toggle_visible": "Tampil/Sembunyikan", "moved_to_account_banner.text": "Akun {disabledAccount} Anda kini dinonaktifkan karena Anda pindah ke {movedToAccount}.", + "mute_modal.hide_options": "Sembunyikan opsi", + "mute_modal.title": "Bisukan pengguna?", "navigation_bar.about": "Tentang", "navigation_bar.blocks": "Pengguna diblokir", "navigation_bar.bookmarks": "Markah", @@ -516,8 +562,6 @@ "server_banner.about_active_users": "Orang menggunakan server ini selama 30 hari terakhir (Pengguna Aktif Bulanan)", "server_banner.active_users": "pengguna aktif", "server_banner.administered_by": "Dikelola oleh:", - "server_banner.introduction": "{domain} adalah bagian dari jaringan sosial terdesentralisasi yang diberdayakan oleh {mastodon}.", - "server_banner.learn_more": "Pelajari lebih lanjut", "server_banner.server_stats": "Statistik server:", "sign_in_banner.create_account": "Buat akun", "sign_in_banner.sign_in": "Masuk", diff --git a/app/javascript/mastodon/locales/ie.json b/app/javascript/mastodon/locales/ie.json index a9d7487eb9..5c1362f118 100644 --- a/app/javascript/mastodon/locales/ie.json +++ b/app/javascript/mastodon/locales/ie.json @@ -35,9 +35,7 @@ "account.follow_back": "Sequer reciprocmen", "account.followers": "Sequitores", "account.followers.empty": "Ancor nequi seque ti-ci usator.", - "account.followers_counter": "{count, plural, one {{counter} Sequitor} other {{counter} Sequitor}}", "account.following": "Sequent", - "account.following_counter": "{count, plural, one {{counter} Sequent} other {{counter} Sequent}}", "account.follows.empty": "Ti-ci usator ancor ne seque quemcunc.", "account.go_to_profile": "Ear a profil", "account.hide_reblogs": "Celar boosts de @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} ha petit sequer te", "account.share": "Distribuer li profil de @{name}", "account.show_reblogs": "Monstrar boosts de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Posta} other {{counter} Postas}}", "account.unblock": "Desbloccar @{name}", "account.unblock_domain": "Desbloccar dominia {domain}", "account.unblock_short": "Desbloccar", @@ -89,6 +86,7 @@ "announcement.announcement": "Proclamation", "attachments_list.unprocessed": "(รญntractat)", "audio.hide": "Celar audio", + "block_modal.remote_users_caveat": "Noi va petir que li servitor {domain} mey respecter tui decision. Tรกmen, obedientie ne es garantit pro que chascun servitor gere bloccas diferentmen. Possibilmen public postas va restar visibil a usatores de inloggat.", "block_modal.show_less": "Monstrar minu", "block_modal.show_more": "Monstrar plu", "block_modal.they_cant_mention": "Ne posse mentionar ni sequer te.", @@ -212,13 +210,23 @@ "domain_block_modal.block_account_instead": "Altrimen, bloccar @{name}", "domain_block_modal.they_can_interact_with_old_posts": "Persones de ti servitor posse interacter con tui old postas.", "domain_block_modal.they_cant_follow": "Nequi de ti-ci servitor posse sequer te.", + "domain_block_modal.they_wont_know": "Ne va esser conscient pri li bloccada.", + "domain_block_modal.title": "Bloccar dominia?", + "domain_block_modal.you_will_lose_followers": "Omni tui sequitores de ti-ci servitor va esser efaciat.", + "domain_block_modal.you_wont_see_posts": "Tu ne va vider postas ni notificationes de usatores sur ti-ci servitor.", + "domain_pill.activitypub_lets_connect": "It possibilisa tui conexiones e interactiones con persones ne solmen sur Mastodon, ma anc tra diferent social aplis.", "domain_pill.activitypub_like_language": "ActivityPub es li lingue usat de Mastodon por parlar con altri social retages.", "domain_pill.server": "Servitor", "domain_pill.their_handle": "Identificator:", "domain_pill.their_server": "Su digital hem e omni su postas.", + "domain_pill.their_username": "Su unic identificator sur su servitor. It es possibil que altri servitores va haver usatores con li sam nรณmine.", "domain_pill.username": "Usator-nรณmine", "domain_pill.whats_in_a_handle": "Ex quo consiste un identificator?", + "domain_pill.who_they_are": "Pro que identificatores informa qui e u un person is, tu posse interacter con persones tra li rete social de .", + "domain_pill.who_you_are": "Pro que tui identificator informa qui e u tu es, persones posse interacter con te tra li rete social de .", "domain_pill.your_handle": "Tui identificator:", + "domain_pill.your_server": "Tui digital hem, u trova se omni tui postas. Si it ne plese te, tu posse transferer ad un altri servitor quandecunc e tui sequitores con te.", + "domain_pill.your_username": "Tui unic identificator sur ti-ci servitor. It es possibil que altri servitores va haver usatores con li sam nรณmine.", "embed.instructions": "Inbedar ti-ci posta per copiar li code in infra.", "embed.preview": "Vi qualmen it va aspecter:", "emoji_button.activity": "Activitรก", @@ -286,6 +294,7 @@ "filter_modal.select_filter.subtitle": "Usar un existent categorie o crear nov", "filter_modal.select_filter.title": "Filtrar ti-ci posta", "filter_modal.title.status": "Filtrar un posta", + "filtered_notifications_banner.mentions": "{count, plural, one {mention} other {mentiones}}", "filtered_notifications_banner.pending_requests": "Notificationes de {count, plural, =0 {nequi} one {un person} other {# persones}} quel tu possibilmen conosse", "filtered_notifications_banner.title": "Filtrat notificationes", "firehose.all": "Omno", @@ -296,6 +305,8 @@ "follow_requests.unlocked_explanation": "Benque tu conto ne es cludet, li administratores de {domain} pensat que tu fรณrsan vell voler tractar seque-petitiones de tis-ci contos manualmen.", "follow_suggestions.curated_suggestion": "Selection del employates", "follow_suggestions.dismiss": "Ne monstrar plu", + "follow_suggestions.featured_longer": "Selectet manualmen del equip de {domain}", + "follow_suggestions.friends_of_friends_longer": "Populari รญnter li persones queles tu seque", "follow_suggestions.hints.featured": "Ti-ci profil ha esset selectet directmen del equip de {domain}.", "follow_suggestions.hints.friends_of_friends": "Ti-ci profil es populari รญnter tis qui tu seque.", "follow_suggestions.hints.most_followed": "Ti-ci profil es un del max sequet sur {domain}.", @@ -303,6 +314,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Ti-ci profil es simil al profiles queles tu ha recentmen sequet.", "follow_suggestions.personalized_suggestion": "Personalisat suggestion", "follow_suggestions.popular_suggestion": "Populari suggestion", + "follow_suggestions.popular_suggestion_longer": "Populari sur {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Simil a profiles queles tu sequet recentmen", "follow_suggestions.view_all": "Vider omnicos", "follow_suggestions.who_to_follow": "Persones a sequer", "followed_tags": "Sequet hashtags", @@ -423,6 +436,8 @@ "mute_modal.they_can_mention_and_follow": "Posse mentionar e sequer te, ma va esser รญnvisibil a te.", "mute_modal.they_wont_know": "Ne va esser conscient pri li silentation.", "mute_modal.title": "Silentiar usator?", + "mute_modal.you_wont_see_mentions": "Tu ne va vider postas mentionant li usator.", + "mute_modal.you_wont_see_posts": "Ne posse vider tui postas e inversi.", "navigation_bar.about": "Information", "navigation_bar.advanced_interface": "Aperter in li web-interfacie avansat", "navigation_bar.blocks": "Bloccat usatores", @@ -455,10 +470,23 @@ "notification.follow": "{name} sequet te", "notification.follow_request": "{name} ha petit sequer te", "notification.mention": "{name} mentionat te", + "notification.moderation-warning.learn_more": "Aprender plu", + "notification.moderation_warning": "Tu ha recivet un moderatori advertiment", + "notification.moderation_warning.action_delete_statuses": "Alcun de tui postas ha esset efaciat.", + "notification.moderation_warning.action_disable": "Tui conto ha esset desactivisat.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Alcun de tui postas ha esset marcat quam sensitiv.", + "notification.moderation_warning.action_none": "Tui conto ha recivet un moderatori advertiment.", + "notification.moderation_warning.action_sensitive": "Desde nu tui postas va esser marcat quam sensitiv.", + "notification.moderation_warning.action_silence": "Tui conto ha esset limitat.", + "notification.moderation_warning.action_suspend": "Tui conto ha esset suspendet.", "notification.own_poll": "Tui balotation ha finit", "notification.poll": "Un balotation in quel tu votat ha finit", "notification.reblog": "{name} boostat tui posta", + "notification.relationships_severance_event": "Perdit conexiones con {name}", + "notification.relationships_severance_event.account_suspension": "Un admin de {from} ha suspendet {target}, dunc con ti person tu ne plu posse reciver actualisationes ni far interactiones.", + "notification.relationships_severance_event.domain_block": "Un admin de {from} ha bloccat {target}, includente {followersCount} de tui sequitores e {followingCount, plural, one {# conto} other {# contos}} sequet de te.", "notification.relationships_severance_event.learn_more": "Aprender plu", + "notification.relationships_severance_event.user_domain_block": "Tu ha bloccat {target}, efaciante {followersCount} de tui sequitores e {followingCount, plural, one {# conto} other {# contos}} sequet de te.", "notification.status": "{name} just postat", "notification.update": "{name} modificat un posta", "notification_requests.accept": "Acceptar", @@ -472,6 +500,7 @@ "notifications.column_settings.alert": "Notificationes sur li computator", "notifications.column_settings.favourite": "Favorites:", "notifications.column_settings.filter_bar.advanced": "Monstrar omni categories", + "notifications.column_settings.filter_bar.category": "Rapid filtre-barre", "notifications.column_settings.follow": "Nov sequitores:", "notifications.column_settings.follow_request": "Nov petitiones de sequer:", "notifications.column_settings.mention": "Mentiones:", @@ -662,13 +691,10 @@ "server_banner.about_active_users": "Gente usant ti-ci servitor durant li ultim 30 dies (Mensual Activ Usatores)", "server_banner.active_users": "activ usatores", "server_banner.administered_by": "Administrat de:", - "server_banner.introduction": "{domain} es un part del decentralisat social retage constructet sur {mastodon}.", - "server_banner.learn_more": "Aprender plu", "server_banner.server_stats": "Statisticas pri li servitor:", "sign_in_banner.create_account": "Crear un conto", "sign_in_banner.sign_in": "Intrar", "sign_in_banner.sso_redirect": "Intrar o registrar se", - "sign_in_banner.text": "Intrar por sequer profiles o hashtags, favoritisar, partir e responder a postas. Tu posse anc interacter per tui conto che un diferent servitor.", "status.admin_account": "Aperter interfacie de moderation por @{name}", "status.admin_domain": "Aperter interfacie de moderation por {domain}", "status.admin_status": "Aperter ti-ci posta in li interfacie de moderation", @@ -707,6 +733,7 @@ "status.reblog": "Boostar", "status.reblog_private": "Boostar con li original visibilitรก", "status.reblogged_by": "{name} boostat", + "status.reblogs": "{count, plural, one {boost} other {boosts}}", "status.reblogs.empty": "Ancor nequi ha boostat ti-ci posta. Quande alqui fa it, ilu va aparir ci.", "status.redraft": "Deleter & redacter", "status.remove_bookmark": "Remover marcator", diff --git a/app/javascript/mastodon/locales/ig.json b/app/javascript/mastodon/locales/ig.json index a9b300fa4f..4e3e3997da 100644 --- a/app/javascript/mastodon/locales/ig.json +++ b/app/javascript/mastodon/locales/ig.json @@ -20,6 +20,7 @@ "column.bookmarks": "Ebenrแปฅtแปฅakฤ", "column.home": "Be", "column.lists": "Ndepแปฅta", + "column.notifications": "Nziแปkwร ", "column.pins": "Pinned post", "column_header.pin": "Gbado na profaแป‹lแปฅ gแป‹", "column_subheading.settings": "Mwube", @@ -42,17 +43,28 @@ "confirmations.reply.confirm": "Zaa", "confirmations.unfollow.confirm": "Kwแปฅsแป‹ iso", "conversation.delete": "Hichapแปฅ nkata", + "disabled_account_banner.account_settings": "Mwube akaแปฅntแปฅ", "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.", "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.", + "domain_pill.username": "Ahaojiaru", "embed.instructions": "Embed this status on your website by copying the code below.", + "emoji_button.activity": "Mmemme", + "emoji_button.label": "Tibanye emoji", "emoji_button.search": "Chแปแป...", + "emoji_button.symbols": "แปŒdแป‹mara", "empty_column.account_timeline": "No posts found", "empty_column.home": "Your home timeline is empty! Follow more people to fill it up. {suggestions}", "empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.", "errors.unexpected_crash.report_issue": "Kpesa nsogbu", + "explore.trending_links": "Akแปฅkแป", + "firehose.all": "Ha niine", + "follow_request.authorize": "Nye ikike", "footer.privacy_policy": "Iwu nzuzu", "getting_started.heading": "Mbido", "hashtag.column_settings.tag_toggle": "Include additional tags in this column", + "home.column_settings.show_replies": "Gosi nzaghachแป‹", + "home.hide_announcements": "Zoo แปkwa", + "home.show_announcements": "Gosi แปkwa", "keyboard_shortcuts.back": "to navigate back", "keyboard_shortcuts.blocked": "to open blocked users list", "keyboard_shortcuts.boost": "to boost", @@ -120,7 +132,6 @@ "report_notification.categories.other": "แปŒzแป", "search.placeholder": "Chแปแป", "server_banner.active_users": "ojiarแปฅ dแป‹ รฌrรจ", - "server_banner.learn_more": "Mแปฅtakwuo", "sign_in_banner.sign_in": "Sign in", "status.admin_status": "Open this status in the moderation interface", "status.bookmark": "Kee ebenrแปฅtแปฅakฤ", diff --git a/app/javascript/mastodon/locales/io.json b/app/javascript/mastodon/locales/io.json index 3382fa1aec..6aa954ae57 100644 --- a/app/javascript/mastodon/locales/io.json +++ b/app/javascript/mastodon/locales/io.json @@ -33,9 +33,7 @@ "account.follow": "Sequar", "account.followers": "Sequanti", "account.followers.empty": "Nulu sequas ca uzanto til nun.", - "account.followers_counter": "{count, plural, one {{counter} Sequanto} other {{counter} Sequanti}}", "account.following": "Sequata", - "account.following_counter": "{count, plural, one {{counter} Sequas} other {{counter} Sequanti}}", "account.follows.empty": "Ca uzanto ne sequa irgu til nun.", "account.go_to_profile": "Irez al profilo", "account.hide_reblogs": "Celez repeti de @{name}", @@ -60,7 +58,6 @@ "account.requested_follow": "{name} demandis sequar tu", "account.share": "Partigez profilo di @{name}", "account.show_reblogs": "Montrez repeti de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Posto} other {{counter} Posti}}", "account.unblock": "Desblokusar @{name}", "account.unblock_domain": "Desblokusar {domain}", "account.unblock_short": "Desblokusar", @@ -582,13 +579,10 @@ "server_banner.about_active_users": "Personi quo uzas ca servilo dum antea 30 dii (monate aktiva uzanti)", "server_banner.active_users": "aktiva uzanti", "server_banner.administered_by": "Administresis da:", - "server_banner.introduction": "{domain} esas parto di necentraligita sociala ret quo povizesas da {mastodon}.", - "server_banner.learn_more": "Lernez plue", "server_banner.server_stats": "Servilstatistiko:", "sign_in_banner.create_account": "Kreez konto", "sign_in_banner.sign_in": "Enirez", "sign_in_banner.sso_redirect": "Enirar o krear konto", - "sign_in_banner.text": "Enirez por sequar profili o hashtagi, favorizar, partigar e respondizar posti. On povas anke interagar de vua konto kun diferanta servilo.", "status.admin_account": "Apertez jerintervizajo por @{name}", "status.admin_domain": "Apertez jerintervizajo por {domain}", "status.admin_status": "Open this status in the moderation interface", diff --git a/app/javascript/mastodon/locales/is.json b/app/javascript/mastodon/locales/is.json index 435f98f30f..1a38591b85 100644 --- a/app/javascript/mastodon/locales/is.json +++ b/app/javascript/mastodon/locales/is.json @@ -63,7 +63,7 @@ "account.requested_follow": "{name} hefur beรฐiรฐ um aรฐ fylgjast meรฐ รพรฉr", "account.share": "Deila notandasniรฐi fyrir @{name}", "account.show_reblogs": "Sรฝna endurbirtingar frรก @{name}", - "account.statuses_counter": "{count, plural, one {Fรฆrsla: {counter}} other {Fรฆrslur: {counter}}}", + "account.statuses_counter": "{count, plural, one {{counter} fรฆrsla} other {{counter} fรฆrslur}}", "account.unblock": "Aflรฉtta รบtilokun af @{name}", "account.unblock_domain": "Aflรฉtta รบtilokun lรฉnsins {domain}", "account.unblock_short": "Hรฆtta aรฐ loka รก", @@ -308,6 +308,8 @@ "follow_requests.unlocked_explanation": "Jafnvel รพรณtt aรฐgangurinn รพinn sรฉ ekki lรฆstur, hafa umsjรณnarmenn {domain} รญmyndaรฐ sรฉr aรฐ รพรบ gรฆtir viljaรฐ yfirfara handvirkt fylgjendabeiรฐnir frรก รพessum notendum.", "follow_suggestions.curated_suggestion": "รšrval starfsfรณlks", "follow_suggestions.dismiss": "Ekki birta รพetta aftur", + "follow_suggestions.featured_longer": "Handvaliรฐ af {domain}-teyminu", + "follow_suggestions.friends_of_friends_longer": "Vinsรฆlt hjรก fรณlki sem รพรบ fylgist meรฐ", "follow_suggestions.hints.featured": "รžetta notandasniรฐ hefur veriรฐ handvaliรฐ af {domain}-teyminu.", "follow_suggestions.hints.friends_of_friends": "รžetta notandasniรฐ er vinsรฆlt hjรก fรณlki sem รพรบ fylgist meรฐ.", "follow_suggestions.hints.most_followed": "รžetta notandasniรฐ er eitt af รพeim sem mest er fylgst meรฐ รก {domain}.", @@ -315,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "รžetta notandasniรฐ er lรญkt รพeim sniรฐum sem รพรบ hefur valiรฐ aรฐ fylgjast meรฐ aรฐ undanfรถrnu.", "follow_suggestions.personalized_suggestion": "Persรณnuaรฐlรถguรฐ tillaga", "follow_suggestions.popular_suggestion": "Vinsรฆl tillaga", + "follow_suggestions.popular_suggestion_longer": "Vinsรฆlt รก {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Svipar til notenda sem รพรบ hefur nรฝlega fariรฐ aรฐ fylgjast meรฐ", "follow_suggestions.view_all": "Skoรฐa allt", "follow_suggestions.who_to_follow": "Hverjum รก aรฐ fylgjast meรฐ", "followed_tags": "Myllumerki sem fylgst er meรฐ", @@ -410,6 +414,8 @@ "limited_account_hint.action": "Birta notandasniรฐiรฐ samt", "limited_account_hint.title": "รžetta notandasniรฐ hefur veriรฐ faliรฐ af umsjรณnarmรถnnum {domain}.", "link_preview.author": "Eftir {name}", + "link_preview.more_from_author": "Meira frรก {name}", + "link_preview.shares": "{count, plural, one {{counter} fรฆrsla} other {{counter} fรฆrslur}}", "lists.account.add": "Bรฆta รก lista", "lists.account.remove": "Fjarlรฆgja af lista", "lists.delete": "Eyรฐa lista", @@ -469,6 +475,15 @@ "notification.follow": "{name} fylgist meรฐ รพรฉr", "notification.follow_request": "{name} hefur beรฐiรฐ um aรฐ fylgjast meรฐ รพรฉr", "notification.mention": "{name} minntist รก รพig", + "notification.moderation-warning.learn_more": "Kanna nรกnar", + "notification.moderation_warning": "รžรบ hefur fengiรฐ aรฐvรถrun frรก umsjรณnarmanni", + "notification.moderation_warning.action_delete_statuses": "Sumar fรฆrslurnar รพรญnar hafa veriรฐ fjarlรฆgรฐar.", + "notification.moderation_warning.action_disable": "Aรฐgangurinn รพinn hefur veriรฐ gerรฐur รณvirkur.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Sumar fรฆrslurnar รพรญnar hafa veriรฐ merktar sem viรฐkvรฆmt efni.", + "notification.moderation_warning.action_none": "Aรฐgangurinn รพinn hefur fengiรฐ aรฐvรถrun frรก umsjรณnarmanni.", + "notification.moderation_warning.action_sensitive": "Fรฆrslur รพรญnar รก verรฐa hรฉรฐan รญ frรก merktar sem viรฐkvรฆmar.", + "notification.moderation_warning.action_silence": "Notandaaรฐgangurinn รพinn hefur veriรฐ takmarkaรฐur.", + "notification.moderation_warning.action_suspend": "Notandaaรฐgangurinn รพinn hefur veriรฐ settur รญ frysti.", "notification.own_poll": "Kรถnnuninni รพinni er lokiรฐ", "notification.poll": "Kรถnnun sem รพรบ tรณkst รพรกtt รญ er lokiรฐ", "notification.reblog": "{name} endurbirti fรฆrsluna รพรญna", @@ -681,13 +696,13 @@ "server_banner.about_active_users": "Folk sem hefur notaรฐ รพennan netรพjรณn sรญรฐustu 30 daga (virkir notendur รญ mรกnuรฐinum)", "server_banner.active_users": "virkir notendur", "server_banner.administered_by": "Stรฝrt af:", - "server_banner.introduction": "{domain} er hluti af dreifhรฝsta samfรฉlagsnetinu sem keyrt er af {mastodon}.", - "server_banner.learn_more": "Kanna nรกnar", + "server_banner.is_one_of_many": "{domain} er einn af fjรถlmรถrgum รณhรกรฐum Mastodon-รพjรณnum sem รพรบ getur notaรฐ til aรฐ taka รพรกtt รญ fediverse-samfรฉlaginu.", "server_banner.server_stats": "Tรถlfrรฆรฐi รพjรณns:", "sign_in_banner.create_account": "Bรบa til notandaaรฐgang", + "sign_in_banner.follow_anyone": "Fylgstu meรฐ hverjum sem er รญ รพessum samtvinnaรฐa heimi og skoรฐaรฐu allt รญ tรญmarรถรฐ. Engin reiknirit, auglรฝsingar eรฐa smellbeitur.", + "sign_in_banner.mastodon_is": "Mastodon er besta leiรฐin til aรฐ fylgjast meรฐ hvaรฐ sรฉ รญ gangi.", "sign_in_banner.sign_in": "Skrรก inn", "sign_in_banner.sso_redirect": "Skrรก inn eรฐa nรฝskrรก", - "sign_in_banner.text": "Skrรกรฐu รพig inn til aรฐ fylgjast meรฐ notendum eรฐa myllumerkjum, svara fรฆrslum, deila รพeim eรฐa setja รญ eftirlรฆti. รžรบ getur einnig รกtt รญ samskiptum รก aรฐgangnum รพรญnum รก รถรฐrum netรพjรณnum.", "status.admin_account": "Opna umsjรณnarviรฐmรณt fyrir @{name}", "status.admin_domain": "Opna umsjรณnarviรฐmรณt fyrir @{domain}", "status.admin_status": "Opna รพessa fรฆrslu รญ umsjรณnarviรฐmรณtinu", diff --git a/app/javascript/mastodon/locales/it.json b/app/javascript/mastodon/locales/it.json index fcfd09d75f..73c4f9ba60 100644 --- a/app/javascript/mastodon/locales/it.json +++ b/app/javascript/mastodon/locales/it.json @@ -35,9 +35,9 @@ "account.follow_back": "Segui a tua volta", "account.followers": "Follower", "account.followers.empty": "Ancora nessuno segue questo utente.", - "account.followers_counter": "{count, plural, one {{counter} Follower} other {{counter} Follower}}", + "account.followers_counter": "{count, plural, one {{counter} seguace} other {{counter} seguaci}}", "account.following": "Seguiti", - "account.following_counter": "{count, plural, one {{counter} Seguiti} other {{counter} Seguiti}}", + "account.following_counter": "{count, plural, one {{counter} segui} other {{counter} segui}}", "account.follows.empty": "Questo utente non segue ancora nessuno.", "account.go_to_profile": "Vai al profilo", "account.hide_reblogs": "Nascondi potenziamenti da @{name}", @@ -63,7 +63,7 @@ "account.requested_follow": "{name} ha richiesto di seguirti", "account.share": "Condividi il profilo di @{name}", "account.show_reblogs": "Mostra potenziamenti da @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Post} other {{counter} Post}}", + "account.statuses_counter": "{count, plural, one {{counter} post} other {{counter} post}}", "account.unblock": "Sblocca @{name}", "account.unblock_domain": "Sblocca il dominio {domain}", "account.unblock_short": "Sblocca", @@ -308,6 +308,8 @@ "follow_requests.unlocked_explanation": "Anche se il tuo profilo non รจ privato, lo staff di {domain} ha pensato che potresti voler revisionare manualmente le richieste di seguirti da questi profili.", "follow_suggestions.curated_suggestion": "Scelta personale", "follow_suggestions.dismiss": "Non visualizzare piรน", + "follow_suggestions.featured_longer": "Selezionato personalmente dal team di {domain}", + "follow_suggestions.friends_of_friends_longer": "Popolare tra le persone che segui", "follow_suggestions.hints.featured": "Questo profilo รจ stato selezionato personalmente dal team di {domain}.", "follow_suggestions.hints.friends_of_friends": "Questo profilo รจ popolare tra le persone che segui.", "follow_suggestions.hints.most_followed": "Questo profilo รจ uno dei piรน seguiti su {domain}.", @@ -315,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Questo profilo รจ simile ai profili che hai seguito piรน recentemente.", "follow_suggestions.personalized_suggestion": "Suggerimenti personalizzati", "follow_suggestions.popular_suggestion": "Suggerimento frequente", + "follow_suggestions.popular_suggestion_longer": "Popolare su {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Simile ai profili che hai seguito di recente", "follow_suggestions.view_all": "Vedi tutto", "follow_suggestions.who_to_follow": "Chi seguire", "followed_tags": "Hashtag seguiti", @@ -410,6 +414,8 @@ "limited_account_hint.action": "Mostra comunque il profilo", "limited_account_hint.title": "Questo profilo รจ stato nascosto dai moderatori di {domain}.", "link_preview.author": "Di {name}", + "link_preview.more_from_author": "Altro da {name}", + "link_preview.shares": "{count, plural, one {{counter} post} other {{counter} post}}", "lists.account.add": "Aggiungi all'elenco", "lists.account.remove": "Rimuovi dall'elenco", "lists.delete": "Elimina elenco", @@ -469,6 +475,15 @@ "notification.follow": "{name} ha iniziato a seguirti", "notification.follow_request": "{name} ha richiesto di seguirti", "notification.mention": "{name} ti ha menzionato", + "notification.moderation-warning.learn_more": "Scopri di piรน", + "notification.moderation_warning": "Hai ricevuto un avviso di moderazione", + "notification.moderation_warning.action_delete_statuses": "Alcuni dei tuoi post sono stati rimossi.", + "notification.moderation_warning.action_disable": "Il tuo account รจ stato disattivato.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Alcuni dei tuoi post sono stati contrassegnati come sensibili.", + "notification.moderation_warning.action_none": "Il tuo account ha ricevuto un avviso di moderazione.", + "notification.moderation_warning.action_sensitive": "I tuoi post d'ora in poi saranno contrassegnati come sensibili.", + "notification.moderation_warning.action_silence": "Il tuo account รจ stato limitato.", + "notification.moderation_warning.action_suspend": "Il tuo account รจ stato sospeso.", "notification.own_poll": "Il tuo sondaggio รจ terminato", "notification.poll": "Un sondaggio in cui hai votato รจ terminato", "notification.reblog": "{name} ha rebloggato il tuo post", @@ -681,13 +696,13 @@ "server_banner.about_active_users": "Persone che hanno utilizzato questo server negli ultimi 30 giorni (Utenti Attivi Mensilmente)", "server_banner.active_users": "utenti attivi", "server_banner.administered_by": "Amministrato da:", - "server_banner.introduction": "{domain} รจ parte del social network decentralizzato, sviluppato da {mastodon}.", - "server_banner.learn_more": "Scopri di piรน", + "server_banner.is_one_of_many": "{domain} รจ uno dei tanti server Mastodon indipendenti che puoi usare per partecipare al fediverso.", "server_banner.server_stats": "Statistiche del server:", "sign_in_banner.create_account": "Crea un profilo", + "sign_in_banner.follow_anyone": "Segui chiunque nel fediverso e vedi tutto in ordine cronologico. Nessun algoritmo, annunci o clickbait in vista.", + "sign_in_banner.mastodon_is": "Mastodon รจ il modo migliore per tenere il passo con quello che sta accadendo.", "sign_in_banner.sign_in": "Accedi", "sign_in_banner.sso_redirect": "Accedi o Registrati", - "sign_in_banner.text": "Accedi per seguire profili o hashtag, condividere, rispondere e aggiungere post ai preferiti. Puoi anche interagire dal tuo account su un server diverso.", "status.admin_account": "Apri interfaccia di moderazione per @{name}", "status.admin_domain": "Apri l'interfaccia di moderazione per {domain}", "status.admin_status": "Apri questo post nell'interfaccia di moderazione", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 30c9eb77a9..f3ba028eab 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -35,9 +35,7 @@ "account.follow_back": "ใƒ•ใ‚ฉใƒญใƒผใƒใƒƒใ‚ฏ", "account.followers": "ใƒ•ใ‚ฉใƒญใƒฏใƒผ", "account.followers.empty": "ใพใ ่ชฐใ‚‚ใƒ•ใ‚ฉใƒญใƒผใ—ใฆใ„ใพใ›ใ‚“ใ€‚", - "account.followers_counter": "{counter} ใƒ•ใ‚ฉใƒญใƒฏใƒผ", "account.following": "ใƒ•ใ‚ฉใƒญใƒผไธญ", - "account.following_counter": "{counter} ใƒ•ใ‚ฉใƒญใƒผ", "account.follows.empty": "ใพใ ่ชฐใ‚‚ใƒ•ใ‚ฉใƒญใƒผใ—ใฆใ„ใพใ›ใ‚“ใ€‚", "account.go_to_profile": "ใƒ—ใƒญใƒ•ใ‚ฃใƒผใƒซใƒšใƒผใ‚ธใธ", "account.hide_reblogs": "@{name}ใ•ใ‚“ใ‹ใ‚‰ใฎใƒ–ใƒผใ‚นใƒˆใ‚’้ž่กจ็คบ", @@ -63,7 +61,6 @@ "account.requested_follow": "{name}ใ•ใ‚“ใŒใ‚ใชใŸใซใƒ•ใ‚ฉใƒญใƒผใƒชใ‚ฏใ‚จใ‚นใƒˆใ—ใพใ—ใŸ", "account.share": "@{name}ใ•ใ‚“ใฎใƒ—ใƒญใƒ•ใ‚ฃใƒผใƒซใ‚’ๅ…ฑๆœ‰ใ™ใ‚‹", "account.show_reblogs": "@{name}ใ•ใ‚“ใ‹ใ‚‰ใฎใƒ–ใƒผใ‚นใƒˆใ‚’่กจ็คบ", - "account.statuses_counter": "{counter} ๆŠ•็จฟ", "account.unblock": "@{name}ใ•ใ‚“ใฎใƒ–ใƒญใƒƒใ‚ฏใ‚’่งฃ้™ค", "account.unblock_domain": "{domain}ใฎใƒ–ใƒญใƒƒใ‚ฏใ‚’่งฃ้™ค", "account.unblock_short": "ใƒ–ใƒญใƒƒใ‚ฏ่งฃ้™ค", @@ -298,8 +295,8 @@ "filter_modal.select_filter.title": "ใ“ใฎๆŠ•็จฟใ‚’ใƒ•ใ‚ฃใƒซใ‚ฟใƒผใ™ใ‚‹", "filter_modal.title.status": "ๆŠ•็จฟใ‚’ใƒ•ใ‚ฃใƒซใ‚ฟใƒผใ™ใ‚‹", "filtered_notifications_banner.mentions": "{count, plural, one {ใƒกใƒณใ‚ทใƒงใƒณ} other {ใƒกใƒณใ‚ทใƒงใƒณ}}", - "filtered_notifications_banner.pending_requests": "{count, plural, =0 {ใ‚ขใ‚ซใ‚ฆใƒณใƒˆ} other {#ใ‚ขใ‚ซใ‚ฆใƒณใƒˆ}}ใ‹ใ‚‰ใฎ้€š็ŸฅใŒใƒ–ใƒญใƒƒใ‚ฏใ•ใ‚Œใฆใ„ใพใ™", - "filtered_notifications_banner.title": "ใƒ–ใƒญใƒƒใ‚ฏๆธˆใฟใฎ้€š็Ÿฅ", + "filtered_notifications_banner.pending_requests": "{count, plural, =0 {้€š็ŸฅใŒใƒ–ใƒญใƒƒใ‚ฏใ•ใ‚Œใฆใ„ใ‚‹ใ‚ขใ‚ซใ‚ฆใƒณใƒˆใฏใ‚ใ‚Šใพใ›ใ‚“} other {#ใ‚ขใ‚ซใ‚ฆใƒณใƒˆใ‹ใ‚‰ใฎ้€š็ŸฅใŒใƒ–ใƒญใƒƒใ‚ฏใ•ใ‚Œใฆใ„ใพใ™}}", + "filtered_notifications_banner.title": "ไฟ็•™ไธญใฎ้€š็Ÿฅ", "firehose.all": "ใ™ในใฆ", "firehose.local": "ใ“ใฎใ‚ตใƒผใƒใƒผ", "firehose.remote": "ใปใ‹ใฎใ‚ตใƒผใƒใƒผ", @@ -308,6 +305,8 @@ "follow_requests.unlocked_explanation": "ใ‚ใชใŸใฎใ‚ขใ‚ซใ‚ฆใƒณใƒˆใฏๆ‰ฟ่ชๅˆถใงใฏใ‚ใ‚Šใพใ›ใ‚“ใŒใ€{domain}ใฎใ‚นใ‚ฟใƒƒใƒ•ใฏใ“ใ‚Œใ‚‰ใฎใ‚ขใ‚ซใ‚ฆใƒณใƒˆใ‹ใ‚‰ใฎใƒ•ใ‚ฉใƒญใƒผใƒชใ‚ฏใ‚จใ‚นใƒˆใฎ็ขบ่ชใŒๅฟ…่ฆใงใ‚ใ‚‹ใจๅˆคๆ–ญใ—ใพใ—ใŸใ€‚", "follow_suggestions.curated_suggestion": "ใ‚ตใƒผใƒใƒผใ‚นใ‚ฟใƒƒใƒ•ๅ…ฌ่ช", "follow_suggestions.dismiss": "ไปŠๅพŒ่กจ็คบใ—ใชใ„", + "follow_suggestions.featured_longer": "{domain} ใ‚นใ‚ฟใƒƒใƒ•ๅ…ฌ่ช", + "follow_suggestions.friends_of_friends_longer": "ใƒ•ใ‚ฉใƒญใƒผไธญใฎใƒฆใƒผใ‚ถใƒผใซไบบๆฐ—", "follow_suggestions.hints.featured": "{domain} ใฎ้‹ๅ–ถใ‚นใ‚ฟใƒƒใƒ•ใŒ้ธใ‚“ใ ใ‚ขใ‚ซใ‚ฆใƒณใƒˆใงใ™ใ€‚", "follow_suggestions.hints.friends_of_friends": "ใƒ•ใ‚ฉใƒญใƒผไธญใฎใƒฆใƒผใ‚ถใƒผใฎใ‚ใ„ใ ใงไบบๆฐ—ใฎใ‚ขใ‚ซใ‚ฆใƒณใƒˆใงใ™ใ€‚", "follow_suggestions.hints.most_followed": "{domain} ใงใ‚‚ใฃใจใ‚‚ใƒ•ใ‚ฉใƒญใƒผใ•ใ‚Œใฆใ„ใ‚‹ใ‚ขใ‚ซใ‚ฆใƒณใƒˆใฎใฒใจใคใงใ™ใ€‚", @@ -315,6 +314,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "ๆœ€่ฟ‘ใƒ•ใ‚ฉใƒญใƒผใ—ใŸใƒฆใƒผใ‚ถใƒผใซไผผใฆใ„ใ‚‹ใ‚ขใ‚ซใ‚ฆใƒณใƒˆใงใ™ใ€‚", "follow_suggestions.personalized_suggestion": "ใƒ•ใ‚ฉใƒญใƒผใซๅŸบใฅใๆๆกˆ", "follow_suggestions.popular_suggestion": "ไบบๆฐ—ใฎใ‚ขใ‚ซใ‚ฆใƒณใƒˆ", + "follow_suggestions.popular_suggestion_longer": "{domain} ใงไบบๆฐ—", + "follow_suggestions.similar_to_recently_followed_longer": "ๆœ€่ฟ‘ใƒ•ใ‚ฉใƒญใƒผใ—ใŸใƒฆใƒผใ‚ถใƒผใจไผผใฆใ„ใ‚‹ใ‚ขใ‚ซใ‚ฆใƒณใƒˆ", "follow_suggestions.view_all": "ใ™ในใฆ่กจ็คบ", "follow_suggestions.who_to_follow": "ใƒ•ใ‚ฉใƒญใƒผใ‚’ๅข—ใ‚„ใ—ใฆใฟใพใ›ใ‚“ใ‹๏ผŸ", "followed_tags": "ใƒ•ใ‚ฉใƒญใƒผไธญใฎใƒใƒƒใ‚ทใƒฅใ‚ฟใ‚ฐ", @@ -410,6 +411,7 @@ "limited_account_hint.action": "ๆง‹ใ‚ใš่กจ็คบใ™ใ‚‹", "limited_account_hint.title": "ใ“ใฎใƒ—ใƒญใƒ•ใ‚ฃใƒผใƒซใฏ{domain}ใฎใƒขใƒ‡ใƒฌใƒผใ‚ฟใƒผใซใ‚ˆใฃใฆ้ž่กจ็คบใซใ•ใ‚Œใฆใ„ใพใ™ใ€‚", "link_preview.author": "{name}", + "link_preview.more_from_author": "{name}ใ•ใ‚“ใฎๆŠ•็จฟใ‚’ใ‚‚ใฃใจ่ชญใ‚€", "lists.account.add": "ใƒชใ‚นใƒˆใซ่ฟฝๅŠ ", "lists.account.remove": "ใƒชใ‚นใƒˆใ‹ใ‚‰ๅค–ใ™", "lists.delete": "ใƒชใ‚นใƒˆใ‚’ๅ‰Š้™ค", @@ -469,6 +471,15 @@ "notification.follow": "{name}ใ•ใ‚“ใซใƒ•ใ‚ฉใƒญใƒผใ•ใ‚Œใพใ—ใŸ", "notification.follow_request": "{name}ใ•ใ‚“ใŒใ‚ใชใŸใซใƒ•ใ‚ฉใƒญใƒผใƒชใ‚ฏใ‚จใ‚นใƒˆใ—ใพใ—ใŸ", "notification.mention": "{name}ใ•ใ‚“ใŒใ‚ใชใŸใซ่ฟ”ไฟกใ—ใพใ—ใŸ", + "notification.moderation-warning.learn_more": "ใ•ใ‚‰ใซ่ฉณใ—ใ", + "notification.moderation_warning": "็ฎก็†่€…ใ‹ใ‚‰่ญฆๅ‘ŠใŒๆฅใฆใ„ใพใ™", + "notification.moderation_warning.action_delete_statuses": "ใ‚ใชใŸใซใ‚ˆใ‚‹ใ„ใใคใ‹ใฎๆŠ•็จฟใŒๅ‰Š้™คใ•ใ‚Œใพใ—ใŸใ€‚", + "notification.moderation_warning.action_disable": "ใ‚ใชใŸใฎใ‚ขใ‚ซใ‚ฆใƒณใƒˆใฏ็„กๅŠนใซใชใ‚Šใพใ—ใŸใ€‚", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "ใ‚ใชใŸใฎๆŠ•็จฟใฎใ„ใใคใ‹ใฏ้–ฒ่ฆงๆณจๆ„ใจใ—ใฆๅˆคๅฎšใ•ใ‚Œใฆใ„ใพใ™ใ€‚", + "notification.moderation_warning.action_none": "ใ‚ใชใŸใฎใ‚ขใ‚ซใ‚ฆใƒณใƒˆใฏ็ฎก็†่€…ใ‹ใ‚‰ใฎ่ญฆๅ‘Šใ‚’ๅ—ใ‘ใฆใ„ใพใ™ใ€‚", + "notification.moderation_warning.action_sensitive": "ใ‚ใชใŸใฎๆŠ•็จฟใฏใ“ใ‚Œใ‹ใ‚‰้–ฒ่ฆงๆณจๆ„ใจใ—ใฆใƒžใƒผใ‚ฏใ•ใ‚Œใพใ™ใ€‚", + "notification.moderation_warning.action_silence": "ใ‚ใชใŸใฎใ‚ขใ‚ซใ‚ฆใƒณใƒˆใฏๅˆถ้™ใ•ใ‚Œใฆใ„ใพใ™ใ€‚", + "notification.moderation_warning.action_suspend": "ใ‚ใชใŸใฎใ‚ขใ‚ซใ‚ฆใƒณใƒˆใฏๅœๆญขใ•ใ‚Œใพใ—ใŸใ€‚", "notification.own_poll": "ใ‚ขใƒณใ‚ฑใƒผใƒˆใŒ็ต‚ไบ†ใ—ใพใ—ใŸ", "notification.poll": "ใ‚ขใƒณใ‚ฑใƒผใƒˆใŒ็ต‚ไบ†ใ—ใพใ—ใŸ", "notification.reblog": "{name}ใ•ใ‚“ใŒใ‚ใชใŸใฎๆŠ•็จฟใ‚’ใƒ–ใƒผใ‚นใƒˆใ—ใพใ—ใŸ", @@ -482,7 +493,7 @@ "notification_requests.accept": "ๅ—ใ‘ๅ…ฅใ‚Œใ‚‹", "notification_requests.dismiss": "็„ก่ฆ–", "notification_requests.notifications_from": "{name}ใ‹ใ‚‰ใฎ้€š็Ÿฅ", - "notification_requests.title": "ใƒ–ใƒญใƒƒใ‚ฏๆธˆใฟใฎ้€š็Ÿฅ", + "notification_requests.title": "ไฟ็•™ไธญใฎ้€š็Ÿฅ", "notifications.clear": "้€š็Ÿฅใ‚’ๆถˆๅŽป", "notifications.clear_confirmation": "ๆœฌๅฝ“ใซ้€š็Ÿฅใ‚’ๆถˆๅŽปใ—ใพใ™ใ‹๏ผŸ", "notifications.column_settings.admin.report": "ๆ–ฐใ—ใ„้€šๅ ฑ:", @@ -681,13 +692,13 @@ "server_banner.about_active_users": "้ŽๅŽป30ๆ—ฅ้–“ใซใ“ใฎใ‚ตใƒผใƒใƒผใ‚’ไฝฟ็”จใ—ใฆใ„ใ‚‹ไบบ (ๆœˆ้–“ใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใƒฆใƒผใ‚ถใƒผ)", "server_banner.active_users": "ไบบใฎใ‚ขใ‚ฏใƒ†ใ‚ฃใƒ–ใƒฆใƒผใ‚ถใƒผ", "server_banner.administered_by": "็ฎก็†่€…", - "server_banner.introduction": "{domain}ใฏ{mastodon}ใ‚’ไฝฟใฃใŸๅˆ†ๆ•ฃๅž‹ใ‚ฝใƒผใ‚ทใƒฃใƒซใƒใƒƒใƒˆใƒฏใƒผใ‚ฏใฎไธ€้ƒจใงใ™ใ€‚", - "server_banner.learn_more": "ใ‚‚ใฃใจ่ฉณใ—ใ", + "server_banner.is_one_of_many": "{domain} ใฏใ€ๆ•ฐใ€…ใฎ็‹ฌ็ซ‹ใ—ใŸMastodonใ‚ตใƒผใƒใƒผใฎใ†ใกใฎใฒใจใคใงใ™ใ€‚ใ‚ตใƒผใƒใƒผใซ็™ป้Œฒใ—ใฆFediverseใฎใ‚ณใƒŸใƒฅใƒ‹ใƒ†ใ‚ฃใซๅŠ ใ‚ใฃใฆใฟใพใ›ใ‚“ใ‹ใ€‚", "server_banner.server_stats": "ใ‚ตใƒผใƒใƒผใฎๆƒ…ๅ ฑ", "sign_in_banner.create_account": "ใ‚ขใ‚ซใ‚ฆใƒณใƒˆไฝœๆˆ", + "sign_in_banner.follow_anyone": "้€ฃๅˆๅ†…ใฎ่ชฐใงใ‚‚ใƒ•ใ‚ฉใƒญใƒผใ—ใฆๆŠ•็จฟใ‚’ๆ™‚็ณปๅˆ—ใง่ฆ‹ใ‚‹ใ“ใจใŒใงใใพใ™ใ€‚ใ‚ขใƒซใ‚ดใƒชใ‚บใƒ ใ€ๅบƒๅ‘Šใ€ใ‚ฏใƒชใƒƒใ‚ฏใƒ™ใ‚คใƒˆใฏใ‚ใ‚Šใพใ›ใ‚“ใ€‚", + "sign_in_banner.mastodon_is": "Mastodonใซๅ‚ๅŠ ใ—ใฆใ€ไธ–็•Œใง่ตทใใฆใ„ใ‚‹ใ“ใจใ‚’่ฆ‹ใคใ‘ใ‚ˆใ†ใ€‚", "sign_in_banner.sign_in": "ใƒญใ‚ฐใ‚คใƒณ", "sign_in_banner.sso_redirect": "ใƒญใ‚ฐใ‚คใƒณใพใŸใฏ็™ป้Œฒ", - "sign_in_banner.text": "ใ‚ขใ‚ซใ‚ฆใƒณใƒˆใŒใ‚ใ‚Œใฐใƒฆใƒผใ‚ถใƒผใ‚„ใƒใƒƒใ‚ทใƒฅใ‚ฟใ‚ฐใ‚’ใƒ•ใ‚ฉใƒญใƒผใ—ใŸใ‚Šใ€ๆŠ•็จฟใฎใŠๆฐ—ใซๅ…ฅใ‚Š็™ป้Œฒใ‚„ใƒ–ใƒผใ‚นใƒˆใ€ๆŠ•็จฟใธใฎ่ฟ”ไฟกใŒใงใใพใ™ใ€‚ๅˆฅใฎใ‚ตใƒผใƒใƒผใฎใƒฆใƒผใ‚ถใƒผใจใฎไบคๆตใ‚‚ๅฏ่ƒฝใงใ™ใ€‚", "status.admin_account": "@{name}ใ•ใ‚“ใฎใƒขใƒ‡ใƒฌใƒผใ‚ทใƒงใƒณ็”ป้ขใ‚’้–‹ใ", "status.admin_domain": "{domain}ใฎใƒขใƒ‡ใƒฌใƒผใ‚ทใƒงใƒณ็”ป้ขใ‚’้–‹ใ", "status.admin_status": "ใ“ใฎๆŠ•็จฟใ‚’ใƒขใƒ‡ใƒฌใƒผใ‚ทใƒงใƒณ็”ป้ขใง้–‹ใ", diff --git a/app/javascript/mastodon/locales/ka.json b/app/javascript/mastodon/locales/ka.json index 7af4dccd86..b2e67e143e 100644 --- a/app/javascript/mastodon/locales/ka.json +++ b/app/javascript/mastodon/locales/ka.json @@ -26,7 +26,6 @@ "account.requested": "แƒ“แƒแƒ›แƒขแƒ™แƒ˜แƒชแƒ”แƒ‘แƒ˜แƒก แƒ›แƒแƒšแƒแƒ“แƒ˜แƒœแƒจแƒ˜. แƒ“แƒแƒแƒฌแƒ™แƒแƒžแƒฃแƒœแƒ”แƒ— แƒ แƒแƒ› แƒฃแƒแƒ แƒงแƒแƒ— แƒ“แƒแƒ“แƒ”แƒ•แƒœแƒ”แƒ‘แƒ˜แƒก แƒ›แƒแƒ—แƒฎแƒแƒœแƒ•แƒ", "account.share": "แƒ’แƒแƒแƒ–แƒ˜แƒแƒ แƒ” @{name}-แƒ˜แƒก แƒžแƒ แƒแƒคแƒ˜แƒšแƒ˜", "account.show_reblogs": "แƒแƒฉแƒ•แƒ”แƒœแƒ” แƒ‘แƒฃแƒกแƒขแƒ”แƒ‘แƒ˜ @{name}-แƒกแƒ’แƒแƒœ", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account.unblock": "แƒ’แƒแƒœแƒ‘แƒšแƒแƒ™แƒ” @{name}", "account.unblock_domain": "แƒ’แƒแƒ›แƒแƒแƒฉแƒ˜แƒœแƒ” {domain}", "account.unendorse": "แƒแƒ  แƒ’แƒแƒ›แƒแƒ˜แƒ แƒฉแƒ”แƒก แƒžแƒ แƒแƒคแƒ˜แƒšแƒ–แƒ”", diff --git a/app/javascript/mastodon/locales/kab.json b/app/javascript/mastodon/locales/kab.json index 9fdf602992..868edbc8c0 100644 --- a/app/javascript/mastodon/locales/kab.json +++ b/app/javascript/mastodon/locales/kab.json @@ -1,6 +1,10 @@ { + "about.blocks": "Ulac agbur", "about.contact": "Anermis:", "about.disclaimer": "Mastodon d aseษฃแบ“an ilelli, d aseษฃแบ“an n uษฃbalu yeldin, d tnezzut n Mastodon gGmbH.", + "about.domain_blocks.preamble": "Maแนฃแนญudun s umata yeแธmen-ak ad teแบ“reแธ agbur, ad tesdemreแธ akked yimseqdacen-nniแธen seg yal aqeddac deg fedivers. Ha-tent-an ษฃur-k tsuraf i yellan deg uqeddac-agi.", + "about.domain_blocks.silenced.title": "ฦ”ur-s talast", + "about.domain_blocks.suspended.title": "Yeแธฅbes", "about.not_available": "Talษฃut-a ur tettwabder ara deg uqeddac-a.", "about.powered_by": "Azeแนญแนญa inmetti yettwasษฃelsen sษฃur {mastodon}", "about.rules": "Ilugan n uqeddac", @@ -24,9 +28,10 @@ "account.featured_tags.last_status_at": "Tasuffeษฃt taneggarut ass n {date}", "account.featured_tags.last_status_never": "Ulac tisuffaษฃ", "account.follow": "แธŒfer", + "account.follow_back": "แธŒfer-it ula d keฤฤยทm", "account.followers": "Imeแธfaren", "account.followers.empty": "Ar tura, ulac yiwen i yeแนญแนญafaแน›en amseqdac-agi.", - "account.followers_counter": "{count, plural, one {{count} n umeแธfar} other {{count} n imeแธfaren}}", + "account.followers_counter": "{count, plural, one {{counter} n umแธfar} other {{counter} n yimeแธfaren}}", "account.following": "Yeแนญแนญafaแน›", "account.following_counter": "{count, plural, one {{counter} yettwaแธfaren} other {{counter} yettwaแธfaren}}", "account.follows.empty": "Ar tura, amseqdac-agi ur yeแนญแนญafaแน› yiwen.", @@ -169,6 +174,7 @@ "dismissable_banner.explore_tags": "D wiyi i d ihacแนญagen i d-yettawin tamyigawt deg web anmetti ass-a. Ihacแนญagen i sseqdacen ugar n medden, ฮตlayit d imezwura.", "domain_block_modal.block": "Sewแธฅel aqeddac", "domain_block_modal.they_cant_follow": "Yiwen ur yezmir ad kยทm-id-yeแธfer seg uqeddac-a.", + "domain_block_modal.title": "Sewแธฅel taษฃult?", "domain_pill.activitypub_like_language": "ActivityPub am tutlayt yettmeslay Mastodon d izeแธwan inmettiyen nniแธen.", "domain_pill.server": "Aqeddac", "domain_pill.username": "Isem n useqdac", @@ -217,6 +223,7 @@ "filter_modal.added.review_and_configure_title": "Iษฃewwaแน›en n imzizdig", "filter_modal.added.settings_link": "asebter n yiษฃewwaแน›en", "filter_modal.added.short_explanation": "Tasuffeษฃt-a tettwarna ษฃer taggayt-a n yimsizdegen: {title}.", + "filter_modal.added.title": "Yettwarna umsizdeg!", "filter_modal.select_filter.expired": "yemmut", "filter_modal.select_filter.prompt_new": "Taggayt tamaynutt : {name}", "filter_modal.select_filter.search": "Nadi neษฃ snulfu-d", @@ -227,9 +234,9 @@ "firehose.remote": "Iqeddacen nniแธen", "follow_request.authorize": "Ssireg", "follow_request.reject": "Agi", - "follow_suggestions.dismiss": "Ur ttษ›awad ara ad t-id-sekneแนญ", + "follow_suggestions.dismiss": "Dayen ur t-id-skan ara", "follow_suggestions.view_all": "Wali-ten akk", - "follow_suggestions.who_to_follow": "Menhu ara แธefแน›eแธ", + "follow_suggestions.who_to_follow": "Ad tแธefreแธ?", "followed_tags": "Ihacแนญagen yettwaแธfaren", "footer.about": "ฦ”ef", "footer.directory": "Akaram n imeษฃna", @@ -238,6 +245,7 @@ "footer.keyboard_shortcuts": "Inegzumen n unasiw", "footer.privacy_policy": "Tasertit tabaแธnit", "footer.source_code": "Wali tangalt taษฃbalut", + "footer.status": "N tsuffeษฃt", "generic.saved": "Yettwasekles", "getting_started.heading": "Bdu", "hashtag.column_header.tag_mode.all": "d {additional}", @@ -316,11 +324,14 @@ "lightbox.previous": "ฦ”er deffir", "limited_account_hint.action": "Wali amaษฃnu akken yebษฃu yili", "link_preview.author": "S-ษฃur {name}", + "link_preview.more_from_author": "Ugar sษฃur {name}", + "link_preview.shares": "{count, plural, one {{counter} post} other {{counter} posts}}", "lists.account.add": "Rnu ษฃer tebdart", "lists.account.remove": "Kkes seg tebdart", "lists.delete": "Kkes tabdart", "lists.edit": "แบ’reg tabdart", "lists.edit.submit": "Beddel azwel", + "lists.exclusive": "Ffer tisuffaษฃ-a seg ugejdan", "lists.new.create": "Rnu tabdart", "lists.new.title_placeholder": "Azwel amaynut n tebdart", "lists.replies_policy.followed": "Kra n useqdac i yettwaแธefren", @@ -341,6 +352,7 @@ "navigation_bar.bookmarks": "Ticraแธ", "navigation_bar.community_timeline": "Tasuddemt tadigant", "navigation_bar.compose": "Aru tajewwiqt tamaynut", + "navigation_bar.direct": "Tibdarin tusligin", "navigation_bar.discover": "แบ’er", "navigation_bar.domain_blocks": "Tiษฃula yeffren", "navigation_bar.explore": "Snirem", @@ -360,9 +372,14 @@ "navigation_bar.search": "Nadi", "navigation_bar.security": "Taษฃellist", "not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.", + "notification.admin.report": "Yemla-t-id {name} {target}", + "notification.admin.sign_up": "Ijerred {name}", + "notification.favourite": "{name} yesmenyaf addad-ikยทim", "notification.follow": "iแนญแนญafar-ikยทem-id {name}", "notification.follow_request": "{name} yessuter-d ad kยทm-yeแธfeแน›", "notification.mention": "{name} yebder-ik-id", + "notification.moderation-warning.learn_more": "Issin ugar", + "notification.moderation_warning.action_suspend": "Yettwaseแธฅbes umiแธan-ik.", "notification.own_poll": "Tafrant-ikยทim tfuk", "notification.poll": "Tfukk tefrant ideg tettekkaแธ", "notification.reblog": "{name} yebแธa tajewwiqt-ik i tikelt-nniแธen", @@ -373,6 +390,7 @@ "notification_requests.notifications_from": "Ilษฃa sษฃur {name}", "notifications.clear": "Sfeแธ tilษฃa", "notifications.clear_confirmation": "Tebษฃiแธ s tidet ad tekkseแธ akk tilษฃa-inekยทem i lebda?", + "notifications.column_settings.admin.report": "Ineqqisen imaynuten:", "notifications.column_settings.alert": "Tilษฃa n tnarit", "notifications.column_settings.favourite": "Imenyafen:", "notifications.column_settings.filter_bar.advanced": "Sken-d akk taggayin", @@ -387,6 +405,7 @@ "notifications.column_settings.sound": "Rmed imesli", "notifications.column_settings.status": "Tisuffaษฃ timaynutin :", "notifications.column_settings.unread_notifications.category": "Ilษฃa ur nettwaษฃra", + "notifications.column_settings.update": "Iแบ“reg:", "notifications.filter.all": "Akk", "notifications.filter.boosts": "Seวงhed", "notifications.filter.favourites": "Imenyafen", @@ -416,6 +435,7 @@ "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting pointโ€”you can always unfollow them later!", "onboarding.follows.title": "Ttwassnen deg Mastodon", "onboarding.profile.display_name": "Isem ara d-yettwaskanen", + "onboarding.profile.display_name_hint": "Isem-ikยทim ummid neษฃ isem-ikยทim n uqeแนฃแนฃerโ€ฆ", "onboarding.profile.note": "Tameddurt", "onboarding.profile.note_hint": "Tzemreแธ ad d-@tbedreแธ imdanen niแธen neษฃ #ihacแนญagen โ€ฆ", "onboarding.profile.save_and_continue": "Sekles, tkemmleแธ", @@ -444,6 +464,7 @@ "poll.total_votes": "{count, plural, one {# n udษฃaแน›} other {# n yedษฃaแน›en}}", "poll.vote": "Dษฃeแน›", "poll.voted": "Tdeษฃแน›eแธ ษฃef tririt-ayi", + "poll.votes": "{votes, plural, one {# n udษฃaแน›} other {# n yedษฃaแน›en}}", "poll_button.add_poll": "Rnu asenqed", "poll_button.remove_poll": "Kkes asenqed", "privacy.change": "Seggem tabaแธnit n yizen", @@ -468,9 +489,12 @@ "relative_time.seconds": "{number}tas", "relative_time.today": "assa", "reply_indicator.cancel": "Sefsex", + "reply_indicator.poll": "Afmiแธi", "report.block": "Sewแธฅel", + "report.categories.legal": "Azerfan", "report.categories.other": "Tiyyaแธ", "report.categories.spam": "Aspam", + "report.category.subtitle": "Fren amแนฃada akk ufrin", "report.category.title_account": "ameษฃnu", "report.category.title_status": "tasuffeษฃt", "report.close": "Immed", @@ -479,13 +503,25 @@ "report.next": "Uแธfiแน›", "report.placeholder": "Iwenniten-nniแธen", "report.reasons.dislike": "Ur t-แธฅemmleษฃ ara", + "report.reasons.dislike_description": "D ayen akk ur bษฃiษฃ ara ad waliษฃ", "report.reasons.other": "D ayen nniแธen", + "report.reasons.other_description": "Ugur ur yemแนฃada ara akk d taggayin-nniแธen", "report.reasons.spam": "D aspam", + "report.reasons.spam_description": "Yir iseษฃwan, yir agman d tririyin i d-yettuษฃalen", + "report.reasons.violation": "Truแบ“i n yilugan n uqeddac", + "report.reasons.violation_description": "Teแบ“riแธ yยทtettruแบ“u kra n yilugan", + "report.rules.subtitle": "Fren ayen akk yemแนฃadan", + "report.rules.title": "Acu n yilugan i yettwarแบ“an?", + "report.statuses.subtitle": "Fren ayen akk yemแนฃadan", + "report.statuses.title": "Llant tsuffaษฃ ara isdemren aneqqis-a?", "report.submit": "Azen", "report.target": "Mmel {target}", + "report.thanks.take_action_actionable": "Ideg nekkni nessenqad tuttra-inekโ€ขinem, tzemreแธ ad tแธฅadreแธ mgal @{name}:", "report.thanks.title": "Ur tebษฃiแธ ara ad twaliแธ aya?", + "report.thanks.title_actionable": "Tanemmirt ษฃef uneqqis, ad nwali deg waya.", "report.unfollow": "Seแธฅbes aแธfar n @{name}", "report_notification.attached_statuses": "{count, plural, one {# post} other {# posts}} attached", + "report_notification.categories.legal": "Azerfan", "report_notification.categories.other": "Ayen nniแธen", "report_notification.categories.spam": "Aspam", "report_notification.open": "Ldi aneqqis", @@ -500,6 +536,7 @@ "search_popout.full_text_search_disabled_message": "Ur yelli ara deg {domain}.", "search_popout.language_code": "Tangalt ISO n tutlayt", "search_popout.options": "Iwellihen n unadi", + "search_popout.quick_actions": "Tigawin tiruradin", "search_popout.recent": "Inadiyen ineggura", "search_popout.user": "amseqdac", "search_results.accounts": "Imeษฃna", @@ -508,8 +545,9 @@ "search_results.see_all": "Wali-ten akk", "search_results.statuses": "Tisuffaษฃ", "search_results.title": "Anadi ษฃef {q}", + "server_banner.active_users": "iseqdacen urmiden", "server_banner.administered_by": "Yettwadbel sษฃur :", - "server_banner.learn_more": "Issin ugar", + "server_banner.server_stats": "Tidaddanin n uqeddac:", "sign_in_banner.create_account": "Snulfu-d amiแธan", "sign_in_banner.sign_in": "Qqen", "sign_in_banner.sso_redirect": "Qqen neษฃ jerred", @@ -520,13 +558,21 @@ "status.cannot_reblog": "Tasuffeษฃt-a ur tezmir ara ad tettwabแธu tikelt-nniแธen", "status.copy": "Nษฃel assaษฃ ษฃer tasuffeษฃt", "status.delete": "Kkes", + "status.direct": "Bder-d @{name} weแธฅd-s", + "status.direct_indicator": "Abdar uslig", "status.edit": "แบ’reg", "status.edited_x_times": "Tettwaแบ“reg {count, plural, one {{count} n tikkelt} other {{count} n tikkal}}", "status.embed": "Seddu", + "status.favourite": "Amenyaf", + "status.favourites": "{count, plural, one {n usmenyaf} other {n ismenyafen}}", "status.filter": "Sizdeg tassufeษฃt-a", "status.filtered": "Yettwasizdeg", "status.hide": "Ffer tasuffeษฃt", + "status.history.created": "Yerna-t {name} {date}", + "status.history.edited": "Ibeddel-it {name} {date}", "status.load_more": "Sali ugar", + "status.media.open": "Sit i ulday", + "status.media.show": "Sit i uskan", "status.media_hidden": "Amidya yettwaffer", "status.mention": "Bder-d @{name}", "status.more": "Ugar", @@ -538,6 +584,7 @@ "status.read_more": "Issin ugar", "status.reblog": "Bแธu", "status.reblogged_by": "Yebแธa-tt {name}", + "status.reblogs": "{count, plural, one {n usnerni} other {n yisnernuyen}}", "status.reblogs.empty": "Ula yiwen ur yebแธi tajewwiqt-agi ar tura. Ticki yebแธa-tt yiwen, ad d-iban da.", "status.redraft": "Kkes tษ›iwdeแธ tira", "status.remove_bookmark": "Kkes tacreแธt", @@ -552,6 +599,7 @@ "status.show_less_all": "Semแบ“i akk tisuffษฃin", "status.show_more": "Ssken-d ugar", "status.show_more_all": "แบ’err ugar lebda", + "status.show_original": "Sken aษฃbalu", "status.title.with_attachments": "{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}", "status.translate": "Suqel", "status.translated_from_with": "Yettwasuqel seg {lang} s {provider}", @@ -586,6 +634,7 @@ "upload_form.video_description": "Glem-d i yemdanen i yesษ›an ugur deg tmesliwt neษฃ deg yiแบ“ri", "upload_modal.analyzing_picture": "Tasleแธt n tugna tettedduโ€ฆ", "upload_modal.apply": "Snes", + "upload_modal.applying": "Asnasโ€ฆ", "upload_modal.choose_image": "Fren tugna", "upload_modal.description_placeholder": "Aberraษฃ arurad ineggez nnig n uqjun amuแนญแนญis", "upload_modal.detect_text": "Sefru-d aแธris seg tugna", @@ -593,6 +642,7 @@ "upload_modal.preparing_ocr": "Aheyyi n OCRโ€ฆ", "upload_modal.preview_label": "Taskant ({ratio})", "upload_progress.label": "Asali iteddu...", + "upload_progress.processing": "Asesferโ€ฆ", "username.taken": "Yettwaแนญแนญef yisem-a n useqdac. ฦreแธ wayeแธ", "video.close": "Mdel tabidyutt", "video.download": "Sidered afaylu", diff --git a/app/javascript/mastodon/locales/kk.json b/app/javascript/mastodon/locales/kk.json index bd0a806cdb..efeee16c65 100644 --- a/app/javascript/mastodon/locales/kk.json +++ b/app/javascript/mastodon/locales/kk.json @@ -31,9 +31,7 @@ "account.follow": "ะ–ะฐะทั‹ะปัƒ", "account.followers": "ะ–ะฐะทั‹ะปัƒัˆั‹", "account.followers.empty": "ะ‘าฑะป า›ะพะปะดะฐะฝัƒัˆั‹า“ะฐ ำ™ะปั– ะตัˆะบั–ะผ ะถะฐะทั‹ะปะผะฐา“ะฐะฝ.", - "account.followers_counter": "{count, plural, one {{counter} ะถะฐะทั‹ะปัƒัˆั‹} other {{counter} ะถะฐะทั‹ะปัƒัˆั‹}}", "account.following": "ะ–ะฐะทั‹ะปั‹ะผ", - "account.following_counter": "{count, plural, one {{counter} ะถะฐะทั‹ะปั‹ะผ} other {{counter} ะถะฐะทั‹ะปั‹ะผ}}", "account.follows.empty": "ะ‘าฑะป า›ะพะปะดะฐะฝัƒัˆั‹ ำ™ะปั– ะตัˆะบั–ะผะณะต ะถะฐะทั‹ะปะผะฐา“ะฐะฝ.", "account.go_to_profile": "ะŸั€ะพั„ะธะปั–ะฝะต ำฉั‚ัƒ", "account.hide_reblogs": "@{name} ะฑัƒัั‚ะฐั€ั‹ะฝ ะถะฐัั‹ั€ัƒ", @@ -52,7 +50,6 @@ "account.requested": "ะ ะฐัั‚ะฐัƒั‹ะฝ ะบาฏั‚ั–าฃั–ะท. ะ–ะฐะทั‹ะปัƒะดะฐะฝ ะฑะฐั ั‚ะฐั€ั‚ัƒ าฏัˆั–ะฝ ะฑะฐัั‹าฃั‹ะท", "account.share": "@{name} ะฟั€ะพั„ะธะปั–ะฝ ะฑำฉะปั–ััƒ\"", "account.show_reblogs": "@{name} ะฑำฉะปั–ัะบะตะฝะดะตั€ั–ะฝ ะบำฉั€ัะตั‚ัƒ", - "account.statuses_counter": "{count, plural, one {{counter} ะŸะพัั‚} other {{counter} ะŸะพัั‚}}", "account.unblock": "ะ‘าฑา“ะฐั‚ั‚ะฐะฝ ัˆั‹า“ะฐั€ัƒ @{name}", "account.unblock_domain": "ะ‘าฑา“ะฐั‚ั‚ะฐะฝ ัˆั‹า“ะฐั€ัƒ {domain}", "account.unendorse": "ะŸั€ะพั„ะธะปัŒะดะต ั€ะตะบะพะผะตะฝะดะตะผะตัƒ", diff --git a/app/javascript/mastodon/locales/kn.json b/app/javascript/mastodon/locales/kn.json index ceb0f8b9b6..24592e37fc 100644 --- a/app/javascript/mastodon/locales/kn.json +++ b/app/javascript/mastodon/locales/kn.json @@ -16,7 +16,6 @@ "account.posts": "เฒŸเณ‚เฒŸเณโ€Œเฒ—เฒณเณ", "account.posts_with_replies": "Toots and replies", "account.requested": "Awaiting approval", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account.unblock_domain": "Unhide {domain}", "account_note.placeholder": "Click to add a note", "alert.unexpected.title": "เฒ…เฒฏเณเฒฏเณ‹!", diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json index 8628dbb336..fe3582c1d8 100644 --- a/app/javascript/mastodon/locales/ko.json +++ b/app/javascript/mastodon/locales/ko.json @@ -32,12 +32,12 @@ "account.featured_tags.last_status_never": "๊ฒŒ์‹œ๋ฌผ ์—†์Œ", "account.featured_tags.title": "{name} ๋‹˜์˜ ์ถ”์ฒœ ํ•ด์‹œํƒœ๊ทธ", "account.follow": "ํŒ”๋กœ์šฐ", - "account.follow_back": "๋งžํŒ”๋กœ์šฐ", + "account.follow_back": "๋งžํŒ”๋กœ์šฐ ํ•˜๊ธฐ", "account.followers": "ํŒ”๋กœ์›Œ", "account.followers.empty": "์•„์ง ์•„๋ฌด๋„ ์ด ์‚ฌ์šฉ์ž๋ฅผ ํŒ”๋กœ์šฐํ•˜๊ณ  ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", - "account.followers_counter": "{counter} ํŒ”๋กœ์›Œ", + "account.followers_counter": "{count, plural, other {{counter} ํŒ”๋กœ์›Œ}}", "account.following": "ํŒ”๋กœ์ž‰", - "account.following_counter": "{counter} ํŒ”๋กœ์ž‰", + "account.following_counter": "{count, plural, other {{counter} ํŒ”๋กœ์ž‰}}", "account.follows.empty": "์ด ์‚ฌ์šฉ์ž๋Š” ์•„์ง ์•„๋ฌด๋„ ํŒ”๋กœ์šฐํ•˜๊ณ  ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.", "account.go_to_profile": "ํ”„๋กœํ•„๋กœ ์ด๋™", "account.hide_reblogs": "@{name}์˜ ๋ถ€์ŠคํŠธ๋ฅผ ์ˆจ๊ธฐ๊ธฐ", @@ -53,7 +53,7 @@ "account.mute_notifications_short": "์•Œ๋ฆผ ๋ฎคํŠธ", "account.mute_short": "๋ฎคํŠธ", "account.muted": "๋ฎคํŠธ๋จ", - "account.mutual": "์ƒํ˜ธ ํŒ”๋กœ์šฐ", + "account.mutual": "๋งžํŒ”๋กœ์šฐ ์ค‘", "account.no_bio": "์ œ๊ณต๋œ ์„ค๋ช…์ด ์—†์Šต๋‹ˆ๋‹ค.", "account.open_original_page": "์›๋ณธ ํŽ˜์ด์ง€ ์—ด๊ธฐ", "account.posts": "๊ฒŒ์‹œ๋ฌผ", @@ -63,7 +63,7 @@ "account.requested_follow": "{name} ๋‹˜์ด ํŒ”๋กœ์šฐ ์š”์ฒญ์„ ๋ณด๋ƒˆ์Šต๋‹ˆ๋‹ค", "account.share": "@{name}์˜ ํ”„๋กœํ•„ ๊ณต์œ ", "account.show_reblogs": "@{name}์˜ ๋ถ€์ŠคํŠธ ๋ณด๊ธฐ", - "account.statuses_counter": "{counter} ๊ฒŒ์‹œ๋ฌผ", + "account.statuses_counter": "{count, plural, other {{counter} ๊ฒŒ์‹œ๋ฌผ}}", "account.unblock": "์ฐจ๋‹จ ํ•ด์ œ", "account.unblock_domain": "๋„๋ฉ”์ธ {domain} ์ฐจ๋‹จ ํ•ด์ œ", "account.unblock_short": "์ฐจ๋‹จ ํ•ด์ œ", @@ -234,7 +234,7 @@ "embed.preview": "์ด๋ ‡๊ฒŒ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค:", "emoji_button.activity": "ํ™œ๋™", "emoji_button.clear": "์ง€์šฐ๊ธฐ", - "emoji_button.custom": "์‚ฌ์šฉ์ž ์ง€์ •", + "emoji_button.custom": "์ปค์Šคํ…€", "emoji_button.flags": "๊นƒ๋ฐœ", "emoji_button.food": "์Œ์‹๊ณผ ๋งˆ์‹ค๊ฒƒ", "emoji_button.label": "์—๋ชจ์ง€ ์ถ”๊ฐ€", @@ -308,6 +308,8 @@ "follow_requests.unlocked_explanation": "๊ท€ํ•˜์˜ ๊ณ„์ •์ด ์ž ๊ธด ๊ณ„์ •์ด ์•„๋‹์ง€๋ผ๋„, {domain} ์Šคํƒœํ”„๋Š” ์ด ๊ณ„์ •๋“ค์˜ ํŒ”๋กœ์šฐ ์š”์ฒญ์„ ์ˆ˜๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•ด ์ฃผ์‹œ๋ฉด ์ข‹๊ฒ ๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค.", "follow_suggestions.curated_suggestion": "์Šคํƒœํ”„์˜ ์ถ”์ฒœ", "follow_suggestions.dismiss": "๋‹ค์‹œ ๋ณด์ง€ ์•Š๊ธฐ", + "follow_suggestions.featured_longer": "{domain} ํŒ€์ด ์†์ˆ˜ ๊ณ ๋ฆ„", + "follow_suggestions.friends_of_friends_longer": "๋‚ด๊ฐ€ ํŒ”๋กœ์šฐ ํ•˜๋Š” ์‚ฌ๋žŒ๋“ค ์‚ฌ์ด์—์„œ ์ธ๊ธฐ", "follow_suggestions.hints.featured": "์ด ํ”„๋กœํ•„์€ {domain} ํŒ€์ด ์†์ˆ˜ ์„ ํƒํ–ˆ์Šต๋‹ˆ๋‹ค.", "follow_suggestions.hints.friends_of_friends": "์ด ํ”„๋กœํ•„์€ ๋‚ด๊ฐ€ ํŒ”๋กœ์šฐ ํ•˜๋Š” ์‚ฌ๋žŒ๋“ค์—๊ฒŒ์„œ ์œ ๋ช…ํ•ฉ๋‹ˆ๋‹ค.", "follow_suggestions.hints.most_followed": "์ด ํ”„๋กœํ•„์€ {domain}์—์„œ ๊ฐ€์žฅ ๋งŽ์ด ํŒ”๋กœ์šฐ ๋œ ์‚ฌ๋žŒ๋“ค ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค.", @@ -315,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "์ด ํ”„๋กœํ•„์€ ๋‚ด๊ฐ€ ์ตœ๊ทผ์— ํŒ”๋กœ์šฐ ํ•œ ํ”„๋กœํ•„๋“ค๊ณผ ์œ ์‚ฌํ•ฉ๋‹ˆ๋‹ค.", "follow_suggestions.personalized_suggestion": "๊ฐœ์ธํ™”๋œ ์ถ”์ฒœ", "follow_suggestions.popular_suggestion": "์ธ๊ธฐ์žˆ๋Š” ์ถ”์ฒœ", + "follow_suggestions.popular_suggestion_longer": "{domain}์—์„œ ์ธ๊ธฐ", + "follow_suggestions.similar_to_recently_followed_longer": "๋‚ด๊ฐ€ ์ตœ๊ทผ์— ํŒ”๋กœ์šฐ ํ•œ ํ”„๋กœํ•„๋“ค๊ณผ ์œ ์‚ฌ", "follow_suggestions.view_all": "๋ชจ๋‘ ๋ณด๊ธฐ", "follow_suggestions.who_to_follow": "ํŒ”๋กœ์šฐํ•  ๋งŒํ•œ ์‚ฌ๋žŒ", "followed_tags": "ํŒ”๋กœ์šฐ ์ค‘์ธ ํ•ด์‹œํƒœ๊ทธ", @@ -410,6 +414,8 @@ "limited_account_hint.action": "๊ทธ๋ž˜๋„ ํ”„๋กœํ•„ ๋ณด๊ธฐ", "limited_account_hint.title": "์ด ํ”„๋กœํ•„์€ {domain}์˜ ์ค‘์žฌ์ž์— ์˜ํ•ด ์ˆจ๊ฒจ์ง„ ์ƒํƒœ์ž…๋‹ˆ๋‹ค.", "link_preview.author": "{name}", + "link_preview.more_from_author": "{name} ํ”„๋กœํ•„ ๋ณด๊ธฐ", + "link_preview.shares": "{count, plural, other {{counter} ๊ฐœ์˜ ๊ฒŒ์‹œ๋ฌผ}}", "lists.account.add": "๋ฆฌ์ŠคํŠธ์— ์ถ”๊ฐ€", "lists.account.remove": "๋ฆฌ์ŠคํŠธ์—์„œ ์ œ๊ฑฐ", "lists.delete": "๋ฆฌ์ŠคํŠธ ์‚ญ์ œ", @@ -469,6 +475,15 @@ "notification.follow": "{name} ๋‹˜์ด ๋‚˜๋ฅผ ํŒ”๋กœ์šฐํ–ˆ์Šต๋‹ˆ๋‹ค", "notification.follow_request": "{name} ๋‹˜์ด ํŒ”๋กœ์šฐ ์š”์ฒญ์„ ๋ณด๋ƒˆ์Šต๋‹ˆ๋‹ค", "notification.mention": "{name} ๋‹˜์˜ ๋ฉ˜์…˜", + "notification.moderation-warning.learn_more": "๋” ์•Œ์•„๋ณด๊ธฐ", + "notification.moderation_warning": "์ค‘์žฌ ๊ฒฝ๊ณ ๋ฅผ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค", + "notification.moderation_warning.action_delete_statuses": "๊ฒŒ์‹œ๋ฌผ ๋ช‡ ๊ฐœ๊ฐ€ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "notification.moderation_warning.action_disable": "๊ณ„์ •์ด ๋น„ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "๊ฒŒ์‹œ๋ฌผ ๋ช‡ ๊ฐœ๊ฐ€ ๋ฏผ๊ฐํ•จ ์ฒ˜๋ฆฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "notification.moderation_warning.action_none": "๊ณ„์ •์— ์ค‘์žฌ ๊ฒฝ๊ณ ๋ฅผ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค.", + "notification.moderation_warning.action_sensitive": "์•ž์œผ๋กœ์˜ ๊ฒŒ์‹œ๋ฌผ์„ ๋ฏผ๊ฐํ•œ ๊ฒƒ์œผ๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.", + "notification.moderation_warning.action_silence": "๊ณ„์ •์ด ์ œํ•œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", + "notification.moderation_warning.action_suspend": "๊ณ„์ •์ด ์ •์ง€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.", "notification.own_poll": "์„ค๋ฌธ์„ ๋งˆ์นจ", "notification.poll": "์ฐธ์—ฌํ•œ ์„ค๋ฌธ์ด ์ข…๋ฃŒ๋จ", "notification.reblog": "{name} ๋‹˜์ด ๋ถ€์ŠคํŠธํ–ˆ์Šต๋‹ˆ๋‹ค", @@ -681,13 +696,13 @@ "server_banner.about_active_users": "30์ผ ๋™์•ˆ ์ด ์„œ๋ฒ„๋ฅผ ์‚ฌ์šฉํ•œ ์‚ฌ๋žŒ๋“ค (์›”๊ฐ„ ํ™œ์„ฑ ์ด์šฉ์ž)", "server_banner.active_users": "ํ™œ์„ฑ ์‚ฌ์šฉ์ž", "server_banner.administered_by": "๊ด€๋ฆฌ์ž:", - "server_banner.introduction": "{domain}์€ ๋งˆ์Šคํ† ๋ˆ์œผ๋กœ ์šด์˜๋˜๋Š” ํƒˆ์ค‘์•™ํ™” ๋œ ์†Œ์…œ ๋„คํŠธ์›Œํฌ์˜ ์ผ๋ถ€์ž…๋‹ˆ๋‹ค.", - "server_banner.learn_more": "๋” ์•Œ์•„๋ณด๊ธฐ", + "server_banner.is_one_of_many": "{domain}์€ ํŽ˜๋””๋ฒ„์Šค๋ฅผ ํ†ตํ•ด ์ฐธ์—ฌํ•  ์ˆ˜ ์žˆ๋Š” ๋งŽ์€ ๋งˆ์Šคํ† ๋ˆ ์„œ๋ฒ„๋“ค ์ค‘ ํ•˜๋‚˜์ž…๋‹ˆ๋‹ค", "server_banner.server_stats": "์„œ๋ฒ„ ํ†ต๊ณ„:", "sign_in_banner.create_account": "๊ณ„์ • ์ƒ์„ฑ", + "sign_in_banner.follow_anyone": "ํŽ˜๋””๋ฒ„์Šค๋ฅผ ํ†ตํ•ด ๋ˆ„๊ตฌ๋“ ์ง€ ํŒ”๋กœ์šฐํ•˜๊ณ  ์‹œ๊ฐ„์ˆœ์œผ๋กœ ๊ฒŒ์‹œ๋ฌผ์„ ๋ฐ›์•„๋ณด์„ธ์š”. ์•Œ๊ณ ๋ฆฌ์ฆ˜๋„, ๊ด‘๊ณ ๋„, ํด๋ฆญ์„ ์œ ๋„ํ•˜๋Š” ๊ฒƒ๋“ค๋„ ์—†์Šต๋‹ˆ๋‹ค.", + "sign_in_banner.mastodon_is": "๋งˆ์Šคํ† ๋ˆ์€ ๋ฌด์—‡์ด ์ผ์–ด๋‚˜๋Š”์ง€ ๋ฐ›์•„๋ณด๋Š” ๊ฐ€์žฅ ์ข‹์€ ์ˆ˜๋‹จ์ž…๋‹ˆ๋‹ค.", "sign_in_banner.sign_in": "๋กœ๊ทธ์ธ", "sign_in_banner.sso_redirect": "๋กœ๊ทธ์ธ ๋˜๋Š” ๊ฐ€์ž…ํ•˜๊ธฐ", - "sign_in_banner.text": "๋กœ๊ทธ์ธ์„ ํ†ตํ•ด ํ”„๋กœํ•„์ด๋‚˜ ํ•ด์‹œํƒœ๊ทธ๋ฅผ ํŒ”๋กœ์šฐํ•˜๊ฑฐ๋‚˜ ๋งˆ์Œ์— ๋“ค์–ดํ•˜๊ฑฐ๋‚˜ ๊ณต์œ ํ•˜๊ณ  ๋‹ต๊ธ€์„ ๋‹ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์„œ๋ฒ„์— ์žˆ๋Š” ๋ณธ์ธ์˜ ๊ณ„์ •์„ ํ†ตํ•ด ์ฐธ์—ฌํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.", "status.admin_account": "@{name}์— ๋Œ€ํ•œ ์ค‘์žฌ ํ™”๋ฉด ์—ด๊ธฐ", "status.admin_domain": "{domain}์— ๋Œ€ํ•œ ์ค‘์žฌ ํ™”๋ฉด ์—ด๊ธฐ", "status.admin_status": "์ค‘์žฌ ํ™”๋ฉด์—์„œ ์ด ๊ฒŒ์‹œ๋ฌผ ์—ด๊ธฐ", diff --git a/app/javascript/mastodon/locales/ku.json b/app/javascript/mastodon/locales/ku.json index c78861b60c..5248cdfa51 100644 --- a/app/javascript/mastodon/locales/ku.json +++ b/app/javascript/mastodon/locales/ku.json @@ -32,9 +32,7 @@ "account.follow": "BiลŸopรฎne", "account.followers": "ลžopรฎner", "account.followers.empty": "Kesekรฎ hin ev bikarhรชner neลŸopandiye.", - "account.followers_counter": "{count, plural, one {{counter} ลžopรฎner} other {{counter} ลžopรฎner}}", "account.following": "DiลŸopรฎne", - "account.following_counter": "{count, plural, one {{counter} DiลŸopรฎne} other {{counter} DiลŸopรฎne}}", "account.follows.empty": "Ev bikarhรชner hin kesekรฎ heya niha neลŸopandiye.", "account.go_to_profile": "Biรงe bo profรฎlรช", "account.hide_reblogs": "Bilindkirinรชn ji @{name} veลŸรชre", @@ -56,7 +54,6 @@ "account.requested_follow": "{name} dixwaze te biลŸopรฎne", "account.share": "Profรฎla @{name} parve bike", "account.show_reblogs": "Bilindkirinรชn ji @{name} nรฎลŸan bike", - "account.statuses_counter": "{count, plural,one {{counter} ลžandรฎ}other {{counter} ลžandรฎ}}", "account.unblock": "Astengรช li ser @{name} rake", "account.unblock_domain": "Astengรช li ser navperรช {domain} rake", "account.unblock_short": "Astengiyรช rake", @@ -492,8 +489,6 @@ "server_banner.about_active_users": "Kesรชn ku di van 30 rojรชn dawรฎ de vรช rajekarรช bi kar tรฎnin (Bikarhรชnerรชn ร‡alak รชn Mehane)", "server_banner.active_users": "bikarhรชnerรชn รงalak", "server_banner.administered_by": "Tรช bi rรชvebirin ji aliyรช:", - "server_banner.introduction": "{domain} beลŸek ji tora civakรฎ ya nenavendรฎ ye bi hรชzdariya {mastodon}.", - "server_banner.learn_more": "Bรชtir fรชr bibe", "server_banner.server_stats": "Amarรชn rajekar:", "sign_in_banner.create_account": "Ajimรชr biafirรฎne", "sign_in_banner.sign_in": "Tรชkeve", diff --git a/app/javascript/mastodon/locales/kw.json b/app/javascript/mastodon/locales/kw.json index 794cbd9ede..1afcf645cf 100644 --- a/app/javascript/mastodon/locales/kw.json +++ b/app/javascript/mastodon/locales/kw.json @@ -17,8 +17,6 @@ "account.follow": "Holya", "account.followers": "Holyoryon", "account.followers.empty": "Ny wra nagonan holya'n devnydhyer ma hwath.", - "account.followers_counter": "{count, plural, one {{counter} Holyer} other {{counter} Holyer}}", - "account.following_counter": "{count, plural, one {Ow holya {counter}} other {Ow holya {counter}}}", "account.follows.empty": "Ny wra'n devnydhyer ma holya nagonan hwath.", "account.hide_reblogs": "Kudha kenerthow a @{name}", "account.link_verified_on": "Perghenogeth an kolm ma a veu checkys dhe {date}", @@ -33,7 +31,6 @@ "account.requested": "Ow kortos komendyans. Klyckyewgh dhe hedhi govyn holya", "account.share": "Kevrenna profil @{name}", "account.show_reblogs": "Diskwedhes kenerthow a @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Tout} other {{counter} Tout}}", "account.unblock": "Anlettya @{name}", "account.unblock_domain": "Anlettya gorfarth {domain}", "account.unendorse": "Na wra diskwedhes yn profil", diff --git a/app/javascript/mastodon/locales/la.json b/app/javascript/mastodon/locales/la.json index 48b2334008..aa209fcc00 100644 --- a/app/javascript/mastodon/locales/la.json +++ b/app/javascript/mastodon/locales/la.json @@ -1,7 +1,9 @@ { "about.contact": "Ratio:", "about.domain_blocks.no_reason_available": "Ratio abdere est", + "about.domain_blocks.silenced.explanation": "Tua profilia atque tuum contentum ab hac serve praecipue non videbis, nisi explลrฤ“s expresse aut subsequeris et optฤ“s.", "account.account_note_header": "Annotatio", + "account.add_or_remove_from_list": "Adde aut ฤ“ripe ex tabellฤซs", "account.badges.bot": "Robotum", "account.badges.group": "Congregatio", "account.block": "Impedire @{name}", @@ -11,11 +13,18 @@ "account.domain_blocked": "Dominium impeditum", "account.edit_profile": "Recolere notionem", "account.featured_tags.last_status_never": "Nulla contributa", + "account.featured_tags.title": "Hashtag notฤtฤซ {name}", + "account.moved_to": "{name} significavit eum suam rationem novam nunc esse:", "account.muted": "Confutatus", + "account.requested_follow": "{name} postulavit ut te sequeretur", "account.unblock_short": "Solvere impedimentum", "account_note.placeholder": "Click to add a note", "admin.dashboard.retention.average": "Mediocritas", + "admin.impact_report.instance_accounts": "Rationes perfiles hoc deleret", + "alert.unexpected.message": "Error inopinatus occurrit.", "announcement.announcement": "Proclamatio", + "attachments_list.unprocessed": "(immลซtฤtus)", + "block_modal.you_wont_see_mentions": "Nuntios quibus eos commemorant non videbis.", "bundle_column_error.error.title": "Eheu!", "bundle_column_error.retry": "Retemptare", "bundle_column_error.routing.title": "CCCCIIII", @@ -32,30 +41,60 @@ "compose_form.direct_message_warning_learn_more": "Discere plura", "compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.", "compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.", + "compose_form.lock_disclaimer": "Tua ratio non est {clausa}. Quisquis te sequi potest ut visum accipiat nuntios tuos tantum pro sectatoribus.", "compose_form.lock_disclaimer.lock": "clausum", "compose_form.placeholder": "What is on your mind?", "compose_form.publish_form": "Barrire", "compose_form.spoiler.marked": "Text is hidden behind warning", - "compose_form.spoiler.unmarked": "Text is not hidden", + "compose_form.spoiler.unmarked": "Adde praeconium contentลซs", "confirmations.block.confirm": "Impedire", "confirmations.delete.confirm": "Oblitterare", "confirmations.delete.message": "Are you sure you want to delete this status?", "confirmations.delete_list.confirm": "Oblitterare", + "confirmations.discard_edit_media.message": "Habฤ“s mutationฤ“s in descriptionem vel prลspectum medii quae nลn sunt servฤtae; eas dฤ“mittam?", "confirmations.mute.confirm": "Confutare", "confirmations.reply.confirm": "Respondere", + "disabled_account_banner.account_settings": "Praeferentiae ratiลnis", + "disabled_account_banner.text": "Ratio tua {disabledAccount} debilitata est.", "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.", "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.", + "domain_block_modal.you_will_lose_followers": "Omnes sectatores tuฤซ ex hoc servล removฤ“buntur.", + "domain_block_modal.you_wont_see_posts": "Nuntios aut notificฤtiลnฤ“s ab usoribus in hลc servล nลn vidฤ“bis.", + "domain_pill.activitypub_like_language": "ActivityPub est velut lingua quam Mastodon cum aliฤซs sociฤlibus rฤ“tibus loquitur.", + "domain_pill.your_handle": "Tuus nominulus:", + "domain_pill.your_server": "Tua domus digitalis, ubi omnia tua nuntia habitant. Hanc non amas? Servฤ“s trฤnsferฤre potes quลcumque tempore et sectฤtลrฤ“s tuลs simul addลซcere.", + "domain_pill.your_username": "Tuล singulฤre id indicium in hลc servล est. Est possibile invenฤซre usลrฤ“s cum eลdem nลmine in servฤซs aliฤซs.", "embed.instructions": "Embed this status on your website by copying the code below.", + "emoji_button.activity": "Actiล", "emoji_button.food": "Cibus et potus", "emoji_button.people": "Homines", "emoji_button.search": "Quaerere...", + "empty_column.account_suspended": "Rฤtiล suspฤ“nsa", "empty_column.account_timeline": "Hic nulla contributa!", "empty_column.account_unavailable": "Notio non impetrabilis", - "empty_column.home": "Your home timeline is empty! Follow more people to fill it up. {suggestions}", + "empty_column.blocks": "Nondum quemquam usorem obsฤ“cฤvisti.", + "empty_column.direct": "Nลn habฤ“s adhลซc ullo mentionฤ“s prฤซvฤtฤs. Cum ลซnam mฤซseris aut accipis, hฤซc apparฤ“bit.", + "empty_column.followed_tags": "Nลn adhลซc aliquem hastฤginem secลซtus es. Cum id fฤ“ceris, hic ostendฤ“tur.", + "empty_column.home": "Tua linea temporum domesticus vacua est! Sequere plures personas ut eam compleas.", "empty_column.list": "There is nothing in this list yet. When members of this list post new statuses, they will appear here.", + "empty_column.lists": "Nลn adhลซc habฤ“s ullo tabellฤs. Cum creฤs, hฤซc apparฤ“bunt.", + "empty_column.mutes": "Nondum quemquam usorem tacuisti.", + "empty_column.notification_requests": "Omnia clara sunt! Nihil hic est. Cum novฤs notificฤtiลnฤ“s accipฤซs, hic secundum tua praecepta apparebunt.", + "empty_column.notifications": "Nลn adhลซc habฤ“s ullo notificฤtiลnฤ“s. Cum aliฤซ tฤ“ interagunt, hฤซc videbis.", "explore.trending_statuses": "Contributa", + "filtered_notifications_banner.mentions": "{count, plural, one {mentiล} other {mentiลnฤ“s}}", + "firehose.all": "Omnis", + "footer.about": "De", "generic.saved": "Servavit", + "hashtag.column_settings.tag_mode.all": "Haec omnia", "hashtag.column_settings.tag_toggle": "Include additional tags in this column", + "hashtag.counter_by_accounts": "{count, plural, one {{counter} particeps} other {{counter} participฤ“s}}", + "hashtag.counter_by_uses": "{count, plural, one {{counter} nuntius} other {{counter} nuntii}}", + "hashtag.counter_by_uses_today": "{count, plural, one {{counter} nuntius} other {{counter} nuntii}} hodie", + "hashtags.and_other": "โ€ฆet {count, plural, other {# plus}}", + "intervals.full.days": "{number, plural, one {# die} other {# dies}}", + "intervals.full.hours": "{number, plural, one {# hora} other {# horae}}", + "intervals.full.minutes": "{number, plural, one {# minutum} other {# minuta}}", "keyboard_shortcuts.back": "Re navigare", "keyboard_shortcuts.blocked": "Aperire listam usorum obstructorum", "keyboard_shortcuts.boost": "Inlustrare publicatio", @@ -89,17 +128,47 @@ "keyboard_shortcuts.up": "to move up in the list", "lightbox.close": "Claudere", "lightbox.next": "Secundum", + "lists.account.add": "Adde ad tabellฤs", + "lists.new.create": "Addere tabella", + "load_pending": "{count, plural, one {# novum item} other {# nova itema}}", + "media_gallery.toggle_visible": "{number, plural, one {Cฤ“la imaginem} other {Cฤ“la imagines}}", + "moved_to_account_banner.text": "Tua ratione {disabledAccount} interdum reposita est, quod ad {movedToAccount} migrฤvisti.", + "mute_modal.you_wont_see_mentions": "Non videbis nuntios quฤซ eลs commemorant.", + "navigation_bar.about": "De", "navigation_bar.domain_blocks": "Hidden domains", - "not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.", - "notification.reblog": "{name} boosted your status", + "not_signed_in_indicator.not_signed_in": "Ad hunc locum pervenire oportet ut inฤซre facias.", + "notification.admin.report": "{name} nuntiavit {target}", + "notification.admin.sign_up": "{name} subscripsit", + "notification.favourite": "{name} nuntium tuum favit", + "notification.follow": "{name} te secutus est", + "notification.follow_request": "{name} postulavit ut te sequeretur", + "notification.mention": "{name} memoravi", + "notification.moderation_warning": "Accepistฤซ monitionem moderationis.", + "notification.moderation_warning.action_disable": "Ratio tua debilitata est.", + "notification.moderation_warning.action_none": "Tua ratiล monitum moderฤtiลnis accฤ“pit.", + "notification.moderation_warning.action_sensitive": "Tua nuntia hinc sensibiliter notabuntur.", + "notification.moderation_warning.action_silence": "Ratio tua est limitata.", + "notification.moderation_warning.action_suspend": "Ratio tua suspensus est.", + "notification.own_poll": "Suffragium tuum terminatum est.", + "notification.poll": "Electione in quam suffragium dedisti finita est.", + "notification.reblog": "{name} tuum nuntium amplificavit.", + "notification.relationships_severance_event.account_suspension": "Admin ab {from} {target} suspendit, quod significat nลn iam posse tฤ“ novitฤtฤ“s ab eฤซs accipere aut cum eฤซs interagere.", + "notification.relationships_severance_event.domain_block": "Admin ab {from} {target} obsฤ“cฤvit, includฤ“ns {followersCount} ex tuฤซs sectฤtลribus et {followingCount, plural, one {# ratione} other {# rationibus}} quฤs sequeris.", + "notification.relationships_severance_event.user_domain_block": "Bloqueฤstฤซ {target}, removฤ“ns {followersCount} ex sectฤtลribus tuฤซs et {followingCount, plural, one {# rationem} other {# rationฤ“s}} quลs sequeris.", + "notification.status": "{name} nuper publicavit", + "notification.update": "{name} nuntium correxit", + "notification_requests.accept": "Accipe", "notifications.filter.all": "Omnia", "notifications.filter.polls": "Eventus electionis", + "notifications.group": "Notificฤtiลnฤ“s", "onboarding.actions.go_to_explore": "See what's trending", "onboarding.actions.go_to_home": "Go to your home feed", - "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting pointโ€”you can always unfollow them later!", + "onboarding.follows.lead": "Tua domus feed est principalis via Mastodon experฤซrฤซ. Quล plลซrฤ“s persลnas sequeris, eล actฤซvior et interessantior erit. Ad tฤ“ incipiendum, ecce quaedam suฤsiones:", "onboarding.follows.title": "Popular on Mastodon", - "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:", + "onboarding.profile.display_name_hint": "Tuum nomen completum aut tuum nomen ludens...", + "onboarding.start.lead": "Nunc pars es Mastodonis, singularis, socialis medii platformae decentralis ubiโ€”non algorismusโ€”tuam ipsius experientiam curas. Incipiฤmus in nova hac socialis regione:", "onboarding.start.skip": "Want to skip right ahead?", + "onboarding.start.title": "Perfecisti eam!", "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.", "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}", "onboarding.steps.publish_status.body": "Say hello to the world.", @@ -107,30 +176,47 @@ "onboarding.steps.setup_profile.title": "Customize your profile", "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!", "onboarding.steps.share_profile.title": "Share your profile", + "onboarding.tips.2fa": "Scisne? Tลซam ratiลnem sฤ“cลซrฤre potes duลrum elementลrum authentฤซcฤtiลnem in ratiลnis tuฤซ praeferentiฤซs statuendล. Cum ลซllฤ app TOTP ex tuฤ ฤ“lฤ“ctiลne operฤtur, numerus tฤ“lephลnicus necessฤrius nลn est!", + "onboarding.tips.accounts_from_other_servers": "Scisne? Quoniam Mastodon dฤ“centrฤlis est, nลnnulla profฤซlia quae invenฤซs in servฤซs aliฤซs quam tuลrum erunt hospitฤta. Tamen cum eฤซs sine impedฤซmentล interฤgere potes! Servus eลrum in alterฤ parte nลminis eลrum est!", + "onboarding.tips.migration": "Scisne? Sฤซ sentฤซs {domain} tibi in futลซrล nลn esse optimam servฤซ ฤ“lฤ“ctiลnem, ad alium servum Mastodon sine amittendล sectฤtลribus tuฤซs migrฤre potes. Etiam tuum servum hospitฤrฤซ potes!", + "onboarding.tips.verification": "Scisne? Tลซam ratiลnem verificฤre potes iungendล nexum ad prลfฤซlium Mastodon tuum in propriฤ pฤginฤ interrฤ“tiฤ et addendล pฤginam ad prลfฤซlium tuum. Nullae pecลซniae aut documenta necessฤria sunt!", "poll.closed": "Clausum", + "poll.total_people": "{count, plural, one {# persona} other {# personae}}", + "poll.total_votes": "{count, plural, one {# suffragium} other {# suffragia}}", "poll.vote": "Eligere", "poll.voted": "Elegisti hoc responsum", + "poll.votes": "{votes, plural, one {# sufragium} other {# sufragia}}", "poll_button.add_poll": "Addere electionem", "poll_button.remove_poll": "Auferre electionem", "privacy.change": "Adjust status privacy", "privacy.public.short": "Coram publico", + "regeneration_indicator.sublabel": "Tua domus feed praeparฤtur!", + "relative_time.full.days": "{number, plural, one {# ante die} other {# ante dies}}", + "relative_time.full.hours": "{number, plural, one {# ante horam} other {# ante horas}}", "relative_time.full.just_now": "nunc", + "relative_time.full.minutes": "{number, plural, one {# ante minutum} other {# ante minuta}}", + "relative_time.full.seconds": "{number, plural, one {# ante secundum} other {# ante secunda}}", "relative_time.just_now": "nunc", "relative_time.today": "hodie", + "reply_indicator.attachments": "{count, plural, one {# annexus} other {# annexลซs}}", "report.block": "Impedimentum", + "report.block_explanation": "Non videbis eorum nuntios. Non poterunt vidฤ“re tuลs nuntios aut tฤ“ sequฤซ. Intelligere poterunt sฤ“ obstrลซctลs esse.", "report.categories.other": "Altera", "report.category.title_account": "notio", "report.category.title_status": "contributum", "report.close": "Confectum", "report.mute": "Confutare", + "report.mute_explanation": "Non videbis eลrum nuntiลs. Possunt adhuc tฤ“ sequฤซ et tuลs nuntiลs vidฤ“re, nec sciฤ“bunt sฤ“ tacitลs esse.", "report.next": "Secundum", - "report.placeholder": "Type or paste additional comments", + "report.placeholder": "Commentฤriฤซ adiลซnctฤซ", "report.submit": "Mittere", "report.target": "Report {target}", - "report_notification.attached_statuses": "{count, plural, one {# post} other {# posts}} attached", + "report_notification.attached_statuses": "{count, plural, one {{count} nuntius} other {{count} nuntii}} attachiatus", "report_notification.categories.other": "Altera", "search.placeholder": "Quaerere", - "server_banner.learn_more": "Discere plura", + "search_results.all": "Omnis", + "server_banner.active_users": "Usลซrฤriฤซ ฤctฤซvฤซ", + "server_banner.administered_by": "Administratur:", "sign_in_banner.sign_in": "Sign in", "status.admin_status": "Open this status in the moderation interface", "status.block": "Impedire @{name}", @@ -139,13 +225,29 @@ "status.delete": "Oblitterare", "status.edit": "Recolere", "status.edited_x_times": "Edited {count, plural, one {# time} other {# times}}", + "status.favourites": "{count, plural, one {favoritum} other {favorita}}", + "status.history.created": "{name} creatum {date}", + "status.history.edited": "{name} correxit {date}", "status.open": "Expand this status", - "status.title.with_attachments": "{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}", + "status.reblogged_by": "{name} adiuvavit", + "status.reblogs": "{count, plural, one {auctus} other {auctลซs}}", + "status.title.with_attachments": "{user} publicavit {attachmentCount, plural, one {unum annexum} other {{attachmentCount} annexa}}", "tabs_bar.home": "Domi", + "time_remaining.days": "{number, plural, one {# die} other {# dies}} restant", + "time_remaining.hours": "{number, plural, one {# hora} other {# horae}} restant", + "time_remaining.minutes": "{number, plural, one {# minutum} other {# minuta}} restant", + "time_remaining.seconds": "{number, plural, one {# secundum} other {# secunda}} restant", + "timeline_hint.remote_resource_not_displayed": "{resource} ab aliฤซs servฤซs nลn ostenduntur.", "timeline_hint.resources.statuses": "Contributa pristina", - "trends.counter_by_accounts": "{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}", + "trends.counter_by_accounts": "{count, plural, one {{counter} persลna} other {{counter} persลnae}} in {days, plural, one {diฤ“ prฤซdiฤ“} other {diฤ“bus praeteritฤซs {days}}}", + "ui.beforeunload": "Si Mastodon discesseris, tua epitome peribit.", + "units.short.billion": "{count} millia milionum", + "units.short.million": "{count} milionum", + "units.short.thousand": "{count} millia", + "upload_button.label": "Imaginฤ“s, vฤซdeล aut fฤซle audฤซtลซs adde", "upload_form.audio_description": "Describe for people who are hard of hearing", "upload_form.edit": "Recolere", + "upload_modal.description_placeholder": "A velox brunneis vulpes salit super piger canis", "upload_progress.label": "Uploadingโ€ฆ", "video.mute": "Confutare soni" } diff --git a/app/javascript/mastodon/locales/lad.json b/app/javascript/mastodon/locales/lad.json index cf6c3f772f..292f00818c 100644 --- a/app/javascript/mastodon/locales/lad.json +++ b/app/javascript/mastodon/locales/lad.json @@ -35,9 +35,7 @@ "account.follow_back": "Sige tamyen", "account.followers": "Suivantes", "account.followers.empty": "Por agora dingun no sige a este utilizador.", - "account.followers_counter": "{count, plural, one {{counter} suivante} other {{counter} suivantes}}", "account.following": "Sigiendo", - "account.following_counter": "{count, plural, other {Sigiendo a {counter}}}", "account.follows.empty": "Este utilizador ainda no sige a dingun.", "account.go_to_profile": "Va al profil", "account.hide_reblogs": "Eskonde repartajasyones de @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} tiene solisitado segirte", "account.share": "Partaja el profil de @{name}", "account.show_reblogs": "Amostra repartajasyones de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} publikasyon} other {{counter} publikasyones}}", "account.unblock": "Dezbloka a @{name}", "account.unblock_domain": "Dezbloka domeno {domain}", "account.unblock_short": "Dezbloka", @@ -295,6 +292,7 @@ "follow_requests.unlocked_explanation": "Aunke tu kuento no esta serrado, la taifa de {domain} kreye ke talvez keres revizar manualmente las solisitudes de segimento de estos kuentos.", "follow_suggestions.curated_suggestion": "Seleksyon de la taifa", "follow_suggestions.dismiss": "No amostra mas", + "follow_suggestions.friends_of_friends_longer": "Popular entre personas a las kualas siges", "follow_suggestions.hints.featured": "Este profil tiene sido eskojido por la taifa de {domain}.", "follow_suggestions.hints.friends_of_friends": "Este profil es popular entre las personas ke siges.", "follow_suggestions.hints.most_followed": "Este profil es uno de los mas segidos en {domain}.", @@ -397,6 +395,7 @@ "limited_account_hint.action": "Amostra el profil entanto", "limited_account_hint.title": "Este profil fue eskondido por los moderadores de {domain}.", "link_preview.author": "Publikasyon de {name}", + "link_preview.more_from_author": "Mas de {name}", "lists.account.add": "Adjusta a lista", "lists.account.remove": "Kita de lista", "lists.delete": "Efasa lista", @@ -454,6 +453,9 @@ "notification.follow": "{name} te ampeso a segir", "notification.follow_request": "{name} tiene solisitado segirte", "notification.mention": "{name} te enmento", + "notification.moderation-warning.learn_more": "Ambezate mas", + "notification.moderation_warning.action_silence": "Tu kuento tiene sido limitado.", + "notification.moderation_warning.action_suspend": "Tu kuento tiene sido suspendido.", "notification.own_poll": "Tu anketa eskapo", "notification.poll": "Anketa en ke votates eskapo", "notification.reblog": "{name} repartajo tu publikasyon", @@ -661,13 +663,10 @@ "server_banner.about_active_users": "Utilizadores aktivos en este sirvidor durante los ultimos 30 diyas (utilizadores aktivos mensuales)", "server_banner.active_users": "utilizadores aktivos", "server_banner.administered_by": "Administrado por:", - "server_banner.introduction": "{domain} es parte de la red sosyala desentralizada liderada por {mastodon}.", - "server_banner.learn_more": "Ambezate mas", "server_banner.server_stats": "Estatistikas del sirvidor:", "sign_in_banner.create_account": "Kriya kuento", "sign_in_banner.sign_in": "Konektate", "sign_in_banner.sso_redirect": "Konektate o enrejistrate", - "sign_in_banner.text": "Konektate para segir prefiles o etiketas, partajar publikasyones, arispondir a eyas i markar ke te plazen. Puedes tambyen enteraktuar dizde tu kuento en un sirvidor desferente.", "status.admin_account": "Avre la enterfaz de moderasyon para @{name}", "status.admin_domain": "Avre la enterfaz de moderasyon para @{domain}", "status.admin_status": "Avre esto en la enterfaz de moderasyon", diff --git a/app/javascript/mastodon/locales/lt.json b/app/javascript/mastodon/locales/lt.json index 7b3511cfe1..bb69b73399 100644 --- a/app/javascript/mastodon/locales/lt.json +++ b/app/javascript/mastodon/locales/lt.json @@ -1,18 +1,18 @@ { "about.blocks": "Priลพiลซrimi serveriai", "about.contact": "Kontaktai:", - "about.disclaimer": "Mastodon โ€“ nemokama atvirojo kodo programa ir Mastodon gGmbH prekฤ—s ลพenklas.", + "about.disclaimer": "โ€žMastodonโ€œ โ€“ tai nemokama atvirojo kodo programinฤ— ฤฏranga ir โ€žMastodonโ€œ gGmbH prekฤ—s ลพenklas.", "about.domain_blocks.no_reason_available": "Prieลพastis nepateikta", - "about.domain_blocks.preamble": "Mastodon paprastai leidลพia perลพiลซrฤ—ti turinฤฏ ir bendrauti su naudotojais iลก bet kurio kito fediverse esanฤio serverio. ล ios yra iลกimtys, kurios buvo padarytos ลกiame konkreฤiame serveryje.", + "about.domain_blocks.preamble": "โ€žMastodonโ€œ paprastai leidลพia perลพiลซrฤ—ti turinฤฏ ir bendrauti su naudotojais iลก bet kurio kito fediverse esanฤio serverio. ล ios yra iลกimtys, kurios buvo padarytos ลกiame konkreฤiame serveryje.", "about.domain_blocks.silenced.explanation": "Paprastai nematysi profiliลณ ir turinio iลก ลกio serverio, nebent jฤฏ aiลกkiai ieลกkosi arba pasirinksi jฤฏ sekdamas (-a).", "about.domain_blocks.silenced.title": "Ribota", "about.domain_blocks.suspended.explanation": "Jokie duomenys iลก ลกio serverio nebus apdorojami, saugomi ar keiฤiami, todฤ—l bet kokia sฤ…veika ar bendravimas su ลกio serverio naudotojais bus neฤฏmanomas.", - "about.domain_blocks.suspended.title": "Uลพdrausta", + "about.domain_blocks.suspended.title": "Pristabdyta", "about.not_available": "ล i informacija nebuvo pateikta ลกiame serveryje.", - "about.powered_by": "Decentralizuota socialinฤ— medija, kuriฤ… valdo {mastodon}", + "about.powered_by": "Decentralizuota socialinฤ— medija, veikianti pagal โ€ž{mastodon}โ€œ", "about.rules": "Serverio taisyklฤ—s", "account.account_note_header": "Pastaba", - "account.add_or_remove_from_list": "Pridฤ—ti arba iลกtrinti iลก sฤ…raลกลณ", + "account.add_or_remove_from_list": "Pridฤ—ti arba paลกalinti iลก sฤ…raลกลณ", "account.badges.bot": "Automatizuotas", "account.badges.group": "Grupฤ—", "account.block": "Blokuoti @{name}", @@ -30,7 +30,7 @@ "account.endorse": "Rodyti profilyje", "account.featured_tags.last_status_at": "Paskutinis ฤฏraลกas {date}", "account.featured_tags.last_status_never": "Nฤ—ra ฤฏraลกลณ", - "account.featured_tags.title": "{name} rekomenduojami saitaลพodลพiai", + "account.featured_tags.title": "{name} rodomi saitaลพodลพiai", "account.follow": "Sekti", "account.follow_back": "Sekti atgal", "account.followers": "Sekฤ—jai", @@ -38,13 +38,13 @@ "account.followers_counter": "{count, plural, one {{counter} sekฤ—jas} few {{counter} sekฤ—jai} many {{counter} sekฤ—jo} other {{counter} sekฤ—jลณ}}", "account.following": "Sekama", "account.following_counter": "{count, plural, one {{counter} sekimas} few {{counter} sekimai} many {{counter} sekimo} other {{counter} sekimลณ}}", - "account.follows.empty": "ล is (-i) naudotojas (-a) dar nieko neseka.", + "account.follows.empty": "ล is naudotojas dar nieko neseka.", "account.go_to_profile": "Eiti ฤฏ profilฤฏ", "account.hide_reblogs": "Slฤ—pti pakฤ—limus iลก @{name}", "account.in_memoriam": "Atminimui.", "account.joined_short": "Prisijungฤ—", "account.languages": "Keisti prenumeruojamas kalbas", - "account.link_verified_on": "ล ios nuorodos nuosavybฤ— buvo patikrinta {date}.", + "account.link_verified_on": "ล ios nuorodos nuosavybฤ— buvo patikrinta {date}", "account.locked_info": "ล ios paskyros privatumo bลซsena nustatyta kaip uลพrakinta. Savininkas (-ฤ—) rankiniu bลซdu perลพiลซri, kas gali sekti.", "account.media": "Medija", "account.mention": "Paminฤ—ti @{name}", @@ -59,7 +59,7 @@ "account.posts": "ฤฎraลกai", "account.posts_with_replies": "ฤฎraลกai ir atsakymai", "account.report": "Praneลกti apie @{name}", - "account.requested": "Laukiama patvirtinimo. Spustelฤ—k, jei nori atลกaukti sekimo praลกymฤ….", + "account.requested": "Laukiama patvirtinimo. Spustelฤ—k, jei nori atลกaukti sekimo praลกymฤ…", "account.requested_follow": "{name} papraลกฤ— tave sekti", "account.share": "Bendrinti @{name} profilฤฏ", "account.show_reblogs": "Rodyti pakฤ—limus iลก @{name}", @@ -82,7 +82,7 @@ "admin.impact_report.instance_followers": "Sekฤ—jai, kuriuos prarastลณ mลซsลณ naudotojai", "admin.impact_report.instance_follows": "Sekฤ—jai, kuriuos prarastลณ jลณ naudotojai", "admin.impact_report.title": "Poveikio apibendrinimas", - "alert.rate_limited.message": "Pabandyk vฤ—liau po {retry_time, time, medium}.", + "alert.rate_limited.message": "Bandyk vฤ—liau po {retry_time, time, medium}.", "alert.rate_limited.title": "Sparta ribota.", "alert.unexpected.message": "ฤฎvyko netikฤ—ta klaida.", "alert.unexpected.title": "Ups!", @@ -92,7 +92,12 @@ "block_modal.remote_users_caveat": "Papraลกysime serverio {domain} gerbti tavo sprendimฤ…. Taฤiau atitiktis negarantuojama, nes kai kurie serveriai gali skirtingai tvarkyti blokavimus. Vieลกi ฤฏraลกai vis tiek gali bลซti matomi neprisijungusiems naudotojams.", "block_modal.show_less": "Rodyti maลพiau", "block_modal.show_more": "Rodyti daugiau", - "boost_modal.combo": "Galima paspausti {combo}, kad praleisti kitฤ… kartฤ….", + "block_modal.they_cant_mention": "Jie negali tave paminฤ—ti ar sekti.", + "block_modal.they_cant_see_posts": "Jie negali matyti tavo ฤฏraลกus, o tu nematysi jลณ.", + "block_modal.they_will_know": "Jie mato, kad yra uลพblokuoti.", + "block_modal.title": "Blokuoti naudotojฤ…?", + "block_modal.you_wont_see_mentions": "Nematysi ฤฏraลกus, kuriuose jie paminimi.", + "boost_modal.combo": "Galima paspausti {combo}, kad praleisti tai kitฤ… kartฤ…", "bundle_column_error.copy_stacktrace": "Kopijuoti klaidos ataskaitฤ…", "bundle_column_error.error.body": "Papraลกytos puslapio nepavyko atvaizduoti. Tai gali bลซti dฤ—l mลซsลณ kodo klaidos arba narลกyklฤ—s suderinamumo problemos.", "bundle_column_error.error.title": "O, ne!", @@ -117,7 +122,7 @@ "column.direct": "Privatลซs paminฤ—jimai", "column.directory": "Narลกyti profilius", "column.domain_blocks": "Uลพblokuoti domenai", - "column.favourites": "Mฤ—gstamiausi", + "column.favourites": "Mฤ—gstami", "column.firehose": "Tiesioginiai srautai", "column.follow_requests": "Sekimo praลกymai", "column.home": "Pagrindinis", @@ -143,8 +148,8 @@ "compose.published.open": "Atidaryti", "compose.saved.body": "ฤฎraลกas iลกsaugotas.", "compose_form.direct_message_warning_learn_more": "Suลพinoti daugiau", - "compose_form.encryption_warning": "Mastodon ฤฏraลกai nฤ—ra ลกifruojami nuo galo iki galo. Per Mastodon nesidalyk jokia slapta informacija.", - "compose_form.hashtag_warning": "ล is ฤฏraลกas nebus ฤฏtraukta ฤฏ jokฤฏ saitaลพodฤฏ, nes ji nฤ—ra vieลกa. Tik vieลกลณ ฤฏraลกลณ galima ieลกkoti pagal saitaลพodฤฏ.", + "compose_form.encryption_warning": "Mastodon ฤฏraลกai nฤ—ra visapusiลกkai ลกifruojami. Per Mastodon nesidalyk jokia slapta informacija.", + "compose_form.hashtag_warning": "ล is ฤฏraลกas nebus ฤฏtrauktas ฤฏ jokฤฏ saitaลพodฤฏ, nes ji nฤ—ra vieลกa. Tik vieลกลณ ฤฏraลกลณ galima ieลกkoti pagal saitaลพodฤฏ.", "compose_form.lock_disclaimer": "Tavo paskyra nฤ—ra {locked}. Bet kas gali sekti tave ir perลพiลซrฤ—ti tik sekฤ—jams skirtus ฤฏraลกus.", "compose_form.lock_disclaimer.lock": "uลพrakinta", "compose_form.placeholder": "Kas tavo mintyse?", @@ -152,7 +157,7 @@ "compose_form.poll.multiple": "Keli pasirinkimai", "compose_form.poll.option_placeholder": "{number} parinktis", "compose_form.poll.single": "Pasirinkti vienฤ…", - "compose_form.poll.switch_to_multiple": "Keisti apklausฤ…, kad bลซtลณ galima pasirinkti kelis pasirinkimus.", + "compose_form.poll.switch_to_multiple": "Keisti apklausฤ…, kad bลซtลณ galima pasirinkti kelis pasirinkimus", "compose_form.poll.switch_to_single": "Keisti apklausฤ…, kad bลซtลณ galima pasirinkti vienฤ… pasirinkimฤ…", "compose_form.poll.type": "Stilius", "compose_form.publish": "Skelbti", @@ -172,16 +177,17 @@ "confirmations.delete_list.message": "Ar tikrai nori visam laikui iลกtrinti ลกฤฏ sฤ…raลกฤ…?", "confirmations.discard_edit_media.confirm": "Atmesti", "confirmations.discard_edit_media.message": "Turi neiลกsaugotลณ medijos apraลกymo ar perลพiลซros pakeitimลณ, vis tiek juos atmesti?", + "confirmations.domain_block.confirm": "Blokuoti serverฤฏ", "confirmations.domain_block.message": "Ar tikrai, tikrai nori uลพblokuoti visฤ… {domain}? Daugeliu atvejลณ uลพtenka keliลณ tiksliniลณ blokavimลณ arba nutildymลณ. ล io domeno turinio nematysi jokiose vieลกose laiko skalฤ—se ar praneลกimuose. Tavo sekฤ—jai iลก to domeno bus paลกalinti.", "confirmations.edit.confirm": "Redaguoti", "confirmations.edit.message": "Redaguojant dabar, bus perraลกyta ลกiuo metu kuriama ลพinutฤ—. Ar tikrai nori tฤ™sti?", "confirmations.logout.confirm": "Atsijungti", "confirmations.logout.message": "Ar tikrai nori atsijungti?", "confirmations.mute.confirm": "Nutildyti", - "confirmations.redraft.confirm": "Iลกtrinti ir parengti iลก naujo", - "confirmations.redraft.message": "Ar tikrai nori iลกtrinti ลกฤฏ ฤฏraลกฤ… ir parengti jฤฏ iลก naujo kaip juodraลกtฤฏ? Bus prarastos mฤ—gstamiausios ir pakฤ—limai, o atsakymai ฤฏ originalinฤฏ ฤฏraลกฤ… taps liekamojais.", + "confirmations.redraft.confirm": "Iลกtrinti ir perraลกyti", + "confirmations.redraft.message": "Ar tikrai nori iลกtrinti ลกฤฏ ฤฏraลกฤ… ir paraลกyti jฤฏ iลก naujo? Bus prarastos mฤ—gstamai ir pakฤ—limai, o atsakymai ฤฏ originalinฤฏ ฤฏraลกฤ… taps liekamojais.", "confirmations.reply.confirm": "Atsakyti", - "confirmations.reply.message": "Atsakant dabar, bus perraลกyta metu kuriama ลพinutฤ—. Ar tikrai nori tฤ™sti?", + "confirmations.reply.message": "Atsakant dabar, bus perraลกyta ลกiuo metu kuriama ลพinutฤ—. Ar tikrai nori tฤ™sti?", "confirmations.unfollow.confirm": "Nebesekti", "confirmations.unfollow.message": "Ar tikrai nori nebesekti {name}?", "conversation.delete": "Iลกtrinti pokalbฤฏ", @@ -196,34 +202,42 @@ "directory.new_arrivals": "Nauji atvykฤ—liai", "directory.recently_active": "Neseniai aktyvus (-i)", "disabled_account_banner.account_settings": "Paskyros nustatymai", - "disabled_account_banner.text": "Tavo paskyra {disabledAccount} ลกiuo metu iลกjungta.", - "dismissable_banner.community_timeline": "Tai โ€“ naujausi vieลกi ฤฏraลกai, kuriuos paskelbฤ— ลพmonฤ—s, kuriลณ paskyros talpinamos {domain}.", + "disabled_account_banner.text": "Tavo paskyra {disabledAccount} ลกiuo metu yra iลกjungta.", + "dismissable_banner.community_timeline": "Tai โ€“ naujausi vieลกi ฤฏraลกai iลก ลพmoniลณ, kuriลณ paskyros talpinamos {domain}.", "dismissable_banner.dismiss": "Atmesti", "dismissable_banner.explore_links": "Tai โ€“ naujienos, kuriomis ลกiandien daugiausiai bendrinamasi socialiniame ลพiniatinklyje. Naujesnฤ—s naujienลณ istorijos, kurias paskelbฤ— daugiau skirtingลณ ลพmoniลณ, vertinamos aukลกฤiau.", "dismissable_banner.explore_statuses": "Tai โ€“ ฤฏraลกai iลก viso socialinio ลพiniatinklio, kurie ลกiandien sulaukia daug dฤ—mesio. Naujesni ฤฏraลกai, turintys daugiau pakฤ—limลณ ir mฤ—gstamลณ, vertinami aukลกฤiau.", "dismissable_banner.explore_tags": "Tai โ€“ saitaลพodลพiai, kurie ลกiandien sulaukia daug dฤ—mesio socialiniame ลพiniatinklyje. Saitaลพodลพiai, kuriuos naudoja daugiau skirtingลณ ลพmoniลณ, vertinami aukลกฤiau.", - "dismissable_banner.public_timeline": "Tai โ€“ naujausi vieลกi ฤฏraลกai, kuriuos socialiniame ลพiniatinklyje paskelbฤ— ลพmonฤ—s, sekantys {domain}.", - "domain_pill.activitypub_lets_connect": "Tai leidลพia tau bendrauti su ลพmonฤ—mis ne tik Mastodon, bet ir ฤฏvairiose socialinฤ—se programฤ—lฤ—se.", - "domain_pill.activitypub_like_language": "ActivityPub โ€“ tarsi kalba, kuria Mastodon kalba su kitais socialiniais tinklais.", + "dismissable_banner.public_timeline": "Tai โ€“ naujausi vieลกi ฤฏraลกai iลก ลพmoniลณ socialiniame ลพiniatinklyje, kuriuos seka {domain} ลพmonฤ—s.", + "domain_block_modal.block": "Blokuoti serverฤฏ", + "domain_block_modal.block_account_instead": "Blokuoti {name} vietoj to", + "domain_block_modal.they_can_interact_with_old_posts": "ลฝmonฤ—s iลก ลกio serverio gali sฤ…veikauti su tavo senomis ฤฏraลกomis.", + "domain_block_modal.they_cant_follow": "Niekas iลก ลกio serverio negali tavฤ™s sekti.", + "domain_block_modal.they_wont_know": "Jie neลพinos, kad buvo uลพblokuoti.", + "domain_block_modal.title": "Blokuoti domenฤ…?", + "domain_block_modal.you_will_lose_followers": "Visi tavo sekฤ—jai iลก ลกio serverio bus paลกalinti.", + "domain_block_modal.you_wont_see_posts": "Nematysi naudotojลณ ฤฏraลกลณ ar praneลกimลณ ลกiame serveryje.", + "domain_pill.activitypub_lets_connect": "Tai leidลพia tau prisijungti ir bendrauti su ลพmonฤ—mis ne tik Mastodon, bet ir ฤฏvairiose socialinฤ—se programฤ—lฤ—se.", + "domain_pill.activitypub_like_language": "ActivityPub โ€“ tai tarsi kalba, kuria Mastodon kalba su kitais socialiniais tinklais.", "domain_pill.server": "Serveris", "domain_pill.their_handle": "Jลณ socialinis medijos vardas:", "domain_pill.their_server": "Jลณ skaitmeniniai namai, kuriuose saugomi visi jลณ ฤฏraลกai.", "domain_pill.their_username": "Jลณ unikalus identifikatorius jลณ serveryje. Skirtinguose serveriuose galima rasti naudotojลณ, turinฤiลณ tฤ… patฤฏ naudotojo vardฤ….", "domain_pill.username": "Naudotojo vardas", "domain_pill.whats_in_a_handle": "Kas yra socialiniame medijos varde?", - "domain_pill.who_they_are": "Kadangi socialines medijos vardai nurodo, kas ir kur jie yra, galima bendrauti su ลพmonฤ—mis visame socialiniame tinkle, kuriame yra .", - "domain_pill.who_you_are": "Kadangi tavo socialinis medijos vardas nurodo, kas esi ir kur esi, ลพmonฤ—s gali bendrauti su tavimi visame socialiniame tinkle, kurฤฏ sudaro .", + "domain_pill.who_they_are": "Kadangi socialines medijos vardai nurodo, kas ลพmogus yra ir kur jie yra, gali sฤ…veikauti su ลพmonฤ—mis visame socialiniame ลพiniatinklyje, kurฤฏ sudaro .", + "domain_pill.who_you_are": "Kadangi tavo socialinis medijos vardas nurodo, kas esi ir kur esi, ลพmonฤ—s gali sฤ…veikauti su tavimi visame socialiniame tinkle, kurฤฏ sudaro .", "domain_pill.your_handle": "Tavo socialinis medijos vardas:", "domain_pill.your_server": "Tavo skaitmeniniai namai, kuriuose saugomi visi tavo ฤฏraลกai. Nepatinka ลกis? Bet kada perkelk serverius ir atsivesk ir savo sekฤ—jus.", - "domain_pill.your_username": "Tavo unikalus identifikatorius ลกiame serveryje. Skirtinguose serveriuose galima rasti naudotojลณ, turinฤiลณ tฤ… patฤฏ naudotojo vardฤ….", + "domain_pill.your_username": "Tavo unikalus identifikatorius ลกiame serveryje. Skirtinguose serveriuose galima rasti naudotojลณ su tuo paฤiu naudotojo vardu.", "embed.instructions": "ฤฎterpk ลกฤฏ ฤฏraลกฤ… ฤฏ savo svetainฤ™ nukopijavus (-usi) toliau pateiktฤ… kodฤ….", - "embed.preview": "ล tai, kaip tai atrodys:", + "embed.preview": "ล tai kaip tai atrodys:", "emoji_button.activity": "Veikla", "emoji_button.clear": "Iลกvalyti", "emoji_button.custom": "Pasirinktinis", "emoji_button.flags": "Vฤ—liavos", "emoji_button.food": "Maistas ir gฤ—rimai", - "emoji_button.label": "ฤฎterpti veidelius", + "emoji_button.label": "ฤฎterpti jaustukฤ…", "emoji_button.nature": "Gamta", "emoji_button.not_found": "Nerasta jokiลณ tinkamลณ jaustukลณ.", "emoji_button.objects": "Objektai", @@ -234,26 +248,27 @@ "emoji_button.symbols": "Simboliai", "emoji_button.travel": "Kelionฤ—s ir vietos", "empty_column.account_hides_collections": "ล is (-i) naudotojas (-a) pasirinko nepadaryti ลกiฤ… informacijฤ… prieinamฤ….", - "empty_column.account_suspended": "Paskyra sustabdyta.", - "empty_column.account_timeline": "Nฤ—ra ฤฏraลกลณ ฤia.", + "empty_column.account_suspended": "Paskyra pristabdyta.", + "empty_column.account_timeline": "Nฤ—ra ฤia ฤฏraลกลณ.", "empty_column.account_unavailable": "Profilis neprieinamas.", "empty_column.blocks": "Dar neuลพblokavai nฤ— vieno naudotojo.", - "empty_column.bookmarked_statuses": "Dar neturi nฤ— vienos ฤฏraลกo ลพymฤ—s. Kai vienฤ… iลก jลณ pridฤ—si ฤฏ ลพymes, jis bus rodomas ฤia.", - "empty_column.community": "Vietinฤ— laiko skalฤ— tuลกฤia. Paraลกyk kฤ… nors vieลกai, kad pradฤ—tum bendrauti!", + "empty_column.bookmarked_statuses": "Dar neturi nฤ— vienos ฤฏraลกo pridฤ—tos ลพymฤ—s. Kai vienฤ… iลก jลณ pridฤ—si ฤฏ ลพymes, jis bus rodomas ฤia.", + "empty_column.community": "Vietinฤ— laiko skalฤ— yra tuลกฤia. Paraลกyk kฤ… nors vieลกai, kad pradฤ—tum sฤ…veikauti.", "empty_column.direct": "Dar neturi jokiลณ privaฤiลณ paminฤ—jimลณ. Kai iลกsiลณsi arba gausi vienฤ… iลก jลณ, jis bus rodomas ฤia.", "empty_column.domain_blocks": "Dar nฤ—ra uลพblokuotลณ domenลณ.", - "empty_column.explore_statuses": "ล iuo metu niekas nฤ—ra tendencinga. Patikrink vฤ—liau.", + "empty_column.explore_statuses": "ล iuo metu niekas nฤ—ra tendencinga. Patikrink vฤ—liau!", "empty_column.favourited_statuses": "Dar neturi mฤ—gstamลณ ฤฏraลกลณ. Kai vienฤ… iลก jลณ pamฤ—gsi, jis bus rodomas ฤia.", "empty_column.favourites": "ล io ฤฏraลกo dar niekas nepamฤ—go. Kai kas nors tai padarys, jie bus rodomi ฤia.", "empty_column.follow_requests": "Dar neturi jokiลณ sekimo praลกymลณ. Kai gausi tokฤฏ praลกymฤ…, jis bus rodomas ฤia.", "empty_column.followed_tags": "Dar neseki jokiลณ saitaลพodลพiลณ. Kai tai padarysi, jie bus rodomi ฤia.", "empty_column.hashtag": "Nฤ—ra nieko ลกiame saitaลพodyje kol kas.", - "empty_column.home": "Tavo pagrindinio laiko skalฤ— tuลกฤia! Sek daugiau ลพmoniลณ, kad jฤ… uลพpildytum.", + "empty_column.home": "Tavo pagrindinio laiko skalฤ— tuลกฤia. Sek daugiau ลพmoniลณ, kad jฤ… uลพpildytum.", "empty_column.list": "Nฤ—ra nieko ลกiame sฤ…raลกe kol kas. Kai ลกio sฤ…raลกo nariai paskelbs naujลณ ฤฏraลกลณ, jie bus rodomi ฤia.", "empty_column.lists": "Dar neturi jokiลณ sฤ…raลกลณ. Kai jฤฏ sukursi, jis bus rodomas ฤia.", "empty_column.mutes": "Dar nesi nutildฤ™s (-usi) nฤ— vieno naudotojo.", - "empty_column.notifications": "Dar neturi jokiลณ praneลกimลณ. Kai kiti ลพmonฤ—s su tavimi bendraus, matysi tai ฤia.", - "empty_column.public": "ฤŒia nieko nฤ—ra! Paraลกyk kฤ… nors vieลกai arba rankiniu bลซdu sek naudotojus iลก kitลณ serveriลณ, kad jฤฏ uลพpildytum.", + "empty_column.notification_requests": "Viskas ลกvaru! ฤŒia nieko nฤ—ra. Kai gausi naujลณ praneลกimลณ, jie bus rodomi ฤia pagal tavo nustatymus.", + "empty_column.notifications": "Dar neturi jokiลณ praneลกimลณ. Kai kiti ลพmonฤ—s su tavimi sฤ…veikaus, matysi tai ฤia.", + "empty_column.public": "ฤŒia nieko nฤ—ra. Paraลกyk kฤ… nors vieลกai arba rankiniu bลซdu sek naudotojus iลก kitลณ serveriลณ, kad jฤฏ uลพpildytum.", "error.unexpected_crash.explanation": "Dฤ—l mลซsลณ kodo riktos arba narลกyklฤ—s suderinamumo problemos ลกis puslapis negalฤ—jo bลซti rodomas teisingai.", "error.unexpected_crash.explanation_addons": "ล ฤฏ puslapฤฏ nepavyko parodyti teisingai. ล iฤ… klaidฤ… greiฤiausiai sukฤ—lฤ— narลกyklฤ—s priedas arba automatinio vertimo ฤฏrankiai.", "error.unexpected_crash.next_steps": "Pabandyk atnaujinti puslapฤฏ. Jei tai nepadeda, galbลซt vis dar galฤ—si naudotis Mastodon per kitฤ… narลกyklฤ™ arba savฤ…jฤ… programฤ—lฤ™.", @@ -270,9 +285,9 @@ "filter_modal.added.context_mismatch_title": "Konteksto neatitikimas.", "filter_modal.added.expired_explanation": "ล i filtro kategorija nustojo galioti. Kad ji bลซtลณ taikoma, turฤ—si pakeisti galiojimo datฤ….", "filter_modal.added.expired_title": "Baigฤ—si filtro galiojimas.", - "filter_modal.added.review_and_configure": "Norint perลพiลซrฤ—ti ir toliau konfigลซruoti ลกiฤ… filtro kategorijฤ…, eik ฤฏ nuorodฤ… {settings_link}.", + "filter_modal.added.review_and_configure": "Norint perลพiลซrฤ—ti ir toliau konfigลซruoti ลกiฤ… filtro kategorijฤ…, eik ฤฏ {settings_link}.", "filter_modal.added.review_and_configure_title": "Filtro nustatymai", - "filter_modal.added.settings_link": "nustatymลณ puslapis", + "filter_modal.added.settings_link": "nustatymลณ puslapฤฏ", "filter_modal.added.short_explanation": "ล is ฤฏraลกas buvo pridฤ—tas ฤฏ ลกiฤ… filtro kategorijฤ…: {title}.", "filter_modal.added.title": "Pridฤ—tas filtras.", "filter_modal.select_filter.context_mismatch": "netaikoma ลกiame kontekste.", @@ -282,6 +297,9 @@ "filter_modal.select_filter.subtitle": "Naudok esamฤ… kategorijฤ… arba sukurk naujฤ….", "filter_modal.select_filter.title": "Filtruoti ลกฤฏ ฤฏraลกฤ…", "filter_modal.title.status": "Filtruoti ฤฏraลกฤ…", + "filtered_notifications_banner.mentions": "{count, plural, one {paminฤ—jimas} few {paminฤ—jimai} many {paminฤ—jimo} other {paminฤ—jimลณ}}", + "filtered_notifications_banner.pending_requests": "Praneลกimai iลก {count, plural, =0 {nฤ— vieno} one {vienos ลพmogaus} few {# ลพmoniลณ} many {# ลพmoniลณ} other {# ลพmoniลณ}}, kuriuos galbลซt paลพฤฏsti", + "filtered_notifications_banner.title": "Filtruojami praneลกimai", "firehose.all": "Visi", "firehose.local": "ล is serveris", "firehose.remote": "Kiti serveriai", @@ -290,13 +308,17 @@ "follow_requests.unlocked_explanation": "Nors tavo paskyra neuลพrakinta, {domain} personalas mano, kad galbลซt norฤ—si rankiniu bลซdu patikrinti ลกiลณ paskyrลณ sekimo praลกymus.", "follow_suggestions.curated_suggestion": "Personalo pasirinkimai", "follow_suggestions.dismiss": "Daugiau nerodyti", + "follow_suggestions.featured_longer": "Rankomis atrinkta {domain} komanda", + "follow_suggestions.friends_of_friends_longer": "Populiarus tarp ลพmoniลณ, kuriลณ seki", "follow_suggestions.hints.featured": "ล ฤฏ profilฤฏ atrinko {domain} komanda.", "follow_suggestions.hints.friends_of_friends": "ล is profilis yra populiarus tarp ลพmoniลณ, kuriuos seki.", - "follow_suggestions.hints.most_followed": "ล is profilis yra vienas iลก labiausiai sekamลณ {domain}.", - "follow_suggestions.hints.most_interactions": "Pastaruoju metu ลกis profilis sulaukia daug dฤ—mesio ลกiame {domain}.", + "follow_suggestions.hints.most_followed": "ล is profilis yra vienas iลก labiausiai sekamลณ domene {domain}.", + "follow_suggestions.hints.most_interactions": "Pastaruoju metu ลกis profilis sulaukia daug dฤ—mesio domane {domain}.", "follow_suggestions.hints.similar_to_recently_followed": "ล is profilis panaลกus ฤฏ profilius, kuriuos neseniai sekei.", "follow_suggestions.personalized_suggestion": "Suasmenintas pasiลซlymas", "follow_suggestions.popular_suggestion": "Populiarus pasiลซlymas", + "follow_suggestions.popular_suggestion_longer": "Populiarus domene {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Panaลกลซs ฤฏ profilius, kuriuos neseniai seki", "follow_suggestions.view_all": "Perลพiลซrฤ—ti viskฤ…", "follow_suggestions.who_to_follow": "Kฤ… sekti", "followed_tags": "Sekami saitaลพodลพiai", @@ -307,8 +329,8 @@ "footer.keyboard_shortcuts": "Spartieji klaviลกai", "footer.privacy_policy": "Privatumo politika", "footer.source_code": "Perลพiลซrฤ—ti ลกaltinio kodฤ…", - "footer.status": "Bลซsena", - "generic.saved": "Iลกsaugoti", + "footer.status": "Statusas", + "generic.saved": "Iลกsaugota", "getting_started.heading": "Kaip pradฤ—ti", "hashtag.column_header.tag_mode.all": "ir {additional}", "hashtag.column_header.tag_mode.any": "ar {additional}", @@ -328,7 +350,7 @@ "home.column_settings.show_reblogs": "Rodyti pakฤ—limus", "home.column_settings.show_replies": "Rodyti atsakymus", "home.hide_announcements": "Slฤ—pti skelbimus", - "home.pending_critical_update.body": "Kuo greiฤiau atnaujink savo Mastodon serverฤฏ!", + "home.pending_critical_update.body": "Kuo greiฤiau atnaujink savo Mastodon serverฤฏ.", "home.pending_critical_update.link": "ลฝiลซrฤ—ti naujinimus", "home.pending_critical_update.title": "Galimas kritinis saugumo naujinimas.", "home.show_announcements": "Rodyti skelbimus", @@ -392,6 +414,8 @@ "limited_account_hint.action": "Vis tiek rodyti profilฤฏ", "limited_account_hint.title": "ล ฤฏ profilฤฏ paslฤ—pฤ— {domain} priลพiลซrฤ—tojai.", "link_preview.author": "Sukลซrฤ— {name}", + "link_preview.more_from_author": "Daugiau iลก {name}", + "link_preview.shares": "{count, plural, one {{counter} ฤฏraลกas} few {{counter} ฤฏraลกai} many {{counter} ฤฏraลกo} other {{counter} ฤฏraลกลณ}}", "lists.account.add": "Pridฤ—ti ฤฏ sฤ…raลกฤ…", "lists.account.remove": "Paลกalinti iลก sฤ…raลกo", "lists.delete": "Iลกtrinti sฤ…raลกฤ…", @@ -410,6 +434,15 @@ "loading_indicator.label": "Kraunamaโ€ฆ", "media_gallery.toggle_visible": "{number, plural, one {Slฤ—pti vaizdฤ…} few {Slฤ—pti vaizdus} many {Slฤ—pti vaizdo} other {Slฤ—pti vaizdลณ}}", "moved_to_account_banner.text": "Tavo paskyra {disabledAccount} ลกiuo metu iลกjungta, nes persikฤ—lei ฤฏ {movedToAccount}.", + "mute_modal.hide_from_notifications": "Slฤ—pti nuo praneลกimลณ", + "mute_modal.hide_options": "Slฤ—pti parinktis", + "mute_modal.indefinite": "Kol atลกauksiu jลณ nutildymฤ…", + "mute_modal.show_options": "Rodyti parinktis", + "mute_modal.they_can_mention_and_follow": "Jie gali tave paminฤ—ti ir sekti, bet tu jลณ nematysi.", + "mute_modal.they_wont_know": "Jie neลพinos, kad buvo nutildyti.", + "mute_modal.title": "Nutildyti naudotojฤ…?", + "mute_modal.you_wont_see_mentions": "Nematysi ฤฏraลกus, kuriuose jie paminimi.", + "mute_modal.you_wont_see_posts": "Jie vis tiek gali matyti tavo ฤฏraลกus, bet tu nematysi jลณ.", "navigation_bar.about": "Apie", "navigation_bar.advanced_interface": "Atidaryti iลกplฤ—stinฤ™ ลพiniatinklio sฤ…sajฤ…", "navigation_bar.blocks": "Uลพblokuoti naudotojai", @@ -442,9 +475,19 @@ "notification.follow": "{name} seka tave", "notification.follow_request": "{name} papraลกฤ— tave sekti", "notification.mention": "{name} paminฤ—jo tave", + "notification.moderation-warning.learn_more": "Suลพinoti daugiau", + "notification.moderation_warning": "Gavai priลพiลซrฤ—jimo ฤฏspฤ—jimฤ…", + "notification.moderation_warning.action_delete_statuses": "Kai kurie tavo ฤฏraลกai buvo paลกalintos.", + "notification.moderation_warning.action_disable": "Tavo paskyra buvo iลกjungta.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Kai kurie tavo ฤฏraลกai buvo paลพymฤ—tos kaip jautrios.", + "notification.moderation_warning.action_none": "Tavo paskyra gavo priลพiลซrฤ—jimo ฤฏspฤ—jimฤ….", + "notification.moderation_warning.action_sensitive": "Nuo ลกiol tavo ฤฏraลกai bus paลพymฤ—ti kaip jautrลซs.", + "notification.moderation_warning.action_silence": "Tavo paskyra buvo apribota.", + "notification.moderation_warning.action_suspend": "Tavo paskyra buvo sustabdyta.", "notification.own_poll": "Tavo apklausa baigฤ—si", "notification.poll": "Apklausa, kurioje balsavai, pasibaigฤ—", "notification.reblog": "{name} pakฤ—lฤ— tavo ฤฏraลกฤ…", + "notification.relationships_severance_event": "Prarasti sฤ…ryลกiai su {name}", "notification.relationships_severance_event.learn_more": "Suลพinoti daugiau", "notification.relationships_severance_event.user_domain_block": "Tu uลพblokavai {target}. Paลกalinama {followersCount} savo sekฤ—jลณ ir {followingCount, plural, one {# paskyrฤ…} few {# paskyrai} many {# paskyros} other {# paskyrลณ}}, kurios seki.", "notification.status": "{name} kฤ… tik paskelbฤ—", @@ -465,7 +508,7 @@ "notifications.column_settings.follow_request": "Nauji sekimo praลกymai:", "notifications.column_settings.mention": "Paminฤ—jimai:", "notifications.column_settings.poll": "Balsavimo rezultatai:", - "notifications.column_settings.push": "Stumdomieji praneลกimai", + "notifications.column_settings.push": "Tiesioginiai praneลกimai", "notifications.column_settings.reblog": "Pakฤ—limai:", "notifications.column_settings.show": "Rodyti stulpelyje", "notifications.column_settings.sound": "Paleisti garsฤ…", @@ -504,7 +547,7 @@ "onboarding.follows.lead": "Tavo pagrindinis srautas โ€“ pagrindinis bลซdas patirti Mastodon. Kuo daugiau ลพmoniลณ seksi, tuo jis bus aktyvesnis ir ฤฏdomesnis. Norint pradฤ—ti, pateikiame keletฤ… pasiลซlymลณ:", "onboarding.follows.title": "Suasmenink savo pagrindinฤฏ srautฤ…", "onboarding.profile.discoverable": "Padaryti mano profilฤฏ atrandamฤ…", - "onboarding.profile.discoverable_hint": "Kai pasirenki Mastodon atrandamumฤ…, tavo ฤฏraลกai gali bลซti rodomi paieลกkos rezultatuose ir tendencijose, o profilis gali bลซti siลซlomas panaลกiลณ pomฤ—giลณ turintiems ลพmonฤ—ms.", + "onboarding.profile.discoverable_hint": "Kai sutinki su Mastodon atrandamumu, tavo ฤฏraลกai gali bลซti rodomi paieลกkos rezultatuose ir tendencijose, o profilis gali bลซti siลซlomas panaลกiลณ pomฤ—giลณ turintiems ลพmonฤ—ms.", "onboarding.profile.display_name": "Rodomas vardas", "onboarding.profile.display_name_hint": "Tavo pilnas vardas arba linksmas vardasโ€ฆ", "onboarding.profile.lead": "Gali visada tai uลพbaigti vฤ—liau nustatymuose, kur yra dar daugiau pritaikymo parinkฤiลณ.", @@ -529,7 +572,7 @@ "onboarding.steps.setup_profile.title": "Suasmenink savo profilฤฏ", "onboarding.steps.share_profile.body": "Leisk draugams suลพinoti, kaip tave rasti Mastodon.", "onboarding.steps.share_profile.title": "Bendrink savo Mastodon profilฤฏ", - "onboarding.tips.2fa": "Ar ลพinojai? Savo paskyrฤ… gali apsaugoti nustatฤ™s (-usi) dviejลณ veiksniลณ tapatybฤ—s nustatymฤ… paskyros nustatymuose. Jis veikia su bet kuria pasirinkta TOTP programฤ—le, telefono numeris nebลซtinas.", + "onboarding.tips.2fa": "Ar ลพinojai? Savo paskyrฤ… gali apsaugoti nustatant dviejลณ veiksniลณ tapatybฤ—s nustatymฤ… paskyros nustatymuose. Jis veikia su bet kuria pasirinkta TOTP programฤ—le, telefono numeris nebลซtinas.", "onboarding.tips.accounts_from_other_servers": "Ar ลพinojai? Kadangi Mastodon decentralizuotas, kai kurie profiliai, su kuriais susidursi, bus talpinami ne tavo, o kituose serveriuose. Ir vis tiek galฤ—si su jais sklandลพiai bendrauti! Jลณ serveris yra antroje naudotojo vardo pusฤ—je.", "onboarding.tips.migration": "Ar ลพinojai? Jei manai, kad {domain} serveris ateityje tau netiks, gali persikelti ฤฏ kitฤ… Mastodon serverฤฏ neprarandant savo sekฤ—jลณ. Gali net talpinti savo paties serverฤฏ.", "onboarding.tips.verification": "Ar ลพinojai? Savo paskyrฤ… gali patvirtinti pateikฤ™s (-usi) nuorodฤ… ฤฏ Mastodon profilฤฏ savo interneto svetainฤ—je ir pridฤ—jฤ™s (-usi) svetainฤ™ prie savo profilio. Nereikia jokiลณ mokesฤiลณ ar dokumentลณ.", @@ -600,7 +643,7 @@ "report.reasons.legal_description": "Manai, kad tai paลพeidลพia tavo arba serverio ลกalies ฤฏstatymus", "report.reasons.other": "Tai kaลพkas kita", "report.reasons.other_description": "Problema netinka kitoms kategorijoms", - "report.reasons.spam": "Tai ลกlamลกtas", + "report.reasons.spam": "Tai โ€“ ลกlamลกtas", "report.reasons.spam_description": "Kenkฤ—jiลกkos nuorodos, netikras ฤฏsitraukimas arba pasikartojantys atsakymai", "report.reasons.violation": "Tai paลพeidลพia serverio taisykles", "report.reasons.violation_description": "ลฝinai, kad tai paลพeidลพia konkreฤias taisykles", @@ -648,13 +691,13 @@ "server_banner.about_active_users": "ลฝmonฤ—s, kurie naudojosi ลกiuo serveriu per pastarฤ…sias 30 dienลณ (mฤ—nesio aktyvลซs naudotojai)", "server_banner.active_users": "aktyvลซs naudotojai", "server_banner.administered_by": "Administruoja:", - "server_banner.introduction": "{domain} โ€“ decentralizuoto socialinio tinklo dalis, kurฤฏ palaiko {mastodon}.", - "server_banner.learn_more": "Suลพinoti daugiau", + "server_banner.is_one_of_many": "{domain} โ€“ tai vienas iลก daugelio nepriklausomลณ โ€žMastodonโ€œ serveriลณ, kuriuos gali naudoti fediverse.", "server_banner.server_stats": "Serverio statistika:", "sign_in_banner.create_account": "Sukurti paskyrฤ…", + "sign_in_banner.follow_anyone": "Sek bet kurฤฏ asmenฤฏ visoje fediverse ir ลพiลซrฤ—k viskฤ… chronologine tvarka. Jokiลณ algoritmลณ, reklamลณ ar paspaudimลณ.", + "sign_in_banner.mastodon_is": "โ€žMastodonโ€œ โ€“ tai geriausias bลซdas sekti, kas vyksta.", "sign_in_banner.sign_in": "Prisijungimas", "sign_in_banner.sso_redirect": "Prisijungti arba uลพsiregistruoti", - "sign_in_banner.text": "Prisijunk, kad galฤ—tum sekti profilius arba saitaลพodลพius, mฤ—gsti, bendrinti ir atsakyti ฤฏ ฤฏraลกus. Taip pat gali bendrauti iลก savo paskyros kitame serveryje.", "status.admin_account": "Atidaryti priลพiลซrฤ—jimo sฤ…sajฤ… @{name}", "status.admin_domain": "Atidaryti priลพiลซrฤ—jimo sฤ…sajฤ… {domain}", "status.admin_status": "Atidaryti ลกฤฏ ฤฏraลกฤ… priลพiลซrฤ—jimo sฤ…sajoje", diff --git a/app/javascript/mastodon/locales/lv.json b/app/javascript/mastodon/locales/lv.json index 55ceb564b6..701569fa05 100644 --- a/app/javascript/mastodon/locales/lv.json +++ b/app/javascript/mastodon/locales/lv.json @@ -8,7 +8,7 @@ "about.domain_blocks.silenced.title": "Ierobeลพotie", "about.domain_blocks.suspended.explanation": "Nekฤdi dati no ลกฤซ servera netiks apstrฤdฤti, uzglabฤti vai apmainฤซti, padarot neiespฤ“jamu mijiedarbฤซbu vai saziล†u ar lietotฤjiem no ลกฤซ servera.", "about.domain_blocks.suspended.title": "Apturฤ“tie", - "about.not_available": "ล ฤซ informฤcija ลกajฤ serverฤซ nav bijusi pieejama.", + "about.not_available": "ล ฤซ informฤcija nav padarฤซta pieejama ลกajฤ serverฤซ.", "about.powered_by": "Decentralizฤ“tu sociฤlo tฤซklu nodroลกina {mastodon}", "about.rules": "Servera noteikumi", "account.account_note_header": "Piezฤซme", @@ -35,9 +35,8 @@ "account.follow_back": "Sekot atpakaฤผ", "account.followers": "Sekotฤji", "account.followers.empty": "ล im lietotฤjam vฤ“l nav sekotฤju.", - "account.followers_counter": "{count, plural, zero {{counter} sekotฤju} one {{counter} sekotฤjs} other {{counter} sekotฤji}}", + "account.followers_counter": "{count, plural, zero {{count} sekotฤju} one {{count} sekotฤjs} other {{count} sekotฤji}}", "account.following": "Seko", - "account.following_counter": "{count, plural, zero{{counter} sekojamo} one {{counter} sekojamais} other {{counter} sekojamie}}", "account.follows.empty": "ล is lietotฤjs pagaidฤm nevienam neseko.", "account.go_to_profile": "Doties uz profilu", "account.hide_reblogs": "Paslฤ“pt @{name} pastiprinฤtos ierakstus", @@ -63,7 +62,6 @@ "account.requested_follow": "{name} nosลซtฤซja Tev sekoลกanas pieprasฤซjumu", "account.share": "Dalฤซties ar @{name} profilu", "account.show_reblogs": "Parฤdฤซt @{name} pastiprinฤtos ierakstus", - "account.statuses_counter": "{count, plural, zero {{counter} ierakstu} one {{counter} ieraksts} other {{counter} ieraksti}}", "account.unblock": "Atbloฤทฤ“t @{name}", "account.unblock_domain": "Atbloฤทฤ“t domฤ“nu {domain}", "account.unblock_short": "Atbloฤทฤ“t", @@ -89,6 +87,11 @@ "announcement.announcement": "Paziล†ojums", "attachments_list.unprocessed": "(neapstrฤdฤti)", "audio.hide": "Slฤ“pt audio", + "block_modal.remote_users_caveat": "Mฤ“s vaicฤsim serverim {domain} ล†emt vฤ“rฤ Tavu lฤ“mumu. Tomฤ“r atbilstฤซba nav nodroลกinฤta, jo atseviลกฤทi serveri var apstrฤdฤt bloฤทฤ“ลกanu citฤdi. Publiski ieraksti joprojฤm var bลซt redzami lietotฤjiem, kuri nav pieteikuลกies.", + "block_modal.show_less": "Rฤdฤซt mazฤk", + "block_modal.show_more": "Parฤdฤซt mazฤk", + "block_modal.they_cant_mention": "Nevar Tevi pieminฤ“t vai sekot Tev.", + "block_modal.they_cant_see_posts": "Nevar redzฤ“t Tavus ierakstus, un Tu neredzฤ“si lietotฤja.", "boost_modal.combo": "Nospied {combo}, lai nฤkamreiz ลกo izlaistu", "bundle_column_error.copy_stacktrace": "Kopฤ“t kฤผลซdu ziล†ojumu", "bundle_column_error.error.body": "Pieprasฤซto lapu nevarฤ“ja atveidot. Tas varฤ“tu bลซt saistฤซts ar kฤผลซdu mลซsu kodฤ, vai tฤ ir pฤrlลซkprogrammas saderฤซbas problฤ“ma.", @@ -170,7 +173,7 @@ "confirmations.discard_edit_media.message": "Ir nesaglabฤtas izmaiล†as informฤcijas nesฤ“ja aprakstฤ vai priekลกskatฤซjumฤ. Vฤ“lies tฤs atmest tik un tฤ?", "confirmations.domain_block.message": "Vai tu tieลกฤm vฤ“lies bloฤทฤ“t visu domฤ“nu {domain}? Parasti pietiek, ja nobloฤทฤ“ vai apklusini kฤdu. Tu neredzฤ“si saturu vai paziล†ojumus no ลกฤซ domฤ“na nevienฤ laika lฤซnijฤ. Tavi sekotฤji no ลกฤซ domฤ“na tiks noล†emti.", "confirmations.edit.confirm": "Labot", - "confirmations.edit.message": "Rediฤฃฤ“jot, tiks pฤrrakstฤซts ziล†ojums, kuru tu ลกobrฤซd raksti. Vai tieลกฤm vฤ“lies turpinฤt?", + "confirmations.edit.message": "Laboลกana pฤrrakstฤซs ziล†ojumu, kas ลกobrฤซd tiek sastฤdฤซts. Vai tieลกฤm turpinฤt?", "confirmations.logout.confirm": "Iziet", "confirmations.logout.message": "Vai tieลกฤm vฤ“lies izrakstฤซties?", "confirmations.mute.confirm": "Apklusinฤt", @@ -190,7 +193,7 @@ "directory.federated": "No pazฤซstamas federฤcijas", "directory.local": "Tikai no {domain}", "directory.new_arrivals": "Jaunpienฤcฤ“ji", - "directory.recently_active": "Nesen aktฤซvie", + "directory.recently_active": "Nesen aktฤซvi", "disabled_account_banner.account_settings": "Konta iestatฤซjumi", "disabled_account_banner.text": "Tavs konts {disabledAccount} paลกlaik ir atspฤ“jots.", "dismissable_banner.community_timeline": "ล ie ir jaunฤkie publiskie ieraksti no cilvฤ“kiem, kuru konti ir mitinฤti {domain}.", @@ -199,6 +202,9 @@ "dismissable_banner.explore_statuses": "ล ie ir ieraksti, kas ลกodien gลซst arvien lielฤku ievฤ“rฤซbu visฤ sociฤlajฤ tฤซklฤ. Augstฤk tiek kฤrtoti jaunฤki ieraksti, kuri tiek vairฤk pastiprinฤti un ievietoti izlasฤ“s.", "dismissable_banner.explore_tags": "ล ie tฤ“mturi ลกobrฤซd kฤผลซst arvien populฤrฤki cilvฤ“ku vidลซ ลกajฤ un citos decentralizฤ“tฤ tฤซkla serveros.", "dismissable_banner.public_timeline": "ล ie ir jaunฤkie publiskie ieraksti no lietotฤjiem sociฤlajฤ tฤซmeklฤซ, kuriem {domain} seko cilvฤ“ki.", + "domain_block_modal.they_cant_follow": "Neviens ลกajฤ serverฤซ nevar Tev sekot.", + "domain_pill.server": "Serveris", + "domain_pill.username": "Lietotฤjvฤrds", "embed.instructions": "Iestrฤdฤ ลกo ziล†u savฤ mฤjaslapฤ, kopฤ“jot zemฤk redzamo kodu.", "embed.preview": "Tas izskatฤซsies ลกฤdi:", "emoji_button.activity": "Aktivitฤte", @@ -275,6 +281,7 @@ "follow_suggestions.curated_suggestion": "Darbinieku izvฤ“le", "follow_suggestions.dismiss": "Vairs nerฤdฤซt", "follow_suggestions.personalized_suggestion": "Pielฤgots ieteikums", + "follow_suggestions.similar_to_recently_followed_longer": "Lฤซdzฤซgi profieliem, kuriem nesen sฤki sekot", "follow_suggestions.view_all": "Skatฤซt visu", "follow_suggestions.who_to_follow": "Kam sekot", "followed_tags": "Sekojamie tฤ“mturi", @@ -302,13 +309,13 @@ "hashtag.counter_by_uses_today": "{count, plural, zero {{counter} ierakstu} one {{counter} ieraksts} other {{counter} ieraksti}} ลกodien", "hashtag.follow": "Sekot tฤ“mturim", "hashtag.unfollow": "Pฤrstฤt sekot tฤ“mturim", - "hashtags.and_other": "..un {count, plural, other {# vairฤk}}", + "hashtags.and_other": "โ€ฆ un {count, plural, other {vฤ“l #}}", "home.column_settings.show_reblogs": "Rฤdฤซt pastiprinฤtos ierakstus", "home.column_settings.show_replies": "Rฤdฤซt atbildes", "home.hide_announcements": "Slฤ“pt paziล†ojumus", - "home.pending_critical_update.body": "Lลซdzu, pฤ“c iespฤ“jas ฤtrฤk atjaunini savu Mastodon serveri!", + "home.pending_critical_update.body": "Lลซgums pฤ“c iespฤ“jas drฤซzฤk atjauninฤt savu Mastodon serveri.", "home.pending_critical_update.link": "Skatฤซt jauninฤjumus", - "home.pending_critical_update.title": "Pieejams kritisks droลกฤซbas jauninฤjums!", + "home.pending_critical_update.title": "Ir pieejams bลซtisks droลกฤซbas atjauninฤjums.", "home.show_announcements": "Rฤdฤซt paziล†ojumus", "interaction_modal.description.favourite": "Ar Mastodon kontu tu vari pievienot ลกo ziล†u izlasei, lai informฤ“tu autoru, ka to novฤ“rtฤ“, un saglabฤtu to vฤ“lฤkai lasฤซลกanai.", "interaction_modal.description.follow": "Ar Mastodon kontu Tu vari sekot {name}, lai saล†emtu lietotฤja ierakstus savฤ mฤjas plลซsmฤ.", @@ -370,6 +377,7 @@ "limited_account_hint.action": "Tik un tฤ rฤdฤซt profilu", "limited_account_hint.title": "{domain} moderatori ir paslฤ“puลกi ลกo profilu.", "link_preview.author": "Pฤ“c {name}", + "link_preview.more_from_author": "Vairฤk no {name}", "lists.account.add": "Pievienot sarakstam", "lists.account.remove": "Noล†emt no saraksta", "lists.delete": "Izdzฤ“st sarakstu", @@ -388,6 +396,10 @@ "loading_indicator.label": "Ielฤdฤ“โ€ฆ", "media_gallery.toggle_visible": "{number, plural, one {Slฤ“pt attฤ“lu} other {Slฤ“pt attฤ“lus}}", "moved_to_account_banner.text": "Tavs konts {disabledAccount} paลกlaik ir atspฤ“jots, jo Tu pฤrcฤ“lies uz kontu {movedToAccount}.", + "mute_modal.hide_from_notifications": "Paslฤ“pt paziล†ojumos", + "mute_modal.hide_options": "Paslฤ“pt iespฤ“jas", + "mute_modal.show_options": "Parฤdฤซt iespฤ“jas", + "mute_modal.title": "Apklusinฤt lietotฤju?", "navigation_bar.about": "Par", "navigation_bar.advanced_interface": "Atvฤ“rt paplaลกinฤtฤ tฤซmekฤผa saskarnฤ“", "navigation_bar.blocks": "Bloฤทฤ“tie lietotฤji", @@ -420,17 +432,32 @@ "notification.follow": "{name} uzsฤka Tev sekot", "notification.follow_request": "{name} nosลซtฤซja Tev sekoลกanas pieprasฤซjumu", "notification.mention": "{name} pieminฤ“ja Tevi", + "notification.moderation-warning.learn_more": "Uzzinฤt vairฤk", + "notification.moderation_warning.action_delete_statuses": "Daลพi no Taviem ierakstiem tika noล†emti.", + "notification.moderation_warning.action_disable": "Tavs konts tika atspฤ“jots.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Daลพi no Taviem ierakstiem tika atzฤซmฤ“ti kฤ jutฤซgi.", + "notification.moderation_warning.action_sensitive": "Tavi ieraksti turpmฤk tiks atzฤซmฤ“ti kฤ jutฤซgi.", + "notification.moderation_warning.action_silence": "Tavs konts tika ierobeลพots.", + "notification.moderation_warning.action_suspend": "Tava konta darbฤซba tika apturฤ“ta.", "notification.own_poll": "Tava aptauja ir noslฤ“gusies", "notification.poll": "Aptauja, kurฤ tu piedalฤซjies, ir noslฤ“gusies", "notification.reblog": "{name} pastiprinฤja Tavu ierakstu", + "notification.relationships_severance_event": "Zaudฤ“ti savienojumi ar {name}", + "notification.relationships_severance_event.learn_more": "Uzzinฤt vairฤk", "notification.status": "{name} tikko publicฤ“ja", - "notification.update": "{name} rediฤฃฤ“ja ierakstu", + "notification.update": "{name} laboja ierakstu", + "notification_requests.accept": "Pieล†emt", + "notification_requests.dismiss": "Noraidฤซt", + "notification_requests.notifications_from": "Paziล†ojumi no {name}", + "notification_requests.title": "Atlasฤซtie paziล†ojumi", "notifications.clear": "Notฤซrฤซt paziล†ojumus", "notifications.clear_confirmation": "Vai tieลกฤm vฤ“lies neatgriezeniski notฤซrฤซt visus savus paziล†ojumus?", "notifications.column_settings.admin.report": "Jauni ziล†ojumi:", "notifications.column_settings.admin.sign_up": "Jaunas pierakstฤซลกanฤs:", "notifications.column_settings.alert": "Darbvirsmas paziล†ojumi", "notifications.column_settings.favourite": "Izlase:", + "notifications.column_settings.filter_bar.advanced": "Attฤ“lot visas kategorijas", + "notifications.column_settings.filter_bar.category": "Atrฤs atlasฤซลกanas josla", "notifications.column_settings.follow": "Jauni sekotฤji:", "notifications.column_settings.follow_request": "Jauni sekoลกanas pieprasฤซjumi:", "notifications.column_settings.mention": "Pieminฤ“ลกanas:", @@ -456,6 +483,11 @@ "notifications.permission_denied": "Darbvirsmas paziล†ojumi nav pieejami, jo iepriekลก tika noraidฤซts pฤrlลซka atฤผauju pieprasฤซjums", "notifications.permission_denied_alert": "Darbvirsmas paziล†ojumus nevar iespฤ“jot, jo pฤrlลซkprogrammai atฤผauja tika iepriekลก atteikta", "notifications.permission_required": "Darbvirsmas paziล†ojumi nav pieejami, jo nav pieลกฤทirta nepiecieลกamฤ atฤผauja.", + "notifications.policy.filter_new_accounts_title": "Jauni konti", + "notifications.policy.filter_not_followers_title": "Cilvฤ“ki, kuri Tev neseko", + "notifications.policy.filter_not_following_hint": "Lฤซdz tos paลกrocฤซgi apstiprinฤsi", + "notifications.policy.filter_not_following_title": "Cilvฤ“ki, kuriem Tu neseko", + "notifications.policy.title": "Atlasฤซt paziล†ojumus noโ€ฆ", "notifications_permission_banner.enable": "Iespฤ“jot darbvirsmas paziล†ojumus", "notifications_permission_banner.how_to_control": "Lai saล†emtu paziล†ojumus, kad Mastodon nav atvฤ“rts, iespฤ“jo darbvirsmas paziล†ojumus. Vari precฤซzi kontrolฤ“t, kฤda veida mijiedarbฤซbas rada darbvirsmas paziล†ojumus, izmantojot augstฤk redzamo pogu {icon}, kad tie bลซs iespฤ“joti.", "notifications_permission_banner.title": "Nekad nepalaid neko garฤm", @@ -465,7 +497,7 @@ "onboarding.actions.go_to_home": "Dodieties uz manu mฤjas plลซsmu", "onboarding.compose.template": "Sveiki, #Mastodon!", "onboarding.follows.empty": "Diemลพฤ“l paลกlaik nevar parฤdฤซt rezultฤtus. Vari mฤ“ฤฃinฤt izmantot meklฤ“ลกanu vai pฤrlลซkot izpฤ“tes lapu, lai atrastu cilvฤ“kus, kuriem sekot, vai vฤ“lฤk mฤ“ฤฃinฤt vฤ“lreiz.", - "onboarding.follows.lead": "Tava mฤjas plลซsma ir galvenais veids, kฤ izbaudฤซt Mastodon. Jo vairฤk cilvฤ“ku sekosi, jo aktฤซvฤk un interesantฤk tas bลซs. Lai sฤktu, ลกeit ir daลพi ieteikumi:", + "onboarding.follows.lead": "Tava mฤjas plลซsma ir galvenais veids, kฤ pieredzฤ“t Mastodon. Jo vairฤk cilvฤ“kiem sekosi, jo dzฤซvฤซgฤka un aizraujoลกฤka tฤ bลซs. Lai sฤktu, ลกeit ir daลพi ieteikumi:", "onboarding.follows.title": "Pielฤgo savu mฤjas barotni", "onboarding.profile.discoverable": "Padarฤซt manu profilu atklฤjamu", "onboarding.profile.display_name": "Attฤ“lojamais vฤrds", @@ -485,7 +517,7 @@ "onboarding.start.title": "Tev tas izdevฤs!", "onboarding.steps.follow_people.body": "Tu pats veido savu plลซsmu. Piepildฤซsim to ar interesantiem cilvฤ“kiem.", "onboarding.steps.follow_people.title": "Pielฤgo savu mฤjas barotni", - "onboarding.steps.publish_status.body": "Sveicini pasauli ar tekstu, fotoattฤ“liem, video, vai aptaujฤm {emoji}", + "onboarding.steps.publish_status.body": "Pasveicini pasauli ar tekstu, attฤ“liem, video vai aptaujฤm {emoji}", "onboarding.steps.publish_status.title": "Izveido savu pirmo ziล†u", "onboarding.steps.setup_profile.body": "Palielini mijiedarbฤซbu ar aptveroลกu profilu!", "onboarding.steps.setup_profile.title": "Pielฤgo savu profilu", @@ -513,7 +545,9 @@ "privacy.direct.short": "Noteikti cilvฤ“ki", "privacy.private.long": "Tikai Tavi sekotฤji", "privacy.private.short": "Sekotฤji", + "privacy.public.long": "Jebkurลก Mastodon un ฤrpus tฤ", "privacy.public.short": "Publiska", + "privacy.unlisted.long": "Mazฤk algoritmisku fanfaru", "privacy_policy.last_updated": "Pฤ“dฤ“jo reizi atjauninฤta {date}", "privacy_policy.title": "Privฤtuma politika", "recommended": "Ieteicams", @@ -531,6 +565,7 @@ "relative_time.minutes": "{number}m", "relative_time.seconds": "{number}s", "relative_time.today": "ลกodien", + "reply_indicator.attachments": "{count, plural, zero{# pielikumu} one {# pielikums} other {# pielikumi}}", "reply_indicator.cancel": "Atcelt", "reply_indicator.poll": "Aptauja", "report.block": "Bloฤทฤ“t", @@ -603,15 +638,12 @@ "search_results.statuses": "Ieraksti", "search_results.title": "Meklฤ“t {q}", "server_banner.about_active_users": "Cilvฤ“ki, kas izmantojuลกi ลกo serveri pฤ“dฤ“jo 30ย dienu laikฤ (aktฤซvie lietotฤji mฤ“nesฤซ)", - "server_banner.active_users": "aktฤซvie lietotฤji", - "server_banner.administered_by": "Administrฤ“:", - "server_banner.introduction": "{domain} ir daฤผa no decentralizฤ“tฤ sociฤlฤ tฤซkla, ko nodroลกina {mastodon}.", - "server_banner.learn_more": "Uzzinฤt vairฤk", + "server_banner.active_users": "aktฤซvi lietotฤji", + "server_banner.administered_by": "Pฤrvalda:", "server_banner.server_stats": "Servera statistika:", "sign_in_banner.create_account": "Izveidot kontu", "sign_in_banner.sign_in": "Pieteikties", "sign_in_banner.sso_redirect": "Piesakies vai Reฤฃistrฤ“jies", - "sign_in_banner.text": "Jฤpiesakฤs, lai sekotu profiliem vai tฤ“mturiem, pievienotu izlasei, kopฤซgotu ierakstus un atbildฤ“tu uz tiem. Vari arฤซ mijiedarboties ar savu kontu citฤ serverฤซ.", "status.admin_account": "Atvฤ“rt @{name} moderฤ“ลกanas saskarni", "status.admin_domain": "Atvฤ“rt {domain} moderฤ“ลกanas saskarni", "status.admin_status": "Atvฤ“rt ลกo ziล†u moderฤcijas saskarnฤ“", @@ -625,9 +657,11 @@ "status.direct": "Pieminฤ“t @{name} privฤti", "status.direct_indicator": "Pieminฤ“ts privฤti", "status.edit": "Labot", - "status.edited_x_times": "Labots {count, plural, one {{count} reizi} other {{count} reizes}}", + "status.edited": "Pฤ“dฤ“joreiz labots {date}", + "status.edited_x_times": "Labots {count, plural, zero {{count} reiลพu} one {{count} reizi} other {{count} reizes}}", "status.embed": "Iegult", "status.favourite": "Izlasฤ“", + "status.favourites": "{count, plural, zero {izlasฤ“s} one {izlasฤ“} other {izlasฤ“s}}", "status.filter": "Filtrฤ“ ลกo ziล†u", "status.filtered": "Filtrฤ“ts", "status.hide": "Slฤ“pt ierakstu", @@ -648,6 +682,7 @@ "status.reblog": "Pastiprinฤt", "status.reblog_private": "Pastiprinฤt, nemainot redzamฤซbu", "status.reblogged_by": "{name} pastiprinฤja", + "status.reblogs": "{count, plural, zero {pastiprinฤjumu} one {pastiprinฤjums} other {pastiprinฤjumi}}", "status.reblogs.empty": "Neviens ลกo ierakstu vฤ“l nav pastiprinฤjis. Kad bลซs, tie parฤdฤซsies ลกeit.", "status.redraft": "Dzฤ“st un pฤrrakstฤซt", "status.remove_bookmark": "Noล†emt grฤmatzฤซmi", diff --git a/app/javascript/mastodon/locales/mk.json b/app/javascript/mastodon/locales/mk.json index d8a470ed47..a09ad98ebf 100644 --- a/app/javascript/mastodon/locales/mk.json +++ b/app/javascript/mastodon/locales/mk.json @@ -38,7 +38,6 @@ "account.requested": "ะกะต ั‡ะตะบะฐ ะพะดะพะฑั€ัƒะฒะฐัšะต. ะšะปะธะบะฝะธ ะทะฐ ะดะฐ ะพะดะบะฐะถะธัˆ ะฑะฐั€ะฐัšะต ะทะฐ ัะปะตะดะตัšะต", "account.share": "ะกะฟะพะดะตะปะธ @{name} ะฟั€ะพั„ะธะป", "account.show_reblogs": "ะŸั€ะธะบะฐะถะธ ะฑัƒัั‚ะพะฒะธ ะพะด @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account.unblock": "ะžะดะฑะปะพะบะธั€ะฐั˜ @{name}", "account.unblock_domain": "ะŸั€ะธะบะฐะถะธ {domain}", "account.unendorse": "ะะต ะฟั€ะธะบะฐะถัƒะฒะฐั˜ ะฝะฐ ะฟั€ะพั„ะธะป", diff --git a/app/javascript/mastodon/locales/ml.json b/app/javascript/mastodon/locales/ml.json index 8fb4e818db..d9caccef34 100644 --- a/app/javascript/mastodon/locales/ml.json +++ b/app/javascript/mastodon/locales/ml.json @@ -22,9 +22,7 @@ "account.follow": "เดชเดฟเดจเตเดคเตเดŸเดฐเตเด•", "account.followers": "เดชเดฟเดจเตเดคเตเดŸเดฐเตเดจเตเดจเดตเตผ", "account.followers.empty": "เดˆ เด‰เดชเดฏเต‹เด•เตเดคเดพเดตเดฟเดจเต† เด†เดฐเตเด‚ เด‡เดคเตเดตเดฐเต† เดชเดฟเดจเตเดคเตเดŸเดฐเตเดจเตเดจเดฟเดฒเตเดฒ.", - "account.followers_counter": "{count, plural, one {{counter} เดชเดฟเดจเตเดคเตเดŸเดฐเตเดจเตเดจเดตเตผ} other {{counter} เดชเดฟเดจเตเดคเตเดŸเดฐเตเดจเตเดจเดตเตผ}}", "account.following": "เดชเดฟเดจเตเดคเตเดŸเดฐเตเดจเตเดจเต", - "account.following_counter": "{count, plural, one {{counter} เดชเดฟเดจเตเดคเตเดŸเดฐเตเดจเตเดจเต} other {{counter} เดชเดฟเดจเตเดคเตเดŸเดฐเตเดจเตเดจเต}}", "account.follows.empty": "เดˆ เด‰เดชเดฏเต‹เด•เตเดคเดพเดตเต เด†เดฐเต‡เดฏเตเด‚ เด‡เดคเตเดตเดฐเต† เดชเดฟเดจเตเดคเตเดŸเดฐเตเดจเตเดจเดฟเดฒเตเดฒ.", "account.go_to_profile": "เดชเตเดฐเตŠเดซเตˆเดฒเดฟเดฒเต‡เด•เตเด•เต เดชเต‹เด•เดพเด‚", "account.hide_reblogs": "@{name} เดฌเต‚เดธเตเดฑเตเดฑเต เดšเต†เดฏเตเดคเดต เดฎเดฑเดฏเตเด•เตเด•", @@ -42,7 +40,6 @@ "account.requested": "เด…เดจเตเดตเดพเดฆเดคเตเดคเดฟเดจเดพเดฏเดฟ เด•เดพเดคเตเดคเดฟเดฐเดฟเด•เตเด•เตเดจเตเดจเต. เดชเดฟเดจเตเดคเตเดŸเดฐเดพเดจเตเดณเตเดณ เด…เดชเต‡เด•เตเดท เดฑเดฆเตเดฆเดพเด•เตเด•เตเดตเดพเตป เดžเต†เด•เตเด•เตเด•", "account.share": "@{name} เดจเตเดฑเต† เดชเตเดฐเตŠเดซเตˆเตฝ เดชเด™เตเด•เดฟเดŸเตเด•", "account.show_reblogs": "@{name} เตฝ เดจเดฟเดจเตเดจเตเดณเตเดณ เดฌเต‚เดธเตเดฑเตเดฑเตเด•เตพ เด•เดพเดฃเดฟเด•เตเด•เตเด•", - "account.statuses_counter": "{count, plural, one {{counter} เดŸเต‚เดŸเตเดŸเต} other {{counter} เดŸเต‚เดŸเตเดŸเตเด•เตพ}}", "account.unblock": "@{name} เดคเดŸเดžเตเดžเดคเต เดฎเดพเดฑเตเดฑเตเด•", "account.unblock_domain": "{domain} เดŽเดจเตเดจ เดฎเต‡เด–เดฒ เดตเต†เดณเดฟเดชเตเดชเต†เดŸเตเดคเตเดคเตเด•", "account.unblock_short": "เด…เตบเดฌเตเดฒเต‹เด•เตเด•เต เดšเต†เดฏเตเดฏเตเด•", diff --git a/app/javascript/mastodon/locales/mr.json b/app/javascript/mastodon/locales/mr.json index a00b39e838..2757b96f94 100644 --- a/app/javascript/mastodon/locales/mr.json +++ b/app/javascript/mastodon/locales/mr.json @@ -17,9 +17,12 @@ "account.badges.group": "เค—เคŸ", "account.block": "@{name} เคฏเคพเค‚เคจเคพ เคฌเฅเคฒเฅ‰เค• เค•เคฐเคพ", "account.block_domain": "{domain} เคชเคพเคธเฅ‚เคจ เคธเคฐเฅเคต เคฒเคชเคตเคพ", + "account.block_short": "เค…เคตเคฐเฅ‹เคง", "account.blocked": "เคฌเฅเคฒเฅ‰เค• เค•เฅ‡เคฒเฅ‡ เค†เคนเฅ‡", "account.browse_more_on_origin_server": "เคฎเฅ‚เคณ เคชเฅเคฐเฅ‹เคซเคพเค‡เคฒเคตเคฐ เค…เคงเคฟเค• เคฌเฅเคฐเคพเค‰เค เค•เคฐเคพ", "account.cancel_follow_request": "เคซเฅ‰เคฒเฅ‹ เคตเคฟเคจเค‚เคคเฅ€ เคฎเคพเค—เฅ‡ เค˜เฅเคฏเคพ", + "account.copy": "เคฆเฅเคตเคพ เค•เฅ‰เคชเฅ€ เค•เคฐเคพ", + "account.direct": "เค–เคพเคœเค—เฅ€เคฐเคฟเคคเฅเคฏเคพ เค‰เคฒเฅเคฒเฅ‡เค–เฅ€เคค @{name}", "account.disable_notifications": "เคœเฅ‡เคตเฅเคนเคพ @{name} เคชเฅ‹เคธเฅเคŸ เค•เคฐเคคเคพเคค เคคเฅ‡เคตเฅเคนเคพ เคฎเคฒเคพ เคธเฅ‚เคšเคฟเคค เค•เคฐเคฃเฅ‡ เคฅเคพเค‚เคฌเคตเคพ", "account.domain_blocked": "Domain hidden", "account.edit_profile": "เคชเฅเคฐเฅ‹เคซเคพเค‡เคฒ เคเคกเคฟเคŸ เค•เคฐเคพ", @@ -29,11 +32,10 @@ "account.featured_tags.last_status_never": "เคชเฅ‹เคธเฅเคŸ เคจเคพเคนเฅ€เคค", "account.featured_tags.title": "{name} เคšเฅ‡ เคตเฅˆเคถเคฟเคทเฅเคŸเฅเคฏเฅ€เค•เฅƒเคค เคนเฅ…เคถเคŸเฅ…เค—", "account.follow": "เค…เคจเฅเคฏเคพเคฏเฅ€ เคตเฅเคนเคพ", + "account.follow_back": "เค†เคชเคฃเคนเฅ€ เค…เคจเฅเคธเคฐเคฃ เค•เคฐเคพ", "account.followers": "เค…เคจเฅเคฏเคพเคฏเฅ€", "account.followers.empty": "เคนเฅเคฏเคพ เคตเคพเคชเคฐเค•เคฐเฅเคคเฅเคฏเคพเคšเคพ เค†เคคเคพเคชเคฐเฅเคฏเค‚เคค เค•เฅ‹เคฃเฅ€ เค…เคจเฅเคฏเคพเคฏเฅ€ เคจเคพเคนเฅ€.", - "account.followers_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account.following": "เค…เคจเฅเคธเคฐเคฃ", - "account.following_counter": "{count, plural, one {{counter} following} other {{counter} following}}", "account.follows.empty": "เคนเคพ เคตเคพเคชเคฐเค•เคฐเฅเคคเคพ เค…เคœเฅ‚เคจเคชเคฐเฅเคฏเค‚เคค เค•เฅ‹เคฃเคพเคšเคพ เค…เคจเฅเคฏเคพเคฏเฅ€ เคจเคพเคนเฅ€.", "account.go_to_profile": "เคชเฅเคฐเฅ‹เคซเคพเค‡เคฒ เคตเคฐ เคœเคพ", "account.hide_reblogs": "@{name} เคชเคพเคธเฅ‚เคจ เคธเคฐเฅเคต เคฌเฅ‚เคธเฅเคŸ เคฒเคชเคตเคพ", @@ -45,6 +47,7 @@ "account.mention": "@{name} เคšเคพ เค‰เคฒเฅเคฒเฅ‡เค– เค•เคฐเคพ", "account.moved_to": "{name} เคจเฅ‡ เคธเฅ‚เคšเคฟเคค เค•เฅ‡เคฒเฅ‡ เค†เคนเฅ‡ เค•เฅ€ เคคเฅเคฏเคพเค‚เคšเฅ‡ เคจเคตเฅ€เคจ เค–เคพเคคเฅ‡ เค†เคคเคพ เค†เคนเฅ‡:", "account.mute": "@{name} เคฒเคพ เคฎเฅ‚เค• เค•เคพเคฐเคพ", + "account.mute_short": "เคจเคฟ:เคถเคฌเฅเคฆ", "account.muted": "เคฎเฅŒเคจ", "account.open_original_page": "เคฎเฅ‚เคณ เคชเฅƒเคทเฅเค  เค‰เค˜เคกเคพ", "account.posts": "Toots", @@ -54,7 +57,6 @@ "account.requested_follow": "{name} เคจเฅ‡ เค†เคชเคฒเฅเคฏเคพเคฒเคพ เคซเฅ‰เคฒเฅ‹ เค•เคฐเคฃเฅเคฏเคพเคšเฅ€ เคฐเคฟเค•เฅเคตเฅ‡เคธเฅเคŸ เค•เฅ‡เคฒเฅ€ เค†เคนเฅ‡", "account.share": "@{name} เคšเฅ‡ เคชเฅเคฐเฅ‹เคซเคพเค‡เคฒ เคถเฅ‡เค…เคฐ เค•เคฐเคพ", "account.show_reblogs": "{name}เคšเฅ‡ เคธเคฐเฅเคต เคฌเฅเคธเฅเคŸเฅเคธ เคฆเคพเค–เคตเคพ", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account.unblock": "@{name} เคฒเคพ เคฌเฅเคฒเฅ‰เค• เค•เคฐเคพ", "account.unblock_domain": "เค‰เค˜เคก เค•เคฐเคพ {domain}", "account.unblock_short": "เค…เคจเคฌเฅเคฒเฅ‰เค• เค•เคฐเคพ", diff --git a/app/javascript/mastodon/locales/ms.json b/app/javascript/mastodon/locales/ms.json index 8fe043c5dc..88c093bdee 100644 --- a/app/javascript/mastodon/locales/ms.json +++ b/app/javascript/mastodon/locales/ms.json @@ -35,9 +35,7 @@ "account.follow_back": "Ikut balik", "account.followers": "Pengikut", "account.followers.empty": "Belum ada yang mengikuti pengguna ini.", - "account.followers_counter": "{count, plural, one {{counter} Pengikut} other {{counter} Pengikut}}", "account.following": "Mengikuti", - "account.following_counter": "{count, plural, one {{counter} Diikuti} other {{counter} Diikuti}}", "account.follows.empty": "Pengguna ini belum mengikuti sesiapa.", "account.go_to_profile": "Pergi ke profil", "account.hide_reblogs": "Sembunyikan galakan daripada @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} has requested to follow you", "account.share": "Kongsi profil @{name}", "account.show_reblogs": "Tunjukkan galakan daripada @{name}", - "account.statuses_counter": "{count, plural, other {{counter} kiriman}}", "account.unblock": "Nyahsekat @{name}", "account.unblock_domain": "Nyahsekat domain {domain}", "account.unblock_short": "Nyahsekat", @@ -604,13 +601,10 @@ "server_banner.about_active_users": "Pengguna pelayan ini sepanjang 30 hari yang lalu (Pengguna Aktif Bulanan)", "server_banner.active_users": "pengguna aktif", "server_banner.administered_by": "Ditadbir oleh:", - "server_banner.introduction": "{domain} ialah sebahagian daripada rangkaian sosial terpencar dikuasakan oleh {mastodon}.", - "server_banner.learn_more": "Maklumat lanjut", "server_banner.server_stats": "Statistik pelayan:", "sign_in_banner.create_account": "Cipta akaun", "sign_in_banner.sign_in": "Daftar masuk", "sign_in_banner.sso_redirect": "Log masuk atau mendaftar", - "sign_in_banner.text": "Log masuk untuk mengikuti profil atau hashtag, kegemaran, kongsi dan balas pos. Anda juga boleh berinteraksi daripada akaun anda pada server lain.", "status.admin_account": "Buka antara muka penyederhanaan untuk @{name}", "status.admin_domain": "antara muka penyederhanaan", "status.admin_status": "Buka hantaran ini dalam antara muka penyederhanaan", diff --git a/app/javascript/mastodon/locales/my.json b/app/javascript/mastodon/locales/my.json index 23eaa8564b..46c8d18069 100644 --- a/app/javascript/mastodon/locales/my.json +++ b/app/javascript/mastodon/locales/my.json @@ -34,9 +34,7 @@ "account.follow": "แ€…แ€ฑแ€ฌแ€„แ€ทแ€บแ€€แ€ผแ€Šแ€ทแ€บ", "account.followers": "แ€…แ€ฑแ€ฌแ€„แ€ทแ€บแ€€แ€ผแ€Šแ€ทแ€บแ€žแ€ฐแ€™แ€ปแ€ฌแ€ธ", "account.followers.empty": "แ€คแ€žแ€ฐแ€€แ€ญแ€ฏ แ€…แ€ฑแ€ฌแ€„แ€ทแ€บแ€€แ€ผแ€Šแ€ทแ€บแ€žแ€ฐ แ€™แ€›แ€พแ€ญแ€žแ€ฑแ€ธแ€•แ€ซแ‹", - "account.followers_counter": "{count, plural, one {แ€…แ€ฑแ€ฌแ€„แ€บแ€ทแ€€แ€ผแ€Šแ€บแ€ทแ€žแ€ฐ {counter}} other {แ€…แ€ฑแ€ฌแ€„แ€บแ€ทแ€€แ€ผแ€Šแ€บแ€ทแ€žแ€ฐแ€™แ€ปแ€ฌแ€ธ {counter}}}", "account.following": "แ€…แ€ฑแ€ฌแ€„แ€ทแ€บแ€€แ€ผแ€Šแ€ทแ€บแ€”แ€ฑแ€žแ€Šแ€บ", - "account.following_counter": "{count, plural, one {แ€…แ€ฑแ€ฌแ€„แ€บแ€ทแ€€แ€ผแ€Šแ€บแ€ทแ€‘แ€ฌแ€ธแ€žแ€ฐ {counter}} other {แ€…แ€ฑแ€ฌแ€„แ€บแ€ทแ€€แ€ผแ€Šแ€บแ€ทแ€‘แ€ฌแ€ธแ€žแ€ฐแ€™แ€ปแ€ฌแ€ธ {counter}}}", "account.follows.empty": "แ€คแ€žแ€ฐแ€žแ€Šแ€บ แ€™แ€Šแ€บแ€žแ€ฐแ€ทแ€€แ€ญแ€ฏแ€™แ€ปแ€พ แ€…แ€ฑแ€ฌแ€„แ€ทแ€บแ€€แ€ผแ€Šแ€ทแ€บแ€แ€ผแ€„แ€บแ€ธ แ€™แ€›แ€พแ€ญแ€žแ€ฑแ€ธแ€•แ€ซแ‹", "account.go_to_profile": "แ€•แ€›แ€ญแ€ฏแ€–แ€ญแ€ฏแ€„แ€บแ€ธแ€žแ€ญแ€ฏแ€ท แ€žแ€ฝแ€ฌแ€ธแ€›แ€”แ€บ", "account.hide_reblogs": "@{name} แ แ€™แ€ปแ€พแ€แ€ฑแ€™แ€พแ€ฏแ€€แ€ญแ€ฏ แ€แ€พแ€€แ€บแ€‘แ€ฌแ€ธแ€›แ€”แ€บ", @@ -61,7 +59,6 @@ "account.requested_follow": "{name} แ€€ แ€žแ€„แ€ทแ€บแ€€แ€ญแ€ฏ แ€…แ€ฑแ€ฌแ€„แ€ทแ€บแ€€แ€ผแ€Šแ€ทแ€บแ€›แ€”แ€บ แ€แ€ฑแ€ฌแ€„แ€บแ€ธแ€†แ€ญแ€ฏแ€‘แ€ฌแ€ธแ€žแ€Šแ€บ", "account.share": "{name}แแ€•แ€›แ€ญแ€ฏแ€–แ€ญแ€ฏแ€„แ€บแ€€แ€ญแ€ฏแ€™แ€ปแ€พแ€แ€ฑแ€•แ€ซ", "account.show_reblogs": "@{name} แ€™แ€พ แ€™แ€ปแ€พแ€แ€ฑแ€™แ€พแ€ฏแ€™แ€ปแ€ฌแ€ธแ€€แ€ญแ€ฏ แ€•แ€ผแ€•แ€ซ\n", - "account.statuses_counter": "{count, plural, one {{counter} แ€•แ€ญแ€ฏแ€…แ€บแ€ทแ€™แ€ปแ€ฌแ€ธ} other {{counter} แ€•แ€ญแ€ฏแ€…แ€บแ€ทแ€™แ€ปแ€ฌแ€ธ}}", "account.unblock": "{name} แ€€แ€ญแ€ฏ แ€˜แ€œแ€ฑแ€ฌแ€ทแ€–แ€ผแ€ฏแ€แ€บแ€™แ€Šแ€บ", "account.unblock_domain": " {domain} แ€’แ€ญแ€ฏแ€™แ€ญแ€”แ€บแ€ธแ€€แ€ญแ€ฏแ€•แ€ผแ€”แ€บแ€–แ€ฝแ€„แ€บแ€ทแ€™แ€Šแ€บ", "account.unblock_short": "แ€˜แ€œแ€ฑแ€ฌแ€ทแ€–แ€ผแ€ฏแ€แ€บแ€›แ€”แ€บ", @@ -582,13 +579,10 @@ "server_banner.about_active_users": "แ€•แ€ผแ€ฎแ€ธแ€แ€ฒแ€ทแ€žแ€Šแ€ทแ€บ แ€›แ€€แ€บแ€•แ€ฑแ€ซแ€„แ€บแ€ธ แƒแ€ แ€กแ€แ€ฝแ€„แ€บแ€ธ แ€คแ€†แ€ฌแ€—แ€ฌแ€€แ€ญแ€ฏ แ€กแ€žแ€ฏแ€ถแ€ธแ€•แ€ผแ€ฏแ€žแ€ฐแ€™แ€ปแ€ฌแ€ธ (แ€œแ€กแ€œแ€ญแ€ฏแ€€แ€บ แ€œแ€€แ€บแ€›แ€พแ€ญแ€กแ€žแ€ฏแ€ถแ€ธแ€•แ€ผแ€ฏแ€žแ€ฐแ€™แ€ปแ€ฌแ€ธ)", "server_banner.active_users": "แ€œแ€€แ€บแ€›แ€พแ€ญแ€กแ€žแ€ฏแ€ถแ€ธแ€•แ€ผแ€ฏแ€žแ€ฐแ€™แ€ปแ€ฌแ€ธ", "server_banner.administered_by": "แ€™แ€พ แ€…แ€ฎแ€™แ€ถแ€แ€”แ€ทแ€บแ€แ€ฝแ€ฒแ€žแ€Šแ€บ -", - "server_banner.introduction": "{domain} แ€žแ€Šแ€บ {mastodon} แ€™แ€พ แ€•แ€ถแ€ทแ€•แ€ญแ€ฏแ€ธแ€•แ€ฑแ€ธแ€‘แ€ฌแ€ธแ€žแ€ฑแ€ฌ แ€—แ€Ÿแ€ญแ€ฏแ€แ€ปแ€ฏแ€•แ€บแ€€แ€ญแ€ฏแ€„แ€บแ€™แ€พแ€ฏแ€™แ€›แ€พแ€ญแ€žแ€Šแ€ทแ€บ แ€œแ€ฐแ€™แ€พแ€ฏแ€€แ€ฝแ€”แ€บแ€›แ€€แ€บแ€แ€…แ€บแ€แ€ฏแ€–แ€ผแ€…แ€บแ€žแ€Šแ€บแ‹", - "server_banner.learn_more": "แ€•แ€ญแ€ฏแ€™แ€ญแ€ฏแ€žแ€ญแ€›แ€พแ€ญแ€›แ€”แ€บ", "server_banner.server_stats": "แ€†แ€ฌแ€—แ€ฌแ€กแ€ฌแ€ธ แ€œแ€€แ€บแ€›แ€พแ€ญแ€กแ€žแ€ฏแ€ถแ€ธแ€•แ€ผแ€ฏแ€žแ€ฐแ€™แ€ปแ€ฌแ€ธ -", "sign_in_banner.create_account": "แ€กแ€€แ€ฑแ€ฌแ€„แ€บแ€ทแ€–แ€”แ€บแ€แ€ฎแ€ธแ€™แ€Šแ€บ", "sign_in_banner.sign_in": "แ€กแ€€แ€ฑแ€ฌแ€„แ€ทแ€บแ€แ€„แ€บแ€™แ€Šแ€บ", "sign_in_banner.sso_redirect": "แ€กแ€€แ€ฑแ€ฌแ€„แ€ทแ€บแ€แ€„แ€บแ€•แ€ซ แ€žแ€ญแ€ฏแ€ทแ€™แ€Ÿแ€ฏแ€แ€บ แ€™แ€พแ€แ€บแ€•แ€ฏแ€ถแ€แ€„แ€บแ€•แ€ซ", - "sign_in_banner.text": "แ€•แ€›แ€ญแ€ฏแ€–แ€ญแ€ฏแ€„แ€บแ€™แ€ปแ€ฌแ€ธ แ€žแ€ญแ€ฏแ€ทแ€™แ€Ÿแ€ฏแ€แ€บ hashtag แ€™แ€ปแ€ฌแ€ธแŠ favoriteแŠ แ€•แ€ญแ€ฏแ€ทแ€…แ€บแ€™แ€ปแ€พแ€แ€ฑแ€™แ€พแ€ฏแ€™แ€ปแ€ฌแ€ธแ€”แ€พแ€„แ€บแ€ท แ€•แ€ผแ€”แ€บแ€€แ€ผแ€ฌแ€ธแ€…แ€ฌแ€™แ€ปแ€ฌแ€ธแ€กแ€žแ€ฏแ€ถแ€ธแ€•แ€ผแ€ฏแ€›แ€”แ€บแ€กแ€แ€ฝแ€€แ€บ แ€กแ€€แ€ฑแ€ฌแ€„แ€ทแ€บแ€แ€„แ€บแ€•แ€ซแ‹ แ€กแ€แ€ผแ€ฌแ€ธแ€†แ€ฌแ€—แ€ฌแ€•แ€ฑแ€ซแ€บแ€›แ€พแ€ญ แ€žแ€„แ€ทแ€บแ€กแ€€แ€ฑแ€ฌแ€„แ€ทแ€บแ€™แ€พแ€œแ€Šแ€บแ€ธ แ€กแ€•แ€ผแ€”แ€บแ€กแ€œแ€พแ€”แ€บแ€–แ€œแ€พแ€šแ€บแ€”แ€ญแ€ฏแ€„แ€บแ€•แ€ซแ€žแ€Šแ€บแ‹", "status.admin_account": "@{name} แ€กแ€แ€ฝแ€€แ€บ แ€…แ€ญแ€…แ€…แ€บแ€แ€ผแ€„แ€บแ€ธแ€€แ€ผแ€ฌแ€ธแ€แ€ถแ€”แ€šแ€บแ€€แ€ญแ€ฏ แ€–แ€ฝแ€„แ€ทแ€บแ€•แ€ซ", "status.admin_domain": "{domain} แ€กแ€แ€ฝแ€€แ€บ แ€…แ€ญแ€…แ€…แ€บแ€แ€ผแ€„แ€บแ€ธแ€€แ€ผแ€ฌแ€ธแ€แ€ถแ€”แ€šแ€บแ€€แ€ญแ€ฏ แ€–แ€ฝแ€„แ€ทแ€บแ€•แ€ซ", "status.admin_status": "Open this status in the moderation interface", diff --git a/app/javascript/mastodon/locales/ne.json b/app/javascript/mastodon/locales/ne.json index 500261a34b..ca23a1f781 100644 --- a/app/javascript/mastodon/locales/ne.json +++ b/app/javascript/mastodon/locales/ne.json @@ -39,7 +39,6 @@ "account.requested_follow": "{name} เคฒเฅ‡ เคคเคชเคพเคˆเค‚เคฒเคพเคˆ เคซเคฒเฅ‹ เค—เคฐเฅเคจ เค…เคจเฅเคฐเฅ‹เคง เค—เคฐเฅเคจเฅเคญเคเค•เฅ‹ เค›", "account.share": "@{name} เค•เฅ‹ เคชเฅเคฐเฅ‹เคซเคพเค‡เคฒ เคธเฅ‡เคฏเคฐ เค—เคฐเฅเคจเฅเคนเฅ‹เคธเฅ", "account.show_reblogs": "@{name} เค•เฅ‹ เคฌเฅ‚เคธเฅเคŸเคนเคฐเฅ‚ เคฆเฅ‡เค–เคพเค‰เคจเฅเคนเฅ‹เคธเฅ", - "account.statuses_counter": "{count, plural, one {{counter} เคชเฅ‹เคธเฅเคŸ} other {{counter} เคชเฅ‹เคธเฅเคŸเคนเคฐเฅ‚}}", "account.unblock": "@{name} เคฒเคพเคˆ เค…เคจเคฌเฅเคฒเค• เค—เคฐเฅเคจเฅเคนเฅ‹เคธเฅ", "account.unblock_domain": "{domain} เคกเฅ‹เคฎเฅ‡เคจเคฒเคพเคˆ เค…เคจเคฌเฅเคฒเค• เค—เคฐเฅเคจเฅเคนเฅ‹เคธเฅ", "account.unblock_short": "เค…เคจเคฌเฅเคฒเค• เค—เคฐเฅเคจเฅเคนเฅ‹เคธเฅ", diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json index 46d1f97835..8246d8dfd2 100644 --- a/app/javascript/mastodon/locales/nl.json +++ b/app/javascript/mastodon/locales/nl.json @@ -308,13 +308,17 @@ "follow_requests.unlocked_explanation": "Ook al is jouw account niet besloten, de medewerkers van {domain} denken dat jij misschien de volgende volgverzoeken handmatig wil controleren.", "follow_suggestions.curated_suggestion": "Speciaal geselecteerd", "follow_suggestions.dismiss": "Niet meer weergeven", - "follow_suggestions.hints.featured": "Deze gebruiker is geselecteerd door het team van {domain}.", + "follow_suggestions.featured_longer": "Handmatig geselecteerd door het team van {domain}", + "follow_suggestions.friends_of_friends_longer": "Populair onder mensen die je volgt", + "follow_suggestions.hints.featured": "Deze gebruiker is handmatig geselecteerd door het team van {domain}.", "follow_suggestions.hints.friends_of_friends": "Deze gebruiker is populair onder de mensen die jij volgt.", "follow_suggestions.hints.most_followed": "Deze gebruiker is een van de meest gevolgde gebruikers op {domain}.", "follow_suggestions.hints.most_interactions": "Deze gebruiker is de laatste tijd erg populair op {domain}.", "follow_suggestions.hints.similar_to_recently_followed": "Deze gebruiker is vergelijkbaar met gebruikers die je recentelijk hebt gevolgd.", "follow_suggestions.personalized_suggestion": "Gepersonaliseerde aanbeveling", "follow_suggestions.popular_suggestion": "Populaire aanbeveling", + "follow_suggestions.popular_suggestion_longer": "Populair op {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Vergelijkbaar met accounts die je recentelijk bent gaan volgen", "follow_suggestions.view_all": "Alles weergeven", "follow_suggestions.who_to_follow": "Wie te volgen", "followed_tags": "Gevolgde hashtags", @@ -410,6 +414,8 @@ "limited_account_hint.action": "Alsnog het profiel tonen", "limited_account_hint.title": "Dit profiel is door de moderatoren van {domain} verborgen.", "link_preview.author": "Door {name}", + "link_preview.more_from_author": "Meer van {name}", + "link_preview.shares": "{count, plural, one {{counter} bericht} other {{counter} berichten}}", "lists.account.add": "Aan lijst toevoegen", "lists.account.remove": "Uit lijst verwijderen", "lists.delete": "Lijst verwijderen", @@ -469,6 +475,15 @@ "notification.follow": "{name} volgt jou nu", "notification.follow_request": "{name} wil jou graag volgen", "notification.mention": "{name} vermeldde jou", + "notification.moderation-warning.learn_more": "Meer informatie", + "notification.moderation_warning": "Je hebt een moderatie-waarschuwing ontvangen", + "notification.moderation_warning.action_delete_statuses": "Sommige van je berichten zijn verwijderd.", + "notification.moderation_warning.action_disable": "Je account is uitgeschakeld.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Sommige van je berichten zijn gemarkeerd als gevoelig.", + "notification.moderation_warning.action_none": "Jouw account heeft een moderatie-waarschuwing ontvangen.", + "notification.moderation_warning.action_sensitive": "Je berichten worden vanaf nu als gevoelig gemarkeerd.", + "notification.moderation_warning.action_silence": "Jouw account is beperkt.", + "notification.moderation_warning.action_suspend": "Jouw account is opgeschort.", "notification.own_poll": "Jouw peiling is beรซindigd", "notification.poll": "Een peiling waaraan jij hebt meegedaan is beรซindigd", "notification.reblog": "{name} boostte jouw bericht", @@ -681,13 +696,13 @@ "server_banner.about_active_users": "Aantal gebruikers tijdens de afgelopen 30 dagen (MAU)", "server_banner.active_users": "actieve gebruikers", "server_banner.administered_by": "Beheerd door:", - "server_banner.introduction": "{domain} is onderdeel van het decentrale sociale netwerk {mastodon}.", - "server_banner.learn_more": "Meer leren", + "server_banner.is_one_of_many": "{domain} is een van de vele onafhankelijke Mastodon-servers die je kunt gebruiken om deel te nemen aan de fediverse.", "server_banner.server_stats": "Serverstats:", "sign_in_banner.create_account": "Registreren", + "sign_in_banner.follow_anyone": "Volg iedereen in de fediverse en zie het allemaal in chronologische volgorde. Geen algoritmes, advertenties of clickbaits.", + "sign_in_banner.mastodon_is": "Mastodon is de beste manier om wat er gebeurt bij te houden.", "sign_in_banner.sign_in": "Inloggen", "sign_in_banner.sso_redirect": "Inloggen of Registreren", - "sign_in_banner.text": "Wanneer je een account op deze server hebt, kun je inloggen om mensen of hashtags te volgen, op berichten te reageren of om deze te delen. Wanneer je een account op een andere server hebt, kun je daar inloggen en daar ook interactie met mensen op deze server hebben.", "status.admin_account": "Moderatie-omgeving van @{name} openen", "status.admin_domain": "Moderatie-omgeving van {domain} openen", "status.admin_status": "Dit bericht in de moderatie-omgeving tonen", diff --git a/app/javascript/mastodon/locales/nn.json b/app/javascript/mastodon/locales/nn.json index 2cfb0d1247..0fb0edf0a0 100644 --- a/app/javascript/mastodon/locales/nn.json +++ b/app/javascript/mastodon/locales/nn.json @@ -35,9 +35,9 @@ "account.follow_back": "Fylg tilbake", "account.followers": "Fylgjarar", "account.followers.empty": "Ingen fylgjer denne brukaren enno.", - "account.followers_counter": "{count, plural, one {{counter} fylgjar} other {{counter} fylgjarar}}", + "account.followers_counter": "{count, plural, one {{counter} fรธlgjar} other {{counter} fรธlgjarar}}", "account.following": "Fylgjer", - "account.following_counter": "{count, plural, one {Fylgjer {counter}} other {Fylgjer {counter}}}", + "account.following_counter": "{count, plural, one {{counter} fรธlgjer} other {{counter} fรธlgjer}}", "account.follows.empty": "Denne brukaren fylgjer ikkje nokon enno.", "account.go_to_profile": "Gรฅ til profil", "account.hide_reblogs": "Gรธym framhevingar frรฅ @{name}", @@ -63,7 +63,7 @@ "account.requested_follow": "{name} har bedt om รฅ fรฅ fylgja deg", "account.share": "Del @{name} sin profil", "account.show_reblogs": "Vis framhevingar frรฅ @{name}", - "account.statuses_counter": "{count, plural, one {{counter} tut} other {{counter} tut}}", + "account.statuses_counter": "{count, plural, one {{counter} innlegg} other {{counter} innlegg}}", "account.unblock": "Stopp blokkering av @{name}", "account.unblock_domain": "Stopp blokkering av domenet {domain}", "account.unblock_short": "Stopp blokkering", @@ -297,6 +297,7 @@ "filter_modal.select_filter.subtitle": "Bruk ein eksisterande kategori eller opprett ein ny", "filter_modal.select_filter.title": "Filtrer dette innlegget", "filter_modal.title.status": "Filtrer eit innlegg", + "filtered_notifications_banner.mentions": "{count, plural, one {omtale} other {omtaler}}", "filtered_notifications_banner.pending_requests": "Varsel frรฅ {count, plural, =0 {ingen} one {ein person} other {# folk}} du kanskje kjenner", "filtered_notifications_banner.title": "Filtrerte varslingar", "firehose.all": "Alle", @@ -307,6 +308,8 @@ "follow_requests.unlocked_explanation": "Sjรธlv om kontoen din ikkje er lรฅst tenkte dei som driv {domain} at du kanskje ville gรฅ gjennom fรธrespurnadar frรฅ desse kontoane manuelt.", "follow_suggestions.curated_suggestion": "Utvalt av staben", "follow_suggestions.dismiss": "Ikkje vis igjen", + "follow_suggestions.featured_longer": "Hanplukka av gjengen pรฅ {domain}", + "follow_suggestions.friends_of_friends_longer": "Populรฆrt hjรฅ dei du fylgjer", "follow_suggestions.hints.featured": "Denne profilen er handplukka av folka pรฅ {domain}.", "follow_suggestions.hints.friends_of_friends": "Denne profilen er populรฆr hjรฅ dei du fylgjer.", "follow_suggestions.hints.most_followed": "Mange pรฅ {domain} fylgjer denne profilen.", @@ -314,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Denne profilen liknar pรฅ dei andre profilane du har fylgt i det siste.", "follow_suggestions.personalized_suggestion": "Personleg forslag", "follow_suggestions.popular_suggestion": "Populรฆrt forslag", + "follow_suggestions.popular_suggestion_longer": "Populรฆrt pรฅ {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Liknar pรฅ profilar du har fylgt i det siste", "follow_suggestions.view_all": "Vis alle", "follow_suggestions.who_to_follow": "Kven du kan fylgja", "followed_tags": "Fylgde emneknaggar", @@ -409,6 +414,8 @@ "limited_account_hint.action": "Vis profilen likevel", "limited_account_hint.title": "Denne profilen er skjult av moderatorane pรฅ {domain}.", "link_preview.author": "Av {name}", + "link_preview.more_from_author": "Meir frรฅ {name}", + "link_preview.shares": "{count, plural,one {{counter} innlegg} other {{counter} innlegg}}", "lists.account.add": "Legg til i liste", "lists.account.remove": "Fjern frรฅ liste", "lists.delete": "Slett liste", @@ -468,6 +475,15 @@ "notification.follow": "{name} fylgde deg", "notification.follow_request": "{name} har bedt om รฅ fylgja deg", "notification.mention": "{name} nemnde deg", + "notification.moderation-warning.learn_more": "Lรฆr meir", + "notification.moderation_warning": "Du har mottatt ei moderasjonsรฅtvaring", + "notification.moderation_warning.action_delete_statuses": "Nokre av innlegga dine har blitt fjerna.", + "notification.moderation_warning.action_disable": "Kontoen din har blitt deaktivert.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Nokre av innlegga dine har blitt markert som sensitive.", + "notification.moderation_warning.action_none": "Kontoen din har mottatt ei moderasjonsรฅtvaring.", + "notification.moderation_warning.action_sensitive": "Innlegga dine vil bli markerte som sensitive frรฅ no av.", + "notification.moderation_warning.action_silence": "Kontoen din har blitt avgrensa.", + "notification.moderation_warning.action_suspend": "Kontoen din har blitt suspendert.", "notification.own_poll": "Rundspรธrjinga di er ferdig", "notification.poll": "Ei rundspรธrjing du har rรธysta i er ferdig", "notification.reblog": "{name} framheva innlegget ditt", @@ -659,7 +675,7 @@ "search.quick_action.account_search": "Profiler som samsvarer med {x}", "search.quick_action.go_to_account": "Gรฅ til profil {x}", "search.quick_action.go_to_hashtag": "Gรฅ til emneknagg {x}", - "search.quick_action.open_url": "ร…pne URL i Mastodon", + "search.quick_action.open_url": "Opne adressa i Mastodon", "search.quick_action.status_search": "Innlegg som samsvarer med {x}", "search.search_or_paste": "Sรธk eller lim inn URL", "search_popout.full_text_search_disabled_message": "Ikkje tilgjengeleg pรฅ {domain}.", @@ -680,13 +696,13 @@ "server_banner.about_active_users": "Personar som har brukt denne tenaren dei siste 30 dagane (Mรฅnadlege Aktive Brukarar)", "server_banner.active_users": "aktive brukarar", "server_banner.administered_by": "Administrert av:", - "server_banner.introduction": "{domain} er del av det desentraliserte sosiale nettverket drive av {mastodon}.", - "server_banner.learn_more": "Lรฆr meir", + "server_banner.is_one_of_many": "{domain} er ein av dei mange uavhengige Mastodon-serverane du kan bruke til รฅ delta i Fรธdiverset.", "server_banner.server_stats": "Tenarstatistikk:", "sign_in_banner.create_account": "Opprett konto", + "sign_in_banner.follow_anyone": "Fรธlg kven som helst pรฅ tvers av Fรธdiverset og sjรฅ alt i kronologisk rekkjefรธlgje. Ingen algoritmar, reklamar eller clickbait i sikte.", + "sign_in_banner.mastodon_is": "Mastodon er den beste mรฅten รฅ fรธlgje med pรฅ det som skjer.", "sign_in_banner.sign_in": "Logg inn", "sign_in_banner.sso_redirect": "Logg inn eller registrer deg", - "sign_in_banner.text": "Logg inn for รฅ fylgja profilar eller emneknaggar, og for รฅ lika, dela og svara pรฅ innlegg. Du kan รฒg samhandla med aktivitet pรฅ denne tenaren frรฅ kontoar pรฅ andre tenarar.", "status.admin_account": "Opne moderasjonsgrensesnitt for @{name}", "status.admin_domain": "Opna moderatorgrensesnittet for {domain}", "status.admin_status": "Opne denne statusen i moderasjonsgrensesnittet", diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json index 7f93ff0465..2bda373404 100644 --- a/app/javascript/mastodon/locales/no.json +++ b/app/javascript/mastodon/locales/no.json @@ -35,9 +35,7 @@ "account.follow_back": "Fรธlg tilbake", "account.followers": "Fรธlgere", "account.followers.empty": "Ingen fรธlger denne brukeren ennรฅ.", - "account.followers_counter": "{count, plural, one {{counter} fรธlger} other {{counter} fรธlgere}}", "account.following": "Fรธlger", - "account.following_counter": "{count, plural, one {{counter} som fรธlges} other {{counter} som fรธlges}}", "account.follows.empty": "Denne brukeren fรธlger ikke noen enda.", "account.go_to_profile": "Gรฅ til profil", "account.hide_reblogs": "Skjul fremhevinger fra @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} har bedt om รฅ fรฅ fรธlge deg", "account.share": "Del @{name} sin profil", "account.show_reblogs": "Vis fremhevinger fra @{name}", - "account.statuses_counter": "{count, plural, one {{counter} innlegg} other {{counter} innlegg}}", "account.unblock": "Opphev blokkering av @{name}", "account.unblock_domain": "Opphev blokkering av {domain}", "account.unblock_short": "Opphev blokkering", @@ -606,13 +603,10 @@ "server_banner.about_active_users": "Personer som har brukt denne serveren i lรธpet av de siste 30 dagene (aktive brukere mรฅnedlig)", "server_banner.active_users": "aktive brukere", "server_banner.administered_by": "Administrert av:", - "server_banner.introduction": "{domain} er en del av det desentraliserte sosiale nettverket drevet av {mastodon}.", - "server_banner.learn_more": "Finn ut mer", "server_banner.server_stats": "Serverstatistikk:", "sign_in_banner.create_account": "Opprett konto", "sign_in_banner.sign_in": "Logg inn", "sign_in_banner.sso_redirect": "Logg inn eller registrer deg", - "sign_in_banner.text": "Logg inn for รฅ fรธlge profiler eller emneknagger, favorittmarkere, dele og svare pรฅ innlegg. Du kan ogsรฅ samhandle fra din konto pรฅ en annen server.", "status.admin_account": "ร…pne moderatorgrensesnittet for @{name}", "status.admin_domain": "ร…pne moderatorgrensesnittet for {domain}", "status.admin_status": "ร…pne denne statusen i moderatorgrensesnittet", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index 3c32fed0ef..d977eed4af 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -32,9 +32,7 @@ "account.follow_back": "Sรจgre en retorn", "account.followers": "Seguidors", "account.followers.empty": "Degun sรจc pas aqueste utilizaire pel moment.", - "account.followers_counter": "{count, plural, one {{counter} Seguidor} other {{counter} Seguidors}}", "account.following": "Abonat", - "account.following_counter": "{count, plural, one {{counter} Abonaments} other {{counter} Abonaments}}", "account.follows.empty": "Aqueste utilizaire sรจc pas degun pel moment.", "account.go_to_profile": "Anar al perfil", "account.hide_reblogs": "Rescondre los partatges de @{name}", @@ -60,7 +58,6 @@ "account.requested_follow": "{name} a demandat a vos sรจgre", "account.share": "Partejar lo perfil a @{name}", "account.show_reblogs": "Mostrar los partatges de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Tut} other {{counter} Tuts}}", "account.unblock": "Desblocar @{name}", "account.unblock_domain": "Desblocar {domain}", "account.unblock_short": "Desblocat", @@ -499,8 +496,6 @@ "search_results.title": "Recรจrcaโ€ฏ: {q}", "server_banner.active_users": "utilizaires actius", "server_banner.administered_by": "Administrat perโ€ฏ:", - "server_banner.introduction": "{domain} fa part del malhum social descentralizat propulsat per {mastodon}.", - "server_banner.learn_more": "Ne saber mai", "server_banner.server_stats": "Estatisticas del servidorโ€ฏ:", "sign_in_banner.create_account": "Crear un compte", "sign_in_banner.sign_in": "Se connectar", diff --git a/app/javascript/mastodon/locales/pa.json b/app/javascript/mastodon/locales/pa.json index c693c24721..3828ff887e 100644 --- a/app/javascript/mastodon/locales/pa.json +++ b/app/javascript/mastodon/locales/pa.json @@ -25,9 +25,7 @@ "account.follow_back": "เจตเจพเจชเจธ เจซเจพเจฒเจผเฉ‹ เจ•เจฐเฉ‹", "account.followers": "เจซเจผเจพเจฒเฉ‹เจ…เจฐ", "account.followers.empty": "เจ‡เจธ เจตเจฐเจคเฉ‹เจ‚เจ•เจพเจฐ เจจเฉ‚เฉฐ เจนเจพเจฒเฉ‡ เจ•เฉ‹เจˆ เจซเจผเจพเจฒเฉ‹ เจจเจนเฉ€เจ‚ เจ•เจฐเจฆเจพ เจนเฉˆเฅค", - "account.followers_counter": "{count, plural, one {{counter} เฉžเจพเจฒเฉ‹เจ…เจฐ} other {{counter} เฉžเจพเจฒเฉ‹เจ…เจฐ}}", "account.following": "เจซเจผเจพเจฒเฉ‹ เจ•เฉ€เจคเจพ", - "account.following_counter": "{count, plural, one {{counter} เจจเฉ‚เฉฐ เฉžเจพเจฒเฉ‹} other {{counter} เจจเฉ‚เฉฐ เฉžเจพเจฒเฉ‹}}", "account.follows.empty": "เจ‡เจน เจตเจฐเจคเฉ‹เจ‚เจ•เจพเจฐ เจนเจพเจฒเฉ‡ เจ•เจฟเจธเฉ‡ เจจเฉ‚เฉฐ เจซเจผเจพเจฒเฉ‹ เจจเจนเฉ€เจ‚ เจ•เจฐเจฆเจพ เจนเฉˆเฅค", "account.go_to_profile": "เจชเจฐเฉ‹เจซเจพเจ‡เจฒ เจ‰เฉฑเจคเฉ‡ เจœเจพเจ“", "account.media": "เจฎเฉ€เจกเฉ€เจ†", @@ -41,7 +39,6 @@ "account.requested": "เจฎเจจเฉ›เฉ‚เจฐเฉ€ เจ•เฉ€เจคเฉ€ เจœเจพ เจฐเจนเฉ€ เจนเฉˆเฅค เจซเจผเจพเจฒเฉ‹ เจฌเฉ‡เจจเจคเฉ€เจ†เจ‚ เจจเฉ‚เฉฐ เจฐเฉฑเจฆ เจ•เจฐเจจ เจฒเจˆ เจ•เจฒเจฟเฉฑเจ• เจ•เจฐเฉ‹", "account.requested_follow": "{name} เจจเฉ‡ เจคเฉเจนเจพเจจเฉ‚เฉฐ เจซเจผเจพเจฒเฉ‹ เจ•เจฐเจจ เจฆเฉ€ เจฌเฉ‡เจจเจคเฉ€ เจ•เฉ€เจคเฉ€ เจนเฉˆ", "account.share": "{name} เจฆเจพ เจชเจฐเฉ‹เฉžเจพเจ‡เจฒ เจธเจพเจ‚เจเจพ เจ•เจฐเฉ‹", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account.unblock": "@{name} เจคเฉ‹เจ‚ เจชเจพเจฌเฉฐเจฆเฉ€ เจนเจŸเจพเจ“", "account.unblock_domain": "{domain} เจกเฉ‹เจฎเฉ‡เจจ เจคเฉ‹เจ‚ เจชเจพเจฌเฉฐเจฆเฉ€ เจนเจŸเจพเจ“", "account.unblock_short": "เจชเจพเจฌเฉฐเจฆเฉ€ เจนเจŸเจพเจ“", @@ -311,7 +308,6 @@ "search_results.see_all": "เจธเจญ เจตเฉ‡เจ–เฉ‹", "search_results.statuses": "เจชเฉ‹เจธเจŸเจพเจ‚", "search_results.title": "{q} เจฒเจˆ เจ–เฉ‹เจœ", - "server_banner.learn_more": "เจนเฉ‹เจฐ เจœเจพเจฃเฉ‹", "sign_in_banner.create_account": "เจ–เจพเจคเจพ เจฌเจฃเจพเจ“", "sign_in_banner.sign_in": "เจฒเจพเจ—เจ‡เจจ", "sign_in_banner.sso_redirect": "เจฒเจพเจ—เจ‡เจจ เจœเจพเจ‚ เจฐเจœเจฟเจธเจŸเจฐ เจ•เจฐเฉ‹", diff --git a/app/javascript/mastodon/locales/pl.json b/app/javascript/mastodon/locales/pl.json index 72f1ba58f5..ddfe1d4fbc 100644 --- a/app/javascript/mastodon/locales/pl.json +++ b/app/javascript/mastodon/locales/pl.json @@ -308,6 +308,8 @@ "follow_requests.unlocked_explanation": "Mimo ลผe Twoje konto nie jest zablokowane, zespรณล‚ {domain} uznaล‚ ลผe moลผesz chcieฤ‡ rฤ™cznie przejrzeฤ‡ proล›by o moลผliwoล›ฤ‡ obserwacji.", "follow_suggestions.curated_suggestion": "Wybrane przez personel", "follow_suggestions.dismiss": "Nie pokazuj ponownie", + "follow_suggestions.featured_longer": "Wybrane przez zespรณล‚ {domain}", + "follow_suggestions.friends_of_friends_longer": "Popularni wล›rรณd ludzi ktรณrych obserwujesz", "follow_suggestions.hints.featured": "Ten profil zostaล‚ wybrany przez zespรณล‚ {domain}.", "follow_suggestions.hints.friends_of_friends": "Ten profil jest popularny w gronie uลผytkownikรณw, ktรณrych obserwujesz.", "follow_suggestions.hints.most_followed": "Ten profil jest jednym z najczฤ™ล›ciej obserwowanych na {domain}.", @@ -315,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Ten profil jest podobny do profili ostatnio przez ciebie zaobserwowanych.", "follow_suggestions.personalized_suggestion": "Sugestia spersonalizowana", "follow_suggestions.popular_suggestion": "Sugestia popularna", + "follow_suggestions.popular_suggestion_longer": "Popularni na {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Podobne do ostatnio zaobserwowanych przez ciebie profilรณw", "follow_suggestions.view_all": "Pokaลผ wszystkie", "follow_suggestions.who_to_follow": "Kogo obserwowaฤ‡", "followed_tags": "Obserwowane hasztagi", @@ -410,6 +414,8 @@ "limited_account_hint.action": "Pokaลผ profil mimo to", "limited_account_hint.title": "Ten profil zostaล‚ ukryty przez moderatorรณw {domain}.", "link_preview.author": "{name}", + "link_preview.more_from_author": "Wiฤ™cej od {name}", + "link_preview.shares": "{count, plural, one {{counter} wpis} few {{counter} wpisy} many {{counter} wpisรณw} other {{counter} wpisรณw}}", "lists.account.add": "Dodaj do listy", "lists.account.remove": "Usunฤ…ฤ‡ z listy", "lists.delete": "Usuล„ listฤ™", @@ -469,6 +475,15 @@ "notification.follow": "{name} obserwuje Ciฤ™", "notification.follow_request": "{name} chce ciฤ™ zaobserwowaฤ‡", "notification.mention": "Wspomniaล‚o o Tobie przez {name}", + "notification.moderation-warning.learn_more": "Dowiedz siฤ™ wiฤ™cej", + "notification.moderation_warning": "Otrzymaล‚eล›/-ล‚aล› ostrzeลผenie moderacyjne", + "notification.moderation_warning.action_delete_statuses": "Niektรณre twoje wpisy zostaล‚y usuniฤ™te.", + "notification.moderation_warning.action_disable": "Twoje konto zostaล‚o wyล‚ฤ…czone.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Niektรณre twoje wpisy zostaล‚y oznaczone jako wraลผliwe.", + "notification.moderation_warning.action_none": "Twoje konto otrzymaล‚o ostrzeลผenie moderacyjne.", + "notification.moderation_warning.action_sensitive": "Twoje wpisy bฤ™dฤ… od teraz oznaczane jako wraลผliwe.", + "notification.moderation_warning.action_silence": "Twoje konto zostaล‚o ograniczone.", + "notification.moderation_warning.action_suspend": "Twoje konto zostaล‚o zawieszone.", "notification.own_poll": "Twoje gล‚osowanie zakoล„czyล‚o siฤ™", "notification.poll": "Gล‚osowanie w ktรณrym braล‚eล›(-aล›) udziaล‚ zakoล„czyล‚o siฤ™", "notification.reblog": "Twรณj post zostaล‚ podbity przez {name}", @@ -680,13 +695,13 @@ "server_banner.about_active_users": "Osoby korzystajฤ…ce z tego serwera w ciฤ…gu ostatnich 30 dni (Miesiฤ™cznie aktywni uลผytkownicy)", "server_banner.active_users": "aktywni uลผytkownicy", "server_banner.administered_by": "Zarzฤ…dzana przez:", - "server_banner.introduction": "{domain} jest czฤ™ล›ciฤ… zdecentralizowanej sieci spoล‚ecznoล›ciowej wspieranej przez {mastodon}.", - "server_banner.learn_more": "Dowiedz siฤ™ wiฤ™cej", + "server_banner.is_one_of_many": "{domain} jest jednฤ… z wielu niezaleลผnych serwerรณw Mastodon, ktรณrych moลผesz uลผyฤ‡ by uczestniczyฤ‡ w fediwersum.", "server_banner.server_stats": "Statystyki serwera:", "sign_in_banner.create_account": "Zaล‚รณลผ konto", + "sign_in_banner.follow_anyone": "Obserwuj kogokolwiek z fediwersum w kolejnoล›ci chronologicznej. Bez algorytmรณw ani reklam.", + "sign_in_banner.mastodon_is": "Mastodon to najlepszy sposรณb nadฤ…ลผania za bieลผฤ…cymi zdarzeniami.", "sign_in_banner.sign_in": "Zaloguj siฤ™", "sign_in_banner.sso_redirect": "Zaloguj/zarejestruj siฤ™", - "sign_in_banner.text": "Zaloguj siฤ™, aby obserwowaฤ‡ profile lub hashtagi, polubiฤ‡, udostฤ™pniฤ‡ oraz odpowiedzieฤ‡ na posty. Moลผesz rรณwnieลผ wejล›ฤ‡ w interakcjฤ™ z konta na innym serwerze.", "status.admin_account": "Otwรณrz interfejs moderacyjny dla @{name}", "status.admin_domain": "Otwรณrz interfejs moderacyjny dla {domain}", "status.admin_status": "Otwรณrz ten wpis w interfejsie moderacyjnym", diff --git a/app/javascript/mastodon/locales/pt-BR.json b/app/javascript/mastodon/locales/pt-BR.json index 1bb73db238..34d0ba36e6 100644 --- a/app/javascript/mastodon/locales/pt-BR.json +++ b/app/javascript/mastodon/locales/pt-BR.json @@ -35,9 +35,7 @@ "account.follow_back": "Seguir de volta", "account.followers": "Seguidores", "account.followers.empty": "Nada aqui.", - "account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}", "account.following": "Seguindo", - "account.following_counter": "{count, plural, one {segue {counter}} other {segue {counter}}}", "account.follows.empty": "Nada aqui.", "account.go_to_profile": "Ir ao perfil", "account.hide_reblogs": "Ocultar boosts de @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} quer te seguir", "account.share": "Compartilhar perfil de @{name}", "account.show_reblogs": "Mostrar boosts de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account.unblock": "Desbloquear @{name}", "account.unblock_domain": "Desbloquear domรญnio {domain}", "account.unblock_short": "Desbloquear", @@ -297,6 +294,7 @@ "filter_modal.select_filter.subtitle": "Use uma categoria existente ou crie uma nova", "filter_modal.select_filter.title": "Filtrar esta publicaรงรฃo", "filter_modal.title.status": "Filtrar uma publicaรงรฃo", + "filtered_notifications_banner.mentions": "{count, plural, one {menรงรฃo} other {menรงรตes}}", "filtered_notifications_banner.pending_requests": "Notificaรงรตes de {count, plural, =0 {no one} one {one person} other {# people}} que vocรช talvez conheรงa", "filtered_notifications_banner.title": "Notificaรงรตes filtradas", "firehose.all": "Tudo", @@ -307,6 +305,8 @@ "follow_requests.unlocked_explanation": "Apesar de seu perfil nรฃo ser trancado, {domain} exige que vocรช revise a solicitaรงรฃo para te seguir destes perfis manualmente.", "follow_suggestions.curated_suggestion": "Escolha da equipe", "follow_suggestions.dismiss": "Nรฃo mostrar novamente", + "follow_suggestions.featured_longer": "Escolhido ร  mรฃo pela equipe de {domain}", + "follow_suggestions.friends_of_friends_longer": "Popular entre as pessoas que vocรช segue", "follow_suggestions.hints.featured": "Este perfil foi escolhido a dedo pela equipe {domain}.", "follow_suggestions.hints.friends_of_friends": "Este perfil รฉ popular entre as pessoas que vocรช segue.", "follow_suggestions.hints.most_followed": "Este perfil รฉ um dos mais seguidos em {domain}.", @@ -314,6 +314,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Este perfil รฉ semelhante aos perfis que vocรช seguiu recentemente.", "follow_suggestions.personalized_suggestion": "Sugestรฃo personalizada", "follow_suggestions.popular_suggestion": "Sugestรฃo popular", + "follow_suggestions.popular_suggestion_longer": "Popular em {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Similar a perfis que vocรช seguiu recentemente", "follow_suggestions.view_all": "Visualizar tudo", "follow_suggestions.who_to_follow": "Quem seguir", "followed_tags": "Hashtags seguidas", @@ -409,6 +411,8 @@ "limited_account_hint.action": "Exibir perfil mesmo assim", "limited_account_hint.title": "Este perfil foi ocultado pelos moderadores do {domain}.", "link_preview.author": "Por {name}", + "link_preview.more_from_author": "Mais de {name}", + "link_preview.shares": "{count, plural, one {{counter} publicaรงรฃo} other {{counter} publicaรงรตes}}", "lists.account.add": "Adicionar ร  lista", "lists.account.remove": "Remover da lista", "lists.delete": "Excluir lista", @@ -468,6 +472,15 @@ "notification.follow": "{name} te seguiu", "notification.follow_request": "{name} quer te seguir", "notification.mention": "{name} te mencionou", + "notification.moderation-warning.learn_more": "Aprender mais", + "notification.moderation_warning": "Vocรช recebeu um aviso de moderaรงรฃo", + "notification.moderation_warning.action_delete_statuses": "Algumas das suas publicaรงรตes foram removidas.", + "notification.moderation_warning.action_disable": "Sua conta foi desativada.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Algumas de suas publicaรงรตes foram marcadas por ter conteรบdo sensรญvel.", + "notification.moderation_warning.action_none": "Sua conta recebeu um aviso de moderaรงรฃo.", + "notification.moderation_warning.action_sensitive": "Suas publicaรงรตes serรฃo marcadas como sensรญveis a partir de agora.", + "notification.moderation_warning.action_silence": "Sua conta foi limitada.", + "notification.moderation_warning.action_suspend": "Sua conta foi suspensa.", "notification.own_poll": "Sua enquete terminou", "notification.poll": "Uma enquete que vocรช votou terminou", "notification.reblog": "{name} deu boost no teu toot", @@ -680,13 +693,13 @@ "server_banner.about_active_users": "Pessoas usando este servidor durante os รบltimos 30 dias (Usuรกrios ativos mensalmente)", "server_banner.active_users": "usuรกrios ativos", "server_banner.administered_by": "Administrado por:", - "server_banner.introduction": "{domain} faz parte da rede social descentralizada desenvolvida por {mastodon}.", - "server_banner.learn_more": "Saiba mais", + "server_banner.is_one_of_many": "{domain} รฉ um dos muitos servidores Mastodon independentes que vocรช pode usar para participar do fediverso.", "server_banner.server_stats": "Estatรญsticas do servidor:", "sign_in_banner.create_account": "Criar conta", + "sign_in_banner.follow_anyone": "Siga alguรฉm pelo fediverso e veja tudo em ordem cronolรณgica. Sem algoritmos, anรบncios ou clickbait ร  vista.", + "sign_in_banner.mastodon_is": "O Mastodon รฉ a melhor maneira de acompanhar o que estรก acontecendo.", "sign_in_banner.sign_in": "Entrar", "sign_in_banner.sso_redirect": "Entrar ou Registrar-se", - "sign_in_banner.text": "Identifique-se para seguir perfis ou 'hashtags', favoritar, compartilhar e responder publicaรงรตes. Vocรช tambรฉm pode interagir a partir da sua conta em um servidor diferente.", "status.admin_account": "Abrir interface de moderaรงรฃo para @{name}", "status.admin_domain": "Abrir interface de moderaรงรฃo para {domain}", "status.admin_status": "Abrir este toot na interface de moderaรงรฃo", diff --git a/app/javascript/mastodon/locales/pt-PT.json b/app/javascript/mastodon/locales/pt-PT.json index 7e98cde5fa..6a6feca309 100644 --- a/app/javascript/mastodon/locales/pt-PT.json +++ b/app/javascript/mastodon/locales/pt-PT.json @@ -37,7 +37,7 @@ "account.followers.empty": "Ainda ninguรฉm segue este utilizador.", "account.followers_counter": "{count, plural, one {{counter} seguidor} other {{counter} seguidores}}", "account.following": "A seguir", - "account.following_counter": "{count, plural, other {A seguir {counter}}}", + "account.following_counter": "{count, plural, one {A seguir {counter}} other {A seguir {counter}}}", "account.follows.empty": "Este utilizador ainda nรฃo segue ninguรฉm.", "account.go_to_profile": "Ir para o perfil", "account.hide_reblogs": "Esconder partilhas de @{name}", @@ -63,7 +63,7 @@ "account.requested_follow": "{name} pediu para segui-lo", "account.share": "Partilhar o perfil @{name}", "account.show_reblogs": "Mostrar partilhas de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", + "account.statuses_counter": "{count, plural, one {{counter} publicaรงรฃo} other {{counter} publicaรงรตes}}", "account.unblock": "Desbloquear @{name}", "account.unblock_domain": "Desbloquear o domรญnio {domain}", "account.unblock_short": "Desbloquear", @@ -308,6 +308,8 @@ "follow_requests.unlocked_explanation": "Apesar de a sua nรฃo ser privada, a administraรงรฃo de {domain} pensa que poderรก querer rever manualmente os pedidos de seguimento dessas contas.", "follow_suggestions.curated_suggestion": "Escolha da equipe", "follow_suggestions.dismiss": "Nรฃo mostrar novamente", + "follow_suggestions.featured_longer": "Escolhido a dedo pela equipa de {domain}", + "follow_suggestions.friends_of_friends_longer": "Popular entre as pessoas que segue", "follow_suggestions.hints.featured": "Este perfil foi escolhido a dedo pela equipe {domain}.", "follow_suggestions.hints.friends_of_friends": "Este perfil รฉ popular entre as pessoas que vocรช segue.", "follow_suggestions.hints.most_followed": "Este perfil รฉ um dos mais seguidos no {domain}.", @@ -315,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Este perfil รฉ semelhante aos perfis que vocรช seguiu mais recentemente.", "follow_suggestions.personalized_suggestion": "Sugestรฃo personalizada", "follow_suggestions.popular_suggestion": "Sugestรฃo popular", + "follow_suggestions.popular_suggestion_longer": "Popular em {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Semelhantes aos perfis que seguiu recentemente", "follow_suggestions.view_all": "Ver tudo", "follow_suggestions.who_to_follow": "Quem seguir", "followed_tags": "Hashtags seguidas", @@ -410,6 +414,8 @@ "limited_account_hint.action": "Exibir perfil mesmo assim", "limited_account_hint.title": "Este perfil foi ocultado pelos moderadores de {domain}.", "link_preview.author": "Por {name}", + "link_preview.more_from_author": "Mais de {name}", + "link_preview.shares": "{count, plural, one {{counter} publicaรงรฃo} other {{counter} publicaรงรตes}}", "lists.account.add": "Adicionar ร  lista", "lists.account.remove": "Remover da lista", "lists.delete": "Eliminar lista", @@ -469,6 +475,15 @@ "notification.follow": "{name} comeรงou a seguir-te", "notification.follow_request": "{name} pediu para segui-lo", "notification.mention": "{name} mencionou-te", + "notification.moderation-warning.learn_more": "Saber mais", + "notification.moderation_warning": "Recebeu um aviso de moderaรงรฃo", + "notification.moderation_warning.action_delete_statuses": "Algumas das suas publicaรงรตes foram removidas.", + "notification.moderation_warning.action_disable": "A sua conta foi desativada.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Algumas das suas publicaรงรตes foram assinaladas como sensรญveis.", + "notification.moderation_warning.action_none": "A sua conta recebeu um aviso de moderaรงรฃo.", + "notification.moderation_warning.action_sensitive": "As suas publicaรงรตes serรฃo, a partir de agora, assinaladas como sensรญveis.", + "notification.moderation_warning.action_silence": "A sua conta foi limitada.", + "notification.moderation_warning.action_suspend": "A sua conta foi suspensa.", "notification.own_poll": "A sua votaรงรฃo terminou", "notification.poll": "Uma votaรงรฃo em que participaste chegou ao fim", "notification.reblog": "{name} reforรงou a tua publicaรงรฃo", @@ -681,13 +696,13 @@ "server_banner.about_active_users": "Pessoas que utilizaram este servidor nos รบltimos 30 dias (Utilizadores Ativos Mensais)", "server_banner.active_users": "utilizadores ativos", "server_banner.administered_by": "Administrado por:", - "server_banner.introduction": "{domain} faz parte da rede social descentralizada baseada no {mastodon}.", - "server_banner.learn_more": "Saber mais", + "server_banner.is_one_of_many": "{domain} รฉ um dos muitos servidores Mastodon independentes que pode utilizar para participar no fediverso.", "server_banner.server_stats": "Estatรญsticas do servidor:", "sign_in_banner.create_account": "Criar conta", + "sign_in_banner.follow_anyone": "Siga alguรฉm no fediverso e veja tudo em ordem cronolรณgica. Sem algoritmos, anรบncios ou clickbait ร  vista.", + "sign_in_banner.mastodon_is": "O Mastodon รฉ a melhor maneira de acompanhar o que estรก a acontecer.", "sign_in_banner.sign_in": "Iniciar Sessรฃo", "sign_in_banner.sso_redirect": "Inicie Sessรฃo ou Registe-se", - "sign_in_banner.text": "Inicie sessรฃo para seguir perfis ou etiquetas, assinale como favorito, partilhe ou responda a publicaรงรตes. Pode ainda interagir atravรฉs da sua conta noutro servidor.", "status.admin_account": "Abrir a interface de moderaรงรฃo para @{name}", "status.admin_domain": "Abrir interface de moderaรงรฃo para {domain}", "status.admin_status": "Abrir esta publicaรงรฃo na interface de moderaรงรฃo", diff --git a/app/javascript/mastodon/locales/ro.json b/app/javascript/mastodon/locales/ro.json index 0aef0ebd96..35abf1b021 100644 --- a/app/javascript/mastodon/locales/ro.json +++ b/app/javascript/mastodon/locales/ro.json @@ -35,9 +35,7 @@ "account.follow_back": "UrmฤƒreลŸte รฎnapoi", "account.followers": "Urmฤƒritori", "account.followers.empty": "Acest utilizator nu are รฎncฤƒ urmฤƒritori.", - "account.followers_counter": "{count, plural, one {Un abonat} few {{counter} abonaศ›i} other {{counter} de abonaศ›i}}", "account.following": "Urmฤƒriศ›i", - "account.following_counter": "{count, plural, one {Un abonament} few {{counter} abonamente} other {{counter} de abonamente}}", "account.follows.empty": "Momentan acest utilizator nu are niciun abonament.", "account.go_to_profile": "Mergi la profil", "account.hide_reblogs": "Ascunde distribuirile de la @{name}", @@ -62,7 +60,6 @@ "account.requested_follow": "{name} A cerut sฤƒ vฤƒ urmฤƒreascฤƒ", "account.share": "Distribuie profilul lui @{name}", "account.show_reblogs": "Afiศ™eazฤƒ distribuirile de la @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account.unblock": "Deblocheazฤƒ pe @{name}", "account.unblock_domain": "Deblocheazฤƒ domeniul {domain}", "account.unblock_short": "Deblocheazฤƒ", @@ -550,8 +547,6 @@ "server_banner.about_active_users": "Persoane care au folosit acest server รฎn ultimele 30 de zile (Utilizatori Lunari Activi)", "server_banner.active_users": "utilizatori activi", "server_banner.administered_by": "Administrat de:", - "server_banner.introduction": "{domain} face parte din reศ›eaua socialฤƒ descentralizatฤƒ alimentatฤƒ de {mastodon}.", - "server_banner.learn_more": "Aflฤƒ mai multe", "server_banner.server_stats": "Statisticile serverului:", "sign_in_banner.create_account": "Creeazฤƒ-ศ›i un cont", "sign_in_banner.sign_in": "Conecteazฤƒ-te", diff --git a/app/javascript/mastodon/locales/ru.json b/app/javascript/mastodon/locales/ru.json index cd09a505b3..97a1f0b09c 100644 --- a/app/javascript/mastodon/locales/ru.json +++ b/app/javascript/mastodon/locales/ru.json @@ -35,9 +35,7 @@ "account.follow_back": "ะŸะพะดะฟะธัะฐั‚ัŒัั ะฒ ะพั‚ะฒะตั‚", "account.followers": "ะŸะพะดะฟะธัั‡ะธะบะธ", "account.followers.empty": "ะะฐ ัั‚ะพะณะพ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะฟะพะบะฐ ะฝะธะบั‚ะพ ะฝะต ะฟะพะดะฟะธัะฐะฝ.", - "account.followers_counter": "{count, plural, one {{counter} ะฟะพะดะฟะธัั‡ะธะบ} many {{counter} ะฟะพะดะฟะธัั‡ะธะบะพะฒ} other {{counter} ะฟะพะดะฟะธัั‡ะธะบะฐ}}", "account.following": "ะŸะพะดะฟะธัะบะธ", - "account.following_counter": "{count, plural, one {{counter} ะฟะพะดะฟะธัะบะฐ} many {{counter} ะฟะพะดะฟะธัะพะบ} other {{counter} ะฟะพะดะฟะธัะบะธ}}", "account.follows.empty": "ะญั‚ะพั‚ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัŒ ะฟะพะบะฐ ะฝะธ ะฝะฐ ะบะพะณะพ ะฝะต ะฟะพะดะฟะธัะฐะปัั.", "account.go_to_profile": "ะŸะตั€ะตะนั‚ะธ ะบ ะฟั€ะพั„ะธะปัŽ", "account.hide_reblogs": "ะกะบั€ั‹ั‚ัŒ ะฟั€ะพะดะฒะธะถะตะฝะธั ะพั‚ @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} ะพั‚ะฟั€ะฐะฒะธะป(ะฐ) ะฒะฐะผ ะทะฐะฟั€ะพั ะฝะฐ ะฟะพะดะฟะธัะบัƒ", "account.share": "ะŸะพะดะตะปะธั‚ัŒัั ะฟั€ะพั„ะธะปะตะผ @{name}", "account.show_reblogs": "ะŸะพะบะฐะทั‹ะฒะฐั‚ัŒ ะฟั€ะพะดะฒะธะถะตะฝะธั ะพั‚ @{name}", - "account.statuses_counter": "{count, plural, one {{counter} ะฟะพัั‚} many {{counter} ะฟะพัั‚ะพะฒ} other {{counter} ะฟะพัั‚ะฐ}}", "account.unblock": "ะ ะฐะทะฑะปะพะบะธั€ะพะฒะฐั‚ัŒ @{name}", "account.unblock_domain": "ะ ะฐะทะฑะปะพะบะธั€ะพะฒะฐั‚ัŒ {domain}", "account.unblock_short": "ะ ะฐะทะฑะปะพะบะธั€ะพะฒะฐั‚ัŒ", @@ -297,6 +294,7 @@ "filter_modal.select_filter.subtitle": "ะ˜ัะฟะพะปัŒะทัƒะนั‚ะต ััƒั‰ะตัั‚ะฒัƒัŽั‰ัƒัŽ ะบะฐั‚ะตะณะพั€ะธัŽ ะธะปะธ ัะพะทะดะฐะนั‚ะต ะฝะพะฒัƒัŽ", "filter_modal.select_filter.title": "ะคะธะปัŒั‚ั€ะพะฒะฐั‚ัŒ ัั‚ะพั‚ ะฟะพัั‚", "filter_modal.title.status": "ะคะธะปัŒั‚ั€ะพะฒะฐั‚ัŒ ะฟะพัั‚", + "filtered_notifications_banner.mentions": "{count, plural, one {ัƒะฟะพะผะธะฝะฐะฝะธะต} other {ัƒะฟะพะผะธะฝะฐะฝะธั}}", "filtered_notifications_banner.pending_requests": "ะฃะฒะตะดะพะผะปะตะฝะธั ะพั‚ {count, plural, =0 {ะฝะธะบะพะณะพ} one {# ั‡ะตะปะพะฒะตะบะฐ} other {# ะดั€ัƒะณะธั… ะปัŽะดะตะน, ั ะบะตะผ ะฒั‹ ะผะพะถะตั‚ะต ะฑั‹ั‚ัŒ ะทะฝะฐะบะพะผั‹}}", "filtered_notifications_banner.title": "ะžั‚ั„ะธะปัŒั‚ั€ะพะฒะฐะฝะฝั‹ะต ัƒะฒะตะดะพะผะปะตะฝะธั", "firehose.all": "ะ’ัะต", @@ -307,6 +305,8 @@ "follow_requests.unlocked_explanation": "ะฅะพั‚ั ะฒะฐัˆะฐ ัƒั‡ะตั‚ะฝะฐั ะทะฐะฟะธััŒ ะฝะต ะทะฐะบั€ั‹ั‚ะฐ, ะบะพะผะฐะฝะดะฐ {domain} ะฟะพะดัƒะผะฐะปะฐ, ั‡ั‚ะพ ะฒั‹ ะทะฐั…ะพั‚ะธั‚ะต ะฟั€ะพัะผะพั‚ั€ะตั‚ัŒ ะทะฐะฟั€ะพัั‹ ะพั‚ ัั‚ะธั… ัƒั‡ะตั‚ะฝั‹ั… ะทะฐะฟะธัะตะน ะฒั€ัƒั‡ะฝัƒัŽ.", "follow_suggestions.curated_suggestion": "ะ’ั‹ะฑะพั€ ะฐะดะผะธะฝะธัั‚ั€ะฐั†ะธะธ", "follow_suggestions.dismiss": "ะ‘ะพะปัŒัˆะต ะฝะต ะฟะพะบะฐะทั‹ะฒะฐั‚ัŒ", + "follow_suggestions.featured_longer": "ะžั‚ะพะฑั€ะฐะฝะฝั‹ะต ะบะพะผะฐะฝะดะพะน {domain} ะฒั€ัƒั‡ะฝัƒัŽ", + "follow_suggestions.friends_of_friends_longer": "ะŸะพะฟัƒะปัั€ะฝะพ ัั€ะตะดะธ ะปัŽะดะตะน, ะฝะฐ ะบะพั‚ะพั€ั‹ั… ะฒั‹ ะฟะพะดะฟะธัะฐะฝั‹", "follow_suggestions.hints.featured": "ะญั‚ะพั‚ ะฟั€ะพั„ะธะปัŒ ะฑั‹ะป ะฒั€ัƒั‡ะฝัƒัŽ ะฒั‹ะฑั€ะฐะฝ ะบะพะผะฐะฝะดะพะน {domain}.", "follow_suggestions.hints.friends_of_friends": "ะญั‚ะพั‚ ะฟั€ะพั„ะธะปัŒ ะฟะพะฟัƒะปัั€ะตะฝ ัั€ะตะดะธ ะปัŽะดะตะน, ะฝะฐ ะบะพั‚ะพั€ั‹ั… ะฒั‹ ะฟะพะดะฟะธัะฐะฝั‹.", "follow_suggestions.hints.most_followed": "ะญั‚ะพั‚ ะฟั€ะพั„ะธะปัŒ ะพะดะธะฝ ะธะท ัะฐะผั‹ั… ะพั‚ัะปะตะถะธะฒะฐะตะผั‹ั… ะฝะฐ {domain}.", @@ -314,6 +314,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "ะญั‚ะพั‚ ะฟั€ะพั„ะธะปัŒ ะฟะพั…ะพะถ ะฝะฐ ะดั€ัƒะณะธะต ะฟั€ะพั„ะธะปะธ, ะฝะฐ ะบะพั‚ะพั€ั‹ะต ะฒั‹ ะฟะพะดะฟะธัั‹ะฒะฐะปะธััŒ ะฒ ะฟะพัะปะตะดะฝะตะต ะฒั€ะตะผั.", "follow_suggestions.personalized_suggestion": "ะŸะตั€ัะพะฝะฐะปะธะทะธั€ะพะฒะฐะฝะฝะพะต ะฟั€ะตะดะปะพะถะตะฝะธะต", "follow_suggestions.popular_suggestion": "ะŸะพะฟัƒะปัั€ะฝะพะต ะฟั€ะตะดะปะพะถะตะฝะธะต", + "follow_suggestions.popular_suggestion_longer": "ะŸะพะฟัƒะปัั€ะฝะพะต ะฝะฐ {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "ะŸะพั…ะพะถะต ะฝะฐ ะฟั€ะพั„ะธะปะธ, ะฝะฐ ะบะพั‚ะพั€ั‹ะต ะฒั‹ ะฝะตะดะฐะฒะฝะพ ะฟะพะดะฟะธัะฐะปะธััŒ", "follow_suggestions.view_all": "ะŸะพัะผะพั‚ั€ะตั‚ัŒ ะฒัะต", "follow_suggestions.who_to_follow": "ะะฐ ะบะพะณะพ ะฟะพะดะฟะธัะฐั‚ัŒัั", "followed_tags": "ะžั‚ัะปะตะถะธะฒะฐะตะผั‹ะต ั…ััˆั‚ะตะณะธ", @@ -409,6 +411,8 @@ "limited_account_hint.action": "ะ’ัะต ั€ะฐะฒะฝะพ ะฟะพะบะฐะทะฐั‚ัŒ ะฟั€ะพั„ะธะปัŒ", "limited_account_hint.title": "ะญั‚ะพั‚ ะฟั€ะพั„ะธะปัŒ ะฑั‹ะป ัะบั€ั‹ั‚ ะผะพะดะตั€ะฐั‚ะพั€ะฐะผะธ {domain}.", "link_preview.author": "ะะฒั‚ะพั€: {name}", + "link_preview.more_from_author": "ะ‘ะพะปัŒัˆะต ะพั‚ {name}", + "link_preview.shares": "{count, plural, one {{counter} ะฟะพัั‚} other {{counter} ะฟะพัั‚ั‹}}", "lists.account.add": "ะ”ะพะฑะฐะฒะธั‚ัŒ ะฒ ัะฟะธัะพะบ", "lists.account.remove": "ะฃะฑั€ะฐั‚ัŒ ะธะท ัะฟะธัะบะฐ", "lists.delete": "ะฃะดะฐะปะธั‚ัŒ ัะฟะธัะพะบ", @@ -468,6 +472,15 @@ "notification.follow": "{name} ะฟะพะดะฟะธัะฐะปัั (-ะปะฐััŒ) ะฝะฐ ะฒะฐั", "notification.follow_request": "{name} ะพั‚ะฟั€ะฐะฒะธะป ะทะฐะฟั€ะพั ะฝะฐ ะฟะพะดะฟะธัะบัƒ", "notification.mention": "{name} ัƒะฟะพะผัะฝัƒะป(ะฐ) ะฒะฐั", + "notification.moderation-warning.learn_more": "ะฃะทะฝะฐั‚ัŒ ะฑะพะปัŒัˆะต", + "notification.moderation_warning": "ะ’ั‹ ะฟะพะปัƒั‡ะธะปะธ ะฟั€ะตะดัƒะฟั€ะตะถะดะตะฝะธะต ะพั‚ ะผะพะดะตั€ะฐั†ะธะธ", + "notification.moderation_warning.action_delete_statuses": "ะะตะบะพั‚ะพั€ั‹ะต ะธะท ะฒะฐัˆะธั… ะฟัƒะฑะปะธะบะฐั†ะธะน ะฑั‹ะปะธ ัƒะดะฐะปะตะฝั‹.", + "notification.moderation_warning.action_disable": "ะ’ะฐัˆะฐ ัƒั‡ั‘ั‚ะฝะฐั ะทะฐะฟะธััŒ ะฑั‹ะปะฐ ะพั‚ะบะปัŽั‡ะตะฝะฐ.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "ะะตะบะพั‚ะพั€ั‹ะต ะธะท ะฒะฐัˆะธั… ัะพะพะฑั‰ะตะฝะธะน ะฑั‹ะปะธ ะพั‚ะผะตั‡ะตะฝั‹ ะบะฐะบ ะดะตะปะธะบะฐั‚ะฝั‹ะต.", + "notification.moderation_warning.action_none": "ะ’ะฐัˆะฐ ัƒั‡ั‘ั‚ะฝะฐั ะทะฐะฟะธััŒ ะฟะพะปัƒั‡ะธะปะฐ ะฟั€ะตะดัƒะฟั€ะตะถะดะตะฝะธะต ะพั‚ ะผะพะดะตั€ะฐั†ะธะธ.", + "notification.moderation_warning.action_sensitive": "ะก ัั‚ะพะณะพ ะผะพะผะตะฝั‚ะฐ ะฒะฐัˆะธ ัะพะพะฑั‰ะตะฝะธั ะฑัƒะดัƒั‚ ะฟะพะผะตั‡ะตะฝั‹ ะบะฐะบ ะดะตะปะธะบะฐั‚ะฝั‹ะต.", + "notification.moderation_warning.action_silence": "ะ’ะฐัˆะฐ ัƒั‡ั‘ั‚ะฝะฐั ะทะฐะฟะธััŒ ะฑั‹ะปะฐ ะพะณั€ะฐะฝะธั‡ะตะฝะฐ.", + "notification.moderation_warning.action_suspend": "ะ”ะตะนัั‚ะฒะธะต ะฒะฐัˆะตะน ัƒั‡ั‘ั‚ะฝะพะน ะทะฐะฟะธัะธ ะฟั€ะธะพัั‚ะฐะฝะพะฒะปะตะฝะพ.", "notification.own_poll": "ะ’ะฐัˆ ะพะฟั€ะพั ะทะฐะบะพะฝั‡ะธะปัั", "notification.poll": "ะžะฟั€ะพั, ะฒ ะบะพั‚ะพั€ะพะผ ะฒั‹ ะฟั€ะธะฝัะปะธ ัƒั‡ะฐัั‚ะธะต, ะทะฐะฒะตั€ัˆะธะปัั", "notification.reblog": "{name} ะฟั€ะพะดะฒะธะฝัƒะป(ะฐ) ะฒะฐัˆ ะฟะพัั‚", @@ -488,6 +501,8 @@ "notifications.column_settings.admin.sign_up": "ะะพะฒั‹ะต ั€ะตะณะธัั‚ั€ะฐั†ะธะธ:", "notifications.column_settings.alert": "ะฃะฒะตะดะพะผะปะตะฝะธั ะฝะฐ ั€ะฐะฑะพั‡ะตะผ ัั‚ะพะปะต", "notifications.column_settings.favourite": "ะ˜ะทะฑั€ะฐะฝะฝั‹ะต:", + "notifications.column_settings.filter_bar.advanced": "ะžั‚ะพะฑั€ะฐะถะฐั‚ัŒ ะฒัะต ะบะฐั‚ะตะณะพั€ะธะธ", + "notifications.column_settings.filter_bar.category": "ะŸะฐะฝะตะปัŒ ัะพั€ั‚ะธั€ะพะฒะบะธ", "notifications.column_settings.follow": "ะฃ ะฒะฐั ะฝะพะฒั‹ะน ะฟะพะดะฟะธัั‡ะธะบ:", "notifications.column_settings.follow_request": "ะะพะฒั‹ะต ะทะฐะฟั€ะพัั‹ ะฝะฐ ะฟะพะดะฟะธัะบัƒ:", "notifications.column_settings.mention": "ะ’ะฐั ัƒะฟะพะผัะฝัƒะปะธ ะฒ ะฟะพัั‚ะต:", @@ -513,7 +528,14 @@ "notifications.permission_denied": "ะฃะฒะตะดะพะผะปะตะฝะธั ะฝะฐ ั€ะฐะฑะพั‡ะตะผ ัั‚ะพะปะต ะฝะตะดะพัั‚ัƒะฟะฝั‹, ั‚ะฐะบ ะบะฐะบ ะฒั‹ ะทะฐะฟั€ะตั‚ะธะปะธ ะธั… ะพั‚ะฟั€ะฐะฒะบัƒ ะฒ ะฑั€ะฐัƒะทะตั€ะต. ะŸั€ะพะฒะตั€ัŒั‚ะต ะฝะฐัั‚ั€ะพะนะบะธ ะดะปั ัะฐะนั‚ะฐ, ั‡ั‚ะพะฑั‹ ะฒะบะปัŽั‡ะธั‚ัŒ ะธั… ะพะฑั€ะฐั‚ะฝะพ.", "notifications.permission_denied_alert": "ะฃะฒะตะดะพะผะปะตะฝะธั ะฝะฐ ั€ะฐะฑะพั‡ะตะผ ัั‚ะพะปะต ะฝะตะดะพัั‚ัƒะฟะฝั‹, ั‚ะฐะบ ะบะฐะบ ะฒั‹ ั€ะฐะฝะตะต ะพั‚ะบะปะพะฝะธะปะธ ะทะฐะฟั€ะพั ะฝะฐ ะธั… ะพั‚ะฟั€ะฐะฒะบัƒ.", "notifications.permission_required": "ะงั‚ะพะฑั‹ ะฒะบะปัŽั‡ะธั‚ัŒ ัƒะฒะตะดะพะผะปะตะฝะธั ะฝะฐ ั€ะฐะฑะพั‡ะตะผ ัั‚ะพะปะต, ะฝะตะพะฑั…ะพะดะธะผะพ ั€ะฐะทั€ะตัˆะธั‚ัŒ ะธั… ะฒ ะฑั€ะฐัƒะทะตั€ะต.", + "notifications.policy.filter_new_accounts.hint": "ะกะพะทะดะฐะฝะพ ะฒ ั‚ะตั‡ะตะฝะธะต ะฟะพัะปะตะดะฝะธั… {days, plural, one {ะพะดะธะฝ ะดะตะฝัŒ} few {# ะดะฝะตะน} many {# ะดะฝะตะน} other {# ะดะฝั}}", "notifications.policy.filter_new_accounts_title": "ะะพะฒั‹ะต ัƒั‡ั‘ั‚ะฝั‹ะต ะทะฐะฟะธัะธ", + "notifications.policy.filter_not_followers_title": "ะ›ัŽะดะธ, ะฝะต ะฟะพะดะฟะธัะฐะฝะฝั‹ะต ะฝะฐ ะฒะฐั", + "notifications.policy.filter_not_following_hint": "ะŸะพะบะฐ ะฒั‹ ะฝะต ะพะดะพะฑั€ะธั‚ะต ะธั… ะฒั€ัƒั‡ะฝัƒัŽ", + "notifications.policy.filter_not_following_title": "ะ›ัŽะดะธ, ะฝะฐ ะบะพั‚ะพั€ั‹ั… ะฒั‹ ะฝะต ะฟะพะดะฟะธัะฐะฝั‹", + "notifications.policy.filter_private_mentions_hint": "ะคะธะปัŒั‚ั€ัƒะตั‚ัั, ะตัะปะธ ั‚ะพะปัŒะบะพ ัั‚ะพ ะฝะต ะพั‚ะฒะตั‚ ะฝะฐ ะฒะฐัˆะต ัะพะฑัั‚ะฒะตะฝะฝะพะต ัƒะฟะพะผะธะฝะฐะฝะธะต ะธะปะธ ะตัะปะธ ะฒั‹ ะฟะพะดะฟะธัะฐะฝั‹ ะฝะฐ ะพั‚ะฟั€ะฐะฒะธั‚ะตะปั", + "notifications.policy.filter_private_mentions_title": "ะะตะถะตะปะฐั‚ะตะปัŒะฝั‹ะต ะปะธั‡ะฝั‹ะต ัƒะฟะพะผะธะฝะฐะฝะธั", + "notifications.policy.title": "ะคะธะปัŒั‚ั€ะพะฒะฐั‚ัŒ ัƒะฒะตะดะพะผะปะตะฝะธั ะพั‚โ€ฆ", "notifications_permission_banner.enable": "ะ’ะบะปัŽั‡ะธั‚ัŒ ัƒะฒะตะดะพะผะปะตะฝะธั", "notifications_permission_banner.how_to_control": "ะŸะพะปัƒั‡ะฐะนั‚ะต ัƒะฒะตะดะพะผะปะตะฝะธั ะดะฐะถะต ะบะพะณะดะฐ Mastodon ะทะฐะบั€ั‹ั‚, ะฒะบะปัŽั‡ะธะฒ ัƒะฒะตะดะพะผะปะตะฝะธั ะฝะฐ ั€ะฐะฑะพั‡ะตะผ ัั‚ะพะปะต. ะ ั‡ั‚ะพะฑั‹ ะปะธัˆะฝะธะน ัˆัƒะผ ะฝะต ะพั‚ะฒะปะตะบะฐะป, ะฒั‹ ะผะพะถะตั‚ะต ะฝะฐัั‚ั€ะพะธั‚ัŒ ะบะฐะบะธะต ัƒะฒะตะดะพะผะปะตะฝะธั ะฒั‹ ั…ะพั‚ะธั‚ะต ะฟะพะปัƒั‡ะฐั‚ัŒ, ะฝะฐะถะฐะฒ ะฝะฐ ะบะฝะพะฟะบัƒ {icon} ะฒั‹ัˆะต.", "notifications_permission_banner.title": "ะ‘ัƒะดัŒั‚ะต ะฒ ะบัƒั€ัะต ะฟั€ะพะธัั…ะพะดัั‰ะตะณะพ", @@ -670,13 +692,10 @@ "server_banner.about_active_users": "ะ›ัŽะดะธ, ะทะฐั…ะพะดะธะฒัˆะธะต ะฝะฐ ัั‚ะพั‚ ัะตั€ะฒะตั€ ะทะฐ ะฟะพัะปะตะดะฝะธะต 30 ะดะฝะตะน (ะตะถะตะผะตััั‡ะฝั‹ะต ะฐะบั‚ะธะฒะฝั‹ะต ะฟะพะปัŒะทะพะฒะฐั‚ะตะปะธ)", "server_banner.active_users": "ะฐะบั‚ะธะฒะฝั‹ะต ะฟะพะปัŒะทะพะฒะฐั‚ะตะปะธ", "server_banner.administered_by": "ะฃะฟั€ะฐะฒะปัะตั‚ัั:", - "server_banner.introduction": "{domain} ัะฒะปัะตั‚ัั ั‡ะฐัั‚ัŒัŽ ะดะตั†ะตะฝั‚ั€ะฐะปะธะทะพะฒะฐะฝะฝะพะน ัะพั†ะธะฐะปัŒะฝะพะน ัะตั‚ะธ, ะพัะฝะพะฒะฐะฝะฝะพะน ะฝะฐ {mastodon}.", - "server_banner.learn_more": "ะฃะทะฝะฐั‚ัŒ ะฑะพะปัŒัˆะต", "server_banner.server_stats": "ะกั‚ะฐั‚ะธัั‚ะธะบะฐ ัะตั€ะฒะตั€ะฐ:", "sign_in_banner.create_account": "ะกะพะทะดะฐั‚ัŒ ัƒั‡ั‘ั‚ะฝัƒัŽ ะทะฐะฟะธััŒ", "sign_in_banner.sign_in": "ะ’ะพะนั‚ะธ", "sign_in_banner.sso_redirect": "ะ’ะพะนะดะธั‚ะต ะธะปะธ ะ—ะฐั€ะตะณะธัั‚ั€ะธั€ัƒะนั‚ะตััŒ", - "sign_in_banner.text": "ะ’ะพะนะดะธั‚ะต, ั‡ั‚ะพะฑั‹ ะพั‚ัะปะตะถะธะฒะฐั‚ัŒ ะฟั€ะพั„ะธะปะธ, ั…ััˆั‚ะตะณะธ ะธะปะธ ะธะทะฑั€ะฐะฝะฝะพะต, ะดะตะปะธั‚ัŒัั ัะพะพะฑั‰ะตะฝะธัะผะธ ะธ ะพั‚ะฒะตั‡ะฐั‚ัŒ ะฝะฐ ะฝะธั…. ะ’ั‹ ั‚ะฐะบะถะต ะผะพะถะตั‚ะต ะฒะทะฐะธะผะพะดะตะนัั‚ะฒะพะฒะฐั‚ัŒ ั ะฒะฐัˆะตะน ัƒั‡ั‘ั‚ะฝะพะน ะทะฐะฟะธััŒัŽ ะฝะฐ ะดั€ัƒะณะพะผ ัะตั€ะฒะตั€ะต.", "status.admin_account": "ะžั‚ะบั€ั‹ั‚ัŒ ะธะฝั‚ะตั€ั„ะตะนั ะผะพะดะตั€ะฐั‚ะพั€ะฐ ะดะปั @{name}", "status.admin_domain": "ะžั‚ะบั€ั‹ั‚ัŒ ะธะฝั‚ะตั€ั„ะตะนั ะผะพะดะตั€ะฐั†ะธะธ {domain}", "status.admin_status": "ะžั‚ะบั€ั‹ั‚ัŒ ัั‚ะพั‚ ะฟะพัั‚ ะฒ ะธะฝั‚ะตั€ั„ะตะนัะต ะผะพะดะตั€ะฐั‚ะพั€ะฐ", @@ -690,6 +709,7 @@ "status.direct": "ะ›ะธั‡ะฝะพ ัƒะฟะพะผะธะฝะฐั‚ัŒ @{name}", "status.direct_indicator": "ะ›ะธั‡ะฝั‹ะต ัƒะฟะพะผะธะฝะฐะฝะธั", "status.edit": "ะ˜ะทะผะตะฝะธั‚ัŒ", + "status.edited": "ะ”ะฐั‚ะฐ ะฟะพัะปะตะดะฝะตะณะพ ะธะทะผะตะฝะตะฝะธั: {date}", "status.edited_x_times": "{count, plural, one {{count} ะธะทะผะตะฝะตะฝะธะต} many {{count} ะธะทะผะตะฝะตะฝะธะน} other {{count} ะธะทะผะตะฝะตะฝะธั}}", "status.embed": "ะ’ัั‚ั€ะพะธั‚ัŒ ะฝะฐ ัะฒะพะน ัะฐะนั‚", "status.favourite": "ะ˜ะทะฑั€ะฐะฝะฝะพะต", diff --git a/app/javascript/mastodon/locales/ry.json b/app/javascript/mastodon/locales/ry.json index 67aad91005..4f2e2410ef 100644 --- a/app/javascript/mastodon/locales/ry.json +++ b/app/javascript/mastodon/locales/ry.json @@ -3,6 +3,8 @@ "about.contact": "ะšะพะฝั‚ะฐะบั‚:", "about.disclaimer": "Mastodon ั” ะทะฐะดะฐั€ัŒะฝะพะฒ ะฟั€ะพา‘ั€ะฐะผะพะฒ ะธะท ัƒะดะฟะตั€ั‚ั‹ะผ ะบะพะดะพะผ ั‚ะฐะน ั‚ะพั€ะณะพะฒะพะฒ ะทะฝะฐั‡ะบะพะฒ Mastodon gGmbH.", "about.domain_blocks.no_reason_available": "ะŸั€ะธั‡ะธะฝั‹ ะฝะต ััะฝั–", + "about.domain_blocks.preamble": "ะœะฐะนะฑัƒะปัŒัˆ Mastodon ะฟะพะฒะพะปัั‚ ะฒะฐะผ ะฟะพะทะธั€ะฐั‚ะธ ะบะพะฝั‚ะตะฝั‚ ั‚ะฐะน ะบะพะผัƒะฝั–ะบะพะฒะฐั‚ะธ ะธะท ั…ะพัะฝะพะฒะฐั‡ะฐะผะธ ะธะท ะดั€ัƒะณั‹ั… ั„ะตะดะตั€ะพะฒะฐะฝั‹ั… ัะตั€ะฒะตั€ัƒะฒ. ะขัƒะน ะปะธัˆ ัƒะฝัั‚ะบั‹ ัƒั‡ะธะฝะตะฝั– ะฟั€ะพ ัะธััŒ ะบะพะฝะบั€ะตั‚ะฝั‹ะน ัะตั€ะฒะตั€.", + "about.domain_blocks.silenced.explanation": "ะ’ั‹ ะผะฐะนะฑัƒะปัŒัˆ ะฝะต ะฑัƒะดะตั‚ะต ะฒะธะดั–ั‚ะธ ะฟั€ะพั„ั–ะปั– ั‚ะฐะน ะบะพะฝั‚ะตะฝั‚ ะธะท ััŒะพะณะพ ัะตั€ะฒะตั€ะฐ, ะบะธะดัŒ ะฝะต ะฑัƒะดะตั‚ะต ะณะพ ัะฐะผั– ะณะปัะดะฐั‚ะธ ะฐะฒะฐะดัŒ ะฟัƒะดะฟะธัˆะตั‚ะต ัั ะฝะฐ ะฝัŒะพะณะพ.", "about.domain_blocks.silenced.title": "ะžะฑะผะตะถะตะฝะพ", "about.domain_blocks.suspended.explanation": "ะะธัะบั– ะฟะพะดะฐั‚ะบั‹ ะธะท ััŒะพะณะพ ัะตั€ะฒะตั€ะฐ ะฝะต ะฑัƒะดัƒั‚ ัƒะฑั€ะพะฑะปะตะฝั–, ัƒัะพะบะพั‡ะตะฝั– ั†ะธ ะฟะพะผั–ะฝัะฝั–, ัˆั‚ะพ ั‡ะธะฝะธั‚ ะฝะตะฒะพะทะผะพะถะฝะพะฒ ั…ะพั‚ัŒ-ัะบัƒ ั–ะฝั‚ะตั€ะฐะบั†ั–ัŽ ั†ะธ ะทัะทะพะบ ะธะท ั…ะพัะฝะพะฒะฐั‡ะฐะผะธ ะธะท ััŒะพะณะพ ัะตั€ะฒะตั€ะฐ.", "about.domain_blocks.suspended.title": "ะ—ะฐะฑะปะพะบะพะฒะฐะฝะพ", @@ -20,6 +22,7 @@ "account.browse_more_on_origin_server": "ะŸะพะทะธั€ะฐะนั‚ะต ะฑัƒะปัŒัˆะต ะฝะฐ ะพั€ะธา‘ั–ะฝะฐะปะฝัƒะผ ะฟั€ะพั„ั–ะปัŽ", "account.cancel_follow_request": "ะฃะดะผั–ะฝะธั‚ะธ ะฟัƒะดะฟะธัะบัƒ", "account.copy": "ะ—ะบะพะฟั–ั€ะพะฒะฐั‚ะธ ัƒะดะบะปะธะบะพะฒะฐะฝั ะฝะฐ ะฟั€ะพั„ั–ะป", + "account.direct": "ะŸะพัˆะตะฟั‚ะฐั‚ะธ @{name}", "account.disable_notifications": "ะ‘ัƒะปัŒัˆะต ะฝะต ัะฟะพะฒั–ั‰ะฐั‚ะธ ะผะธ ะบะพะปะธ {name} ะฟะธัˆะต", "account.domain_blocked": "ะ”ะพะผะตะฝ ะทะฐะฑะปะพะบะพะฒะฐะฝั‹ะน", "account.edit_profile": "ะฃะฟั€ะฐะฒะธั‚ะธ ะฟั€ะพั„ั–ะป", @@ -39,8 +42,10 @@ "account.joined_short": "ะ”ะฐั‚ัƒะผ ะฟั€ะธะบะฐะฟั‡ะพะฒะฐะฝั", "account.languages": "ะŸะพะผั–ะฝัั‚ะธ ัƒะฑั€ะฐะฝั– ัะทั‹ะบั‹", "account.link_verified_on": "ะ’ะปะฐัั‚ะฝะพัั‚ัŒ ััŒะพะณะพ ัƒะดะบะปะธะบะพะฒะฐะฝั ะฑั‹ะปะพ ะทะฒั–ั€ะตะฝะพ {date}", + "account.locked_info": "ะกะธััŒ ะฟั€ะพั„ั–ะป ั” ะทะฐะผะบะฝัƒั‚ั‹ะน. าะฐะทะดะฐ ะฐะบะฐัƒะฝั‚ะฐ ะฑัƒะดะต ั€ัƒั‡ะฝะพ ะฟั€ะพะฒั–ั€ัั‚ะธ ั‚ะบะพ ะณะพ ะผะพะถะต ะทะฐั„ะพะปะพะฒะธั‚ะธ.", "account.media": "ะœะตะดั–ะฐ", - "account.moved_to": "ะฅะพัะฝะพะฒะฐั‡ {name} ัƒะบะฐะทะฐะฒ, ะพะถ ะฝะพะฒั‹ะน ะฟั€ะพั„ั–ะป ะนะธะผ ั”:", + "account.mention": "ะกะฟะพะผัะฝัƒั‚ะธ @{name}", + "account.moved_to": "ะฅะพัะฝะพะฒะฐั‡ {name} ัƒะบะฐะทะฐะฒ, ะพะถ ะฝะพะฒั‹ะน ะฟั€ะพั„ั–ะป ะผัƒ ั”:", "account.mute": "ะกั‚ะธัˆะธั‚ะธ {name}", "account.mute_notifications_short": "ะกั‚ะธัˆะธั‚ะธ ะณะพะปะพัˆั–ะฝั", "account.mute_short": "ะกั‚ะธัˆะธั‚ะธ", @@ -60,9 +65,12 @@ "account.unblock_short": "ะ ะพะทะฑะปะพะบะพะฒะฐั‚ะธ", "account.unendorse": "ะะต ัƒะบะฐะทะพะฒะฐั‚ะธ ะฝะฐ ะฟั€ะพั„ั–ะปะพะฒะธ", "account.unfollow": "ะฃะดะฟะธัะฐั‚ะธ ัั", + "account.unmute": "ะฃะบะฐะทะพะฒะฐั‚ะธ {name}", "account.unmute_notifications_short": "ะฃะบะฐะทะพะฒะฐั‚ะธ ะณะพะปะพัˆั–ะฝั", "account.unmute_short": "ะฃะบะฐะทะพะฒะฐั‚ะธ", "account_note.placeholder": "ะšะปะพะฟะบะฝั–ั‚ ะพะฑั‹ ะดะพะดะฐั‚ะธ ะฟั€ะธะผั–ั‚ะบัƒ", + "admin.dashboard.retention.average": "ะกะตั€ะตะดะฝัŒะพั”", + "admin.dashboard.retention.cohort": "ะœั–ััั†ัŒ ะฟั€ะธะบะฐะฟั‡ะพะฒะฐะฝั", "admin.dashboard.retention.cohort_size": "ะะพะฒั– ั…ะพัะฝะพะฒะฐั‡ั–", "admin.impact_report.instance_accounts": "ะŸั€ะพั„ั–ะปั– ะธะท ะฐะบะฐัƒะฝั‚ัƒะฒ, ะบะพั‚ั€ั– ัั ัƒะดะฐะปัั‚", "admin.impact_report.instance_followers": "ะŸัƒะดะฟะธัะฝะธะบั‹, ะบะพั‚ั€ั‹ั… ัั‚ั€ะฐั‚ัั‚ ะฝะฐัˆั– ั…ะพัะฝะพะฒะฐั‡ั–", @@ -70,11 +78,77 @@ "admin.impact_report.title": "ะ’ะฟะปั‹ะฒ ั†ั–ะปะบะพะผ", "alert.rate_limited.message": "ะŸะพะฟั€ะพะฑัƒะนั‚ะต ะทะฐััŒ ะฟะพ {retry_time, time, medium}.", "alert.rate_limited.title": "ะงะฐัั‚ะพั‚ะฐ ะพะฑะผะตะถะตะฝะฐ", + "alert.unexpected.message": "ะกั‚ะฐะปะฐ ัั ะฝะตั‡ะตะบะฐะฝะฐ ั…ั‹ะฑะฐ.", + "alert.unexpected.title": "ะ˜ะนะพะน!", + "announcement.announcement": "ะ“ะพะปะพัˆั–ะฝั", + "audio.hide": "ะ—ะฟั€ัั‚ะฐั‚ะธ ะทะฒัƒะบ", + "block_modal.remote_users_caveat": "ะŸะพะฟั€ะพัะธะผะต า‘ะฐะทะดัƒ ัะตั€ะฒะตั€ะฐ {domain} ั‡ะตัั‚ะพะฒะฐั‚ะธ ะฒะฐัˆะพั” ั€ั–ัˆะตะฝั. ะะนะฑะพ ะฝะต า‘ะฐั€ะฐะฝั‚ัƒั”ะผะต ะฟะพะฒะฝั‹ะน ัะพะณะปะฐั, ะฑะพ ะดะฐัะบั– ัะตั€ะฒะตั€ั‹ ะผะพะถัƒั‚ ะฑั€ะฐั‚ะธ ะฑะปะพะบะพะฒะฐะฝั ะฟะพ-ะธะฝั‡ะฐะบะพะผัƒ. ะŸัƒะฑะปะธั‡ะฝั– ะดะพะฟะธัั‹ ะณะพะดะฝะพ ะฑั‹ั‚ะธ ะฒะธะดะบะพ ะฝะตะทะฐะปะพา‘ะพะฒะฐะฝั‹ะผ ั…ะพัะฝะพะฒะฐั‡ะฐะผ.", + "block_modal.show_less": "ะฃะบะฐะทะฐั‚ะธ ะผะตะฝัˆะต", + "block_modal.show_more": "ะฃะบะฐะทะฐั‚ะธ ะฑัƒะปัŒัˆะต", + "block_modal.they_cant_mention": "ะžะฝะธ ะฝะต ะผะพะถัƒั‚ ะฒะฐั ัะฟะพะผะธะฝะฐั‚ะธ ะฐะฒะฐะดัŒ ัะปั–ะดะพะฒะฐั‚ะธ.", + "block_modal.they_cant_see_posts": "ะžะฝะธ ะฝะต ะผะพะถัƒั‚ ะฒะธะดั–ั‚ะธ ะฒะฐัˆั– ะฟัƒะฑะปะธะบะฐั†ั–ั—, ั‚ะฐะน ะฝะฐัะฟะฐะบ โ€” ะฒั‹ ะนะธั…ะฝั–.", + "block_modal.they_will_know": "ะžะฝะธ ะฒะธะดัั‚, ะพะถ ััƒั‚ ะทะฐะฑะปะพะบะพะฒะฐะฝั–.", + "block_modal.title": "ะ—ะฐะฑะปะพะบะพะฒะฐั‚ะธ ั…ะพัะฝะพะฒะฐั‡ะฐ?", + "block_modal.you_wont_see_mentions": "ะะต ะฑัƒะดะตั‚ะต ะฒะธะดั–ั‚ะธ ะฟัƒะฑะปะธะบะฐั†ั–ั— ั‚ะฐะน ัะฟะพะผะธะฝะบั‹ ััŒะพะณะพ ั…ะพัะฝะพะฒะฐั‡ะฐ.", + "boost_modal.combo": "ะœะพะถะตั‚ะต ะบะปั‹ะฝั†ะฝัƒั‚ะธ {combo} ะดั€ัƒะณั‹ะน ั€ะฐะท ะพะฑั‹ ัะตัะต ะฟั€ะพะฟัƒัั‚ะธั‚ะธ", + "bundle_column_error.copy_stacktrace": "ะฃะบะพะฟั–ั€ะพะฒะฐั‚ะธ ะทะฒั–ั‚ ะทะฐ ั…ั‹ะฑัƒ", + "bundle_column_error.error.body": "ะะต ะณะพะดะฝะธ ััŒะผะต ัƒะบะฐะทะฐั‚ะธ ะทะฐะถะฐะดะฐะฝัƒ ัั‚ะพั€ัƒะฝะบัƒ. ะ“ะพะดะฝะพ ะฑั‹ั‚ะธ ัะฟะพะทะฐะด ั…ั‹ะฑั‹ ัƒ ะฝะฐัˆัƒะผ ัั–ัั‚ะตะผั–, ะฐะฒะฐะดัŒ ะฟั€ะพะฑะปะตะผั‹ ะทัƒะผั–ัะฝะพัั‚ะธ ะฑั€ะฐะฒะทะตั€ะฐ.", + "bundle_column_error.error.title": "ะ˜ะนะพะน!", + "bundle_column_error.network.body": "ะกั‚ะฐะปะฐ ัั ั…ั‹ะฑะฐ ัะบ ััŒะผะต ะฟั€ะพะฑะพะฒะฐะปะธ ะฝะฐะฟะฐั€ะพะฒะฐั‚ะธ ัั‚ะพั€ัƒะฝะบัƒ. ะ“ะพะดะฝะพ ัั ะนัะต ะฑั‹ะปะพ ัั‚ะฐั‚ะธ ัะฟะพะทะฐะด ัะปะฐะฑะพะณะพ ัะฟะพั”ะฝั ะฒะฐัˆะพะณะพ ั–ะฝั‚ะตั€ะฝะตั‚ะฐ, ะฐะฒะฐะดัŒ ัะตั€ะฒะตั€ะฐ.", + "bundle_column_error.network.title": "ะฅั‹ะฑะฐ ัะฟะพั”ะฝั", + "bundle_column_error.retry": "ะŸะพะฟั€ะพะฑัƒะนั‚ะต ะทะฐััŒ", "bundle_column_error.return": "ะ’ะตั€ะฝัƒั‚ะธ ัั ะฝะฐ ะณะพะปะพะฒะฝัƒ", "bundle_column_error.routing.body": "ะะต ะผะพะถะตะผะต ะฝะฐะนั‚ะธ ััะบัƒ ัั‚ะพั€ัƒะฝะบัƒ. ะ‘ะธะทัƒะฒะฝั– ััŒั‚ะต, ะพะถ URL ัƒ ะฐะดั€ะตัะฝะพะผัƒ ัˆะพั€ะธะบะพะฒะธ ั” ะดะพะฑั€ั‹ะน?", "bundle_column_error.routing.title": "404", "bundle_modal_error.close": "ะ—ะฐะฟะตั€ั‚ะธ", "bundle_modal_error.message": "ะจั‚ะพััŒ ัั ะฟะพะบะฐะทะธะปะพ, ะทะฐะบะธะดัŒ ััŒะผะต ะปะฐะดะพะฒะฐะปะธ ัะธััŒ ะบะพะผะฟะพะฝะตะฝั‚.", "bundle_modal_error.retry": "ะŸะพะฟั€ะพะฑะพะฒะฐั‚ะธ ะทะฐััŒ", - "closed_registrations.other_server_instructions": "Mastodon ั” ะดะตั†ะตะฝั‚ั€ะฐะปั–ะทะพะฒะฐะฝะพะฒ ะฟะปะฐั‚ั„ะพั€ะผะพะฒ, ะผะพะถะตั‚ะต ัะธ ัƒั‡ะธะฝะธั‚ะธ ะฟั€ะพั„ั–ะป ะธ ะฝะฐ ะดั€ัƒะณะพะผัƒ ัะตั€ะฒะตั€ะพะฒะธ ั‚ะฐะน ะบะพะผัƒะฝั–ะบะพะฒะฐั‚ะธ ะธะท ัะธะผ." + "closed_registrations.other_server_instructions": "Mastodon ั” ะดะตั†ะตะฝั‚ั€ะฐะปั–ะทะพะฒะฐะฝะพะฒ ะฟะปะฐั‚ั„ะพั€ะผะพะฒ, ะผะพะถะตั‚ะต ัะธ ัƒั‡ะธะฝะธั‚ะธ ะฟั€ะพั„ั–ะป ะธ ะฝะฐ ะดั€ัƒะณะพะผัƒ ัะตั€ะฒะตั€ะพะฒะธ ั‚ะฐะน ะบะพะผัƒะฝั–ะบะพะฒะฐั‚ะธ ะธะท ัะธะผ.", + "closed_registrations_modal.description": "ะ ะฐะท ะฝะต ะผะพะถ ัƒั‡ะธะฝะธั‚ะธ ะฟั€ะพั„ั–ะป ะฝะฐ {domain}, ะฐะนะฑะพ ะฝะต ะผัƒัะธั‚ะต ะผะฐั‚ะธ ะฟั€ะพั„ั–ะป ะธะฟะตะฝ ะฝะฐ ัะตั€ะฒะตั€ะพะฒะธ {domain} ะพะฑั‹ ั…ะพัะฝะพะฒะฐั‚ะธ Mastodon.", + "closed_registrations_modal.find_another_server": "ะะฐะนั‚ะธ ะดั€ัƒะณั‹ะน ัะตั€ะฒะตั€", + "column.about": "ะ—ะฐ ัะฐะนั‚", + "column.blocks": "ะ—ะฐะฑะปะพะบะพะฒะฐะฝั– ั…ะพัะฝะพะฒะฐั‡ั–", + "column.bookmarks": "ะฃัะพะบะพั‡ะตะฝะพั”", + "column.direct": "ะจะตะฟั‚ะฐะฝั", + "column.directory": "ะะธะบะฐั‚ะธ ะฟั€ะพั„ั–ะปั–", + "column.domain_blocks": "ะ—ะฐะฑะปะพะบะพะฒะฐะฝั– ะดะพะผะตะฝั‹", + "column.favourites": "ะฃะฑั€ะฐะฝะพั”", + "column.follow_requests": "ะ—ะฐะฟั€ะพัั‹ ะฝะฐ ะฟัƒะดะฟะธัะบัƒ", + "column.lists": "ะ˜ัะฟะธัั‹", + "column.mutes": "ะกั‚ะธัˆะตะฝั– ั…ะพัะฝะพะฒะฐั‡ั–", + "column.notifications": "ะฃะฑะฒั–ั‰ะตะฝั", + "column.pins": "ะ—ะฐะบั€ั–ะฟะปะตะฝั– ะฟัƒะฑะปะธะบะฐั†ั–ั—", + "column_back_button.label": "ะะฐะทะฐะด", + "column_header.hide_settings": "ะกะฟั€ัั‚ะฐั‚ะธ ัˆั‚ั–ะผะพะฒะฐะฝั", + "column_header.moveLeft_settings": "ะŸะพััƒะฝัƒั‚ะธ ะบะพะปะพะฝะบัƒ ะดะพ ะปั–ะฒะฐ", + "column_header.moveRight_settings": "ะŸะพััƒะฝัƒั‚ะธ ะบะพะปะพะฝะบัƒ ะดะพ ะฟั€ะฐะฒะฐ", + "column_header.pin": "ะ—ะฐะบั€ั–ะฟะธั‚ะธ", + "column_header.show_settings": "ะฃะบะฐะทะฐั‚ะธ ัˆั‚ั–ะผะพะฒะฐะฝั", + "column_header.unpin": "ะฃะดะบั€ั–ะฟะธั‚ะธ", + "column_subheading.settings": "ะจั‚ั–ะผะพะฒะฐะฝั", + "compose.language.change": "ะŸะพะผั–ะฝัั‚ะธ ัะทั‹ะบ", + "compose.language.search": "ะ“ะปัะดะฐั‚ะธ ัะทั‹ะบั‹...", + "compose.published.body": "ะŸะพัั‚ ะพะฟัƒะฑะปะธะบะพะฒะฐะฝั‹ะน.", + "compose.saved.body": "ะŸะพัั‚ ัƒัะพะบะพั‡ะตะฝั‹ะน.", + "compose_form.direct_message_warning_learn_more": "ะงะธั‚ะฐะนั‚ะต ะฑัƒะปัŒัˆะต", + "compose_form.encryption_warning": "ะŸัƒะฑะปะธะบะฐั†ั–ั— ะฝะฐ Mastodon ะฝะต ัˆั–ั„ั€ัƒะฒัƒั‚ ัั. ะะต ัˆั‹ั€ัŒั‚ะต ั‡ัƒั‚ะปะธะฒัƒ ั–ะฝั„ะพั€ะผะฐั†ั–ัŽ ั‡ะตั€ะตะท Mastodon.", + "compose_form.hashtag_warning": "ะกะธััŒ ะฟะพัั‚ ะฝะต ะฑัƒะดะต ัั ะฟะพัะฒะปัั‚ะธ ัƒ ะธัะฟะธัะพะฒะธ ะฟะพ ะณะตัˆั‚ะตา‘ะพะฒะธ, ะฑะพ ะฒัƒะฝ ะฝะต ั” ะฟัƒะฑะปะธั‡ะฝั‹ะน. ะ›ะธัˆะตะบ ะฟัƒะฑะปะธั‡ะฝั– ะฟะพัั‚ั‹ ะฑัƒะดะต ะฒะธะดะบะพ ะทะฐ ะณะตัˆั‚ะตา‘ะพะผ.", + "compose_form.lock_disclaimer": "ะ’ะฐัˆ ะฟั€ะพั„ั–ะป ั” {locked}. ะฅะพั‚ัŒ-ั‚ะบะพ ะผะพะถะต ัั ะฝะฐ ะฒะฐั ะฟัƒะดะฟะธัะฐั‚ะธ, ะพะฑั‹ ะฒะธะดั—ั‚ะธ ะฒะฐัˆั– ะตะบัะบะปัƒะทั–ะฒะฝั– ะฟะพัั‚ั‹.", + "compose_form.lock_disclaimer.lock": "ะทะฐะผะบะฝะตะฝะพ", + "compose_form.placeholder": "ะจั‚ะพ ะฝะพะฒะพะณะพ?", + "compose_form.poll.duration": "ะขั€ั‹ะฒะฐะปะพัั‚ัŒ ัƒะฑะทะฒั–ะดะพะฒะฐะฝั", + "compose_form.poll.multiple": "ะ”ะฐะบัƒะปัŒะบะพ ะฒะฐั€ั–ะฐะฝั‚ัƒะฒ", + "compose_form.poll.option_placeholder": "ะ’ะฐั€ั–ะฐะฝั‚ {number}", + "compose_form.poll.single": "ะฃะฑะตั€ั–ั‚ ั”ะดะตะฝ", + "compose_form.poll.switch_to_multiple": "ะ—ะผั–ะฝะธั‚ะธ ัƒะฑะทะฒั–ะดะพะฒะฐะฝั ะพะฑั‹ ะฟะพะฒะพะปะธั‚ะธ ะดะฐะบัƒะปัŒะบะพ ะฒะฐั€ั–ะฐะฝั‚ัƒะฒ", + "compose_form.poll.switch_to_single": "ะ—ะผั–ะฝะธั‚ะธ ัƒะฑะทะฒั–ะดะพะฒะฐะฝั ะพะฑั‹ ะฟะพะฒะพะปะธั‚ะธ ะปะธัˆะตะบ ั”ะดะตะฝ ะฒะฐั€ั–ะฐะฝั‚", + "compose_form.poll.type": "ะกั‚ั–ะป", + "compose_form.publish": "ะŸัƒะฑะปะธะบะฐั†ั–ั", + "compose_form.publish_form": "ะะพะฒะฐ ะฟัƒะฑะปะธะบะฐั†ั–ั", + "compose_form.reply": "ะฃะดะฟะพะฒั–ะดัŒ", + "copypaste.copy_to_clipboard": "ะšะพะฟั–ั€ะพะฒะฐั‚ะธ ัƒ ะฟะฐะผะฝัั‚ัŒ", + "directory.recently_active": "ะะตะดะฐะฒะฝะพ ะฐะบั‚ั–ะฒะฝั–", + "disabled_account_banner.account_settings": "ะจั‚ั–ะผะพะฒะฐะฝั ะฐะบะฐัƒะฝั‚ะฐ", + "disabled_account_banner.text": "ะ’ะฐัˆ ะฐะบะฐัƒะฝั‚ {disabledAccount} ั€ะฐะท ั” ะฝะตะฐะบั‚ั–ะฒะฝั‹ะน.", + "dismissable_banner.community_timeline": "ะขัƒะน ััƒั‚ ะฝะตะดะฐะฒะฝั– ะฟัƒะฑะปะธะบะฐั†ั–ั— ัƒะด ะฟั€ะพั„ั–ะปัƒะฒ ะฝะฐ ัะตั€ะฒะตั€ะพะฒะธ {domain}." } diff --git a/app/javascript/mastodon/locales/sa.json b/app/javascript/mastodon/locales/sa.json index 99aa46bc89..c3880a6b03 100644 --- a/app/javascript/mastodon/locales/sa.json +++ b/app/javascript/mastodon/locales/sa.json @@ -32,9 +32,7 @@ "account.follow": "เค…เคจเฅเคธเฅเคฐเคฟเคฏเคคเคพเคฎเฅ", "account.followers": "เค…เคจเฅเคธเคฐเฅเคคเคพเคฐเคƒ", "account.followers.empty": "เคจเคพเคฝเคจเฅเคธเคฐเฅเคคเคพเคฐเฅ‹ เคตเคฐเฅเคคเคจเฅเคคเฅ‡", - "account.followers_counter": "{count, plural, one {{counter} เค…เคจเฅเคธเคฐเฅเคคเคพ} two {{counter} เค…เคจเฅเคธเคฐเฅเคคเคพเคฐเฅŒ} other {{counter} เค…เคจเฅเคธเคฐเฅเคคเคพเคฐเคƒ}}", "account.following": "เค…เคจเฅเคธเคฐเคคเคฟ", - "account.following_counter": "{count, plural, one {{counter} เค…เคจเฅเคธเฅƒเคคเคƒ} two {{counter} เค…เคจเฅเคธเฅƒเคคเฅŒ} other {{counter} เค…เคจเฅเคธเฅƒเคคเคพเคƒ}}", "account.follows.empty": "เคจ เค•เฅ‹เคฝเคชเฅเคฏเคจเฅเคธเฅƒเคคเฅ‹ เคตเคฐเฅเคคเคคเฅ‡", "account.go_to_profile": "เคชเฅเคฐเฅ‹เคซเคพเคฏเคฟเคฒเค‚ เค—เคšเฅเค›", "account.hide_reblogs": "@{name} เคฎเคฟเคคเฅเคฐเคธเฅเคฏ เคชเฅเคฐเค•เคพเคถเคจเคพเคจเคฟ เค›เคฟเคฆเฅเคฏเคจเฅเคคเคพเคฎเฅ", @@ -56,7 +54,6 @@ "account.requested_follow": "{name} เคคเฅเคตเคพเคฎเคจเฅเคธเคฐเฅเคคเฅเคฎเคฏเคพเคšเฅ€เคคเฅ", "account.share": "@{name} เคฎเคฟเคคเฅเคฐเคธเฅเคฏ เคตเคฟเคตเคฐเคฃเค‚ เคตเคฟเคญเคพเคœเฅเคฏเคคเคพเคฎเฅ", "account.show_reblogs": "@{name} เคฎเคฟเคคเฅเคฐเคธเฅเคฏ เคชเฅเคฐเค•เคพเคถเคจเคพเคจเคฟ เคฆเฅƒเคถเฅเคฏเคจเฅเคคเคพเคฎเฅ", - "account.statuses_counter": "{count, plural, one {{counter} เคชเคคเฅเคฐเคฎเฅ} two{{counter} เคชเคคเฅเคฐเฅ‡} other {{counter} เคชเคคเฅเคฐเคพเคฃเคฟ}}", "account.unblock": "เคจเคฟเคทเฅ‡เคงเคคเคพ เคจเคถเฅเคฏเคคเคพเคฎเฅ @{name}", "account.unblock_domain": "เคชเฅเคฐเคฆเฅ‡เคถเคจเคฟเคทเฅ‡เคงเคคเคพ เคจเคถเฅเคฏเคคเคพเคฎเฅ {domain}", "account.unblock_short": "เค…เคจเคตเคฐเฅเคจเฅเคงเคฟ", @@ -499,8 +496,6 @@ "server_banner.about_active_users": "เคตเคฟเค—เคคเฅ‡เคทเฅ เฅฉเฅฆ เคฆเคฟเคจเฅ‡เคทเฅ เคธเคฐเฅเคตเคฐเคฎเคฟเคฎเคฎเฅเคชเคฏเฅเคœเฅเคฏเคฎเคพเคฃเคพ เคœเคจเคพเคƒ (เคฎเคพเคธเคฟเค•เคธเค•เฅเคฐเคฟเคฏเฅ‹เคชเคญเฅ‹เค•เฅเคคเคพเคฐเคƒ)", "server_banner.active_users": "เคธเค•เฅเคฐเคฟเคฏเฅ‹เคชเคญเฅ‹เค•เฅเคคเคพเคฐเคƒ", "server_banner.administered_by": "เค‡เคคเฅเคฏเคจเฅ‡เคจ เค…เคงเคฟเค•เฅƒเคคเคƒ : ", - "server_banner.introduction": "{domain} {mastodon} เค‡เคคเฅเคฏเคจเฅ‡เคจ เคธเคพเคฎเคฐเฅเคฅเคฟเคคเฅ‹ เคตเคฟเค•เฅ‡เคจเฅเคฆเฅเคฐเฅ€เคฏเคธเคพเคฎเคพเคœเคฟเค•เคœเคพเคฒเค•เคฐเฅเคฎเคฃเฅ‹เค‚เคฝเคถเฅ‹เคฝเคธเฅเคคเคฟเฅค", - "server_banner.learn_more": "เค…เคงเคฟเค•เค‚ เคœเฅเคžเคพเคฏเคคเคพเคฎเฅ", "server_banner.server_stats": "เคธเคฐเฅเคตเคฐเคƒ เคธเฅเคฅเคฟเคคเคฟเคตเคฟเคทเคฏเค•เคพเคจเคฟ :", "sign_in_banner.create_account": "เคธเคฎเคฏเค‚ เคธเค‚เคธเฅƒเคœ", "sign_in_banner.sign_in": "เคธเคฎเฅเคชเฅเคฐเคตเฅ‡เคถเค‚ เค•เฅเคฐเฅ", diff --git a/app/javascript/mastodon/locales/sc.json b/app/javascript/mastodon/locales/sc.json index 1a5f2ef0f3..8955573737 100644 --- a/app/javascript/mastodon/locales/sc.json +++ b/app/javascript/mastodon/locales/sc.json @@ -26,9 +26,7 @@ "account.follow": "Sighi", "account.followers": "Sighiduras", "account.followers.empty": "Nemos sighit ancora custa persone.", - "account.followers_counter": "{count, plural, one {{counter} sighidura} other {{counter} sighiduras}}", "account.following": "Sighende", - "account.following_counter": "{count, plural, one {Sighende a {counter}} other {Sighende a {counter}}}", "account.follows.empty": "Custa persone non sighit ancora a nemos.", "account.hide_reblogs": "Cua is cumpartziduras de @{name}", "account.in_memoriam": "In memoriam.", @@ -47,7 +45,6 @@ "account.requested_follow": "{name} at dimandadu de ti sighire", "account.share": "Cumpartzi su profilu de @{name}", "account.show_reblogs": "Ammustra is cumpartziduras de @{name}", - "account.statuses_counter": "{count, plural, one {{counter} publicatzione} other {{counter} publicatziones}}", "account.unblock": "Isbloca a @{name}", "account.unblock_domain": "Isbloca su domรฌniu {domain}", "account.unendorse": "Non cussiges in su profilu", @@ -375,7 +372,6 @@ "search_results.hashtags": "Etichetas", "search_results.statuses": "Publicatziones", "server_banner.administered_by": "Amministradu dae:", - "server_banner.learn_more": "ร€teras informatziones", "server_banner.server_stats": "Istatรฌsticas de su serbidore:", "sign_in_banner.sign_in": "Sign in", "status.admin_account": "Aberi s'interfache de moderatzione pro @{name}", diff --git a/app/javascript/mastodon/locales/sco.json b/app/javascript/mastodon/locales/sco.json index ba62c11f71..397f63fed4 100644 --- a/app/javascript/mastodon/locales/sco.json +++ b/app/javascript/mastodon/locales/sco.json @@ -31,9 +31,7 @@ "account.follow": "Follae", "account.followers": "Follaers", "account.followers.empty": "Naebody follaes this uiser yit.", - "account.followers_counter": "{count, plural, one {{counter} Follaer} other {{counter} Follaers}}", "account.following": "Follaein", - "account.following_counter": "{count, plural, one {{counter} Follaein} other {{counter} Follaein}}", "account.follows.empty": "This uiser disnae follae oniebody yit.", "account.go_to_profile": "Gang tae profile", "account.hide_reblogs": "Dinnae shaw heezes fae @{name}", @@ -53,7 +51,6 @@ "account.requested": "Haudin fir approval. Chap tae cancel follae request", "account.share": "Share @{name}'s profile", "account.show_reblogs": "Shaw heezes frae @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Post} other {{counter} Posts}}", "account.unblock": "Undingie @{name}", "account.unblock_domain": "Undingie domain {domain}", "account.unblock_short": "Undingie", @@ -471,8 +468,6 @@ "server_banner.about_active_users": "Fowk uisin this server in the last 30 days (Monthly Active Uisers)", "server_banner.active_users": "active uisers", "server_banner.administered_by": "Administert bi:", - "server_banner.introduction": "{domain} is pairt o the decentralized social network pooery bi {mastodon}.", - "server_banner.learn_more": "Lairn mair", "server_banner.server_stats": "Server stats:", "sign_in_banner.create_account": "Mak accoont", "sign_in_banner.sign_in": "Sign in", diff --git a/app/javascript/mastodon/locales/si.json b/app/javascript/mastodon/locales/si.json index 4cb81a760c..fbfdfaa659 100644 --- a/app/javascript/mastodon/locales/si.json +++ b/app/javascript/mastodon/locales/si.json @@ -18,13 +18,12 @@ "account.edit_profile": "เถดเทเถญเท’เถšเถฉ เทƒเถ‚เทƒเทŠเถšเถปเถซเถบ", "account.enable_notifications": "@{name} เถดเท… เถšเถปเถฑ เท€เท’เถง เถธเถง เถฏเทเถฑเท”เถธเทŠ เถฏเท™เถฑเทŠเถฑ", "account.endorse": "เถดเทเถญเท’เถšเถฉเท™เท„เท’ เท€เท’เทเทšเท‚เทเถ‚เถœเถบ", + "account.featured_tags.last_status_at": "เถ…เท€เทƒเทเถฑ เถฝเท’เถดเท’เถบ: {date}", "account.featured_tags.last_status_never": "เถฝเท’เถดเท’ เถฑเทเถญ", "account.follow": "เถ…เถฑเท”เถœเถธเถฑเถบ", "account.followers": "เถ…เถฑเท”เถœเทเถธเท’เถšเถบเท’เถฑเทŠ", "account.followers.empty": "เถšเท’เทƒเท’เท€เท™เถšเทŠ เถ…เถฑเท”เถœเถธเถฑเถบ เถšเถป เถฑเทเถญ.", - "account.followers_counter": "{count, plural, one {เถ…เถฑเท”เถœเทเถธเท’เถšเถบเท’เถฑเทŠ {counter}} other {เถ…เถฑเท”เถœเทเถธเท’เถšเถบเท’เถฑเทŠ {counter}}}", "account.following": "เถ…เถฑเท”เถœเถธเถฑ", - "account.following_counter": "{count, plural, one {เถ…เถฑเท”เถœเถธเถฑ {counter}} other {เถ…เถฑเท”เถœเถธเถฑ {counter}}}", "account.follows.empty": "เถญเท€เถธเถญเทŠ เถšเท’เทƒเท’เท€เท™เถšเทŠ เถ…เถฑเท”เถœเถธเถฑเถบ เถฑเทœเถšเถปเถบเท’.", "account.go_to_profile": "เถดเทเถญเท’เถšเถฉเถง เถบเถฑเทŠเถฑ", "account.joined_short": "เถ‘เถšเทŠ เท€เท– เถฏเท’เถฑเถบ", @@ -38,7 +37,6 @@ "account.posts_with_replies": "เถฝเท’เถดเท’ เทƒเท„ เถดเท’เท…เท’เถญเท”เถปเท”", "account.report": "@{name} เท€เทเถปเทŠเถญเท เถšเถปเถฑเทŠเถฑ", "account.share": "@{name} เถœเทš เถดเทเถญเท’เถšเถฉ เถถเท™เถฏเทเถœเถฑเทŠเถฑ", - "account.statuses_counter": "{count, plural, one {เถฝเท’เถดเท’ {counter}} other {เถฝเท’เถดเท’ {counter}}}", "account.unblock": "@{name} เถ…เถฑเท€เท„เท’เถป เถšเถปเถฑเทŠเถฑ", "account.unblock_domain": "{domain} เท€เทƒเถธ เถ…เถฑเท€เท„เท’เถป เถšเถปเถฑเทŠเถฑ", "account.unblock_short": "เถ…เถฑเท€เท„เท’เถป", @@ -104,6 +102,7 @@ "compose_form.poll.duration": "เถธเถญ เท€เท’เถธเทƒเท“เถธเทš เถšเทเถฝเถบ", "compose_form.poll.switch_to_multiple": "เถญเทšเถปเท“เถธเทŠ เถšเท’เท„เท’เถดเถบเถšเถง เถธเถญ เท€เท’เถธเทƒเท”เถธ เท€เท™เถฑเทƒเทŠ เถšเถปเถฑเทŠเถฑ", "compose_form.poll.switch_to_single": "เถญเถฑเท’ เถญเทšเถปเท“เถธเถšเถง เถธเถญ เท€เท’เถธเทƒเท”เถธ เท€เท™เถฑเทƒเทŠ เถšเถปเถฑเทŠเถฑ", + "compose_form.publish": "เถดเทŠโ€เถปเถšเทเทเถฑเถบ", "compose_form.publish_form": "เถฑเท€ เถฝเท’เถดเท’เถบ", "compose_form.spoiler.marked": "เถ…เถฑเทŠเถญเถปเทŠเถœเถญ เถ…เท€เท€เทเถฏเถบ เถ‰เท€เถญเทŠ เถšเถปเถฑเทŠเถฑ", "compose_form.spoiler.unmarked": "เถ…เถฑเทŠเถญเถปเทŠเถœเถญ เถ…เท€เท€เทเถฏเถบเถšเทŠ เถ‘เถšเทŠ เถšเถปเถฑเทŠเถฑ", @@ -154,6 +153,7 @@ "empty_column.bookmarked_statuses": "เถ”เถถ เทƒเถญเท”เท€ เถดเทœเถญเทŠเถบเทœเถธเท” เถญเถถเถฑ เถฝเถฏ เถฝเท’เถดเท’ เถšเท’เทƒเท’เท€เถšเทŠ เถฑเทเถญ. เถ”เถถ เถดเทœเถญเทŠเถบเทœเถธเท”เท€เถšเทŠ เถญเถถเถฑ เท€เท’เถง, เถ‘เถบ เถธเท™เท„เท’ เถฏเท’เทƒเทŠเท€เถฑเท” เถ‡เถญ.", "empty_column.domain_blocks": "เถ…เท€เท„เท’เถป เถšเถปเถฑ เถฝเถฏ เท€เทƒเถธเทŠ เถฑเทเถญ.", "empty_column.explore_statuses": "เถฏเทเถฑเทŠ เถšเท’เทƒเท’เท€เถšเทŠ เถฑเทเถนเท”เถปเท” เถฑเทœเท€เทš. เถดเทƒเท”เท€ เถฑเทเท€เถญ เถดเถปเท“เถšเทŠเท‚เท เถšเถปเถฑเทŠเถฑ!", + "empty_column.favourited_statuses": "เถ”เถถ เทƒเถญเท”เท€ เถดเทŠโ€เถปเท’เถบเถญเถธ เถฝเท’เถดเท’ เถšเท’เทƒเท’เท€เถšเทŠ เถฑเทเถญ. เถ”เถถ เถบเถธเถšเถง เถดเทŠโ€เถปเท’เถบ เถšเท… เท€เท’เถง เถ‘เถบ เถธเท™เท„เท’ เถดเท™เถฑเทŠเท€เถฑเท” เถ‡เถญ.", "empty_column.follow_requests": "เถ”เถถเถง เถญเท€เถธเถญเทŠ เถ…เถฑเท”เถœเถธเถฑ เถ‰เถฝเทŠเถฝเท“เถธเทŠ เถฝเทเถถเท“ เถฑเทเถญ. เถ‰เถฝเทŠเถฝเท“เถธเถšเทŠ เถฝเทเถถเท”เถซเท” เท€เท’เถง, เถ‘เถบ เถธเท™เท„เท’ เถดเท™เถฑเทŠเท€เถฑเท” เถ‡เถญ.", "empty_column.home": "เถธเท”เถฝเทŠ เถดเท’เถงเท”เท€ เท„เท’เทƒเทŠ เถบ! เถธเท™เถบ เถดเท’เถปเท€เท“เถธเถง เถถเทœเท„เท เถดเท”เถฏเทŠเถœเถฝเถบเท’เถฑเทŠ เถ…เถฑเท”เถœเถธเถฑเถบ เถšเถปเถฑเทŠเถฑ.", "empty_column.lists": "เถ”เถถเถง เถญเท€เถธเถญเทŠ เถฝเทเถบเท’เทƒเทŠเถญเท” เถšเท’เทƒเท’เท€เถšเทŠ เถฑเทเถญ. เถ”เถถ เถ‘เถšเถšเทŠ เทƒเทเถฏเถฑ เท€เท’เถง, เถ‘เถบ เถธเท™เท„เท’ เถดเท™เถฑเทŠเท€เถฑเท” เถ‡เถญ.", @@ -205,6 +205,7 @@ "interaction_modal.on_this_server": "เถธเท™เถธ เทƒเทšเท€เทเถฏเทเถบเถšเถบเท™เท„เท’", "interaction_modal.title.favourite": "{name}เถœเทš เถฝเท’เถดเท’เถบ เถดเทŠโ€เถปเท’เถบ เถšเถปเถฑเทŠเถฑ", "interaction_modal.title.follow": "{name} เถ…เถฑเท”เถœเถธเถฑเถบ", + "interaction_modal.title.reply": "{name}เถœเทš เถฝเท’เถดเท’เถบเถง เถดเท’เท…เท’เถญเท”เถปเท”", "intervals.full.days": "{number, plural, one {เถฏเท€เทƒเทŠ #} other {เถฏเท€เทƒเทŠ #}}", "intervals.full.hours": "{number, plural, one {เถดเทเถบ #} other {เถดเทเถบ #}}", "intervals.full.minutes": "{number, plural, one {เท€เท’เถฑเทเถฉเท’ #} other {เท€เท’เถฑเทเถฉเท’ #}}", @@ -239,6 +240,7 @@ "lists.delete": "เถฝเทเถบเท’เทƒเทŠเถญเท”เท€ เถธเถšเถฑเทŠเถฑ", "lists.edit": "เถฝเทเถบเท’เทƒเทŠเถญเท”เท€ เทƒเถ‚เทƒเทŠเถšเถปเถซเถบ", "lists.edit.submit": "เทƒเท’เถปเทเทƒเท’เถบ เทƒเถ‚เทเทเถฐเถฑเถบ", + "lists.new.create": "เถ‘เถšเถญเท”", "lists.new.title_placeholder": "เถฑเท€ เถฝเทเถบเท’เทƒเทŠเถญเท”เท€เทš เทƒเท’เถปเทเทƒเท’เถบ", "lists.replies_policy.list": "เถฝเทเถบเท’เทƒเทŠเถญเท”เท€เทš เทƒเทเถธเทเถขเท’เถšเถบเท’เถฑเทŠ", "lists.replies_policy.none": "เถšเท’เทƒเท’เท€เท™เถšเทŠ เถฑเทเถญ", @@ -266,6 +268,7 @@ "navigation_bar.search": "เทƒเทœเถบเถฑเทŠเถฑ", "navigation_bar.security": "เถ†เถปเถšเทŠเท‚เทเท€", "not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.", + "notification.favourite": "{name} เถ”เถถเถœเทš เถฝเท’เถดเท’เถบเถง เถดเทŠโ€เถปเท’เถบ เถšเท…เท", "notification.follow": "{name} เถ”เถถเท€ เถ…เถฑเท”เถœเถธเถฑเถบ เถšเท…เท", "notification.mention": "{name} เถ”เถถเท€ เทƒเถณเท„เถฑเทŠ เถšเถป เถ‡เถญ", "notification.own_poll": "เถ”เถถเถœเทš เถธเถญ เท€เท’เถธเทƒเท”เถธ เถฑเท’เถธเถบเท’", @@ -389,12 +392,12 @@ "search_results.statuses": "เถฝเท’เถดเท’", "search_results.title": "{q} เทƒเทœเถบเถฑเทŠเถฑ", "server_banner.active_users": "เทƒเถšเทŠโ€เถปเท’เถบ เถดเถปเท’เทเทŠโ€เถปเท“เถฝเถšเถบเท’เถฑเทŠ", - "server_banner.learn_more": "เถญเท€ เถฏเทเถฑเถœเถฑเทŠเถฑ", "sign_in_banner.create_account": "เถœเท’เถซเท”เถธเถšเทŠ เทƒเทเถฏเถฑเทŠเถฑ", "sign_in_banner.sign_in": "เถดเท’เท€เท’เทƒเท™เถฑเทŠเถฑ", "status.admin_status": "เถธเท™เถธ เถฝเท’เถดเท’เถบ เถธเทเถฏเท’เท„เถญเทŠเถšเถปเถซ เถ…เถญเท”เถปเท”เถธเท”เท„เท”เถซเถญเท™เท„เท’ เถ…เถปเท’เถฑเทŠเถฑ", "status.block": "@{name} เถ…เท€เท„เท’เถป", "status.bookmark": "เถดเทœเถญเทŠเถบเทœเถธเท”เท€เถšเทŠ", + "status.copy": "เถฝเท’เถดเท’เถบเถง เทƒเถถเทเถณเท’เถบเทš เถดเท’เถงเถดเถญเถšเทŠ", "status.delete": "เถธเถšเถฑเทŠเถฑ", "status.detailed_status": "เท€เท’เทƒเทŠเถญเถปเทเถญเทŠเถธเถš เทƒเถ‚เท€เทเถฏ เถฏเทเถšเทŠเถธ", "status.edit": "เทƒเถ‚เทƒเทŠเถšเถปเถซเถบ", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index d143fda52b..ed877f7667 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -35,9 +35,7 @@ "account.follow_back": "Sledovaลฅ spรคลฅ", "account.followers": "Sledovatelia", "account.followers.empty": "Tento รบฤet eลกte nikto nesleduje.", - "account.followers_counter": "{count, plural, one {{counter} sledujรบci รบฤet} few {{counter} sledujรบce รบฤty} many {{counter} sledujรบcich รบฤtov} other {{counter} sledujรบcich รบฤtov}}", "account.following": "Sledovanรฝ รบฤet", - "account.following_counter": "{count, plural, one {{counter} sledovanรฝ รบฤet} few {{counter} sledovanรฉ รบฤty} many {{counter} sledovanรฝch รบฤtov} other {{counter} sledovanรฝch รบฤtov}}", "account.follows.empty": "Tento รบฤet eลกte nikoho nesleduje.", "account.go_to_profile": "Prejsลฅ na profil", "account.hide_reblogs": "Skryลฅ zdieฤพania od @{name}", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} vรกs chce sledovaลฅ", "account.share": "Zdieฤพaj profil @{name}", "account.show_reblogs": "Zobrazovaลฅ zdieฤพania od @{name}", - "account.statuses_counter": "{count, plural, one {{counter} prรญspevok} few {{counter} prรญspevky} many {{counter} prรญspevkov} other {{counter} prรญspevkov}}", "account.unblock": "Odblokovaลฅ @{name}", "account.unblock_domain": "Odblokovaลฅ domรฉnu {domain}", "account.unblock_short": "Odblokovaลฅ", @@ -91,7 +88,10 @@ "audio.hide": "Skryลฅ zvuk", "block_modal.show_less": "Zobraziลฅ menej", "block_modal.show_more": "Zobraziลฅ viac", + "block_modal.they_cant_mention": "Nemรดลพu ลฅa spomenรบลฅ, alebo nasledovaลฅ.", + "block_modal.they_will_know": "Mรดลพu vidieลฅ, ลพe sรบ zablokovanรญ/รฝ.", "block_modal.title": "Blokovaลฅ uลพรญvateฤพa?", + "block_modal.you_wont_see_mentions": "Neuvidรญลก prรญspevky, ktorรฉ ich spomรญnajรบ.", "boost_modal.combo": "Nabudรบce mรดลพete preskoฤiลฅ stlaฤenรญm {combo}", "bundle_column_error.copy_stacktrace": "Kopรญrovaลฅ chybovรบ hlรกลกku", "bundle_column_error.error.body": "Poลพadovanรบ strรกnku nebolo moลพnรฉ vykresliลฅ. Mรดลพe to byลฅ spรดsobenรฉ chybou v naลกom kรณde alebo problรฉmom s kompatibilitou prehliadaฤa.", @@ -246,6 +246,7 @@ "empty_column.list": "Tento zoznam je zatiaฤพ prรกzdny. Keฤ ale ฤlenovia tohoto zoznamu uverejnia novรฉ prรญspevky, objavia sa tu.", "empty_column.lists": "Zatiaฤพ nemรกte ลพiadne zoznamy. Keฤ nejakรฝ vytvorรญte, zobrazรญ sa tu.", "empty_column.mutes": "Zatiaฤพ ste si nikoho nestรญลกili.", + "empty_column.notification_requests": "Vลกetko ฤistรฉ! Niฤ tu nieje. Keฤ dostaneลก novรฉ oboznรกmenia, zobrazia sa tu podฤพa tvojich nastavenรญ.", "empty_column.notifications": "Zatiaฤพ nemรกte ลพiadne upozornenia. Zaฤnรบ vรกm pribรบdaลฅ, keฤ s vami zaฤnรบ interagovaลฅ ostatnรญ.", "empty_column.public": "Zatiaฤพ tu niฤ nie je. Napรญลกte nieฤo verejnรฉ alebo zaฤnite sledovaลฅ รบฤty z inรฝch serverov, aby tu nieฤo pribudlo.", "error.unexpected_crash.explanation": "Pre chybu v naลกom kรณde alebo problรฉm s kompatibilitou prehliadaฤa nebolo tรบto strรกnku moลพnรฉ zobraziลฅ sprรกvne.", @@ -293,6 +294,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Tento profil je podobnรฝ profilom, ktorรฉ ste nedรกvno zaฤali sledovaลฅ.", "follow_suggestions.personalized_suggestion": "Prispรดsobenรฝ nรกvrh", "follow_suggestions.popular_suggestion": "Obฤพรบbenรฝ nรกvrh", + "follow_suggestions.popular_suggestion_longer": "Populรกrne na {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Podobnรฉ profilom, ktorรฉ si nedรกvno nasledoval/a", "follow_suggestions.view_all": "Zobraziลฅ vลกetky", "follow_suggestions.who_to_follow": "Koho sledovaลฅ", "followed_tags": "Sledovanรฉ hashtagy", @@ -388,6 +391,7 @@ "limited_account_hint.action": "Aj tak zobraziลฅ profil", "limited_account_hint.title": "Tento profil bol skrytรฝ sprรกvcami servera {domain}.", "link_preview.author": "Autor: {name}", + "link_preview.more_from_author": "Viac od {name}", "lists.account.add": "Pridaลฅ do zoznamu", "lists.account.remove": "Odstrรกniลฅ zo zoznamu", "lists.delete": "Vymazaลฅ zoznam", @@ -408,6 +412,7 @@ "moved_to_account_banner.text": "Vรกลก รบฤet {disabledAccount} je momentรกlne deaktivovanรฝ, pretoลพe ste sa presunuli na {movedToAccount}.", "mute_modal.hide_from_notifications": "Ukryลฅ z upozornenรญ", "mute_modal.hide_options": "Skryลฅ moลพnosti", + "mute_modal.indefinite": "Pokiaฤพ ich neodtรญลกim", "mute_modal.show_options": "Zobraziลฅ moลพnosti", "mute_modal.title": "Stรญลกiลฅ uลพรญvateฤพa?", "navigation_bar.about": "O tomto serveri", @@ -442,9 +447,15 @@ "notification.follow": "{name} vรกs sleduje", "notification.follow_request": "{name} vรกs ลพiada sledovaลฅ", "notification.mention": "{name} vรกs spomรญna", + "notification.moderation-warning.learn_more": "Zisti viac", + "notification.moderation_warning.action_disable": "Tvoj รบฤet bol vypnutรฝ.", + "notification.moderation_warning.action_silence": "Tvoj รบฤet bol obmedzenรฝ.", + "notification.moderation_warning.action_suspend": "Tvoj รบฤet bol pozastavenรฝ.", "notification.own_poll": "Vaลกa anketa sa skonฤila", "notification.poll": "Anketa, v ktorej ste hlasovali, sa skonฤila", "notification.reblog": "{name} zdieฤพa vรกลก prรญspevok", + "notification.relationships_severance_event": "Stratenรฉ prepojenia s {name}", + "notification.relationships_severance_event.account_suspension": "Sprรกvca z {from} pozastavil/a {target}, ฤo znamenรก, ลพe od nich viac nemรดลพeลก dostรกvaลฅ aktualizรกcie, alebo s nimi interaktovaลฅ.", "notification.relationships_severance_event.learn_more": "Zisti viac", "notification.status": "{name} uverejลˆuje nieฤo novรฉ", "notification.update": "{name} upravuje prรญspevok", @@ -487,6 +498,7 @@ "notifications.policy.filter_new_accounts_title": "Novรฉ รบฤty", "notifications.policy.filter_not_followers_title": "ฤฝudia, ktorรญ ลฅa nenasledujรบ", "notifications.policy.filter_not_following_title": "ฤฝudia, ktorรฝch nenasledujeลก", + "notifications.policy.filter_private_mentions_title": "Nevyลพiadanรฉ priame spomenutia", "notifications.policy.title": "Filtrovaลฅ oznรกmenia odโ€ฆ", "notifications_permission_banner.enable": "Povoliลฅ upozornenia na ploche", "notifications_permission_banner.how_to_control": "Ak chcete dostรกvaลฅ upozornenia, keฤ Mastodon nie je otvorenรฝ, povoฤพte upozornenia na ploche. Po ich zapnutรญ mรดลพete presne kontrolovaลฅ, ktorรฉ typy interakciรญ generujรบ upozornenia na ploche, a to prostrednรญctvom tlaฤidla {icon} vyลกลกie.", @@ -644,13 +656,10 @@ "server_banner.about_active_users": "ฤฝudia pouลพรญvajรบci tento server za poslednรฝch 30 dnรญ (aktรญvni pouลพรญvatelia za mesiac)", "server_banner.active_users": "Aktรญvne รบฤty", "server_banner.administered_by": "Sprรกva servera:", - "server_banner.introduction": "{domain} je sรบฤasลฅou decentralizovanej sociรกlnej siete vyuลพรญvajรบcej technolรณgiu {mastodon}.", - "server_banner.learn_more": "Viac informรกciรญ", "server_banner.server_stats": "ล tatistiky servera:", "sign_in_banner.create_account": "Vytvoriลฅ รบฤet", "sign_in_banner.sign_in": "Prihlรกsiลฅ sa", "sign_in_banner.sso_redirect": "Prihlรกsenie alebo registrรกcia", - "sign_in_banner.text": "Prihlรกste sa, aby ste mohli sledovaลฅ profily alebo hashtagy, hviezdiฤkovaลฅ, zdieฤพaลฅ a odpovedaลฅ na prรญspevky. Mรดลพete tieลพ komunikovaลฅ zo svojho รบฤtu na inom serveri.", "status.admin_account": "Moderovaลฅ @{name}", "status.admin_domain": "Moderovaลฅ {domain}", "status.admin_status": "Moderovaลฅ prรญspevok", diff --git a/app/javascript/mastodon/locales/sl.json b/app/javascript/mastodon/locales/sl.json index ed4fa8dfaf..2a3d74a80f 100644 --- a/app/javascript/mastodon/locales/sl.json +++ b/app/javascript/mastodon/locales/sl.json @@ -35,9 +35,9 @@ "account.follow_back": "Sledi nazaj", "account.followers": "Sledilci", "account.followers.empty": "Nihฤe ne sledi temu uporabniku.", - "account.followers_counter": "{count, plural, one {ima {counter} sledilca} two {ima {counter} sledilca} few {ima {counter} sledilce} other {ima {counter} sledilcev}}", + "account.followers_counter": "{count, plural, one {{counter} sledilec} two {{counter} sledilca} few {{counter} sledilci} other {{counter} sledilcev}}", "account.following": "Sledim", - "account.following_counter": "{count, plural, one {sledi {count} osebi} two {sledi {count} osebama} few {sledi {count} osebam} other {sledi {count} osebam}}", + "account.following_counter": "{count, plural, one {{counter} sleden} two {{counter} sledena} few {{counter} sledeni} other {{counter} sledenih}}", "account.follows.empty": "Ta uporabnik ลกe ne sledi nikomur.", "account.go_to_profile": "Pojdi na profil", "account.hide_reblogs": "Skrij izpostavitve od @{name}", @@ -63,7 +63,7 @@ "account.requested_follow": "{name} vam ลพeli slediti", "account.share": "Deli profil osebe @{name}", "account.show_reblogs": "Pokaลพi izpostavitve osebe @{name}", - "account.statuses_counter": "{count, plural, one {{count} objava} two {{count} objavi} few {{count} objave} other {{count} objav}}", + "account.statuses_counter": "{count, plural, one {{counter} objava} two {{counter} objavi} few {{counter} objave} other {{counter} objav}}", "account.unblock": "Odblokiraj @{name}", "account.unblock_domain": "Odblokiraj domeno {domain}", "account.unblock_short": "Odblokiraj", @@ -308,6 +308,8 @@ "follow_requests.unlocked_explanation": "ฤŒeprav vaลก raฤun ni zaklenjen, zaposleni pri {domain} menijo, da bi morda ลพeleli pregledati zahteve za sledenje teh raฤunov roฤno.", "follow_suggestions.curated_suggestion": "Izbor osebja", "follow_suggestions.dismiss": "Ne pokaลพi veฤ", + "follow_suggestions.featured_longer": "Osebno izbrala ekipa {domain}", + "follow_suggestions.friends_of_friends_longer": "Priljubljeno med osebami, ki jim sledite", "follow_suggestions.hints.featured": "Ta profil so izbrali skrbniki streลพnika {domain}.", "follow_suggestions.hints.friends_of_friends": "Ta profil je priljubljen med osebami, ki jim sledite.", "follow_suggestions.hints.most_followed": "Ta profil na streลพniku {domain} je en izmed najbolj sledenih.", @@ -315,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Ta profil je podoben profilom, ki ste jim nedavno zaฤeli slediti.", "follow_suggestions.personalized_suggestion": "Osebno prilagojen predlog", "follow_suggestions.popular_suggestion": "Priljubljen predlog", + "follow_suggestions.popular_suggestion_longer": "Priljubljeno na {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Podobno profilom, ki ste jim pred kratkim sledili", "follow_suggestions.view_all": "Pokaลพi vse", "follow_suggestions.who_to_follow": "Komu slediti", "followed_tags": "Sledeni kljuฤniki", @@ -410,6 +414,8 @@ "limited_account_hint.action": "Vseeno pokaลพi profil", "limited_account_hint.title": "Profil so moderatorji streลพnika {domain} skrili.", "link_preview.author": "Avtor_ica {name}", + "link_preview.more_from_author": "Veฤ od {name}", + "link_preview.shares": "{count, plural, one {{counter} objava} two {{counter} objavi} few {{counter} objave} other {{counter} objav}}", "lists.account.add": "Dodaj na seznam", "lists.account.remove": "Odstrani s seznama", "lists.delete": "Izbriลกi seznam", @@ -469,6 +475,15 @@ "notification.follow": "{name} vam sledi", "notification.follow_request": "{name} vam ลพeli slediti", "notification.mention": "{name} vas je omenil/a", + "notification.moderation-warning.learn_more": "Veฤ o tem", + "notification.moderation_warning": "Prejeli ste opozorilo moderatorjev", + "notification.moderation_warning.action_delete_statuses": "Nekatere vaลกe objave so odstranjene.", + "notification.moderation_warning.action_disable": "Vaลก raฤun je bil onemogoฤen.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Nekatere vaลกe objave so bile oznaฤene kot obฤutljive.", + "notification.moderation_warning.action_none": "Vaลก raฤun je prejel opozorilo moderatorjev.", + "notification.moderation_warning.action_sensitive": "Vaลกe objave bodo odslej oznaฤene kot obฤutljive.", + "notification.moderation_warning.action_silence": "Vaลก raฤun je bil omejen.", + "notification.moderation_warning.action_suspend": "Vaลก raฤun je bil suspendiran.", "notification.own_poll": "Vaลกa anketa je zakljuฤena", "notification.poll": "Anketa, v kateri ste sodelovali, je zakljuฤena", "notification.reblog": "{name} je izpostavila/a vaลกo objavo", @@ -681,13 +696,13 @@ "server_banner.about_active_users": "Osebe, ki so uporabljale ta streลพnik zadnjih 30 dni (dejavni uporabniki meseca)", "server_banner.active_users": "dejavnih uporabnikov", "server_banner.administered_by": "Upravlja:", - "server_banner.introduction": "{domain} je del decentraliziranega druลพbenega omreลพja, ki ga poganja {mastodon}.", - "server_banner.learn_more": "Veฤ o tem", + "server_banner.is_one_of_many": "{domain} je en izmed mnogih neodvisnih streลพnikov Mastodon, ki ga lahko uporabljate za sodelovanje v fediverzumu.", "server_banner.server_stats": "Statistika streลพnika:", "sign_in_banner.create_account": "Ustvari raฤun", + "sign_in_banner.follow_anyone": "Sledite komurkoli iz fediverzuma in vidite vse objave v ฤasovnem vrstnem redu. Brez skritih algoritmov ter brez oglasov in vab za klikanje na vidiku.", + "sign_in_banner.mastodon_is": "Mastodon je najboljลกi naฤin, da ste na tekoฤem z dogajanjem.", "sign_in_banner.sign_in": "Prijava", "sign_in_banner.sso_redirect": "Prijavite ali registrirajte se", - "sign_in_banner.text": "Prijavite se, da sledite profilom ali kljuฤnikom, dodajate med priljubljene, delite z drugimi ter odgovarjate na objave. V interakciji ste lahko tudi iz svojega raฤuna na drugem streลพniku.", "status.admin_account": "Odpri vmesnik za moderiranje za @{name}", "status.admin_domain": "Odpri vmesnik za moderiranje za {domain}", "status.admin_status": "Odpri to objavo v vmesniku za moderiranje", diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json index da35b3d43b..96b7b3fefc 100644 --- a/app/javascript/mastodon/locales/sq.json +++ b/app/javascript/mastodon/locales/sq.json @@ -35,9 +35,9 @@ "account.follow_back": "Ndiqe gjithashtu", "account.followers": "Ndjekรซs", "account.followers.empty": "Kรซtรซ pรซrdorues ende sโ€™e ndjek kush.", - "account.followers_counter": "{count, plural, one {{counter} Ndjekรซs} other {{counter} Ndjekรซs}}", + "account.followers_counter": "{count, plural, one {{counter} ndjekรซs} other {{counter} ndjekรซs}}", "account.following": "Ndjekje", - "account.following_counter": "{count, plural, one {{counter} i Ndjekur} other {{counter} tรซ Ndjekur}}", + "account.following_counter": "{count, plural, one {{counter} i ndjekur} other {{counter} tรซ ndjekur}}", "account.follows.empty": "Ky pรซrdorues ende sโ€™ndjek kรซnd.", "account.go_to_profile": "Kalo te profili", "account.hide_reblogs": "Fshih pรซrforcime nga @{name}", @@ -63,7 +63,7 @@ "account.requested_follow": "{name} ka kรซrkuar tโ€™ju ndjekรซ", "account.share": "Ndajeni profilin e @{name} me tรซ tjerรซt", "account.show_reblogs": "Shfaq pรซrforcime nga @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Mesazh} other {{counter} Mesazhe}}", + "account.statuses_counter": "{count, plural, one {{counter} postim} other {{counter} postime}}", "account.unblock": "Zhbllokoje @{name}", "account.unblock_domain": "Zhblloko pรซrkatรซsinรซ {domain}", "account.unblock_short": "Zhbllokoje", @@ -297,6 +297,7 @@ "filter_modal.select_filter.subtitle": "Pรซrdorni njรซ kategori ekzistuese, ose krijoni njรซ tรซ re", "filter_modal.select_filter.title": "Filtroje kรซtรซ postim", "filter_modal.title.status": "Filtroni njรซ postim", + "filtered_notifications_banner.mentions": "{count, plural, one {pรซrmendje} other {pรซrmendje}}", "filtered_notifications_banner.pending_requests": "Njoftime prej {count, plural, =0 {askujt} one {njรซ personi} other {# vetรซsh}} qรซ mund tรซ njihni", "filtered_notifications_banner.title": "Njoftime tรซ filtruar", "firehose.all": "Krejt", @@ -307,6 +308,8 @@ "follow_requests.unlocked_explanation": "Edhe pse llogaria juaj sโ€™รซshtรซ e kyรงur, ekipi i {domain} mendoi se mund tรซ donit tรซ shqyrtonit dorazi kรซrkesa ndjekjeje prej kรซtyre llogarive.", "follow_suggestions.curated_suggestion": "Zgjedhur nga ekipi", "follow_suggestions.dismiss": "Mos shfaq mรซ", + "follow_suggestions.featured_longer": "Zgjedhur enkas nga ekipi {domain}", + "follow_suggestions.friends_of_friends_longer": "Popullore mes personash qรซ ndiqni", "follow_suggestions.hints.featured": "Ky profil รซshtรซ zgjedhur nga ekipi {domain}.", "follow_suggestions.hints.friends_of_friends": "Ky profil รซshtรซ popullor mes personave qรซ ndiqni.", "follow_suggestions.hints.most_followed": "Ky profil รซshtรซ njรซ nga mรซ tรซ ndjekur nรซ {domain}.", @@ -314,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Ky profil รซshtรซ i ngjashรซm me profile qรซ keni ndjekur tani afรซr.", "follow_suggestions.personalized_suggestion": "Sugjerim i personalizuar", "follow_suggestions.popular_suggestion": "Sugjerim popullor", + "follow_suggestions.popular_suggestion_longer": "Popullore nรซ {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "I ngjashรซm me profile qรซ keni zรซnรซ tรซ ndiqni sรซ fundi", "follow_suggestions.view_all": "Shihni krejt", "follow_suggestions.who_to_follow": "Cilรซt tรซ ndiqen", "followed_tags": "Hashtag-รซ tรซ ndjekur", @@ -409,6 +414,8 @@ "limited_account_hint.action": "Shfaqe profilin sido qoftรซ", "limited_account_hint.title": "Ky profil รซshtรซ fshehur nga moderatorรซt e {domain}.", "link_preview.author": "Nga {name}", + "link_preview.more_from_author": "Mรซ tepรซr nga {name}", + "link_preview.shares": "{count, plural, one {{counter} post} other {{counter} postime}}", "lists.account.add": "Shto nรซ listรซ", "lists.account.remove": "Hiqe nga lista", "lists.delete": "Fshije listรซn", @@ -468,6 +475,15 @@ "notification.follow": "{name} zuri tโ€™ju ndjekรซ", "notification.follow_request": "{name} ka kรซrkuar tโ€™ju ndjekรซ", "notification.mention": "{name} ju ka pรซrmendur", + "notification.moderation-warning.learn_more": "Mรซsoni mรซ tepรซr", + "notification.moderation_warning": "Ju รซshtรซ dhรซnรซ njรซ sinjalizim moderimi", + "notification.moderation_warning.action_delete_statuses": "Disa nga postimet tuaja janรซ hequr.", + "notification.moderation_warning.action_disable": "Llogaria juaj รซshtรซ รงaktivizuar.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Disa prej postimeve tuaja u รซshtรซ vรซnรซ shenjรซ si me spec.", + "notification.moderation_warning.action_none": "Llogaria juaj ka marrรซ njรซ sinjalizim moderimi.", + "notification.moderation_warning.action_sensitive": "Postimeve tuaja do tโ€™u vihet shenjรซ si me spec, nga tani e tutje.", + "notification.moderation_warning.action_silence": "Llogaria juaj รซshtรซ kufizuar.", + "notification.moderation_warning.action_suspend": "Llogaria juaj รซshtรซ pezulluar.", "notification.own_poll": "Pyetรซsori juaj ka pรซrfunduar", "notification.poll": "Ka pรซrfunduar njรซ pyetรซsor ku keni votuar", "notification.reblog": "{name} pรซrforcoi mesazhin tuaj", @@ -680,13 +696,13 @@ "server_banner.about_active_users": "Persona qรซ pรซrdorin kรซtรซ shรซrbyes gjatรซ 30 ditรซve tรซ fundit (Pรซrdorues Mujorรซ Aktivรซ)", "server_banner.active_users": "pรซrdorues aktivรซ", "server_banner.administered_by": "Administruar nga:", - "server_banner.introduction": "{domain} รซshtรซ pjesรซ e rrjetit shoqรซror tรซ decentralizuar tรซ ngritur mbi {mastodon}.", - "server_banner.learn_more": "Mรซsoni mรซ tepรซr", + "server_banner.is_one_of_many": "{domain} รซshtรซ njรซ nga mjaft shรซrbyes tรซ pavarur Mastodon te tรซ cilรซt mund tรซ merrni pjesรซ nรซ Fedivers.", "server_banner.server_stats": "Statistika shรซrbyesi:", "sign_in_banner.create_account": "Krijoni llogari", + "sign_in_banner.follow_anyone": "Ndiqni kรซdo nรซ Fedivers dhe shihni gjithรงka nรซ rend kohor. Pa algortime, apo marifete.", + "sign_in_banner.mastodon_is": "Mastodon-i รซshtรซ rruga mรซ e mirรซ pรซr tรซ ndjekur se รงโ€™ndodh.", "sign_in_banner.sign_in": "Hyni", "sign_in_banner.sso_redirect": "Bรซni hyrjen, ose Regjistrohuni", - "sign_in_banner.text": "Qรซ tรซ ndiqni profile ose hashtagรซ, tโ€™u vini shenjรซ si tรซ parapรซlqyer, tรซ ndani me tรซ tjerรซ dhe tโ€™i ripostoni nรซ postime, bรซni hyrjen nรซ llogari. Mundeni edhe tรซ ndรซrveproni qรซ nga llogaria juaj nรซ njรซ shรซrbyes tjetรซr.", "status.admin_account": "Hap ndรซrfaqe moderimi pรซr @{name}", "status.admin_domain": "Hap ndรซrfaqe moderimi pรซr {domain}", "status.admin_status": "Hape kรซtรซ mesazh te ndรซrfaqja e moderimit", diff --git a/app/javascript/mastodon/locales/sr-Latn.json b/app/javascript/mastodon/locales/sr-Latn.json index 3eea87d5ec..71b69d428a 100644 --- a/app/javascript/mastodon/locales/sr-Latn.json +++ b/app/javascript/mastodon/locales/sr-Latn.json @@ -63,7 +63,7 @@ "account.requested_follow": "{name} je zatraลพio da vas prati", "account.share": "Podeli profil korisnika @{name}", "account.show_reblogs": "Prikaลพi podrลพavanja od korisnika @{name}", - "account.statuses_counter": "{count, plural, one {{counter} objavio} few {{counter} objavio} other {{counter} objavio}}", + "account.statuses_counter": "{count, plural, one {{counter} objava} few {{counter} objave} other {{counter} objava}}", "account.unblock": "Odblokiraj korisnika @{name}", "account.unblock_domain": "Odblokiraj domen {domain}", "account.unblock_short": "Odblokiraj", @@ -308,6 +308,8 @@ "follow_requests.unlocked_explanation": "Iako vaลก nalog nije zakljuฤan, osoblje {domain} smatra da biste moลพda ลพeleli da ruฤno pregledate zahteve za praฤ‡enje sa ovih naloga.", "follow_suggestions.curated_suggestion": "Izbor osoblja", "follow_suggestions.dismiss": "Ne prikazuj ponovo", + "follow_suggestions.featured_longer": "Ruฤno odabrao tim {domain}", + "follow_suggestions.friends_of_friends_longer": "Popularno meฤ‘u ljudima koje pratite", "follow_suggestions.hints.featured": "Ovaj profil je ruฤno izabrao tim {domain}.", "follow_suggestions.hints.friends_of_friends": "Ovaj profil je popularan meฤ‘u ljudima koje pratite.", "follow_suggestions.hints.most_followed": "Ovaj profil je jedan od najpraฤ‡enijih na {domain}.", @@ -315,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Ovaj profil je sliฤan profilima koje ste nedavno zapratili.", "follow_suggestions.personalized_suggestion": "Personalizovani predlog", "follow_suggestions.popular_suggestion": "Popularni predlog", + "follow_suggestions.popular_suggestion_longer": "Popularno na {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Sliฤno profilima koje ste nedavno zapratili", "follow_suggestions.view_all": "Prikaลพi sve", "follow_suggestions.who_to_follow": "Koga pratiti", "followed_tags": "Praฤ‡ene heลก oznake", @@ -410,6 +414,8 @@ "limited_account_hint.action": "Ipak prikaลพi profil", "limited_account_hint.title": "Ovaj profil su sakrili moderatori {domain}.", "link_preview.author": "Po {name}", + "link_preview.more_from_author": "Viลกe od {name}", + "link_preview.shares": "{count, plural, one {{counter} objava} few {{counter} objave} other {{counter} objava}}", "lists.account.add": "Dodaj na listu", "lists.account.remove": "Ukloni sa liste", "lists.delete": "Izbriลกi listu", @@ -469,6 +475,15 @@ "notification.follow": "{name} vas je zapratio", "notification.follow_request": "{name} je zatraลพio da vas prati", "notification.mention": "{name} vas je pomenuo", + "notification.moderation-warning.learn_more": "Saznajte viลกe", + "notification.moderation_warning": "Dobili ste moderatorsko upozorenje", + "notification.moderation_warning.action_delete_statuses": "Neke od vaลกih objava su uklonjene.", + "notification.moderation_warning.action_disable": "Vaลก nalog je onemoguฤ‡en.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Neke od vaลกih objava su obeleลพene kao osetljive.", + "notification.moderation_warning.action_none": "Vaลก nalog je dobio moderatorsko upozorenje.", + "notification.moderation_warning.action_sensitive": "Vaลกe objave ฤ‡e ubuduฤ‡e biti oznaฤene kao osetljive.", + "notification.moderation_warning.action_silence": "Vaลก nalog je ograniฤen.", + "notification.moderation_warning.action_suspend": "Vaลก nalog je suspendovan.", "notification.own_poll": "Vaลกa anketa je zavrลกena", "notification.poll": "Zavrลกena je anketa u kojoj ste glasali", "notification.reblog": "{name} je podrลพao vaลกu objavu", @@ -681,13 +696,13 @@ "server_banner.about_active_users": "Ljudi koji su koristili ovaj server u prethodnih 30 dana (meseฤno aktivnih korisnika)", "server_banner.active_users": "aktivnih korisnika", "server_banner.administered_by": "Administrira:", - "server_banner.introduction": "{domain} je deo decentralizovane druลกtvene mreลพe koju pokreฤ‡e {mastodon}.", - "server_banner.learn_more": "Saznajte viลกe", + "server_banner.is_one_of_many": "{domain} je jedan od mnogih nezavisnih Mastodon servera koje moลพete koristiti za uฤeลกcฬe u fediverzumu.", "server_banner.server_stats": "Statistike servera:", "sign_in_banner.create_account": "Napravite nalog", + "sign_in_banner.follow_anyone": "Pratite bilo koga ลกirom fediverzuma i pogledajte sve hronoloลกkim redom. Nema algoritama, reklama ili mamaca za klikove na vidiku.", + "sign_in_banner.mastodon_is": "Mastodon je najbolji naฤin da budete u toku sa onim ลกto se deลกava.", "sign_in_banner.sign_in": "Prijavite se", "sign_in_banner.sso_redirect": "Prijavite se ili se registrujte", - "sign_in_banner.text": "Prijavite se da biste pratili profile ili heลก oznake, oznaฤili objave kao omiljene, delili i odgovarali na njih. Takoฤ‘e moลพete komunicirati sa svog naloga na drugom serveru.", "status.admin_account": "Otvori moderatorsko okruลพenje za @{name}", "status.admin_domain": "Otvori moderatorsko okruลพenje za {domain}", "status.admin_status": "Otvori ovu objavu u moderatorskom okruลพenju", diff --git a/app/javascript/mastodon/locales/sr.json b/app/javascript/mastodon/locales/sr.json index 69f755a0b6..2c4649f9d0 100644 --- a/app/javascript/mastodon/locales/sr.json +++ b/app/javascript/mastodon/locales/sr.json @@ -63,7 +63,7 @@ "account.requested_follow": "{name} ั˜ะต ะทะฐั‚ั€ะฐะถะธะพ ะดะฐ ะฒะฐั ะฟั€ะฐั‚ะธ", "account.share": "ะŸะพะดะตะปะธ ะฟั€ะพั„ะธะป ะบะพั€ะธัะฝะธะบะฐ @{name}", "account.show_reblogs": "ะŸั€ะธะบะฐะถะธ ะฟะพะดั€ะถะฐะฒะฐัšะฐ ะพะด ะบะพั€ะธัะฝะธะบะฐ @{name}", - "account.statuses_counter": "{count, plural, one {{counter} ะพะฑั˜ะฐะฒะธะพ} few {{counter} ะพะฑั˜ะฐะฒะธะพ} other {{counter} ะพะฑั˜ะฐะฒะธะพ}}", + "account.statuses_counter": "{count, plural, one {{counter} ะพะฑั˜ะฐะฒะฐ} few {{counter} ะพะฑั˜ะฐะฒะต} other {{counter} ะพะฑั˜ะฐะฒะฐ}}", "account.unblock": "ะžะดะฑะปะพะบะธั€ะฐั˜ ะบะพั€ะธัะฝะธะบะฐ @{name}", "account.unblock_domain": "ะžะดะฑะปะพะบะธั€ะฐั˜ ะดะพะผะตะฝ {domain}", "account.unblock_short": "ะžะดะฑะปะพะบะธั€ะฐั˜", @@ -308,6 +308,8 @@ "follow_requests.unlocked_explanation": "ะ˜ะฐะบะพ ะฒะฐัˆ ะฝะฐะปะพะณ ะฝะธั˜ะต ะทะฐะบั™ัƒั‡ะฐะฝ, ะพัะพะฑั™ะต {domain} ัะผะฐั‚ั€ะฐ ะดะฐ ะฑะธัั‚ะต ะผะพะถะดะฐ ะถะตะปะตะปะธ ะดะฐ ั€ัƒั‡ะฝะพ ะฟั€ะตะณะปะตะดะฐั‚ะต ะทะฐั…ั‚ะตะฒะต ะทะฐ ะฟั€ะฐั›ะตัšะต ัะฐ ะพะฒะธั… ะฝะฐะปะพะณะฐ.", "follow_suggestions.curated_suggestion": "ะ˜ะทะฑะพั€ ะพัะพะฑั™ะฐ", "follow_suggestions.dismiss": "ะะต ะฟั€ะธะบะฐะทัƒั˜ ะฟะพะฝะพะฒะพ", + "follow_suggestions.featured_longer": "ะ ัƒั‡ะฝะพ ะพะดะฐะฑั€ะฐะพ ั‚ะธะผ {domain}", + "follow_suggestions.friends_of_friends_longer": "ะŸะพะฟัƒะปะฐั€ะฝะพ ะผะตั’ัƒ ั™ัƒะดะธะผะฐ ะบะพั˜ะต ะฟั€ะฐั‚ะธั‚ะต", "follow_suggestions.hints.featured": "ะžะฒะฐั˜ ะฟั€ะพั„ะธะป ั˜ะต ั€ัƒั‡ะฝะพ ะธะทะฐะฑั€ะฐะพ ั‚ะธะผ {domain}.", "follow_suggestions.hints.friends_of_friends": "ะžะฒะฐั˜ ะฟั€ะพั„ะธะป ั˜ะต ะฟะพะฟัƒะปะฐั€ะฐะฝ ะผะตั’ัƒ ั™ัƒะดะธะผะฐ ะบะพั˜ะต ะฟั€ะฐั‚ะธั‚ะต.", "follow_suggestions.hints.most_followed": "ะžะฒะฐั˜ ะฟั€ะพั„ะธะป ั˜ะต ั˜ะตะดะฐะฝ ะพะด ะฝะฐั˜ะฟั€ะฐั›ะตะฝะธั˜ะธั… ะฝะฐ {domain}.", @@ -315,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "ะžะฒะฐั˜ ะฟั€ะพั„ะธะป ั˜ะต ัะปะธั‡ะฐะฝ ะฟั€ะพั„ะธะปะธะผะฐ ะบะพั˜ะต ัั‚ะต ะฝะตะดะฐะฒะฝะพ ะทะฐะฟั€ะฐั‚ะธะปะธ.", "follow_suggestions.personalized_suggestion": "ะŸะตั€ัะพะฝะฐะปะธะทะพะฒะฐะฝะธ ะฟั€ะตะดะปะพะณ", "follow_suggestions.popular_suggestion": "ะŸะพะฟัƒะปะฐั€ะฝะธ ะฟั€ะตะดะปะพะณ", + "follow_suggestions.popular_suggestion_longer": "ะŸะพะฟัƒะปะฐั€ะฝะพ ะฝะฐ {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "ะกะปะธั‡ะฝะพ ะฟั€ะพั„ะธะปะธะผะฐ ะบะพั˜ะต ัั‚ะต ะฝะตะดะฐะฒะฝะพ ะทะฐะฟั€ะฐั‚ะธะปะธ", "follow_suggestions.view_all": "ะŸั€ะธะบะฐะถะธ ัะฒะต", "follow_suggestions.who_to_follow": "ะšะพะณะฐ ะฟั€ะฐั‚ะธั‚ะธ", "followed_tags": "ะŸั€ะฐั›ะตะฝะต ั…ะตัˆ ะพะทะฝะฐะบะต", @@ -410,6 +414,8 @@ "limited_account_hint.action": "ะ˜ะฟะฐะบ ะฟั€ะธะบะฐะถะธ ะฟั€ะพั„ะธะป", "limited_account_hint.title": "ะžะฒะฐั˜ ะฟั€ะพั„ะธะป ััƒ ัะฐะบั€ะธะปะธ ะผะพะดะตั€ะฐั‚ะพั€ะธ {domain}.", "link_preview.author": "ะŸะพ {name}", + "link_preview.more_from_author": "ะ’ะธัˆะต ะพะด {name}", + "link_preview.shares": "{count, plural, one {{counter} ะพะฑั˜ะฐะฒะฐ} few {{counter} ะพะฑั˜ะฐะฒะต} other {{counter} ะพะฑั˜ะฐะฒะฐ}}", "lists.account.add": "ะ”ะพะดะฐั˜ ะฝะฐ ะปะธัั‚ัƒ", "lists.account.remove": "ะฃะบะปะพะฝะธ ัะฐ ะปะธัั‚ะต", "lists.delete": "ะ˜ะทะฑั€ะธัˆะธ ะปะธัั‚ัƒ", @@ -469,6 +475,15 @@ "notification.follow": "{name} ะฒะฐั ั˜ะต ะทะฐะฟั€ะฐั‚ะธะพ", "notification.follow_request": "{name} ั˜ะต ะทะฐั‚ั€ะฐะถะธะพ ะดะฐ ะฒะฐั ะฟั€ะฐั‚ะธ", "notification.mention": "{name} ะฒะฐั ั˜ะต ะฟะพะผะตะฝัƒะพ", + "notification.moderation-warning.learn_more": "ะกะฐะทะฝะฐั˜ั‚ะต ะฒะธัˆะต", + "notification.moderation_warning": "ะ”ะพะฑะธะปะธ ัั‚ะต ะผะพะดะตั€ะฐั‚ะพั€ัะบะพ ัƒะฟะพะทะพั€ะตัšะต", + "notification.moderation_warning.action_delete_statuses": "ะะตะบะต ะพะด ะฒะฐัˆะธั… ะพะฑั˜ะฐะฒะฐ ััƒ ัƒะบะปะพัšะตะฝะต.", + "notification.moderation_warning.action_disable": "ะ’ะฐัˆ ะฝะฐะปะพะณ ั˜ะต ะพะฝะตะผะพะณัƒั›ะตะฝ.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "ะะตะบะต ะพะด ะฒะฐัˆะธั… ะพะฑั˜ะฐะฒะฐ ััƒ ะพะฑะตะปะตะถะตะฝะต ะบะฐะพ ะพัะตั‚ั™ะธะฒะต.", + "notification.moderation_warning.action_none": "ะ’ะฐัˆ ะฝะฐะปะพะณ ั˜ะต ะดะพะฑะธะพ ะผะพะดะตั€ะฐั‚ะพั€ัะบะพ ัƒะฟะพะทะพั€ะตัšะต.", + "notification.moderation_warning.action_sensitive": "ะ’ะฐัˆะต ะพะฑั˜ะฐะฒะต ั›ะต ัƒะฑัƒะดัƒั›ะต ะฑะธั‚ะธ ะพะทะฝะฐั‡ะตะฝะต ะบะฐะพ ะพัะตั‚ั™ะธะฒะต.", + "notification.moderation_warning.action_silence": "ะ’ะฐัˆ ะฝะฐะปะพะณ ั˜ะต ะพะณั€ะฐะฝะธั‡ะตะฝ.", + "notification.moderation_warning.action_suspend": "ะ’ะฐัˆ ะฝะฐะปะพะณ ั˜ะต ััƒัะฟะตะฝะดะพะฒะฐะฝ.", "notification.own_poll": "ะ’ะฐัˆะฐ ะฐะฝะบะตั‚ะฐ ั˜ะต ะทะฐะฒั€ัˆะตะฝะฐ", "notification.poll": "ะ—ะฐะฒั€ัˆะตะฝะฐ ั˜ะต ะฐะฝะบะตั‚ะฐ ัƒ ะบะพั˜ะพั˜ ัั‚ะต ะณะปะฐัะฐะปะธ", "notification.reblog": "{name} ั˜ะต ะฟะพะดั€ะถะฐะพ ะฒะฐัˆัƒ ะพะฑั˜ะฐะฒัƒ", @@ -681,13 +696,13 @@ "server_banner.about_active_users": "ะ‰ัƒะดะธ ะบะพั˜ะธ ััƒ ะบะพั€ะธัั‚ะธะปะธ ะพะฒะฐั˜ ัะตั€ะฒะตั€ ัƒ ะฟั€ะตั‚ั…ะพะดะฝะธั… 30 ะดะฐะฝะฐ (ะผะตัะตั‡ะฝะพ ะฐะบั‚ะธะฒะฝะธั… ะบะพั€ะธัะฝะธะบะฐ)", "server_banner.active_users": "ะฐะบั‚ะธะฒะฝะธั… ะบะพั€ะธัะฝะธะบะฐ", "server_banner.administered_by": "ะะดะผะธะฝะธัั‚ั€ะธั€ะฐ:", - "server_banner.introduction": "{domain} ั˜ะต ะดะตะพ ะดะตั†ะตะฝั‚ั€ะฐะปะธะทะพะฒะฐะฝะต ะดั€ัƒัˆั‚ะฒะตะฝะต ะผั€ะตะถะต ะบะพั˜ัƒ ะฟะพะบั€ะตั›ะต {mastodon}.", - "server_banner.learn_more": "ะกะฐะทะฝะฐั˜ั‚ะต ะฒะธัˆะต", + "server_banner.is_one_of_many": "{domain} ั˜ะต ั˜ะตะดะฐะฝ ะพะด ะผะฝะพะณะธั… ะฝะตะทะฐะฒะธัะฝะธั… Mastodon ัะตั€ะฒะตั€ะฐ ะบะพั˜ะต ะผะพะถะตั‚ะต ะบะพั€ะธัั‚ะธั‚ะธ ะทะฐ ัƒั‡ะตัˆั›ะต ัƒ ั„ะตะดะธะฒะตั€ะทัƒะผัƒ.", "server_banner.server_stats": "ะกั‚ะฐั‚ะธัั‚ะธะบะต ัะตั€ะฒะตั€ะฐ:", "sign_in_banner.create_account": "ะะฐะฟั€ะฐะฒะธั‚ะต ะฝะฐะปะพะณ", + "sign_in_banner.follow_anyone": "ะŸั€ะฐั‚ะธั‚ะต ะฑะธะปะพ ะบะพะณะฐ ัˆะธั€ะพะผ ั„ะตะดะธะฒะตั€ะทัƒะผะฐ ะธ ะฟะพะณะปะตะดะฐั˜ั‚ะต ัะฒะต ั…ั€ะพะฝะพะปะพัˆะบะธะผ ั€ะตะดะพะผ. ะะตะผะฐ ะฐะปะณะพั€ะธั‚ะฐะผะฐ, ั€ะตะบะปะฐะผะฐ ะธะปะธ ะผะฐะผะฐั†ะฐ ะทะฐ ะบะปะธะบะพะฒะต ะฝะฐ ะฒะธะดะธะบัƒ.", + "sign_in_banner.mastodon_is": "Mastodon ั˜ะต ะฝะฐั˜ะฑะพั™ะธ ะฝะฐั‡ะธะฝ ะดะฐ ะฑัƒะดะตั‚ะต ัƒ ั‚ะพะบัƒ ัะฐ ะพะฝะธะผ ัˆั‚ะพ ัะต ะดะตัˆะฐะฒะฐ.", "sign_in_banner.sign_in": "ะŸั€ะธั˜ะฐะฒะธั‚ะต ัะต", "sign_in_banner.sso_redirect": "ะŸั€ะธั˜ะฐะฒะธั‚ะต ัะต ะธะปะธ ัะต ั€ะตะณะธัั‚ั€ัƒั˜ั‚ะต", - "sign_in_banner.text": "ะŸั€ะธั˜ะฐะฒะธั‚ะต ัะต ะดะฐ ะฑะธัั‚ะต ะฟั€ะฐั‚ะธะปะธ ะฟั€ะพั„ะธะปะต ะธะปะธ ั…ะตัˆ ะพะทะฝะฐะบะต, ะพะทะฝะฐั‡ะธะปะธ ะพะฑั˜ะฐะฒะต ะบะฐะพ ะพะผะธั™ะตะฝะต, ะดะตะปะธะปะธ ะธ ะพะดะณะพะฒะฐั€ะฐะปะธ ะฝะฐ ัšะธั…. ะขะฐะบะพั’ะต ะผะพะถะตั‚ะต ะบะพะผัƒะฝะธั†ะธั€ะฐั‚ะธ ัะฐ ัะฒะพะณ ะฝะฐะปะพะณะฐ ะฝะฐ ะดั€ัƒะณะพะผ ัะตั€ะฒะตั€ัƒ.", "status.admin_account": "ะžั‚ะฒะพั€ะธ ะผะพะดะตั€ะฐั‚ะพั€ัะบะพ ะพะบั€ัƒะถะตัšะต ะทะฐ @{name}", "status.admin_domain": "ะžั‚ะฒะพั€ะธ ะผะพะดะตั€ะฐั‚ะพั€ัะบะพ ะพะบั€ัƒะถะตัšะต ะทะฐ {domain}", "status.admin_status": "ะžั‚ะฒะพั€ะธ ะพะฒัƒ ะพะฑั˜ะฐะฒัƒ ัƒ ะผะพะดะตั€ะฐั‚ะพั€ัะบะพะผ ะพะบั€ัƒะถะตัšัƒ", diff --git a/app/javascript/mastodon/locales/sv.json b/app/javascript/mastodon/locales/sv.json index 80c2031128..1833a2cfde 100644 --- a/app/javascript/mastodon/locales/sv.json +++ b/app/javascript/mastodon/locales/sv.json @@ -37,7 +37,6 @@ "account.followers.empty": "Ingen fรถljer denna anvรคndare รคn.", "account.followers_counter": "{count, plural, one {{counter} fรถljare} other {{counter} fรถljare}}", "account.following": "Fรถljer", - "account.following_counter": "{count, plural, one {{counter} fรถljd} other {{counter} fรถljda}}", "account.follows.empty": "Denna anvรคndare fรถljer inte nรฅgon รคn.", "account.go_to_profile": "Gรฅ till profilen", "account.hide_reblogs": "Dรถlj boostar frรฅn @{name}", @@ -297,6 +296,7 @@ "filter_modal.select_filter.subtitle": "Anvรคnd en befintlig kategori eller skapa en ny", "filter_modal.select_filter.title": "Filtrera detta inlรคgg", "filter_modal.title.status": "Filtrera ett inlรคgg", + "filtered_notifications_banner.mentions": "{count, plural, one {omnรคmning} other {omnรคmnanden}}", "filtered_notifications_banner.pending_requests": "Aviseringar frรฅn {count, plural, =0 {ingen} one {en person} other {# personer}} du kanske kรคnner", "filtered_notifications_banner.title": "Filtrerade aviseringar", "firehose.all": "Allt", @@ -307,6 +307,8 @@ "follow_requests.unlocked_explanation": "ร„ven om ditt konto inte รคr lรฅst tror {domain}-personalen att du kanske vill granska dessa fรถljares fรถrfrรฅgningar manuellt.", "follow_suggestions.curated_suggestion": "Utvald av personalen", "follow_suggestions.dismiss": "Visa inte igen", + "follow_suggestions.featured_longer": "Handplockad av {domain}-teamet", + "follow_suggestions.friends_of_friends_longer": "Populรคrt bland personer du fรถljer", "follow_suggestions.hints.featured": "Denna profil รคr handplockad av {domain}-teamet.", "follow_suggestions.hints.friends_of_friends": "Denna profil รคr populรคr bland de personer du fรถljer.", "follow_suggestions.hints.most_followed": "Denna profil รคr en av de mest fรถljda pรฅ {domain}.", @@ -314,6 +316,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Denna profil liknar de profiler som du nyligen har fรถljt.", "follow_suggestions.personalized_suggestion": "Personligt fรถrslag", "follow_suggestions.popular_suggestion": "Populรคrt fรถrslag", + "follow_suggestions.popular_suggestion_longer": "Populรคrt pรฅ {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Liknar profiler du nyligen fรถljde", "follow_suggestions.view_all": "Visa alla", "follow_suggestions.who_to_follow": "Rekommenderade profiler", "followed_tags": "Fรถljda hashtags", @@ -409,6 +413,8 @@ "limited_account_hint.action": "Visa profil รคndรฅ", "limited_account_hint.title": "Denna profil har dolts av {domain}s moderatorer.", "link_preview.author": "Av {name}", + "link_preview.more_from_author": "Mer frรฅn {name}", + "link_preview.shares": "{count, plural, one {{counter} inlรคgg} other {{counter} inlรคgg}}", "lists.account.add": "Lรคgg till i lista", "lists.account.remove": "Ta bort frรฅn lista", "lists.delete": "Radera lista", @@ -468,6 +474,15 @@ "notification.follow": "{name} fรถljer dig", "notification.follow_request": "{name} har begรคrt att fรถlja dig", "notification.mention": "{name} nรคmnde dig", + "notification.moderation-warning.learn_more": "Lรคs mer", + "notification.moderation_warning": "Du har fรฅtt en moderationsvarning", + "notification.moderation_warning.action_delete_statuses": "Nรฅgra av dina inlรคgg har tagits bort.", + "notification.moderation_warning.action_disable": "Ditt konto har inaktiverats.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Nรฅgra av dina inlรคgg har markerats som kรคnsliga.", + "notification.moderation_warning.action_none": "Ditt konto har mottagit en modereringsvarning.", + "notification.moderation_warning.action_sensitive": "Dina inlรคgg kommer markeras som kรคnsliga frรฅn och med nu.", + "notification.moderation_warning.action_silence": "Ditt konto har begrรคnsats.", + "notification.moderation_warning.action_suspend": "Ditt konto har stรคngts av.", "notification.own_poll": "Din rรถstning har avslutats", "notification.poll": "En omrรถstning du rรถstat i har avslutats", "notification.reblog": "{name} boostade ditt inlรคgg", @@ -680,13 +695,10 @@ "server_banner.about_active_users": "Personer som anvรคnt denna server de senaste 30 dagarna (mรฅnatligt aktiva anvรคndare)", "server_banner.active_users": "aktiva anvรคndare", "server_banner.administered_by": "Administrerad av:", - "server_banner.introduction": "{domain} รคr en del av det decentraliserade sociala nรคtverket som drivs av {mastodon}.", - "server_banner.learn_more": "Lรคr dig mer", "server_banner.server_stats": "Serverstatistik:", "sign_in_banner.create_account": "Skapa konto", "sign_in_banner.sign_in": "Logga in", "sign_in_banner.sso_redirect": "Logga in eller registrera dig", - "sign_in_banner.text": "Logga in fรถr att fรถlja profiler eller hashtaggar, favoritmarkera, dela och svara pรฅ inlรคgg. Du kan ocksรฅ interagera med ditt konto pรฅ en annan server.", "status.admin_account": "ร–ppet modereringsgrรคnssnitt fรถr @{name}", "status.admin_domain": "ร–ppet modereringsgrรคnssnitt fรถr @{domain}", "status.admin_status": "ร–ppna detta inlรคgg i modereringsgrรคnssnittet", diff --git a/app/javascript/mastodon/locales/szl.json b/app/javascript/mastodon/locales/szl.json index 43cfc78d5b..34d086eb48 100644 --- a/app/javascript/mastodon/locales/szl.json +++ b/app/javascript/mastodon/locales/szl.json @@ -23,7 +23,6 @@ "account.posts": "Toots", "account.posts_with_replies": "Toots and replies", "account.requested": "Awaiting approval", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account_note.placeholder": "Click to add a note", "column.pins": "Pinned toot", "community.column_settings.media_only": "Media only", diff --git a/app/javascript/mastodon/locales/ta.json b/app/javascript/mastodon/locales/ta.json index ac0984293a..d44ac424f4 100644 --- a/app/javascript/mastodon/locales/ta.json +++ b/app/javascript/mastodon/locales/ta.json @@ -24,9 +24,7 @@ "account.follow_back": "เฎชเฎฟเฎฉเฏเฎคเฏŠเฎŸเฎฐเฏ", "account.followers": "เฎชเฎฟเฎฉเฏเฎคเฏŠเฎŸเฎฐเฏเฎชเฎตเฎฐเฏเฎ•เฎณเฏ", "account.followers.empty": "เฎ‡เฎคเฏเฎตเฎฐเฏˆ เฎฏเฎพเฎฐเฏเฎฎเฏ เฎ‡เฎจเฏเฎค เฎชเฎฏเฎฉเฎฐเฏˆเฎชเฏ เฎชเฎฟเฎฉเฏเฎคเฏŠเฎŸเฎฐเฎตเฎฟเฎฒเฏเฎฒเฏˆ.", - "account.followers_counter": "{count, plural, one {{counter} เฎตเฎพเฎšเฎ•เฎฐเฏ} other {{counter} เฎตเฎพเฎšเฎ•เฎฐเฏเฎ•เฎณเฏ}}", "account.following": "เฎชเฎฟเฎฉเฏเฎคเฏŠเฎŸเฎฐเฏเฎฎเฏ", - "account.following_counter": "{count, plural,one {{counter} เฎšเฎจเฏเฎคเฎพ} other {{counter} เฎšเฎจเฏเฎคเฎพเฎ•เฏเฎ•เฎณเฏ}}", "account.follows.empty": "เฎ‡เฎจเฏเฎค เฎชเฎฏเฎฉเฎฐเฏ เฎ‡เฎคเฏเฎตเฎฐเฏˆ เฎฏเฎพเฎฐเฏˆเฎฏเฏเฎฎเฏ เฎชเฎฟเฎฉเฏเฎคเฏŠเฎŸเฎฐเฎตเฎฟเฎฒเฏเฎฒเฏˆ.", "account.go_to_profile": "เฎšเฏเฎฏเฎตเฎฟเฎตเฎฐเฎคเฏเฎคเฎฟเฎฑเฏเฎ•เฏเฎšเฏ เฎšเฏ†เฎฒเฏเฎฒเฎตเฏเฎฎเฏ", "account.hide_reblogs": "เฎ‡เฎฐเฏเฎจเฏเฎคเฏ เฎŠเฎ•เฏเฎ•เฎฟเฎฏเฎพเฎ• เฎฎเฎฑเฏˆ @{name}", @@ -45,7 +43,6 @@ "account.requested": "เฎ’เฎชเฏเฎชเฏเฎคเฎฒเฏเฎ•เฏเฎ•เฎพเฎ•เฎ•เฏ เฎ•เฎพเฎคเฏเฎคเฎฟเฎฐเฏเฎ•เฏเฎ•เฎฟเฎฑเฎคเฏ. เฎชเฎฟเฎฉเฏเฎคเฏŠเฎŸเฎฐเฏเฎฎเฏ เฎ•เฏ‹เฎฐเฎฟเฎ•เฏเฎ•เฏˆเฎฏเฏˆ เฎจเฏ€เฎ•เฏเฎ• เฎ…เฎดเฏเฎคเฏเฎคเฎตเฏเฎฎเฏ", "account.share": "@{name} เฎ‰เฎŸเฏˆเฎฏ เฎตเฎฟเฎตเฎฐเฎคเฏเฎคเฏˆ เฎชเฎ•เฎฟเฎฐเฏ", "account.show_reblogs": "เฎ•เฎพเฎŸเฏเฎŸเฏ boosts เฎ‡เฎฐเฏเฎจเฏเฎคเฏ @{name}", - "account.statuses_counter": "{count, plural, one {{counter} เฎŸเฏ‚เฎŸเฏ} other {{counter} เฎŸเฏ‚เฎŸเฏเฎŸเฏเฎ•เฎณเฏ}}", "account.unblock": "@{name} เฎฎเฏ€เฎคเฏ เฎคเฎŸเฏˆ เฎจเฏ€เฎ•เฏเฎ•เฏเฎ•", "account.unblock_domain": "{domain} เฎ เฎ•เฎพเฎฃเฏเฎชเฎฟ", "account.unblock_short": "เฎคเฎŸเฏˆเฎฏเฏˆ เฎจเฏ€เฎ•เฏเฎ•เฏ", diff --git a/app/javascript/mastodon/locales/tai.json b/app/javascript/mastodon/locales/tai.json index 825cfb93bd..cad6e8eaa5 100644 --- a/app/javascript/mastodon/locales/tai.json +++ b/app/javascript/mastodon/locales/tai.json @@ -9,7 +9,6 @@ "account.posts": "Huah-siann", "account.posts_with_replies": "Huah-siann kah huรช-รฌng", "account.requested": "Tรกn-thฤi phue-tsรบn", - "account.statuses_counter": "{count, plural, one {{counter} Huah-siann} other {{counter} Huah-siann}}", "account_note.placeholder": "Tiรกm tsiฬt-ฤ“ ka-thiam pฤซ-tsรน", "column.pins": "Tah thรขu-tsรฎng รช huah-siann", "community.column_settings.media_only": "Kan-na muรฎ-thรฉ", diff --git a/app/javascript/mastodon/locales/te.json b/app/javascript/mastodon/locales/te.json index 284102c381..c06472561f 100644 --- a/app/javascript/mastodon/locales/te.json +++ b/app/javascript/mastodon/locales/te.json @@ -25,7 +25,6 @@ "account.requested": "เฐ†เฐฎเฑ‹เฐฆเฐ‚ เฐ•เฑ‹เฐธเฐ‚ เฐตเฑ‡เฐšเฐฟ เฐ‰เฐ‚เฐฆเฐฟ. เฐ…เฐญเฑเฐฏเฐฐเฑเฐฅเฐจเฐจเฑ เฐฐเฐฆเฑเฐฆเฑ เฐšเฑ‡เฐฏเฐกเฐพเฐจเฐฟเฐ•เฐฟ เฐ•เฑเฐฒเฐฟเฐ•เฑ เฐšเฑ‡เฐฏเฐ‚เฐกเฐฟ", "account.share": "@{name} เฐฏเฑŠเฐ•เฑเฐ• เฐชเฑเฐฐเฑŠเฐซเฑˆเฐฒเฑเฐจเฑ เฐชเฐ‚เฐšเฑเฐ•เฑ‹เฐ‚เฐกเฐฟ", "account.show_reblogs": "@{name}เฐจเฑเฐ‚เฐšเฐฟ เฐฌเฑ‚เฐธเฑเฐŸเฑ เฐฒเฐจเฑ เฐšเฑ‚เฐชเฐฟเฐ‚เฐšเฑ", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account.unblock": "@{name}เฐชเฑˆ เฐฌเฑเฐฒเฐพเฐ•เฑ เฐจเฑ เฐคเฑŠเฐฒเฐ—เฐฟเฐ‚เฐšเฑ", "account.unblock_domain": "{domain}เฐจเฑ เฐฆเฐพเฐšเฐตเฐฆเฑเฐฆเฑ", "account.unendorse": "เฐชเฑเฐฐเฑŠเฐซเฑˆเฐฒเฑเฐฒเฑ‹ เฐšเฑ‚เฐชเฐฟเฐ‚เฐšเฐตเฐฆเฑเฐฆเฑ", diff --git a/app/javascript/mastodon/locales/th.json b/app/javascript/mastodon/locales/th.json index 379aebbb1f..64abb394bf 100644 --- a/app/javascript/mastodon/locales/th.json +++ b/app/javascript/mastodon/locales/th.json @@ -158,7 +158,7 @@ "compose_form.poll.option_placeholder": "เธ•เธฑเธงเน€เธฅเธทเธญเธ {number}", "compose_form.poll.single": "เน€เธฅเธทเธญเธเธญเธขเนˆเธฒเธ‡เนƒเธ”เธญเธขเนˆเธฒเธ‡เธซเธ™เธถเนˆเธ‡", "compose_form.poll.switch_to_multiple": "เน€เธ›เธฅเธตเนˆเธขเธ™เธเธฒเธฃเธชเธณเธฃเธงเธˆเธ„เธงเธฒเธกเธ„เธดเธ”เน€เธซเน‡เธ™เน€เธ›เน‡เธ™เธญเธ™เธธเธเธฒเธ•เธซเธฅเธฒเธขเธ•เธฑเธงเน€เธฅเธทเธญเธ", - "compose_form.poll.switch_to_single": "เน€เธ›เธฅเธตเนˆเธขเธ™เธเธฒเธฃเธชเธณเธฃเธงเธˆเธ„เธงเธฒเธกเธ„เธดเธ”เน€เธซเน‡เธ™เน€เธ›เน‡เธ™เธญเธ™เธธเธเธฒเธ•เธ•เธฑเธงเน€เธฅเธทเธญเธเน€เธ”เธตเนˆเธขเธง", + "compose_form.poll.switch_to_single": "เน€เธ›เธฅเธตเนˆเธขเธ™เธเธฒเธฃเธชเธณเธฃเธงเธˆเธ„เธงเธฒเธกเธ„เธดเธ”เน€เธซเน‡เธ™เน€เธ›เน‡เธ™เธญเธ™เธธเธเธฒเธ•เธ•เธฑเธงเน€เธฅเธทเธญเธเน€เธ”เธตเธขเธง", "compose_form.poll.type": "เธฅเธฑเธเธฉเธ“เธฐ", "compose_form.publish": "เน‚เธžเธชเธ•เนŒ", "compose_form.publish_form": "เน‚เธžเธชเธ•เนŒเนƒเธซเธกเนˆ", @@ -308,13 +308,17 @@ "follow_requests.unlocked_explanation": "เนเธกเน‰เธงเนˆเธฒเน„เธกเนˆเธกเธตเธเธฒเธฃเธฅเน‡เธญเธ„เธšเธฑเธเธŠเธตเธ‚เธญเธ‡เธ„เธธเธ“ เธžเธ™เธฑเธเธ‡เธฒเธ™เธ‚เธญเธ‡ {domain} เธ„เธดเธ”เธงเนˆเธฒเธ„เธธเธ“เธญเธฒเธˆเธ•เน‰เธญเธ‡เธเธฒเธฃเธ•เธฃเธงเธˆเธ—เธฒเธ™เธ„เธณเธ‚เธญเธ•เธดเธ”เธ•เธฒเธกเธˆเธฒเธเธšเธฑเธเธŠเธตเน€เธซเธฅเนˆเธฒเธ™เธตเน‰เธ”เน‰เธงเธขเธ•เธ™เน€เธญเธ‡", "follow_suggestions.curated_suggestion": "เธ„เธฑเธ”เธชเธฃเธฃเน‚เธ”เธขเธžเธ™เธฑเธเธ‡เธฒเธ™", "follow_suggestions.dismiss": "เน„เธกเนˆเธ•เน‰เธญเธ‡เนเธชเธ”เธ‡เธญเธตเธ", + "follow_suggestions.featured_longer": "เธ„เธฑเธ”เธชเธฃเธฃเน‚เธ”เธขเธ—เธตเธก {domain}", + "follow_suggestions.friends_of_friends_longer": "เน€เธ›เน‡เธ™เธ—เธตเนˆเธ™เธดเธขเธกเนƒเธ™เธซเธกเธนเนˆเธœเธนเน‰เธ„เธ™เธ—เธตเนˆเธ„เธธเธ“เธ•เธดเธ”เธ•เธฒเธก", "follow_suggestions.hints.featured": "เน‚เธ›เธฃเน„เธŸเธฅเนŒเธ™เธตเน‰เน„เธ”เน‰เธฃเธฑเธšเธเธฒเธฃเธ„เธฑเธ”เธชเธฃเธฃเน‚เธ”เธขเธ—เธตเธก {domain}", - "follow_suggestions.hints.friends_of_friends": "เน‚เธ›เธฃเน„เธŸเธฅเนŒเธ™เธตเน‰เน„เธ”เน‰เธฃเธฑเธšเธ„เธงเธฒเธกเธ™เธดเธขเธกเนƒเธ™เธซเธกเธนเนˆเธœเธนเน‰เธ„เธ™เธ—เธตเนˆเธ„เธธเธ“เธ•เธดเธ”เธ•เธฒเธก", + "follow_suggestions.hints.friends_of_friends": "เน‚เธ›เธฃเน„เธŸเธฅเนŒเธ™เธตเน‰เน€เธ›เน‡เธ™เธ—เธตเนˆเธ™เธดเธขเธกเนƒเธ™เธซเธกเธนเนˆเธœเธนเน‰เธ„เธ™เธ—เธตเนˆเธ„เธธเธ“เธ•เธดเธ”เธ•เธฒเธก", "follow_suggestions.hints.most_followed": "เน‚เธ›เธฃเน„เธŸเธฅเนŒเธ™เธตเน‰เน€เธ›เน‡เธ™เธซเธ™เธถเนˆเธ‡เนƒเธ™เน‚เธ›เธฃเน„เธŸเธฅเนŒเธ—เธตเนˆเน„เธ”เน‰เธฃเธฑเธšเธเธฒเธฃเธ•เธดเธ”เธ•เธฒเธกเธกเธฒเธเธ—เธตเนˆเธชเธธเธ”เนƒเธ™ {domain}", "follow_suggestions.hints.most_interactions": "เน‚เธ›เธฃเน„เธŸเธฅเนŒเธ™เธตเน‰เน€เธžเธดเนˆเธ‡เน„เธ”เน‰เธฃเธฑเธšเธ„เธงเธฒเธกเธชเธ™เนƒเธˆเธญเธขเนˆเธฒเธ‡เธกเธฒเธเนƒเธ™ {domain}", "follow_suggestions.hints.similar_to_recently_followed": "เน‚เธ›เธฃเน„เธŸเธฅเนŒเธ™เธตเน‰เธ„เธฅเน‰เธฒเธขเธเธฑเธšเน‚เธ›เธฃเน„เธŸเธฅเนŒเธ—เธตเนˆเธ„เธธเธ“เน„เธ”เน‰เธ•เธดเธ”เธ•เธฒเธกเธฅเนˆเธฒเธชเธธเธ”", "follow_suggestions.personalized_suggestion": "เธ‚เน‰เธญเน€เธชเธ™เธญเนเธ™เธฐเน€เธ‰เธžเธฒเธฐเธšเธธเธ„เธ„เธฅ", "follow_suggestions.popular_suggestion": "เธ‚เน‰เธญเน€เธชเธ™เธญเนเธ™เธฐเธขเธญเธ”เธ™เธดเธขเธก", + "follow_suggestions.popular_suggestion_longer": "เน€เธ›เน‡เธ™เธ—เธตเนˆเธ™เธดเธขเธกเนƒเธ™ {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "เธ„เธฅเน‰เธฒเธขเธเธฑเธšเน‚เธ›เธฃเน„เธŸเธฅเนŒเธ—เธตเนˆเธ„เธธเธ“เน„เธ”เน‰เธ•เธดเธ”เธ•เธฒเธกเธฅเนˆเธฒเธชเธธเธ”", "follow_suggestions.view_all": "เธ”เธนเธ—เธฑเน‰เธ‡เธซเธกเธ”", "follow_suggestions.who_to_follow": "เธ•เธดเธ”เธ•เธฒเธกเนƒเธ„เธฃเธ”เธต", "followed_tags": "เนเธฎเธŠเนเธ—เน‡เธเธ—เธตเนˆเธ•เธดเธ”เธ•เธฒเธก", @@ -410,6 +414,8 @@ "limited_account_hint.action": "เนเธชเธ”เธ‡เน‚เธ›เธฃเน„เธŸเธฅเนŒเธ•เนˆเธญเน„เธ›", "limited_account_hint.title": "เธกเธตเธเธฒเธฃเธ‹เนˆเธญเธ™เน‚เธ›เธฃเน„เธŸเธฅเนŒเธ™เธตเน‰เน‚เธ”เธขเธœเธนเน‰เธเธฅเธฑเนˆเธ™เธเธฃเธญเธ‡เธ‚เธญเธ‡ {domain}", "link_preview.author": "เน‚เธ”เธข {name}", + "link_preview.more_from_author": "เน€เธžเธดเนˆเธกเน€เธ•เธดเธกเธˆเธฒเธ {name}", + "link_preview.shares": "{count, plural, other {{counter} เน‚เธžเธชเธ•เนŒ}}", "lists.account.add": "เน€เธžเธดเนˆเธกเน„เธ›เธขเธฑเธ‡เธฃเธฒเธขเธเธฒเธฃ", "lists.account.remove": "เน€เธญเธฒเธญเธญเธเธˆเธฒเธเธฃเธฒเธขเธเธฒเธฃ", "lists.delete": "เธฅเธšเธฃเธฒเธขเธเธฒเธฃ", @@ -469,6 +475,15 @@ "notification.follow": "{name} เน„เธ”เน‰เธ•เธดเธ”เธ•เธฒเธกเธ„เธธเธ“", "notification.follow_request": "{name} เน„เธ”เน‰เธ‚เธญเธ•เธดเธ”เธ•เธฒเธกเธ„เธธเธ“", "notification.mention": "{name} เน„เธ”เน‰เธเธฅเนˆเธฒเธงเธ–เธถเธ‡เธ„เธธเธ“", + "notification.moderation-warning.learn_more": "เน€เธฃเธตเธขเธ™เธฃเธนเน‰เน€เธžเธดเนˆเธกเน€เธ•เธดเธก", + "notification.moderation_warning": "เธ„เธธเธ“เน„เธ”เน‰เธฃเธฑเธšเธ„เธณเน€เธ•เธทเธญเธ™เธเธฒเธฃเธเธฅเธฑเนˆเธ™เธเธฃเธญเธ‡", + "notification.moderation_warning.action_delete_statuses": "เน€เธญเธฒเน‚เธžเธชเธ•เนŒเธšเธฒเธ‡เธชเนˆเธงเธ™เธ‚เธญเธ‡เธ„เธธเธ“เธญเธญเธเนเธฅเน‰เธง", + "notification.moderation_warning.action_disable": "เธ›เธดเธ”เนƒเธŠเน‰เธ‡เธฒเธ™เธšเธฑเธเธŠเธตเธ‚เธญเธ‡เธ„เธธเธ“เนเธฅเน‰เธง", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "เธ—เธณเน€เธ„เธฃเธทเนˆเธญเธ‡เธซเธกเธฒเธขเน‚เธžเธชเธ•เนŒเธšเธฒเธ‡เธชเนˆเธงเธ™เธ‚เธญเธ‡เธ„เธธเธ“เธงเนˆเธฒเธฅเธฐเน€เธญเธตเธขเธ”เธญเนˆเธญเธ™เนเธฅเน‰เธง", + "notification.moderation_warning.action_none": "เธšเธฑเธเธŠเธตเธ‚เธญเธ‡เธ„เธธเธ“เน„เธ”เน‰เธฃเธฑเธšเธ„เธณเน€เธ•เธทเธญเธ™เธเธฒเธฃเธเธฅเธฑเนˆเธ™เธเธฃเธญเธ‡", + "notification.moderation_warning.action_sensitive": "เธˆเธฐเธ—เธณเน€เธ„เธฃเธทเนˆเธญเธ‡เธซเธกเธฒเธขเน‚เธžเธชเธ•เนŒเธ‚เธญเธ‡เธ„เธธเธ“เธงเนˆเธฒเธฅเธฐเน€เธญเธตเธขเธ”เธญเนˆเธญเธ™เธ™เธฑเธšเธˆเธฒเธเธ™เธตเน‰เน„เธ›", + "notification.moderation_warning.action_silence": "เธˆเธณเธเธฑเธ”เธšเธฑเธเธŠเธตเธ‚เธญเธ‡เธ„เธธเธ“เนเธฅเน‰เธง", + "notification.moderation_warning.action_suspend": "เธฃเธฐเธ‡เธฑเธšเธšเธฑเธเธŠเธตเธ‚เธญเธ‡เธ„เธธเธ“เนเธฅเน‰เธง", "notification.own_poll": "เธเธฒเธฃเธชเธณเธฃเธงเธˆเธ„เธงเธฒเธกเธ„เธดเธ”เน€เธซเน‡เธ™เธ‚เธญเธ‡เธ„เธธเธ“เน„เธ”เน‰เธชเธดเน‰เธ™เธชเธธเธ”เนเธฅเน‰เธง", "notification.poll": "เธเธฒเธฃเธชเธณเธฃเธงเธˆเธ„เธงเธฒเธกเธ„เธดเธ”เน€เธซเน‡เธ™เธ—เธตเนˆเธ„เธธเธ“เน„เธ”เน‰เธฅเธ‡เธ„เธฐเนเธ™เธ™เน„เธ”เน‰เธชเธดเน‰เธ™เธชเธธเธ”เนเธฅเน‰เธง", "notification.reblog": "{name} เน„เธ”เน‰เธ”เธฑเธ™เน‚เธžเธชเธ•เนŒเธ‚เธญเธ‡เธ„เธธเธ“", @@ -681,13 +696,13 @@ "server_banner.about_active_users": "เธœเธนเน‰เธ„เธ™เธ—เธตเนˆเนƒเธŠเน‰เน€เธ‹เธดเธฃเนŒเธŸเน€เธงเธญเธฃเนŒเธ™เธตเน‰เนƒเธ™เธฃเธฐเธซเธงเนˆเธฒเธ‡ 30 เธงเธฑเธ™เธ—เธตเนˆเธœเนˆเธฒเธ™เธกเธฒ (เธœเธนเน‰เนƒเธŠเน‰เธ—เธตเนˆเนƒเธŠเน‰เธ‡เธฒเธ™เธญเธขเธนเนˆเธฃเธฒเธขเน€เธ”เธทเธญเธ™)", "server_banner.active_users": "เธœเธนเน‰เนƒเธŠเน‰เธ—เธตเนˆเนƒเธŠเน‰เธ‡เธฒเธ™เธญเธขเธนเนˆ", "server_banner.administered_by": "เธ”เธนเนเธฅเน‚เธ”เธข:", - "server_banner.introduction": "{domain} เน€เธ›เน‡เธ™เธชเนˆเธงเธ™เธซเธ™เธถเนˆเธ‡เธ‚เธญเธ‡เน€เธ„เธฃเธทเธญเธ‚เนˆเธฒเธขเธชเธฑเธ‡เธ„เธกเนเธšเธšเธเธฃเธฐเธˆเธฒเธขเธจเธนเธ™เธขเนŒเธ—เธตเนˆเธ‚เธฑเธšเน€เธ„เธฅเธทเนˆเธญเธ™เน‚เธ”เธข {mastodon}", - "server_banner.learn_more": "เน€เธฃเธตเธขเธ™เธฃเธนเน‰เน€เธžเธดเนˆเธกเน€เธ•เธดเธก", + "server_banner.is_one_of_many": "{domain} เน€เธ›เน‡เธ™เธซเธ™เธถเนˆเธ‡เนƒเธ™เน€เธ‹เธดเธฃเนŒเธŸเน€เธงเธญเธฃเนŒ Mastodon เธญเธดเธชเธฃเธฐเธˆเธณเธ™เธงเธ™เธกเธฒเธเธ—เธตเนˆเธ„เธธเธ“เธชเธฒเธกเธฒเธฃเธ–เนƒเธŠเน‰เน€เธžเธทเนˆเธญเธกเธตเธชเนˆเธงเธ™เธฃเนˆเธงเธกเนƒเธ™เธˆเธฑเธเธฃเธงเธฒเธฅเธชเธซเธžเธฑเธ™เธ˜เนŒ", "server_banner.server_stats": "เธชเธ–เธดเธ•เธดเน€เธ‹เธดเธฃเนŒเธŸเน€เธงเธญเธฃเนŒ:", "sign_in_banner.create_account": "เธชเธฃเน‰เธฒเธ‡เธšเธฑเธเธŠเธต", + "sign_in_banner.follow_anyone": "เธ•เธดเธ”เธ•เธฒเธกเนƒเธ„เธฃเธเน‡เธ•เธฒเธกเธ—เธฑเนˆเธงเธ—เธฑเน‰เธ‡เธˆเธฑเธเธฃเธงเธฒเธฅเธชเธซเธžเธฑเธ™เธ˜เนŒเนเธฅเธฐเธ”เธนเธˆเธฑเธเธฃเธงเธฒเธฅเธชเธซเธžเธฑเธ™เธ˜เนŒเธ—เธฑเน‰เธ‡เธซเธกเธ”เธ•เธฒเธกเธฅเธณเธ”เธฑเธšเน€เธงเธฅเธฒ เน„เธกเนˆเธกเธตเธญเธฑเธฅเธเธญเธฃเธดเธ—เธถเธก, เน‚เธ†เธฉเธ“เธฒ เธซเธฃเธทเธญเธ„เธฅเธดเธเน€เธšเธ•เธญเธขเธนเนˆเนƒเธ™เธชเธฒเธขเธ•เธฒ", + "sign_in_banner.mastodon_is": "Mastodon เน€เธ›เน‡เธ™เธงเธดเธ˜เธตเธ—เธตเนˆเธ”เธตเธ—เธตเนˆเธชเธธเธ”เธ—เธตเนˆเธˆเธฐเธ•เธดเธ”เธ•เธฒเธกเธชเธดเนˆเธ‡เธ—เธตเนˆเธเธณเธฅเธฑเธ‡เน€เธเธดเธ”เธ‚เธถเน‰เธ™", "sign_in_banner.sign_in": "เน€เธ‚เน‰เธฒเธชเธนเนˆเธฃเธฐเธšเธš", "sign_in_banner.sso_redirect": "เน€เธ‚เน‰เธฒเธชเธนเนˆเธฃเธฐเธšเธšเธซเธฃเธทเธญเธฅเธ‡เธ—เธฐเน€เธšเธตเธขเธ™", - "sign_in_banner.text": "เน€เธ‚เน‰เธฒเธชเธนเนˆเธฃเธฐเธšเธšเน€เธžเธทเนˆเธญเธ•เธดเธ”เธ•เธฒเธกเน‚เธ›เธฃเน„เธŸเธฅเนŒเธซเธฃเธทเธญเนเธฎเธŠเนเธ—เน‡เธ เธŠเธทเนˆเธ™เธŠเธญเธš เนเธŠเธฃเนŒ เนเธฅเธฐเธ•เธญเธšเธเธฅเธฑเธšเน‚เธžเธชเธ•เนŒ เธ„เธธเธ“เธขเธฑเธ‡เธชเธฒเธกเธฒเธฃเธ–เน‚เธ•เน‰เธ•เธญเธšเธˆเธฒเธเธšเธฑเธเธŠเธตเธ‚เธญเธ‡เธ„เธธเธ“เนƒเธ™เน€เธ‹เธดเธฃเนŒเธŸเน€เธงเธญเธฃเนŒเธญเธทเนˆเธ™", "status.admin_account": "เน€เธ›เธดเธ”เธชเนˆเธงเธ™เธ•เธดเธ”เธ•เนˆเธญเธเธฒเธฃเธเธฅเธฑเนˆเธ™เธเธฃเธญเธ‡เธชเธณเธซเธฃเธฑเธš @{name}", "status.admin_domain": "เน€เธ›เธดเธ”เธชเนˆเธงเธ™เธ•เธดเธ”เธ•เนˆเธญเธเธฒเธฃเธเธฅเธฑเนˆเธ™เธเธฃเธญเธ‡เธชเธณเธซเธฃเธฑเธš {domain}", "status.admin_status": "เน€เธ›เธดเธ”เน‚เธžเธชเธ•เนŒเธ™เธตเน‰เนƒเธ™เธชเนˆเธงเธ™เธ•เธดเธ”เธ•เนˆเธญเธเธฒเธฃเธเธฅเธฑเนˆเธ™เธเธฃเธญเธ‡", diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json index dc07480ef6..ac39a3fd7b 100644 --- a/app/javascript/mastodon/locales/tr.json +++ b/app/javascript/mastodon/locales/tr.json @@ -35,9 +35,9 @@ "account.follow_back": "Geri takip et", "account.followers": "Takipรงi", "account.followers.empty": "Henรผz kimse bu kullanฤฑcฤฑyฤฑ takip etmiyor.", - "account.followers_counter": "{count, plural, one {{counter} Takipรงi} other {{counter} Takipรงi}}", + "account.followers_counter": "{count, plural, one {{counter} takipรงi} other {{counter} takipรงi}}", "account.following": "Takip Ediliyor", - "account.following_counter": "{count, plural, one {{counter} Takip Edilen} other {{counter} Takip Edilen}}", + "account.following_counter": "{count, plural, one {{counter} takip edilen} other {{counter} takip edilen}}", "account.follows.empty": "Bu kullanฤฑcฤฑ henรผz kimseyi takip etmiyor.", "account.go_to_profile": "Profile git", "account.hide_reblogs": "@{name} kiลŸisinin boostlarฤฑnฤฑ gizle", @@ -63,7 +63,7 @@ "account.requested_follow": "{name} size takip isteฤŸi gรถnderdi", "account.share": "@{name} adlฤฑ kiลŸinin profilini paylaลŸ", "account.show_reblogs": "@{name} kiลŸisinin yeniden paylaลŸฤฑmlarฤฑnฤฑ gรถster", - "account.statuses_counter": "{count, plural, one {{counter} Gรถnderi} other {{counter} Gรถnderi}}", + "account.statuses_counter": "{count, plural, one {{counter} gรถnderi} other {{counter} gรถnderi}}", "account.unblock": "@{name} adlฤฑ kiลŸinin engelini kaldฤฑr", "account.unblock_domain": "{domain} alan adฤฑnฤฑn engelini kaldฤฑr", "account.unblock_short": "Engeli kaldฤฑr", @@ -308,6 +308,8 @@ "follow_requests.unlocked_explanation": "Hesabฤฑnฤฑz kilitli olmasa da, {domain} personeli bu hesaplardan gelen takip isteklerini gรถzden geรงirmek isteyebileceฤŸinizi dรผลŸรผndรผ.", "follow_suggestions.curated_suggestion": "ร‡alฤฑลŸanlarฤฑn seรงtikleri", "follow_suggestions.dismiss": "Tekrar gรถsterme", + "follow_suggestions.featured_longer": "{domain} takฤฑmฤฑ tarafฤฑndan elle seรงildi", + "follow_suggestions.friends_of_friends_longer": "Takip ettiฤŸiniz kiลŸiler arasฤฑnda popรผler", "follow_suggestions.hints.featured": "Bu profil {domain} ekibi tarafฤฑndan elle seรงilmiลŸtir.", "follow_suggestions.hints.friends_of_friends": "Bu profil takip ettiฤŸiniz insanlar arasฤฑnda popรผlerdir.", "follow_suggestions.hints.most_followed": "Bu, {domain} sunucusunda en fazla izlenen profildir.", @@ -315,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Bu profil, son zamanlarda takip ettiฤŸiniz profillere benziyor.", "follow_suggestions.personalized_suggestion": "KiลŸiselleลŸmiลŸ รถneriler", "follow_suggestions.popular_suggestion": "Popรผler รถneriler", + "follow_suggestions.popular_suggestion_longer": "{domain} รผzerinde popรผler", + "follow_suggestions.similar_to_recently_followed_longer": "Yakฤฑn zamanda takip ettiฤŸiniz hesaplara benziyor", "follow_suggestions.view_all": "Tรผmรผnรผ gรถr", "follow_suggestions.who_to_follow": "Takip edebileceklerin", "followed_tags": "Takip edilen etiketler", @@ -410,6 +414,8 @@ "limited_account_hint.action": "Yine de profili gรถster", "limited_account_hint.title": "Bu profil {domain} moderatรถrleri tarafฤฑndan gizlendi.", "link_preview.author": "Yazar: {name}", + "link_preview.more_from_author": "{name} kiลŸisinden daha fazlasฤฑ", + "link_preview.shares": "{count, plural, one {{counter} gรถnderi} other {{counter} gรถnderi}}", "lists.account.add": "Listeye ekle", "lists.account.remove": "Listeden kaldฤฑr", "lists.delete": "Listeyi sil", @@ -469,6 +475,15 @@ "notification.follow": "{name} seni takip etti", "notification.follow_request": "{name} size takip isteฤŸi gรถnderdi", "notification.mention": "{name} senden bahsetti", + "notification.moderation-warning.learn_more": "Daha fazlasฤฑ", + "notification.moderation_warning": "Hesabฤฑnฤฑz bir denetim uyarฤฑsฤฑ aldฤฑ", + "notification.moderation_warning.action_delete_statuses": "Bazฤฑ gรถnderileriniz kaldฤฑrฤฑldฤฑ.", + "notification.moderation_warning.action_disable": "Hesabฤฑnฤฑz devre dฤฑลŸฤฑ bฤฑrakฤฑldฤฑ.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Bazฤฑ gรถnderileriniz hassas olarak iลŸaretlendi.", + "notification.moderation_warning.action_none": "Hesabฤฑnฤฑz bir denetim uyarฤฑsฤฑ aldฤฑ.", + "notification.moderation_warning.action_sensitive": "Gรถnderileriniz artฤฑk hassas olarak iลŸaretlenecek.", + "notification.moderation_warning.action_silence": "Hesabฤฑnฤฑz sฤฑnฤฑrlandฤฑrฤฑldฤฑ.", + "notification.moderation_warning.action_suspend": "Hesabฤฑnฤฑz askฤฑya alฤฑndฤฑ.", "notification.own_poll": "Anketiniz sona erdi", "notification.poll": "Oy verdiฤŸiniz bir anket sona erdi", "notification.reblog": "{name} gรถnderini yeniden paylaลŸtฤฑ", @@ -681,13 +696,13 @@ "server_banner.about_active_users": "Bu sunucuyu son 30 gรผnde kullanan insanlar (Aylฤฑk Etkin Kullanฤฑcฤฑlar)", "server_banner.active_users": "etkin kullanฤฑcฤฑlar", "server_banner.administered_by": "Yรถnetici:", - "server_banner.introduction": "{domain}, {mastodon} destekli merkeziyetsiz sosyal aฤŸฤฑn bir parรงasฤฑdฤฑr.", - "server_banner.learn_more": "Daha fazlasฤฑnฤฑ รถฤŸrenin", + "server_banner.is_one_of_many": "{domain} fediverse katฤฑlฤฑmฤฑ iรงin kullanabileceฤŸiniz birรงok baฤŸฤฑmsฤฑz Mastodon sunucusundan biridir.", "server_banner.server_stats": "Sunucu istatistikleri:", "sign_in_banner.create_account": "Hesap oluลŸtur", + "sign_in_banner.follow_anyone": "Fediverse รงapฤฑnda herhangi bir kimseyi takip edin ve tรผmรผnรผ kronolojik sฤฑrada gรถrรผntรผleyin. Algoritma, reklam veya tฤฑklama tuzaฤŸฤฑ yok.", + "sign_in_banner.mastodon_is": "Neler olup bittiฤŸini izlemenin en iyi aracฤฑ Mastodon'dur.", "sign_in_banner.sign_in": "GiriลŸ yap", "sign_in_banner.sso_redirect": "GiriลŸ yap veya kaydol", - "sign_in_banner.text": "Profilleri ve hashtagleri takip etmek, gรถnderileri favorilerine eklemek, paylaลŸmak ve yanฤฑtlamak iรงin giriลŸ yap. Farklฤฑ bir sunucudaki hesabฤฑnla da etkileลŸimde bulunabilirsin.", "status.admin_account": "@{name} iรงin denetim arayรผzรผnรผ aรงฤฑn", "status.admin_domain": "{domain} iรงin denetim arayรผzรผnรผ aรงฤฑn", "status.admin_status": "Denetim arayรผzรผnde bu gรถnderiyi aรงฤฑn", diff --git a/app/javascript/mastodon/locales/tt.json b/app/javascript/mastodon/locales/tt.json index 9a402472d9..baba3190dc 100644 --- a/app/javascript/mastodon/locales/tt.json +++ b/app/javascript/mastodon/locales/tt.json @@ -31,9 +31,7 @@ "account.follow": "ะฏะทั‹ะปัƒ", "account.followers": "ะฏะทั‹ะปัƒั‡ั‹", "account.followers.empty": "ำ˜ะปะต ะฑะตั€ะบะตะผ ะดำ™ ัะทั‹ะปะผะฐะณะฐะฝ.", - "account.followers_counter": "{count, plural,one {{counter} ัะทั‹ะปัƒั‡ั‹} other {{counter} ัะทั‹ะปัƒั‡ั‹}}", "account.following": "ะฏะทั‹ะปัƒะปะฐั€", - "account.following_counter": "{count, plural, one {{counter} ัะทั‹ะปัƒ} other {{counter} ัะทั‹ะปัƒ}}", "account.follows.empty": "ะ‘ะตั€ะบะตะผะณำ™ ะดำ™ ัะทั‹ะปะผะฐะณะฐะฝ ำ™ะปะต.", "account.go_to_profile": "ะŸั€ะพั„ะธะปัŒะณำ™ ะบาฏั‡าฏ", "account.hide_reblogs": "ะกะบั€ั‹ะฒะฐั‚ัŒ ะบำฉั‡ะตะฝ ะฝั‡ะต @{name}", @@ -55,7 +53,6 @@ "account.requested_follow": "{name} ะกะตะทะณำ™ ัะทั‹ะปัƒ ัะพั€ะฐะฒั‹ะฝ า—ะธะฑะตั€ะดะต", "account.share": "@{name} ะฟั€ะพั„ะธะปะต ะฑะตะปำ™ะฝ ัƒั€ั‚ะฐะบะปะฐัˆัƒ", "account.show_reblogs": "ะšาฏั€ัำ™ั‚ะตั€ะณำ™ ะบำฉั‡ำ™ะนั‚าฏ ะฝั‡ะต @{name}", - "account.statuses_counter": "{count, plural, one {{counter} ัะทะผะฐ} other {{counter} ัะทะผะฐ}}", "account.unblock": "@{name} ะฑะธะบะปำ™ะฒะตะฝ ั‡ั‹ะณัƒ", "account.unblock_domain": "{domain} ะฑะธะบะปำ™ะฒะตะฝ ั‡ั‹ะณัƒ", "account.unblock_short": "ะ‘ะธะบะปำ™าฏะฝะต ั‡ั‹ะณัƒ", @@ -407,7 +404,6 @@ "search_results.statuses": "ะฏะทะผะฐะปะฐั€", "search_results.title": "{q} ำฉั‡ะตะฝ ัะทะปำ™าฏ", "server_banner.administered_by": "ะ˜ะดะฐั€ำ™ ะธั‚าฏั‡ะต:", - "server_banner.learn_more": "ะšาฏะฑั€ำ™ะบ ะฑะตะปาฏ", "server_banner.server_stats": "ะกะตั€ะฒะตั€ ัั‚ะฐั‚ะธัั‚ะธะบะฐัั‹:", "sign_in_banner.create_account": "ะะบะบะฐัƒะฝั‚ะฝั‹ ััะฐัƒ", "sign_in_banner.sign_in": "ะšะตั€าฏ", diff --git a/app/javascript/mastodon/locales/ug.json b/app/javascript/mastodon/locales/ug.json index 4120d4483b..e3dd0e6b11 100644 --- a/app/javascript/mastodon/locales/ug.json +++ b/app/javascript/mastodon/locales/ug.json @@ -6,7 +6,6 @@ "account.posts": "Toots", "account.posts_with_replies": "Toots and replies", "account.requested": "Awaiting approval", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account_note.placeholder": "Click to add a note", "column.pins": "Pinned toot", "community.column_settings.media_only": "Media only", diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json index 6ae4e162b3..67ebb031ae 100644 --- a/app/javascript/mastodon/locales/uk.json +++ b/app/javascript/mastodon/locales/uk.json @@ -32,7 +32,7 @@ "account.featured_tags.last_status_never": "ะะตะผะฐั” ะดะพะฟะธัั–ะฒ", "account.featured_tags.title": "{name} ะฒะธะดั–ะปัั” ั…ะตัˆั‚ะตา‘ะธ", "account.follow": "ะŸั–ะดะฟะธัะฐั‚ะธัั", - "account.follow_back": "ะŸั–ะดะฟะธัะฐั‚ะธัั ะฒะทะฐั”ะผะฝะพ", + "account.follow_back": "ะกั‚ะตะถะธั‚ะธ ั‚ะฐะบะพะถ", "account.followers": "ะŸั–ะดะฟะธัะฝะธะบะธ", "account.followers.empty": "ะั–ั…ั‚ะพ ั‰ะต ะฝะต ะฟั–ะดะฟะธัะฐะฝะธะน ะฝะฐ ั†ัŒะพะณะพ ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ.", "account.followers_counter": "{count, plural, one {{counter} ะฟั–ะดะฟะธัะฝะธะบ} few {{counter} ะฟั–ะดะฟะธัะฝะธะบะธ} many {{counter} ะฟั–ะดะฟะธัะฝะธะบั–ะฒ} other {{counter} ะฟั–ะดะฟะธัะฝะธะบะธ}}", @@ -63,7 +63,7 @@ "account.requested_follow": "{name} ะฝะฐะดัะธะปะฐั” ะทะฐะฟะธั‚ ะฝะฐ ัั‚ะตะถะตะฝะฝั", "account.share": "ะŸะพะดั–ะปะธั‚ะธัั ะฟั€ะพั„ั–ะปะตะผ @{name}", "account.show_reblogs": "ะŸะพะบะฐะทะฐั‚ะธ ะฟะพัˆะธั€ะตะฝะฝั ะฒั–ะด @{name}", - "account.statuses_counter": "{count, plural, one {{counter} ะดะพะฟะธั} few {{counter} ะดะพะฟะธัะธ} many {{counter} ะดะพะฟะธัั–ะฒ} other {{counter} ะดะพะฟะธัะธ}}", + "account.statuses_counter": "{count, plural, one {{counter} ะดะพะฟะธั} few {{counter} ะดะพะฟะธัะธ} many {{counter} ะดะพะฟะธัั–ะฒ} other {{counter} ะดะพะฟะธั}}", "account.unblock": "ะ ะพะทะฑะปะพะบัƒะฒะฐั‚ะธ @{name}", "account.unblock_domain": "ะ ะพะทะฑะปะพะบัƒะฒะฐั‚ะธ {domain}", "account.unblock_short": "ะ ะพะทะฑะปะพะบัƒะฒะฐั‚ะธ", @@ -217,8 +217,19 @@ "domain_block_modal.title": "ะ—ะฐะฑะปะพะบัƒะฒะฐั‚ะธ ะดะพะผะตะฝ?", "domain_block_modal.you_will_lose_followers": "ะฃัั–ั… ะฒะฐัˆะธั… ะฟั–ะดะฟะธัะฝะธะบั–ะฒ ะท ั†ัŒะพะณะพ ัะตั€ะฒะตั€ะฐ ะฑัƒะดะต ะฒะธะปัƒั‡ะตะฝะพ.", "domain_block_modal.you_wont_see_posts": "ะ’ะธ ะฝะต ะฑะฐั‡ะธั‚ะธะผะตั‚ะต ะดะพะฟะธัั–ะฒ ั– ัะฟะพะฒั–ั‰ะตะฝัŒ ะฒั–ะด ะบะพั€ะธัั‚ัƒะฒะฐั‡ั–ะฒ ะฝะฐ ั†ัŒะพะผัƒ ัะตั€ะฒะตั€ั–.", + "domain_pill.activitypub_lets_connect": "ะฆะต ะดะพะทะฒะพะปัั” ะฒะฐะผ ัะฟั–ะปะบัƒะฒะฐั‚ะธัั ั‚ะฐ ะฒะทะฐั”ะผะพะดั–ัั‚ะธ ะท ะปัŽะดัŒะผะธ ะฝะต ะปะธัˆะต ะฝะฐ Mastodon, ะฐะปะต ะน ัƒ ั€ั–ะทะฝะธั… ัะพั†ั–ะฐะปัŒะฝะธั… ะทะฐัั‚ะพััƒะฝะบะฐั….", + "domain_pill.activitypub_like_language": "ActivityPub - ั†ะต ัะบ ะผะพะฒะฐ, ัะบะพัŽ Mastodon ั€ะพะทะผะพะฒะปัั” ะท ั–ะฝัˆะธะผะธ ัะพั†ั–ะฐะปัŒะฝะธะผะธ ะผะตั€ะตะถะฐะผะธ.", "domain_pill.server": "ะกะตั€ะฒะตั€", + "domain_pill.their_handle": "ะ‡ั…ะฝั ะฐะดั€ะตัะฐ:", + "domain_pill.their_server": "ะ‡ั…ะฝั–ะน ั†ะธั„ั€ะพะฒะธะน ะดั–ะผ, ะดะต ะถะธะฒัƒั‚ัŒ ัƒัั– ั—ั…ะฝั– ะดะพะฟะธัะธ.", + "domain_pill.their_username": "ะ‡ั…ะฝั–ะน ัƒะฝั–ะบะฐะปัŒะฝะธะน ั–ะดะตะฝั‚ะธั„ั–ะบะฐั‚ะพั€ ะฝะฐ ั—ั…ะฝัŒะพะผัƒ ัะตั€ะฒะตั€ั–. ะ’ะธ ะผะพะถะตั‚ะต ะทะฝะฐะนั‚ะธ ะบะพั€ะธัั‚ัƒะฒะฐั‡ั–ะฒ ะท ะพะดะฝะฐะบะพะฒะธะผะธ ั–ะผะตะฝะฐะผะธ ะฝะฐ ั€ั–ะทะฝะธั… ัะตั€ะฒะตั€ะฐั….", "domain_pill.username": "ะ†ะผ'ั ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ", + "domain_pill.whats_in_a_handle": "ะฉะพ ั” ะฒ ะฐะดั€ะตัั–?", + "domain_pill.who_they_are": "ะžัะบั–ะปัŒะบะธ ะดะตัะบั€ะธะฟั‚ะพั€ะธ ะฒะบะฐะทัƒัŽั‚ัŒ, ั…ั‚ะพ ั†ะต ั– ะดะต ะฒั–ะฝ ะทะฝะฐั…ะพะดะธั‚ัŒัั, ะฒะธ ะผะพะถะตั‚ะต ะฒะทะฐั”ะผะพะดั–ัั‚ะธ ะท ะปัŽะดัŒะผะธ ั‡ะตั€ะตะท ัะพั†ั–ะฐะปัŒะฝัƒ ะผะตั€ะตะถัƒ ะฟะปะฐั‚ั„ะพั€ะผ ะฝะฐ ะพัะฝะพะฒั– .", + "domain_pill.who_you_are": "ะžัะบั–ะปัŒะบะธ ะฒะฐัˆ ะฝั–ะบะฝะตะนะผ ะฒะบะฐะทัƒั”, ั…ั‚ะพ ะฒะธ ั‚ะฐ ะดะต ะฒะธ, ะปัŽะดะธ ะผะพะถัƒั‚ัŒ ะฒะทะฐั”ะผะพะดั–ัั‚ะธ ะท ะฒะฐะผะธ ั‡ะตั€ะตะท ัะพั†ั–ะฐะปัŒะฝัƒ ะผะตั€ะตะถัƒ ะฟะปะฐั‚ั„ะพั€ะผ ะฝะฐ ะพัะฝะพะฒั– .", + "domain_pill.your_handle": "ะ’ะฐัˆะฐ ะฐะดั€ะตัะฐ:", + "domain_pill.your_server": "ะ’ะฐัˆ ั†ะธั„ั€ะพะฒะธะน ะดั–ะผ, ะดะต ะถะธะฒัƒั‚ัŒ ัƒัั– ะฒะฐัˆั– ะดะพะฟะธัะธ. ะะต ะฟะพะดะพะฑะฐั”ั‚ัŒัั ั†ะตะน? ะŸะตั€ะตะฝะตัั–ั‚ัŒ ัะตั€ะฒะตั€ะธ ะฒ ะฑัƒะดัŒ-ัะบะธะน ั‡ะฐั ั– ะทะฐะปัƒั‡ะฐะนั‚ะต ัะฒะพั—ั… ะฟั–ะดะฟะธัะฝะธะบั–ะฒ.", + "domain_pill.your_username": "ะ’ะฐัˆ ัƒะฝั–ะบะฐะปัŒะฝะธะน ั–ะดะตะฝั‚ะธั„ั–ะบะฐั‚ะพั€ ะฝะฐ ั†ัŒะพะผัƒ ัะตั€ะฒะตั€ั–. ะ’ะธ ะผะพะถะตั‚ะต ะทะฝะฐะนั‚ะธ ะบะพั€ะธัั‚ัƒะฒะฐั‡ั–ะฒ ะท ะพะดะฝะฐะบะพะฒะธะผะธ ั–ะผะตะฝะฐะผะธ ะฝะฐ ั€ั–ะทะฝะธั… ัะตั€ะฒะตั€ะฐั….", "embed.instructions": "ะ’ะฑัƒะดัƒะนั‚ะต ั†ะตะน ะดะพะฟะธั ะดะพ ะฒะฐัˆะพะณะพ ะฒะตะฑัะฐะนั‚ัƒ, ัะบะพะฟั–ัŽะฒะฐะฒัˆะธ ะบะพะด ะฝะธะถั‡ะต.", "embed.preview": "ะžััŒ ัะบะธะน ะฒะธะณะปัะด ั†ะต ะผะฐั‚ะธะผะต:", "emoji_button.activity": "ะ”ั–ัะปัŒะฝั–ัั‚ัŒ", @@ -286,6 +297,7 @@ "filter_modal.select_filter.subtitle": "ะ’ะธะบะพั€ะธัั‚ะฐั‚ะธ ะฝะฐัะฒะฝัƒ ะบะฐั‚ะตะณะพั€ั–ัŽ ะฐะฑะพ ัั‚ะฒะพั€ะธั‚ะธ ะฝะพะฒัƒ", "filter_modal.select_filter.title": "ะคั–ะปัŒั‚ั€ัƒะฒะฐั‚ะธ ั†ะตะน ะดะพะฟะธั", "filter_modal.title.status": "ะคั–ะปัŒั‚ั€ัƒะฒะฐั‚ะธ ะดะพะฟะธั", + "filtered_notifications_banner.mentions": "{count, plural, one {mention} other {mentions}}", "filtered_notifications_banner.pending_requests": "ะกะฟะพะฒั–ั‰ะตะฝะฝั ะฒั–ะด {count, plural, =0 {ะถะพะดะฝะพั— ะพัะพะฑะธ} one {ะพะดะฝั–ั”ั— ะพัะพะฑะธ} few {# ะพัั–ะฑ} many {# ะพัั–ะฑ} other {# ะพัะพะฑะธ}}, ะบะพั‚ั€ะธั… ะฒะธ ะผะพะถะตั‚ะต ะทะฝะฐั‚ะธ", "filtered_notifications_banner.title": "ะ’ั–ะดั„ั–ะปัŒั‚ั€ะพะฒะฐะฝั– ัะฟะพะฒั–ั‰ะตะฝะฝั", "firehose.all": "ะ’ัั–", @@ -296,6 +308,8 @@ "follow_requests.unlocked_explanation": "ะฅะพั‡ะฐ ะฒะฐัˆ ะพะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั ะฝะต ะทะฐะฑะปะพะบะพะฒะฐะฝะพ, ะฟะตั€ัะพะฝะฐะป {domain} ะฟั€ะธะฟัƒัะบะฐั”, ั‰ะพ, ะผะพะถะปะธะฒะพ, ะฒะธ ั…ะพั‚ั–ะปะธ ะฑ ะฟะตั€ะตะณะปัะฝัƒั‚ะธ ั†ั– ะทะฐะฟะธั‚ะธ ะฝะฐ ะฟั–ะดะฟะธัะบัƒ.", "follow_suggestions.curated_suggestion": "ะ’ั–ะดั–ะฑั€ะฐะฝะพ ะบะพะผะฐะฝะดะพัŽ", "follow_suggestions.dismiss": "ะ‘ั–ะปัŒัˆะต ะฝะต ะฟะพะบะฐะทัƒะฒะฐั‚ะธ", + "follow_suggestions.featured_longer": "ะ’ะธะฑั€ะฐะฝะพ ะบะพะผะฐะฝะดะพัŽ {domain} ะฒั€ัƒั‡ะฝัƒ", + "follow_suggestions.friends_of_friends_longer": "ะŸะพะฟัƒะปัั€ะฝั– ัะตั€ะตะด ะปัŽะดะตะน, ะทะฐ ัะบะธะผะธ ะฒะธ ัะปั–ะดะบัƒั”ั‚ะต", "follow_suggestions.hints.featured": "ะฆะตะน ะฟั€ะพั„ั–ะปัŒ ะฑัƒะฒ ะพะฑั€ะฐะฝะธะน ะบะพะผะฐะฝะดะพัŽ {domain}.", "follow_suggestions.hints.friends_of_friends": "ะฆะตะน ะฟั€ะพั„ั–ะปัŒ ะฟะพะฟัƒะปัั€ะฝะธะน ัะตั€ะตะด ั‚ะธั… ะปัŽะดะตะน, ะฝะฐ ัะบะธั… ะฒะธ ะฟั–ะดะฟะธัะฐะฝั–.", "follow_suggestions.hints.most_followed": "ะ—ะฐ ั†ะธะผ ะฟั€ะพั„ั–ะปะตะผ ะพะดะธะฝ ะท ะฝะฐะนะฟะพะฟัƒะปัั€ะฝั–ัˆะธั… ะฝะฐ {domain}.", @@ -303,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "ะฆะตะน ะฟั€ะพั„ั–ะปัŒ ัั…ะพะถะธะน ะฝะฐ ะฟั€ะพั„ั–ะปั–, ะทะฐ ัะบะธะผะธ ะฒะธ ัั‚ะตะถะธะปะธ ะพัั‚ะฐะฝะฝั–ะผ ั‡ะฐัะพะผ.", "follow_suggestions.personalized_suggestion": "ะŸะตั€ัะพะฝะฐะปั–ะทะพะฒะฐะฝะฐ ะฟั€ะพะฟะพะทะธั†ั–ั", "follow_suggestions.popular_suggestion": "ะŸะพะฟัƒะปัั€ะฝะฐ ะฟั€ะพะฟะพะทะธั†ั–ั", + "follow_suggestions.popular_suggestion_longer": "ะŸะพะฟัƒะปัั€ะฝะพ ะฝะฐ {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "ะกั…ะพะถั– ะฝะฐ ะฟั€ะพั„ั–ะปั–, ะทะฐ ัะบะธะผะธ ะฒะธ ะฝะตั‰ะพะดะฐะฒะฝะพ ัั‚ะตะถะธะปะธ", "follow_suggestions.view_all": "ะŸะตั€ะตะณะปัะฝัƒั‚ะธ ะฒัะต", "follow_suggestions.who_to_follow": "ะะฐ ะบะพะณะพ ะฟั–ะดะฟะธัะฐั‚ะธัั", "followed_tags": "ะ’ั–ะดัั‚ะตะถัƒะฒะฐะฝั– ั…ะตัˆั‚ะตา‘ะธ", @@ -398,6 +414,8 @@ "limited_account_hint.action": "ะฃัะต ะพะดะฝะพ ะฟะพะบะฐะทะฐั‚ะธ ะฟั€ะพั„ั–ะปัŒ", "limited_account_hint.title": "ะฆะตะน ะฟั€ะพั„ั–ะปัŒ ัั…ะพะฒะฐะปะธ ะผะพะดะตั€ะฐั‚ะพั€ะธ {domain}.", "link_preview.author": "ะ’ั–ะด {name}", + "link_preview.more_from_author": "ะ‘ั–ะปัŒัˆะต ะฒั–ะด {name}", + "link_preview.shares": "{count, plural, one {{counter} ะดะพะฟะธั} few {{counter} ะดะพะฟะธัะธ} many {{counter} ะดะพะฟะธัั–ะฒ} other {{counter} ะดะพะฟะธั}}", "lists.account.add": "ะ”ะพะดะฐั‚ะธ ะดะพ ัะฟะธัะบัƒ", "lists.account.remove": "ะ’ะธะปัƒั‡ะธั‚ะธ ะทั– ัะฟะธัะบัƒ", "lists.delete": "ะ’ะธะดะฐะปะธั‚ะธ ัะฟะธัะพะบ", @@ -457,12 +475,23 @@ "notification.follow": "{name} ะฟั–ะดะฟะธัะฐะปะธัั ะฝะฐ ะฒะฐั", "notification.follow_request": "{name} ะฒั–ะดะฟั€ะฐะฒะธะปะธ ะทะฐะฟะธั‚ ะฝะฐ ะฟั–ะดะฟะธัะบัƒ", "notification.mention": "{name} ะทะณะฐะดะฐะปะธ ะฒะฐั", + "notification.moderation-warning.learn_more": "ะ”ั–ะทะฝะฐั‚ะธัั ะฑั–ะปัŒัˆะต", + "notification.moderation_warning": "ะ’ะธ ะพั‚ั€ะธะผะฐะปะธ ะฟะพะฟะตั€ะตะดะถะตะฝะฝั ะผะพะดะตั€ะฐั†ั–ั—", + "notification.moderation_warning.action_delete_statuses": "ะ”ะตัะบั– ะท ะฒะฐัˆะธั… ะดะพะฟะธัั–ะฒ ะฑัƒะปะพ ะฒะธะดะฐะปะตะฝะพ.", + "notification.moderation_warning.action_disable": "ะ’ะฐัˆ ะพะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั ะฑัƒะปะพ ะฒะธะผะบะฝะตะฝะพ.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "ะ”ะตัะบั– ะท ะฒะฐัˆะธั… ะดะพะฟะธัั–ะฒ ะฑัƒะปะธ ะฟะพะทะฝะฐั‡ะตะฝั– ัะบ ั‡ัƒั‚ะปะธะฒั–.", + "notification.moderation_warning.action_none": "ะ’ะฐัˆ ะพะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั ะพั‚ั€ะธะผะฐะฒ ะฟะพะฟะตั€ะตะดะถะตะฝะฝั ะผะพะดะตั€ะฐั†ั–ั—.", + "notification.moderation_warning.action_sensitive": "ะ’ั–ะดั‚ะตะฟะตั€ ะฒะฐัˆั– ะดะพะฟะธัะธ ะฑัƒะดัƒั‚ัŒ ะฟะพะทะฝะฐั‡ะตะฝั– ัะบ ั‡ัƒั‚ะปะธะฒั–.", + "notification.moderation_warning.action_silence": "ะ’ะฐัˆ ะพะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั ะฑัƒะปะพ ะพะฑะผะตะถะตะฝะพ.", + "notification.moderation_warning.action_suspend": "ะ’ะฐัˆ ะพะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั ะฑัƒะปะพ ะทะฐะฑะปะพะบะพะฒะฐะฝะพ.", "notification.own_poll": "ะ’ะฐัˆะต ะพะฟะธั‚ัƒะฒะฐะฝะฝั ะทะฐะฒะตั€ัˆะธะปะพัั", "notification.poll": "ะžะฟะธั‚ัƒะฒะฐะฝะฝั, ัƒ ัะบะพะผัƒ ะฒะธ ะณะพะปะพััƒะฒะฐะปะธ, ัะบั–ะฝั‡ะธะปะพัั", "notification.reblog": "{name} ะฟะพัˆะธั€ัŽั” ะฒะฐัˆ ะดะพะฟะธั", "notification.relationships_severance_event": "ะ’ั‚ั€ะฐั‡ะตะฝะพ ะท'ั”ะดะฝะฐะฝะฝั ะท {name}", "notification.relationships_severance_event.account_suspension": "ะะดะผั–ะฝั–ัั‚ั€ะฐั‚ะพั€ ะท {from} ะฟั€ะธะทัƒะฟะธะฝะธะฒ {target}, ั‰ะพ ะพะทะฝะฐั‡ะฐั”, ั‰ะพ ะฒะธ ะฑั–ะปัŒัˆะต ะฝะต ะผะพะถะตั‚ะต ะพั‚ั€ะธะผัƒะฒะฐั‚ะธ ะพะฝะพะฒะปะตะฝะฝั ะฒั–ะด ะฝะธั… ะฐะฑะพ ะฒะทะฐั”ะผะพะดั–ัั‚ะธ ะท ะฝะธะผะธ.", - "notification.relationships_severance_event.learn_more": "ะ”ั–ะทะฝะฐั‚ะธัั ะฑั–ะปัŒัˆะต", + "notification.relationships_severance_event.domain_block": "ะะดะผั–ะฝั–ัั‚ั€ะฐั‚ะพั€ ะท {from} ะทะฐะฑะปะพะบัƒะฒะฐะฒ {target}, ะฒะบะปัŽั‡ะฐัŽั‡ะธ {followersCount} ะฒะฐัˆะธั… ะฟั–ะดะฟะธัะฝะธะบั–ะฒ ั– {followingCount , plural, one {# ะพะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั} few {# ะพะฑะปั–ะบะพะฒั– ะทะฐะฟะธัะธ} many {# ะพะฑะปั–ะบะพะฒะธั… ะทะฐะฟะธัั–ะฒ} other {# ะพะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั}}, ะฝะฐ ัะบั– ะฒะธ ะฟั–ะดะฟะธัะฐะฝั–.", + "notification.relationships_severance_event.learn_more": "ะ”ะพะบะปะฐะดะฝั–ัˆะต", + "notification.relationships_severance_event.user_domain_block": "ะ’ะธ ะทะฐะฑะปะพะบัƒะฒะฐะปะธ {target}, ะฒะธะดะฐะปะธะฒัˆะธ {followersCount} ะฒะฐัˆะธั… ะฟั–ะดะฟะธัะฝะธะบั–ะฒ ั– {followingCount, plural, one {# ะพะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั} few {# ะพะฑะปั–ะบะพะฒั– ะทะฐะฟะธัะธ} many {# ะพะฑะปั–ะบะพะฒะธั… ะทะฐะฟะธัั–ะฒ} other {# ะพะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั}}, ะทะฐ ัะบะธะผะธ ะฒะธ ัั‚ะตะถะธั‚ะต.", "notification.status": "{name} ั‰ะพะนะฝะพ ะดะพะฟะธััƒั”", "notification.update": "{name} ะทะผั–ะฝัŽั” ะดะพะฟะธั", "notification_requests.accept": "ะŸั€ะธะนะฝัั‚ะธ", @@ -504,9 +533,13 @@ "notifications.permission_required": "ะกะฟะพะฒั–ั‰ะตะฝะฝั ะฝะฐ ัั‚ั–ะปัŒะฝะธั†ั– ะฝะต ะดะพัั‚ัƒะฟะฝั–, ะพัะบั–ะปัŒะบะธ ะฝะตะพะฑั…ั–ะดะฝะธะน ะดะพะทะฒั–ะป ะฝะต ะฝะฐะดะฐะฝะพ.", "notifications.policy.filter_new_accounts.hint": "ะกั‚ะฒะพั€ะตะฝะพ ะฒะฟั€ะพะดะพะฒะถ {days, plural, one {ะพะดะฝะพะณะพ} few {# ะดะฝั–ะฒ} many {# ะดะฝั–ะฒ} other {# ะดะฝั}}", "notifications.policy.filter_new_accounts_title": "ะะพะฒั– ะพะฑะปั–ะบะพะฒั– ะทะฐะฟะธัะธ", + "notifications.policy.filter_not_followers_hint": "ะ’ะบะปัŽั‡ะฐัŽั‡ะธ ะปัŽะดะตะน, ัะบั– ัั‚ะตะถะฐั‚ัŒ ะทะฐ ะฒะฐะผะธ ะผะตะฝัˆะต {days, plural, one {one day} other {# days}}", "notifications.policy.filter_not_followers_title": "ะ›ัŽะดะธ ะฝะต ะฟั–ะดะฟะธัะฐะฝั– ะฝะฐ ะฒะฐั", "notifications.policy.filter_not_following_hint": "ะ”ะพะบะธ ะฒะธ ะฝะต ัั…ะฒะฐะปัŽั”ั‚ะต ั—ั… ะฒั€ัƒั‡ะฝัƒ", "notifications.policy.filter_not_following_title": "ะ›ัŽะดะธ, ะฝะฐ ัะบะธั… ะฒะธ ะฝะต ะฟั–ะดะฟะธัะฐะฝั–", + "notifications.policy.filter_private_mentions_hint": "ะ’ั–ะดั„ั–ะปัŒั‚ั€ะพะฒัƒั”ั‚ัŒัั, ัะบั‰ะพ ั†ะต ะฝะต ะฒั–ะดะฟะพะฒั–ะดัŒ ะฝะฐ ะฒะฐัˆัƒ ะฒะปะฐัะฝัƒ ะทะณะฐะดะบัƒ ะฐะฑะพ ัะบั‰ะพ ะฒะธ ะฒั–ะดัั‚ะตะถัƒั”ั‚ะต ะฒั–ะดะฟั€ะฐะฒะฝะธะบะฐ", + "notifications.policy.filter_private_mentions_title": "ะะตะฑะฐะถะฐะฝั– ะฟั€ะธะฒะฐั‚ะฝั– ะทะณะฐะดะบะธ", + "notifications.policy.title": "ะ’ั–ะดั„ั–ะปัŒั‚ั€ัƒะฒะฐั‚ะธ ัะฟะพะฒั–ั‰ะตะฝะฝั ะฒั–ะดโ€ฆ", "notifications_permission_banner.enable": "ะฃะฒั–ะผะบะฝัƒั‚ะธ ัะฟะพะฒั–ั‰ะตะฝะฝั ัั‚ั–ะปัŒะฝะธั†ั–", "notifications_permission_banner.how_to_control": "ะฉะพะฑ ะพั‚ั€ะธะผัƒะฒะฐั‚ะธ ัะฟะพะฒั–ั‰ะตะฝะฝั, ะบะพะปะธ Mastodon ะฝะต ะฒั–ะดะบั€ะธั‚ะพ, ัƒะฒั–ะผะบะฝั–ั‚ัŒ ัะฟะพะฒั–ั‰ะตะฝะฝั ัั‚ั–ะปัŒะฝะธั†ั–. ะ’ะธ ะผะพะถะตั‚ะต ะบะพะฝั‚ั€ะพะปัŽะฒะฐั‚ะธ, ัะบั– ั‚ะธะฟะธ ะฒะทะฐั”ะผะพะดั–ะน ัั‚ะฒะพั€ัŽัŽั‚ัŒ ัะฟะพะฒั–ั‰ะตะฝะฝั ั‡ะตั€ะตะท ะบะฝะพะฟะบัƒ {icon} ะฒะณะพั€ั– ะฟั–ัะปั ั—ั…ะฝัŒะพะณะพ ัƒะฒั–ะผะบะฝะตะฝะฝั.", "notifications_permission_banner.title": "ะะต ะฟั€ะพา‘ะฐะฒั‚ะต ะฝั–ั‡ะพะณะพ", @@ -663,13 +696,13 @@ "server_banner.about_active_users": "ะ›ัŽะดะธ, ัะบั– ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‚ัŒ ั†ะตะน ัะตั€ะฒะตั€ ะฟั€ะพั‚ัะณะพะผ ะพัั‚ะฐะฝะฝั–ั… 30 ะดะฝั–ะฒ (ะฉะพะผั–ััั‡ะฝั– ะะบั‚ะธะฒะฝั– ะšะพั€ะธัั‚ัƒะฒะฐั‡ั–)", "server_banner.active_users": "ะฐะบั‚ะธะฒะฝั– ะบะพั€ะธัั‚ัƒะฒะฐั‡ั–", "server_banner.administered_by": "ะะดะผั–ะฝั–ัั‚ั€ะฐั‚ะพั€:", - "server_banner.introduction": "{domain} ั” ั‡ะฐัั‚ะธะฝะพัŽ ะดะตั†ะตะฝั‚ั€ะฐะปั–ะทะพะฒะฐะฝะพั— ัะพั†ั–ะฐะปัŒะฝะพั— ะผะตั€ะตะถั– ะฒั–ะด {mastodon}.", - "server_banner.learn_more": "ะ”ั–ะทะฝะฐะนั‚ะตััŒ ะฑั–ะปัŒัˆะต", + "server_banner.is_one_of_many": "{domain} - ะพะดะธะฝ ะท ะฑะฐะณะฐั‚ัŒะพั… ะฝะตะทะฐะปะตะถะฝะธั… ัะตั€ะฒะตั€ั–ะฒ Mastodon, ัะบั– ะฒะธ ะผะพะถะตั‚ะต ะฒะธะบะพั€ะธัั‚ะฐั‚ะธ, ั‰ะพะฑ ะฑั€ะฐั‚ะธ ัƒั‡ะฐัั‚ัŒ ัƒ ั„ะตะดั–ะฒะตั€ั–.", "server_banner.server_stats": "ะกั‚ะฐั‚ะธัั‚ะธะบะฐ ัะตั€ะฒะตั€ะฐ:", "sign_in_banner.create_account": "ะกั‚ะฒะพั€ะธั‚ะธ ะพะฑะปั–ะบะพะฒะธะน ะทะฐะฟะธั", + "sign_in_banner.follow_anyone": "ะกะปั–ะดะบัƒะนั‚ะต ะทะฐ ะบะธะผ ะทะฐะฒะณะพะดะฝะพ ัƒ ะฒััŒะพะผัƒ fediverse ั– ะดะธะฒั–ั‚ัŒัั ะฒัะต ั†ะต ะฒ ั…ั€ะพะฝะพะปะพะณั–ั‡ะฝะพะผัƒ ะฟะพั€ัะดะบัƒ. ะะตะผะฐั” ะฐะปะณะพั€ะธั‚ะผั–ะฒ, ั€ะตะบะปะฐะผะธ ั‡ะธ ะฝะฐะถะธะฒะพะบ ะดะปั ะฝะฐั‚ะธัะบะฐะฝัŒ ะฟั€ะธ ะฟะตั€ะตะณะปัะดั–.", + "sign_in_banner.mastodon_is": "ะœะฐัั‚ะพะดะพะฝ - ะฝะฐะนะบั€ะฐั‰ะธะน ัะฟะพัั–ะฑ ะฟั€ะพะดะพะฒะถัƒะฒะฐั‚ะธ ัะฒะพัŽ ัะฟั€ะฐะฒัƒ.", "sign_in_banner.sign_in": "ะฃะฒั–ะนั‚ะธ", "sign_in_banner.sso_redirect": "ะฃะฒั–ะนะดั–ั‚ัŒ ะฐะฑะพ ะทะฐั€ะตั”ัั‚ั€ัƒะนั‚ะตััŒ", - "sign_in_banner.text": "ะฃะฒั–ะนะดั–ั‚ัŒ, ั‰ะพะฑ ัะปั–ะดะบัƒะฒะฐั‚ะธ ะทะฐ ะฟั€ะพั„ั–ะปัะผะธ ะฐะฑะพ ั…ะตัˆั‚ะตะณะฐะผะธ, ะฒะฟะพะดะพะฑะฐะฝะธะผะธ, ะดั–ะปะธั‚ะธัั ั– ะฒั–ะดะฟะพะฒั–ะดะฐั‚ะธ ะฝะฐ ะดะพะฟะธัะธ. ะ’ะธ ั‚ะฐะบะพะถ ะผะพะถะตั‚ะต ะฒะทะฐั”ะผะพะดั–ัั‚ะธ ะท ะฒะฐัˆะพะณะพ ะพะฑะปั–ะบะพะฒะพะณะพ ะทะฐะฟะธััƒ ะฝะฐ ั–ะฝัˆะพะผัƒ ัะตั€ะฒะตั€ั–.", "status.admin_account": "ะ’ั–ะดะบั€ะธั‚ะธ ั–ะฝั‚ะตั€ั„ะตะนั ะผะพะดะตั€ะฐั†ั–ั— ะดะปั @{name}", "status.admin_domain": "ะ’ั–ะดะบั€ะธั‚ะธ ั–ะฝั‚ะตั€ั„ะตะนั ะผะพะดะตั€ะฐั†ั–ั— ะดะปั {domain}", "status.admin_status": "ะ’ั–ะดะบั€ะธั‚ะธ ั†ะตะน ะดะพะฟะธั ะฒ ั–ะฝั‚ะตั€ั„ะตะนัั– ะผะพะดะตั€ะฐั†ั–ั—", diff --git a/app/javascript/mastodon/locales/ur.json b/app/javascript/mastodon/locales/ur.json index 37f156c28f..cf53eb6fe8 100644 --- a/app/javascript/mastodon/locales/ur.json +++ b/app/javascript/mastodon/locales/ur.json @@ -26,11 +26,10 @@ "account.featured_tags.last_status_never": "ฺฉูˆุฆŒ ู…ุฑุงุณู„ ู†Œฺบ", "account.featured_tags.title": "{name} ฺฉ’ ู†ู…ุงŒุงฺบ Œุด ูนŒฺฏุฒ", "account.follow": "ูพŒุฑูˆŒ ฺฉุฑŒฺบ", + "account.follow_back": "ุงฺฉุงุคู†ูน ฺฉูˆ ูุงู„ูˆ ุจŒฺฉ ", "account.followers": "ูพŒุฑูˆฺฉุงุฑ", - "account.followers.empty": "\"ู†ูˆุฒ ุงุณ ุตุงุฑู ฺฉŒ ฺฉูˆุฆŒ ูพŒุฑูˆŒ ู†Œฺบ ฺฉุฑุชุง\".", - "account.followers_counter": "{count, plural,one {{counter} ูพŒุฑูˆฺฉุงุฑ} other {{counter} ูพŒุฑูˆฺฉุงุฑ}}", + "account.followers.empty": "ู†ูˆุฒ ุงุณ ุตุงุฑู ฺฉŒ ฺฉูˆุฆŒ ูพŒุฑูˆŒ ู†Œฺบ ฺฉุฑุชุง.", "account.following": "ูุงู„ูˆ ฺฉุฑ ุฑ’ Œฺบ", - "account.following_counter": "{count, plural, one {{counter} ูพŒุฑูˆŒ ฺฉุฑ ุฑ’ Œฺบ} other {{counter} ูพŒุฑูˆŒ ฺฉุฑ ุฑ’ Œฺบ}}", "account.follows.empty": "\"Œ ุตุงุฑู ู†ูˆุฒ ฺฉุณŒ ฺฉŒ ูพŒุฑูˆŒ ู†Œฺบ ฺฉุฑุชุง ’\".", "account.go_to_profile": "ูพุฑูˆูุงุฆู„ ูพุฑ ุฌุงุฆŒฺบ", "account.hide_reblogs": "@{name} ุณ’ ูุฑูˆุบ ฺ†ฺพูพุงุฆŒฺบ", @@ -46,6 +45,7 @@ "account.mute_notifications_short": "ู†ูˆูนŒูŒฺฉŒุดู†ุฒ ฺฉูˆ ุฎุงู…ูˆุด ฺฉุฑŒฺบ", "account.mute_short": "ุฎุงู…ูˆุด", "account.muted": "ุฎุงู…ูˆุด ฺฉุฑุฏ", + "account.mutual": "ู…Œูˆฺ†ูˆู„ ุงฺฉุงุคู†ูน", "account.no_bio": "ฺฉูˆุฆŒ ุชูุตŒู„ ู†Œฺบ ุฏŒ ฺฏุฆŒ”", "account.open_original_page": "ุงุตู„ ุตูุญ ฺฉฺพูˆู„Œฺบ", "account.posts": "ูนูˆูน", @@ -55,7 +55,6 @@ "account.requested_follow": "{name} ุขูพ ฺฉูˆ ูุงู„ูˆ ฺฉุฑู†ุง ฺ†ฺพุงุชุง ’”", "account.share": "@{name} ฺฉ’ ู…ุดุฎุต ฺฉูˆ ุจุงู†ูนŒฺบ", "account.show_reblogs": "@{name} ฺฉŒ ุงูุฒุงุฆุดุงุช ฺฉูˆ ุฏฺฉฺพุงุฆŒฺบ", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account.unblock": "@{name} ฺฉูˆ ุจุญุงู„ ฺฉุฑŒฺบ", "account.unblock_domain": "{domain} ฺฉูˆ ู† ฺ†ฺพูพุงุฆŒฺบ", "account.unblock_short": "ุจู„ุงฺฉ ุฎุชู… ฺฉุฑŒฺบ", @@ -64,7 +63,8 @@ "account.unmute": "@{name} ฺฉูˆ ุจุง ุขูˆุงุฒ ฺฉุฑŒฺบ", "account.unmute_notifications_short": "ู†ูˆูนŒูŒฺฉŒุดู†ุฒ ฺฉูˆ ุฎุงู…ูˆุด ู† ฺฉุฑŒฺบ", "account.unmute_short": "ฺฉูˆ ุฎุงู…ูˆุด ู† ฺฉุฑŒฺบ", - "account_note.placeholder": "Click to add a note", + "admin.dashboard.daily_retention": "ุงŒฺˆู…ู† ฺˆŒุด ุจูˆุฑฺˆ ฺฉูˆ ฺˆŒู„Œ ฺ†Œฺฉ ุงู† ฺฉุฑŒฺบ", + "admin.dashboard.monthly_retention": "ุงŒฺˆู…ู† ฺฉŒุด ุจูˆุฑฺˆ ฺฉูˆ ู…ู†ุชฺพู„Œ ฺ†Œฺฉ ุงู† ฺฉุฑŒฺบ", "admin.dashboard.retention.average": "ุงูˆุณุท", "admin.dashboard.retention.cohort_size": "ู†ุฆ’ Œุณุฑุฒ", "alert.rate_limited.message": "\"{retry_time, time, medium} ฺฉ’ ุจุนุฏ ฺฉูˆุดุด ฺฉุฑŒฺบ\".", diff --git a/app/javascript/mastodon/locales/uz.json b/app/javascript/mastodon/locales/uz.json index 77892914a4..4824b1d332 100644 --- a/app/javascript/mastodon/locales/uz.json +++ b/app/javascript/mastodon/locales/uz.json @@ -31,9 +31,7 @@ "account.follow": "Obuna boโ€˜lish", "account.followers": "Obunachilar", "account.followers.empty": "Bu foydalanuvchini hali hech kim kuzatmaydi.", - "account.followers_counter": "{count, plural, one {{counter} Muxlis} other {{counter} Muxlislar}}", "account.following": "Kuzatish", - "account.following_counter": "{count, plural, one {{counter} ga Muxlis} other {{counter} larga muxlis}}", "account.follows.empty": "Bu foydalanuvchi hali hech kimni kuzatmagan.", "account.go_to_profile": "Profilga o'tish", "account.hide_reblogs": "@{name} dan boostlarni yashirish", @@ -54,7 +52,6 @@ "account.requested_follow": "{name} sizni kuzatishni soสปradi", "account.share": "@{name} profilini ulashing", "account.show_reblogs": "@{name} dan bootlarni ko'rsatish", - "account.statuses_counter": "{count, plural, one {{counter} Post} other {{counter} Postlar}}", "account.unblock": "@{name} ni blokdan chiqarish", "account.unblock_domain": "{domain} domenini blokdan chiqarish", "account.unblock_short": "Blokdan chiqarish", diff --git a/app/javascript/mastodon/locales/vi.json b/app/javascript/mastodon/locales/vi.json index e1f886b1f5..70932d10be 100644 --- a/app/javascript/mastodon/locales/vi.json +++ b/app/javascript/mastodon/locales/vi.json @@ -35,9 +35,9 @@ "account.follow_back": "Theo dรตi lแบกi", "account.followers": "Ngฦฐแปi theo dรตi", "account.followers.empty": "Chฦฐa cรณ ngฦฐแปi theo dรตi nร o.", - "account.followers_counter": "{count, plural, one {{counter} Ngฦฐแปi theo dรตi} other {{counter} Ngฦฐแปi theo dรตi}}", + "account.followers_counter": "{count, plural, other {{counter} ngฦฐแปi theo dรตi}}", "account.following": "ฤang theo dรตi", - "account.following_counter": "{count, plural, one {{counter} Theo dรตi} other {{counter} Theo dรตi}}", + "account.following_counter": "{count, plural, other {{counter} ฤ‘ang theo dรตi}}", "account.follows.empty": "Ngฦฐแปi nร y chฦฐa theo dรตi ai.", "account.go_to_profile": "Xem hแป“ sฦก", "account.hide_reblogs": "แบจn tรบt @{name} ฤ‘ฤƒng lแบกi", @@ -63,7 +63,7 @@ "account.requested_follow": "{name} yรชu cแบงu theo dรตi bแบกn", "account.share": "Chia sแบป @{name}", "account.show_reblogs": "Hiแป‡n tรบt do @{name} ฤ‘ฤƒng lแบกi", - "account.statuses_counter": "{count, plural, one {{counter} Tรบt} other {{counter} Tรบt}}", + "account.statuses_counter": "{count, plural, other {{counter} tรบt}}", "account.unblock": "Bแป chแบทn @{name}", "account.unblock_domain": "Bแป แบฉn {domain}", "account.unblock_short": "Bแป chแบทn", @@ -308,6 +308,8 @@ "follow_requests.unlocked_explanation": "Mแบทc dรน tร i khoแบฃn cแปงa bแบกn ฤ‘ang แปŸ chแบฟ ฤ‘แป™ cรดng khai, quแบฃn trแป‹ viรชn cแปงa {domain} vแบซn tin rแบฑng bแบกn sแบฝ muแป‘n xem lแบกi yรชu cแบงu theo dรตi tแปซ nhแปฏng ngฦฐแปi khรกc.", "follow_suggestions.curated_suggestion": "Gแปฃi รฝ tแปซ mรกy chแปง", "follow_suggestions.dismiss": "Khรดng hiแป‡n lแบกi", + "follow_suggestions.featured_longer": "Tuyแปƒn chแปn bแปŸi {domain}", + "follow_suggestions.friends_of_friends_longer": "Nแป•i tiแบฟng vแป›i nhแปฏng ngฦฐแปi mร  bแบกn theo dรตi", "follow_suggestions.hints.featured": "Ngฦฐแปi nร y ฤ‘ฦฐแปฃc ฤ‘แป™i ngลฉ {domain} ฤ‘แป xuแบฅt.", "follow_suggestions.hints.friends_of_friends": "Ngฦฐแปi nร y nแป•i tiแบฟng vแป›i nhแปฏng ngฦฐแปi bแบกn theo dรตi.", "follow_suggestions.hints.most_followed": "Ngฦฐแปi nร y ฤ‘ฦฐแปฃc theo dรตi nhiแปu nhแบฅt trรชn {domain}.", @@ -315,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "Ngฦฐแปi nร y cรณ nรฉt giแป‘ng nhแปฏng ngฦฐแปi mร  bแบกn theo dรตi gแบงn ฤ‘รขy.", "follow_suggestions.personalized_suggestion": "Gแปฃi รฝ cรก nhรขn hรณa", "follow_suggestions.popular_suggestion": "Nhแปฏng ngฦฐแปi nแป•i tiแบฟng", + "follow_suggestions.popular_suggestion_longer": "Nแป•i tiแบฟng trรชn {domain}", + "follow_suggestions.similar_to_recently_followed_longer": "Tฦฐฦกng tแปฑ nhแปฏng ngฦฐแปi mร  bแบกn theo dรตi gแบงn ฤ‘รขy", "follow_suggestions.view_all": "Xem tแบฅt cแบฃ", "follow_suggestions.who_to_follow": "Gแปฃi รฝ theo dรตi", "followed_tags": "Hashtag theo dรตi", @@ -410,6 +414,8 @@ "limited_account_hint.action": "Vแบซn cแปฉ xem", "limited_account_hint.title": "Ngฦฐแปi nร y ฤ‘รฃ bแป‹ แบฉn bแปŸi quแบฃn trแป‹ viรชn cแปงa {domain}.", "link_preview.author": "BแปŸi {name}", + "link_preview.more_from_author": "Thรชm tแปซ {name}", + "link_preview.shares": "{count, plural, other {{counter} lฦฐแปฃt chia sแบป}}", "lists.account.add": "Thรชm vร o danh sรกch", "lists.account.remove": "Xรณa khแปi danh sรกch", "lists.delete": "Xรณa danh sรกch", @@ -469,6 +475,15 @@ "notification.follow": "{name} theo dรตi bแบกn", "notification.follow_request": "{name} yรชu cแบงu theo dรตi bแบกn", "notification.mention": "{name} nhแบฏc ฤ‘แบฟn bแบกn", + "notification.moderation-warning.learn_more": "Tรฌm hiแปƒu", + "notification.moderation_warning": "Bแบกn vแปซa nhแบญn mแป™t cแบฃnh bรกo kiแปƒm duyแป‡t", + "notification.moderation_warning.action_delete_statuses": "Mแป™t vร i tรบt cแปงa bแบกn bแป‹ gแปก.", + "notification.moderation_warning.action_disable": "Tร i khoแบฃn cแปงa bแบกn ฤ‘รฃ bแป‹ vรด hiแป‡u hรณa.", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "Vร i tรบt bแบกn bแป‹ ฤ‘รกnh dแบฅu nhแบกy cแบฃm.", + "notification.moderation_warning.action_none": "Bแบกn ฤ‘รฃ nhแบญn mแป™t cแบฃnh bรกo kiแปƒm duyแป‡t.", + "notification.moderation_warning.action_sensitive": "Tรบt cแปงa bแบกn sแบฝ bแป‹ ฤ‘รกnh dแบฅu nhแบกy cแบฃm kแปƒ tแปซ bรขy giแป.", + "notification.moderation_warning.action_silence": "Tร i khoแบฃn cแปงa bแบกn ฤ‘รฃ bแป‹ hแบกn chแบฟ.", + "notification.moderation_warning.action_suspend": "Tร i khoแบฃn cแปงa bแบกn ฤ‘รฃ bแป‹ vรด hiแป‡u hรณa.", "notification.own_poll": "Cuแป™c bรฌnh chแปn cแปงa bแบกn ฤ‘รฃ kแบฟt thรบc", "notification.poll": "Cuแป™c bรฌnh chแปn ฤ‘รฃ kแบฟt thรบc", "notification.reblog": "{name} ฤ‘ฤƒng lแบกi tรบt cแปงa bแบกn", @@ -681,13 +696,13 @@ "server_banner.about_active_users": "Nhแปฏng ngฦฐแปi แปŸ mรกy chแปง nร y trong 30 ngร y qua (MAU)", "server_banner.active_users": "ngฦฐแปi hoแบกt ฤ‘แป™ng", "server_banner.administered_by": "Vแบญn hร nh:", - "server_banner.introduction": "{domain} lร  mแป™t phแบงn cแปงa mแบกng xรฃ hแป™i liรชn hแปฃp {mastodon}.", - "server_banner.learn_more": "Tรฌm hiแปƒu", + "server_banner.is_one_of_many": "{domain} lร  mแป™t trong nhiแปu mรกy chแปง Mastodon ฤ‘แป™c lแบญp mร  bแบกn cรณ thแปƒ sแปญ dแปฅng ฤ‘แปƒ tham gia vร o Fediverse.", "server_banner.server_stats": "Thแป‘ng kรช:", "sign_in_banner.create_account": "ฤฤƒng kรฝ", + "sign_in_banner.follow_anyone": "Theo dรตi bแบฅt kแปณ ai trรชn Fediverse vร  ฤ‘แปc tรบt theo thแปฉ tแปฑ thแปi gian. Khรดng thuแบญt toรกn, quแบฃng cรกo hoแบทc clickbait.", + "sign_in_banner.mastodon_is": "Mastodon lร  cรกch tแป‘t nhแบฅt ฤ‘แปƒ nแบฏm bแบฏt nhแปฏng gรฌ ฤ‘ang xแบฃy ra.", "sign_in_banner.sign_in": "ฤฤƒng nhแบญp", "sign_in_banner.sso_redirect": "ฤฤƒng nhแบญp", - "sign_in_banner.text": "ฤฤƒng nhแบญp ฤ‘แปƒ theo dรตi ngฦฐแปi hoแบทc hashtag, thรญch, chia sแบป vร  trแบฃ lแปi tรบt. Bแบกn cลฉng cรณ thแปƒ tฦฐฦกng tรกc tแปซ tร i khoแบฃn cแปงa mรฌnh trรชn mแป™t mรกy chแปง khรกc.", "status.admin_account": "MแปŸ giao diแป‡n quแบฃn trแป‹ @{name}", "status.admin_domain": "MแปŸ giao diแป‡n quแบฃn trแป‹ @{domain}", "status.admin_status": "MแปŸ tรบt nร y trong giao diแป‡n quแบฃn trแป‹", diff --git a/app/javascript/mastodon/locales/zgh.json b/app/javascript/mastodon/locales/zgh.json index 1d3a22108c..b42bb7589c 100644 --- a/app/javascript/mastodon/locales/zgh.json +++ b/app/javascript/mastodon/locales/zgh.json @@ -19,7 +19,6 @@ "account.posts_with_replies": "Toots and replies", "account.requested": "Awaiting approval", "account.share": "โดฑโดนโต“ โต‰โดผโต”โต™ โต @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", "account.unfollow": "โดฝโดฝโต™ โดฐโดนโดผโดผโต“โต•", "account_note.placeholder": "Click to add a note", "bundle_column_error.retry": "โดฐโตโต™ โดฐโต”โตŽ", diff --git a/app/javascript/mastodon/locales/zh-CN.json b/app/javascript/mastodon/locales/zh-CN.json index 83fa723388..f2accae0d0 100644 --- a/app/javascript/mastodon/locales/zh-CN.json +++ b/app/javascript/mastodon/locales/zh-CN.json @@ -35,9 +35,9 @@ "account.follow_back": "ๅ›žๅ…ณ", "account.followers": "ๅ…ณๆณจ่€…", "account.followers.empty": "็›ฎๅ‰ๆ— ไบบๅ…ณๆณจๆญค็”จๆˆทใ€‚", - "account.followers_counter": "่ขซ {counter} ไบบๅ…ณๆณจ", + "account.followers_counter": "{count, plural, other {{counter} ๅ…ณๆณจ่€…}}", "account.following": "ๆญฃๅœจๅ…ณๆณจ", - "account.following_counter": "ๆญฃๅœจๅ…ณๆณจ {counter} ไบบ", + "account.following_counter": "{count, plural, other {{counter} ๅ…ณๆณจ}}", "account.follows.empty": "ๆญค็”จๆˆท็›ฎๅ‰ๆœชๅ…ณๆณจไปปไฝ•ไบบใ€‚", "account.go_to_profile": "ๅ‰ๅพ€ไธชไบบ่ต„ๆ–™้กต", "account.hide_reblogs": "้š่—ๆฅ่‡ช @{name} ็š„่ฝฌๅ˜Ÿ", @@ -63,7 +63,7 @@ "account.requested_follow": "{name} ๅทฒ็ปๅ‘ไฝ ๅ‘้€ไบ†ๅ…ณๆณจ่ฏทๆฑ‚", "account.share": "ๅˆ†ไบซ @{name} ็š„ไธชไบบ่ต„ๆ–™้กต", "account.show_reblogs": "ๆ˜พ็คบๆฅ่‡ช @{name} ็š„่ฝฌๅ˜Ÿ", - "account.statuses_counter": "{counter} ๆกๅ˜Ÿๆ–‡", + "account.statuses_counter": "{count, plural, other {{counter} ๅ˜Ÿๆ–‡}}", "account.unblock": "ๅ–ๆถˆๅฑ่”ฝ @{name}", "account.unblock_domain": "ๅ–ๆถˆๅฑ่”ฝ {domain} ๅŸŸๅ", "account.unblock_short": "ๅ–ๆถˆๅฑ่”ฝ", @@ -308,6 +308,8 @@ "follow_requests.unlocked_explanation": "ๅฐฝ็ฎกไฝ ๆฒกๆœ‰้”ๅ˜Ÿ๏ผŒไฝ†ๆ˜ฏ {domain} ็š„ๅทฅไฝœไบบๅ‘˜่ฎคไธบไฝ ไนŸ่ฎธไผšๆƒณๆ‰‹ๅŠจๅฎกๆ ธๅฎกๆ ธ่ฟ™ไบ›่ดฆๅท็š„ๅ…ณๆณจ่ฏทๆฑ‚ใ€‚", "follow_suggestions.curated_suggestion": "็ซ™ๅŠกไบบๅ‘˜็ฒพ้€‰", "follow_suggestions.dismiss": "ไธๅ†ๆ˜พ็คบ", + "follow_suggestions.featured_longer": "็”ฑ {domain} ็ฎก็†ๅ›ข้˜Ÿ็ฒพ้€‰", + "follow_suggestions.friends_of_friends_longer": "ๅœจไฝ ๅ…ณๆณจ็š„ไบบไธญๅพˆๅ—ๆฌข่ฟŽ", "follow_suggestions.hints.featured": "่ฏฅ็”จๆˆทๅทฒ่ขซ {domain} ็ฎก็†ๅ›ข้˜Ÿ็ฒพ้€‰ใ€‚", "follow_suggestions.hints.friends_of_friends": "่ฏฅ็”จๆˆทๅœจๆ‚จๅ…ณๆณจ็š„ไบบไธญๅพˆๅ—ๆฌข่ฟŽใ€‚", "follow_suggestions.hints.most_followed": "่ฏฅ็”จๆˆทๆ˜ฏ {domain} ไธŠๅ…ณๆณจๅบฆๆœ€้ซ˜็š„็”จๆˆทไน‹ไธ€ใ€‚", @@ -315,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "่ฏฅ็”จๆˆทไธŽๆ‚จๆœ€่ฟ‘ๅ…ณๆณจ็š„็”จๆˆท็ฑปไผผใ€‚", "follow_suggestions.personalized_suggestion": "ไธชๆ€งๅŒ–ๅปบ่ฎฎ", "follow_suggestions.popular_suggestion": "็ƒญ้—จๅปบ่ฎฎ", + "follow_suggestions.popular_suggestion_longer": "ๅœจ {domain} ไธŠๅพˆๅ—ๆฌข่ฟŽ", + "follow_suggestions.similar_to_recently_followed_longer": "ไธŽไฝ ่ฟ‘ๆœŸๅ…ณๆณจ็š„็”จๆˆท็›ธไผผ", "follow_suggestions.view_all": "ๆŸฅ็œ‹ๅ…จ้ƒจ", "follow_suggestions.who_to_follow": "ๆŽจ่ๅ…ณๆณจ", "followed_tags": "ๅ…ณๆณจ็š„่ฏ้ข˜ๆ ‡็ญพ", @@ -410,6 +414,8 @@ "limited_account_hint.action": "ไป่ฆๆ˜พ็คบไธชไบบ่ต„ๆ–™", "limited_account_hint.title": "ๆญค่ดฆๅท่ต„ๆ–™ๅทฒ่ขซ {domain} ็ฎก็†ๅ‘˜้š่—ใ€‚", "link_preview.author": "็”ฑ {name}", + "link_preview.more_from_author": "ๆŸฅ็œ‹ {name} ็š„ๆ›ดๅคšๅ†…ๅฎน", + "link_preview.shares": "{count, plural, other {{counter} ๆกๅ˜Ÿๆ–‡}}", "lists.account.add": "ๆทปๅŠ ๅˆฐๅˆ—่กจ", "lists.account.remove": "ไปŽๅˆ—่กจไธญ็งป้™ค", "lists.delete": "ๅˆ ้™คๅˆ—่กจ", @@ -469,6 +475,15 @@ "notification.follow": "{name} ๅผ€ๅง‹ๅ…ณๆณจไฝ ", "notification.follow_request": "{name} ๅ‘ไฝ ๅ‘้€ไบ†ๅ…ณๆณจ่ฏทๆฑ‚", "notification.mention": "{name} ๆๅŠไบ†ไฝ ", + "notification.moderation-warning.learn_more": "ไบ†่งฃๆ›ดๅคš", + "notification.moderation_warning": "ไฝ ๆ”ถๅˆฐไบ†ไธ€ๆก็ฎก็†่ญฆๅ‘Š", + "notification.moderation_warning.action_delete_statuses": "ไฝ ็š„ไธ€ไบ›ๅ˜Ÿๆ–‡ๅทฒ่ขซ็งป้™คใ€‚", + "notification.moderation_warning.action_disable": "ไฝ ็š„่ดฆๅทๅทฒ่ขซ็ฆ็”จใ€‚", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "ไฝ ็š„ไธ€ไบ›ๅ˜Ÿๆ–‡ๅทฒ่ขซๆ ‡่ฎฐไธบๆ•ๆ„Ÿๅ†…ๅฎนใ€‚", + "notification.moderation_warning.action_none": "ไฝ ็š„่ดฆๅทๆ”ถๅˆฐไบ†็ฎก็†่ญฆๅ‘Šใ€‚", + "notification.moderation_warning.action_sensitive": "ไปŠๅŽไฝ ็š„ๅ˜Ÿๆ–‡้ƒฝไผš่ขซๆ ‡่ฎฐไธบๆ•ๆ„Ÿๅ†…ๅฎนใ€‚", + "notification.moderation_warning.action_silence": "ไฝ ็š„่ดฆๅทๅทฒ่ขซ้™ๅˆถใ€‚", + "notification.moderation_warning.action_suspend": "ไฝ ็š„่ดฆๅทๅทฒ่ขซๅฐ็ฆ.", "notification.own_poll": "ไฝ ็š„ๆŠ•็ฅจๅทฒ็ป็ป“ๆŸ", "notification.poll": "ไฝ ๅ‚ไธŽ็š„ไธ€ไธชๆŠ•็ฅจๅทฒ็ป็ป“ๆŸ", "notification.reblog": "{name} ่ฝฌๅ‘ไบ†ไฝ ็š„ๅ˜Ÿๆ–‡", @@ -681,13 +696,13 @@ "server_banner.about_active_users": "่ฟ‡ๅŽป 30 ๅคฉๅ†…ไฝฟ็”จๆญคๆœๅŠกๅ™จ็š„ไบบ(ๆฏๆœˆๆดป่ทƒ็”จๆˆท)", "server_banner.active_users": "ๆดป่ทƒ็”จๆˆท", "server_banner.administered_by": "ๆœฌ็ซ™็ฎก็†ๅ‘˜๏ผš", - "server_banner.introduction": "{domain} ๆ˜ฏ็”ฑ {mastodon} ้ฉฑๅŠจ็š„ๅŽปไธญๅฟƒๅŒ–็คพไบค็ฝ‘็ปœ็š„ไธ€้ƒจๅˆ†ใ€‚", - "server_banner.learn_more": "่ฏฆ็ป†ไบ†่งฃ", + "server_banner.is_one_of_many": "{domain} ๆ˜ฏๅฏ็”จไบŽๅ‚ไธŽ่”้‚ฆๅฎ‡ๅฎ™็š„ไผ—ๅคš็‹ฌ็ซ‹ Mastodon ๆœๅŠกๅ™จไน‹ไธ€ใ€‚", "server_banner.server_stats": "ๆœๅŠกๅ™จ็ปŸ่ฎกๆ•ฐๆฎ๏ผš", "sign_in_banner.create_account": "ๅˆ›ๅปบ่ดฆๆˆท", + "sign_in_banner.follow_anyone": "ๅ…ณๆณจ่”้‚ฆๅฎ‡ๅฎ™ไธญ็š„ไปปไฝ•ไบบ๏ผŒๅนถๆŒ‰ๆ—ถ้—ด้กบๅบๆŸฅ็œ‹ๆ‰€ๆœ‰ๅ†…ๅฎนใ€‚ๆฒกๆœ‰็ฎ—ๆณ•ใ€ๅนฟๅ‘Šๆˆ–่ฏฑๅฏผ้“พๆŽฅใ€‚", + "sign_in_banner.mastodon_is": "Mastodon ๆ˜ฏไบ†่งฃๆœ€ๆ–ฐๅŠจๆ€็š„ๆœ€ไฝณ้€”ๅพ„ใ€‚", "sign_in_banner.sign_in": "็™ปๅฝ•", "sign_in_banner.sso_redirect": "็™ปๅฝ•ๆˆ–ๆณจๅ†Œ", - "sign_in_banner.text": "็™ปๅฝ•ๅ…ณๆณจ็”จๆˆทๅ’Œ่ฏ้ข˜ๆ ‡็ญพ๏ผŒๅ–œๆฌขใ€ๅˆ†ไบซๅ’Œๅ›žๅคๅ˜Ÿๆ–‡ใ€‚ๆ‚จ่ฟ˜ๅฏไปฅไธŽๅ…ถไป–ๆœๅŠกๅ™จไธŠ็š„็”จๆˆท่ฟ›่กŒไบ’ๅŠจใ€‚", "status.admin_account": "ๆ‰“ๅผ€ @{name} ็š„็ฎก็†็•Œ้ข", "status.admin_domain": "ๆ‰“ๅผ€ {domain} ็š„็ฎก็†็•Œ้ข", "status.admin_status": "ๆ‰“ๅผ€ๆญคๅธ–็š„็ฎก็†็•Œ้ข", diff --git a/app/javascript/mastodon/locales/zh-HK.json b/app/javascript/mastodon/locales/zh-HK.json index 0b328c7386..09a497e889 100644 --- a/app/javascript/mastodon/locales/zh-HK.json +++ b/app/javascript/mastodon/locales/zh-HK.json @@ -35,9 +35,7 @@ "account.follow_back": "่ฟฝ่นคๅฐๆ–น", "account.followers": "่ฟฝ่นค่€…", "account.followers.empty": "ๅฐšๆœชๆœ‰ไบบ่ฟฝ่นค้€™ไฝไฝฟ็”จ่€…ใ€‚", - "account.followers_counter": "ๆœ‰ {count, plural,one {{counter} ๅ€‹} other {{counter} ๅ€‹}}่ฟฝ่นค่€…", "account.following": "ๆญฃๅœจ่ฟฝ่นค", - "account.following_counter": "ๆญฃๅœจ่ฟฝ่นค {count, plural,one {{counter}}other {{counter} ไบบ}}", "account.follows.empty": "้€™ไฝไฝฟ็”จ่€…ๅฐšๆœช่ฟฝ่นคไปปไฝ•ไบบใ€‚", "account.go_to_profile": "ๅ‰ๅพ€ๅ€‹ไบบๆช”ๆกˆ", "account.hide_reblogs": "้šฑ่— @{name} ็š„่ฝ‰ๆŽจ", @@ -63,7 +61,6 @@ "account.requested_follow": "{name} ่ฆๆฑ‚่ฟฝ่นคไฝ ", "account.share": "ๅˆ†ไบซ @{name} ็š„ๅ€‹ไบบๆช”ๆกˆ", "account.show_reblogs": "้กฏ็คบ @{name} ็š„่ฝ‰ๆŽจ", - "account.statuses_counter": "{count, plural,one {{counter} ็ฏ‡}other {{counter} ็ฏ‡}}ๅธ–ๆ–‡", "account.unblock": "่งฃ้™คๅฐ้Ž– @{name}", "account.unblock_domain": "่งฃ้™คๅฐ้Ž–็ถฒๅŸŸ {domain}", "account.unblock_short": "่งฃ้™คๅฐ้Ž–", @@ -297,6 +294,7 @@ "filter_modal.select_filter.subtitle": "ไฝฟ็”จๆ—ขๆœ‰้กžๅˆฅ๏ผŒๆˆ–ๅ‰ตๅปบไธ€ๅ€‹ๆ–ฐ้กžๅˆฅ", "filter_modal.select_filter.title": "้Žๆฟพๆญคๅธ–ๆ–‡", "filter_modal.title.status": "้Žๆฟพไธ€ๅ‰‡ๅธ–ๆ–‡", + "filtered_notifications_banner.mentions": "{count, plural, one {ๅ‰‡ๆๅŠ} other {ๅ‰‡ๆๅŠ}}", "filtered_notifications_banner.pending_requests": "ไพ†่‡ช {count, plural, =0 {0 ไฝ} other {# ไฝ}}ไฝ ๅฏ่ƒฝ่ช่ญ˜็š„ไบบ็š„้€š็Ÿฅ", "filtered_notifications_banner.title": "ๅทฒ้Žๆฟพไน‹้€š็Ÿฅ", "firehose.all": "ๅ…จ้ƒจ", @@ -307,6 +305,8 @@ "follow_requests.unlocked_explanation": "ๅณไฝฟๆ‚จ็š„ๅธณ่™ŸๆœชไธŠ้Ž–๏ผŒ{domain} ็š„ๅทฅไฝœไบบๅ“ก่ช็‚บๆ‚จๅฏ่ƒฝๆœƒๆƒณๆ‰‹ๅ‹•ๅฏฉๆ ธไพ†่‡ช้€™ไบ›ๅธณ่™Ÿ็š„่ฟฝ่นค่ซ‹ๆฑ‚ใ€‚", "follow_suggestions.curated_suggestion": "็ทจ่ผฏ็ฒพ้ธ", "follow_suggestions.dismiss": "ไธๅ†้กฏ็คบ", + "follow_suggestions.featured_longer": "{domain} ๅœ˜้šŠ็ฒพ้ธ", + "follow_suggestions.friends_of_friends_longer": "ๅ—ไฝ ็š„่ฟฝ่นคๅฐ่ฑกๆญก่ฟŽ", "follow_suggestions.hints.featured": "้€™ๅ€‹ไบบๆช”ๆกˆๆ˜ฏ็”ฑ {domain} ๅœ˜้šŠ็ฒพๆŒ‘็ดฐ้ธใ€‚", "follow_suggestions.hints.friends_of_friends": "้€™ๅ€‹ไบบๆช”ๆกˆๅœจไฝ ่ฟฝ่นค็š„ไบบ็•ถไธญๅพˆๅ—ๆญก่ฟŽใ€‚", "follow_suggestions.hints.most_followed": "้€™ๅ€‹ไบบๆช”ๆกˆๆ˜ฏๅœจ {domain} ไธŠๆœ€ๅคš่ฟฝ่นคไน‹ไธ€ใ€‚", @@ -314,6 +314,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "้€™ๅ€‹ไบบๆช”ๆกˆ่ˆ‡ไฝ ๆœ€่ฟ‘่ฟฝ่นค็š„้กžไผผใ€‚", "follow_suggestions.personalized_suggestion": "ๅ€‹ไบบๅŒ–ๆŽจ่–ฆ", "follow_suggestions.popular_suggestion": "็†ฑ้–€ๆŽจ่–ฆ", + "follow_suggestions.popular_suggestion_longer": "{domain} ็†ฑ้–€", + "follow_suggestions.similar_to_recently_followed_longer": "่ˆ‡ไฝ ๆœ€่ฟ‘่ฟฝ่นค็š„ๅธณ่™Ÿ็›ธไผผ", "follow_suggestions.view_all": "ๆŸฅ็œ‹ๆ‰€ๆœ‰", "follow_suggestions.who_to_follow": "่ฟฝ่นคๅฐ่ฑก", "followed_tags": "ๅทฒ่ฟฝ่นคๆจ™็ฑค", @@ -468,6 +470,14 @@ "notification.follow": "{name} ้–‹ๅง‹่ฟฝ่นคไฝ ", "notification.follow_request": "{name} ่ฆๆฑ‚่ฟฝ่นคไฝ ", "notification.mention": "{name} ๆๅŠไฝ ", + "notification.moderation-warning.learn_more": "ไบ†่งฃๆ›ดๅคš", + "notification.moderation_warning.action_delete_statuses": "ไฝ ็š„้ƒจไปฝๅธ–ๆ–‡ๅทฒ่ขซๅˆช้™คใ€‚", + "notification.moderation_warning.action_disable": "ไฝ ็š„ๅธณ่™Ÿๅทฒ่ขซๅœ็”จใ€‚", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "ไฝ ๆŸไบ›ๅธ–ๆ–‡ๅทฒ่ขซๆจ™่จ˜็‚บๆ•ๆ„Ÿๅ…งๅฎนใ€‚", + "notification.moderation_warning.action_none": "ไฝ ็š„ๅธณ่™Ÿๆ”ถๅˆฐไธ€ๅ‰‡ๅฏฉๆ ธ่ญฆๅ‘Šใ€‚", + "notification.moderation_warning.action_sensitive": "ๅพž็พๅœจ่ตท๏ผŒไฝ ็š„ๅธ–ๆ–‡ๅฐ‡่ขซๆจ™่จ˜็‚บๆ•ๆ„Ÿๅ…งๅฎนใ€‚", + "notification.moderation_warning.action_silence": "ไฝ ็š„ๅธณ่™Ÿๅทฒๅ—ๅˆฐ้™ๅˆถใ€‚", + "notification.moderation_warning.action_suspend": "ไฝ ็š„ๅธณ่™Ÿๅทฒ่ขซๅœๆฌŠใ€‚", "notification.own_poll": "ไฝ ็š„ๆŠ•็ฅจๅทฒ็ตๆŸ", "notification.poll": "ไฝ ๅƒ่ˆ‡้Ž็š„ไธ€ๅ€‹ๆŠ•็ฅจๅทฒ็ถ“็ตๆŸ", "notification.reblog": "{name} ่ฝ‰ๆŽจไฝ ็š„ๆ–‡็ซ ", @@ -680,13 +690,10 @@ "server_banner.about_active_users": "ๅœจๆœ€่ฟ‘ 30 ๅคฉๅ…งๅ…งไฝฟ็”จๆญคไผบๆœๅ™จ็š„ไบบ (ๆœˆๆดป่บ็”จๆˆถ)", "server_banner.active_users": "ๆดป่บ็”จๆˆถ", "server_banner.administered_by": "็ฎก็†่€…๏ผš", - "server_banner.introduction": "{domain} ๆ˜ฏ็”ฑ {mastodon} ๆไพ›ไน‹ๅŽปไธญๅฟƒๅŒ–็คพไบค็ถฒ็ตก็š„ไธ€้ƒจไปฝใ€‚", - "server_banner.learn_more": "ไบ†่งฃๆ›ดๅคš", "server_banner.server_stats": "ไผบๆœๅ™จ็ตฑ่จˆ๏ผš", "sign_in_banner.create_account": "ๅปบ็ซ‹ๅธณ่™Ÿ", "sign_in_banner.sign_in": "็™ปๅ…ฅ", "sign_in_banner.sso_redirect": "็™ปๅ…ฅๆˆ–่จปๅ†Š", - "sign_in_banner.text": "็™ปๅ…ฅไปฅ่ฟฝ่นคๅ€‹ไบบๆช”ๆกˆใ€ไธป้กŒๆจ™็ฑค๏ผŒๆˆ–ๆœ€ๆ„›ใ€ๅˆ†ไบซๅ’Œๅ›ž่ฆ†ๅธ–ๆ–‡ใ€‚ไฝ ไนŸๅฏไปฅๅพžๅ…ถไป–ไผบๆœๅ™จไธŠ็š„ๅธณ่™Ÿ้€ฒ่กŒไบ’ๅ‹•ใ€‚", "status.admin_account": "้–‹ๅ•Ÿ @{name} ็š„็ฎก็†ไป‹้ข", "status.admin_domain": "ๆ‰“้–‹ {domain} ็ฎก็†ไป‹้ข", "status.admin_status": "ๅœจ็ฎก็†ไป‹้ข้–‹ๅ•Ÿ้€™็ฏ‡ๆ–‡็ซ ", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 33a7070a51..04469a971d 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -35,9 +35,9 @@ "account.follow_back": "่ทŸ้šจๅ›žๅŽป", "account.followers": "่ทŸ้šจ่€…", "account.followers.empty": "ๅฐšๆœชๆœ‰ไบบ่ทŸ้šจ้€™ไฝไฝฟ็”จ่€…ใ€‚", - "account.followers_counter": "่ขซ {count, plural, other {{counter} ไบบ}}่ทŸ้šจ", + "account.followers_counter": "่ขซ {count, plural, other {{count} ไบบ}}่ทŸ้šจ", "account.following": "่ทŸ้šจไธญ", - "account.following_counter": "ๆญฃๅœจ่ทŸ้šจ {count,plural,other {{counter} ไบบ}}", + "account.following_counter": "ๆญฃๅœจ่ทŸ้šจ {count,plural,other {{count} ไบบ}}", "account.follows.empty": "้€™ไฝไฝฟ็”จ่€…ๅฐšๆœช่ทŸ้šจไปปไฝ•ไบบใ€‚", "account.go_to_profile": "ๅ‰ๅพ€ๅ€‹ไบบๆช”ๆกˆ", "account.hide_reblogs": "้šฑ่—ไพ†่‡ช @{name} ็š„่ฝ‰ๅ˜Ÿ", @@ -62,8 +62,8 @@ "account.requested": "ๆญฃๅœจ็ญ‰ๅ€™ๅฏฉๆ ธใ€‚ๆŒ‰ไธ€ไธ‹ไปฅๅ–ๆถˆ่ทŸ้šจ่ซ‹ๆฑ‚", "account.requested_follow": "{name} ่ฆๆฑ‚่ทŸ้šจๆ‚จ", "account.share": "ๅˆ†ไบซ @{name} ็š„ๅ€‹ไบบๆช”ๆกˆ", - "account.show_reblogs": "้กฏ็คบไพ†่‡ช @{name} ็š„ๅ˜Ÿๆ–‡", - "account.statuses_counter": "{count, plural,one {{counter} ๅ‰‡}other {{counter} ๅ‰‡}}ๅ˜Ÿๆ–‡", + "account.show_reblogs": "้กฏ็คบไพ†่‡ช @{name} ็š„่ฝ‰ๅ˜Ÿ", + "account.statuses_counter": "{count, plural, other {{count} ๅ‰‡ๅ˜Ÿๆ–‡}}", "account.unblock": "่งฃ้™คๅฐ้Ž– @{name}", "account.unblock_domain": "่งฃ้™คๅฐ้Ž–็ถฒๅŸŸ {domain}", "account.unblock_short": "่งฃ้™คๅฐ้Ž–", @@ -308,6 +308,8 @@ "follow_requests.unlocked_explanation": "ๅณไพฟๆ‚จ็š„ๅธณ่™Ÿๆœช่ขซ้Ž–ๅฎš๏ผŒ{domain} ็š„็ฎก็†ๅ“ก่ช็‚บๆ‚จๅฏ่ƒฝๆƒณ่ฆ่‡ชๅทฑๅฏฉๆ ธ้€™ไบ›ๅธณ่™Ÿ็š„่ทŸ้šจ่ซ‹ๆฑ‚ใ€‚", "follow_suggestions.curated_suggestion": "็ฒพ้ธๅ…งๅฎน", "follow_suggestions.dismiss": "ไธๅ†้กฏ็คบ", + "follow_suggestions.featured_longer": "{domain} ๅœ˜้šŠ็ฒพ้ธ", + "follow_suggestions.friends_of_friends_longer": "ๅ—ๆ‚จ่ทŸ้šจไน‹ไฝฟ็”จ่€…ๆ„›ๆˆด็š„้ขจ้›ฒไบบ็‰ฉ", "follow_suggestions.hints.featured": "้€™ๅ€‹ๅ€‹ไบบๆช”ๆกˆๆ˜ฏ {domain} ็ฎก็†ๅœ˜้šŠ็ฒพๅฟƒๆŒ‘้ธใ€‚", "follow_suggestions.hints.friends_of_friends": "้€™ๅ€‹ๅ€‹ไบบๆช”ๆกˆๆ–ผๆ‚จ่ทŸ้šจ็š„ๅธณ่™Ÿไธญๅพˆๅ—ๆญก่ฟŽใ€‚", "follow_suggestions.hints.most_followed": "้€™ๅ€‹ๅ€‹ไบบๆช”ๆกˆๆ˜ฏ {domain} ไธญๆœ€ๅ—ๆญก่ฟŽ็š„ๅธณ่™Ÿไน‹ไธ€ใ€‚", @@ -315,6 +317,8 @@ "follow_suggestions.hints.similar_to_recently_followed": "้€™ๅ€‹ๅ€‹ไบบๆช”ๆกˆ่ˆ‡ๆ‚จๆœ€่ฟ‘่ทŸ้šจไน‹ๅธณ่™Ÿ้กžไผผใ€‚", "follow_suggestions.personalized_suggestion": "ๅ€‹ไบบๅŒ–ๆŽจ่–ฆ", "follow_suggestions.popular_suggestion": "็†ฑ้–€ๆŽจ่–ฆ", + "follow_suggestions.popular_suggestion_longer": "{domain} ไธŠ็š„ไบบๆฐฃ็Ž‹", + "follow_suggestions.similar_to_recently_followed_longer": "่ˆ‡ๆ‚จ่ฟ‘ๆ—ฅ่ทŸ้šจ็›ธ่ฟ‘ไน‹ๅธณ่™Ÿ", "follow_suggestions.view_all": "ๆชข่ฆ–ๅ…จ้ƒจ", "follow_suggestions.who_to_follow": "ๆŽจ่–ฆ่ทŸ้šจๅธณ่™Ÿ", "followed_tags": "ๅทฒ่ทŸ้šจไธป้กŒๆจ™็ฑค", @@ -410,6 +414,8 @@ "limited_account_hint.action": "ไธ€ๅพ‹้กฏ็คบๅ€‹ไบบๆช”ๆกˆ", "limited_account_hint.title": "ๆญคๅ€‹ไบบๆช”ๆกˆๅทฒ่ขซ {domain} ็š„็ฎก็†ๅ“ก้šฑ่—ใ€‚", "link_preview.author": "ไพ†่‡ช {name}", + "link_preview.more_from_author": "ไพ†่‡ช {name} ไน‹ๆ›ดๅคšๅ…งๅฎน", + "link_preview.shares": "{count, plural, other {{count} ๅ‰‡ๅ˜Ÿๆ–‡}}", "lists.account.add": "ๆ–ฐๅขž่‡ณๅˆ—่กจ", "lists.account.remove": "่‡ชๅˆ—่กจไธญ็งป้™ค", "lists.delete": "ๅˆช้™คๅˆ—่กจ", @@ -469,6 +475,15 @@ "notification.follow": "{name} ๅทฒ่ทŸ้šจๆ‚จ", "notification.follow_request": "{name} ่ฆๆฑ‚่ทŸ้šจๆ‚จ", "notification.mention": "{name} ๅทฒๆๅˆฐๆ‚จ", + "notification.moderation-warning.learn_more": "ไบ†่งฃๆ›ดๅคš", + "notification.moderation_warning": "ๆ‚จๅทฒๆ”ถๅˆฐ็ฎก็†ๅ“ก่ญฆๅ‘Š", + "notification.moderation_warning.action_delete_statuses": "ๆŸไบ›ๆ‚จ็š„ๅ˜Ÿๆ–‡ๅทฒ่ขซๅˆช้™คใ€‚", + "notification.moderation_warning.action_disable": "ๆ‚จ็š„ๅธณ่™Ÿๅทฒ่ขซๅœ็”จใ€‚", + "notification.moderation_warning.action_mark_statuses_as_sensitive": "ๆŸไบ›ๆ‚จ็š„ๅ˜Ÿๆ–‡ๅทฒ่ขซๆจ™่จ˜็‚บๆ•ๆ„Ÿๅ…งๅฎนใ€‚", + "notification.moderation_warning.action_none": "ๆ‚จ็š„ๅธณ่™Ÿๅทฒๆ”ถๅˆฐ็ฎก็†ๅ“ก่ญฆๅ‘Šใ€‚", + "notification.moderation_warning.action_sensitive": "ๅณๆ—ฅ่ตท๏ผŒๆ‚จ็š„ๅ˜Ÿๆ–‡ๅฐ‡ๆœƒ่ขซๆจ™่จ˜็‚บๆ•ๆ„Ÿๅ…งๅฎนใ€‚", + "notification.moderation_warning.action_silence": "ๆ‚จ็š„ๅธณ่™Ÿๅทฒ่ขซ้™ๅˆถใ€‚", + "notification.moderation_warning.action_suspend": "ๆ‚จ็š„ๅธณ่™Ÿๅทฒ่ขซๅœๆฌŠใ€‚", "notification.own_poll": "ๆ‚จ็š„ๆŠ•็ฅจๅทฒ็ตๆŸ", "notification.poll": "ๆ‚จๆ›พๆŠ•้Ž็š„ๆŠ•็ฅจๅทฒ็ถ“็ตๆŸ", "notification.reblog": "{name} ๅทฒ่ฝ‰ๅ˜Ÿๆ‚จ็š„ๅ˜Ÿๆ–‡", @@ -681,13 +696,13 @@ "server_banner.about_active_users": "ๆœ€่ฟ‘ไธ‰ๅๆ—ฅๅ…งไฝฟ็”จๆญคไผบๆœๅ™จ็š„ไบบ๏ผˆๆœˆๆดป่บไฝฟ็”จ่€…๏ผ‰", "server_banner.active_users": "ๆดป่บไฝฟ็”จ่€…", "server_banner.administered_by": "็ฎก็†่€…๏ผš", - "server_banner.introduction": "{domain} ๆ˜ฏ็”ฑ {mastodon} ๆไพ›ไน‹ๅŽปไธญๅฟƒๅŒ–็คพ็พค็ถฒ่ทฏไธ€้ƒจๅˆ†ใ€‚", - "server_banner.learn_more": "ไบ†่งฃๆ›ดๅคš", + "server_banner.is_one_of_many": "{domain} ็‚บ่จฑๅคš็จ็ซ‹็š„ Mastodon ไผบๆœๅ™จไน‹ไธ€๏ผŒๆ‚จ่ƒฝ้€้Ž่ฉฒไผบๆœๅ™จๅƒ่ˆ‡่ฏ้‚ฆๅฎ‡ๅฎ™ใ€‚", "server_banner.server_stats": "ไผบๆœๅ™จ็ตฑ่จˆ๏ผš", "sign_in_banner.create_account": "ๆ–ฐๅขžๅธณ่™Ÿ", + "sign_in_banner.follow_anyone": "่ทŸ้šจ่ฏ้‚ฆๅฎ‡ๅฎ™ไธญ็š„ไปปไฝ•ไบบ๏ผŒไธฆไธ”ไปฅๆ™‚้–“้ †ๅบ็€่ฆฝๆ‰€ๆœ‰ๅ…งๅฎนใ€‚ๆฒ’ๆœ‰ๆผ”็ฎ—ๆณ•ใ€ๅปฃๅ‘Šใ€ๆˆ–้จ™้ปžๆ“Š้€ฃ็ตใ€‚", + "sign_in_banner.mastodon_is": "Mastodon ๆ˜ฏ่ทŸไธŠๆ™‚ไปฃๆฝฎๆต็š„ๆœ€ไฝณๅทฅๅ…ท๏ผ", "sign_in_banner.sign_in": "็™ปๅ…ฅ", "sign_in_banner.sso_redirect": "็™ปๅ…ฅๆˆ–่จปๅ†Š", - "sign_in_banner.text": "็™ปๅ…ฅไปฅ่ทŸ้šจๅ€‹ไบบๆช”ๆกˆ่ˆ‡ไธป้กŒๆจ™็ฑค๏ผŒๆˆ–ๆ”ถ่—ใ€ๅˆ†ไบซๅŠๅ›ž่ฆ†ๅ˜Ÿๆ–‡ใ€‚ๆ‚จไนŸๅฏไปฅไฝฟ็”จๆ‚จ็š„ๅธณ่™Ÿๆ–ผๅ…ถไป–ไผบๆœๅ™จ้€ฒ่กŒไบ’ๅ‹•ใ€‚", "status.admin_account": "้–‹ๅ•Ÿ @{name} ็š„็ฎก็†ไป‹้ข", "status.admin_domain": "้–‹ๅ•Ÿ {domain} ็š„็ฎก็†ไป‹้ข", "status.admin_status": "ๆ–ผ็ฎก็†ไป‹้ข้–‹ๅ•Ÿๆญคๅ˜Ÿๆ–‡", diff --git a/app/javascript/mastodon/models/notification_policy.ts b/app/javascript/mastodon/models/notification_policy.ts new file mode 100644 index 0000000000..eb65403292 --- /dev/null +++ b/app/javascript/mastodon/models/notification_policy.ts @@ -0,0 +1,3 @@ +import type { NotificationPolicyJSON } from 'mastodon/api_types/notification_policies'; + +export type NotificationPolicy = NotificationPolicyJSON; // No changes from the API type diff --git a/app/javascript/mastodon/models/status.ts b/app/javascript/mastodon/models/status.ts index 7907fc34f8..3900df4e38 100644 --- a/app/javascript/mastodon/models/status.ts +++ b/app/javascript/mastodon/models/status.ts @@ -1,4 +1,12 @@ +import type { RecordOf } from 'immutable'; + +import type { ApiPreviewCardJSON } from 'mastodon/api_types/statuses'; + export type { StatusVisibility } from 'mastodon/api_types/statuses'; // Temporary until we type it correctly export type Status = Immutable.Map; + +type CardShape = Required; + +export type Card = RecordOf; diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index 97218e9f75..9f66c09631 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -1,5 +1,7 @@ import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable'; +import { timelineDelete } from 'mastodon/actions/timelines_typed'; + import { COMPOSE_MOUNT, COMPOSE_UNMOUNT, @@ -51,7 +53,6 @@ import { } from '../actions/compose'; import { REDRAFT } from '../actions/statuses'; import { STORE_HYDRATE } from '../actions/store'; -import { TIMELINE_DELETE } from '../actions/timelines'; import { me } from '../initial_state'; import { unescapeHTML } from '../utils/html'; import { uuid } from '../uuid'; @@ -446,10 +447,10 @@ export default function compose(state = initialState, action) { return updateSuggestionTags(state, action.token); case COMPOSE_TAG_HISTORY_UPDATE: return state.set('tagHistory', fromJS(action.tags)); - case TIMELINE_DELETE: - if (action.id === state.get('in_reply_to')) { + case timelineDelete.type: + if (action.payload.statusId === state.get('in_reply_to')) { return state.set('in_reply_to', null); - } else if (action.id === state.get('id')) { + } else if (action.payload.statusId === state.get('id')) { return state.set('id', null); } else { return state; diff --git a/app/javascript/mastodon/reducers/contexts.js b/app/javascript/mastodon/reducers/contexts.js index f7d7419a4e..b2c6f3f1ab 100644 --- a/app/javascript/mastodon/reducers/contexts.js +++ b/app/javascript/mastodon/reducers/contexts.js @@ -1,11 +1,13 @@ import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; +import { timelineDelete } from 'mastodon/actions/timelines_typed'; + import { blockAccountSuccess, muteAccountSuccess, } from '../actions/accounts'; import { CONTEXT_FETCH_SUCCESS } from '../actions/statuses'; -import { TIMELINE_DELETE, TIMELINE_UPDATE } from '../actions/timelines'; +import { TIMELINE_UPDATE } from '../actions/timelines'; import { compareId } from '../compare_id'; const initialState = ImmutableMap({ @@ -97,8 +99,8 @@ export default function replies(state = initialState, action) { return filterContexts(state, action.payload.relationship, action.payload.statuses); case CONTEXT_FETCH_SUCCESS: return normalizeContext(state, action.id, action.ancestors, action.descendants); - case TIMELINE_DELETE: - return deleteFromContexts(state, [action.id]); + case timelineDelete.type: + return deleteFromContexts(state, [action.payload.statusId]); case TIMELINE_UPDATE: return updateContext(state, action.status); default: diff --git a/app/javascript/mastodon/reducers/meta.js b/app/javascript/mastodon/reducers/meta.js index 96baf2f115..ddb7884592 100644 --- a/app/javascript/mastodon/reducers/meta.js +++ b/app/javascript/mastodon/reducers/meta.js @@ -6,7 +6,6 @@ import { layoutFromWindow } from 'mastodon/is_mobile'; const initialState = ImmutableMap({ streaming_api_base_url: null, - access_token: null, layout: layoutFromWindow(), permissions: '0', }); @@ -14,7 +13,8 @@ const initialState = ImmutableMap({ export default function meta(state = initialState, action) { switch(action.type) { case STORE_HYDRATE: - return state.merge(action.state.get('meta')).set('permissions', action.state.getIn(['role', 'permissions'])); + // we do not want `access_token` to be stored in the state + return state.merge(action.state.get('meta')).delete('access_token').set('permissions', action.state.getIn(['role', 'permissions'])); case changeLayout.type: return state.set('layout', action.payload.layout); default: diff --git a/app/javascript/mastodon/reducers/modal.ts b/app/javascript/mastodon/reducers/modal.ts index 368f26542c..ca85eb8c7f 100644 --- a/app/javascript/mastodon/reducers/modal.ts +++ b/app/javascript/mastodon/reducers/modal.ts @@ -1,10 +1,11 @@ import type { Reducer } from '@reduxjs/toolkit'; import { Record as ImmutableRecord, Stack } from 'immutable'; +import { timelineDelete } from 'mastodon/actions/timelines_typed'; + import { COMPOSE_UPLOAD_CHANGE_SUCCESS } from '../actions/compose'; import type { ModalType } from '../actions/modal'; import { openModal, closeModal } from '../actions/modal'; -import { TIMELINE_DELETE } from '../actions/timelines'; export type ModalProps = Record; interface Modal { @@ -72,10 +73,10 @@ export const modalReducer: Reducer = (state = initialState, action) => { // TODO: type those actions else if (action.type === COMPOSE_UPLOAD_CHANGE_SUCCESS) return popModal(state, { modalType: 'FOCAL_POINT', ignoreFocus: false }); - else if (action.type === TIMELINE_DELETE) + else if (timelineDelete.match(action)) return state.update('stack', (stack) => stack.filterNot( - (modal) => modal.get('modalProps').statusId === action.id, + (modal) => modal.get('modalProps').statusId === action.payload.statusId, ), ); else return state; diff --git a/app/javascript/mastodon/reducers/notification_policy.js b/app/javascript/mastodon/reducers/notification_policy.js deleted file mode 100644 index 8edb4d12a1..0000000000 --- a/app/javascript/mastodon/reducers/notification_policy.js +++ /dev/null @@ -1,12 +0,0 @@ -import { fromJS } from 'immutable'; - -import { NOTIFICATION_POLICY_FETCH_SUCCESS } from 'mastodon/actions/notifications'; - -export const notificationPolicyReducer = (state = null, action) => { - switch(action.type) { - case NOTIFICATION_POLICY_FETCH_SUCCESS: - return fromJS(action.policy); - default: - return state; - } -}; diff --git a/app/javascript/mastodon/reducers/notification_policy.ts b/app/javascript/mastodon/reducers/notification_policy.ts new file mode 100644 index 0000000000..ab111066cc --- /dev/null +++ b/app/javascript/mastodon/reducers/notification_policy.ts @@ -0,0 +1,18 @@ +import { createReducer, isAnyOf } from '@reduxjs/toolkit'; + +import { + fetchNotificationPolicy, + updateNotificationsPolicy, +} from 'mastodon/actions/notification_policies'; +import type { NotificationPolicy } from 'mastodon/models/notification_policy'; + +export const notificationPolicyReducer = + createReducer(null, (builder) => { + builder.addMatcher( + isAnyOf( + fetchNotificationPolicy.fulfilled, + updateNotificationsPolicy.fulfilled, + ), + (_state, action) => action.payload, + ); + }); diff --git a/app/javascript/mastodon/reducers/notifications.js b/app/javascript/mastodon/reducers/notifications.js index 7230fabcae..79aa5651ff 100644 --- a/app/javascript/mastodon/reducers/notifications.js +++ b/app/javascript/mastodon/reducers/notifications.js @@ -1,6 +1,7 @@ import { fromJS, Map as ImmutableMap, List as ImmutableList } from 'immutable'; import { blockDomainSuccess } from 'mastodon/actions/domain_blocks'; +import { timelineDelete } from 'mastodon/actions/timelines_typed'; import { authorizeFollowRequestSuccess, @@ -30,7 +31,7 @@ import { NOTIFICATIONS_SET_BROWSER_SUPPORT, NOTIFICATIONS_SET_BROWSER_PERMISSION, } from '../actions/notifications'; -import { TIMELINE_DELETE, TIMELINE_DISCONNECT } from '../actions/timelines'; +import { disconnectTimeline } from '../actions/timelines'; import { compareId } from '../compare_id'; const initialState = ImmutableMap({ @@ -56,6 +57,7 @@ export const notificationToMap = notification => ImmutableMap({ status: notification.status ? notification.status.id : null, report: notification.report ? fromJS(notification.report) : null, event: notification.event ? fromJS(notification.event) : null, + moderation_warning: notification.moderation_warning ? fromJS(notification.moderation_warning) : null, }); const normalizeNotification = (state, notification, usePendingItems) => { @@ -290,11 +292,11 @@ export default function notifications(state = initialState, action) { return filterNotifications(state, [action.payload.id], 'follow_request'); case NOTIFICATIONS_CLEAR: return state.set('items', ImmutableList()).set('pendingItems', ImmutableList()).set('hasMore', false); - case TIMELINE_DELETE: - return deleteByStatus(state, action.id); - case TIMELINE_DISCONNECT: - return action.timeline === 'home' ? - state.update(action.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(null) : items) : + case timelineDelete.type: + return deleteByStatus(state, action.payload.statusId); + case disconnectTimeline.type: + return action.payload.timeline === 'home' ? + state.update(action.payload.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(null) : items) : state; case NOTIFICATIONS_MARK_AS_READ: const lastNotification = state.get('items').find(item => item !== null); diff --git a/app/javascript/mastodon/reducers/picture_in_picture.ts b/app/javascript/mastodon/reducers/picture_in_picture.ts index 0feddcb706..10d4f1fae5 100644 --- a/app/javascript/mastodon/reducers/picture_in_picture.ts +++ b/app/javascript/mastodon/reducers/picture_in_picture.ts @@ -4,8 +4,7 @@ import { deployPictureInPictureAction, removePictureInPicture, } from 'mastodon/actions/picture_in_picture'; - -import { TIMELINE_DELETE } from '../actions/timelines'; +import { timelineDelete } from 'mastodon/actions/timelines_typed'; export interface PIPMediaProps { src: string; @@ -49,8 +48,9 @@ export const pictureInPictureReducer: Reducer = ( ...action.payload.props, }; else if (removePictureInPicture.match(action)) return initialState; - else if (action.type === TIMELINE_DELETE) - if (state.type && state.statusId === action.id) return initialState; + else if (timelineDelete.match(action)) + if (state.type && state.statusId === action.payload.statusId) + return initialState; return state; }; diff --git a/app/javascript/mastodon/reducers/search.js b/app/javascript/mastodon/reducers/search.js index 72835eb917..7828d49eee 100644 --- a/app/javascript/mastodon/reducers/search.js +++ b/app/javascript/mastodon/reducers/search.js @@ -50,6 +50,7 @@ export default function search(state = initialState, action) { return state.set('hidden', true); case SEARCH_FETCH_REQUEST: return state.withMutations(map => { + map.set('results', ImmutableMap()); map.set('isLoading', true); map.set('submitted', true); map.set('type', action.searchType); diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js index 683fe848f7..d92174f806 100644 --- a/app/javascript/mastodon/reducers/statuses.js +++ b/app/javascript/mastodon/reducers/statuses.js @@ -1,12 +1,10 @@ import { Map as ImmutableMap, fromJS } from 'immutable'; +import { timelineDelete } from 'mastodon/actions/timelines_typed'; + import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer'; import { normalizeStatusTranslation } from '../actions/importer/normalizer'; import { - REBLOG_REQUEST, - REBLOG_FAIL, - UNREBLOG_REQUEST, - UNREBLOG_FAIL, FAVOURITE_REQUEST, FAVOURITE_FAIL, UNFAVOURITE_REQUEST, @@ -16,6 +14,10 @@ import { UNBOOKMARK_REQUEST, UNBOOKMARK_FAIL, } from '../actions/interactions'; +import { + reblog, + unreblog, +} from '../actions/interactions_typed'; import { STATUS_MUTE_SUCCESS, STATUS_UNMUTE_SUCCESS, @@ -27,7 +29,6 @@ import { STATUS_FETCH_REQUEST, STATUS_FETCH_FAIL, } from '../actions/statuses'; -import { TIMELINE_DELETE } from '../actions/timelines'; const importStatus = (state, status) => state.set(status.id, fromJS(status)); @@ -65,6 +66,7 @@ const statusTranslateUndo = (state, id) => { const initialState = ImmutableMap(); +/** @type {import('@reduxjs/toolkit').Reducer} */ export default function statuses(state = initialState, action) { switch(action.type) { case STATUS_FETCH_REQUEST: @@ -91,14 +93,6 @@ export default function statuses(state = initialState, action) { return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'bookmarked'], false); case UNBOOKMARK_FAIL: return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'bookmarked'], true); - case REBLOG_REQUEST: - return state.setIn([action.status.get('id'), 'reblogged'], true); - case REBLOG_FAIL: - return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'reblogged'], false); - case UNREBLOG_REQUEST: - return state.setIn([action.status.get('id'), 'reblogged'], false); - case UNREBLOG_FAIL: - return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'reblogged'], true); case STATUS_MUTE_SUCCESS: return state.setIn([action.id, 'muted'], true); case STATUS_UNMUTE_SUCCESS: @@ -121,13 +115,22 @@ export default function statuses(state = initialState, action) { }); case STATUS_COLLAPSE: return state.setIn([action.id, 'collapsed'], action.isCollapsed); - case TIMELINE_DELETE: - return deleteStatus(state, action.id, action.references); + case timelineDelete.type: + return deleteStatus(state, action.payload.statusId, action.payload.references); case STATUS_TRANSLATE_SUCCESS: return statusTranslateSuccess(state, action.id, action.translation); case STATUS_TRANSLATE_UNDO: return statusTranslateUndo(state, action.id); default: - return state; + if(reblog.pending.match(action)) + return state.setIn([action.meta.arg.statusId, 'reblogged'], true); + else if(reblog.rejected.match(action)) + return state.get(action.meta.arg.statusId) === undefined ? state : state.setIn([action.meta.arg.statusId, 'reblogged'], false); + else if(unreblog.pending.match(action)) + return state.setIn([action.meta.arg.statusId, 'reblogged'], false); + else if(unreblog.rejected.match(action)) + return state.get(action.meta.arg.statusId) === undefined ? state : state.setIn([action.meta.arg.statusId, 'reblogged'], true); + else + return state; } } diff --git a/app/javascript/mastodon/reducers/timelines.js b/app/javascript/mastodon/reducers/timelines.js index 4c9ab98a82..b07281ab87 100644 --- a/app/javascript/mastodon/reducers/timelines.js +++ b/app/javascript/mastodon/reducers/timelines.js @@ -1,5 +1,7 @@ import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable'; +import { timelineDelete } from 'mastodon/actions/timelines_typed'; + import { blockAccountSuccess, muteAccountSuccess, @@ -7,19 +9,18 @@ import { } from '../actions/accounts'; import { TIMELINE_UPDATE, - TIMELINE_DELETE, TIMELINE_CLEAR, TIMELINE_EXPAND_SUCCESS, TIMELINE_EXPAND_REQUEST, TIMELINE_EXPAND_FAIL, TIMELINE_SCROLL_TOP, TIMELINE_CONNECT, - TIMELINE_DISCONNECT, TIMELINE_LOAD_PENDING, TIMELINE_MARK_AS_PARTIAL, TIMELINE_INSERT, TIMELINE_GAP, TIMELINE_SUGGESTIONS, + disconnectTimeline, } from '../actions/timelines'; import { compareId } from '../compare_id'; @@ -158,7 +159,7 @@ const filterTimelines = (state, relationship, statuses) => { return; } - references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => item.get('id')); + references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => item.get('id')).valueSeq().toJSON(); state = deleteStatus(state, status.get('id'), references, relationship.id); }); @@ -201,8 +202,8 @@ export default function timelines(state = initialState, action) { return expandNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial, action.isLoadingRecent, action.usePendingItems); case TIMELINE_UPDATE: return updateTimeline(state, action.timeline, fromJS(action.status), action.usePendingItems); - case TIMELINE_DELETE: - return deleteStatus(state, action.id, action.references, action.reblogOf); + case timelineDelete.type: + return deleteStatus(state, action.payload.statusId, action.payload.references, action.payload.reblogOf); case TIMELINE_CLEAR: return clearTimeline(state, action.timeline); case blockAccountSuccess.type: @@ -214,11 +215,11 @@ export default function timelines(state = initialState, action) { return updateTop(state, action.timeline, action.top); case TIMELINE_CONNECT: return state.update(action.timeline, initialTimeline, map => reconnectTimeline(map, action.usePendingItems)); - case TIMELINE_DISCONNECT: + case disconnectTimeline.type: return state.update( - action.timeline, + action.payload.timeline, initialTimeline, - map => map.set('online', false).update(action.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(TIMELINE_GAP) : items), + map => map.set('online', false).update(action.payload.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(TIMELINE_GAP) : items), ); case TIMELINE_MARK_AS_PARTIAL: return state.update( diff --git a/app/javascript/mastodon/reducers/user_lists.js b/app/javascript/mastodon/reducers/user_lists.js index 2f17fed5fd..7a4c04c5c7 100644 --- a/app/javascript/mastodon/reducers/user_lists.js +++ b/app/javascript/mastodon/reducers/user_lists.js @@ -1,12 +1,8 @@ import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; import { - DIRECTORY_FETCH_REQUEST, - DIRECTORY_FETCH_SUCCESS, - DIRECTORY_FETCH_FAIL, - DIRECTORY_EXPAND_REQUEST, - DIRECTORY_EXPAND_SUCCESS, - DIRECTORY_EXPAND_FAIL, + expandDirectory, + fetchDirectory } from 'mastodon/actions/directory'; import { FEATURED_TAGS_FETCH_REQUEST, @@ -117,6 +113,7 @@ const normalizeFeaturedTags = (state, path, featuredTags, accountId) => { })); }; +/** @type {import('@reduxjs/toolkit').Reducer} */ export default function userLists(state = initialState, action) { switch(action.type) { case FOLLOWERS_FETCH_SUCCESS: @@ -194,16 +191,6 @@ export default function userLists(state = initialState, action) { case MUTES_FETCH_FAIL: case MUTES_EXPAND_FAIL: return state.setIn(['mutes', 'isLoading'], false); - case DIRECTORY_FETCH_SUCCESS: - return normalizeList(state, ['directory'], action.accounts, action.next); - case DIRECTORY_EXPAND_SUCCESS: - return appendToList(state, ['directory'], action.accounts, action.next); - case DIRECTORY_FETCH_REQUEST: - case DIRECTORY_EXPAND_REQUEST: - return state.setIn(['directory', 'isLoading'], true); - case DIRECTORY_FETCH_FAIL: - case DIRECTORY_EXPAND_FAIL: - return state.setIn(['directory', 'isLoading'], false); case FEATURED_TAGS_FETCH_SUCCESS: return normalizeFeaturedTags(state, ['featured_tags', action.id], action.tags, action.id); case FEATURED_TAGS_FETCH_REQUEST: @@ -211,6 +198,17 @@ export default function userLists(state = initialState, action) { case FEATURED_TAGS_FETCH_FAIL: return state.setIn(['featured_tags', action.id, 'isLoading'], false); default: - return state; + if(fetchDirectory.fulfilled.match(action)) + return normalizeList(state, ['directory'], action.payload.accounts, undefined); + else if( expandDirectory.fulfilled.match(action)) + return appendToList(state, ['directory'], action.payload.accounts, undefined); + else if(fetchDirectory.pending.match(action) || + expandDirectory.pending.match(action)) + return state.setIn(['directory', 'isLoading'], true); + else if(fetchDirectory.rejected.match(action) || + expandDirectory.rejected.match(action)) + return state.setIn(['directory', 'isLoading'], false); + else + return state; } } diff --git a/app/javascript/mastodon/store/middlewares/sounds.ts b/app/javascript/mastodon/store/middlewares/sounds.ts index 720ee163e9..91407b1ec0 100644 --- a/app/javascript/mastodon/store/middlewares/sounds.ts +++ b/app/javascript/mastodon/store/middlewares/sounds.ts @@ -74,8 +74,9 @@ export const soundsMiddleware = (): Middleware< if (isActionWithMetaSound(action)) { const sound = action.meta.sound; - if (sound && Object.hasOwn(soundCache, sound)) { - play(soundCache[sound]); + if (sound) { + const s = soundCache[sound]; + if (s) play(s); } } diff --git a/app/javascript/mastodon/store/typed_functions.ts b/app/javascript/mastodon/store/typed_functions.ts index b66d7545c5..e5820149db 100644 --- a/app/javascript/mastodon/store/typed_functions.ts +++ b/app/javascript/mastodon/store/typed_functions.ts @@ -2,6 +2,8 @@ import { createAsyncThunk } from '@reduxjs/toolkit'; // eslint-disable-next-line @typescript-eslint/no-restricted-imports import { useDispatch, useSelector } from 'react-redux'; +import type { BaseThunkAPI } from '@reduxjs/toolkit/dist/createAsyncThunk'; + import type { AppDispatch, RootState } from './store'; export const useAppDispatch = useDispatch.withTypes(); @@ -13,8 +15,199 @@ export interface AsyncThunkRejectValue { error?: unknown; } +interface AppMeta { + skipLoading?: boolean; +} + export const createAppAsyncThunk = createAsyncThunk.withTypes<{ state: RootState; dispatch: AppDispatch; rejectValue: AsyncThunkRejectValue; }>(); + +type AppThunkApi = Pick< + BaseThunkAPI< + RootState, + unknown, + AppDispatch, + AsyncThunkRejectValue, + AppMeta, + AppMeta + >, + 'getState' | 'dispatch' +>; + +interface AppThunkOptions { + skipLoading?: boolean; +} + +const createBaseAsyncThunk = createAsyncThunk.withTypes<{ + state: RootState; + dispatch: AppDispatch; + rejectValue: AsyncThunkRejectValue; + fulfilledMeta: AppMeta; + rejectedMeta: AppMeta; +}>(); + +export function createThunk( + name: string, + creator: (arg: Arg, api: AppThunkApi) => Returned | Promise, + options: AppThunkOptions = {}, +) { + return createBaseAsyncThunk( + name, + async ( + arg: Arg, + { getState, dispatch, fulfillWithValue, rejectWithValue }, + ) => { + try { + const result = await creator(arg, { dispatch, getState }); + + return fulfillWithValue(result, { + skipLoading: options.skipLoading, + }); + } catch (error) { + return rejectWithValue({ error }, { skipLoading: true }); + } + }, + { + getPendingMeta() { + if (options.skipLoading) return { skipLoading: true }; + return {}; + }, + }, + ); +} + +const discardLoadDataInPayload = Symbol('discardLoadDataInPayload'); +type DiscardLoadData = typeof discardLoadDataInPayload; + +type OnData = ( + data: LoadDataResult, + api: AppThunkApi & { + actionArg: ActionArg; + discardLoadData: DiscardLoadData; + }, +) => ReturnedData | DiscardLoadData | Promise; + +type LoadData = ( + args: Args, + api: AppThunkApi, +) => Promise; + +type ArgsType = Record | undefined; + +// Overload when there is no `onData` method, the payload is the `onData` result +export function createDataLoadingThunk( + name: string, + loadData: (args: Args) => Promise, + thunkOptions?: AppThunkOptions, +): ReturnType>; + +// Overload when the `onData` method returns discardLoadDataInPayload, then the payload is empty +export function createDataLoadingThunk( + name: string, + loadData: LoadData, + onDataOrThunkOptions?: + | AppThunkOptions + | OnData, + thunkOptions?: AppThunkOptions, +): ReturnType>; + +// Overload when the `onData` method returns nothing, then the mayload is the `onData` result +export function createDataLoadingThunk( + name: string, + loadData: LoadData, + onDataOrThunkOptions?: AppThunkOptions | OnData, + thunkOptions?: AppThunkOptions, +): ReturnType>; + +// Overload when there is an `onData` method returning something +export function createDataLoadingThunk< + LoadDataResult, + Args extends ArgsType, + Returned, +>( + name: string, + loadData: LoadData, + onDataOrThunkOptions?: + | AppThunkOptions + | OnData, + thunkOptions?: AppThunkOptions, +): ReturnType>; + +/** + * This function creates a Redux Thunk that handles loading data asynchronously (usually from the API), dispatching `pending`, `fullfilled` and `rejected` actions. + * + * You can run a callback on the `onData` results to either dispatch side effects or modify the payload. + * + * It is a wrapper around RTK's [`createAsyncThunk`](https://redux-toolkit.js.org/api/createAsyncThunk) + * @param name Prefix for the actions types + * @param loadData Function that loads the data. It's (object) argument will become the thunk's argument + * @param onDataOrThunkOptions + * Callback called on the results from `loadData`. + * + * First argument will be the return from `loadData`. + * + * Second argument is an object with: `dispatch`, `getState` and `discardLoadData`. + * It can return: + * - `undefined` (or no explicit return), meaning that the `onData` results will be the payload + * - `discardLoadData` to discard the `onData` results and return an empty payload + * - anything else, which will be the payload + * + * You can also omit this parameter and pass `thunkOptions` directly + * @param maybeThunkOptions + * Additional Mastodon specific options for the thunk. Currently supports: + * - `skipLoading` to avoid showing the loading bar when the request is in progress + * @returns The created thunk + */ +export function createDataLoadingThunk< + LoadDataResult, + Args extends ArgsType, + Returned, +>( + name: string, + loadData: LoadData, + onDataOrThunkOptions?: + | AppThunkOptions + | OnData, + maybeThunkOptions?: AppThunkOptions, +) { + let onData: OnData | undefined; + let thunkOptions: AppThunkOptions | undefined; + + if (typeof onDataOrThunkOptions === 'function') onData = onDataOrThunkOptions; + else if (typeof onDataOrThunkOptions === 'object') + thunkOptions = onDataOrThunkOptions; + + if (maybeThunkOptions) { + thunkOptions = maybeThunkOptions; + } + + return createThunk( + name, + async (arg, { getState, dispatch }) => { + const data = await loadData(arg, { + dispatch, + getState, + }); + + if (!onData) return data as Returned; + + const result = await onData(data, { + dispatch, + getState, + discardLoadData: discardLoadDataInPayload, + actionArg: arg, + }); + + // if there is no return in `onData`, we return the `onData` result + if (typeof result === 'undefined') return data as Returned; + // the user explicitely asked to discard the payload + else if (result === discardLoadDataInPayload) + return undefined as Returned; + else return result; + }, + thunkOptions, + ); +} diff --git a/app/javascript/mastodon/stream.js b/app/javascript/mastodon/stream.js index ff3af5fd88..40d69136a8 100644 --- a/app/javascript/mastodon/stream.js +++ b/app/javascript/mastodon/stream.js @@ -2,6 +2,8 @@ import WebSocketClient from '@gamestdio/websocket'; +import { getAccessToken } from './initial_state'; + /** * @type {WebSocketClient | undefined} */ @@ -145,9 +147,11 @@ const channelNameWithInlineParams = (channelName, params) => { // @ts-expect-error export const connectStream = (channelName, params, callbacks) => (dispatch, getState) => { const streamingAPIBaseURL = getState().getIn(['meta', 'streaming_api_base_url']); - const accessToken = getState().getIn(['meta', 'access_token']); + const accessToken = getAccessToken(); const { onConnect, onReceive, onDisconnect } = callbacks(dispatch, getState); + if(!accessToken) throw new Error("Trying to connect to the streaming server but no access token is available."); + // If we cannot use a websockets connection, we must fall back // to using individual connections for each channel if (!streamingAPIBaseURL.startsWith('ws')) { diff --git a/app/javascript/mastodon/test_helpers.tsx b/app/javascript/mastodon/test_helpers.tsx index 69d57b95a0..f405090730 100644 --- a/app/javascript/mastodon/test_helpers.tsx +++ b/app/javascript/mastodon/test_helpers.tsx @@ -1,52 +1,35 @@ -import PropTypes from 'prop-types'; -import type { PropsWithChildren } from 'react'; -import { Component } from 'react'; - import { IntlProvider } from 'react-intl'; import { MemoryRouter } from 'react-router'; +import type { RenderOptions } from '@testing-library/react'; // eslint-disable-next-line import/no-extraneous-dependencies import { render as rtlRender } from '@testing-library/react'; -class FakeIdentityWrapper extends Component< - PropsWithChildren<{ signedIn: boolean }> -> { - static childContextTypes = { - identity: PropTypes.shape({ - signedIn: PropTypes.bool.isRequired, - accountId: PropTypes.string, - disabledAccountId: PropTypes.string, - accessToken: PropTypes.string, - }).isRequired, - }; - - getChildContext() { - return { - identity: { - signedIn: this.props.signedIn, - accountId: '123', - accessToken: 'test-access-token', - }, - }; - } - - render() { - return this.props.children; - } -} +import { IdentityContext } from './identity_context'; function render( ui: React.ReactElement, - { locale = 'en', signedIn = true, ...renderOptions } = {}, + { + locale = 'en', + signedIn = true, + ...renderOptions + }: RenderOptions & { locale?: string; signedIn?: boolean } = {}, ) { + const fakeIdentity = { + signedIn: signedIn, + accountId: '123', + disabledAccountId: undefined, + permissions: 0, + }; + const Wrapper = (props: { children: React.ReactNode }) => { return ( - + {props.children} - + ); diff --git a/app/javascript/material-icons/400-24px/gavel-fill.svg b/app/javascript/material-icons/400-24px/gavel-fill.svg new file mode 100644 index 0000000000..9699b8480a --- /dev/null +++ b/app/javascript/material-icons/400-24px/gavel-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/material-icons/400-24px/gavel.svg b/app/javascript/material-icons/400-24px/gavel.svg new file mode 100644 index 0000000000..9699b8480a --- /dev/null +++ b/app/javascript/material-icons/400-24px/gavel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/javascript/packs/sign_up.js b/app/javascript/packs/sign_up.js deleted file mode 100644 index cf9c837773..0000000000 --- a/app/javascript/packs/sign_up.js +++ /dev/null @@ -1,42 +0,0 @@ -import './public-path'; -import axios from 'axios'; - -import ready from '../mastodon/ready'; - -ready(() => { - setInterval(() => { - axios.get('/api/v1/emails/check_confirmation').then((response) => { - if (response.data) { - window.location = '/start'; - } - }).catch(error => { - console.error(error); - }); - }, 5000); - - document.querySelectorAll('.timer-button').forEach(button => { - let counter = 30; - - const container = document.createElement('span'); - - const updateCounter = () => { - container.innerText = ` (${counter})`; - }; - - updateCounter(); - - const countdown = setInterval(() => { - counter--; - - if (counter === 0) { - button.disabled = false; - button.removeChild(container); - clearInterval(countdown); - } else { - updateCounter(); - } - }, 1000); - - button.appendChild(container); - }); -}); diff --git a/app/javascript/packs/two_factor_authentication.js b/app/javascript/packs/two_factor_authentication.js deleted file mode 100644 index e77965c757..0000000000 --- a/app/javascript/packs/two_factor_authentication.js +++ /dev/null @@ -1,119 +0,0 @@ -import * as WebAuthnJSON from '@github/webauthn-json'; -import axios from 'axios'; - -import ready from '../mastodon/ready'; -import 'regenerator-runtime/runtime'; - -function getCSRFToken() { - var CSRFSelector = document.querySelector('meta[name="csrf-token"]'); - if (CSRFSelector) { - return CSRFSelector.getAttribute('content'); - } else { - return null; - } -} - -function hideFlashMessages() { - Array.from(document.getElementsByClassName('flash-message')).forEach(function(flashMessage) { - flashMessage.classList.add('hidden'); - }); -} - -function callback(url, body) { - axios.post(url, JSON.stringify(body), { - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'X-CSRF-Token': getCSRFToken(), - }, - credentials: 'same-origin', - }).then(function(response) { - window.location.replace(response.data.redirect_path); - }).catch(function(error) { - if (error.response.status === 422) { - const errorMessage = document.getElementById('security-key-error-message'); - errorMessage.classList.remove('hidden'); - console.error(error.response.data.error); - } else { - console.error(error); - } - }); -} - -ready(() => { - if (!WebAuthnJSON.supported()) { - const unsupported_browser_message = document.getElementById('unsupported-browser-message'); - if (unsupported_browser_message) { - unsupported_browser_message.classList.remove('hidden'); - document.querySelector('.btn.js-webauthn').disabled = true; - } - } - - - const webAuthnCredentialRegistrationForm = document.getElementById('new_webauthn_credential'); - if (webAuthnCredentialRegistrationForm) { - webAuthnCredentialRegistrationForm.addEventListener('submit', (event) => { - event.preventDefault(); - - var nickname = event.target.querySelector('input[name="new_webauthn_credential[nickname]"]'); - if (nickname.value) { - axios.get('/settings/security_keys/options') - .then((response) => { - const credentialOptions = response.data; - - WebAuthnJSON.create({ 'publicKey': credentialOptions }).then((credential) => { - var params = { 'credential': credential, 'nickname': nickname.value }; - callback('/settings/security_keys', params); - }).catch((error) => { - const errorMessage = document.getElementById('security-key-error-message'); - errorMessage.classList.remove('hidden'); - console.error(error); - }); - }).catch((error) => { - console.error(error.response.data.error); - }); - } else { - nickname.focus(); - } - }); - } - - const webAuthnCredentialAuthenticationForm = document.getElementById('webauthn-form'); - if (webAuthnCredentialAuthenticationForm) { - webAuthnCredentialAuthenticationForm.addEventListener('submit', (event) => { - event.preventDefault(); - - axios.get('sessions/security_key_options') - .then((response) => { - const credentialOptions = response.data; - - WebAuthnJSON.get({ 'publicKey': credentialOptions }).then((credential) => { - var params = { 'user': { 'credential': credential } }; - callback('sign_in', params); - }).catch((error) => { - const errorMessage = document.getElementById('security-key-error-message'); - errorMessage.classList.remove('hidden'); - console.error(error); - }); - }).catch((error) => { - console.error(error.response.data.error); - }); - }); - - const otpAuthenticationForm = document.getElementById('otp-authentication-form'); - - const linkToOtp = document.getElementById('link-to-otp'); - linkToOtp.addEventListener('click', () => { - webAuthnCredentialAuthenticationForm.classList.add('hidden'); - otpAuthenticationForm.classList.remove('hidden'); - hideFlashMessages(); - }); - - const linkToWebAuthn = document.getElementById('link-to-webauthn'); - linkToWebAuthn.addEventListener('click', () => { - otpAuthenticationForm.classList.add('hidden'); - webAuthnCredentialAuthenticationForm.classList.remove('hidden'); - hideFlashMessages(); - }); - } -}); diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss index 07e9d9868b..a41272364a 100644 --- a/app/javascript/styles/mastodon-light/diff.scss +++ b/app/javascript/styles/mastodon-light/diff.scss @@ -48,6 +48,10 @@ html { } } +.icon-button:disabled { + color: darken($action-button-color, 25%); +} + .account__header__bar .avatar .account__avatar { border-color: $white; } diff --git a/app/javascript/styles/mastodon-light/variables.scss b/app/javascript/styles/mastodon-light/variables.scss index 09a75a834b..9f571b3f26 100644 --- a/app/javascript/styles/mastodon-light/variables.scss +++ b/app/javascript/styles/mastodon-light/variables.scss @@ -56,11 +56,13 @@ $account-background-color: $white !default; $emojis-requiring-inversion: 'chains'; -.theme-mastodon-light { +body { --dropdown-border-color: #d9e1e8; --dropdown-background-color: #fff; + --modal-border-color: #d9e1e8; + --modal-background-color: var(--background-color-tint); --background-border-color: #d9e1e8; --background-color: #fff; - --background-color-tint: rgba(255, 255, 255, 90%); + --background-color-tint: rgba(255, 255, 255, 80%); --background-filter: blur(10px); } diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index 7ae1bbdd6c..1556b69e9d 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -10,6 +10,13 @@ $content-width: 840px; width: 100%; min-height: 100vh; + .icon { + width: 16px; + height: 16px; + vertical-align: middle; + margin: 0 2px; + } + .sidebar-wrapper { min-height: 100vh; overflow: hidden; diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 9324fb2ee0..385b9af877 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -120,8 +120,27 @@ text-decoration: none; } - &:disabled { - opacity: 0.5; + &.button--destructive { + &:active, + &:focus, + &:hover { + border-color: $ui-button-destructive-focus-background-color; + color: $ui-button-destructive-focus-background-color; + } + } + + &:disabled, + &.disabled { + opacity: 0.7; + border-color: $ui-primary-color; + color: $ui-primary-color; + + &:active, + &:focus, + &:hover { + border-color: $ui-primary-color; + color: $ui-primary-color; + } } } @@ -903,9 +922,15 @@ body > [data-popper-placement] { padding: 10px; p { + font-size: 15px; + line-height: 22px; color: $darker-text-color; margin-bottom: 20px; + strong { + font-weight: 700; + } + a { color: $secondary-text-color; text-decoration: none; @@ -1341,6 +1366,8 @@ body > [data-popper-placement] { min-height: 54px; border-bottom: 1px solid var(--background-border-color); cursor: auto; + opacity: 1; + animation: fade 150ms linear; @keyframes fade { 0% { @@ -1352,9 +1379,6 @@ body > [data-popper-placement] { } } - opacity: 1; - animation: fade 150ms linear; - .media-gallery, .video-player, .audio-player, @@ -1411,10 +1435,15 @@ body > [data-popper-placement] { .audio-player, .attachment-list, .picture-in-picture-placeholder, + .more-from-author, .status-card, .hashtag-bar { margin-inline-start: $thread-margin; - width: calc(100% - ($thread-margin)); + width: calc(100% - $thread-margin); + } + + .more-from-author { + width: calc(100% - $thread-margin + 2px); } .status__content__read-more-button { @@ -2015,7 +2044,22 @@ a .account__avatar { white-space: nowrap; display: flex; align-items: center; - gap: 4px; + gap: 8px; +} + +.account__relationship, +.explore__suggestions__card { + .icon-button { + border: 1px solid var(--background-border-color); + border-radius: 4px; + box-sizing: content-box; + padding: 5px; + + .icon { + width: 24px; + height: 24px; + } + } } .account-authorize { @@ -2165,7 +2209,8 @@ a.account__display-name { } } -.notification__relationships-severance-event { +.notification__relationships-severance-event, +.notification__moderation-warning { display: flex; gap: 16px; color: $secondary-text-color; @@ -2393,7 +2438,7 @@ a.account__display-name { } .dropdown-animation { - animation: dropdown 150ms cubic-bezier(0.1, 0.7, 0.1, 1); + animation: dropdown 250ms cubic-bezier(0.1, 0.7, 0.1, 1); @keyframes dropdown { from { @@ -2952,6 +2997,75 @@ $ui-header-logo-wordmark-width: 99px; display: none; } +.explore__suggestions__card { + padding: 12px 16px; + gap: 8px; + display: flex; + flex-direction: column; + border-bottom: 1px solid var(--background-border-color); + + &:last-child { + border-bottom: 0; + } + + &__source { + padding-inline-start: 60px; + font-size: 13px; + line-height: 16px; + color: $dark-text-color; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + + &__body { + display: flex; + gap: 12px; + align-items: center; + + &__main { + flex: 1 1 auto; + display: flex; + flex-direction: column; + gap: 8px; + min-width: 0; + + &__name-button { + display: flex; + align-items: center; + gap: 8px; + + &__name { + display: block; + color: inherit; + text-decoration: none; + flex: 1 1 auto; + min-width: 0; + } + + .button { + min-width: 80px; + } + + .display-name { + font-size: 15px; + line-height: 20px; + color: $secondary-text-color; + + strong { + font-weight: 700; + } + + &__account { + color: $darker-text-color; + display: block; + } + } + } + } + } +} + @media screen and (max-width: $no-gap-breakpoint - 1px) { .columns-area__panels__pane--compositional { display: none; @@ -3811,6 +3925,10 @@ $ui-header-logo-wordmark-width: 99px; border: 1px solid var(--background-border-color); border-radius: 8px; + &.bottomless { + border-radius: 8px 8px 0 0; + } + &__actions { bottom: 0; inset-inline-start: 0; @@ -4040,6 +4158,13 @@ a.status-card { border-end-start-radius: 0; } +.status-card.bottomless .status-card__image, +.status-card.bottomless .status-card__image-image, +.status-card.bottomless .status-card__image-preview { + border-end-end-radius: 0; + border-end-start-radius: 0; +} + .status-card.expanded > a { width: 100%; } @@ -4286,6 +4411,10 @@ a.status-card { &:hover { color: $primary-text-color; } + + .icon-sliders { + transform: rotate(60deg); + } } &:disabled { @@ -4294,6 +4423,10 @@ a.status-card { } } +.no-reduce-motion .column-header__button .icon-sliders { + transition: transform 150ms ease-in-out; +} + .column-header__collapsible { max-height: 70vh; overflow: hidden; @@ -4717,8 +4850,10 @@ a.status-card { &__menu { @include search-popout; - padding: 0; - background: $ui-secondary-color; + & { + padding: 0; + background: $ui-secondary-color; + } } &__menu-list { @@ -5531,7 +5666,7 @@ a.status-card { user-select: text; display: flex; - @media screen and (max-width: $no-gap-breakpoint) { + @media screen and (width <= 630px) { margin-top: auto; } } @@ -7281,10 +7416,11 @@ a.status-card { content: ''; position: absolute; bottom: -1px; - left: 0; - width: 100%; + left: 50%; + transform: translateX(-50%); + width: 40px; height: 3px; - border-radius: 4px; + border-radius: 4px 4px 0 0; background: $highlight-text-color; } } @@ -8686,43 +8822,80 @@ noscript { display: flex; align-items: center; color: $primary-text-color; - text-decoration: none; - padding: 15px; + padding: 16px; border-bottom: 1px solid var(--background-border-color); - gap: 15px; + gap: 16px; &:last-child { border-bottom: 0; } - &:hover, - &:active, - &:focus { - color: $highlight-text-color; - - .story__details__publisher, - .story__details__shared { - color: $highlight-text-color; - } - } - &__details { flex: 1 1 auto; &__publisher { color: $darker-text-color; margin-bottom: 8px; + font-size: 14px; + line-height: 20px; } &__title { + display: block; font-size: 19px; line-height: 24px; font-weight: 500; margin-bottom: 8px; + text-decoration: none; + color: $primary-text-color; + + &:hover, + &:active, + &:focus { + color: $highlight-text-color; + } } &__shared { + display: flex; + align-items: center; color: $darker-text-color; + gap: 8px; + justify-content: space-between; + font-size: 14px; + line-height: 20px; + + & > span { + display: flex; + align-items: center; + gap: 4px; + } + + &__pill { + background: var(--surface-variant-background-color); + border-radius: 4px; + color: inherit; + text-decoration: none; + padding: 4px 12px; + font-size: 12px; + font-weight: 500; + line-height: 16px; + } + + &__author-link { + display: inline-flex; + align-items: center; + gap: 4px; + color: $primary-text-color; + font-weight: 500; + text-decoration: none; + + &:hover, + &:active, + &:focus { + color: $highlight-text-color; + } + } } strong { @@ -8787,14 +8960,14 @@ noscript { } .server-banner { - padding: 20px 0; - &__introduction { + font-size: 15px; + line-height: 22px; color: $darker-text-color; margin-bottom: 20px; strong { - font-weight: 600; + font-weight: 700; } a { @@ -8822,6 +8995,9 @@ noscript { } &__description { + font-size: 15px; + line-height: 22px; + color: $darker-text-color; margin-bottom: 20px; } @@ -9793,14 +9969,14 @@ noscript { color: inherit; text-decoration: none; padding: 4px 12px; - background: $ui-base-color; + background: var(--surface-variant-background-color); border-radius: 4px; font-weight: 500; &:hover, &:focus, &:active { - background: lighten($ui-base-color, 4%); + background: var(--surface-variant-active-background-color); } } @@ -10059,6 +10235,7 @@ noscript { font-weight: 500; font-size: 11px; line-height: 16px; + word-break: keep-all; &__badge { background: $ui-button-background-color; @@ -10128,3 +10305,198 @@ noscript { } } } + +.more-from-author { + box-sizing: border-box; + font-size: 14px; + color: $darker-text-color; + background: var(--surface-background-color); + border: 1px solid var(--background-border-color); + border-top: 0; + border-radius: 0 0 8px 8px; + padding: 15px; + display: flex; + align-items: center; + gap: 8px; + + .logo { + height: 16px; + color: $darker-text-color; + } + + & > span { + display: flex; + align-items: center; + gap: 8px; + } + + a { + display: inline-flex; + align-items: center; + gap: 4px; + font-weight: 500; + color: $primary-text-color; + text-decoration: none; + + &:hover, + &:focus, + &:active { + color: $highlight-text-color; + } + } +} + +.hover-card-controller[data-popper-reference-hidden='true'] { + opacity: 0; + pointer-events: none; +} + +.hover-card { + box-shadow: var(--dropdown-shadow); + background: var(--modal-background-color); + backdrop-filter: var(--background-filter); + border: 1px solid var(--modal-border-color); + border-radius: 8px; + padding: 16px; + width: 270px; + display: flex; + flex-direction: column; + gap: 12px; + + &--loading { + position: relative; + min-height: 100px; + } + + &__name { + display: flex; + gap: 12px; + text-decoration: none; + color: inherit; + } + + &__number { + font-size: 15px; + line-height: 22px; + color: $secondary-text-color; + + strong { + font-weight: 700; + } + } + + &__text-row { + display: flex; + flex-direction: column; + gap: 8px; + } + + &__bio { + color: $secondary-text-color; + font-size: 14px; + line-height: 20px; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + max-height: 2 * 20px; + overflow: hidden; + + p { + margin-bottom: 0; + } + + a { + color: inherit; + text-decoration: underline; + + &:hover, + &:focus, + &:active { + text-decoration: none; + } + } + } + + .display-name { + font-size: 15px; + line-height: 22px; + + bdi { + font-weight: 500; + color: $primary-text-color; + } + + &__account { + display: block; + color: $dark-text-color; + } + } + + .account-fields { + color: $secondary-text-color; + font-size: 14px; + line-height: 20px; + + a { + color: inherit; + text-decoration: none; + + &:focus, + &:hover, + &:active { + text-decoration: underline; + } + } + + dl { + display: flex; + align-items: center; + gap: 4px; + + dt { + flex: 0 1 auto; + color: $dark-text-color; + min-width: 0; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + dd { + flex: 1 1 auto; + font-weight: 500; + min-width: 0; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + text-align: end; + } + + &.verified { + dd { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 4px; + overflow: hidden; + white-space: nowrap; + color: $valid-value-color; + + & > span { + overflow: hidden; + text-overflow: ellipsis; + } + + a { + font-weight: 500; + } + + .icon { + width: 16px; + height: 16px; + } + } + } + } + } +} diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index f6ec44fb53..26bb2bee14 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -613,9 +613,10 @@ code { font-family: inherit; pointer-events: none; cursor: default; - max-width: 140px; + max-width: 50%; white-space: nowrap; overflow: hidden; + text-overflow: ellipsis; &::after { content: ''; diff --git a/app/javascript/styles/mastodon/variables.scss b/app/javascript/styles/mastodon/variables.scss index 58b9dd9b61..2848a42b3f 100644 --- a/app/javascript/styles/mastodon/variables.scss +++ b/app/javascript/styles/mastodon/variables.scss @@ -106,4 +106,6 @@ $font-monospace: 'mastodon-font-monospace' !default; --background-color: #{darken($ui-base-color, 8%)}; --background-color-tint: #{rgba(darken($ui-base-color, 8%), 0.9)}; --surface-background-color: #{darken($ui-base-color, 4%)}; + --surface-variant-background-color: #{$ui-base-color}; + --surface-variant-active-background-color: #{lighten($ui-base-color, 4%)}; } diff --git a/app/javascript/styles/modern-contrast.scss b/app/javascript/styles/modern-contrast.scss index 1c674a84c4..b2166f813b 100644 --- a/app/javascript/styles/modern-contrast.scss +++ b/app/javascript/styles/modern-contrast.scss @@ -1,4 +1,4 @@ -// Mastodon Modern theme by Freeplay! Check the original repo for more info: https://codeberg.org/Freeplay/Mastodon-Modern +// Mastodon Modern theme by Freeplay! Check the original repo for more info: https://git.gay/freeplay/Mastodon-Modern // Everything in the "modern" directory is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License: http://creativecommons.org/licenses/by-sa/4.0/ @import 'contrast/variables'; diff --git a/app/javascript/styles/modern-dark.scss b/app/javascript/styles/modern-dark.scss index 64830fab2b..444ce76976 100644 --- a/app/javascript/styles/modern-dark.scss +++ b/app/javascript/styles/modern-dark.scss @@ -1,4 +1,4 @@ -// Mastodon Modern theme by Freeplay! Check the original repo for more info: https://codeberg.org/Freeplay/Mastodon-Modern +// Mastodon Modern theme by Freeplay! Check the original repo for more info: https://git.gay/freeplay/Mastodon-Modern // Everything in the "modern" directory is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License: http://creativecommons.org/licenses/by-sa/4.0/ @import 'mastodon/variables'; diff --git a/app/javascript/styles/modern-light.scss b/app/javascript/styles/modern-light.scss index 9afb1c04a8..0ce16e71e3 100644 --- a/app/javascript/styles/modern-light.scss +++ b/app/javascript/styles/modern-light.scss @@ -1,4 +1,4 @@ -// Mastodon Modern theme by Freeplay! Check the original repo for more info: https://codeberg.org/Freeplay/Mastodon-Modern +// Mastodon Modern theme by Freeplay! Check the original repo for more info: https://git.gay/freeplay/Mastodon-Modern // Everything in the "modern" directory is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License: http://creativecommons.org/licenses/by-sa/4.0/ @import 'mastodon-light/variables'; diff --git a/app/javascript/styles/modern/style.scss b/app/javascript/styles/modern/style.scss index 1046f10711..d2eefa69d2 100644 --- a/app/javascript/styles/modern/style.scss +++ b/app/javascript/styles/modern/style.scss @@ -1,48 +1,46 @@ :root { - --tl-width: 750px; + --tl-width: 720px; + --emoji-size: 2em; + --avatar-size: 46px; --radius: 12px; --radius-round: 24px; + --panel-radius: var(--radius); --hover-color: rgba(170,170,170,0.07); --elevated-color: rgba(150,150,200,0.1); --elevated-tint: rgba(200,200,240,0.07); --border-color: rgba(120,120,200,0.2); --border-color-2: #787878; --shadow: 0 10px 40px -10px rgba(0,0,0,0.15); - --shadow-low: 0 8px 16px -10px rgba(0,0,0,0.4); + --shadow-low: 0 8px 24px -16px rgba(0,0,0,0.2); --shadow-med: 0 8px 60px -30px rgba(0,0,0,0.1); + --column-shadow: 0 8px 24px 12px rgba(0,0,0,0.02); } -body::before { +@media (max-width: 889px) { + :root { + --panel-radius: 0px; + } +} +.layout-multiple-columns { + --panel-radius: 0px; +} +body { + font-display: swap !important; +} +body:not(.admin)::before { content: ""; position: fixed; inset: 0; background: rgba(0,0,0,0.06); z-index: -1; } -:not(body):not(.scrollable)::-webkit-scrollbar { - width: 6px; - margin-block: 10px; +p { + line-height: 1.5; } -:not(body):not(.scrollable)::-webkit-scrollbar-track { - background: none; -} -:not(body):not(.scrollable)::-webkit-scrollbar-thumb { - border-radius: 100px; - transition: background-color 0.2s; -} -:not(body):not(.scrollable):not(:hover)::-webkit-scrollbar-thumb { - background: none; - padding-block: 10px; -} -.rtl textarea { - text-align: right; -} -a, -button, -label { - user-select: none; +input { + text-align: start; } .button--block { - font-weight: 700; + font-weight: bold; } .unhandled-link span, .mention span { @@ -64,7 +62,7 @@ video, .react-toggle-track, .reply-indicator, .compose-form__warning { - border-radius: var(--radius) !important; + border-radius: var(--radius); } button:focus-visible, .focusable:focus-visible, @@ -74,14 +72,51 @@ a:focus-visible, outline: 2px #dc7b18 solid; outline-offset: -2px; } -*:not(.radio-button__input):not(input) { - font-display: swap !important; -} :not(.radio-button__input):not(span) { border-color: var(--border-color) !important; } -.setting-text { - padding-inline: 10px; +.nothing-here, +.column-inline-form, +.scrollable, +.detailed-status__action-bar, +.column-back-button, +.column-header__collapsible.collapsed, +.column-header__collapsible-inner, +.audio-player, +.search__input { + border: 0 !important; +} +.account__section-headline, +.notification__filter-bar, +.column-header { + border-inline: 0; +} +.account__section-headline, +.notification__filter-bar, +.column > .scrollable { + background: none; +} +.account__avatar, +#profile_page_avatar, +.account__avatar-composite, +.account-card__title__avatar img { + border-radius: 30%; + flex: none; +} +:not(body):not(.scrollable)::-webkit-scrollbar { + width: 6px; + margin-block: 10px; +} +:not(body):not(.scrollable)::-webkit-scrollbar-track { + background: none; +} +:not(body):not(.scrollable)::-webkit-scrollbar-thumb { + border-radius: 100px; + transition: background-color 0.2s; +} +:not(body):not(.scrollable):not(:hover)::-webkit-scrollbar-thumb { + background: none; + padding-block: 10px; } @media (prefers-reduced-motion: no-preference) { body:not(.reduce-motion) .load-more, @@ -136,7 +171,7 @@ a:focus-visible, body:not(.reduce-motion) .react-toggle-track, body:not(.reduce-motion) .icon-button, body:not(.reduce-motion) .floating-action-button { - transition: transform 0.4s cubic-bezier(0, 0, 0, 4) !important; + transition: transform 0.4s cubic-bezier(0, 0, 0, 4), background 0.2s !important; } body:not(.reduce-motion) .column-header__button:active, body:not(.reduce-motion) .column-header__buttons > .column-header__back-button:active, @@ -273,25 +308,25 @@ a:focus-visible, filter: opacity(0); } } -@-moz-keyframes slideDowFade { +@-moz-keyframes slideDownFade { from { transform: translateY(-20px); filter: opacity(0); } } -@-webkit-keyframes slideDowFade { +@-webkit-keyframes slideDownFade { from { transform: translateY(-20px); filter: opacity(0); } } -@-o-keyframes slideDowFade { +@-o-keyframes slideDownFade { from { transform: translateY(-20px); filter: opacity(0); } } -@keyframes slideDowFade { +@keyframes slideDownFade { from { transform: translateY(-20px); filter: opacity(0); @@ -406,71 +441,6 @@ a:focus-visible, } } } -.account__avatar, -#profile_page_avatar, -.account__avatar-composite, -.account-card__title__avatar img { - border-radius: 30% !important; -} -.scrollable, -.detailed-status__action-bar, -.column-back-button, -.column-header__collapsible.collapsed, -.column-header__collapsible-inner, -.audio-player, -.search__input { - border: 0 !important; -} -.account__section-headline, -.notification__filter-bar, -.column-header { - border-inline: 0; -} -.dropdown-menu, -.dropdown-animation { - border-radius: var(--radius); - animation: scaleIn 0.2s cubic-bezier(0, 0, 0, 1.1); -} -.dropdown-menu__container__list { - overflow: hidden auto; - border-radius: var(--radius); - max-height: 70vh; -} -.dropdown-menu__item { - overflow: hidden; -} -.dropdown-menu__item a { - padding: 0.7em 1em !important; - transition: background-color 0.2s, color 0.2s; - min-width: 150px; -} -.dropdown-menu__separator { - margin: 0 !important; -} -.interaction-modal { - border-radius: var(--radius); - overflow-y: auto; -} -.interaction-modal__choices { - gap: 10px; - display: flex; - flex-wrap: wrap; -} -.interaction-modal__choices .interaction-modal__choices__choice { - max-height: 50vh; - overflow-y: auto; - border: 1px solid var(--border-color); - flex: 1 0 270px; - border-radius: var(--radius); - transition: background 0.2s; - position: relative; -} -.compare-history-modal { - margin-block: 20px; -} -.compare-history-modal__container { - overflow: hidden auto; -} .columns-area__panels { --top: 5px; gap: 0; @@ -489,57 +459,7 @@ a:focus-visible, --top: 30px; } } -.emoji-picker-dropdown__menu { - border-radius: var(--radius); - overflow: hidden; - resize: both; - width: 400px; -} -.emoji-mart { - display: flex !important; - flex-direction: column !important; - width: 100% !important; - height: 100% !important; -} -.emoji-mart-scroll { - flex-grow: 1; - max-height: unset !important; -} -.emoji-mart-bar { - order: 2; -} -.emoji-mart-category-list { - overflow: visible !important; - display: grid; - grid-template-columns: repeat(auto-fill, minmax(calc(20px + 6%), 1fr)); -} -.emoji-mart-category-list li { - display: contents; -} -.emoji-mart-category-list button { - position: relative; - padding: 0 !important; - padding-top: 100% !important; -} -.emoji-mart-category-list button img, -.emoji-mart-category-list button span { - height: calc(100% - 10px) !important; - width: calc(100% - 10px) !important; - inset: 5px; - position: absolute !important; - transition: transform 0.1s cubic-bezier(0, 0, 0, 1); -} -.emoji-mart-category-list button:hover img, -.emoji-mart-category-list button:hover span { - transform: scale(1.2); -} -.emoji-picker-dropdown__modifiers { - top: 16px; -} -#mastodon { - overflow: clip; -} -#mastodon .compose-panel { +.compose-panel { overflow-y: auto; margin-top: calc(0px - var(--top)); padding-top: var(--top); @@ -549,27 +469,27 @@ a:focus-visible, max-height: unset !important; height: 100%; } -#mastodon .compose-panel > * { +.compose-panel > * { padding-inline: 0; } -#mastodon .compose-panel > .navigation-bar { +.compose-panel > .navigation-bar { padding-top: 0 !important; } -#mastodon .compose-panel .search, +.compose-panel .search, .drawer .search { margin-bottom: 25px; } -#mastodon .search { +.search { border-radius: var(--radius); margin-inline: -5px; } -#mastodon .search label { +.search label { box-sizing: border-box; } -#mastodon .search input { +.search input { border-radius: var(--radius-round) !important; } -#mastodon .search .search__icon > i { +.search .search__icon > i { margin-inline: 5px; } .search__popout { @@ -580,65 +500,66 @@ a:focus-visible, margin-inline: 4px; width: calc(100% - 8px); } -#mastodon .navigation-bar .icon-button { +.navigation-bar .icon-button { width: auto !important; height: auto !important; padding: 8px; } -#mastodon .compose-form { +.compose-form { min-height: unset; overflow: unset; gap: 15px; + flex: 1 0 auto !important; } -#mastodon .compose-form > * { +.compose-form > * { margin: 0 !important; } -#mastodon .compose-form > [aria-hidden="true"] { +.compose-form > [aria-hidden="true"] { display: none; } -#mastodon .compose-form > .navigation-bar { +.compose-form > .navigation-bar { margin-top: 10px; } -#mastodon .compose-form .reply-indicator { +.compose-form .reply-indicator { position: relative; transition: min-height 0.1s; } -#mastodon .compose-form .reply-indicator__display-name { +.compose-form .reply-indicator__display-name { padding: 0; } -#mastodon .compose-form .compose-form__autosuggest-wrapper, -#mastodon .compose-form .autosuggest-textarea__textarea { +.compose-form .compose-form__autosuggest-wrapper, +.compose-form .autosuggest-textarea__textarea { border-radius: var(--radius) var(--radius) 0 0 !important; border-bottom: 0; } -#mastodon .compose-form .compose-form__buttons-wrapper { +.compose-form .compose-form__buttons-wrapper { border-radius: 0 0 var(--radius) var(--radius) !important; } -#mastodon .compose-form .compose-form__publish-button-wrapper { +.compose-form .compose-form__publish-button-wrapper { overflow: visible !important; max-width: 100%; padding: 0; } -#mastodon .compose-form .compose-form__upload__actions { +.compose-form .compose-form__upload__actions { z-index: 5; position: relative; } -#mastodon .compose-form .compose-form__upload__actions button { +.compose-form .compose-form__upload__actions button { background: none; } -#mastodon .compose-form .compose-form__upload__thumbnail { +.compose-form .compose-form__upload__thumbnail { display: flex; flex-direction: column; } -#mastodon .compose-form .compose-form__upload__warning { +.compose-form .compose-form__upload__warning { position: relative; flex-grow: 1; display: flex; } -#mastodon .compose-form .compose-form__upload__warning button { +.compose-form .compose-form__upload__warning button { margin-top: auto; } -#mastodon .compose-form .compose-form__upload__warning button.active { +.compose-form .compose-form__upload__warning button.active { box-shadow: 0 0 0 100px rgba(0,0,0,0.75); width: 100%; height: 100%; @@ -647,61 +568,61 @@ a:focus-visible, color: inherit; transition: background 0.2s, transform 0.2s cubic-bezier(0, 0, 0, 1) !important; } -#mastodon .compose-form .compose-form__upload__warning button.active svg { +.compose-form .compose-form__upload__warning button.active svg { height: 1.2em; width: 1.2em; } -#mastodon .compose-form .compose-form__upload__warning button.active:hover, -#mastodon .compose-form .compose-form__upload__warning button.active:focus { +.compose-form .compose-form__upload__warning button.active:hover, +.compose-form .compose-form__upload__warning button.active:focus { background: rgba(20,20,20,0.75); } -#mastodon .compose-form__highlightable { +.compose-form__highlightable { border-radius: var(--radius); overflow: visible !important; } -#mastodon .compose-form__highlightable #cw-spoiler-input { +.compose-form__highlightable #cw-spoiler-input { border-radius: 0 !important; } -#mastodon .compose-form__highlightable textarea { +.compose-form__highlightable textarea { background: none !important; } -#mastodon .compose-form__highlightable > .compose-form__footer { +.compose-form__highlightable > .compose-form__footer { gap: 12px; } -#mastodon .compose-form__highlightable > .compose-form__footer .compose-form__dropdowns { +.compose-form__highlightable > .compose-form__footer .compose-form__dropdowns { max-width: calc(100% - 7ch); } -#mastodon .compose-form__highlightable > .compose-form__footer .compose-form__actions { +.compose-form__highlightable > .compose-form__footer .compose-form__actions { position: relative; } -#mastodon .compose-form__highlightable > .compose-form__footer .compose-form__buttons { +.compose-form__highlightable > .compose-form__footer .compose-form__buttons { display: flex; flex-wrap: wrap; flex-direction: row; gap: 0; flex-grow: 9999; } -#mastodon .compose-form__highlightable > .compose-form__footer .compose-form__buttons > * { +.compose-form__highlightable > .compose-form__footer .compose-form__buttons * { + display: flex; flex-grow: 1; - box-sizing: border-box; } -#mastodon .compose-form__highlightable > .compose-form__footer .compose-form__buttons button { +.compose-form__highlightable > .compose-form__footer .compose-form__buttons label { + display: none; +} +.compose-form__highlightable > .compose-form__footer .compose-form__buttons button { flex-grow: 1; padding: 5px; } -#mastodon .compose-form__highlightable > .compose-form__footer .compose-form__submit button { +.compose-form__highlightable > .compose-form__footer .compose-form__submit button { padding: 8px 16px; } -#mastodon .compose-form__highlightable > .compose-form__footer .character-counter { +.compose-form__highlightable > .compose-form__footer .character-counter { position: absolute; inset-inline-end: 0; bottom: calc(100% + 12px); padding: 4px; font-size: 13px; } -.server-banner { - padding: 10px; -} .server-banner .server-banner__hero { border-radius: var(--radius); width: 100%; @@ -753,18 +674,17 @@ a:focus-visible, margin-inline: 5px; position: relative; } -.navigation-panel__sign-in-banner .sign-in-banner p { - line-height: 1.5; -} -.navigation-panel__sign-in-banner .sign-in-banner > :last-child { - margin-bottom: 0; -} -#mastodon .link-footer { +.link-footer { margin-top: 20px; } -#mastodon .link-footer > p:last-child { +.link-footer > p:last-child { margin-bottom: 0; } +.columns-area { + box-shadow: var(--column-shadow); + padding: 0; + overflow: visible; +} .columns-area__panels__main { overflow: visible !important; transition: max-width 0.2s cubic-bezier(0, 0, 0, 1.1), margin 0.2s cubic-bezier(0, 0, 0, 1.1); @@ -773,14 +693,13 @@ a:focus-visible, .columns-area__panels__main { width: 0; flex-grow: 1; - padding-inline: 10px; + margin-inline: 10px; + max-width: var(--tl-width) !important; } } @media (min-width: 1320px) { .columns-area__panels__main { - max-width: var(--tl-width) !important; - padding: 0 15px; - margin: 0 10px; + margin: 0 20px; } } @media (min-width: 890px) { @@ -790,6 +709,7 @@ a:focus-visible, } .columns-area__panels__main .column, .columns-area__panels__main .columns-area { + grid-column: 1; overflow: clip !important; border-radius: var(--radius) var(--radius) 0 0 !important; } @@ -801,2219 +721,294 @@ a:focus-visible, .columns-area__panels__main > div { grid-row: 1; } -.columns-area__panels__main :not(.scrollable--flex) > .scrollable { - padding-bottom: 40vh !important; -} -#mastodon .column { +.column { background: var(--background-color); + overflow: clip; } -#mastodon .column::before { +.column::before { content: ""; position: absolute; inset: 0; background: var(--elevated-tint); pointer-events: none; } -#mastodon .columns-area { - box-shadow: 0 8px 24px 12px rgba(0,0,0,0.02); -} -#mastodon .column-header__wrapper ~ .scrollable { - overflow: auto !important; -} -#mastodon .scrollable:not(.scrollable--flex), -#mastodon .activity-stream { - contain: unset !important; -} -#mastodon .scrollable:not(.scrollable--flex):not(.activity-stream):not(.privacy-policy), -#mastodon .activity-stream:not(.activity-stream):not(.privacy-policy) { - padding: 10px; -} -#mastodon .scrollable:not(.scrollable--flex) > [tabindex]:first-child > .focusable, -#mastodon .activity-stream > [tabindex]:first-child > .focusable { - margin-top: 0 !important; -} -#mastodon .search-results__section__header { - margin: 0px -10px 10px; - color: unset; - background: none; - padding-inline: 20px; - font-weight: 600; -} -#mastodon .search-results__section { - border: 0; - margin-bottom: 20px; -} -#mastodon .dismissable-banner, -#mastodon .follow_requests-unlocked_explanation { - display: flex; - align-items: center; - border: 0 !important; - margin: -10px !important; - margin-bottom: 10px !important; - border-radius: 0px !important; - padding: 15px !important; - height: max-content; - isolation: isolate; - overflow: hidden; -} -.dismissable-banner__message { - padding-block: 10px; - order: -1; -} -#mastodon .dismissable-banner .dismissable-banner__action, -#mastodon .follow_requests-unlocked_explanation .dismissable-banner__action { - position: static; -} -#mastodon .dismissable-banner .scrollable:not(.scrollable--flex), -#mastodon .follow_requests-unlocked_explanation .scrollable:not(.scrollable--flex) { - padding: 0px !important; - padding-bottom: 40vh !important; -} -#mastodon .dismissable-banner .scrollable:not(.scrollable--flex)::before, -#mastodon .follow_requests-unlocked_explanation .scrollable:not(.scrollable--flex)::before { - content: ""; - position: absolute; - inset: 0; - background-color: inherit; - z-index: -1; -} -#mastodon .dismissable-banner .scrollable:not(.scrollable--flex) .account-timeline__header, -#mastodon .follow_requests-unlocked_explanation .scrollable:not(.scrollable--flex) .account-timeline__header, -#mastodon .dismissable-banner .scrollable:not(.scrollable--flex) .dismissable-banner, -#mastodon .follow_requests-unlocked_explanation .scrollable:not(.scrollable--flex) .dismissable-banner { - margin: 0px !important; -} -#mastodon .dismissable-banner .focusable, -#mastodon .follow_requests-unlocked_explanation .focusable, -#mastodon .dismissable-banner .entry, -#mastodon .follow_requests-unlocked_explanation .entry, -#mastodon .dismissable-banner .statuses-grid__item .detailed-status, -#mastodon .follow_requests-unlocked_explanation .statuses-grid__item .detailed-status, -#mastodon .dismissable-banner .trends__item, -#mastodon .follow_requests-unlocked_explanation .trends__item, -#mastodon .dismissable-banner .story, -#mastodon .follow_requests-unlocked_explanation .story, -#mastodon .dismissable-banner .account-card, -#mastodon .follow_requests-unlocked_explanation .account-card, -#mastodon .dismissable-banner .scrollable :not(.focusable) > .account, -#mastodon .follow_requests-unlocked_explanation .scrollable :not(.focusable) > .account, -#mastodon .dismissable-banner .timeline-hint, -#mastodon .follow_requests-unlocked_explanation .timeline-hint { - border-radius: 0; -} -#mastodon .dismissable-banner .focusable::before, -#mastodon .follow_requests-unlocked_explanation .focusable::before, -#mastodon .dismissable-banner .entry::before, -#mastodon .follow_requests-unlocked_explanation .entry::before, -#mastodon .dismissable-banner .statuses-grid__item .detailed-status::before, -#mastodon .follow_requests-unlocked_explanation .statuses-grid__item .detailed-status::before, -#mastodon .dismissable-banner .trends__item::before, -#mastodon .follow_requests-unlocked_explanation .trends__item::before, -#mastodon .dismissable-banner .story::before, -#mastodon .follow_requests-unlocked_explanation .story::before, -#mastodon .dismissable-banner .account-card::before, -#mastodon .follow_requests-unlocked_explanation .account-card::before, -#mastodon .dismissable-banner .scrollable :not(.focusable) > .account::before, -#mastodon .follow_requests-unlocked_explanation .scrollable :not(.focusable) > .account::before, -#mastodon .dismissable-banner .timeline-hint::before, -#mastodon .follow_requests-unlocked_explanation .timeline-hint::before { - border-radius: 0 !important; -} -#mastodon .dismissable-banner .focusable::after, -#mastodon .follow_requests-unlocked_explanation .focusable::after, -#mastodon .dismissable-banner .entry::after, -#mastodon .follow_requests-unlocked_explanation .entry::after, -#mastodon .dismissable-banner .statuses-grid__item .detailed-status::after, -#mastodon .follow_requests-unlocked_explanation .statuses-grid__item .detailed-status::after, -#mastodon .dismissable-banner .trends__item::after, -#mastodon .follow_requests-unlocked_explanation .trends__item::after, -#mastodon .dismissable-banner .story::after, -#mastodon .follow_requests-unlocked_explanation .story::after, -#mastodon .dismissable-banner .account-card::after, -#mastodon .follow_requests-unlocked_explanation .account-card::after, -#mastodon .dismissable-banner .scrollable :not(.focusable) > .account::after, -#mastodon .follow_requests-unlocked_explanation .scrollable :not(.focusable) > .account::after, -#mastodon .dismissable-banner .timeline-hint::after, -#mastodon .follow_requests-unlocked_explanation .timeline-hint::after { - inset-inline: 0 !important; -} -#mastodon .dismissable-banner [class*="explore__"] > *, -#mastodon .follow_requests-unlocked_explanation [class*="explore__"] > * { - border-radius: var(--radius); -} -#mastodon .dismissable-banner .detailed-status__wrapper, -#mastodon .follow_requests-unlocked_explanation .detailed-status__wrapper { - margin: 0 !important; -} -#mastodon .dismissable-banner .status__action-bar, -#mastodon .follow_requests-unlocked_explanation .status__action-bar { - margin-bottom: 0px; - gap: 0; - margin-inline-end: 0 !important; -} -#mastodon .dismissable-banner .status__action-bar :not(i):not(.status__action-bar-spacer), -#mastodon .follow_requests-unlocked_explanation .status__action-bar :not(i):not(.status__action-bar-spacer) { - display: flex; - flex-grow: 9999; - justify-content: center !important; - max-width: 55px; - min-width: max-content; -} -#mastodon .dismissable-banner .status__action-bar > .icon-button:first-child, -#mastodon .follow_requests-unlocked_explanation .status__action-bar > .icon-button:first-child { - margin-inline-start: -8px !important; -} -#mastodon .dismissable-banner .status__action-bar, -#mastodon .follow_requests-unlocked_explanation .status__action-bar, -#mastodon .dismissable-banner .detailed-status__action-bar, -#mastodon .follow_requests-unlocked_explanation .detailed-status__action-bar, -#mastodon .dismissable-banner .picture-in-picture__footer, -#mastodon .follow_requests-unlocked_explanation .picture-in-picture__footer { - flex-wrap: wrap; -} -@media (max-width: 890px) { - #mastodon .dismissable-banner, - #mastodon .follow_requests-unlocked_explanation { - margin: 0 !important; - } -} -#mastodon .column:not(.scrollable--flex) > .dismissable-banner { - margin: 0 !important; -} -#mastodon .column:not(.scrollable--flex) > .dismissable-banner ~ .scrollable { - border-radius: 0 !important; -} -#mastodon :not(.account__section-headline) + .scrollable > .dismissable-banner { - margin: 0px !important; - margin-bottom: 0 !important; -} -#mastodon .empty-column-indicator { - contain: unset; - padding: 10px !important; - color: unset; - opacity: 0.8; - font-size: 1.2em; - line-height: 2; -} -#mastodon .empty-column-indicator a { - display: block; - font-weight: 700; - font-size: 1.1em; -} -#mastodon .scrollable--flex .account-timeline__header { - margin: 0 !important; -} -#mastodon .item-list { - background-color: inherit; - border-radius: var(--radius); -} -#mastodon .account-timeline__header { - margin: -10px; - margin-bottom: 10px; - background-color: inherit; - border-radius: var(--radius) !important; - animation: fade backwards 0.4s cubic-bezier(0, 1, 1, 1); -} -#mastodon .account-timeline__header .account__moved-note { - box-sizing: border-box; - border-radius: var(--radius); - margin-bottom: calc(0px - var(--radius)); - padding: 30px; - padding-bottom: calc(var(--radius) + 30px); - background: inherit; -} -#mastodon .account-timeline__header .account__moved-note .detailed-status__display-name { - overflow: visible !important; -} -#mastodon .account-timeline__header .account__header { - overflow: visible !important; - border-radius: var(--radius) var(--radius) 0 0; - background: inherit; -} -#mastodon .account-timeline__header .account__header__info { - z-index: 2; -} -#mastodon .account-timeline__header .account__header__image { - height: 0; - padding-bottom: 35%; - border-radius: var(--radius) var(--radius) 0 0; - position: sticky; - top: calc(0px - var(--radius)); - overflow: clip; -} -#mastodon .account-timeline__header .account__header__image img { - position: absolute; -} -#mastodon .account-timeline__header .account__header__image .account__header__info { - height: 100%; -} -#mastodon .account-timeline__header .account__header__image .account__header__info > span { - position: sticky; - top: var(--radius); -} -#mastodon .account-timeline__header .account__header__bar { - position: relative; - z-index: 2; - border: 0; - padding-inline: 20px; - border-radius: var(--radius) var(--radius) 0 0; - margin-top: calc(0px - var(--radius)) !important; - display: flex; - flex-direction: column; - background: var(--background-color); - isolation: isolate; -} -@media (max-width: 890px) { - #mastodon .account-timeline__header .account__header__bar { - padding-inline: 15px; - } -} -#mastodon .account-timeline__header .account__header__bar::before { - content: ""; - background: var(--elevated-tint); - position: absolute; - inset: 0; - pointer-events: none; -} -#mastodon .account-timeline__header .account__header__bar::after { - content: ""; - position: absolute; - inset-inline: 0; - height: 95px; - background: inherit; - z-index: -1; - border-radius: var(--radius); - mask: linear-gradient(to bottom, transparent, #000); -} -#mastodon .account-timeline__header .account__header__bar .account__header__tabs { - overflow: visible !important; - align-items: flex-end; - padding: 0; - height: unset !important; - margin-top: -55px !important; -} -#mastodon .account-timeline__header .account__header__bar .account__header__tabs::before { - content: ""; - position: absolute; - top: -55px; - inset-inline: 0; - height: 150px; - backdrop-filter: blur(40px); - filter: brightness(1.1); - pointer-events: none; - opacity: 0.7; - z-index: -2; - clip-path: inset(55px 0 0 0 round var(--radius)); -} -#mastodon .account-timeline__header .account__header__bar .account__header__tabs ~ div { - z-index: 2; -} -#mastodon .account-timeline__header .account__header__bar .avatar { - margin-inline-start: 0 !important; - overflow: visible !important; - position: relative; - margin-top: 20px; -} -#mastodon .account-timeline__header .account__header__bar .avatar .account-role { - position: absolute; - bottom: 0; - left: 110%; - border-radius: var(--radius); -} -#mastodon .account-timeline__header .account__header__bar .account__avatar { - border: 0; - box-shadow: var(--shadow); - background: none; - background-size: cover !important; -} -#mastodon .account-timeline__header .account__header__tabs__name { - margin-bottom: 0; - padding: 0; - margin-top: 16px; -} -#mastodon .account-timeline__header .account__header__tabs__name h1 { - display: flex; - flex-wrap: wrap; - white-space: unset; - gap: 0 0.4em; - font-weight: 600; -} -#mastodon .account-timeline__header .account__header__extra { - margin-top: 5px; -} -#mastodon .account-timeline__header .account__header__bio { - margin: 0; -} -#mastodon .account-timeline__header .account__header__bio > * { - padding-inline: 0; -} -#mastodon .account-timeline__header .account__header__bio .account__header__content { - padding: 0px; -} -#mastodon .account-timeline__header .account__header__badges { - margin-top: 10px; -} -#mastodon .account-timeline__header .account__header__badges span { - font-weight: 600; -} -#mastodon .account__header__fields, -#mastodon .account__header__account-note { - display: flex; - flex-wrap: wrap; - gap: 2px; - background: none; - border-radius: var(--radius); - overflow: hidden; - width: max-content; - max-width: 100%; - border: 0; -} -#mastodon .account__header__fields:first-child, -#mastodon .account__header__account-note:first-child { - margin-block: 5px 15px; -} -#mastodon .account__header__fields dl, -#mastodon .account__header__account-note dl { - display: inline; - border-radius: 0; - overflow: hidden; - border: 0; - padding: 8px 12px !important; - position: relative; - overflow: hidden; - flex-grow: 1; -} -#mastodon .account__header__fields dl:not(.verified), -#mastodon .account__header__account-note dl:not(.verified) { - background-color: var(--elevated-color); -} -#mastodon .account__header__fields dl dt, -#mastodon .account__header__account-note dl dt { - all: unset; - color: unset; - opacity: 0.9; -} -#mastodon .account__header__fields dl dd, -#mastodon .account__header__account-note dl dd { - padding: 0; - white-space: unset; - max-height: unset; - text-align: unset; -} -#mastodon .account__header__fields dl dd > span > a:first-child:last-child::after, -#mastodon .account__header__account-note dl dd > span > a:first-child:last-child::after, -#mastodon .account__header__fields dl dd .h-card:first-child:last-child a::after, -#mastodon .account__header__account-note dl dd .h-card:first-child:last-child a::after { - content: ""; - position: absolute; - inset: 0; - background-color: var(--hover-color); - opacity: 0; - transition: opacity 0.2s; -} -#mastodon .account__header__fields dl dd > span > a:first-child:last-child:hover:after, -#mastodon .account__header__account-note dl dd > span > a:first-child:last-child:hover:after, -#mastodon .account__header__fields dl dd .h-card:first-child:last-child a:hover:after, -#mastodon .account__header__account-note dl dd .h-card:first-child:last-child a:hover:after, -#mastodon .account__header__fields dl dd > span > a:first-child:last-child:focus:after, -#mastodon .account__header__account-note dl dd > span > a:first-child:last-child:focus:after, -#mastodon .account__header__fields dl dd .h-card:first-child:last-child a:focus:after, -#mastodon .account__header__account-note dl dd .h-card:first-child:last-child a:focus:after { - opacity: 1; -} -#mastodon .account__header__fields dl dd.verified, -#mastodon .account__header__account-note dl dd.verified { - overflow: visible !important; - border: 0; - background: none; -} -#mastodon .account__header__fields dl dd.verified a::before, -#mastodon .account__header__account-note dl dd.verified a::before, -#mastodon .account__header__fields dl dd.verified a::after, -#mastodon .account__header__account-note dl dd.verified a::after { - content: ""; - position: absolute; - inset: 0; - background: currentcolor; - opacity: 0.2; -} -#mastodon .account__header__fields dl dd.verified a::after, -#mastodon .account__header__account-note dl dd.verified a::after { - background: linear-gradient(20deg, currentcolor, transparent) !important; - opacity: 0.2 !important; - z-index: -2; -} -#mastodon .account__header__fields.account__header__account-note, -#mastodon .account__header__account-note.account__header__account-note { - display: flex; - margin-bottom: 10px !important; - gap: 0; - border: 0; - padding: 0 !important; - margin-inline: 0 !important; - background: none !important; - border-radius: 8px; - font-size: 12px; - width: unset; -} -#mastodon .account__header__fields.account__header__account-note label, -#mastodon .account__header__account-note.account__header__account-note label { - z-index: 2; - padding: 0 !important; - padding-inline-end: 0.7em !important; - pointer-events: none; - line-height: inherit; - font-size: inherit; - font-weight: inherit; - vertical-align: unset; -} -#mastodon .account__header__fields.account__header__account-note:focus-within, -#mastodon .account__header__account-note.account__header__account-note:focus-within { - padding: 0.5em 0.7em !important; - margin-inline: -5px !important; - border: 1px solid; -} -#mastodon .account__header__fields.account__header__account-note:not(:focus-within), -#mastodon .account__header__account-note.account__header__account-note:not(:focus-within) { - border-radius: 0; - border-bottom: 1px solid; - padding-bottom: 0.4em !important; -} -#mastodon .account__header__fields.account__header__account-note textarea, -#mastodon .account__header__account-note.account__header__account-note textarea { - width: 0; - flex-grow: 1; - margin: 0 !important; - border-radius: 0; - padding: 0; - margin: -100px !important; - padding: 100px !important; - padding-inline-end: 0.7em !important; - margin-inline-end: -0.7em !important; - line-height: inherit; - font-size: inherit; - font-weight: inherit; - vertical-align: unset; - background: none; -} -#mastodon .account__header__fields.account__header__account-note textarea::placeholder, -#mastodon .account__header__account-note.account__header__account-note textarea::placeholder { - font-weight: 600; -} -#mastodon .account__header__fields.account__header__account-note label, -#mastodon .account__header__account-note.account__header__account-note label { - margin: 0; - font-weight: 600; - padding-inline: 14px; -} -#mastodon :not(.explore__search-header) + .account__section-headline:not(:first-child) { - background: none; - position: relative; - z-index: 2; -} -.with-modals #mastodon :not(.explore__search-header) + .account__section-headline:not(:first-child) { - border: 0 !important; -} -#mastodon :not(.explore__search-header) + .account__section-headline:not(:first-child) a, -#mastodon :not(.explore__search-header) + .account__section-headline:not(:first-child) button { - background: none; - border-radius: 0 !important; -} -#mastodon :not(.explore__search-header) + .account__section-headline:not(:first-child) a span, -#mastodon :not(.explore__search-header) + .account__section-headline:not(:first-child) button span { - font-weight: 400; - opacity: 0.7; - transition: opacity 0.2s; -} -#mastodon :not(.explore__search-header) + .account__section-headline:not(:first-child) a.active span, -#mastodon :not(.explore__search-header) + .account__section-headline:not(:first-child) button.active span { - font-weight: 700; - opacity: 1; -} -#mastodon :not(.explore__search-header) + .account__section-headline:not(:first-child) a:hover span, -#mastodon :not(.explore__search-header) + .account__section-headline:not(:first-child) button:hover span, -#mastodon :not(.explore__search-header) + .account__section-headline:not(:first-child) a:active span, -#mastodon :not(.explore__search-header) + .account__section-headline:not(:first-child) button:active span { - opacity: 1 !important; -} -#mastodon :not(.explore__search-header) + .account__section-headline:not(:first-child) a::before, -#mastodon :not(.explore__search-header) + .account__section-headline:not(:first-child) button::before { - border-color: transparent transparent var(--border-color); -} -#mastodon .account__disclaimer { - border-top: 1px solid; -} -#mastodon .account-gallery__container { - border-radius: var(--radius); - overflow: clip; - padding: 0; - margin: 4px; - gap: 4px; - margin-bottom: calc(-40vh + 4px); -} -.account-gallery__item { - margin: 0; - flex: 1 1 calc(100px + 15%); - transition: flex 0.7s cubic-bezier(0, 0, 0, 1); - min-height: 180px !important; -} -.media-gallery__item-thumbnail { - transition: transform 0.2s; -} -.account-gallery__item:hover, -.account-gallery__item:focus-within { - flex-grow: 1.5; -} -.account-gallery__item:hover .media-gallery__item-thumbnail, -.account-gallery__item:focus-within .media-gallery__item-thumbnail { - transform: scale(1.02); -} -#mastodon .account-gallery__container > button { - width: unset; - flex-grow: 1; - flex: 1 1 calc(100px + 15% - 24px); - margin: 12px; - font-size: 1.2em; - font-weight: 700; - background: var(--elevated-color); - color: inherit; -} -#mastodon .account-gallery__container > button:hover:not(:focus) { - transform: scale(1.01); -} -@media (max-width: 890px) { - #mastodon #Follow-requests.column-header { - display: none; - } -} @media (min-width: 890px) { - #mastodon #Follow-requests ~ .scrollable .item-list { - display: grid; - align-items: stretch; - grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); - gap: 0 10px; - } - #mastodon #Follow-requests ~ .scrollable .item-list > article { - display: flex; - } -} -.layout-multiple-columns article:first-child .account-authorize__wrapper { - margin-top: 10px; -} -@media (max-width: 890px) { - #mastodon article:first-child .account-authorize__wrapper { + .layout-single-column .scrollable > [tabindex="-1"]:first-child { margin-top: 10px; } -} -#mastodon .account-authorize__wrapper { - background: var(--elevated-color); - border-radius: var(--radius); - overflow: hidden; - flex-grow: 1; - margin-bottom: 10px; - display: flex; - flex-direction: column; -} -@media (max-width: 890px) { - #mastodon .account-authorize__wrapper { - margin-inline: 10px; + .layout-single-column .item-list > article:first-of-type { + margin-top: 10px; + } + .layout-single-column .load-more, + .layout-single-column .trends__item, + .layout-single-column .focusable, + .layout-single-column .entry, + .layout-single-column .statuses-grid__item .detailed-status, + .layout-single-column .story, + .layout-single-column .account-card, + .layout-single-column .scrollable :not(.focusable) > .account:not(.account--minimal), + .layout-single-column .timeline-hint { + margin-inline: 10px !important; + max-width: calc(100% - 20px); } } -.layout-multiple-columns #mastodon .account-authorize__wrapper { - margin-inline: 10px; +.scrollable { + padding-bottom: 40vh !important; } -#mastodon .account-authorize__wrapper .account-authorize { - padding: 20px 15px 10px; -} -#mastodon .account-authorize__wrapper .detailed-status__display-name { - margin-bottom: 10px !important; -} -#mastodon .account-authorize__wrapper .account--panel { - margin-top: auto; - border-bottom: 0; - padding-inline: 15px; - gap: 10px; +.empty-column-indicator, +.error-column { background: none; } -#mastodon .account-authorize__wrapper br { - display: block; -} -#mastodon .account-authorize__wrapper p { - margin-bottom: 0.2em; -} -#mastodon .account-authorize__wrapper .account--panel__button:first-child .icon-button:not(:hover):not(:focus) { - background: var(--elevated-color); -} -#mastodon .account-authorize__wrapper .icon-button { - width: 100% !important; - padding: 10px; - height: unset !important; -} -#mastodon .account-card { - display: flex; - flex-direction: column; - max-height: 360px; - margin: 0; - border-radius: var(--radius) !important; - background: var(--elevated-color); - box-shadow: none !important; - box-shadow: var(--shadow); -} -.explore__suggestions, -.directory__list { - padding: 15px; - display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); - gap: 10px; -} -.explore__suggestions.directory__list, -.directory__list.directory__list { - padding: 15px 10px; -} -.layout-multiple-columns .explore__suggestions, -.layout-multiple-columns .directory__list { - display: block; -} -.layout-multiple-columns .explore__suggestions > *, -.layout-multiple-columns .directory__list > * { - margin: 10px !important; -} -@media (max-width: 890px) { - .explore__suggestions, - .directory__list { - gap: 0 !important; - } - .explore__suggestions > *, - .directory__list > * { - margin: 10px !important; - } -} -#mastodon .account-card .account-card__header { - padding: 0 !important; -} -#mastodon .account-card .account-card__title { - margin-bottom: 10px; - margin-top: -24px; -} -#mastodon .account-card .account-card__title__avatar { - padding-inline-end: 10px; - padding-bottom: 0; -} -#mastodon .account-card .display-name { - padding-bottom: 0; -} -#mastodon .account-card .display-name__account { - font-size: 0.9em !important; -} -#mastodon .account-card .account-card__title__avatar .account__avatar, -#mastodon .account-card .account-card__title__avatar { - width: 64px !important; - height: 64px !important; - background-size: 64px 64px !important; -} -#mastodon .account-card .account-card__title__avatar .account__avatar img, -#mastodon .account-card .account-card__title__avatar img { - width: inherit; - height: inherit; -} -#mastodon .account-card .account-card__title { - padding-inline-end: 15px; -} -#mastodon .account-card .display-name bdi { - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; -} -#mastodon .account-card .account-card__bio { - margin-top: 0 !important; - max-height: unset; - mask: linear-gradient(#000 65px, rgba(0,0,0,0.5), transparent); - -webkit-mask: linear-gradient(#000 65px, rgba(0,0,0,0.5), transparent); - flex-grow: 1; - margin-bottom: -50px; - margin-bottom: -15px; - font-size: 1em; - padding-bottom: 60px; -} -#mastodon .account-card .account-card__bio::after { - content: unset !important; -} -#mastodon .account-card .account-card__bio br { - display: block; -} -#mastodon .account-card .account-card__actions { - margin-top: auto !important; - display: unset !important; -} -#mastodon .account-card .account-card__actions .account-card__counters { - display: flex; - gap: 1em; - padding-inline: 15px; -} -#mastodon .account-card .account-card__actions .account-card__counters__item { +.dismissable-banner { display: flex; align-items: center; - font-size: 1em; -} -#mastodon .account-card .account-card__actions .account-card__counters__item > small { - display: inline !important; - margin-inline-start: 0.4em; - font-size: 1em !important; -} -#mastodon .account-card .account-card__actions .account-card__actions__button { - position: absolute; - top: 10px; - right: 10px; - padding: 0; - background: rgba(0,0,0,0.4); - border-radius: var(--radius-round); - padding: 4px; - box-shadow: 0 4px 12px rgba(0,0,0,0.2); -} -#mastodon .account-card .account-card__actions .account-card__actions__button button, -#mastodon .account-card .account-card__actions .account-card__actions__button a { - border-radius: var(--radius-round) !important; - padding: 0.7em 1.7em; - min-height: unset; - font-size: 14px !important; - line-height: 1.2; - font-size: 1em !important; -} -#mastodon .account-card .account-card__actions .account-card__actions__button:empty { - display: none; -} -#mastodon .account-card::after { - content: unset !important; -} -#mastodon .account__wrapper { - gap: 15px; - flex-wrap: wrap; -} -#mastodon .account__wrapper .account__display-name { - flex-grow: 100; -} -#mastodon .account__wrapper .account__contents { - line-height: 1.4; - flex-basis: 70%; - width: 100px; - flex-grow: 100; - display: flex; - align-items: center; - justify-content: space-between; - gap: 0 20px; - flex-wrap: wrap; -} -#mastodon .account__wrapper .account__contents * { - line-height: unset !important; -} -#mastodon .account__wrapper .account__contents .display-name { - height: unset; + flex-direction: row-reverse; + gap: 20px; margin: 0; - width: 27ch !important; - flex-grow: 1; -} -#mastodon .account__wrapper .account__contents .display-name span { - display: block; -} -#mastodon .account__wrapper .account__contents .account__details { - flex-direction: column; - width: 25ch; -} -#mastodon .account__wrapper .account__contents .account__details span { - white-space: break-spaces !important; -} -#mastodon .account__wrapper .account__contents .account__details:has(.verified-badge) > span:first-child { - display: none; -} -#mastodon .account__wrapper .account__relationship { - display: flex !important; - flex-wrap: wrap; - justify-content: flex-end; - min-width: 10ch; - gap: 10px; - flex-grow: 1; -} -#mastodon .account__wrapper .account__relationship button { - background: var(--elevated-color); - color: inherit; -} -#mastodon .scrollable > div:first-child > [tabindex="-1"]:last-child .status__wrapper::after { - content: unset; -} -.focusable, -.entry, -.statuses-grid__item .detailed-status, -.trends__item, -.story, -.account-card, -.scrollable :not(.focusable) > .account, -.timeline-hint { - overflow: hidden; - transition: background 0.2s, box-shadow 0.4s, border 0.2s; - animation: fade 0.4s backwards cubic-bezier(0, 1, 1, 1); - position: relative; - border-radius: var(--radius); -} -.focusable.trends__item, -.entry.trends__item, -.statuses-grid__item .detailed-status.trends__item, -.trends__item.trends__item, -.story.trends__item, -.account-card.trends__item, -.scrollable :not(.focusable) > .account.trends__item, -.timeline-hint.trends__item, -.focusable.story, -.entry.story, -.statuses-grid__item .detailed-status.story, -.trends__item.story, -.story.story, -.account-card.story, -.scrollable :not(.focusable) > .account.story, -.timeline-hint.story, -.focusable.account-card, -.entry.account-card, -.statuses-grid__item .detailed-status.account-card, -.trends__item.account-card, -.story.account-card, -.account-card.account-card, -.scrollable :not(.focusable) > .account.account-card, -.timeline-hint.account-card { - animation: slideUpFade backwards 0.44s cubic-bezier(0, 1, 1, 1); -} -.focusable.trends__item:nth-child(1), -.entry.trends__item:nth-child(1), -.statuses-grid__item .detailed-status.trends__item:nth-child(1), -.trends__item.trends__item:nth-child(1), -.story.trends__item:nth-child(1), -.account-card.trends__item:nth-child(1), -.scrollable :not(.focusable) > .account.trends__item:nth-child(1), -.timeline-hint.trends__item:nth-child(1), -.focusable.story:nth-child(1), -.entry.story:nth-child(1), -.statuses-grid__item .detailed-status.story:nth-child(1), -.trends__item.story:nth-child(1), -.story.story:nth-child(1), -.account-card.story:nth-child(1), -.scrollable :not(.focusable) > .account.story:nth-child(1), -.timeline-hint.story:nth-child(1), -.focusable.account-card:nth-child(1), -.entry.account-card:nth-child(1), -.statuses-grid__item .detailed-status.account-card:nth-child(1), -.trends__item.account-card:nth-child(1), -.story.account-card:nth-child(1), -.account-card.account-card:nth-child(1), -.scrollable :not(.focusable) > .account.account-card:nth-child(1), -.timeline-hint.account-card:nth-child(1) { - animation-delay: 0.04s; -} -.focusable.trends__item:nth-child(2), -.entry.trends__item:nth-child(2), -.statuses-grid__item .detailed-status.trends__item:nth-child(2), -.trends__item.trends__item:nth-child(2), -.story.trends__item:nth-child(2), -.account-card.trends__item:nth-child(2), -.scrollable :not(.focusable) > .account.trends__item:nth-child(2), -.timeline-hint.trends__item:nth-child(2), -.focusable.story:nth-child(2), -.entry.story:nth-child(2), -.statuses-grid__item .detailed-status.story:nth-child(2), -.trends__item.story:nth-child(2), -.story.story:nth-child(2), -.account-card.story:nth-child(2), -.scrollable :not(.focusable) > .account.story:nth-child(2), -.timeline-hint.story:nth-child(2), -.focusable.account-card:nth-child(2), -.entry.account-card:nth-child(2), -.statuses-grid__item .detailed-status.account-card:nth-child(2), -.trends__item.account-card:nth-child(2), -.story.account-card:nth-child(2), -.account-card.account-card:nth-child(2), -.scrollable :not(.focusable) > .account.account-card:nth-child(2), -.timeline-hint.account-card:nth-child(2) { - animation-delay: 0.08s; -} -.focusable.trends__item:nth-child(3), -.entry.trends__item:nth-child(3), -.statuses-grid__item .detailed-status.trends__item:nth-child(3), -.trends__item.trends__item:nth-child(3), -.story.trends__item:nth-child(3), -.account-card.trends__item:nth-child(3), -.scrollable :not(.focusable) > .account.trends__item:nth-child(3), -.timeline-hint.trends__item:nth-child(3), -.focusable.story:nth-child(3), -.entry.story:nth-child(3), -.statuses-grid__item .detailed-status.story:nth-child(3), -.trends__item.story:nth-child(3), -.story.story:nth-child(3), -.account-card.story:nth-child(3), -.scrollable :not(.focusable) > .account.story:nth-child(3), -.timeline-hint.story:nth-child(3), -.focusable.account-card:nth-child(3), -.entry.account-card:nth-child(3), -.statuses-grid__item .detailed-status.account-card:nth-child(3), -.trends__item.account-card:nth-child(3), -.story.account-card:nth-child(3), -.account-card.account-card:nth-child(3), -.scrollable :not(.focusable) > .account.account-card:nth-child(3), -.timeline-hint.account-card:nth-child(3) { - animation-delay: 0.12s; -} -.focusable.trends__item:nth-child(4), -.entry.trends__item:nth-child(4), -.statuses-grid__item .detailed-status.trends__item:nth-child(4), -.trends__item.trends__item:nth-child(4), -.story.trends__item:nth-child(4), -.account-card.trends__item:nth-child(4), -.scrollable :not(.focusable) > .account.trends__item:nth-child(4), -.timeline-hint.trends__item:nth-child(4), -.focusable.story:nth-child(4), -.entry.story:nth-child(4), -.statuses-grid__item .detailed-status.story:nth-child(4), -.trends__item.story:nth-child(4), -.story.story:nth-child(4), -.account-card.story:nth-child(4), -.scrollable :not(.focusable) > .account.story:nth-child(4), -.timeline-hint.story:nth-child(4), -.focusable.account-card:nth-child(4), -.entry.account-card:nth-child(4), -.statuses-grid__item .detailed-status.account-card:nth-child(4), -.trends__item.account-card:nth-child(4), -.story.account-card:nth-child(4), -.account-card.account-card:nth-child(4), -.scrollable :not(.focusable) > .account.account-card:nth-child(4), -.timeline-hint.account-card:nth-child(4) { - animation-delay: 0.16s; -} -.focusable.trends__item:nth-child(5), -.entry.trends__item:nth-child(5), -.statuses-grid__item .detailed-status.trends__item:nth-child(5), -.trends__item.trends__item:nth-child(5), -.story.trends__item:nth-child(5), -.account-card.trends__item:nth-child(5), -.scrollable :not(.focusable) > .account.trends__item:nth-child(5), -.timeline-hint.trends__item:nth-child(5), -.focusable.story:nth-child(5), -.entry.story:nth-child(5), -.statuses-grid__item .detailed-status.story:nth-child(5), -.trends__item.story:nth-child(5), -.story.story:nth-child(5), -.account-card.story:nth-child(5), -.scrollable :not(.focusable) > .account.story:nth-child(5), -.timeline-hint.story:nth-child(5), -.focusable.account-card:nth-child(5), -.entry.account-card:nth-child(5), -.statuses-grid__item .detailed-status.account-card:nth-child(5), -.trends__item.account-card:nth-child(5), -.story.account-card:nth-child(5), -.account-card.account-card:nth-child(5), -.scrollable :not(.focusable) > .account.account-card:nth-child(5), -.timeline-hint.account-card:nth-child(5) { - animation-delay: 0.2s; -} -.focusable.trends__item:nth-child(6), -.entry.trends__item:nth-child(6), -.statuses-grid__item .detailed-status.trends__item:nth-child(6), -.trends__item.trends__item:nth-child(6), -.story.trends__item:nth-child(6), -.account-card.trends__item:nth-child(6), -.scrollable :not(.focusable) > .account.trends__item:nth-child(6), -.timeline-hint.trends__item:nth-child(6), -.focusable.story:nth-child(6), -.entry.story:nth-child(6), -.statuses-grid__item .detailed-status.story:nth-child(6), -.trends__item.story:nth-child(6), -.story.story:nth-child(6), -.account-card.story:nth-child(6), -.scrollable :not(.focusable) > .account.story:nth-child(6), -.timeline-hint.story:nth-child(6), -.focusable.account-card:nth-child(6), -.entry.account-card:nth-child(6), -.statuses-grid__item .detailed-status.account-card:nth-child(6), -.trends__item.account-card:nth-child(6), -.story.account-card:nth-child(6), -.account-card.account-card:nth-child(6), -.scrollable :not(.focusable) > .account.account-card:nth-child(6), -.timeline-hint.account-card:nth-child(6) { - animation-delay: 0.24s; -} -.focusable.trends__item:nth-child(7), -.entry.trends__item:nth-child(7), -.statuses-grid__item .detailed-status.trends__item:nth-child(7), -.trends__item.trends__item:nth-child(7), -.story.trends__item:nth-child(7), -.account-card.trends__item:nth-child(7), -.scrollable :not(.focusable) > .account.trends__item:nth-child(7), -.timeline-hint.trends__item:nth-child(7), -.focusable.story:nth-child(7), -.entry.story:nth-child(7), -.statuses-grid__item .detailed-status.story:nth-child(7), -.trends__item.story:nth-child(7), -.story.story:nth-child(7), -.account-card.story:nth-child(7), -.scrollable :not(.focusable) > .account.story:nth-child(7), -.timeline-hint.story:nth-child(7), -.focusable.account-card:nth-child(7), -.entry.account-card:nth-child(7), -.statuses-grid__item .detailed-status.account-card:nth-child(7), -.trends__item.account-card:nth-child(7), -.story.account-card:nth-child(7), -.account-card.account-card:nth-child(7), -.scrollable :not(.focusable) > .account.account-card:nth-child(7), -.timeline-hint.account-card:nth-child(7) { - animation-delay: 0.28s; -} -.focusable.trends__item:nth-child(8), -.entry.trends__item:nth-child(8), -.statuses-grid__item .detailed-status.trends__item:nth-child(8), -.trends__item.trends__item:nth-child(8), -.story.trends__item:nth-child(8), -.account-card.trends__item:nth-child(8), -.scrollable :not(.focusable) > .account.trends__item:nth-child(8), -.timeline-hint.trends__item:nth-child(8), -.focusable.story:nth-child(8), -.entry.story:nth-child(8), -.statuses-grid__item .detailed-status.story:nth-child(8), -.trends__item.story:nth-child(8), -.story.story:nth-child(8), -.account-card.story:nth-child(8), -.scrollable :not(.focusable) > .account.story:nth-child(8), -.timeline-hint.story:nth-child(8), -.focusable.account-card:nth-child(8), -.entry.account-card:nth-child(8), -.statuses-grid__item .detailed-status.account-card:nth-child(8), -.trends__item.account-card:nth-child(8), -.story.account-card:nth-child(8), -.account-card.account-card:nth-child(8), -.scrollable :not(.focusable) > .account.account-card:nth-child(8), -.timeline-hint.account-card:nth-child(8) { - animation-delay: 0.32s; -} -.focusable.trends__item:nth-child(9), -.entry.trends__item:nth-child(9), -.statuses-grid__item .detailed-status.trends__item:nth-child(9), -.trends__item.trends__item:nth-child(9), -.story.trends__item:nth-child(9), -.account-card.trends__item:nth-child(9), -.scrollable :not(.focusable) > .account.trends__item:nth-child(9), -.timeline-hint.trends__item:nth-child(9), -.focusable.story:nth-child(9), -.entry.story:nth-child(9), -.statuses-grid__item .detailed-status.story:nth-child(9), -.trends__item.story:nth-child(9), -.story.story:nth-child(9), -.account-card.story:nth-child(9), -.scrollable :not(.focusable) > .account.story:nth-child(9), -.timeline-hint.story:nth-child(9), -.focusable.account-card:nth-child(9), -.entry.account-card:nth-child(9), -.statuses-grid__item .detailed-status.account-card:nth-child(9), -.trends__item.account-card:nth-child(9), -.story.account-card:nth-child(9), -.account-card.account-card:nth-child(9), -.scrollable :not(.focusable) > .account.account-card:nth-child(9), -.timeline-hint.account-card:nth-child(9) { - animation-delay: 0.36s; -} -.focusable.trends__item:nth-child(10), -.entry.trends__item:nth-child(10), -.statuses-grid__item .detailed-status.trends__item:nth-child(10), -.trends__item.trends__item:nth-child(10), -.story.trends__item:nth-child(10), -.account-card.trends__item:nth-child(10), -.scrollable :not(.focusable) > .account.trends__item:nth-child(10), -.timeline-hint.trends__item:nth-child(10), -.focusable.story:nth-child(10), -.entry.story:nth-child(10), -.statuses-grid__item .detailed-status.story:nth-child(10), -.trends__item.story:nth-child(10), -.story.story:nth-child(10), -.account-card.story:nth-child(10), -.scrollable :not(.focusable) > .account.story:nth-child(10), -.timeline-hint.story:nth-child(10), -.focusable.account-card:nth-child(10), -.entry.account-card:nth-child(10), -.statuses-grid__item .detailed-status.account-card:nth-child(10), -.trends__item.account-card:nth-child(10), -.story.account-card:nth-child(10), -.account-card.account-card:nth-child(10), -.scrollable :not(.focusable) > .account.account-card:nth-child(10), -.timeline-hint.account-card:nth-child(10) { - animation-delay: 0.4s; -} -.focusable.trends__item:nth-child(11), -.entry.trends__item:nth-child(11), -.statuses-grid__item .detailed-status.trends__item:nth-child(11), -.trends__item.trends__item:nth-child(11), -.story.trends__item:nth-child(11), -.account-card.trends__item:nth-child(11), -.scrollable :not(.focusable) > .account.trends__item:nth-child(11), -.timeline-hint.trends__item:nth-child(11), -.focusable.story:nth-child(11), -.entry.story:nth-child(11), -.statuses-grid__item .detailed-status.story:nth-child(11), -.trends__item.story:nth-child(11), -.story.story:nth-child(11), -.account-card.story:nth-child(11), -.scrollable :not(.focusable) > .account.story:nth-child(11), -.timeline-hint.story:nth-child(11), -.focusable.account-card:nth-child(11), -.entry.account-card:nth-child(11), -.statuses-grid__item .detailed-status.account-card:nth-child(11), -.trends__item.account-card:nth-child(11), -.story.account-card:nth-child(11), -.account-card.account-card:nth-child(11), -.scrollable :not(.focusable) > .account.account-card:nth-child(11), -.timeline-hint.account-card:nth-child(11) { - animation-delay: 0.44s; -} -.focusable.focusable, -.entry.focusable, -.statuses-grid__item .detailed-status.focusable, -.trends__item.focusable, -.story.focusable, -.account-card.focusable, -.scrollable :not(.focusable) > .account.focusable, -.timeline-hint.focusable { - background: none; -} -.focusable.entry, -.entry.entry, -.statuses-grid__item .detailed-status.entry, -.trends__item.entry, -.story.entry, -.account-card.entry, -.scrollable :not(.focusable) > .account.entry, -.timeline-hint.entry { - margin-bottom: 10px; -} -.focusable:not(.detailed-status__wrapper)::before, -.entry:not(.detailed-status__wrapper)::before, -.statuses-grid__item .detailed-status:not(.detailed-status__wrapper)::before, -.trends__item:not(.detailed-status__wrapper)::before, -.story:not(.detailed-status__wrapper)::before, -.account-card:not(.detailed-status__wrapper)::before, -.scrollable :not(.focusable) > .account:not(.detailed-status__wrapper)::before, -.timeline-hint:not(.detailed-status__wrapper)::before { - content: ""; - position: absolute; - inset: 0px !important; - height: unset !important; - width: unset !important; - border-radius: var(--radius); - pointer-events: none; - transition: background-color 0.2s; -} -.focusable:not(.detailed-status__wrapper).unread::before, -.entry:not(.detailed-status__wrapper).unread::before, -.statuses-grid__item .detailed-status:not(.detailed-status__wrapper).unread::before, -.trends__item:not(.detailed-status__wrapper).unread::before, -.story:not(.detailed-status__wrapper).unread::before, -.account-card:not(.detailed-status__wrapper).unread::before, -.scrollable :not(.focusable) > .account:not(.detailed-status__wrapper).unread::before, -.timeline-hint:not(.detailed-status__wrapper).unread::before { - border-start-start-radius: 0 !important; - border-end-start-radius: 0 !important; -} -.focusable:not(.detailed-status__wrapper):hover::before, -.entry:not(.detailed-status__wrapper):hover::before, -.statuses-grid__item .detailed-status:not(.detailed-status__wrapper):hover::before, -.trends__item:not(.detailed-status__wrapper):hover::before, -.story:not(.detailed-status__wrapper):hover::before, -.account-card:not(.detailed-status__wrapper):hover::before, -.scrollable :not(.focusable) > .account:not(.detailed-status__wrapper):hover::before, -.timeline-hint:not(.detailed-status__wrapper):hover::before, -.focusable:not(.detailed-status__wrapper):focus-within::before, -.entry:not(.detailed-status__wrapper):focus-within::before, -.statuses-grid__item .detailed-status:not(.detailed-status__wrapper):focus-within::before, -.trends__item:not(.detailed-status__wrapper):focus-within::before, -.story:not(.detailed-status__wrapper):focus-within::before, -.account-card:not(.detailed-status__wrapper):focus-within::before, -.scrollable :not(.focusable) > .account:not(.detailed-status__wrapper):focus-within::before, -.timeline-hint:not(.detailed-status__wrapper):focus-within::before { - background-color: var(--hover-color); -} -.focusable:not(.detailed-status__wrapper):not(.status__wrapper), -.entry:not(.detailed-status__wrapper):not(.status__wrapper), -.statuses-grid__item .detailed-status:not(.detailed-status__wrapper):not(.status__wrapper), -.trends__item:not(.detailed-status__wrapper):not(.status__wrapper), -.story:not(.detailed-status__wrapper):not(.status__wrapper), -.account-card:not(.detailed-status__wrapper):not(.status__wrapper), -.scrollable :not(.focusable) > .account:not(.detailed-status__wrapper):not(.status__wrapper), -.timeline-hint:not(.detailed-status__wrapper):not(.status__wrapper) { - border-radius: var(--radius); + border-radius: 0; border: 0; + padding: 25px; } -.focusable:not(.detailed-status__wrapper):not(.status__wrapper):not(:last-child:not(:only-child))::after, -.entry:not(.detailed-status__wrapper):not(.status__wrapper):not(:last-child:not(:only-child))::after, -.statuses-grid__item .detailed-status:not(.detailed-status__wrapper):not(.status__wrapper):not(:last-child:not(:only-child))::after, -.trends__item:not(.detailed-status__wrapper):not(.status__wrapper):not(:last-child:not(:only-child))::after, -.story:not(.detailed-status__wrapper):not(.status__wrapper):not(:last-child:not(:only-child))::after, -.account-card:not(.detailed-status__wrapper):not(.status__wrapper):not(:last-child:not(:only-child))::after, -.scrollable :not(.focusable) > .account:not(.detailed-status__wrapper):not(.status__wrapper):not(:last-child:not(:only-child))::after, -.timeline-hint:not(.detailed-status__wrapper):not(.status__wrapper):not(:last-child:not(:only-child))::after { - content: ""; - position: absolute; - bottom: 0px; - inset-inline: var(--radius); - border-top: 1px solid var(--border-color); - pointer-events: none; +.dismissable-banner > div { + padding: 0; } -.status__wrapper-reply.status--in-thread::after { - top: 0; +.dismissable-banner button { + padding: 16px; + margin: -16px -14px; } -.status--in-thread.status__wrapper-reply:not(.status--first-in-thread)::after, -.status--in-thread:not(.status__wrapper-reply)::after { - border-top: 0 !important; -} -.explore__links { - padding: 10px !important; - display: flex; - flex-wrap: wrap; -} -.explore__links .trends__item { - margin: 7.5px; - padding: 25px !important; - box-shadow: var(--shadow-med); - width: 200px; - background: var(--elevated-color); -} -.explore__links .trends__item::after { - content: unset !important; - inset: 0 !important; - border-radius: var(--radius); - pointer-events: none; - border: 1px solid var(--border-color) !important; -} -.explore__links .trends__item a { - font-size: 1.4em; - line-height: 1.7em; -} -.explore__links .trends__item .trends__item__sparkline { - height: 100%; -} -.explore__links .trends__item .trends__item__sparkline svg { - height: 100%; - float: right; - overflow: visible !important; - position: relative; -} -.explore__links .trends__item .trends__item__sparkline svg path { - display: unset !important; - transition: transform 1s; -} -.explore__links .trends__item .trends__item__sparkline svg path:first-child { - transform-origin: center; -} -.explore__links .trends__item:hover svg path:first-child, -.explore__links .trends__item:focus-within svg path:first-child { - transform: scale(2); -} -.focusable.trends__item, -.entry.trends__item, -.statuses-grid__item .detailed-status.trends__item, -.trends__item.trends__item, -.story.trends__item, -.account-card.trends__item, -.scrollable :not(.focusable) > .account.trends__item, -.timeline-hint.trends__item, -.focusable.story, -.entry.story, -.statuses-grid__item .detailed-status.story, -.trends__item.story, -.story.story, -.account-card.story, -.scrollable :not(.focusable) > .account.story, -.timeline-hint.story { - padding: 10px; - flex-grow: 1; -} -.focusable.story, -.entry.story, -.statuses-grid__item .detailed-status.story, -.trends__item.story, -.story.story, -.account-card.story, -.scrollable :not(.focusable) > .account.story, -.timeline-hint.story { - padding: 15px; -} -.focusable.story .story__details, -.entry.story .story__details, -.statuses-grid__item .detailed-status.story .story__details, -.trends__item.story .story__details, -.story.story .story__details, -.account-card.story .story__details, -.scrollable :not(.focusable) > .account.story .story__details, -.timeline-hint.story .story__details { - padding-inline-start: 0 !important; -} -.focusable.story .story__thumbnail, -.entry.story .story__thumbnail, -.statuses-grid__item .detailed-status.story .story__thumbnail, -.trends__item.story .story__thumbnail, -.story.story .story__thumbnail, -.account-card.story .story__thumbnail, -.scrollable :not(.focusable) > .account.story .story__thumbnail, -.timeline-hint.story .story__thumbnail { - margin-inline-end: 0; - border-radius: var(--radius); - overflow: hidden; -} -.focusable.empty-column-indicator, -.entry.empty-column-indicator, -.statuses-grid__item .detailed-status.empty-column-indicator, -.trends__item.empty-column-indicator, -.story.empty-column-indicator, -.account-card.empty-column-indicator, -.scrollable :not(.focusable) > .account.empty-column-indicator, -.timeline-hint.empty-column-indicator { - display: block; -} -.focusable.timeline-hint, -.entry.timeline-hint, -.statuses-grid__item .detailed-status.timeline-hint, -.trends__item.timeline-hint, -.story.timeline-hint, -.account-card.timeline-hint, -.scrollable :not(.focusable) > .account.timeline-hint, -.timeline-hint.timeline-hint { - display: block; -} -.focusable.timeline-hint a::before, -.entry.timeline-hint a::before, -.statuses-grid__item .detailed-status.timeline-hint a::before, -.trends__item.timeline-hint a::before, -.story.timeline-hint a::before, -.account-card.timeline-hint a::before, -.scrollable :not(.focusable) > .account.timeline-hint a::before, -.timeline-hint.timeline-hint a::before { - content: ""; - position: absolute; - inset: 0; -} -#mastodon .status__wrapper { - background: none; -} -#mastodon .status, -#mastodon .scrollable .account { - padding-block: 15px; -} -#mastodon .status::before, -#mastodon .scrollable .account::before { - inset: -10px; - border-radius: var(--radius); -} -#mastodon .status.light { - overflow: hidden !important; -} -#mastodon .status.light .boost-modal__status-header { - display: flow-root !important; -} -#mastodon .account__relationship:empty { - display: none; -} -#mastodon .status__prepend { - white-space: nowrap; -} -#mastodon .status__prepend > span { - display: flex; - flex-grow: 1; - gap: 0.3em; - align-items: center; -} -#mastodon .status__prepend > span > a { - overflow: hidden; - text-overflow: ellipsis; -} -#mastodon .status__prepend + .status:not(.status-direct) { - padding-top: 0; -} -#mastodon .notification .status .status__info { - margin-top: 0px !important; -} -#mastodon .notification .status .status__content ~ .media-gallery, -#mastodon .notification .status .status__content ~ [style*="aspect-ratio:"] { - height: 60px; - width: 100px; - margin: 0 !important; - opacity: 0.5; - overflow: hidden; - border-radius: var(--radius); -} -#mastodon .notification .status .status__content ~ [style*="aspect-ratio:"] .video-player__controls { - display: none; -} -#mastodon .status__prepend, -#mastodon .notification__message { - display: flex; - padding-top: 15px !important; - padding-bottom: 0 !important; - margin: 0 !important; - z-index: 2; - position: relative; -} -#mastodon .status__prepend [class*="icon-wrapper"], -#mastodon .notification__message [class*="icon-wrapper"] { - display: flex; - align-items: center; -} -#mastodon .status__prepend a, -#mastodon .notification__message a { - white-space: nowrap; - font-weight: 700; - text-overflow: ellipsis; - overflow: hidden !important; -} -#mastodon .status__prepend bdi, -#mastodon .notification__message bdi { - text-overflow: ellipsis; - overflow: hidden; - max-width: 100%; -} -#mastodon .notification__message > span, -#mastodon .notification__message > span > span { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 0em 0.4em; - line-height: 1.4; - max-width: 100%; -} -#mastodon .notification-favourite .notification__message, -#mastodon .notification-reblog .notification__message { - margin-bottom: -10px !important; -} -#mastodon .notification-favourite .notification__message ~ div .status__info, -#mastodon .notification-reblog .notification__message ~ div .status__info, -#mastodon .notification-favourite .notification__message ~ div .status__action-bar, -#mastodon .notification-reblog .notification__message ~ div .status__action-bar { - display: none; -} -#mastodon .notification-favourite .notification__message ~ div .status, -#mastodon .notification-reblog .notification__message ~ div .status { - min-height: unset; -} -#mastodon .notification-favourite .notification__message ~ div .attachment-list, -#mastodon .notification-reblog .notification__message ~ div .attachment-list { - margin-top: 0; -} -#mastodon .notification-favourite .notification__message ~ div .status__content__text.status__content__text, -#mastodon .notification-reblog .notification__message ~ div .status__content__text.status__content__text { - max-height: 80px !important; - mask: linear-gradient(#000 60px, transparent); - -webkit-mask: linear-gradient(#000 60px, transparent); -} -#mastodon .notification-favourite .notification__message ~ div .status__content__text.status__content__text p, -#mastodon .notification-reblog .notification__message ~ div .status__content__text.status__content__text p { - margin: 0; -} -#mastodon .notification-favourite .notification__message ~ div .attachment-list__list, -#mastodon .notification-reblog .notification__message ~ div .attachment-list__list { - display: flex; - flex-direction: row; - justify-content: flex-start; - gap: 0 1em; - margin-top: 4px; - z-index: 2; - pointer-events: none; - max-width: 100%; -} -#mastodon .notification-favourite .notification__message ~ div .attachment-list__list li, -#mastodon .notification-reblog .notification__message ~ div .attachment-list__list li { - display: contents; -} -#mastodon .notification-favourite .notification__message ~ div .attachment-list__list a, -#mastodon .notification-reblog .notification__message ~ div .attachment-list__list a { - pointer-events: all; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; -} -#mastodon .status__line:not(.status__line--full) { - height: 20px; - filter: contrast(2); -} -#mastodon .status__line::before { - top: 20px; - height: 48px; -} -#mastodon .status__avatar { - min-width: 45px; -} -#mastodon .account__avatar-overlay-base { - width: 100%; - height: 100%; - background-size: cover; - border-radius: var(--radius); -} -#mastodon .account__avatar-overlay-base .account__avatar { - width: 90% !important; - height: 90% !important; -} -#mastodon .account__avatar-overlay-overlay { - border-radius: var(--radius-round); - overflow: hidden; -} -#mastodon .status__info { - margin-block: 5px 0; - padding: 0 !important; - align-items: flex-start; -} -#mastodon .status__info > * { - position: relative; - z-index: 2; -} -#mastodon .status__info .display-name { - color: unset !important; -} -#mastodon .status__info .display-name strong { - font-weight: 600; - overflow: hidden; - text-overflow: ellipsis; -} -#mastodon .status__info b { - unicode-bidi: isolate; -} -#mastodon .status__info .status__relative-time { - display: flex; - font-weight: 500; - font-size: 15px; -} -#mastodon .status__info .status__relative-time .status__visibility-icon { - order: 2; - margin-inline-start: 0.4em; -} -#mastodon .status__info .status__relative-time abbr { - margin-inline-start: 0.7em; -} -#mastodon .status__info .status__relative-time abbr::after { - content: "๏€"; - font: normal normal normal 14px/1 FontAwesome; -} -#mastodon .status__content { - padding-top: 2px; - text-align: unset !important; - line-height: 1.5; - margin-top: 10px; -} -#mastodon .status__content.status__content--with-spoiler { - overflow: visible; -} -#mastodon .status__content.status__content--with-spoiler > p { - margin-inline: -100px; - padding-inline: 100px; - overflow: hidden; -} -#mastodon .status__content.status__content--with-spoiler > p:first-child { - margin-bottom: 0; -} -#mastodon .status__content p:empty { - max-height: 0; -} -#mastodon .status__content .custom-emoji { - height: 2em; - min-width: 2em; - width: auto; -} -.custom-emoji { - transition: transform 1s cubic-bezier(0, 0.7, 0, 1); -} -.custom-emoji:hover { - transform: scale(1.7); - transition: transform 0.4s cubic-bezier(0, 0.7, 0, 1); -} -#mastodon .status__content ~ [style*="aspect-ratio"] { - max-height: 80vh; -} -#mastodon .status > .status__content .status__content__text:empty { - margin-top: -5px !important; -} -#mastodon .status__content__spoiler-link { - display: flex; - align-items: center; - position: relative; - padding: 0.4em 1.2em; - border-radius: var(--radius-round) !important; - color: inherit; - background: var(--elevated-color); - margin-block: 8px; -} -#mastodon .status__content__spoiler-link::before, -#mastodon .status__content__spoiler-link::after { - content: ""; - position: absolute; - inset: 0; - border-radius: var(--radius-round); - background-color: var(--hover-color); - opacity: 0; - transition: opacity 0.2s; -} -#mastodon .status__content__spoiler-link::after { - inset: -6px -9999px; -} -#mastodon .status__content__spoiler-link:hover::before, -#mastodon .status__content__spoiler-link:focus::before, -#mastodon .status__content__spoiler-link:hover::after, -#mastodon .status__content__spoiler-link:focus::after { - opacity: 1; -} -#mastodon .detailed-status__wrapper-direct .status__content, -#mastodon .status-direct .status__content, -#mastodon .status__wrapper-direct .status__content, -#mastodon .conversation .status__content { - position: relative !important; - background: var(--elevated-color); - padding: 8px 12px; - padding: 0.7em 0.9em !important; - border-radius: var(--radius-round); - border-top-left-radius: 6px; - box-sizing: border-box; - margin-top: 5px !important; - margin-bottom: 0; - overflow: hidden !important; - max-width: max-content; -} -#mastodon .detailed-status__wrapper-direct .status__content .media-gallery, -#mastodon .status-direct .status__content .media-gallery, -#mastodon .status__wrapper-direct .status__content .media-gallery, -#mastodon .conversation .status__content .media-gallery { - width: 999px; - max-width: 100% !important; -} -.detailed-status__wrapper-direct .status__content { - font-size: 17px; -} -#mastodon .status__wrapper-direct:not(.detailed-status__wrapper-direct) .status__prepend { - position: absolute; - font-size: 0; - opacity: 0; -} -#mastodon .status-direct .icon-at, -#mastodon .status-direct .status__visibility-icon { - color: var(--accent, #8c8dff); -} -#mastodon .status-direct .status__info .status__relative-time { - height: auto; - color: var(--accent, #8c8dff); -} -#mastodon .status-direct.status--in-thread .status__info { - align-items: center; - gap: 10px; -} -#mastodon .status-direct.status--in-thread .status__info > span { - width: 0; - flex-grow: 1; -} -#mastodon .status-direct.status--in-thread .status__info> span, -#mastodon .status-direct.status--in-thread .status__display-name { - overflow: visible !important; -} -#mastodon .status-direct.status--in-thread .status__display-name { - overflow: hidden; - width: 0; - flex-grow: 1; -} -#mastodon .status-direct.status--in-thread .status__avatar { - height: auto; - margin-bottom: -100px; -} -#mastodon .status-direct.status--in-thread .status__avatar .account__avatar { - position: absolute; - top: 0 !important; - height: 46px !important; - width: 46px !important; -} -#mastodon .status-direct.status--in-thread .display-name * { - display: inline; - margin-right: 0.2em; -} -#mastodon .media-gallery, -#mastodon .video-player, -#mastodon .status-card.horizontal.interactive, -#mastodon .status-card, -#mastodon .audio-player, -#mastodon .picture-in-picture-placeholder { - box-shadow: var(--shadow-low); - border-radius: var(--radius); - margin-top: 10px !important; - margin-bottom: 10px !important; - animation: scaleIn 0.4s; - max-width: unset !important; -} -#mastodon .status .media-gallery__item { - max-height: 80vh; -} -.status-card { - line-height: 1; -} -.status-card:not(.horizontal) { - border: 1px solid var(--border-color) !important; -} -.status-card__content { - padding: 12px !important; - margin-block: auto; -} -.status-card .status-card__image { - border-radius: 0; - width: 90px; - min-height: 100%; -} -.status-card .status-card__image img { - border-radius: 0; - height: 100%; -} -.status-card.compact:not(.interactive) .status-card__image { - position: relative; - aspect-ratio: unset !important; -} -.status-card.compact:not(.interactive) .status-card__image img { - position: absolute; - inset: 0; -} -.status-card__host { - font-size: 0.85em; - line-height: 1.5; - margin: 0; -} -.status-card__title { - font-size: 1em; - margin-top: 0.2em; - margin-bottom: 0; - line-height: 1.4; -} -.status-card__description { - line-height: 1.4 !important; - margin: 0 !important; -} -.status-card__author { - margin-top: 0.4em; - font-size: 0.85em; -} -.status-card:hover { - background-color: var(--hover-color); -} -.audio-player .video-player__seek { - margin: var(--radius); -} -#mastodon .hashtag-bar { - margin-top: 10px; -} -#mastodon .hashtag-bar a, -#mastodon .hashtag-bar button { - font-size: 0.9em; - padding: 0.2em 0.6em; - color: inherit; - opacity: 0.9; - color: var(--accent, #8c8dff); - transition: opacity 0.2s; -} -#mastodon .hashtag-bar a { - position: relative; - border-radius: var(--radius); - background: var(--elevated-color); -} -#mastodon .hashtag-bar a::after { - content: ""; - position: absolute; - inset: 0; - background: var(--elevated-color); - border-radius: inherit; - opacity: 0; - transition: opacity 0.2s; -} -#mastodon .hashtag-bar a:hover, -#mastodon .hashtag-bar a:focus { - opacity: 1; -} -#mastodon .hashtag-bar a:hover::after, -#mastodon .hashtag-bar a:focus::after { - opacity: 1; -} -#mastodon .hashtag-bar button { - padding-block: 0; -} -#mastodon .detailed-status__wrapper { - border-radius: var(--radius); - overflow: clip; -} -#mastodon .detailed-status { - border: 0 !important; - padding: 15px !important; - padding-bottom: 5px !important; -} -#mastodon .detailed-status .status__prepend { - padding-top: 0 !important; - margin-bottom: 1em !important; -} -#mastodon .detailed-status .detailed-status__display-name { - margin-bottom: 10px; -} -#mastodon div:empty + div > .detailed-status__wrapper { - margin-top: 0 !important; -} -#mastodon .detailed-status__wrapper, -#mastodon .detailed-status, -#mastodon .picture-in-picture { - box-shadow: var(--shadow); -} -#mastodon .detailed-status__wrapper .media-gallery, -#mastodon .detailed-status .media-gallery, -#mastodon .picture-in-picture .media-gallery, -#mastodon .detailed-status__wrapper .video-player, -#mastodon .detailed-status .video-player, -#mastodon .picture-in-picture .video-player, -#mastodon .detailed-status__wrapper .status-card.horizontal.interactive, -#mastodon .detailed-status .status-card.horizontal.interactive, -#mastodon .picture-in-picture .status-card.horizontal.interactive, -#mastodon .detailed-status__wrapper .status-card, -#mastodon .detailed-status .status-card, -#mastodon .picture-in-picture .status-card, -#mastodon .detailed-status__wrapper .audio-player, -#mastodon .detailed-status .audio-player, -#mastodon .picture-in-picture .audio-player, -#mastodon .detailed-status__wrapper .picture-in-picture-placeholder, -#mastodon .detailed-status .picture-in-picture-placeholder, -#mastodon .picture-in-picture .picture-in-picture-placeholder { - margin-inline: 0 !important; - max-height: unset !important; -} -#mastodon .detailed-status__wrapper .status__content, -#mastodon .detailed-status .status__content, -#mastodon .picture-in-picture .status__content { - min-height: unset !important; -} -#mastodon .detailed-status__wrapper { - isolation: isolate; -} -#mastodon .detailed-status__wrapper::before { - content: ""; - position: absolute; - inset: 0; - background: var(--elevated-tint); - pointer-events: none; - z-index: -1; -} -#mastodon .detailed-status__wrapper .detailed-status { - box-shadow: none; -} -#mastodon .picture-in-picture { - z-index: 101; -} -#mastodon .picture-in-picture .picture-in-picture__header { - border-radius: var(--radius) var(--radius) 0 0; -} -#mastodon .picture-in-picture .media-gallery, -#mastodon .picture-in-picture .video-player, -#mastodon .picture-in-picture .status-card.horizontal.interactive, -#mastodon .picture-in-picture .status-card, -#mastodon .picture-in-picture .audio-player, -#mastodon .picture-in-picture .picture-in-picture-placeholder { - --radius: 0; - margin: 0 !important; -} -#mastodon .picture-in-picture .picture-in-picture__footer { - border-radius: 0 0 var(--radius) var(--radius); -} -#mastodon .status__action-bar { - margin-top: 0.4em; - margin-bottom: -8px; -} -#mastodon .status__action-bar .icon-button { - padding: 0.25em 0.25em !important; - margin: 0; -} -#mastodon .status__action-bar .icon-button::before { - content: ""; - position: absolute; - inset: -0.5em; -} -#mastodon .status__action-bar, -#mastodon .detailed-status__action-bar, -#mastodon .picture-in-picture__footer { - position: relative; - z-index: 2; - pointer-events: none; - gap: 0 18px; - justify-content: unset; -} -#mastodon .status__action-bar :not(i), -#mastodon .detailed-status__action-bar :not(i), -#mastodon .picture-in-picture__footer :not(i) { - pointer-events: all; -} -#mastodon .status__action-bar > div, -#mastodon .detailed-status__action-bar > div, -#mastodon .picture-in-picture__footer > div { - all: unset; -} -#mastodon .status__action-bar .icon-button, -#mastodon .detailed-status__action-bar .icon-button, -#mastodon .picture-in-picture__footer .icon-button { - display: inline-flex; - align-items: center; - justify-content: center; - width: unset !important; - padding: 0.5em !important; - height: unset !important; - border-radius: var(--radius); - position: relative; -} -#mastodon .status__action-bar .icon-button:last-child, -#mastodon .detailed-status__action-bar .icon-button:last-child, -#mastodon .picture-in-picture__footer .icon-button:last-child { - margin: 0 !important; -} -#mastodon .status__action-bar .icon-button .icon-button__counter, -#mastodon .detailed-status__action-bar .icon-button .icon-button__counter, -#mastodon .picture-in-picture__footer .icon-button .icon-button__counter { - width: auto !important; -} -#mastodon .status__action-bar.detailed-status__action-bar, -#mastodon .detailed-status__action-bar.detailed-status__action-bar, -#mastodon .picture-in-picture__footer.detailed-status__action-bar, -#mastodon .status__action-bar.picture-in-picture__footer, -#mastodon .detailed-status__action-bar.picture-in-picture__footer, -#mastodon .picture-in-picture__footer.picture-in-picture__footer { - padding-inline: 15px !important; - gap: 0; -} -#mastodon .status__action-bar.detailed-status__action-bar .icon-button, -#mastodon .detailed-status__action-bar.detailed-status__action-bar .icon-button, -#mastodon .picture-in-picture__footer.detailed-status__action-bar .icon-button, -#mastodon .status__action-bar.picture-in-picture__footer .icon-button, -#mastodon .detailed-status__action-bar.picture-in-picture__footer .icon-button, -#mastodon .picture-in-picture__footer.picture-in-picture__footer .icon-button { - flex-grow: 1 !important; -} -#mastodon .status__action-bar.detailed-status__action-bar div, -#mastodon .detailed-status__action-bar.detailed-status__action-bar div, -#mastodon .picture-in-picture__footer.detailed-status__action-bar div, -#mastodon .status__action-bar.picture-in-picture__footer div, -#mastodon .detailed-status__action-bar.picture-in-picture__footer div, -#mastodon .picture-in-picture__footer.picture-in-picture__footer div, -#mastodon .status__action-bar.detailed-status__action-bar > div > span, -#mastodon .detailed-status__action-bar.detailed-status__action-bar > div > span, -#mastodon .picture-in-picture__footer.detailed-status__action-bar > div > span, -#mastodon .status__action-bar.picture-in-picture__footer > div > span, -#mastodon .detailed-status__action-bar.picture-in-picture__footer > div > span, -#mastodon .picture-in-picture__footer.picture-in-picture__footer > div > span { - display: flex; - justify-content: center; - flex-grow: 1; -} -#mastodon .status__action-bar.picture-in-picture__footer .icon-button::after, -#mastodon .detailed-status__action-bar.picture-in-picture__footer .icon-button::after, -#mastodon .picture-in-picture__footer.picture-in-picture__footer .icon-button::after { - content: unset !important; -} -.layout-single-column .tabs-bar__wrapper, -.layout-single-column .column-back-button--slim .column-back-button { +.tabs-bar__wrapper { grid-column: 2; border: 0 !important; + padding-top: 0; transition: margin 0.2s cubic-bezier(0, 0, 0, 1.1), top 0.4s; } -#mastodon .column-header, -#mastodon .column-inline-form { +@media (min-width: 890px) { + .tabs-bar__wrapper { + margin-top: -100vh; + } +} +.column-header, +.column-inline-form { font-weight: 600; border-bottom-left-radius: 0 !important; border-bottom-right-radius: 0 !important; } -#mastodon .column-header ~ .scrollable, -#mastodon .column-inline-form ~ .scrollable { +.column-header ~ .scrollable, +.column-inline-form ~ .scrollable { border-top-left-radius: 0 !important; border-top-right-radius: 0 !important; } -.layout-single-column .tabs-bar__wrapper .announcements, -.layout-single-column .column-back-button--slim .column-back-button .announcements, -.layout-single-column .tabs-bar__wrapper .column-header__collapsible:not(.collapsed), -.layout-single-column .column-back-button--slim .column-back-button .column-header__collapsible:not(.collapsed) { +.column-header__title { + display: inline; +} +.column-header__title svg { + vertical-align: -0.4em; +} +.announcements, +.column-header__collapsible:not(.collapsed) { flex-direction: column-reverse; align-items: flex-start; border: 0; - animation: slideDowFade 0.3s backwards cubic-bezier(0, 1, 0, 1.2); + animation: slideDownFade 0.3s backwards cubic-bezier(0, 1, 0, 1.2); } -.layout-single-column .tabs-bar__wrapper .column-header__collapsible, -.layout-single-column .column-back-button--slim .column-back-button .column-header__collapsible { +.column-header__collapsible { transition: none; - background: var(--modal-background-color); - backdrop-filter: var(--background-filter); + background: var(--surface-background-color); + overflow-y: auto !important; } -.layout-single-column .tabs-bar__wrapper .collapsed, -.layout-single-column .column-back-button--slim .column-back-button .collapsed { +.tabs-bar__wrapper .collapsed { display: none; } -.layout-single-column .tabs-bar__wrapper .announcements__container, -.layout-single-column .column-back-button--slim .column-back-button .announcements__container { +.announcements { + background: var(--surface-background-color); +} +.announcements__container { width: 100% !important; } -.layout-single-column .tabs-bar__wrapper .announcements__mastodon, -.layout-single-column .column-back-button--slim .column-back-button .announcements__mastodon { - display: block; +.announcements__mastodon { + display: block !important; z-index: -1; position: relative; } -.layout-single-column .tabs-bar__wrapper .announcements__pagination, -.layout-single-column .column-back-button--slim .column-back-button .announcements__pagination { +.announcements__pagination { bottom: unset; padding-block: 0; + display: flex; + align-items: center; +} +.column-header__wrapper > :not(.column-header):not(.collapsed) { + border-top: 2px solid var(--background-color) !important; +} +.column-header { + overflow: hidden; +} +.column-header > button { + z-index: 2; +} +.column-header__buttons { + isolation: isolate; +} +.column-header__buttons button { + transition: background 0.2s, transform 0.3s !important; + position: relative; + border-radius: 100px !important; + min-width: 40px; + margin: 5px; + margin-inline-start: 0; + font-size: 0.9em; + padding-inline: 10px; +} +.column-header__buttons button:not(.active) { + background: var(--elevated-color) !important; + z-index: 2; +} +.column-header__buttons button svg { + margin: 0; +} +.column-header__buttons button span { + display: none; +} +.column-header__buttons button::before { + content: ""; + position: absolute; + inset: -20px -800px; + transform: scale(0); + transform-origin: bottom center; + background: var(--surface-background-color); + z-index: -1; + border-radius: 100px; + pointer-events: none; + opacity: 0; + transition: transform 0.3s, opacity 0.3s; +} +@media (prefers-reduced-motion) { + .column-header__buttons button::before { + transition: none !important; + } +} +.column-header__buttons button.active::before { + transform: scale(1, 5); + opacity: 1; + transition: transform 0.3s, opacity 0.1s; } @media (min-width: 890px) { - .layout-single-column .tabs-bar__wrapper, - .layout-single-column .column-back-button--slim .column-back-button { - width: 285px; - top: 0 !important; - top: var(--top) !important; + .tabs-bar__wrapper { inset-inline: unset !important; height: 50px !important; - margin-top: -50px !important; - margin-inline-start: 10px; - margin-top: 30px; + top: 0; + top: var(--top) !important; + width: 285px; border-radius: var(--radius) var(--radius) !important; box-shadow: 0 12px 12px -12px rgba(0,0,0,0.1); + margin-inline-start: 20px; } - .layout-single-column .tabs-bar__wrapper:not(.column-back-button), - .layout-single-column .column-back-button--slim .column-back-button:not(.column-back-button) { - padding-top: 0; - } - .layout-single-column .tabs-bar__wrapper .column-header__wrapper, - .layout-single-column .column-back-button--slim .column-back-button .column-header__wrapper { - gap: 2px !important; + .tabs-bar__wrapper .column-header__wrapper { display: flex; flex-direction: column; border-radius: var(--radius); overflow: hidden; } - .layout-single-column .tabs-bar__wrapper .column-header, - .layout-single-column .column-back-button--slim .column-back-button .column-header { + .tabs-bar__wrapper .column-header { background: none !important; overflow: hidden; border: 0; } - .layout-single-column .tabs-bar__wrapper .column-header > button, - .layout-single-column .column-back-button--slim .column-back-button .column-header > button { - z-index: 2; - } - .layout-single-column .tabs-bar__wrapper .column-header__buttons button, - .layout-single-column .column-back-button--slim .column-back-button .column-header__buttons button { - transition: background 0.2s, transform 0.3s !important; - position: relative; - border-radius: 100px !important; - min-width: 40px; - margin: 5px; - margin-inline-start: 0; - font-size: 0.9em; - padding-inline: 10px; - } - .layout-single-column .tabs-bar__wrapper .column-header__buttons button .column-header__icon, - .layout-single-column .column-back-button--slim .column-back-button .column-header__buttons button .column-header__icon { - margin-inline-end: 0; - } - .layout-single-column .tabs-bar__wrapper .column-header__buttons button:not(.active), - .layout-single-column .column-back-button--slim .column-back-button .column-header__buttons button:not(.active) { - background: var(--elevated-color) !important; - } - .layout-single-column .tabs-bar__wrapper .column-header__buttons button svg, - .layout-single-column .column-back-button--slim .column-back-button .column-header__buttons button svg { - margin: 0; - } - .layout-single-column .tabs-bar__wrapper .column-header__buttons button span, - .layout-single-column .column-back-button--slim .column-back-button .column-header__buttons button span { - display: none; - } - .layout-single-column .tabs-bar__wrapper .column-header__buttons button::before, - .layout-single-column .column-back-button--slim .column-back-button .column-header__buttons button::before { - content: ""; - position: absolute; - inset: 0; - top: calc(100% + 5px); - bottom: -5px; - background: var(--modal-background-color); - backdrop-filter: var(--background-filter); - z-index: -1; - transition: inset 0.1s; - border-radius: 100px; - pointer-events: none; - } - .layout-single-column .tabs-bar__wrapper .column-header__buttons button.active::before, - .layout-single-column .column-back-button--slim .column-back-button .column-header__buttons button.active::before { - inset: -10px -300px; - } } -@media (min-width: 890px) and (min-width: 1320px) { - .layout-single-column .tabs-bar__wrapper, - .layout-single-column .column-back-button--slim .column-back-button { - margin-inline-start: 25px; - } -} -@media (min-width: 890px) and (max-width: 1174px) { - .layout-single-column .tabs-bar__wrapper, - .layout-single-column .column-back-button--slim .column-back-button { - width: 265px; - margin-top: -60px !important; - top: 10px !important; +@media (min-width: 890px) and (max-width: 1319px) { + .tabs-bar__wrapper { margin-inline-start: 10px; } } +@media (min-width: 890px) and (max-width: 1174px) { + .tabs-bar__wrapper { + width: 265px; + top: 10px !important; + } +} @media (min-width: 890px) { - .layout-single-column .column-back-button--slim { + .column-back-button--slim { margin-left: auto !important; order: -1; } - .layout-single-column .column-back-button--slim > .column-back-button { + .column-back-button--slim > .column-back-button { margin-top: 0 !important; top: unset !important; } } -@media (min-width: 890px) and (max-width: 1174px) { - .layout-single-column .column-back-button--slim > .column-back-button { - margin-top: -55px !important; - top: unset !important; - } -} -#mastodon .column-settings__row, -#mastodon .column-settings__hashtags { +.column-settings__row, +.column-settings__hashtags { gap: 0; } -#mastodon .column-settings__section { - margin-bottom: 4px; - padding-inline: 4px; +.column-settings h3 { + font-size: 1em; + margin-bottom: 8px; } -#mastodon .column-select__control { +.column-select__control { border-radius: var(--radius); } -#mastodon .setting-toggle, -#mastodon .app-form__toggle { +.local-settings__page__item, +.glitch-setting-text.glitch-setting-text, +.setting-toggle, +.app-form__toggle { display: flex; align-items: center; margin-bottom: 14px; position: relative; padding: 0.7em; background: var(--elevated-color); - margin-bottom: 2px !important; + margin-block: 0 2px !important; overflow: hidden; } -#mastodon .setting-toggle:first-child, -#mastodon .app-form__toggle:first-child { +.local-settings__page__item:first-of-type, +.glitch-setting-text.glitch-setting-text:first-of-type, +.setting-toggle:first-of-type, +.app-form__toggle:first-of-type { border-top-left-radius: var(--radius); border-top-right-radius: var(--radius); } -#mastodon .setting-toggle:last-child, -#mastodon .app-form__toggle:last-child { +.local-settings__page__item:last-of-type, +.glitch-setting-text.glitch-setting-text:last-of-type, +.setting-toggle:last-of-type, +.app-form__toggle:last-of-type { border-bottom-left-radius: var(--radius); border-bottom-right-radius: var(--radius); } -#mastodon .setting-toggle .react-toggle, -#mastodon .app-form__toggle .react-toggle { +.local-settings__page__item label, +.glitch-setting-text.glitch-setting-text label, +.setting-toggle label, +.app-form__toggle label, +.local-settings__page__item legend, +.glitch-setting-text.glitch-setting-text legend, +.setting-toggle legend, +.app-form__toggle legend { + padding-block: 2px !important; +} +.local-settings__page__item label span::before, +.glitch-setting-text.glitch-setting-text label span::before, +.setting-toggle label span::before, +.app-form__toggle label span::before { + content: ""; + position: absolute; + inset: -900px; +} +.local-settings__page__item .react-toggle, +.glitch-setting-text.glitch-setting-text .react-toggle, +.setting-toggle .react-toggle, +.app-form__toggle .react-toggle { order: 2; } -#mastodon .setting-toggle .setting-toggle__label, -#mastodon .app-form__toggle .setting-toggle__label { +.local-settings__page__item .setting-toggle__label, +.glitch-setting-text.glitch-setting-text .setting-toggle__label, +.setting-toggle .setting-toggle__label, +.app-form__toggle .setting-toggle__label { margin-bottom: 0 !important; flex-grow: 1; width: 0; } -#mastodon .setting-toggle::before, -#mastodon .app-form__toggle::before { +.local-settings__page__item::before, +.glitch-setting-text.glitch-setting-text::before, +.setting-toggle::before, +.app-form__toggle::before { content: ""; position: absolute; inset: 0; @@ -3022,13 +1017,23 @@ a:focus-visible, transition: opacity 0.2s; pointer-events: none; } -#mastodon .setting-toggle:hover::before, -#mastodon .app-form__toggle:hover::before, -#mastodon .setting-toggle:focus-within::before, -#mastodon .app-form__toggle:focus-within::before { +.local-settings__page__item:hover::before, +.glitch-setting-text.glitch-setting-text:hover::before, +.setting-toggle:hover::before, +.app-form__toggle:hover::before, +.local-settings__page__item:focus-within::before, +.glitch-setting-text.glitch-setting-text:focus-within::before, +.setting-toggle:focus-within::before, +.app-form__toggle:focus-within::before { opacity: 1; } -#mastodon .navigation-panel.navigation-panel { +@media (min-width: 890px) and (max-width: 1174px) { + .column-back-button--slim > .column-back-button { + margin-top: -55px !important; + top: unset !important; + } +} +.navigation-panel.navigation-panel { box-sizing: border-box; height: calc(100vh - var(--top) - 50px + var(--radius)); padding-bottom: 10px; @@ -3036,27 +1041,25 @@ a:focus-visible, border: 0; margin-top: calc(0px - var(--radius)); padding-top: calc(10px + var(--radius)); + overflow: hidden auto; } -#mastodon .navigation-panel.navigation-panel > hr { +.navigation-panel.navigation-panel > hr { display: none; } -#mastodon .navigation-panel.navigation-panel hr { - margin-inline: 15px; -} @media (min-width: 1175px) { - #mastodon .navigation-panel.navigation-panel { + .navigation-panel.navigation-panel { padding-top: calc(var(--radius) + 10px); margin-top: calc(50px - var(--radius)); } - #mastodon .navigation-panel.navigation-panel .navigation-panel__logo .column-link, - #mastodon .navigation-panel.navigation-panel .navigation-panel__logo hr { + .navigation-panel.navigation-panel .navigation-panel__logo .column-link, + .navigation-panel.navigation-panel .navigation-panel__logo hr { display: none !important; } - #mastodon .navigation-panel.navigation-panel .switch-to-advanced { + .navigation-panel.navigation-panel .switch-to-advanced { border-radius: var(--radius); margin-top: 0; } - #mastodon .navigation-panel.navigation-panel [href="/settings/preferences"] { + .navigation-panel.navigation-panel [href="/settings/preferences"] { display: none !important; } } @@ -3077,6 +1080,14 @@ a:focus-visible, top: -10px; box-sizing: border-box; } +.column-header__back-button, +.column-back-button > svg, +.column-link__icon, +.column-header > button .column-header__icon { + margin-inline-end: 0.7em; + font-size: 16px !important; + padding-right: 0 !important; +} @media (min-width: 890px) { .column-link { flex-grow: 100 !important; @@ -3093,11 +1104,6 @@ a:focus-visible, overflow: hidden; background: none !important; } - .column-link .column-link__icon, - .column-header > button .column-header__icon { - margin-inline-end: 0.7em !important; - font-size: 16px !important; - } .column-link::before { content: "" !important; position: absolute; @@ -3126,252 +1132,1567 @@ a:focus-visible, .column-link[href="/lists"] + div .column-link i { opacity: 0.2; } -} -@media (min-width: 890px) { - #mastodon .getting-started__trends { + .navigation-panel.navigation-panel .getting-started__trends { display: unset !important; } } -#mastodon .trends__item { - display: flex !important; +.navigation-panel.navigation-panel .trends__item { + margin: 0 !important; } -#mastodon .trends__item__name a::before { +.scrollable > div:first-child > [tabindex="-1"]:last-child .status__wrapper::after { + content: unset; +} +.focusable, +.entry, +.statuses-grid__item .detailed-status, +.trends__item, +.story, +.account-card, +.scrollable :not(.focusable) > .account:not(.account--minimal), +.timeline-hint { + overflow: hidden; + contain: paint inline-size; + position: relative; + border-radius: var(--panel-radius); + border: 0; +} +.focusable.focusable, +.entry.focusable, +.statuses-grid__item .detailed-status.focusable, +.trends__item.focusable, +.story.focusable, +.account-card.focusable, +.scrollable :not(.focusable) > .account:not(.account--minimal).focusable, +.timeline-hint.focusable { + background: none; +} +@media (pointer: fine) { + .focusable::before, + .entry::before, + .statuses-grid__item .detailed-status::before, + .trends__item::before, + .story::before, + .account-card::before, + .scrollable :not(.focusable) > .account:not(.account--minimal)::before, + .timeline-hint::before { + content: ""; + position: absolute; + inset: 0px !important; + height: unset !important; + width: unset !important; + pointer-events: none; + transition: background-color 0.2s; + } + .focusable:hover::before, + .entry:hover::before, + .statuses-grid__item .detailed-status:hover::before, + .trends__item:hover::before, + .story:hover::before, + .account-card:hover::before, + .scrollable :not(.focusable) > .account:not(.account--minimal):hover::before, + .timeline-hint:hover::before, + .focusable:focus-within::before, + .entry:focus-within::before, + .statuses-grid__item .detailed-status:focus-within::before, + .trends__item:focus-within::before, + .story:focus-within::before, + .account-card:focus-within::before, + .scrollable :not(.focusable) > .account:not(.account--minimal):focus-within::before, + .timeline-hint:focus-within::before { + background-color: var(--hover-color); + } +} +.focusable:not(:last-child)::after, +.entry:not(:last-child)::after, +.statuses-grid__item .detailed-status:not(:last-child)::after, +.trends__item:not(:last-child)::after, +.story:not(:last-child)::after, +.account-card:not(:last-child)::after, +.scrollable :not(.focusable) > .account:not(.account--minimal):not(:last-child)::after, +.timeline-hint:not(:last-child)::after { + content: ""; + position: absolute; + bottom: 0px; + inset-inline: var(--radius); + border-top: 1px solid var(--border-color); + pointer-events: none; +} +.status__wrapper-reply.status--in-thread::after { + top: 0; +} +.status--in-thread.status__wrapper-reply:not(.status--first-in-thread)::after, +.status--in-thread:not(.status__wrapper-reply)::after { + border-top: 0 !important; +} +.detailed-status, +.status { + padding: 16px; +} +.status__info .account__avatar, +.status__info .status__avatar { + max-width: var(--avatar-size) !important; + max-height: var(--avatar-size) !important; +} +.status__line { + left: calc(16px + (var(--avatar-size) / 2)); +} +.status__prepend + .status:not(.status-direct) { + padding-top: 0; +} +@media (max-width: 450px) { + .status--in-thread { + --avatar-size: 34px; + } + .status--in-thread .status__info ~ * { + margin-inline-start: calc(var(--avatar-size) + 10px); + width: calc(100% - (var(--avatar-size) + 10px)); + } +} +.status__content { + text-align: unset !important; + line-height: 1.5; +} +.status__content.status__content--with-spoiler { + overflow: visible; +} +.status__content.status__content--with-spoiler > p { + margin-inline: -100px; + padding-inline: 100px; + overflow: hidden; +} +.status__content.status__content--with-spoiler > p:first-child { + margin-bottom: 0; +} +.status__content p:empty { + max-height: 0; +} +.status__content picture { + display: contents; +} +.status__content .custom-emoji { + display: inline-block; + height: var(--emoji-size) !important; + min-width: var(--emoji-size) !important; + width: auto; + margin: -0.2ex 0 0.2ex; +} +@media (prefers-reduced-motion: no-preference) { + .custom-emoji { + transition: transform 1s cubic-bezier(0, 0.7, 0, 1); + } + .custom-emoji:hover { + transform: scale(1.7); + transition: transform 0.4s cubic-bezier(0, 0.7, 0, 1); + } +} +.status__content ~ [style*="aspect-ratio"] { + max-height: 80vh; +} +#mastodon .status__content__spoiler-link { + display: flex; + align-items: center; + position: relative; + padding: 0.4em 1.2em; + border-radius: var(--radius-round); + color: inherit; + background: var(--elevated-color); + margin-block: 8px; +} +#mastodon .status__content__spoiler-link::before, +#mastodon .status__content__spoiler-link::after { + content: ""; + position: absolute; + inset: 0; + border-radius: var(--radius-round); + background-color: var(--hover-color); + opacity: 0; + transition: opacity 0.2s; +} +#mastodon .status__content__spoiler-link::after { + inset: -6px -9999px; +} +#mastodon .status__content__spoiler-link:hover::before, +#mastodon .status__content__spoiler-link:focus::before, +#mastodon .status__content__spoiler-link:hover::after, +#mastodon .status__content__spoiler-link:focus::after { + opacity: 1; +} +.detailed-status__wrapper-direct .status__content, +.status-direct .status__content, +.status__wrapper-direct .status__content, +.conversation .status__content { + position: relative !important; + background: var(--elevated-color); + padding: 12px 14px; + border-radius: var(--radius-round); + border-top-left-radius: 6px; + box-sizing: border-box; + margin-bottom: 0; + overflow: hidden !important; + max-width: max-content; +} +.detailed-status__wrapper-direct .status__content .media-gallery, +.status-direct .status__content .media-gallery, +.status__wrapper-direct .status__content .media-gallery, +.conversation .status__content .media-gallery { + width: 999px; + max-width: 100% !important; +} +.status__wrapper-direct:not(.detailed-status__wrapper-direct) .status__prepend { + position: absolute; + contain: strict; +} +.detailed-status { + border: 0; + padding-bottom: 4px; +} +.detailed-status__wrapper, +.detailed-status { + box-shadow: var(--shadow); +} +.detailed-status__wrapper .status__content, +.detailed-status .status__content { + min-height: unset !important; +} +.detailed-status__wrapper { + isolation: isolate; + background: none; +} +.detailed-status__wrapper::before { + content: ""; + position: absolute; + inset: 0; + background: var(--elevated-tint) !important; + pointer-events: none; + z-index: -1; +} +.detailed-status__wrapper .detailed-status { + box-shadow: none; +} +.detailed-status__meta { + margin-top: 14px; + line-height: 2; +} +.detailed-status__meta > * { + display: inline-flex; + border: 0 !important; + padding: 0 !important; + margin-inline-end: 8px; +} +.detailed-status__meta > *:not(:last-child)::after { + content: "ยท"; +} +.media-gallery, +.video-player, +.status-card.horizontal.interactive, +.status-card, +.audio-player, +.picture-in-picture-placeholder { + box-shadow: var(--shadow-low); + border-radius: var(--radius) !important; + margin-top: 10px !important; + margin-bottom: 10px !important; + animation: scaleIn 0.4s; + max-width: unset !important; +} +.media-gallery:has(.spoiler-button:not(.spoiler-button--minified)) { + height: 150px !important; + aspect-ratio: unset !important; +} +.media-gallery__item { + border-radius: 0; +} +.spoiler-button--minified button { + padding: 6px !important; + background: rgba(0,0,0,0.2) !important; +} +.spoiler-button--minified button::after { + content: ""; + position: absolute; + inset: -50px; +} +.spoiler-button--minified button:hover { + background: rgba(0,0,0,0.4) !important; +} +.spoiler-button--minified .icon { + width: 18px; + height: 18px; +} +.status-card { + align-items: stretch; + gap: 0; +} +.status-card:not(.horizontal) { + border: 1px solid var(--border-color) !important; +} +.status-card:not(.expanded) .status-card__image { + overflow: hidden; +} +.status-card:not(.expanded) .status-card__image img { + border-radius: 0; +} +.status-card:not(.interactive) .status-card__image { + position: relative; + aspect-ratio: unset !important; +} +.status-card__content { + margin-block: auto; + padding: 15px; +} +.status-card__host { + font-size: 0.85em; + line-height: 1.5; + margin: 0; +} +.status-card__title { + font-size: 1em; + margin-top: 0.2em; + margin-bottom: 0; + line-height: 1.4; +} +.status-card__description { + line-height: 1.4 !important; + margin: 0; +} +@supports (-webkit-line-clamp: 8) { + .status-card__description { + display: -webkit-box; + -webkit-line-clamp: 8; + -webkit-box-orient: vertical; + white-space: unset; + } +} +.status-card__author { + margin-top: 0.4em; + font-size: 0.85em; +} +.status-card:hover { + background-color: var(--hover-color); +} +.more-from-author { + background: none; + border: 0; + padding-top: 0; + border-radius: var(--radius); +} +.audio-player .video-player__seek { + margin: var(--radius); +} +.hashtag-bar { + margin-top: 10px; +} +.hashtag-bar a, +.hashtag-bar button { + color: var(--accent, #8c8dff); + transition: opacity 0.2s; + padding: 5px 10px; +} +.hashtag-bar a { + position: relative; + border-radius: var(--radius-round); + background: var(--elevated-color); +} +.hashtag-bar a::after { + content: ""; + position: absolute; + inset: 0; + background: var(--elevated-color); + border-radius: inherit; + opacity: 0; + transition: opacity 0.2s; +} +.hashtag-bar a:hover, +.hashtag-bar a:focus { + opacity: 1; +} +.hashtag-bar a:hover::after, +.hashtag-bar a:focus::after { + opacity: 1; +} +.hashtag-bar button { + padding-block: 0; +} +.status__action-bar { + flex-wrap: wrap; + margin-top: 0.4em; + margin-bottom: -6px; + gap: 0; + margin-inline-start: -8px; + pointer-events: none; +} +.status__action-bar > * { + pointer-events: all; + flex-grow: 1; + max-width: 55px; + min-width: max-content; +} +.status__action-bar > * button.icon-button { + width: 100% !important; +} +.status__action-bar > :not(.icon-button) { + display: flex; + height: 100%; +} +.status__action-bar .icon-button { + margin: 0; +} +.status__action-bar .icon-button::before { + content: ""; + position: absolute; + inset: -0.5em; +} +.status__action-bar, +.detailed-status__action-bar, +.picture-in-picture__footer { + position: relative; + z-index: 2; + justify-content: unset; +} +.status__action-bar .icon-button, +.detailed-status__action-bar .icon-button, +.picture-in-picture__footer .icon-button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.5em 0.4em !important; + border-radius: var(--radius); + position: relative; +} +.status__action-bar .icon-button .icon-button__counter, +.detailed-status__action-bar .icon-button .icon-button__counter, +.picture-in-picture__footer .icon-button .icon-button__counter { + width: auto !important; +} +.status__action-bar .icon-button--with-counter, +.detailed-status__action-bar .icon-button--with-counter, +.picture-in-picture__footer .icon-button--with-counter { + padding-inline: 0.7em !important; +} +.detailed-status__action-bar, +.picture-in-picture__footer { + padding-inline: 15px !important; + gap: 0; +} +.detailed-status__action-bar .icon-button, +.picture-in-picture__footer .icon-button { + flex-grow: 1 !important; +} +.detailed-status__action-bar div, +.picture-in-picture__footer div { + display: flex; + justify-content: center; + flex-grow: 1; +} +.account__wrapper { + line-height: 1.5; +} +.account__contents { + display: flex; + flex-wrap: wrap; + flex-grow: 1; + gap: 0 10px; +} +.display-name { + margin-bottom: 1px !important; +} +.account:not(.account--minimal) .display-name__account { + display: block; + width: 300px; +} +.account__details { + font-size: 0.9em; + opacity: 0.8; + width: 100px; + flex-grow: 1; + text-align: end; + align-items: center; + line-height: 1.2; +} +.account__details:has(.verified-badge) > :not(.verified-badge) { + display: none; +} +.account__wrapper button:not(:hover):not(:focus) { + background: var(--elevated-color); + color: inherit; +} +.trends__item, +.story, +.account-card { + animation: slideUpFade backwards 0.4s 0.24s cubic-bezier(0, 1, 1, 1); + border-radius: var(--radius); +} +.trends__item:nth-child(1), +.story:nth-child(1), +.account-card:nth-child(1) { + animation-delay: 0.04s; +} +.trends__item:nth-child(2), +.story:nth-child(2), +.account-card:nth-child(2) { + animation-delay: 0.08s; +} +.trends__item:nth-child(3), +.story:nth-child(3), +.account-card:nth-child(3) { + animation-delay: 0.12s; +} +.trends__item:nth-child(4), +.story:nth-child(4), +.account-card:nth-child(4) { + animation-delay: 0.16s; +} +.trends__item:nth-child(5), +.story:nth-child(5), +.account-card:nth-child(5) { + animation-delay: 0.2s; +} +.trends__item:nth-child(6), +.story:nth-child(6), +.account-card:nth-child(6) { + animation-delay: 0.24s; +} +.explore__links { + padding: 10px; + display: flex; + flex-wrap: wrap; + align-content: flex-start; +} +.explore__links > .dismissable-banner { + margin: -10px; + margin-bottom: 10px; +} +.explore__links .story { + margin-inline: 0 !important; +} +.trends__item { + display: flex !important; + margin-inline: 0 !important; +} +.trends__item__name a::before { content: ""; position: absolute; inset: 0; } -#mastodon .trends__item__current { +.trends__item__current { display: none; } -#mastodon .trends__item__sparkline { +.trends__item__sparkline { overflow: visible !important; pointer-events: none; } -#mastodon .trends__item__sparkline svg { +.trends__item__sparkline svg { overflow: visible !important; } -#mastodon .trends__item__sparkline path:first-child { - filter: blur(10px); +.trends__item__sparkline path:first-child { + filter: blur(8px); } -#mastodon .trends__item__sparkline path:last-child { +.trends__item__sparkline path:last-child { mask: linear-gradient(to left, #000, #000, transparent); -webkit-mask: linear-gradient(to left, #000, #000, transparent); } -.rtl #mastodon .trends__item__sparkline { +.rtl .trends__item__sparkline { transform: scaleX(-1); } -.getting-started, -#mastodon .column[aria-labelledby="Getting-started"] > .scrollable.scrollable.scrollable { +.explore__links .trends__item { + margin: 7.5px !important; + padding: 25px !important; + box-shadow: var(--shadow-med); + width: 200px; + background: var(--elevated-color); + flex-grow: 1; +} +.explore__links .trends__item::after { + content: unset !important; +} +.explore__links .trends__item a { + font-size: 1.4em; + line-height: 1.7em; +} +.explore__links .trends__item a::before { + content: ""; + position: absolute; + inset: 0; +} +.explore__links .trends__item .trends__item__sparkline { + height: 100%; +} +.explore__links .trends__item .trends__item__sparkline svg { + height: 100%; + float: right; + overflow: visible !important; + position: relative; +} +.explore__links .trends__item .trends__item__sparkline svg path { + display: unset !important; + transition: transform 1s; +} +.explore__links .trends__item .trends__item__sparkline svg path:first-child { + transform-origin: center; +} +.explore__links .trends__item:hover svg path:first-child, +.explore__links .trends__item:focus-within svg path:first-child { + transform: scale(2); +} +.explore__links .story { + width: 100%; + margin: 0; +} +.account__header { + overflow: visible; +} +.follow-request-banner { + margin-bottom: -100px; + padding-bottom: 120px; +} +.account__header__image { + height: 0; + padding-bottom: 35%; + border-radius: var(--panel-radius) var(--panel-radius) 0 0; + position: sticky; + top: calc(0px - var(--panel-radius)); + overflow: clip; +} +.account__header__image img { + position: absolute; +} +.account__header__image .account__header__info { + position: absolute; + z-index: 2; + height: 100%; +} +.account__header__image .account__header__info > span { + position: sticky; + top: var(--radius); +} +.account__header__bar { + position: relative; + z-index: 2; + border: 0; + padding-inline: 20px; + border-radius: var(--radius) var(--radius) 0 0; + margin-top: calc(0px - var(--radius)) !important; + display: flex; + flex-direction: column; + background: var(--background-color); + isolation: isolate; +} +@media (max-width: 890px) { + .account__header__bar { + padding-inline: 15px; + } +} +.account__header__bar::before { + content: ""; + background: var(--elevated-tint); + position: absolute; + inset: 0; + pointer-events: none; +} +.account__header__bar::after { + content: ""; + position: absolute; + inset-inline: 0; + height: 95px; + background: inherit; + z-index: -1; + border-radius: var(--radius); + mask: linear-gradient(to bottom, transparent, #000); +} +.account__header__tabs { + overflow: visible !important; + align-items: flex-end; + padding: 0; + height: unset !important; + margin-top: -55px !important; +} +.account__header__tabs::before { + content: ""; + position: absolute; + top: -55px; + inset-inline: 0; + height: 150px; + backdrop-filter: blur(40px); + filter: brightness(1.1); + pointer-events: none; + opacity: 0.7; + z-index: -2; + clip-path: inset(55px 0 0 0 round var(--radius)); +} +.account__header__tabs ~ div { + z-index: 2; +} +.account__header__bar .avatar { + margin-inline-start: 0 !important; + overflow: visible !important; + position: relative; + margin-top: 20px; +} +.account__header__bar .avatar .account-role { + position: absolute; + bottom: 0; + left: 110%; + border-radius: var(--radius); +} +.account__header__bar .account__avatar { + border: 0; + box-shadow: var(--shadow); + background: none; + background-size: cover !important; +} +.account__header__tabs__name { + margin-bottom: 0; + padding: 0; + margin-top: 16px; +} +.account__header__tabs__name h1 { + display: flex; + flex-wrap: wrap; + white-space: unset; + gap: 0 0.4em; + font-weight: 600; +} +.account__header__extra { + margin-top: 8px; +} +.account__header__fields, +.account__header__account-note { + display: flex; + flex-wrap: wrap; + gap: 2px; + background: none; + border-radius: var(--radius) !important; + overflow: hidden; + max-width: max-content; + border: 0 !important; +} +.account__header__fields dl { + display: inline; + border-radius: 0; + overflow: hidden; + border: 0 !important; + padding: 8px 12px !important; + position: relative; + overflow: hidden; + flex-grow: 1; +} +.account__header__fields dl:not(.verified) { + background-color: var(--elevated-color); +} +.account__header__fields dl dt { + all: unset !important; + color: unset !important; + opacity: 0.9 !important; +} +.account__header__fields dl dd { + padding: 0; + white-space: unset; + max-height: unset; + text-align: unset; +} +.account__header__fields dl dd > span > a:first-child:last-child::after, +.account__header__fields dl dd .h-card:first-child:last-child a::after { + content: ""; + position: absolute; + inset: 0; + background-color: var(--hover-color); + opacity: 0; + transition: opacity 0.2s; +} +.account__header__fields dl dd > span > a:first-child:last-child:hover:after, +.account__header__fields dl dd .h-card:first-child:last-child a:hover:after, +.account__header__fields dl dd > span > a:first-child:last-child:focus:after, +.account__header__fields dl dd .h-card:first-child:last-child a:focus:after { + opacity: 1; +} +.account__header__fields dl dd.verified { + overflow: visible !important; + border: 0; + background: none; +} +.account__header__fields dl dd.verified a::before, +.account__header__fields dl dd.verified a::after { + content: ""; + position: absolute; + inset: 0; + background: currentcolor; + opacity: 0.2; +} +.account__header__fields dl dd.verified a::after { + background: linear-gradient(20deg, currentcolor, transparent) !important; + opacity: 0.2 !important; + z-index: -2; +} +.account__header__account-note { + position: relative; + font-size: 0.9em; + max-width: unset; + padding: 0.5em 10px !important; + margin-block: -5px 10px; + margin-inline: -10px !important; + border-radius: var(--radius) !important; +} +.account__header__account-note::after { + content: ""; + position: absolute; + bottom: 0; + inset-inline: 10px; + border-top: 1px solid var(--border-color); + transition: opacity 0.2s; +} +.account__header__account-note:focus-within::after { + opacity: 0; +} +.account__header__account-note label { + z-index: 2; + margin: 0; + pointer-events: none; + font-size: inherit; +} +.account__header__account-note textarea { + margin: -100px !important; + padding: 100px !important; + padding-inline-end: 0.7em !important; + margin-inline-end: -0.7em !important; + box-sizing: content-box; + width: 100%; + font-size: inherit; + transition: background 0.2s; +} +.account__header__account-note textarea::placeholder { + font-weight: 600; +} +.account-gallery__container { + border-radius: var(--radius); + overflow: clip; + padding: 0; + margin: 4px; + gap: 4px; + margin-bottom: calc(-40vh + 4px); +} +.account-gallery__item { + margin: 0; + flex: 1 1 calc(100px + 15%); + transition: flex 0.7s cubic-bezier(0, 0, 0, 1); + min-height: 180px !important; +} +.media-gallery__item-thumbnail { + transition: transform 0.2s; +} +.account-gallery__item:hover, +.account-gallery__item:focus-within { + flex-grow: 1.5; +} +.account-gallery__item:hover .media-gallery__item-thumbnail, +.account-gallery__item:focus-within .media-gallery__item-thumbnail { + transform: scale(1.02); +} +.account-gallery__container > button { + width: unset; + flex-grow: 1; + flex: 1 1 calc(100px + 15% - 24px); + margin: 12px; + font-size: 1.2em; + font-weight: 700; + background: var(--elevated-color); + color: inherit; +} +.account-gallery__container > button:hover:not(:focus) { + transform: scale(1.01); +} +.account-authorize__wrapper { + background: var(--elevated-color); + border-radius: var(--radius); + overflow: hidden; + flex-grow: 1; + margin: 10px; + display: flex; + flex-direction: column; +} +@media (max-width: 890px) { + .account-authorize__wrapper { + margin-inline: 10px; + } +} +.layout-multiple-columns .account-authorize__wrapper { + margin-inline: 10px; +} +.account-authorize__wrapper .account-authorize { + padding: 20px 15px 10px; +} +.account-authorize__wrapper .detailed-status__display-name { + margin-bottom: 10px !important; +} +.account-authorize__wrapper .account--panel { + margin-top: auto; + border-bottom: 0; + padding-inline: 15px; + gap: 10px; + background: none; +} +.account-authorize__wrapper br { + display: block; +} +.account-authorize__wrapper p { + margin-bottom: 0.2em; +} +.account-authorize__wrapper .account--panel__button:first-child .icon-button:not(:hover):not(:focus) { + background: var(--elevated-color); +} +.account-authorize__wrapper .icon-button { + width: 100% !important; + padding: 10px; + height: unset !important; +} +.about__meta { + border-radius: var(--radius); +} +.account--minimal { + max-width: 100%; +} +.about__section { + margin: 30px -20px; + padding-inline: 20px; + contain: inline-size paint; +} +.about__section.active .about__section__title { + margin-inline: -20px; + border-radius: 0; + border-inline: 0; + border-bottom: 0; +} +.about__section__title { + position: sticky; + top: -1px; + z-index: 2; + background: var(--background-color-tint); + border: 1px solid var(--border-color); + border-radius: var(--radius); + overflow: hidden; + transition: margin 0.2s cubic-bezier(0, 1, 0, 1), border-radius 0.2s; +} +.about__section__title::after { + content: ""; + position: absolute; + inset: 0; + background: var(--elevated-tint); + backdrop-filter: blur(10px); + z-index: -1; +} +.about__section__body { + border: 0; + padding: 0; + animation: slideDownFade 0.4s 0.1s backwards cubic-bezier(0, 1, 0, 1.2); +} +.explore__search-results { + border: 0; +} +.search-results__section__header { + margin: 0px 0px 10px; + color: unset; + background: none; + padding-inline: 25px; + font-weight: 600; +} +.search-results__section { + border: 0; + margin-bottom: 20px; +} +.sidebar-wrapper { + overflow: visible !important; + width: unset; +} +.sidebar-wrapper__inner { + position: sticky; + top: 0; + max-height: 100vh !important; + overflow-y: auto !important; + pointer-events: all !important; + z-index: 100; +} +.sidebar-wrapper__inner .fa { + margin-inline-end: 1em !important; +} +.sidebar-wrapper__inner .sidebar > ul > li { + overflow: hidden; + margin-inline: 15px !important; +} +.sidebar-wrapper__inner .sidebar > ul > li > a:not(.selected) { + background: none; +} +.sidebar-wrapper__inner .sidebar > ul > li a { + display: flex !important; + align-items: center; + white-space: unset; +} +.sidebar-wrapper__inner .sidebar > ul > li.selected { + margin: 6px; + border-radius: var(--radius); +} +.sidebar-wrapper__inner .sidebar > ul > li.selected > a.selected { + font-weight: 600; + color: unset; + position: relative; + overflow: visible; + border-radius: 0 !important; + border: 0; +} +.sidebar-wrapper__inner .sidebar > ul > li.selected > a.selected::after { + content: ""; + position: absolute; + top: 100%; + inset-inline: 0; + height: 9999px; + background: inherit; + z-index: -1; +} +.sidebar-wrapper__inner .sidebar > ul > li > ul { + border-radius: var(--radius) !important; + overflow: hidden !important; + gap: 2px !important; + margin: 8px; + margin-top: 0; + background: none; +} +.sidebar-wrapper__inner .sidebar > ul > li > ul > li { + border-radius: 8px; +} +.sidebar-wrapper__inner .sidebar > ul > li > ul > li:not(:first-child):not(:last-child) { + margin-block: 2px; +} +.sidebar-wrapper__inner .sidebar > ul > li > ul > li a { + padding: 14px 16px; + font-weight: 600; + border: 0; +} +.sidebar-wrapper__inner .sidebar > ul > li > ul > li:not(.selected) a { + background-color: rgba(150,150,250,0.1); +} +.admin-wrapper .content__heading { + margin-bottom: 2em; +} +.admin-wrapper h4 { + margin: 0; + border-bottom: 0; +} +.admin-wrapper form > h4 { + margin-top: 2em !important; + border-bottom: 0 !important; + margin-bottom: 0 !important; +} +.admin-wrapper .lead { + margin-bottom: 15px; +} +.admin-wrapper .flash-message, +.admin-wrapper .applications-list__item, +.admin-wrapper .filters-list__item { + border-radius: var(--radius); + border: 0; + overflow: clip; +} +.admin-wrapper .fields-row { + margin-inline: 0; + border-radius: var(--radius); + overflow: clip; + padding-top: 0; + gap: 2px; + display: flex; + flex-wrap: wrap; +} +.admin-wrapper .fields-group:not(.fields-row__column), +.admin-wrapper .fields-row { + margin-bottom: 1em !important; +} +.admin-wrapper .fields-row > .fields-row__column { + max-width: unset; + width: 300px; + border-radius: 0 !important; + display: flex; + flex-direction: column; + flex-grow: 1; + margin: 0 !important; +} +.admin-wrapper .fields-row > .fields-row__column .fields-group { + border-radius: 0 !important; + margin: 0 !important; +} +.admin-wrapper .fields-row > .fields-row__column .with_block_label { + display: flex; + flex-direction: column; + height: 100%; +} +.admin-wrapper .fields-row > .fields-row__column .with_block_label > .label_input { + display: flex; + flex-direction: column; + flex-grow: 1; +} +.admin-wrapper .fields-row > .fields-row__column .with_block_label > .label_input > textarea { + min-height: 300px; + flex-grow: 1; +} +.admin-wrapper .fields-row > .fields-row__column > :last-child { + flex-grow: 1; + align-items: flex-start; + border: 0; +} +.admin-wrapper .fields-row > .fields-row__column > :not(:first-child):not(:last-child) { + padding-block: 0.5em !important; + margin-block: -3px; +} +.admin-wrapper :not(.fields-row__column) > .fields-group, +.admin-wrapper .fields-row > *, +.admin-wrapper .label_input > ul, +.admin-wrapper .label_input__wrapper > ul, +.admin-wrapper .with_block_label.radio_buttons .label_input { + border-radius: var(--radius); + overflow: hidden; + padding: 0; + display: flex; + flex-direction: column; + gap: 2px; +} +.admin-wrapper :not(.fields-row__column) > .fields-group > *, +.admin-wrapper .fields-row > * > *, +.admin-wrapper .label_input > ul > *, +.admin-wrapper .label_input__wrapper > ul > *, +.admin-wrapper .with_block_label.radio_buttons .label_input > * { + background-color: var(--elevated-color); + padding: 0.8rem; + margin-block: 0px; + position: relative; + border-radius: 0 !important; + overflow: hidden; +} +.admin-wrapper :not(.fields-row__column) > .fields-group > *::after, +.admin-wrapper .fields-row > * > *::after, +.admin-wrapper .label_input > ul > *::after, +.admin-wrapper .label_input__wrapper > ul > *::after, +.admin-wrapper .with_block_label.radio_buttons .label_input > *::after { + content: ""; + position: absolute; + inset: 0; + background-color: var(--hover-color); + z-index: -1; + opacity: 0; + transition: opacity 0.2s; +} +.admin-wrapper :not(.fields-row__column) > .fields-group > *:hover::after, +.admin-wrapper .fields-row > * > *:hover::after, +.admin-wrapper .label_input > ul > *:hover::after, +.admin-wrapper .label_input__wrapper > ul > *:hover::after, +.admin-wrapper .with_block_label.radio_buttons .label_input > *:hover::after, +.admin-wrapper :not(.fields-row__column) > .fields-group > *:focus-within::after, +.admin-wrapper .fields-row > * > *:focus-within::after, +.admin-wrapper .label_input > ul > *:focus-within::after, +.admin-wrapper .label_input__wrapper > ul > *:focus-within::after, +.admin-wrapper .with_block_label.radio_buttons .label_input > *:focus-within::after { + opacity: 1; +} +.admin-wrapper :not(.fields-row__column) > .fields-group label::before, +.admin-wrapper .fields-row > * label::before, +.admin-wrapper .label_input > ul label::before, +.admin-wrapper .label_input__wrapper > ul label::before, +.admin-wrapper .with_block_label.radio_buttons .label_input label::before { + content: ""; + position: absolute; + inset: -900px; +} +.admin-wrapper .label_input__wrapper > :not([type="checkbox"]):not(label) { + margin-top: 4px; +} +.admin-wrapper .label_input { + position: relative; +} +.admin-wrapper label { + margin: 0 !important; + display: flex; + align-items: center; + padding: 0 !important; +} +.admin-wrapper label input { + margin: 0; + margin-inline-end: 10px !important; + position: static !important; +} +.admin-wrapper input { + border-radius: var(--radius) !important; +} +.admin-wrapper .radio { + flex-grow: 1; +} +.admin-wrapper .radio:not(:last-child) { + margin-bottom: 0 !important; +} +.admin-wrapper .hint:last-child { + margin-bottom: 0 !important; +} +.admin-wrapper .input.with_block_label > .row { + flex-wrap: wrap; + margin: 0; +} +.admin-wrapper .input.with_block_label > .row > .string { + padding: 0; + width: 100%; + margin: 0; +} +.admin-wrapper .input.with_block_label > .row > .string:first-child input { + border-radius: var(--radius) var(--radius) 0 0 !important; +} +.admin-wrapper .input.with_block_label > .row > .string:last-child input { + border-radius: 0 0 var(--radius) var(--radius) !important; +} +.admin-wrapper .input.with_block_label > .row:not(:last-child) { + margin-bottom: 8px; +} +.admin-wrapper li.checkbox { + flex-grow: 1; + overflow: hidden; +} +.admin-wrapper ul { + flex-direction: row !important; + flex-wrap: wrap; + gap: 2px; + flex-grow: 1; +} +.admin-wrapper li.checkbox { + flex-basis: 45%; +} +.admin-wrapper .spacer { + border-top: 1px solid var(--border-color) !important; +} +.batch-table label { + padding-inline-start: 20px !important; +} +.batch-table, +.table, +:not(.batch-table__row__content) > table { + overflow: clip; + border-radius: var(--radius); + border-spacing: 0 2px; + border-collapse: separate; +} +.batch-table__toolbar, +.batch-table__row, +.batch-table tr > *, +.table tr > *, +:not(.batch-table__row__content) > table tr > * { + border: 0; + margin-bottom: 2px !important; +} +.batch-table td, +.table td, +:not(.batch-table__row__content) > table td, +.batch-table th, +.table th, +:not(.batch-table__row__content) > table th, +.batch-table__row { + position: relative; +} +.batch-table tr > td > div > span, +.table tr > td > div > span, +:not(.batch-table__row__content) > table tr > td > div > span, +.batch-table tr > th > div > span, +.table tr > th > div > span, +:not(.batch-table__row__content) > table tr > th > div > span { + padding-inline: 0.7em; + display: inline-block; +} +.keyboard-shortcuts { + padding: 0; + margin-top: -4px; +} +.keyboard-shortcuts table { + width: 100%; + border-radius: 0; +} +.keyboard-shortcuts td { + padding: 0.7em; +} +.batch-table__row, +.batch-table th, +.table th, +:not(.batch-table__row__content) > table th, +.batch-table > tbody > tr > td, +.table > tbody > tr > td, +:not(.batch-table__row__content) > table > tbody > tr > td, +.batch-table tfoot td, +.table tfoot td, +:not(.batch-table__row__content) > table tfoot td { + background: var(--elevated-color) !important; + vertical-align: middle; +} +.batch-table__row::after, +.batch-table th::after, +.table th::after, +:not(.batch-table__row__content) > table th::after, +.batch-table > tbody > tr > td::after, +.table > tbody > tr > td::after, +:not(.batch-table__row__content) > table > tbody > tr > td::after, +.batch-table tfoot td::after, +.table tfoot td::after, +:not(.batch-table__row__content) > table tfoot td::after { + content: ""; + position: absolute; + inset: 0 0; + background: var(--hover-color); + opacity: 0; + transition: 0.2s; + pointer-events: none; +} +.batch-table__row:hover::after, +.batch-table th:hover::after, +.table th:hover::after, +:not(.batch-table__row__content) > table th:hover::after, +.batch-table > tbody > tr > td:hover::after, +.table > tbody > tr > td:hover::after, +:not(.batch-table__row__content) > table > tbody > tr > td:hover::after, +.batch-table tfoot td:hover::after, +.table tfoot td:hover::after, +:not(.batch-table__row__content) > table tfoot td:hover::after, +.batch-table__row:focus-within::after, +.batch-table th:focus-within::after, +.table th:focus-within::after, +:not(.batch-table__row__content) > table th:focus-within::after, +.batch-table > tbody > tr > td:focus-within::after, +.table > tbody > tr > td:focus-within::after, +:not(.batch-table__row__content) > table > tbody > tr > td:focus-within::after, +.batch-table tfoot td:focus-within::after, +.table tfoot td:focus-within::after, +:not(.batch-table__row__content) > table tfoot td:focus-within::after { + opacity: 1; +} +.batch-table__row > a:first-child:last-child, +.batch-table th > a:first-child:last-child, +.table th > a:first-child:last-child, +:not(.batch-table__row__content) > table th > a:first-child:last-child, +.batch-table > tbody > tr > td > a:first-child:last-child, +.table > tbody > tr > td > a:first-child:last-child, +:not(.batch-table__row__content) > table > tbody > tr > td > a:first-child:last-child, +.batch-table tfoot td > a:first-child:last-child, +.table tfoot td > a:first-child:last-child, +:not(.batch-table__row__content) > table tfoot td > a:first-child:last-child { + margin: 0; + width: 100%; + padding: 0.5em; +} +.batch-table th:hover td:not([rowspan])::after, +.table th:hover td:not([rowspan])::after, +:not(.batch-table__row__content) > table th:hover td:not([rowspan])::after, +.batch-table tr:hover td:not([rowspan])::after, +.table tr:hover td:not([rowspan])::after, +:not(.batch-table__row__content) > table tr:hover td:not([rowspan])::after, +.batch-table th:hover th:not([rowspan])::after, +.table th:hover th:not([rowspan])::after, +:not(.batch-table__row__content) > table th:hover th:not([rowspan])::after, +.batch-table tr:hover th:not([rowspan])::after, +.table tr:hover th:not([rowspan])::after, +:not(.batch-table__row__content) > table tr:hover th:not([rowspan])::after { + opacity: 1 !important; +} +.batch-table th [rowspan]:hover ~ td::after, +.table th [rowspan]:hover ~ td::after, +:not(.batch-table__row__content) > table th [rowspan]:hover ~ td::after, +.batch-table tr [rowspan]:hover ~ td::after, +.table tr [rowspan]:hover ~ td::after, +:not(.batch-table__row__content) > table tr [rowspan]:hover ~ td::after { + opacity: 0 !important; +} +.batch-table th [rowspan]::after, +.table th [rowspan]::after, +:not(.batch-table__row__content) > table th [rowspan]::after, +.batch-table tr [rowspan]::after, +.table tr [rowspan]::after, +:not(.batch-table__row__content) > table tr [rowspan]::after { + inset-inline: -900px; +} +.layout-multiple-columns.layout-multiple-columns { + --column-header-height: 45px; +} +.layout-multiple-columns.layout-multiple-columns .column-header, +.layout-multiple-columns.layout-multiple-columns .scrollable, +.layout-multiple-columns.layout-multiple-columns .column-back-button, +.layout-multiple-columns.layout-multiple-columns .account__header__image { + border-radius: 0 !important; + gap: 0 !important; +} +.layout-multiple-columns.layout-multiple-columns .columns-area { + background: none !important; + height: 100%; +} +.layout-multiple-columns.layout-multiple-columns .columns-area > div { + border: 0 !important; + padding: 0 !important; +} +.layout-multiple-columns.layout-multiple-columns .columns-area > div:not(.drawer):not(:last-child) { + margin-inline-end: 2px !important; +} +.layout-multiple-columns.layout-multiple-columns .columns-area > div.column { + flex-grow: 1; + max-width: 600px; +} +.layout-multiple-columns.layout-multiple-columns .columns-area > div:first-child { + margin-inline-start: auto !important; +} +.layout-multiple-columns.layout-multiple-columns .columns-area > div:last-child { + margin-inline-end: auto !important; +} +.layout-multiple-columns.layout-multiple-columns .drawer.drawer { + padding-top: 15px !important; + overflow: clip; + flex-grow: 1; + max-width: 350px; +} +.drawer__header { + border-radius: var(--radius-round); + background: var(--elevated-color); + margin-inline: 15px; + overflow: hidden; + border: 0 !important; +} +.drawer__header a { + border: 0; +} +.drawer__header a:first-child { + padding-inline-start: 15px !important; +} +.drawer__header a:last-child { + padding-inline-end: 15px !important; +} +.layout-multiple-columns.layout-multiple-columns .drawer.drawer .search { + z-index: 2; + margin-inline: 15px; + margin-bottom: 0; +} +.layout-multiple-columns.layout-multiple-columns .drawer.drawer > .drawer__pager { + border: 0; + overflow: visible !important; +} +.layout-multiple-columns.layout-multiple-columns .drawer.drawer .drawer__inner:not(.darker) { + margin-top: -20px; + padding-top: 30px; + height: unset; + bottom: 0; +} +.layout-multiple-columns.layout-multiple-columns .drawer.drawer .drawer__inner__mastodon { + margin-inline: -6px; + z-index: -1; +} +.layout-multiple-columns.layout-multiple-columns .compose-form { + margin-inline: 5px; +} +.layout-multiple-columns.layout-multiple-columns .drawer__inner:not(.darker), +.layout-multiple-columns.layout-multiple-columns .drawer__inner__mastodon { + background-color: transparent !important; +} +.layout-multiple-columns.layout-multiple-columns .darker { + background-color: var(--surface-background-color); + border-radius: var(--radius) var(--radius) 0 0; + top: 10px; + width: unset; + inset-inline: 2px; +} +.layout-multiple-columns.layout-multiple-columns .column { + background: none; +} +.layout-multiple-columns.layout-multiple-columns .column::after { + content: ""; + position: absolute; + inset: 0; + top: var(--column-header-height); + background: var(--background-color); + z-index: -1; +} +.layout-multiple-columns.layout-multiple-columns .column::before, +.layout-multiple-columns.layout-multiple-columns .column::after { + top: var(--column-header-height); + border-radius: var(--radius) var(--radius) 0 0; +} +.layout-multiple-columns.layout-multiple-columns .column-back-button, +.layout-multiple-columns.layout-multiple-columns .column-header__wrapper { + height: var(--column-header-height); +} +.layout-multiple-columns.layout-multiple-columns .column-back-button.active, +.layout-multiple-columns.layout-multiple-columns .column-header__wrapper.active { + box-shadow: none; +} +.layout-multiple-columns.layout-multiple-columns .column-back-button.active::before, +.layout-multiple-columns.layout-multiple-columns .column-header__wrapper.active::before { + inset-inline: var(--radius); +} +.layout-multiple-columns.layout-multiple-columns .column-back-button .column-header, +.layout-multiple-columns.layout-multiple-columns .column-header__wrapper .column-header { + border: 0 !important; + height: var(--column-header-height); +} +.layout-multiple-columns.layout-multiple-columns .column-back-button .column-header__buttons, +.layout-multiple-columns.layout-multiple-columns .column-header__wrapper .column-header__buttons { + height: 100%; +} +.layout-multiple-columns.layout-multiple-columns .column-back-button svg, +.layout-multiple-columns.layout-multiple-columns .column-header__wrapper svg { + height: 1.4em; +} +.layout-multiple-columns.layout-multiple-columns .column-back-button + .scrollable.scrollable, +.layout-multiple-columns.layout-multiple-columns .column-header__wrapper + .scrollable.scrollable { + border-radius: var(--radius) var(--radius) 0 0 !important; + overflow-y: scroll; +} +.layout-multiple-columns.layout-multiple-columns .getting-started__trends { + padding: 0px 20px; +} +.column[aria-labelledby="Misc"] > .scrollable, +.column[aria-labelledby="Getting-started"] > .scrollable, +.getting-started { position: relative; padding: 5px 10px !important; } -.getting-started .getting-started__wrapper, -#mastodon .column[aria-labelledby="Getting-started"] > .scrollable.scrollable.scrollable .getting-started__wrapper { +.column[aria-labelledby="Misc"] > .scrollable .getting-started__wrapper, +.column[aria-labelledby="Getting-started"] > .scrollable .getting-started__wrapper, +.getting-started .getting-started__wrapper { background: none; } -.getting-started .getting-started__wrapper a, -#mastodon .column[aria-labelledby="Getting-started"] > .scrollable.scrollable.scrollable .getting-started__wrapper a, -.getting-started .getting-started__wrapper .column-subheading, -#mastodon .column[aria-labelledby="Getting-started"] > .scrollable.scrollable.scrollable .getting-started__wrapper .column-subheading { +.column[aria-labelledby="Misc"] > .scrollable .column-link, +.column[aria-labelledby="Getting-started"] > .scrollable .column-link, +.getting-started .column-link, +.column[aria-labelledby="Misc"] > .scrollable .column-subheading, +.column[aria-labelledby="Getting-started"] > .scrollable .column-subheading, +.getting-started .column-subheading { border: 0 !important; - padding: 20px; + padding: 20px !important; background: none; } -.getting-started .getting-started__footer, -#mastodon .column[aria-labelledby="Getting-started"] > .scrollable.scrollable.scrollable .getting-started__footer { +.column[aria-labelledby="Misc"] > .scrollable .getting-started__footer, +.column[aria-labelledby="Getting-started"] > .scrollable .getting-started__footer, +.getting-started .getting-started__footer { padding-inline: 20px; } -.getting-started .getting-started__footer a span, -#mastodon .column[aria-labelledby="Getting-started"] > .scrollable.scrollable.scrollable .getting-started__footer a span { +.column[aria-labelledby="Misc"] > .scrollable .getting-started__footer a span, +.column[aria-labelledby="Getting-started"] > .scrollable .getting-started__footer a span, +.getting-started .getting-started__footer a span { font-size: 1.1em !important; line-height: 2; } -.about .account { - padding: 0 !important; - overflow: visible !important; -} -.about .account::after { - content: unset !important; -} -.about .about__meta { - border-radius: var(--radius); -} -.about .about__section__title { - position: sticky; - top: 0; - z-index: 2; - border-radius: 0 !important; -} -.about .about__section__body { - animation: slideDowFade 0.3s 0.1s backwards cubic-bezier(0, 1, 0, 1.2); -} -.about .about__section { - margin: 10px 0px !important; - margin-top: 20px; - border-radius: var(--radius); - overflow: clip; - transition: margin 0.2s cubic-bezier(0, 1, 0, 1), border-radius 0.2s; -} -.about .about__section:not(.active) { - border: 0 !important; -} -.about .about__section:not(.active) .about__section__title { - background: var(--elevated-color) !important; - border-radius: var(--radius); -} -#mastodon.modal-layout { - overflow: hidden; -} -#mastodon.modal-layout .container-alt { - background: inherit; - height: 100%; -} -#mastodon.modal-layout .container-alt .public-layout { - padding: 0 !important; -} -#mastodon.modal-layout .container-alt .form-container { - max-width: 500px !important; - padding: 0; - background: inherit; - display: flex; - flex-direction: column; - justify-content: center; - height: 100%; -} -#mastodon.modal-layout .container-alt .form-container h2 { - margin: 0; - padding: 20px; - font-weight: 600; -} -#mastodon.modal-layout .container-alt .form-container .follow-prompt { - margin: 0; - border-radius: 0 0 var(--radius) var(--radius); - overflow-y: auto; -} -#mastodon.modal-layout .container-alt .form-container .follow-prompt .activity-stream { - margin: 0 !important; -} -#mastodon.modal-layout .container-alt .entry { - border-radius: var(--radius) !important; -} -#mastodon.modal-layout #new_remote_follow { - position: sticky; - bottom: 0; - padding: 20px; - padding-bottom: 60px; - background: inherit; -} -@media (min-width: 890px) and (max-width: 1174px) { - .layout-single-column #mastodon .ui__header { +@media (min-width: 890px) and (max-width: 1175px) { + .ui__header { background: none !important; - border: 0; - margin-inline-end: 280px; - padding-top: 12px; - position: static; backdrop-filter: none; + position: relative; + margin-inline-end: 285px; + border: 0 !important; } - .layout-single-column #mastodon .columns-area__panels__main { - margin-inline: 10px !important; - margin-top: 10px; - } - .layout-single-column #mastodon .columns-area__panels__main .columns-area { - padding-bottom: 0 !important; - } - .layout-single-column #mastodon .dismissable-banner { - border-top-left-radius: 0 !important; - } - .layout-single-column #mastodon .navigation-panel { - background: none; - border: 0; - width: 265px !important; - padding-bottom: 10px; + .navigation-panel { + padding-inline: 10px; } } @media (max-width: 889px) { - #mastodon .scrollable:not(.scrollable--flex) { - padding: 0px !important; - padding-bottom: 40vh !important; - } - #mastodon .scrollable:not(.scrollable--flex)::before { - content: ""; - position: absolute; - inset: 0; - background-color: inherit; - z-index: -1; - } - #mastodon .scrollable:not(.scrollable--flex) .account-timeline__header, - #mastodon .scrollable:not(.scrollable--flex) .dismissable-banner { - margin: 0px !important; - } - #mastodon .focusable, - #mastodon .entry, - #mastodon .statuses-grid__item .detailed-status, - #mastodon .trends__item, - #mastodon .story, - #mastodon .account-card, - #mastodon .scrollable :not(.focusable) > .account, - #mastodon .timeline-hint { - border-radius: 0; - } - #mastodon .focusable::before, - #mastodon .entry::before, - #mastodon .statuses-grid__item .detailed-status::before, - #mastodon .trends__item::before, - #mastodon .story::before, - #mastodon .account-card::before, - #mastodon .scrollable :not(.focusable) > .account::before, - #mastodon .timeline-hint::before { - border-radius: 0 !important; - } - #mastodon .focusable::after, - #mastodon .entry::after, - #mastodon .statuses-grid__item .detailed-status::after, - #mastodon .trends__item::after, - #mastodon .story::after, - #mastodon .account-card::after, - #mastodon .scrollable :not(.focusable) > .account::after, - #mastodon .timeline-hint::after { - inset-inline: 0 !important; - } - #mastodon [class*="explore__"] > * { - border-radius: var(--radius); - } - #mastodon .detailed-status__wrapper { - margin: 0 !important; - } - #mastodon .status__action-bar { - margin-bottom: 0px; - gap: 0; - margin-inline-end: 0 !important; - } - #mastodon .status__action-bar :not(i):not(.status__action-bar-spacer) { - display: flex; - flex-grow: 9999; - justify-content: center !important; - max-width: 55px; - min-width: max-content; - } - #mastodon .status__action-bar > .icon-button:first-child { - margin-inline-start: -8px !important; - } - #mastodon .status__action-bar, - #mastodon .detailed-status__action-bar, - #mastodon .picture-in-picture__footer { - flex-wrap: wrap; - } - #mastodon .column-header { - border-inline: 0; - } .ui__header { border-bottom: 0; box-sizing: content-box; - min-height: 55px; height: auto; position: relative; box-sizing: border-box; gap: 5px 10px; flex-wrap: wrap; - padding-block: 8px; + padding-block: 12px; border: 0 !important; backdrop-filter: none !important; background: none !important; @@ -3389,7 +2710,7 @@ a:focus-visible, right: 0; } .ui__header [href="/search"] { - margin-inline-end: 5px; + display: none; } .ui__header [href="/publish"] { position: fixed; @@ -3454,11 +2775,11 @@ a:focus-visible, position: static !important; width: unset; } - #mastodon .tabs-bar__wrapper { + .tabs-bar__wrapper { top: 0; z-index: 200; } - #mastodon .tabs-bar__wrapper::after { + .tabs-bar__wrapper::after { content: ""; position: absolute; inset: 0; @@ -3466,103 +2787,73 @@ a:focus-visible, pointer-events: none; z-index: 2; } - #mastodon .column-back-button.column-back-button { - border-radius: 0 !important; - } @supports selector(.foxxo:has(.waf)) { - #mastodon [href="/publish"] { + [href="/publish"] { bottom: 70px !important; } - #mastodon .columns-area__panels { + .columns-area__panels { flex-direction: column; } - #mastodon .columns-area__panels__main { + .columns-area__panels__main { width: 100%; border-radius: var(--radius) !important; margin: 0 !important; border: 0 !important; overflow: clip !important; + flex-grow: 1; } - #mastodon .tabs-bar__wrapper { + .tabs-bar__wrapper { transition: none !important; } - #mastodon .columns-area__panels__pane--navigational { + .columns-area__panels__pane--navigational { display: contents; } - #mastodon .columns-area__panels__pane--navigational .columns-area__panels__pane__inner { - position: relative !important; - inset: unset !important; + .columns-area__panels__pane--navigational .columns-area__panels__pane__inner { + position: static !important; order: -1; width: unset; height: auto; white-space: nowrap; } - #mastodon .columns-area__panels__pane--navigational .navigation-panel { + .columns-area__panels__pane--navigational .navigation-panel { flex-direction: row; margin: 0; - padding: 0; - padding: 0 8px; - height: 5em; + padding: 12px; border-top: 1px solid; - overflow: scroll hidden; align-items: center; + overflow: visible; + height: auto; } - #mastodon .columns-area__panels__pane--navigational .navigation-panel::-webkit-scrollbar { + .columns-area__panels__pane--navigational .navigation-panel .flex-spacer { display: none; } - #mastodon .columns-area__panels__pane--navigational hr { + .columns-area__panels__pane--navigational hr { display: none; } - #mastodon .columns-area__panels__pane--navigational .trends__item__name > span, - #mastodon .columns-area__panels__pane--navigational .trends__item__sparkline { + .columns-area__panels__pane--navigational .trends__item__name > span, + .columns-area__panels__pane--navigational .trends__item__sparkline { display: none; } - #mastodon .columns-area__panels__pane--navigational .navigation-panel__legal, - #mastodon .columns-area__panels__pane--navigational h4, - #mastodon .columns-area__panels__pane--navigational .trends__item, - #mastodon .columns-area__panels__pane--navigational .trends__item__name { - all: unset; - display: contents !important; - } - #mastodon .columns-area__panels__pane--navigational .navigation-panel__legal::before, - #mastodon .columns-area__panels__pane--navigational h4::before, - #mastodon .columns-area__panels__pane--navigational .trends__item::before, - #mastodon .columns-area__panels__pane--navigational .trends__item__name::before, - #mastodon .columns-area__panels__pane--navigational .navigation-panel__legal::after, - #mastodon .columns-area__panels__pane--navigational h4::after, - #mastodon .columns-area__panels__pane--navigational .trends__item::after, - #mastodon .columns-area__panels__pane--navigational .trends__item__name::after { - content: unset; - } - #mastodon .columns-area__panels__pane--navigational h4 a span::after { + .columns-area__panels__pane--navigational h4 a span::after { content: ":" !important; } - #mastodon .columns-area__panels__pane--navigational:has(.getting-started__trends):has(.navigation-panel__sign-in-banner) .flex-spacer { - border-right: 1px solid var(--border-color); - height: 50%; - margin: 10px; + .columns-area__panels__pane--navigational .navigation-panel__legal { + display: contents; } - #mastodon .columns-area__panels__pane--navigational:has(.getting-started__trends):has(.navigation-panel__sign-in-banner) .getting-started__trends { - all: unset; - display: contents !important; - } - #mastodon .columns-area__panels__pane--navigational:has(.getting-started__trends) .trends__item:last-child a { - position: relative; - z-index: 3; - } - #mastodon .columns-area__panels__pane--navigational a, - #mastodon .columns-area__panels__pane--navigational h4 > span { + .columns-area__panels__pane--navigational a { position: relative; overflow: hidden; border-radius: var(--radius); font-weight: 600; font-size: 1.1em; + flex-grow: 1; text-align: center; min-width: max-content; + justify-content: center; transition: padding 0.2s; + padding-inline: 6px; } - #mastodon .columns-area__panels__pane--navigational a::before, - #mastodon .columns-area__panels__pane--navigational h4 > span::before { + .columns-area__panels__pane--navigational a::before { content: ""; position: absolute; inset: 0; @@ -3571,83 +2862,80 @@ a:focus-visible, border-radius: inherit; transition: inset 0.4s cubic-bezier(0, 0, 0, 1.2), opacity 0.2s; } - #mastodon .columns-area__panels__pane--navigational a svg, - #mastodon .columns-area__panels__pane--navigational h4 > span svg { + .columns-area__panels__pane--navigational a svg { width: 1em; height: 1em; + margin-inline-end: 0 !important; } - #mastodon .columns-area__panels__pane--navigational a.active, - #mastodon .columns-area__panels__pane--navigational h4 > span.active { - padding-inline: 1.2em; - margin-inline: 4px; + .columns-area__panels__pane--navigational a.active { position: relative; } - #mastodon .columns-area__panels__pane--navigational a.active::before, - #mastodon .columns-area__panels__pane--navigational h4 > span.active::before { + .columns-area__panels__pane--navigational a.active::before { inset: 0 !important; opacity: 0.1; } - #mastodon .columns-area__panels__pane--navigational span { - display: unset; + .columns-area__panels__pane--navigational span { + display: unset !important; + width: 0; + flex-grow: 1; + max-width: max-content; + overflow: hidden; + text-overflow: ellipsis; } } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .columns-area__panels__main { + :not(:has(.navigation-panel__sign-in-banner)) .columns-area__panels__main { margin-top: 2px !important; } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .ui__header { + :not(:has(.navigation-panel__sign-in-banner)) .ui__header { z-index: 199; } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .ui__header::before, - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .ui__header::after { + :not(:has(.navigation-panel__sign-in-banner)) .ui__header::before, + :not(:has(.navigation-panel__sign-in-banner)) .ui__header::after { all: unset; content: ""; position: fixed; bottom: 0; inset-inline: 0; - background: var(--background-color-tint, inherit); - border-top: 1px solid var(--divider); + background: var(--background-color); z-index: 200; - height: 61px; + height: 60px; visibility: visible; z-index: -1; - backdrop-filter: blur(10px); border-top: 1px solid var(--background-border-color); } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .ui__header::after { + :not(:has(.navigation-panel__sign-in-banner)) .ui__header::after { background: var(--elevated-tint); - height: 60px; + border: 0; } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .navigation-panel { + :not(:has(.navigation-panel__sign-in-banner)) .navigation-panel { + flex-wrap: wrap; border-top: none; - margin-top: -10px; + margin-bottom: 8px; + padding-block: 0; } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .navigation-panel a { - font-size: 1em; + :not(:has(.navigation-panel__sign-in-banner)) .navigation-panel a span { + display: none !important; } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .navigation-panel::after { - content: ""; - display: block; - position: sticky; - right: -20px; - min-width: 150px; - height: 100%; - margin-left: -50px; - background: inherit; - mask: linear-gradient(to right, transparent, #000); - -webkit-mask: linear-gradient(to right, transparent, #000); - pointer-events: none; - } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column { - min-height: 100vh; + :not(:has(.navigation-panel__sign-in-banner)) .navigation-panel [href="/settings/preferences"] { + position: absolute; + top: 12px; + inset-inline-end: 60px; + z-index: 900; + padding-block: 0; + height: 35px; + width: 35px; + box-sizing: border-box; + margin: 0; + border: 1px solid var(--border-color); } :root { --selector: '.column-link[href='/home'],.column-link[href='/notifications'],.column-link[href='/explore'],.column-link[href='/public/local'],.column-link[href='/lists']'; } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/home'], - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/notifications'], - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/explore'], - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/public/local'], - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/lists'] { + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/home'], + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/notifications'], + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/explore'], + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/public/local'], + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/lists'] { position: fixed !important; display: flex; flex-direction: column; @@ -3661,984 +2949,310 @@ a:focus-visible, justify-content: center; width: 19%; left: 0; - transform: translateX(10%); box-sizing: border-box; padding: 1px !important; } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/home']::before, - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/notifications']::before, - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/explore']::before, - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/public/local']::before, - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/lists']::before { + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/home']::before, + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/notifications']::before, + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/explore']::before, + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/public/local']::before, + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/lists']::before { inset-inline: 6px; } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/home'] span, - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/notifications'] span, - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/explore'] span, - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/public/local'] span, - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/lists'] span { + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/home'] span, + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/notifications'] span, + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/explore'] span, + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/public/local'] span, + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/lists'] span { margin: 0; font-size: 0; line-height: 1; opacity: 0; transition: font-size 0.4s, margin 0.7s, opacity 0.2s; } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/home'] svg, - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/notifications'] svg, - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/explore'] svg, - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/public/local'] svg, - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/lists'] svg { + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/home'] svg, + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/notifications'] svg, + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/explore'] svg, + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/public/local'] svg, + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/lists'] svg { width: 24px; height: 24px; transition: transform 0.1s; } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/home'] { - left: 0%; + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/home'] { + left: 2%; } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/notifications'] { - left: 19.25%; + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/notifications'] { + left: 21.25%; } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/explore'] { - left: 38.5%; + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/explore'] { + left: 40.5%; } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/public/local'] { - left: 57.75%; + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/public/local'] { + left: 59.75%; } - #mastodon:not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/lists'] { - left: 77%; - } - #mastodon:not(:has(.navigation-panel__sign-in-banner)):has(.column-link.active:not([href='/home']):not([href='/notifications']):not([href='/explore']):not([href='/public/local']):not([href='/lists']):not([href^="/lists"])) .tabs-bar__wrapper { - position: static; - height: auto; - z-index: unset; - margin: 0; - top: 0; - inset-inline: 0; - visibility: hidden; - } - #mastodon:not(:has(.navigation-panel__sign-in-banner)):has(.column-link.active:not([href='/home']):not([href='/notifications']):not([href='/explore']):not([href='/public/local']):not([href='/lists']):not([href^="/lists"])) .tabs-bar__wrapper #tabs-bar__portal, - #mastodon:not(:has(.navigation-panel__sign-in-banner)):has(.column-link.active:not([href='/home']):not([href='/notifications']):not([href='/explore']):not([href='/public/local']):not([href='/lists']):not([href^="/lists"])) .tabs-bar__wrapper .column-header__wrapper, - #mastodon:not(:has(.navigation-panel__sign-in-banner)):has(.column-link.active:not([href='/home']):not([href='/notifications']):not([href='/explore']):not([href='/public/local']):not([href='/lists']):not([href^="/lists"])) .tabs-bar__wrapper .column-header { - height: 100%; - } - #mastodon:not(:has(.navigation-panel__sign-in-banner)):has(.column-link.active:not([href='/home']):not([href='/notifications']):not([href='/explore']):not([href='/public/local']):not([href='/lists']):not([href^="/lists"])) .tabs-bar__wrapper .column-header__buttons { - visibility: visible; - height: auto; - width: 100%; - } - #mastodon:not(:has(.navigation-panel__sign-in-banner)):has(.column-link.active:not([href='/home']):not([href='/notifications']):not([href='/explore']):not([href='/public/local']):not([href='/lists']):not([href^="/lists"])) .tabs-bar__wrapper .column-header__buttons button { - height: 50px; - width: 100% !important; - text-align: left; - transform: none; - } - #mastodon:not(:has(.navigation-panel__sign-in-banner)):has(.column-link.active:not([href='/home']):not([href='/notifications']):not([href='/explore']):not([href='/public/local']):not([href='/lists']):not([href^="/lists"])) .tabs-bar__wrapper .column-header__buttons button::after { - content: attr(title); - margin-left: 1em; - font-weight: 600; - font-size: 0.9em; - } - #mastodon:not(:has(.navigation-panel__sign-in-banner)):has(.column-link.active:not([href='/home']):not([href='/notifications']):not([href='/explore']):not([href='/public/local']):not([href='/lists']):not([href^="/lists"])) .tabs-bar__wrapper .column-header__back-button, - #mastodon:not(:has(.navigation-panel__sign-in-banner)):has(.column-link.active:not([href='/home']):not([href='/notifications']):not([href='/explore']):not([href='/public/local']):not([href='/lists']):not([href^="/lists"])) .tabs-bar__wrapper .column-header > button { - display: none; - } - #mastodon:not(:has(.navigation-panel__sign-in-banner)):has(.column-link.active:not([href='/home']):not([href='/notifications']):not([href='/explore']):not([href='/public/local']):not([href='/lists']):not([href^="/lists"])) .tabs-bar__wrapper .column-header__collapsible { - visibility: visible; - } - #mastodon .columns-area__panels__main { - padding: 0 !important; - max-width: unset; - flex-grow: 1; - margin-top: 1px; - } - .is-composing .columns-area__panels__main { - padding-bottom: 40px !important; - } - #mastodon .columns-area__panels__main .scrollable, - #mastodon .columns-area__panels__main .account__header__image, - #mastodon .columns-area__panels__main > div { - border-radius: 0 !important; - } - #mastodon .dismissable-banner { - margin: 0; - padding-left: 4px; - } - #mastodon .status .status__avatar { - width: 42px !important; - min-width: 45px !important; - height: 45px !important; - background-size: 45px !important; - } - #mastodon .status .status__avatar > div { - width: 100% !important; - height: 100% !important; - background-size: cover !important; - } - #mastodon .status::before { - content: unset; - } - #mastodon .status__action-bar { - margin-bottom: -5px; - } - #mastodon .status__action-bar .icon-button { - margin: 0 !important; - justify-content: center; - } - #mastodon .icon-button:after { - content: unset !important; - } - #mastodon .navigation-panel { - margin-top: -200px; - padding-top: 200px; - height: calc(100vh + 200px - 55px); - border: 0; - border-inline-start: 1px solid; - padding-bottom: 90px; - } - #mastodon .columns-area { - padding-bottom: 0 !important; - overflow: hidden !important; - } - #mastodon .getting-started { - padding: 20px; - padding-bottom: 60px; - } - #mastodon .getting-started__wrapper { - flex-grow: 1; - overflow: visible !important; - } - #mastodon .compose-form { - padding: 10px; - margin-bottom: 100px; - } - #mastodon .compose-form::before { - content: ""; - position: absolute; - inset: 0; - background: var(--elevated-color); - z-index: -1; - } - #mastodon .about.about.about { - padding-inline: 10px !important; - } - #mastodon .about.about.about .account { - margin-top: 0; - } - #mastodon .about.about.about .about__header__hero, - #mastodon .about.about.about .about__section.active { - margin-inline: -11px !important; - width: unset; - border-radius: 0; - } - #mastodon .about.about.about .about__section.active { - margin-block: 20px !important; - } - #mastodon .about.about.about .about__section { - margin-bottom: 0; - border-bottom: 1px solid; + :not(:has(.navigation-panel__sign-in-banner)) .column-link[href='/lists'] { + left: 79%; } } -.admin-wrapper .sidebar-wrapper { - overflow: visible !important; - width: unset; +#hover-card, +.dropdown-menu { + border-radius: var(--radius); + animation: scaleIn 0.2s cubic-bezier(0, 0, 0, 1.1); } -.admin-wrapper .sidebar-wrapper .sidebar-wrapper__inner { - position: sticky; - top: 0; - max-height: 100vh !important; - overflow-y: auto !important; - pointer-events: all !important; - z-index: 100; +.dropdown-menu__container__list { + overflow: hidden auto; + border-radius: var(--radius); + max-height: 70vh; } -.admin-wrapper .sidebar-wrapper .sidebar-wrapper__inner .fa { - margin-inline-end: 1em !important; -} -.admin-wrapper .sidebar-wrapper .sidebar-wrapper__inner .sidebar > ul > li { +.dropdown-menu__item { overflow: hidden; - margin-inline: 15px !important; } -.admin-wrapper .sidebar-wrapper .sidebar-wrapper__inner .sidebar > ul > li > a:not(.selected) { - background: none; +.dropdown-menu__item a { + padding: 0.7em 1em !important; + transition: background-color 0.2s, color 0.2s; + min-width: 150px; } -.admin-wrapper .sidebar-wrapper .sidebar-wrapper__inner .sidebar > ul > li a { - display: flex !important; - align-items: center; - white-space: unset; +.dropdown-menu__separator { + margin: 0 !important; } -.admin-wrapper .sidebar-wrapper .sidebar-wrapper__inner .sidebar > ul > li.selected { - margin: 6px; +.interaction-modal { border-radius: var(--radius); + overflow-y: auto; + box-sizing: border-box; + width: 700px; + text-align: center; } -.admin-wrapper .sidebar-wrapper .sidebar-wrapper__inner .sidebar > ul > li.selected > a.selected { - font-weight: 600; - color: unset; - position: relative; - overflow: visible; - border-radius: 0 !important; - border: 0; -} -.admin-wrapper .sidebar-wrapper .sidebar-wrapper__inner .sidebar > ul > li.selected > a.selected::after { - content: ""; - position: absolute; - top: 100%; - inset-inline: 0; - height: 9999px; - background: inherit; - z-index: -1; -} -.admin-wrapper .sidebar-wrapper .sidebar-wrapper__inner .sidebar > ul > li > ul { - border-radius: var(--radius) !important; - overflow: hidden !important; - gap: 2px !important; - margin: 8px; - margin-top: 0; - background: none; -} -.admin-wrapper .sidebar-wrapper .sidebar-wrapper__inner .sidebar > ul > li > ul > li { - border-radius: 8px; -} -.admin-wrapper .sidebar-wrapper .sidebar-wrapper__inner .sidebar > ul > li > ul > li:not(:first-child):not(:last-child) { - margin-block: 2px; -} -.admin-wrapper .sidebar-wrapper .sidebar-wrapper__inner .sidebar > ul > li > ul > li a { - padding: 14px 16px; - font-weight: 600; - border: 0; -} -.admin-wrapper .sidebar-wrapper .sidebar-wrapper__inner .sidebar > ul > li > ul > li:not(.selected) a { - background-color: rgba(150,150,250,0.1); -} -.admin-wrapper .content__heading { - margin-bottom: 2em; -} -.admin-wrapper h4 { - margin: 0; - border-bottom: 0; -} -.admin-wrapper form > h4 { - margin-top: 2em !important; - border-bottom: 0 !important; - margin-bottom: 0 !important; -} -.admin-wrapper .flash-message, -.admin-wrapper .applications-list__item, -.admin-wrapper .filters-list__item { - border-radius: var(--radius); - border: 0; - overflow: clip; -} -.admin-wrapper .fields-row { - margin-inline: 0; - border-radius: var(--radius); - overflow: clip; - padding-top: 0; - gap: 2px; +.interaction-modal__choices { + gap: 10px; display: flex; flex-wrap: wrap; } -.admin-wrapper .fields-group:not(.fields-row__column), -.admin-wrapper .fields-row { - margin-bottom: 1em !important; -} -.admin-wrapper .fields-row > .fields-row__column { - max-width: unset; - width: 300px; - border-radius: 0 !important; - display: flex; - flex-direction: column; - flex-grow: 1; - margin: 0 !important; -} -.admin-wrapper .fields-row > .fields-row__column .fields-group { - border-radius: 0 !important; - margin: 0 !important; -} -.admin-wrapper .fields-row > .fields-row__column .fields-group > .with_block_label { - display: flex; - flex-direction: column; - height: 100%; -} -.admin-wrapper .fields-row > .fields-row__column .fields-group > .with_block_label > .label_input { - flex-grow: 1; -} -.admin-wrapper .fields-row > .fields-row__column .fields-group > .with_block_label > .label_input > textarea { - min-height: 100%; -} -.admin-wrapper .fields-row > .fields-row__column > :last-child { - flex-grow: 1; - align-items: flex-start; - border: 0; -} -.admin-wrapper .fields-row > .fields-row__column > :not(:first-child):not(:last-child) { - padding-block: 0.5em !important; - margin-block: -3px; -} -.admin-wrapper :not(.fields-row__column) > .fields-group, -.admin-wrapper .fields-row > *, -.admin-wrapper .label_input > ul, -.admin-wrapper .label_input__wrapper > ul, -.admin-wrapper .radio_buttons > ul, -.admin-wrapper .with_block_label.radio_buttons .label_input { +.interaction-modal__choices .interaction-modal__choices__choice { + max-height: 50vh; + overflow-y: auto; + border: 1px solid var(--border-color); + padding: 24px; + margin: 0; border-radius: var(--radius); - overflow: clip; - padding: 0; - display: flex; - flex-direction: column; - gap: 2px; -} -.admin-wrapper :not(.fields-row__column) > .fields-group > *, -.admin-wrapper .fields-row > * > *, -.admin-wrapper .label_input > ul > *, -.admin-wrapper .label_input__wrapper > ul > *, -.admin-wrapper .radio_buttons > ul > *, -.admin-wrapper .with_block_label.radio_buttons .label_input > * { - background-color: var(--elevated-color); - padding: 0.8rem; - margin-block: 0px; + transition: background 0.2s; position: relative; - border-radius: 0 !important; } -.admin-wrapper :not(.fields-row__column) > .fields-group > *:not(p):not(h6):not(.input-copy)::after, -.admin-wrapper .fields-row > * > *:not(p):not(h6):not(.input-copy)::after, -.admin-wrapper .label_input > ul > *:not(p):not(h6):not(.input-copy)::after, -.admin-wrapper .label_input__wrapper > ul > *:not(p):not(h6):not(.input-copy)::after, -.admin-wrapper .radio_buttons > ul > *:not(p):not(h6):not(.input-copy)::after, -.admin-wrapper .with_block_label.radio_buttons .label_input > *:not(p):not(h6):not(.input-copy)::after { - content: ""; - position: absolute; - inset: 0; - background-color: var(--hover-color); - z-index: -1; - opacity: 0; - transition: opacity 0.2s; -} -.admin-wrapper :not(.fields-row__column) > .fields-group > *:not(p):not(h6):not(.input-copy):hover::after, -.admin-wrapper .fields-row > * > *:not(p):not(h6):not(.input-copy):hover::after, -.admin-wrapper .label_input > ul > *:not(p):not(h6):not(.input-copy):hover::after, -.admin-wrapper .label_input__wrapper > ul > *:not(p):not(h6):not(.input-copy):hover::after, -.admin-wrapper .radio_buttons > ul > *:not(p):not(h6):not(.input-copy):hover::after, -.admin-wrapper .with_block_label.radio_buttons .label_input > *:not(p):not(h6):not(.input-copy):hover::after, -.admin-wrapper :not(.fields-row__column) > .fields-group > *:not(p):not(h6):not(.input-copy):focus-within::after, -.admin-wrapper .fields-row > * > *:not(p):not(h6):not(.input-copy):focus-within::after, -.admin-wrapper .label_input > ul > *:not(p):not(h6):not(.input-copy):focus-within::after, -.admin-wrapper .label_input__wrapper > ul > *:not(p):not(h6):not(.input-copy):focus-within::after, -.admin-wrapper .radio_buttons > ul > *:not(p):not(h6):not(.input-copy):focus-within::after, -.admin-wrapper .with_block_label.radio_buttons .label_input > *:not(p):not(h6):not(.input-copy):focus-within::after { - opacity: 1; -} -.admin-wrapper :not(.fields-row__column) > .fields-group .input-copy__wrapper, -.admin-wrapper .fields-row > * .input-copy__wrapper, -.admin-wrapper .label_input > ul .input-copy__wrapper, -.admin-wrapper .label_input__wrapper > ul .input-copy__wrapper, -.admin-wrapper .radio_buttons > ul .input-copy__wrapper, -.admin-wrapper .with_block_label.radio_buttons .label_input .input-copy__wrapper { - border: 1px solid var(--border-color-2); - border-radius: var(--radius); -} -.admin-wrapper :not(.fields-row__column) > .fields-group > .input, -.admin-wrapper .fields-row > * > .input, -.admin-wrapper .label_input > ul > .input, -.admin-wrapper .label_input__wrapper > ul > .input, -.admin-wrapper .radio_buttons > ul > .input, -.admin-wrapper .with_block_label.radio_buttons .label_input > .input, -.admin-wrapper :not(.fields-row__column) > .fields-group .checkbox, -.admin-wrapper .fields-row > * .checkbox, -.admin-wrapper .label_input > ul .checkbox, -.admin-wrapper .label_input__wrapper > ul .checkbox, -.admin-wrapper .radio_buttons > ul .checkbox, -.admin-wrapper .with_block_label.radio_buttons .label_input .checkbox, -.admin-wrapper :not(.fields-row__column) > .fields-group .radio, -.admin-wrapper .fields-row > * .radio, -.admin-wrapper .label_input > ul .radio, -.admin-wrapper .label_input__wrapper > ul .radio, -.admin-wrapper .radio_buttons > ul .radio, -.admin-wrapper .with_block_label.radio_buttons .label_input .radio { - flex-grow: 1; -} -.admin-wrapper :not(.fields-row__column) > .fields-group > .input:not(:last-child), -.admin-wrapper .fields-row > * > .input:not(:last-child), -.admin-wrapper .label_input > ul > .input:not(:last-child), -.admin-wrapper .label_input__wrapper > ul > .input:not(:last-child), -.admin-wrapper .radio_buttons > ul > .input:not(:last-child), -.admin-wrapper .with_block_label.radio_buttons .label_input > .input:not(:last-child), -.admin-wrapper :not(.fields-row__column) > .fields-group .checkbox:not(:last-child), -.admin-wrapper .fields-row > * .checkbox:not(:last-child), -.admin-wrapper .label_input > ul .checkbox:not(:last-child), -.admin-wrapper .label_input__wrapper > ul .checkbox:not(:last-child), -.admin-wrapper .radio_buttons > ul .checkbox:not(:last-child), -.admin-wrapper .with_block_label.radio_buttons .label_input .checkbox:not(:last-child), -.admin-wrapper :not(.fields-row__column) > .fields-group .radio:not(:last-child), -.admin-wrapper .fields-row > * .radio:not(:last-child), -.admin-wrapper .label_input > ul .radio:not(:last-child), -.admin-wrapper .label_input__wrapper > ul .radio:not(:last-child), -.admin-wrapper .radio_buttons > ul .radio:not(:last-child), -.admin-wrapper .with_block_label.radio_buttons .label_input .radio:not(:last-child) { - margin-bottom: 2px; +.interaction-modal__choices .prose:last-child { margin-bottom: 0; } -.admin-wrapper :not(.fields-row__column) > .fields-group > .input.radio .hint, -.admin-wrapper .fields-row > * > .input.radio .hint, -.admin-wrapper .label_input > ul > .input.radio .hint, -.admin-wrapper .label_input__wrapper > ul > .input.radio .hint, -.admin-wrapper .radio_buttons > ul > .input.radio .hint, -.admin-wrapper .with_block_label.radio_buttons .label_input > .input.radio .hint, -.admin-wrapper :not(.fields-row__column) > .fields-group .checkbox.radio .hint, -.admin-wrapper .fields-row > * .checkbox.radio .hint, -.admin-wrapper .label_input > ul .checkbox.radio .hint, -.admin-wrapper .label_input__wrapper > ul .checkbox.radio .hint, -.admin-wrapper .radio_buttons > ul .checkbox.radio .hint, -.admin-wrapper .with_block_label.radio_buttons .label_input .checkbox.radio .hint, -.admin-wrapper :not(.fields-row__column) > .fields-group .radio.radio .hint, -.admin-wrapper .fields-row > * .radio.radio .hint, -.admin-wrapper .label_input > ul .radio.radio .hint, -.admin-wrapper .label_input__wrapper > ul .radio.radio .hint, -.admin-wrapper .radio_buttons > ul .radio.radio .hint, -.admin-wrapper .with_block_label.radio_buttons .label_input .radio.radio .hint, -.admin-wrapper :not(.fields-row__column) > .fields-group > .input.checkbox .hint, -.admin-wrapper .fields-row > * > .input.checkbox .hint, -.admin-wrapper .label_input > ul > .input.checkbox .hint, -.admin-wrapper .label_input__wrapper > ul > .input.checkbox .hint, -.admin-wrapper .radio_buttons > ul > .input.checkbox .hint, -.admin-wrapper .with_block_label.radio_buttons .label_input > .input.checkbox .hint, -.admin-wrapper :not(.fields-row__column) > .fields-group .checkbox.checkbox .hint, -.admin-wrapper .fields-row > * .checkbox.checkbox .hint, -.admin-wrapper .label_input > ul .checkbox.checkbox .hint, -.admin-wrapper .label_input__wrapper > ul .checkbox.checkbox .hint, -.admin-wrapper .radio_buttons > ul .checkbox.checkbox .hint, -.admin-wrapper .with_block_label.radio_buttons .label_input .checkbox.checkbox .hint, -.admin-wrapper :not(.fields-row__column) > .fields-group .radio.checkbox .hint, -.admin-wrapper .fields-row > * .radio.checkbox .hint, -.admin-wrapper .label_input > ul .radio.checkbox .hint, -.admin-wrapper .label_input__wrapper > ul .radio.checkbox .hint, -.admin-wrapper .radio_buttons > ul .radio.checkbox .hint, -.admin-wrapper .with_block_label.radio_buttons .label_input .radio.checkbox .hint, -.admin-wrapper :not(.fields-row__column) > .fields-group > .input.radio label, -.admin-wrapper .fields-row > * > .input.radio label, -.admin-wrapper .label_input > ul > .input.radio label, -.admin-wrapper .label_input__wrapper > ul > .input.radio label, -.admin-wrapper .radio_buttons > ul > .input.radio label, -.admin-wrapper .with_block_label.radio_buttons .label_input > .input.radio label, -.admin-wrapper :not(.fields-row__column) > .fields-group .checkbox.radio label, -.admin-wrapper .fields-row > * .checkbox.radio label, -.admin-wrapper .label_input > ul .checkbox.radio label, -.admin-wrapper .label_input__wrapper > ul .checkbox.radio label, -.admin-wrapper .radio_buttons > ul .checkbox.radio label, -.admin-wrapper .with_block_label.radio_buttons .label_input .checkbox.radio label, -.admin-wrapper :not(.fields-row__column) > .fields-group .radio.radio label, -.admin-wrapper .fields-row > * .radio.radio label, -.admin-wrapper .label_input > ul .radio.radio label, -.admin-wrapper .label_input__wrapper > ul .radio.radio label, -.admin-wrapper .radio_buttons > ul .radio.radio label, -.admin-wrapper .with_block_label.radio_buttons .label_input .radio.radio label, -.admin-wrapper :not(.fields-row__column) > .fields-group > .input.checkbox label, -.admin-wrapper .fields-row > * > .input.checkbox label, -.admin-wrapper .label_input > ul > .input.checkbox label, -.admin-wrapper .label_input__wrapper > ul > .input.checkbox label, -.admin-wrapper .radio_buttons > ul > .input.checkbox label, -.admin-wrapper .with_block_label.radio_buttons .label_input > .input.checkbox label, -.admin-wrapper :not(.fields-row__column) > .fields-group .checkbox.checkbox label, -.admin-wrapper .fields-row > * .checkbox.checkbox label, -.admin-wrapper .label_input > ul .checkbox.checkbox label, -.admin-wrapper .label_input__wrapper > ul .checkbox.checkbox label, -.admin-wrapper .radio_buttons > ul .checkbox.checkbox label, -.admin-wrapper .with_block_label.radio_buttons .label_input .checkbox.checkbox label, -.admin-wrapper :not(.fields-row__column) > .fields-group .radio.checkbox label, -.admin-wrapper .fields-row > * .radio.checkbox label, -.admin-wrapper .label_input > ul .radio.checkbox label, -.admin-wrapper .label_input__wrapper > ul .radio.checkbox label, -.admin-wrapper .radio_buttons > ul .radio.checkbox label, -.admin-wrapper .with_block_label.radio_buttons .label_input .radio.checkbox label { - margin-bottom: 0 !important; -} -.admin-wrapper :not(.fields-row__column) > .fields-group > .input .label_input, -.admin-wrapper .fields-row > * > .input .label_input, -.admin-wrapper .label_input > ul > .input .label_input, -.admin-wrapper .label_input__wrapper > ul > .input .label_input, -.admin-wrapper .radio_buttons > ul > .input .label_input, -.admin-wrapper .with_block_label.radio_buttons .label_input > .input .label_input, -.admin-wrapper :not(.fields-row__column) > .fields-group .checkbox .label_input, -.admin-wrapper .fields-row > * .checkbox .label_input, -.admin-wrapper .label_input > ul .checkbox .label_input, -.admin-wrapper .label_input__wrapper > ul .checkbox .label_input, -.admin-wrapper .radio_buttons > ul .checkbox .label_input, -.admin-wrapper .with_block_label.radio_buttons .label_input .checkbox .label_input, -.admin-wrapper :not(.fields-row__column) > .fields-group .radio .label_input, -.admin-wrapper .fields-row > * .radio .label_input, -.admin-wrapper .label_input > ul .radio .label_input, -.admin-wrapper .label_input__wrapper > ul .radio .label_input, -.admin-wrapper .radio_buttons > ul .radio .label_input, -.admin-wrapper .with_block_label.radio_buttons .label_input .radio .label_input { - flex-direction: column; -} -.admin-wrapper :not(.fields-row__column) > .fields-group > .input .label_input > label, -.admin-wrapper .fields-row > * > .input .label_input > label, -.admin-wrapper .label_input > ul > .input .label_input > label, -.admin-wrapper .label_input__wrapper > ul > .input .label_input > label, -.admin-wrapper .radio_buttons > ul > .input .label_input > label, -.admin-wrapper .with_block_label.radio_buttons .label_input > .input .label_input > label, -.admin-wrapper :not(.fields-row__column) > .fields-group .checkbox .label_input > label, -.admin-wrapper .fields-row > * .checkbox .label_input > label, -.admin-wrapper .label_input > ul .checkbox .label_input > label, -.admin-wrapper .label_input__wrapper > ul .checkbox .label_input > label, -.admin-wrapper .radio_buttons > ul .checkbox .label_input > label, -.admin-wrapper .with_block_label.radio_buttons .label_input .checkbox .label_input > label, -.admin-wrapper :not(.fields-row__column) > .fields-group .radio .label_input > label, -.admin-wrapper .fields-row > * .radio .label_input > label, -.admin-wrapper .label_input > ul .radio .label_input > label, -.admin-wrapper .label_input__wrapper > ul .radio .label_input > label, -.admin-wrapper .radio_buttons > ul .radio .label_input > label, -.admin-wrapper .with_block_label.radio_buttons .label_input .radio .label_input > label { - margin-bottom: 0; - padding-top: 0.1em; -} -.admin-wrapper :not(.fields-row__column) > .fields-group > .input .label_input__wrapper > :not(.checkbox), -.admin-wrapper .fields-row > * > .input .label_input__wrapper > :not(.checkbox), -.admin-wrapper .label_input > ul > .input .label_input__wrapper > :not(.checkbox), -.admin-wrapper .label_input__wrapper > ul > .input .label_input__wrapper > :not(.checkbox), -.admin-wrapper .radio_buttons > ul > .input .label_input__wrapper > :not(.checkbox), -.admin-wrapper .with_block_label.radio_buttons .label_input > .input .label_input__wrapper > :not(.checkbox), -.admin-wrapper :not(.fields-row__column) > .fields-group .checkbox .label_input__wrapper > :not(.checkbox), -.admin-wrapper .fields-row > * .checkbox .label_input__wrapper > :not(.checkbox), -.admin-wrapper .label_input > ul .checkbox .label_input__wrapper > :not(.checkbox), -.admin-wrapper .label_input__wrapper > ul .checkbox .label_input__wrapper > :not(.checkbox), -.admin-wrapper .radio_buttons > ul .checkbox .label_input__wrapper > :not(.checkbox), -.admin-wrapper .with_block_label.radio_buttons .label_input .checkbox .label_input__wrapper > :not(.checkbox), -.admin-wrapper :not(.fields-row__column) > .fields-group .radio .label_input__wrapper > :not(.checkbox), -.admin-wrapper .fields-row > * .radio .label_input__wrapper > :not(.checkbox), -.admin-wrapper .label_input > ul .radio .label_input__wrapper > :not(.checkbox), -.admin-wrapper .label_input__wrapper > ul .radio .label_input__wrapper > :not(.checkbox), -.admin-wrapper .radio_buttons > ul .radio .label_input__wrapper > :not(.checkbox), -.admin-wrapper .with_block_label.radio_buttons .label_input .radio .label_input__wrapper > :not(.checkbox) { - margin-top: 0.4em; -} -.admin-wrapper :not(.fields-row__column) > .fields-group > .input .checkbox, -.admin-wrapper .fields-row > * > .input .checkbox, -.admin-wrapper .label_input > ul > .input .checkbox, -.admin-wrapper .label_input__wrapper > ul > .input .checkbox, -.admin-wrapper .radio_buttons > ul > .input .checkbox, -.admin-wrapper .with_block_label.radio_buttons .label_input > .input .checkbox, -.admin-wrapper :not(.fields-row__column) > .fields-group .checkbox .checkbox, -.admin-wrapper .fields-row > * .checkbox .checkbox, -.admin-wrapper .label_input > ul .checkbox .checkbox, -.admin-wrapper .label_input__wrapper > ul .checkbox .checkbox, -.admin-wrapper .radio_buttons > ul .checkbox .checkbox, -.admin-wrapper .with_block_label.radio_buttons .label_input .checkbox .checkbox, -.admin-wrapper :not(.fields-row__column) > .fields-group .radio .checkbox, -.admin-wrapper .fields-row > * .radio .checkbox, -.admin-wrapper .label_input > ul .radio .checkbox, -.admin-wrapper .label_input__wrapper > ul .radio .checkbox, -.admin-wrapper .radio_buttons > ul .radio .checkbox, -.admin-wrapper .with_block_label.radio_buttons .label_input .radio .checkbox { - inset: 0; - padding: 1em !important; -} -.admin-wrapper :not(.fields-row__column) > .fields-group li.checkbox, -.admin-wrapper .fields-row > * li.checkbox, -.admin-wrapper .label_input > ul li.checkbox, -.admin-wrapper .label_input__wrapper > ul li.checkbox, -.admin-wrapper .radio_buttons > ul li.checkbox, -.admin-wrapper .with_block_label.radio_buttons .label_input li.checkbox { - width: calc(50% - 27px); -} -.admin-wrapper :not(.fields-row__column) > .fields-group li.checkbox label, -.admin-wrapper .fields-row > * li.checkbox label, -.admin-wrapper .label_input > ul li.checkbox label, -.admin-wrapper .label_input__wrapper > ul li.checkbox label, -.admin-wrapper .radio_buttons > ul li.checkbox label, -.admin-wrapper .with_block_label.radio_buttons .label_input li.checkbox label { - position: static; - padding-top: 0; -} -.admin-wrapper :not(.fields-row__column) > .fields-group li.checkbox label::before, -.admin-wrapper .fields-row > * li.checkbox label::before, -.admin-wrapper .label_input > ul li.checkbox label::before, -.admin-wrapper .label_input__wrapper > ul li.checkbox label::before, -.admin-wrapper .radio_buttons > ul li.checkbox label::before, -.admin-wrapper .with_block_label.radio_buttons .label_input li.checkbox label::before { - content: ""; - position: absolute; - inset: 0; -} -.admin-wrapper :not(.fields-row__column) > .fields-group li.checkbox label input, -.admin-wrapper .fields-row > * li.checkbox label input, -.admin-wrapper .label_input > ul li.checkbox label input, -.admin-wrapper .label_input__wrapper > ul li.checkbox label input, -.admin-wrapper .radio_buttons > ul li.checkbox label input, -.admin-wrapper .with_block_label.radio_buttons .label_input li.checkbox label input { - inset: 1em !important; -} -.admin-wrapper :not(.fields-row__column) > .fields-group > h6, -.admin-wrapper .fields-row > * > h6, -.admin-wrapper .label_input > ul > h6, -.admin-wrapper .label_input__wrapper > ul > h6, -.admin-wrapper .radio_buttons > ul > h6, -.admin-wrapper .with_block_label.radio_buttons .label_input > h6, -.admin-wrapper :not(.fields-row__column) > .fields-group > p, -.admin-wrapper .fields-row > * > p, -.admin-wrapper .label_input > ul > p, -.admin-wrapper .label_input__wrapper > ul > p, -.admin-wrapper .radio_buttons > ul > p, -.admin-wrapper .with_block_label.radio_buttons .label_input > p { - margin: 0; -} -.admin-wrapper :not(.fields-row__column) > .fields-group > h6:not(:last-child), -.admin-wrapper .fields-row > * > h6:not(:last-child), -.admin-wrapper .label_input > ul > h6:not(:last-child), -.admin-wrapper .label_input__wrapper > ul > h6:not(:last-child), -.admin-wrapper .radio_buttons > ul > h6:not(:last-child), -.admin-wrapper .with_block_label.radio_buttons .label_input > h6:not(:last-child), -.admin-wrapper :not(.fields-row__column) > .fields-group > p:not(:last-child), -.admin-wrapper .fields-row > * > p:not(:last-child), -.admin-wrapper .label_input > ul > p:not(:last-child), -.admin-wrapper .label_input__wrapper > ul > p:not(:last-child), -.admin-wrapper .radio_buttons > ul > p:not(:last-child), -.admin-wrapper .with_block_label.radio_buttons .label_input > p:not(:last-child) { - padding-bottom: 0; -} -.admin-wrapper ul { - flex-direction: row !important; - flex-wrap: wrap; - gap: 2px; - margin-top: 0.4em; -} -.admin-wrapper .spacer { - border-top: 1px solid var(--border-color) !important; -} -.batch-table, -.table, -:not(.batch-table__row__content) > table { - overflow: clip; - border-radius: var(--radius); - border-spacing: 0 2px; - border-collapse: separate; -} -.batch-table .batch-table__toolbar, -.table .batch-table__toolbar, -:not(.batch-table__row__content) > table .batch-table__toolbar, -.batch-table .batch-table__row, -.table .batch-table__row, -:not(.batch-table__row__content) > table .batch-table__row, -.batch-table tr > *, -.table tr > *, -:not(.batch-table__row__content) > table tr > * { - border: 0; - margin-bottom: 2px !important; -} -.batch-table td, -.table td, -:not(.batch-table__row__content) > table td, -.batch-table th, -.table th, -:not(.batch-table__row__content) > table th, -.batch-table .batch-table__row, -.table .batch-table__row, -:not(.batch-table__row__content) > table .batch-table__row { - position: relative; -} -.batch-table tr > td > div > span, -.table tr > td > div > span, -:not(.batch-table__row__content) > table tr > td > div > span, -.batch-table tr > th > div > span, -.table tr > th > div > span, -:not(.batch-table__row__content) > table tr > th > div > span { - padding-inline: 0.7em; - display: inline-block; -} -.keyboard-shortcuts td { - padding: 0.7em; -} -.batch-table .batch-table__row, -.table .batch-table__row, -:not(.batch-table__row__content) > table .batch-table__row, -.batch-table th, -.table th, -:not(.batch-table__row__content) > table th, -.batch-table > tbody > tr > td, -.table > tbody > tr > td, -:not(.batch-table__row__content) > table > tbody > tr > td, -.batch-table tfoot td, -.table tfoot td, -:not(.batch-table__row__content) > table tfoot td { - background: var(--elevated-color) !important; - vertical-align: middle; -} -.batch-table .batch-table__row::after, -.table .batch-table__row::after, -:not(.batch-table__row__content) > table .batch-table__row::after, -.batch-table th::after, -.table th::after, -:not(.batch-table__row__content) > table th::after, -.batch-table > tbody > tr > td::after, -.table > tbody > tr > td::after, -:not(.batch-table__row__content) > table > tbody > tr > td::after, -.batch-table tfoot td::after, -.table tfoot td::after, -:not(.batch-table__row__content) > table tfoot td::after { - content: ""; - position: absolute; - inset: 0 0; - background: var(--hover-color); - opacity: 0; - transition: 0.2s; - pointer-events: none; -} -.batch-table .batch-table__row:hover::after, -.table .batch-table__row:hover::after, -:not(.batch-table__row__content) > table .batch-table__row:hover::after, -.batch-table th:hover::after, -.table th:hover::after, -:not(.batch-table__row__content) > table th:hover::after, -.batch-table > tbody > tr > td:hover::after, -.table > tbody > tr > td:hover::after, -:not(.batch-table__row__content) > table > tbody > tr > td:hover::after, -.batch-table tfoot td:hover::after, -.table tfoot td:hover::after, -:not(.batch-table__row__content) > table tfoot td:hover::after, -.batch-table .batch-table__row:focus-within::after, -.table .batch-table__row:focus-within::after, -:not(.batch-table__row__content) > table .batch-table__row:focus-within::after, -.batch-table th:focus-within::after, -.table th:focus-within::after, -:not(.batch-table__row__content) > table th:focus-within::after, -.batch-table > tbody > tr > td:focus-within::after, -.table > tbody > tr > td:focus-within::after, -:not(.batch-table__row__content) > table > tbody > tr > td:focus-within::after, -.batch-table tfoot td:focus-within::after, -.table tfoot td:focus-within::after, -:not(.batch-table__row__content) > table tfoot td:focus-within::after { - opacity: 1; -} -.batch-table .batch-table__row > a:first-child:last-child, -.table .batch-table__row > a:first-child:last-child, -:not(.batch-table__row__content) > table .batch-table__row > a:first-child:last-child, -.batch-table th > a:first-child:last-child, -.table th > a:first-child:last-child, -:not(.batch-table__row__content) > table th > a:first-child:last-child, -.batch-table > tbody > tr > td > a:first-child:last-child, -.table > tbody > tr > td > a:first-child:last-child, -:not(.batch-table__row__content) > table > tbody > tr > td > a:first-child:last-child, -.batch-table tfoot td > a:first-child:last-child, -.table tfoot td > a:first-child:last-child, -:not(.batch-table__row__content) > table tfoot td > a:first-child:last-child { - margin: 0; - width: 100%; - padding: 0.5em; -} -.batch-table th:hover td:not([rowspan])::after, -.table th:hover td:not([rowspan])::after, -:not(.batch-table__row__content) > table th:hover td:not([rowspan])::after, -.batch-table tr:hover td:not([rowspan])::after, -.table tr:hover td:not([rowspan])::after, -:not(.batch-table__row__content) > table tr:hover td:not([rowspan])::after, -.batch-table th:hover th:not([rowspan])::after, -.table th:hover th:not([rowspan])::after, -:not(.batch-table__row__content) > table th:hover th:not([rowspan])::after, -.batch-table tr:hover th:not([rowspan])::after, -.table tr:hover th:not([rowspan])::after, -:not(.batch-table__row__content) > table tr:hover th:not([rowspan])::after { - opacity: 1 !important; -} -.batch-table th [rowspan]:hover ~ td::after, -.table th [rowspan]:hover ~ td::after, -:not(.batch-table__row__content) > table th [rowspan]:hover ~ td::after, -.batch-table tr [rowspan]:hover ~ td::after, -.table tr [rowspan]:hover ~ td::after, -:not(.batch-table__row__content) > table tr [rowspan]:hover ~ td::after { - opacity: 0 !important; -} -.batch-table th [rowspan]::after, -.table th [rowspan]::after, -:not(.batch-table__row__content) > table th [rowspan]::after, -.batch-table tr [rowspan]::after, -.table tr [rowspan]::after, -:not(.batch-table__row__content) > table tr [rowspan]::after { - inset-inline: -900px; -} -.layout-multiple-columns #mastodon .columns-area { - overflow: auto hidden !important; - padding: 0; -} -.layout-multiple-columns #mastodon .columns-area .scrollable:not(.scrollable--flex) { - padding: 0px !important; - padding-bottom: 40vh !important; -} -.layout-multiple-columns #mastodon .columns-area .scrollable:not(.scrollable--flex)::before { - content: ""; - position: absolute; - inset: 0; - background-color: inherit; - z-index: -1; -} -.layout-multiple-columns #mastodon .columns-area .scrollable:not(.scrollable--flex) .account-timeline__header, -.layout-multiple-columns #mastodon .columns-area .scrollable:not(.scrollable--flex) .dismissable-banner { - margin: 0px !important; -} -.layout-multiple-columns #mastodon .columns-area .focusable, -.layout-multiple-columns #mastodon .columns-area .entry, -.layout-multiple-columns #mastodon .columns-area .statuses-grid__item .detailed-status, -.layout-multiple-columns #mastodon .columns-area .trends__item, -.layout-multiple-columns #mastodon .columns-area .story, -.layout-multiple-columns #mastodon .columns-area .account-card, -.layout-multiple-columns #mastodon .columns-area .scrollable :not(.focusable) > .account, -.layout-multiple-columns #mastodon .columns-area .timeline-hint { - border-radius: 0; -} -.layout-multiple-columns #mastodon .columns-area .focusable::before, -.layout-multiple-columns #mastodon .columns-area .entry::before, -.layout-multiple-columns #mastodon .columns-area .statuses-grid__item .detailed-status::before, -.layout-multiple-columns #mastodon .columns-area .trends__item::before, -.layout-multiple-columns #mastodon .columns-area .story::before, -.layout-multiple-columns #mastodon .columns-area .account-card::before, -.layout-multiple-columns #mastodon .columns-area .scrollable :not(.focusable) > .account::before, -.layout-multiple-columns #mastodon .columns-area .timeline-hint::before { - border-radius: 0 !important; -} -.layout-multiple-columns #mastodon .columns-area .focusable::after, -.layout-multiple-columns #mastodon .columns-area .entry::after, -.layout-multiple-columns #mastodon .columns-area .statuses-grid__item .detailed-status::after, -.layout-multiple-columns #mastodon .columns-area .trends__item::after, -.layout-multiple-columns #mastodon .columns-area .story::after, -.layout-multiple-columns #mastodon .columns-area .account-card::after, -.layout-multiple-columns #mastodon .columns-area .scrollable :not(.focusable) > .account::after, -.layout-multiple-columns #mastodon .columns-area .timeline-hint::after { - inset-inline: 0 !important; -} -.layout-multiple-columns #mastodon .columns-area [class*="explore__"] > * { - border-radius: var(--radius); -} -.layout-multiple-columns #mastodon .columns-area .detailed-status__wrapper { - margin: 0 !important; -} -.layout-multiple-columns #mastodon .columns-area .status__action-bar { - margin-bottom: 0px; - gap: 0; - margin-inline-end: 0 !important; -} -.layout-multiple-columns #mastodon .columns-area .status__action-bar :not(i):not(.status__action-bar-spacer) { - display: flex; - flex-grow: 9999; - justify-content: center !important; - max-width: 55px; - min-width: max-content; -} -.layout-multiple-columns #mastodon .columns-area .status__action-bar > .icon-button:first-child { - margin-inline-start: -8px !important; -} -.layout-multiple-columns #mastodon .columns-area .status__action-bar, -.layout-multiple-columns #mastodon .columns-area .detailed-status__action-bar, -.layout-multiple-columns #mastodon .columns-area .picture-in-picture__footer { - flex-wrap: wrap; -} -.layout-multiple-columns #mastodon .columns-area .follow_requests-unlocked_explanation { - margin: 0 !important; -} -.layout-multiple-columns #mastodon .columns-area .column-header, -.layout-multiple-columns #mastodon .columns-area .scrollable, -.layout-multiple-columns #mastodon .columns-area .column-back-button, -.layout-multiple-columns #mastodon .columns-area .account__header__image { - border-radius: 0 !important; -} -.layout-multiple-columns #mastodon .columns-area .icon-button:after { - content: unset !important; -} -.layout-multiple-columns #mastodon .columns-area > div { - border: 0 !important; - padding: 0 !important; -} -.layout-multiple-columns #mastodon .columns-area > div:not(.drawer):not(:last-child) { - margin-inline-end: 2px !important; -} -.layout-multiple-columns #mastodon .columns-area > div.column { - flex-grow: 1; - max-width: 600px; -} -.layout-multiple-columns #mastodon .columns-area > div:first-child { - margin-inline-start: auto !important; -} -.layout-multiple-columns #mastodon .columns-area > div:last-child { - margin-inline-end: auto !important; -} -.layout-multiple-columns #mastodon .columns-area .drawer { - padding-inline: 6px !important; - padding-top: 20px !important; - overflow: clip; -} -.layout-multiple-columns #mastodon .columns-area .drawer .drawer__header { - border-radius: var(--radius-round); - background: var(--elevated-tint); - margin-inline: 10px; - overflow: hidden; - border: 0 !important; -} -.layout-multiple-columns #mastodon .columns-area .drawer .drawer__header a { - border: 0; -} -.layout-multiple-columns #mastodon .columns-area .drawer .drawer__header a:first-child { - padding-inline-start: 15px !important; -} -.layout-multiple-columns #mastodon .columns-area .drawer .drawer__header a:last-child { - padding-inline-end: 15px !important; -} -.layout-multiple-columns #mastodon .columns-area .drawer .search { - z-index: 2; -} -.layout-multiple-columns #mastodon .columns-area .drawer > .drawer__pager { - border: 0; - overflow: visible !important; -} -.layout-multiple-columns #mastodon .columns-area .drawer > .drawer__pager > .drawer__inner:not(.darker) { - top: -20px; - margin-inline-start: -6px; - margin-inline-end: -4px; - width: calc(100% + 10px); - padding-inline-start: 6px; - padding-inline-end: 4px; - height: calc(100% + 20px); -} -.layout-multiple-columns #mastodon .columns-area .drawer .drawer__inner__mastodon { - margin-inline: -6px; - margin-inline-end: -4px; - z-index: -1; -} -.layout-multiple-columns #mastodon .columns-area .search { - margin-inline: 10px; -} -.layout-multiple-columns #mastodon .columns-area .compose-form { - display: flex; - flex-direction: column; -} -.layout-multiple-columns #mastodon .columns-area .drawer__inner:not(.darker), -.layout-multiple-columns #mastodon .columns-area .drawer__inner__mastodon { - background-color: transparent; - border: 0 !important; - background-color: transparent !important; -} -.layout-multiple-columns #mastodon .columns-area .drawer__inner.darker { - padding: 0 !important; - border-radius: var(--radius-round) var(--radius-round) 0 0; -} -.layout-multiple-columns #mastodon .columns-area .drawer__inner.darker::before { - content: ""; - position: absolute; - inset: 0; - background: var(--elevated-tint); - pointer-events: none; -} -.layout-multiple-columns #mastodon .columns-area .getting-started__trends { - padding: 0px 20px; -} -.layout-multiple-columns #mastodon .columns-area .column-header { - border-top: 0; -} -.layout-multiple-columns #mastodon .columns-area .column-header__title { - padding-block: 0; -} -.layout-multiple-columns #mastodon .columns-area .status { - padding-bottom: 10px !important; -} -.layout-multiple-columns #mastodon .columns-area .detailed-status .status__content { - font-size: 1.3em; +.interaction-modal__choices h3 { + margin-bottom: 10px; } .modal-root__container { animation: bounceIn 0.7s; } +@media (max-width: 890px) { + .modal-root__modal { + margin-top: auto; + max-width: 100%; + border-radius: var(--radius) var(--radius) 0 0; + } +} +.picture-in-picture { + z-index: 101; +} +.picture-in-picture .picture-in-picture__header { + border-radius: var(--radius) var(--radius) 0 0; +} +.picture-in-picture .media-gallery, +.picture-in-picture .video-player, +.picture-in-picture .status-card.horizontal.interactive, +.picture-in-picture .status-card, +.picture-in-picture .audio-player, +.picture-in-picture .picture-in-picture-placeholder { + --radius: 0; + margin: 0 !important; +} +.picture-in-picture .picture-in-picture__footer { + border-radius: 0 0 var(--radius) var(--radius); +} +.report-modal[style="max-width: 960px;"] { + background: var(--background-color); +} +.report-modal[style="max-width: 960px;"], +.report-modal[style="max-width: 960px;"] * { + color: inherit !important; +} +.report-modal[style="max-width: 960px;"] .report-modal__comment { + min-width: unset; + width: 370px; + max-width: unset; + flex: none; + padding: 20px; + height: 100%; + overflow-y: auto; +} +.report-modal[style="max-width: 960px;"] .setting-text__wrapper { + border-radius: var(--radius); + overflow: hidden; + background: none; +} +.report-modal[style="max-width: 960px;"] .setting-text__wrapper textarea { + border: 0; + max-height: unset !important; + background: none; +} +.report-modal[style="max-width: 960px;"] .focal-point-modal__content { + position: sticky; + top: 0; + max-height: 100vh; + flex-grow: 0 !important; + max-width: 100%; +} +.report-modal[style="max-width: 960px;"] .focal-point { + width: 100%; + height: 100%; +} +.report-modal[style="max-width: 960px;"] .audio-player, +.report-modal[style="max-width: 960px;"] .focal-point img { + width: unset !important; + height: unset !important; + max-height: 100% !important; + max-width: 100% !important; +} +.report-modal[style="max-width: 960px;"] .audio-player { + margin: 10px !important; + width: 600px !important; + max-width: calc(100% - 20px) !important; +} +.report-modal[style="max-width: 960px;"] .focal-point__reticle { + box-shadow: 0 0 300px 200px rgba(0,0,0,0.2); +} +@media not all and (max-width: 900px) { + .report-modal[style="max-width: 960px;"] { + max-width: max-content !important; + max-height: 98vh; + border: 0; + box-shadow: var(--shadow); + overflow: hidden; + border-radius: var(--radius); + width: 98vw; + } + .report-modal[style="max-width: 960px;"] .report-modal__container { + flex-wrap: nowrap; + border: 0; + max-width: max-content; + max-height: 100%; + } + .report-modal[style="max-width: 960px;"] .report-modal__target { + position: absolute; + padding: 24px 20px 12px; + font-weight: bold; + width: 348px; + box-sizing: border-box; + text-align: start; + background: inherit; + } + .report-modal[style="max-width: 960px;"] .report-modal__close { + position: fixed !important; + right: 12px; + top: 12px; + order: 2; + color: #fff; + background: var(--modal-background-color); + padding: 12px; + } + .report-modal[style="max-width: 960px;"] .report-modal__comment { + padding-top: calc(30px + 2em); + padding-bottom: 160px; + } + .report-modal[style="max-width: 960px;"] .focal-point-modal__content, + .report-modal[style="max-width: 960px;"] .focal-point { + overflow: visible; + } + .report-modal[style="max-width: 960px;"] .focal-point img { + min-width: 500px; + } + .report-modal[style="max-width: 960px;"] .focal-point__preview { + inset-inline-start: -220px; + right: unset; + bottom: 20px; + pointer-events: none; + text-align: end; + } + .report-modal[style="max-width: 960px;"] .focal-point__preview strong { + color: inherit; + } + .report-modal[style="max-width: 960px;"] .focal-point__preview div { + border-radius: var(--radius); + box-shadow: none; + } +} +@media (max-width: 900px) { + .report-modal[style="max-width: 960px;"] { + height: auto; + width: 100vw; + max-width: unset !important; + border-radius: 0; + } + .report-modal[style="max-width: 960px;"] .report-modal__container { + height: auto; + min-height: 0; + } + .report-modal[style="max-width: 960px;"] .report-modal__container { + flex-direction: column-reverse; + flex-wrap: nowrap; + flex-grow: 1; + } + .report-modal[style="max-width: 960px;"] .report-modal__comment { + width: 100%; + border: 0; + max-height: 70%; + flex: 0 0 auto; + height: unset; + order: unset; + } +} +.emoji-picker-dropdown__menu { + border-radius: var(--radius); + overflow: hidden; + resize: both; + width: 400px; +} +.emoji-mart { + display: flex !important; + flex-direction: column !important; + width: 100% !important; + height: 100% !important; +} +.emoji-mart-scroll { + flex-grow: 1; + max-height: unset !important; +} +.emoji-mart-bar { + order: 2; +} +.emoji-mart-category-list { + overflow: visible !important; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(calc(20px + 6%), 1fr)); +} +.emoji-mart-category-list li { + display: contents; +} +.emoji-mart-category-list button { + position: relative; + padding: 0 !important; + padding-top: 100% !important; +} +.emoji-mart-category-list button img, +.emoji-mart-category-list button span { + height: calc(100% - 10px) !important; + width: calc(100% - 10px) !important; + inset: 5px; + position: absolute !important; + transition: transform 0.1s cubic-bezier(0, 0, 0, 1); +} +.emoji-mart-category-list button:hover img, +.emoji-mart-category-list button:hover span { + transform: scale(1.2); +} +.emoji-picker-dropdown__modifiers { + top: 16px; +} \ No newline at end of file diff --git a/app/javascript/styles/win95.scss b/app/javascript/styles/win95.scss index 66d451303a..2302dc40d5 100644 --- a/app/javascript/styles/win95.scss +++ b/app/javascript/styles/win95.scss @@ -2503,6 +2503,7 @@ body { background: $win95-tooltip-yellow; border: 1px solid black; padding: 4px; + margin-bottom: 24px; h1, h1 small { color:black; @@ -2510,8 +2511,6 @@ body { text-overflow: unset; } - margin-bottom: 24px; - &:after { content: ""; display:block; diff --git a/app/lib/access_grant_extension.rb b/app/lib/access_grant_extension.rb new file mode 100644 index 0000000000..bf8f5ae25a --- /dev/null +++ b/app/lib/access_grant_extension.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module AccessGrantExtension + extend ActiveSupport::Concern + + included do + scope :expired, -> { where.not(expires_in: nil).where('created_at + MAKE_INTERVAL(secs => expires_in) < NOW()') } + scope :revoked, -> { where.not(revoked_at: nil).where(revoked_at: ...Time.now.utc) } + end +end diff --git a/app/lib/access_token_extension.rb b/app/lib/access_token_extension.rb index f51bde4927..6e06f988a5 100644 --- a/app/lib/access_token_extension.rb +++ b/app/lib/access_token_extension.rb @@ -6,7 +6,13 @@ module AccessTokenExtension included do include Redisable + has_many :web_push_subscriptions, class_name: 'Web::PushSubscription', inverse_of: :access_token + after_commit :push_to_streaming_api + + scope :expired, -> { where.not(expires_in: nil).where('created_at + MAKE_INTERVAL(secs => expires_in) < NOW()') } + scope :not_revoked, -> { where(revoked_at: nil) } + scope :revoked, -> { where.not(revoked_at: nil).where(revoked_at: ...Time.now.utc) } end def revoke(clock = Time) diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 89b8a5b87c..f378d1ea47 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -116,13 +116,13 @@ class ActivityPub::Activity::Create < ActivityPub::Activity def find_existing_status status = status_from_uri(object_uri) status ||= Status.find_by(uri: @object['atomUri']) if @object['atomUri'].present? - status + status if status&.account_id == @account.id end def process_status_params @status_parser = ActivityPub::Parser::StatusParser.new(@json, followers_collection: @account.followers_url, object: @object) - attachment_ids = process_attachments.take(4).map(&:id) + attachment_ids = process_attachments.take(Status::MEDIA_ATTACHMENTS_LIMIT).map(&:id) @params = { uri: @status_parser.uri, @@ -272,7 +272,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity as_array(@object['attachment']).each do |attachment| media_attachment_parser = ActivityPub::Parser::MediaAttachmentParser.new(attachment) - next if media_attachment_parser.remote_url.blank? || media_attachments.size >= 4 + next if media_attachment_parser.remote_url.blank? || media_attachments.size >= Status::MEDIA_ATTACHMENTS_LIMIT begin media_attachment = MediaAttachment.create( diff --git a/app/lib/activitypub/activity/emoji_react.rb b/app/lib/activitypub/activity/emoji_react.rb index c9d88bc51c..ad9f9abbaa 100644 --- a/app/lib/activitypub/activity/emoji_react.rb +++ b/app/lib/activitypub/activity/emoji_react.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class ActivityPub::Activity::EmojiReact < ActivityPub::Activity + CUSTOM_EMOJI_REGEX = /^:[^:]+:$/ + def perform original_status = status_from_uri(object_uri) name = @json['content'] @@ -8,7 +10,7 @@ class ActivityPub::Activity::EmojiReact < ActivityPub::Activity !original_status.account.local? || delete_arrived_first?(@json['id']) - if /^:.*:$/.match?(name) + if CUSTOM_EMOJI_REGEX.match?(name) name.delete! ':' custom_emoji = process_emoji_tags(name, @json['tag']) diff --git a/app/lib/activitypub/activity/flag.rb b/app/lib/activitypub/activity/flag.rb index 68ee43d0eb..b7a412485c 100644 --- a/app/lib/activitypub/activity/flag.rb +++ b/app/lib/activitypub/activity/flag.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class ActivityPub::Activity::Flag < ActivityPub::Activity + COMMENT_SIZE_LIMIT = 5000 + def perform return if skip_reports? @@ -38,6 +40,6 @@ class ActivityPub::Activity::Flag < ActivityPub::Activity end def report_comment - (@json['content'] || '')[0...5000] + (@json['content'] || '')[0...COMMENT_SIZE_LIMIT] end end diff --git a/app/lib/activitypub/activity/like.rb b/app/lib/activitypub/activity/like.rb index 0063820825..8856f7be4c 100644 --- a/app/lib/activitypub/activity/like.rb +++ b/app/lib/activitypub/activity/like.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class ActivityPub::Activity::Like < ActivityPub::Activity + CUSTOM_EMOJI_REGEX = /^:[^:]+:$/ + def perform original_status = status_from_uri(object_uri) return if original_status.nil? || !original_status.account.local? || delete_arrived_first?(@json['id']) @@ -23,7 +25,7 @@ class ActivityPub::Activity::Like < ActivityPub::Activity name = @json['content'] || @json['_misskey_reaction'] return false if name.nil? - if /^:.*:$/.match?(name) + if CUSTOM_EMOJI_REGEX.match?(name) name.delete! ':' custom_emoji = process_emoji_tags(name, @json['tag']) diff --git a/app/lib/activitypub/activity/undo.rb b/app/lib/activitypub/activity/undo.rb index 5f9f1bbecb..0c7714d6ed 100644 --- a/app/lib/activitypub/activity/undo.rb +++ b/app/lib/activitypub/activity/undo.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class ActivityPub::Activity::Undo < ActivityPub::Activity + CUSTOM_EMOJI_REGEX = /^:[^:]+:$/ + def perform case @object['type'] when 'Announce' @@ -125,7 +127,7 @@ class ActivityPub::Activity::Undo < ActivityPub::Activity return if status.nil? || !status.account.local? - if /^:.*:$/.match?(name) + if CUSTOM_EMOJI_REGEX.match?(name) name.delete! ':' custom_emoji = process_emoji_tags(name, @object['tag']) diff --git a/app/lib/activitypub/parser/status_parser.rb b/app/lib/activitypub/parser/status_parser.rb index ea9b8a473c..025c46449d 100644 --- a/app/lib/activitypub/parser/status_parser.rb +++ b/app/lib/activitypub/parser/status_parser.rb @@ -3,6 +3,8 @@ class ActivityPub::Parser::StatusParser include JsonLdHelper + NORMALIZED_LOCALE_NAMES = LanguagesHelper::SUPPORTED_LOCALES.keys.index_by(&:downcase).freeze + # @param [Hash] json # @param [Hash] options # @option options [String] :followers_collection @@ -89,6 +91,13 @@ class ActivityPub::Parser::StatusParser end def language + lang = raw_language_code + lang.presence && NORMALIZED_LOCALE_NAMES.fetch(lang.downcase.to_sym, lang) + end + + private + + def raw_language_code if content_language_map? @object['contentMap'].keys.first elsif name_language_map? @@ -102,8 +111,6 @@ class ActivityPub::Parser::StatusParser @object['directMessage'] end - private - def audience_to as_array(@object['to'] || @json['to']).map { |x| value_or_id(x) } end diff --git a/app/lib/activitypub/serializer.rb b/app/lib/activitypub/serializer.rb index 1fdc793104..b17ec3fdfb 100644 --- a/app/lib/activitypub/serializer.rb +++ b/app/lib/activitypub/serializer.rb @@ -33,6 +33,6 @@ class ActivityPub::Serializer < ActiveModel::Serializer adapter_options[:named_contexts].merge!(_named_contexts) adapter_options[:context_extensions].merge!(_context_extensions) end - super(adapter_options, options, adapter_instance) + super end end diff --git a/app/lib/admin/metrics/dimension/software_versions_dimension.rb b/app/lib/admin/metrics/dimension/software_versions_dimension.rb index ccf556eae0..a260a66e2a 100644 --- a/app/lib/admin/metrics/dimension/software_versions_dimension.rb +++ b/app/lib/admin/metrics/dimension/software_versions_dimension.rb @@ -10,7 +10,7 @@ class Admin::Metrics::Dimension::SoftwareVersionsDimension < Admin::Metrics::Dim protected def perform_query - [mastodon_version, ruby_version, postgresql_version, redis_version, elasticsearch_version].compact + [mastodon_version, ruby_version, postgresql_version, redis_version, elasticsearch_version, libvips_version, imagemagick_version, ffmpeg_version].compact end def mastodon_version @@ -25,14 +25,11 @@ class Admin::Metrics::Dimension::SoftwareVersionsDimension < Admin::Metrics::Dim end def ruby_version - yjit = defined?(RubyVM::YJIT) && RubyVM::YJIT.enabled? - value = "#{RUBY_VERSION}p#{RUBY_PATCHLEVEL}#{yjit ? ' +YJIT' : ''}" - { key: 'ruby', human_key: 'Ruby', - value: value, - human_value: value, + value: RUBY_DESCRIPTION, + human_value: "#{RUBY_VERSION}p#{RUBY_PATCHLEVEL}", } end @@ -74,6 +71,45 @@ class Admin::Metrics::Dimension::SoftwareVersionsDimension < Admin::Metrics::Dim nil end + def libvips_version + return unless Rails.configuration.x.use_vips + + { + key: 'libvips', + human_key: 'libvips', + value: Vips.version_string, + human_value: Vips.version_string, + } + end + + def imagemagick_version + return if Rails.configuration.x.use_vips + + version = `convert -version`.match(/Version: ImageMagick ([\d\.]+)/)[1] + + { + key: 'imagemagick', + human_key: 'ImageMagick', + value: version, + human_value: version, + } + rescue Errno::ENOENT + nil + end + + def ffmpeg_version + version = `ffmpeg -version`.match(/ffmpeg version ([\d\.]+)/)[1] + + { + key: 'ffmpeg', + human_key: 'FFmpeg', + value: version, + human_value: version, + } + rescue Errno::ENOENT + nil + end + def redis_info @redis_info ||= if redis.is_a?(Redis::Namespace) redis.redis.info diff --git a/app/lib/admin/metrics/measure/active_users_measure.rb b/app/lib/admin/metrics/measure/active_users_measure.rb index e6f09d4bcf..c085ced629 100644 --- a/app/lib/admin/metrics/measure/active_users_measure.rb +++ b/app/lib/admin/metrics/measure/active_users_measure.rb @@ -22,12 +22,4 @@ class Admin::Metrics::Measure::ActiveUsersMeasure < Admin::Metrics::Measure::Bas def activity_tracker @activity_tracker ||= ActivityTracker.new('activity:logins', :unique) end - - def time_period - (@start_at.to_date..@end_at.to_date) - end - - def previous_time_period - ((@start_at.to_date - length_of_period)..(@end_at.to_date - length_of_period)) - end end diff --git a/app/lib/admin/metrics/measure/base_measure.rb b/app/lib/admin/metrics/measure/base_measure.rb index e33a6c494f..8b7fe39b55 100644 --- a/app/lib/admin/metrics/measure/base_measure.rb +++ b/app/lib/admin/metrics/measure/base_measure.rb @@ -86,11 +86,11 @@ class Admin::Metrics::Measure::BaseMeasure end def time_period - (@start_at..@end_at) + (@start_at.to_date..@end_at.to_date) end def previous_time_period - ((@start_at - length_of_period)..(@end_at - length_of_period)) + ((@start_at.to_date - length_of_period)..(@end_at.to_date - length_of_period)) end def length_of_period diff --git a/app/lib/admin/metrics/measure/instance_accounts_measure.rb b/app/lib/admin/metrics/measure/instance_accounts_measure.rb index 3d081fdd90..889a5e6f05 100644 --- a/app/lib/admin/metrics/measure/instance_accounts_measure.rb +++ b/app/lib/admin/metrics/measure/instance_accounts_measure.rb @@ -43,19 +43,11 @@ class Admin::Metrics::Measure::InstanceAccountsMeasure < Admin::Metrics::Measure SELECT count(*) FROM new_accounts ) AS value FROM ( - SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period + #{generated_series_days} ) AS axis SQL end - def time_period - (@start_at.to_date..@end_at.to_date) - end - - def previous_time_period - ((@start_at.to_date - length_of_period)..(@end_at.to_date - length_of_period)) - end - def params @params.permit(:domain, :include_subdomains) end diff --git a/app/lib/admin/metrics/measure/instance_followers_measure.rb b/app/lib/admin/metrics/measure/instance_followers_measure.rb index 378c6754d9..fa934c6b96 100644 --- a/app/lib/admin/metrics/measure/instance_followers_measure.rb +++ b/app/lib/admin/metrics/measure/instance_followers_measure.rb @@ -44,19 +44,11 @@ class Admin::Metrics::Measure::InstanceFollowersMeasure < Admin::Metrics::Measur SELECT count(*) FROM new_followers ) AS value FROM ( - SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period + #{generated_series_days} ) AS axis SQL end - def time_period - (@start_at.to_date..@end_at.to_date) - end - - def previous_time_period - ((@start_at.to_date - length_of_period)..(@end_at.to_date - length_of_period)) - end - def params @params.permit(:domain, :include_subdomains) end diff --git a/app/lib/admin/metrics/measure/instance_follows_measure.rb b/app/lib/admin/metrics/measure/instance_follows_measure.rb index e213348fbc..3f3ab73fc9 100644 --- a/app/lib/admin/metrics/measure/instance_follows_measure.rb +++ b/app/lib/admin/metrics/measure/instance_follows_measure.rb @@ -44,19 +44,11 @@ class Admin::Metrics::Measure::InstanceFollowsMeasure < Admin::Metrics::Measure: SELECT count(*) FROM new_follows ) AS value FROM ( - SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period + #{generated_series_days} ) AS axis SQL end - def time_period - (@start_at.to_date..@end_at.to_date) - end - - def previous_time_period - ((@start_at.to_date - length_of_period)..(@end_at.to_date - length_of_period)) - end - def params @params.permit(:domain, :include_subdomains) end diff --git a/app/lib/admin/metrics/measure/instance_media_attachments_measure.rb b/app/lib/admin/metrics/measure/instance_media_attachments_measure.rb index 1d2dbbe414..996ca52e0b 100644 --- a/app/lib/admin/metrics/measure/instance_media_attachments_measure.rb +++ b/app/lib/admin/metrics/measure/instance_media_attachments_measure.rb @@ -53,19 +53,11 @@ class Admin::Metrics::Measure::InstanceMediaAttachmentsMeasure < Admin::Metrics: SELECT COALESCE(SUM(size), 0) FROM new_media_attachments ) AS value FROM ( - SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period + #{generated_series_days} ) AS axis SQL end - def time_period - (@start_at.to_date..@end_at.to_date) - end - - def previous_time_period - ((@start_at.to_date - length_of_period)..(@end_at.to_date - length_of_period)) - end - def params @params.permit(:domain, :include_subdomains) end diff --git a/app/lib/admin/metrics/measure/instance_reports_measure.rb b/app/lib/admin/metrics/measure/instance_reports_measure.rb index 9da3d53e34..ae1bb6e68d 100644 --- a/app/lib/admin/metrics/measure/instance_reports_measure.rb +++ b/app/lib/admin/metrics/measure/instance_reports_measure.rb @@ -44,19 +44,11 @@ class Admin::Metrics::Measure::InstanceReportsMeasure < Admin::Metrics::Measure: SELECT count(*) FROM new_reports ) AS value FROM ( - SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period + #{generated_series_days} ) AS axis SQL end - def time_period - (@start_at.to_date..@end_at.to_date) - end - - def previous_time_period - ((@start_at.to_date - length_of_period)..(@end_at.to_date - length_of_period)) - end - def params @params.permit(:domain, :include_subdomains) end diff --git a/app/lib/admin/metrics/measure/instance_statuses_measure.rb b/app/lib/admin/metrics/measure/instance_statuses_measure.rb index b918a30a57..324d427b18 100644 --- a/app/lib/admin/metrics/measure/instance_statuses_measure.rb +++ b/app/lib/admin/metrics/measure/instance_statuses_measure.rb @@ -45,7 +45,7 @@ class Admin::Metrics::Measure::InstanceStatusesMeasure < Admin::Metrics::Measure SELECT count(*) FROM new_statuses ) AS value FROM ( - SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period + #{generated_series_days} ) AS axis SQL end @@ -58,14 +58,6 @@ class Admin::Metrics::Measure::InstanceStatusesMeasure < Admin::Metrics::Measure Mastodon::Snowflake.id_at(@end_at.end_of_day, with_random: false) end - def time_period - (@start_at.to_date..@end_at.to_date) - end - - def previous_time_period - ((@start_at.to_date - length_of_period)..(@end_at.to_date - length_of_period)) - end - def params @params.permit(:domain, :include_subdomains) end diff --git a/app/lib/admin/metrics/measure/interactions_measure.rb b/app/lib/admin/metrics/measure/interactions_measure.rb index 7a2b7e0fac..f4b4836b4a 100644 --- a/app/lib/admin/metrics/measure/interactions_measure.rb +++ b/app/lib/admin/metrics/measure/interactions_measure.rb @@ -22,12 +22,4 @@ class Admin::Metrics::Measure::InteractionsMeasure < Admin::Metrics::Measure::Ba def activity_tracker @activity_tracker ||= ActivityTracker.new('activity:interactions', :basic) end - - def time_period - (@start_at.to_date..@end_at.to_date) - end - - def previous_time_period - ((@start_at.to_date - length_of_period)..(@end_at.to_date - length_of_period)) - end end diff --git a/app/lib/admin/metrics/measure/new_users_measure.rb b/app/lib/admin/metrics/measure/new_users_measure.rb index 6837c14c82..32057154d6 100644 --- a/app/lib/admin/metrics/measure/new_users_measure.rb +++ b/app/lib/admin/metrics/measure/new_users_measure.rb @@ -32,7 +32,7 @@ class Admin::Metrics::Measure::NewUsersMeasure < Admin::Metrics::Measure::BaseMe SELECT count(*) FROM new_users ) AS value FROM ( - SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period + #{generated_series_days} ) AS axis SQL end diff --git a/app/lib/admin/metrics/measure/opened_reports_measure.rb b/app/lib/admin/metrics/measure/opened_reports_measure.rb index c395c46341..47de38bbe6 100644 --- a/app/lib/admin/metrics/measure/opened_reports_measure.rb +++ b/app/lib/admin/metrics/measure/opened_reports_measure.rb @@ -32,7 +32,7 @@ class Admin::Metrics::Measure::OpenedReportsMeasure < Admin::Metrics::Measure::B SELECT count(*) FROM new_reports ) AS value FROM ( - SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period + #{generated_series_days} ) AS axis SQL end diff --git a/app/lib/admin/metrics/measure/query_helper.rb b/app/lib/admin/metrics/measure/query_helper.rb index 969065f73f..47cfc63e5c 100644 --- a/app/lib/admin/metrics/measure/query_helper.rb +++ b/app/lib/admin/metrics/measure/query_helper.rb @@ -15,6 +15,14 @@ module Admin::Metrics::Measure::QueryHelper ActiveRecord::Base.sanitize_sql_array(sql_array) end + def generated_series_days + Arel.sql( + <<~SQL.squish + SELECT generate_series(:start_at::timestamp, :end_at::timestamp, '1 day')::date AS period + SQL + ) + end + def account_domain_sql(include_subdomains) if include_subdomains "accounts.domain IN (SELECT domain FROM instances WHERE reverse('.' || domain) LIKE reverse('.' || :domain::text))" diff --git a/app/lib/admin/metrics/measure/resolved_reports_measure.rb b/app/lib/admin/metrics/measure/resolved_reports_measure.rb index 780db75a10..ecfd779c86 100644 --- a/app/lib/admin/metrics/measure/resolved_reports_measure.rb +++ b/app/lib/admin/metrics/measure/resolved_reports_measure.rb @@ -32,7 +32,7 @@ class Admin::Metrics::Measure::ResolvedReportsMeasure < Admin::Metrics::Measure: SELECT count(*) FROM resolved_reports ) AS value FROM ( - SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period + #{generated_series_days} ) AS axis SQL end diff --git a/app/lib/admin/metrics/measure/tag_accounts_measure.rb b/app/lib/admin/metrics/measure/tag_accounts_measure.rb index 8f4512efe7..906277b7d5 100644 --- a/app/lib/admin/metrics/measure/tag_accounts_measure.rb +++ b/app/lib/admin/metrics/measure/tag_accounts_measure.rb @@ -27,14 +27,6 @@ class Admin::Metrics::Measure::TagAccountsMeasure < Admin::Metrics::Measure::Bas @tag ||= Tag.find(params[:id]) end - def time_period - (@start_at.to_date..@end_at.to_date) - end - - def previous_time_period - ((@start_at.to_date - length_of_period)..(@end_at.to_date - length_of_period)) - end - def params @params.permit(:id) end diff --git a/app/lib/admin/metrics/measure/tag_servers_measure.rb b/app/lib/admin/metrics/measure/tag_servers_measure.rb index f273d739d0..5db1076062 100644 --- a/app/lib/admin/metrics/measure/tag_servers_measure.rb +++ b/app/lib/admin/metrics/measure/tag_servers_measure.rb @@ -40,7 +40,7 @@ class Admin::Metrics::Measure::TagServersMeasure < Admin::Metrics::Measure::Base SELECT COUNT(*) FROM tag_servers ) AS value FROM ( - SELECT generate_series(date_trunc('day', :start_at::timestamp)::date, date_trunc('day', :end_at::timestamp)::date, interval '1 day') AS period + #{generated_series_days} ) as axis SQL end diff --git a/app/lib/admin/metrics/measure/tag_uses_measure.rb b/app/lib/admin/metrics/measure/tag_uses_measure.rb index bce86b89f1..2be96a02b3 100644 --- a/app/lib/admin/metrics/measure/tag_uses_measure.rb +++ b/app/lib/admin/metrics/measure/tag_uses_measure.rb @@ -27,14 +27,6 @@ class Admin::Metrics::Measure::TagUsesMeasure < Admin::Metrics::Measure::BaseMea @tag ||= Tag.find(params[:id]) end - def time_period - (@start_at.to_date..@end_at.to_date) - end - - def previous_time_period - ((@start_at.to_date - length_of_period)..(@end_at.to_date - length_of_period)) - end - def params @params.permit(:id) end diff --git a/app/lib/advanced_text_formatter.rb b/app/lib/advanced_text_formatter.rb index cdf1e2d9cd..3ba4c92be3 100644 --- a/app/lib/advanced_text_formatter.rb +++ b/app/lib/advanced_text_formatter.rb @@ -31,7 +31,7 @@ class AdvancedTextFormatter < TextFormatter # @option options [String] :content_type def initialize(text, options = {}) @content_type = options.delete(:content_type) - super(text, options) + super @text = format_markdown(text) if content_type == 'text/markdown' end diff --git a/app/lib/application_extension.rb b/app/lib/application_extension.rb index 400c51a023..d7aaeba5bd 100644 --- a/app/lib/application_extension.rb +++ b/app/lib/application_extension.rb @@ -16,17 +16,25 @@ module ApplicationExtension # dependent: delete_all, which means the ActiveRecord callback in # AccessTokenExtension is not run, so instead we manually announce to # streaming that these tokens are being deleted. - before_destroy :push_to_streaming_api, prepend: true + before_destroy :close_streaming_sessions, prepend: true end def confirmation_redirect_uri redirect_uri.lines.first.strip end - def push_to_streaming_api + def redirect_uris + # Doorkeeper stores the redirect_uri value as a newline delimeted list in + # the database: + redirect_uri.split + end + + def close_streaming_sessions(resource_owner = nil) # TODO: #28793 Combine into a single topic payload = Oj.dump(event: :kill) - access_tokens.in_batches do |tokens| + scope = access_tokens + scope = scope.where(resource_owner_id: resource_owner.id) unless resource_owner.nil? + scope.in_batches do |tokens| redis.pipelined do |pipeline| tokens.ids.each do |id| pipeline.publish("timeline:access_token:#{id}", payload) diff --git a/app/lib/cache_buster.rb b/app/lib/cache_buster.rb index 554f2ba95d..d3395f8f0a 100644 --- a/app/lib/cache_buster.rb +++ b/app/lib/cache_buster.rb @@ -2,13 +2,8 @@ class CacheBuster def initialize(options = {}) - Rails.application.deprecators[:mastodon].warn('Default values for the cache buster secret header name and values will be removed in Mastodon 4.3. Please set them explicitely if you rely on those.') unless options[:http_method] || (options[:secret] && options[:secret_header]) - - @secret_header = options[:secret_header] || - (options[:http_method] ? nil : 'Secret-Header') - @secret = options[:secret] || - (options[:http_method] ? nil : 'True') - + @secret_header = options[:secret_header] + @secret = options[:secret] @http_method = options[:http_method] || 'GET' end diff --git a/app/lib/connection_pool/shared_connection_pool.rb b/app/lib/connection_pool/shared_connection_pool.rb index 3ca22d0eff..1cfcc5823b 100644 --- a/app/lib/connection_pool/shared_connection_pool.rb +++ b/app/lib/connection_pool/shared_connection_pool.rb @@ -5,7 +5,7 @@ require_relative 'shared_timed_stack' class ConnectionPool::SharedConnectionPool < ConnectionPool def initialize(options = {}, &block) - super(options, &block) + super @available = ConnectionPool::SharedTimedStack.new(@size, &block) end diff --git a/app/lib/extractor.rb b/app/lib/extractor.rb index 9090773ae9..7e647a7587 100644 --- a/app/lib/extractor.rb +++ b/app/lib/extractor.rb @@ -86,10 +86,6 @@ module Extractor possible_entries end - def extract_cashtags_with_indices(_text) - [] - end - def extract_extra_uris_with_indices(text) return [] unless text&.index(':') diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index e4801b53a9..5a1710c52d 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -470,10 +470,7 @@ class FeedManager check_for_blocks = status.active_mentions.pluck(:account_id) check_for_blocks.push(status.in_reply_to_account) if status.reply? && !status.in_reply_to_account_id.nil? - should_filter = blocks_or_mutes?(receiver_id, check_for_blocks, :mentions) # Filter if it's from someone I blocked, in reply to someone I blocked, or mentioning someone I blocked (or muted) - should_filter ||= status.account.silenced? && !Follow.exists?(account_id: receiver_id, target_account_id: status.account_id) # Filter if the account is silenced and I'm not following them - - should_filter + blocks_or_mutes?(receiver_id, check_for_blocks, :mentions) # Filter if it's from someone I blocked, in reply to someone I blocked, or mentioning someone I blocked (or muted) end # Check if status should not be added to the linear direct message feed diff --git a/app/lib/link_details_extractor.rb b/app/lib/link_details_extractor.rb index bb031986d6..d81f4a3062 100644 --- a/app/lib/link_details_extractor.rb +++ b/app/lib/link_details_extractor.rb @@ -62,7 +62,8 @@ class LinkDetailsExtractor end def author_name - author['name'] + name = author['name'] + name.is_a?(Array) ? name.join(', ') : name end def author_url @@ -156,11 +157,11 @@ class LinkDetailsExtractor end def title - html_entities.decode(structured_data&.headline || opengraph_tag('og:title') || document.xpath('//title').map(&:content).first) + html_entities_decode(structured_data&.headline || opengraph_tag('og:title') || document.xpath('//title').map(&:content).first)&.strip end def description - html_entities.decode(structured_data&.description || opengraph_tag('og:description') || meta_tag('description')) + html_entities_decode(structured_data&.description || opengraph_tag('og:description') || meta_tag('description')) end def published_at @@ -180,7 +181,7 @@ class LinkDetailsExtractor end def provider_name - html_entities.decode(structured_data&.publisher_name || opengraph_tag('og:site_name')) + html_entities_decode(structured_data&.publisher_name || opengraph_tag('og:site_name')) end def provider_url @@ -188,13 +189,17 @@ class LinkDetailsExtractor end def author_name - html_entities.decode(structured_data&.author_name || opengraph_tag('og:author') || opengraph_tag('og:author:username')) + html_entities_decode(structured_data&.author_name || opengraph_tag('og:author') || opengraph_tag('og:author:username')) end def author_url structured_data&.author_url end + def author_account + opengraph_tag('fediverse:creator') + end + def embed_url valid_url_or_nil(opengraph_tag('twitter:player:stream')) end @@ -253,7 +258,7 @@ class LinkDetailsExtractor next if json_ld.blank? - structured_data = StructuredData.new(html_entities.decode(json_ld)) + structured_data = StructuredData.new(html_entities_decode(json_ld)) next unless structured_data.valid? @@ -265,14 +270,27 @@ class LinkDetailsExtractor end def document - @document ||= Nokogiri::HTML(@html, nil, encoding) + @document ||= detect_encoding_and_parse_document end - def encoding - @encoding ||= begin - guess = detector.detect(@html, @html_charset) - guess&.fetch(:confidence, 0).to_i > 60 ? guess&.fetch(:encoding, nil) : nil + def detect_encoding_and_parse_document + [detect_encoding, nil, header_encoding].uniq.each do |encoding| + document = Nokogiri::HTML(@html, nil, encoding) + return document if document.to_s.valid_encoding? end + Nokogiri::HTML(@html, nil, 'UTF-8') + end + + def detect_encoding + guess = detector.detect(@html, @html_charset) + guess&.fetch(:confidence, 0).to_i > 60 ? guess&.fetch(:encoding, nil) : nil + end + + def header_encoding + Encoding.find(@html_charset).name if @html_charset + rescue ArgumentError + # Encoding from HTTP header is not recognized by ruby + nil end def detector @@ -281,7 +299,16 @@ class LinkDetailsExtractor end end + def html_entities_decode(string) + return if string.nil? + + unicode_string = string.to_s.encode('UTF-8') + raise EncodingError, 'cannot convert string to valid UTF-8' unless unicode_string.valid_encoding? + + html_entities.decode(unicode_string) + end + def html_entities - @html_entities ||= HTMLEntities.new + @html_entities ||= HTMLEntities.new(:expanded) end end diff --git a/app/lib/private_address_check.rb b/app/lib/private_address_check.rb new file mode 100644 index 0000000000..d00b16e66b --- /dev/null +++ b/app/lib/private_address_check.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module PrivateAddressCheck + module_function + + CIDR_LIST = [ + IPAddr.new('0.0.0.0/8'), # Current network (only valid as source address) + IPAddr.new('100.64.0.0/10'), # Shared Address Space + IPAddr.new('172.16.0.0/12'), # Private network + IPAddr.new('192.0.0.0/24'), # IETF Protocol Assignments + IPAddr.new('192.0.2.0/24'), # TEST-NET-1, documentation and examples + IPAddr.new('192.88.99.0/24'), # IPv6 to IPv4 relay (includes 2002::/16) + IPAddr.new('198.18.0.0/15'), # Network benchmark tests + IPAddr.new('198.51.100.0/24'), # TEST-NET-2, documentation and examples + IPAddr.new('203.0.113.0/24'), # TEST-NET-3, documentation and examples + IPAddr.new('224.0.0.0/4'), # IP multicast (former Class D network) + IPAddr.new('240.0.0.0/4'), # Reserved (former Class E network) + IPAddr.new('255.255.255.255'), # Broadcast + IPAddr.new('64:ff9b::/96'), # IPv4/IPv6 translation (RFC 6052) + IPAddr.new('100::/64'), # Discard prefix (RFC 6666) + IPAddr.new('2001::/32'), # Teredo tunneling + IPAddr.new('2001:10::/28'), # Deprecated (previously ORCHID) + IPAddr.new('2001:20::/28'), # ORCHIDv2 + IPAddr.new('2001:db8::/32'), # Addresses used in documentation and example source code + IPAddr.new('2002::/16'), # 6to4 + IPAddr.new('fc00::/7'), # Unique local address + IPAddr.new('ff00::/8'), # Multicast + ].freeze + + def private_address?(address) + address.private? || address.loopback? || address.link_local? || CIDR_LIST.any? { |cidr| cidr.include?(address) } + end +end diff --git a/app/lib/redis_configuration.rb b/app/lib/redis_configuration.rb index f0e86d985b..fb1249640f 100644 --- a/app/lib/redis_configuration.rb +++ b/app/lib/redis_configuration.rb @@ -42,9 +42,13 @@ class RedisConfiguration ENV['REDIS_URL'] end + def redis_driver + ENV.fetch('REDIS_DRIVER', 'hiredis') == 'ruby' ? :ruby : :hiredis + end + private def raw_connection - Redis.new(url: url, driver: :hiredis) + Redis.new(url: url, driver: redis_driver) end end diff --git a/app/lib/rss/channel.rb b/app/lib/rss/channel.rb index 9013ed066a..518ea71405 100644 --- a/app/lib/rss/channel.rb +++ b/app/lib/rss/channel.rb @@ -2,7 +2,7 @@ class RSS::Channel < RSS::Element def initialize - super() + super @root = create_element('channel') end diff --git a/app/lib/rss/item.rb b/app/lib/rss/item.rb index 6739a2c184..8be8d4bf35 100644 --- a/app/lib/rss/item.rb +++ b/app/lib/rss/item.rb @@ -2,7 +2,7 @@ class RSS::Item < RSS::Element def initialize - super() + super @root = create_element('item') end diff --git a/app/lib/scope_transformer.rb b/app/lib/scope_transformer.rb index adcb711f8a..7dda709229 100644 --- a/app/lib/scope_transformer.rb +++ b/app/lib/scope_transformer.rb @@ -11,6 +11,9 @@ class ScopeTransformer < Parslet::Transform @namespace = scope[:namespace]&.to_s @access = scope[:access] ? [scope[:access].to_s] : DEFAULT_ACCESS.dup @term = scope[:term]&.to_s || DEFAULT_TERM + + # # override for profile scope which is read only + @access = %w(read) if @term == 'profile' end def key diff --git a/app/lib/search_query_transformer.rb b/app/lib/search_query_transformer.rb index 927495eace..606819ed40 100644 --- a/app/lib/search_query_transformer.rb +++ b/app/lib/search_query_transformer.rb @@ -225,7 +225,7 @@ class SearchQueryTransformer < Parslet::Transform end rule(clause: subtree(:clause)) do - prefix = clause[:prefix][:term].to_s if clause[:prefix] + prefix = clause[:prefix][:term].to_s.downcase if clause[:prefix] operator = clause[:operator]&.to_s term = clause[:phrase] ? clause[:phrase].map { |term| term[:term].to_s }.join(' ') : clause[:term].to_s diff --git a/app/lib/themes.rb b/app/lib/themes.rb index a87323d8e9..64c2f1bfe3 100644 --- a/app/lib/themes.rb +++ b/app/lib/themes.rb @@ -8,7 +8,7 @@ class Themes THEME_COLORS = { dark: '#191b22', - light: '#f3f5f7', + light: '#ffffff', }.freeze def initialize diff --git a/app/lib/vacuum/access_tokens_vacuum.rb b/app/lib/vacuum/access_tokens_vacuum.rb index a224f6d638..281ae22bf0 100644 --- a/app/lib/vacuum/access_tokens_vacuum.rb +++ b/app/lib/vacuum/access_tokens_vacuum.rb @@ -9,12 +9,12 @@ class Vacuum::AccessTokensVacuum private def vacuum_revoked_access_tokens! - Doorkeeper::AccessToken.where.not(expires_in: nil).where('created_at + make_interval(secs => expires_in) < NOW()').in_batches.delete_all - Doorkeeper::AccessToken.where.not(revoked_at: nil).where('revoked_at < NOW()').in_batches.delete_all + Doorkeeper::AccessToken.expired.in_batches.delete_all + Doorkeeper::AccessToken.revoked.in_batches.delete_all end def vacuum_revoked_access_grants! - Doorkeeper::AccessGrant.where.not(expires_in: nil).where('created_at + make_interval(secs => expires_in) < NOW()').in_batches.delete_all - Doorkeeper::AccessGrant.where.not(revoked_at: nil).where('revoked_at < NOW()').in_batches.delete_all + Doorkeeper::AccessGrant.expired.in_batches.delete_all + Doorkeeper::AccessGrant.revoked.in_batches.delete_all end end diff --git a/app/lib/vacuum/applications_vacuum.rb b/app/lib/vacuum/applications_vacuum.rb deleted file mode 100644 index ba88655f16..0000000000 --- a/app/lib/vacuum/applications_vacuum.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -class Vacuum::ApplicationsVacuum - def perform - Doorkeeper::Application.where(owner_id: nil) - .where.missing(:created_users, :access_tokens, :access_grants) - .where(created_at: ...1.day.ago) - .in_batches.delete_all - end -end diff --git a/app/lib/vacuum/imports_vacuum.rb b/app/lib/vacuum/imports_vacuum.rb index 8c8bb783ac..700bd81847 100644 --- a/app/lib/vacuum/imports_vacuum.rb +++ b/app/lib/vacuum/imports_vacuum.rb @@ -9,10 +9,10 @@ class Vacuum::ImportsVacuum private def clean_unconfirmed_imports! - BulkImport.state_unconfirmed.where('created_at <= ?', 10.minutes.ago).reorder(nil).in_batches.delete_all + BulkImport.state_unconfirmed.where(created_at: ..10.minutes.ago).reorder(nil).in_batches.delete_all end def clean_old_imports! - BulkImport.where('created_at <= ?', 1.week.ago).reorder(nil).in_batches.delete_all + BulkImport.where(created_at: ..1.week.ago).reorder(nil).in_batches.delete_all end end diff --git a/app/lib/vacuum/statuses_vacuum.rb b/app/lib/vacuum/statuses_vacuum.rb index ad1de07380..92d3ccf4f4 100644 --- a/app/lib/vacuum/statuses_vacuum.rb +++ b/app/lib/vacuum/statuses_vacuum.rb @@ -34,7 +34,7 @@ class Vacuum::StatusesVacuum def statuses_scope Status.unscoped.kept .joins(:account).merge(Account.remote) - .where('statuses.id < ?', retention_period_as_id) + .where(statuses: { id: ...retention_period_as_id }) end def retention_period_as_id diff --git a/app/lib/video_metadata_extractor.rb b/app/lib/video_metadata_extractor.rb index df5409375f..2155766251 100644 --- a/app/lib/video_metadata_extractor.rb +++ b/app/lib/video_metadata_extractor.rb @@ -41,8 +41,8 @@ class VideoMetadataExtractor @colorspace = video_stream[:pix_fmt] @width = video_stream[:width] @height = video_stream[:height] - @frame_rate = video_stream[:avg_frame_rate] == '0/0' ? nil : Rational(video_stream[:avg_frame_rate]) - @r_frame_rate = video_stream[:r_frame_rate] == '0/0' ? nil : Rational(video_stream[:r_frame_rate]) + @frame_rate = parse_framerate(video_stream[:avg_frame_rate]) + @r_frame_rate = parse_framerate(video_stream[:r_frame_rate]) # For some video streams the frame_rate reported by `ffprobe` will be 0/0, but for these streams we # should use `r_frame_rate` instead. Video screencast generated by Gnome Screencast have this issue. @frame_rate ||= @r_frame_rate @@ -55,4 +55,10 @@ class VideoMetadataExtractor @invalid = true if @metadata.key?(:error) end + + def parse_framerate(raw) + Rational(raw) + rescue ZeroDivisionError + nil + end end diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index f8c1c9a8d0..81a2c0c6d0 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -5,7 +5,6 @@ class UserMailer < Devise::Mailer helper :accounts helper :application - helper :mascot helper :formatting helper :instance helper :routing diff --git a/app/models/account.rb b/app/models/account.rb index 28dfbbe7c7..f2d042a38d 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -71,6 +71,9 @@ class Account < ApplicationRecord MENTION_RE = %r{(? { local? && will_save_change_to_username? && actor_type != 'Application' } + validates :username, format: { with: /\A[a-z0-9_]+\z/i }, length: { maximum: USERNAME_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_username? && actor_type != 'Application' } validates_with UnreservedUsernameValidator, if: -> { local? && will_save_change_to_username? && actor_type != 'Application' } - validates :display_name, length: { maximum: MAX_DISPLAY_NAME_LENGTH }, if: -> { local? && will_save_change_to_display_name? } - validates :note, note_length: { maximum: MAX_NOTE_LENGTH }, if: -> { local? && will_save_change_to_note? } + validates :display_name, length: { maximum: DISPLAY_NAME_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_display_name? } + validates :note, note_length: { maximum: NOTE_LENGTH_LIMIT }, if: -> { local? && will_save_change_to_note? } validates :fields, length: { maximum: DEFAULT_FIELDS_SIZE }, if: -> { local? && will_save_change_to_fields? } validates :uri, absence: true, if: :local?, on: :create validates :inbox_url, absence: true, if: :local?, on: :create @@ -138,11 +138,12 @@ class Account < ApplicationRecord scope :discoverable, -> { searchable.without_silenced.where(discoverable: true).joins(:account_stat) } scope :by_recent_status, -> { includes(:account_stat).merge(AccountStat.by_recent_status).references(:account_stat) } scope :by_recent_activity, -> { left_joins(:user, :account_stat).order(coalesced_activity_timestamps.desc).order(id: :desc) } - scope :popular, -> { order('account_stats.followers_count desc') } scope :by_domain_and_subdomains, ->(domain) { where(domain: Instance.by_domain_and_subdomains(domain).select(:domain)) } scope :not_excluded_by_account, ->(account) { where.not(id: account.excluded_from_timeline_account_ids) } scope :not_domain_blocked_by_account, ->(account) { where(arel_table[:domain].eq(nil).or(arel_table[:domain].not_in(account.excluded_from_timeline_domains))) } scope :dormant, -> { joins(:account_stat).merge(AccountStat.without_recent_activity) } + scope :with_username, ->(value) { where arel_table[:username].lower.eq(value.to_s.downcase) } + scope :with_domain, ->(value) { where arel_table[:domain].lower.eq(value&.to_s&.downcase) } after_update_commit :trigger_update_webhooks diff --git a/app/models/account_moderation_note.rb b/app/models/account_moderation_note.rb index ff399bab0c..79b8b4d25e 100644 --- a/app/models/account_moderation_note.rb +++ b/app/models/account_moderation_note.rb @@ -13,10 +13,12 @@ # class AccountModerationNote < ApplicationRecord + CONTENT_SIZE_LIMIT = 2_000 + belongs_to :account belongs_to :target_account, class_name: 'Account' scope :latest, -> { reorder('created_at DESC') } - validates :content, presence: true, length: { maximum: 500 } + validates :content, presence: true, length: { maximum: CONTENT_SIZE_LIMIT } end diff --git a/app/models/account_note.rb b/app/models/account_note.rb index 9bc704d988..317e6873fa 100644 --- a/app/models/account_note.rb +++ b/app/models/account_note.rb @@ -14,9 +14,11 @@ class AccountNote < ApplicationRecord include RelationshipCacheable + COMMENT_SIZE_LIMIT = 2_000 + belongs_to :account belongs_to :target_account, class_name: 'Account' validates :account_id, uniqueness: { scope: :target_account_id } - validates :comment, length: { maximum: 2_000 } + validates :comment, length: { maximum: COMMENT_SIZE_LIMIT } end diff --git a/app/models/account_suggestions/setting_source.rb b/app/models/account_suggestions/setting_source.rb index 9f3cd7bd3d..6143481723 100644 --- a/app/models/account_suggestions/setting_source.rb +++ b/app/models/account_suggestions/setting_source.rb @@ -3,7 +3,7 @@ class AccountSuggestions::SettingSource < AccountSuggestions::Source def get(account, limit: DEFAULT_LIMIT) if setting_enabled? - base_account_scope(account).where(setting_to_where_condition).limit(limit).pluck(:id).zip([key].cycle) + base_account_scope(account).merge(setting_to_where_condition).limit(limit).pluck(:id).zip([key].cycle) else [] end @@ -25,11 +25,9 @@ class AccountSuggestions::SettingSource < AccountSuggestions::Source def setting_to_where_condition usernames_and_domains.map do |(username, domain)| - Arel::Nodes::Grouping.new( - Account.arel_table[:username].lower.eq(username.downcase).and( - Account.arel_table[:domain].lower.eq(domain&.downcase) - ) - ) + Account + .with_username(username) + .with_domain(domain) end.reduce(:or) end diff --git a/app/models/admin/account_action.rb b/app/models/admin/account_action.rb index 2b5560e2eb..3700ce4cd6 100644 --- a/app/models/admin/account_action.rb +++ b/app/models/admin/account_action.rb @@ -52,7 +52,7 @@ class Admin::AccountAction process_reports! end - process_email! + process_notification! process_queue! end @@ -158,8 +158,11 @@ class Admin::AccountAction queue_suspension_worker! if type == 'suspend' end - def process_email! - UserMailer.warning(target_account.user, warning).deliver_later! if warnable? + def process_notification! + return unless warnable? + + UserMailer.warning(target_account.user, warning).deliver_later! + LocalNotificationWorker.perform_async(target_account.id, warning.id, 'AccountWarning', 'moderation_warning') end def warnable? diff --git a/app/models/admin/action_log_filter.rb b/app/models/admin/action_log_filter.rb index f581af74e8..fc984b2445 100644 --- a/app/models/admin/action_log_filter.rb +++ b/app/models/admin/action_log_filter.rb @@ -59,6 +59,7 @@ class Admin::ActionLogFilter unsuspend_account: { target_type: 'Account', action: 'unsuspend' }.freeze, update_announcement: { target_type: 'Announcement', action: 'update' }.freeze, update_custom_emoji: { target_type: 'CustomEmoji', action: 'update' }.freeze, + update_report: { target_type: 'Report', action: 'update' }.freeze, update_status: { target_type: 'Status', action: 'update' }.freeze, update_user_role: { target_type: 'UserRole', action: 'update' }.freeze, update_ip_block: { target_type: 'IpBlock', action: 'update' }.freeze, diff --git a/app/models/admin/status_batch_action.rb b/app/models/admin/status_batch_action.rb index 8a8e2fa378..4a10001935 100644 --- a/app/models/admin/status_batch_action.rb +++ b/app/models/admin/status_batch_action.rb @@ -65,7 +65,8 @@ class Admin::StatusBatchAction statuses.each { |status| Tombstone.find_or_create_by(uri: status.uri, account: status.account, by_moderator: true) } unless target_account.local? end - UserMailer.warning(target_account.user, @warning).deliver_later! if warnable? + process_notification! + RemovalWorker.push_bulk(status_ids) { |status_id| [status_id, { 'preserve' => target_account.local?, 'immediate' => !target_account.local? }] } end @@ -101,7 +102,7 @@ class Admin::StatusBatchAction text: text ) - UserMailer.warning(target_account.user, @warning).deliver_later! if warnable? + process_notification! end def handle_report! @@ -127,6 +128,13 @@ class Admin::StatusBatchAction !report.nil? end + def process_notification! + return unless warnable? + + UserMailer.warning(target_account.user, @warning).deliver_later! + LocalNotificationWorker.perform_async(target_account.id, @warning.id, 'AccountWarning', 'moderation_warning') + end + def warnable? send_email_notification && target_account.local? end diff --git a/app/models/appeal.rb b/app/models/appeal.rb index 395056b76f..fafa75e69d 100644 --- a/app/models/appeal.rb +++ b/app/models/appeal.rb @@ -18,6 +18,8 @@ class Appeal < ApplicationRecord MAX_STRIKE_AGE = 20.days + TEXT_LENGTH_LIMIT = 2_000 + belongs_to :account belongs_to :strike, class_name: 'AccountWarning', foreign_key: 'account_warning_id', inverse_of: :appeal @@ -26,7 +28,7 @@ class Appeal < ApplicationRecord belongs_to :rejected_by_account end - validates :text, presence: true, length: { maximum: 2_000 } + validates :text, presence: true, length: { maximum: TEXT_LENGTH_LIMIT } validates :account_warning_id, uniqueness: true validate :validate_time_frame, on: :create diff --git a/app/models/application_record.rb b/app/models/application_record.rb index 014a73997d..299aad6340 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -22,4 +22,10 @@ class ApplicationRecord < ActiveRecord::Base value end end + + # Prevent implicit serialization in ActiveModel::Serializer or other code paths. + # This is a hardening step to avoid accidental leaking of attributes. + def as_json + raise NotImplementedError + end end diff --git a/app/models/canonical_email_block.rb b/app/models/canonical_email_block.rb index c05eb9801d..d09df6f5e2 100644 --- a/app/models/canonical_email_block.rb +++ b/app/models/canonical_email_block.rb @@ -20,7 +20,6 @@ class CanonicalEmailBlock < ApplicationRecord validates :canonical_email_hash, presence: true, uniqueness: true scope :matching_email, ->(email) { where(canonical_email_hash: email_to_canonical_email_hash(email)) } - scope :matching_account, ->(account) { matching_email(account&.user_email).or(where(reference_account: account)) } def to_log_human_identifier canonical_email_hash diff --git a/app/models/concerns/account/associations.rb b/app/models/concerns/account/associations.rb index 959406abe4..2bfd9fa54a 100644 --- a/app/models/concerns/account/associations.rb +++ b/app/models/concerns/account/associations.rb @@ -63,7 +63,7 @@ module Account::Associations has_many :aliases, class_name: 'AccountAlias', dependent: :destroy, inverse_of: :account # Hashtags - has_and_belongs_to_many :tags + has_and_belongs_to_many :tags # rubocop:disable Rails/HasAndBelongsToMany has_many :featured_tags, -> { includes(:tag) }, dependent: :destroy, inverse_of: :account # Account deletion requests diff --git a/app/models/concerns/account/finder_concern.rb b/app/models/concerns/account/finder_concern.rb index a7acff1cbb..249a7b5fd1 100644 --- a/app/models/concerns/account/finder_concern.rb +++ b/app/models/concerns/account/finder_concern.rb @@ -25,42 +25,11 @@ module Account::FinderConcern end def find_remote(username, domain) - AccountFinder.new(username, domain).account - end - end - - class AccountFinder - attr_reader :username, :domain - - def initialize(username, domain) - @username = username - @domain = domain - end - - def account - scoped_accounts.order(id: :asc).take - end - - private - - def scoped_accounts - Account.unscoped.tap do |scope| - scope.merge! with_usernames - scope.merge! matching_username - scope.merge! matching_domain - end - end - - def with_usernames - Account.where.not(Account.arel_table[:username].lower.eq '') - end - - def matching_username - Account.where(Account.arel_table[:username].lower.eq username.to_s.downcase) - end - - def matching_domain - Account.where(Account.arel_table[:domain].lower.eq(domain.nil? ? nil : domain.to_s.downcase)) + Account + .with_username(username) + .with_domain(domain) + .order(id: :asc) + .take end end end diff --git a/app/models/concerns/attachmentable.rb b/app/models/concerns/attachmentable.rb index 3b7db1fcef..a83e178fc4 100644 --- a/app/models/concerns/attachmentable.rb +++ b/app/models/concerns/attachmentable.rb @@ -23,7 +23,7 @@ module Attachmentable included do def self.has_attached_file(name, options = {}) # rubocop:disable Naming/PredicateName - super(name, options) + super send(:"before_#{name}_validate", prepend: true) do attachment = send(name) @@ -69,7 +69,7 @@ module Attachmentable original_extension = Paperclip::Interpolations.extension(attachment, :original) proper_extension = extensions_for_mime_type.first.to_s extension = extensions_for_mime_type.include?(original_extension) ? original_extension : proper_extension - extension = 'jpeg' if extension == 'jpe' + extension = 'jpeg' if ['jpe', 'jfif'].include?(extension) extension end diff --git a/app/models/concerns/expireable.rb b/app/models/concerns/expireable.rb index c64fc7d807..26740e8213 100644 --- a/app/models/concerns/expireable.rb +++ b/app/models/concerns/expireable.rb @@ -4,7 +4,7 @@ module Expireable extend ActiveSupport::Concern included do - scope :expired, -> { where.not(expires_at: nil).where('expires_at < ?', Time.now.utc) } + scope :expired, -> { where.not(expires_at: nil).where(expires_at: ...Time.now.utc) } def expires_in return @expires_in if defined?(@expires_in) diff --git a/app/models/concerns/legacy_otp_secret.rb b/app/models/concerns/legacy_otp_secret.rb new file mode 100644 index 0000000000..466c4ec9bb --- /dev/null +++ b/app/models/concerns/legacy_otp_secret.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +# TODO: This file is here for legacy support during devise-two-factor upgrade. +# It should be removed after all records have been migrated. + +module LegacyOtpSecret + extend ActiveSupport::Concern + + private + + # Decrypt and return the `encrypted_otp_secret` attribute which was used in + # prior versions of devise-two-factor + # @return [String] The decrypted OTP secret + def legacy_otp_secret + return nil unless self[:encrypted_otp_secret] + return nil unless self.class.otp_secret_encryption_key + + hmac_iterations = 2000 # a default set by the Encryptor gem + key = self.class.otp_secret_encryption_key + salt = Base64.decode64(encrypted_otp_secret_salt) + iv = Base64.decode64(encrypted_otp_secret_iv) + + raw_cipher_text = Base64.decode64(encrypted_otp_secret) + # The last 16 bytes of the ciphertext are the authentication tag - we use + # Galois Counter Mode which is an authenticated encryption mode + cipher_text = raw_cipher_text[0..-17] + auth_tag = raw_cipher_text[-16..-1] # rubocop:disable Style/SlicingWithRange + + # this alrorithm lifted from + # https://github.com/attr-encrypted/encryptor/blob/master/lib/encryptor.rb#L54 + + # create an OpenSSL object which will decrypt the AES cipher with 256 bit + # keys in Galois Counter Mode (GCM). See + # https://ruby.github.io/openssl/OpenSSL/Cipher.html + cipher = OpenSSL::Cipher.new('aes-256-gcm') + + # tell the cipher we want to decrypt. Symmetric algorithms use a very + # similar process for encryption and decryption, hence the same object can + # do both. + cipher.decrypt + + # Use a Password-Based Key Derivation Function to generate the key actually + # used for encryptoin from the key we got as input. + cipher.key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(key, salt, hmac_iterations, cipher.key_len) + + # set the Initialization Vector (IV) + cipher.iv = iv + + # The tag must be set after calling Cipher#decrypt, Cipher#key= and + # Cipher#iv=, but before calling Cipher#final. After all decryption is + # performed, the tag is verified automatically in the call to Cipher#final. + # + # If the auth_tag does not verify, then #final will raise OpenSSL::Cipher::CipherError + cipher.auth_tag = auth_tag + + # auth_data must be set after auth_tag has been set when decrypting See + # http://ruby-doc.org/stdlib-2.0.0/libdoc/openssl/rdoc/OpenSSL/Cipher.html#method-i-auth_data-3D + # we are not adding any authenticated data but OpenSSL docs say this should + # still be called. + cipher.auth_data = '' + + # #update is (somewhat confusingly named) the method which actually + # performs the decryption on the given chunk of data. Our OTP secret is + # short so we only need to call it once. + # + # It is very important that we call #final because: + # + # 1. The authentication tag is checked during the call to #final + # 2. Block based cipher modes (e.g. CBC) work on fixed size chunks. We need + # to call #final to get it to process the last chunk properly. The output + # of #final should be appended to the decrypted value. This isn't + # required for streaming cipher modes but including it is a best practice + # so that your code will continue to function correctly even if you later + # change to a block cipher mode. + cipher.update(cipher_text) + cipher.final + end +end diff --git a/app/models/concerns/status/threading_concern.rb b/app/models/concerns/status/threading_concern.rb index ca8c448140..478a139d63 100644 --- a/app/models/concerns/status/threading_concern.rb +++ b/app/models/concerns/status/threading_concern.rb @@ -3,6 +3,23 @@ module Status::ThreadingConcern extend ActiveSupport::Concern + class_methods do + def permitted_statuses_from_ids(ids, account, stable: false) + statuses = Status.with_accounts(ids).to_a + account_ids = statuses.map(&:account_id).uniq + domains = statuses.filter_map(&:account_domain).uniq + relations = account&.relations_map(account_ids, domains) || {} + + statuses.reject! { |status| StatusFilter.new(status, account, relations).filtered? } + + if stable + statuses.sort_by! { |status| ids.index(status.id) } + else + statuses + end + end + end + def ancestors(limit, account = nil) find_statuses_from_tree_path(ancestor_ids(limit), account) end @@ -76,15 +93,7 @@ module Status::ThreadingConcern end def find_statuses_from_tree_path(ids, account, promote: false) - statuses = Status.with_accounts(ids).to_a - account_ids = statuses.map(&:account_id).uniq - domains = statuses.filter_map(&:account_domain).uniq - relations = account&.relations_map(account_ids, domains) || {} - - statuses.reject! { |status| StatusFilter.new(status, account, relations).filtered? } - - # Order ancestors/descendants by tree path - statuses.sort_by! { |status| ids.index(status.id) } + statuses = Status.permitted_statuses_from_ids(ids, account, stable: true) # Bring self-replies to the top if promote diff --git a/app/models/concerns/user/has_settings.rb b/app/models/concerns/user/has_settings.rb index 7697ddc1d3..f3125f23f5 100644 --- a/app/models/concerns/user/has_settings.rb +++ b/app/models/concerns/user/has_settings.rb @@ -103,6 +103,10 @@ module User::HasSettings settings['web.disable_swiping'] end + def setting_disable_hover_cards + settings['web.disable_hover_cards'] + end + def setting_always_send_emails settings['always_send_emails'] end diff --git a/app/models/concerns/user/ldap_authenticable.rb b/app/models/concerns/user/ldap_authenticable.rb index 180df9d310..fc1ee78d09 100644 --- a/app/models/concerns/user/ldap_authenticable.rb +++ b/app/models/concerns/user/ldap_authenticable.rb @@ -22,7 +22,7 @@ module User::LdapAuthenticable safe_username = safe_username.gsub(keys, replacement) end - resource = joins(:account).find_by(accounts: { username: safe_username }) + resource = joins(:account).merge(Account.with_username(safe_username)).take if resource.blank? resource = new( diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb index 9c14a9aa78..22790c1b78 100644 --- a/app/models/custom_emoji.rb +++ b/app/models/custom_emoji.rb @@ -38,7 +38,7 @@ class CustomEmoji < ApplicationRecord has_one :local_counterpart, -> { where(domain: nil) }, class_name: 'CustomEmoji', primary_key: :shortcode, foreign_key: :shortcode, inverse_of: false, dependent: nil - has_attached_file :image, styles: { static: { format: 'png', convert_options: '-coalesce +profile "!icc,*" +set date:modify +set date:create +set date:timestamp' } }, validate_media_type: false + has_attached_file :image, styles: { static: { format: 'png', convert_options: '-coalesce +profile "!icc,*" +set date:modify +set date:create +set date:timestamp', file_geometry_parser: FastGeometryParser } }, validate_media_type: false, processors: [:lazy_thumbnail] normalizes :domain, with: ->(domain) { domain.downcase } diff --git a/app/models/custom_filter.rb b/app/models/custom_filter.rb index 2d8f5b6cba..bacf158261 100644 --- a/app/models/custom_filter.rb +++ b/app/models/custom_filter.rb @@ -28,6 +28,8 @@ class CustomFilter < ApplicationRecord account ).freeze + EXPIRATION_DURATIONS = [30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week].freeze + include Expireable include Redisable @@ -49,10 +51,9 @@ class CustomFilter < ApplicationRecord after_commit :invalidate_cache! def expires_in - return @expires_in if defined?(@expires_in) return nil if expires_at.nil? - [30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week].find { |expires_in| expires_in.from_now >= expires_at } + EXPIRATION_DURATIONS.find { |expires_in| expires_in.from_now >= expires_at } end def irreversible=(value) diff --git a/app/models/form/admin_settings.rb b/app/models/form/admin_settings.rb index 56abd64ed5..a60f3dec4f 100644 --- a/app/models/form/admin_settings.rb +++ b/app/models/form/admin_settings.rb @@ -46,6 +46,8 @@ class Form::AdminSettings authorized_fetch reject_pattern reject_blurhash + app_icon + favicon ).freeze INTEGER_KEYS = %i( @@ -76,6 +78,8 @@ class Form::AdminSettings UPLOAD_KEYS = %i( thumbnail mascot + app_icon + favicon ).freeze PSEUDO_KEYS = %i( diff --git a/app/models/invite.rb b/app/models/invite.rb index c0cbc58458..ea095a3ac1 100644 --- a/app/models/invite.rb +++ b/app/models/invite.rb @@ -19,12 +19,14 @@ class Invite < ApplicationRecord include Expireable + COMMENT_SIZE_LIMIT = 420 + belongs_to :user, inverse_of: :invites has_many :users, inverse_of: :invite, dependent: nil - scope :available, -> { where(expires_at: nil).or(where('expires_at >= ?', Time.now.utc)) } + scope :available, -> { where(expires_at: nil).or(where(expires_at: Time.now.utc..)) } - validates :comment, length: { maximum: 420 } + validates :comment, length: { maximum: COMMENT_SIZE_LIMIT } before_validation :set_code diff --git a/app/models/link_feed.rb b/app/models/link_feed.rb new file mode 100644 index 0000000000..32efb331b6 --- /dev/null +++ b/app/models/link_feed.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +class LinkFeed < PublicFeed + # @param [PreviewCard] preview_card + # @param [Account] account + # @param [Hash] options + def initialize(preview_card, account, options = {}) + @preview_card = preview_card + super(account, options) + end + + # @param [Integer] limit + # @param [Integer] max_id + # @param [Integer] since_id + # @param [Integer] min_id + # @return [Array] + def get(limit, max_id = nil, since_id = nil, min_id = nil) + scope = public_scope + + scope.merge!(discoverable) + scope.merge!(attached_to_preview_card) + + scope.to_a_paginated_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id) + end + + private + + def attached_to_preview_card + Status.joins(:preview_cards_status).where(preview_cards_status: { preview_card_id: @preview_card.id }) + end + + def discoverable + Account.discoverable + end +end diff --git a/app/models/mention.rb b/app/models/mention.rb index 2348b2905c..af9bb7378b 100644 --- a/app/models/mention.rb +++ b/app/models/mention.rb @@ -5,10 +5,10 @@ # Table name: mentions # # id :bigint(8) not null, primary key -# status_id :bigint(8) +# status_id :bigint(8) not null # created_at :datetime not null # updated_at :datetime not null -# account_id :bigint(8) +# account_id :bigint(8) not null # silent :boolean default(FALSE), not null # diff --git a/app/models/notification.rb b/app/models/notification.rb index b6b4c208e1..6560194155 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -13,6 +13,7 @@ # from_account_id :bigint(8) not null # type :string # filtered :boolean default(FALSE), not null +# group_key :string # class Notification < ApplicationRecord @@ -61,6 +62,9 @@ class Notification < ApplicationRecord severed_relationships: { filterable: false, }.freeze, + moderation_warning: { + filterable: false, + }.freeze, 'admin.sign_up': { filterable: false, }.freeze, @@ -96,6 +100,7 @@ class Notification < ApplicationRecord belongs_to :poll, inverse_of: false belongs_to :report, inverse_of: false belongs_to :account_relationship_severance_event, inverse_of: false + belongs_to :account_warning, inverse_of: false end validates :type, inclusion: { in: TYPES } @@ -140,6 +145,69 @@ class Notification < ApplicationRecord end end + # This returns notifications from the request page, but with at most one notification per group. + # Notifications that have no `group_key` each count as a separate group. + def paginate_groups_by_max_id(limit, max_id: nil, since_id: nil) + query = reorder(id: :desc) + query = query.where(id: ...max_id) if max_id.present? + query = query.where(id: (since_id + 1)...) if since_id.present? + + unscoped + .with_recursive( + grouped_notifications: [ + query + .select('notifications.*', "ARRAY[COALESCE(notifications.group_key, 'ungrouped-' || notifications.id)] groups") + .limit(1), + query + .joins('CROSS JOIN grouped_notifications') + .where('array_length(grouped_notifications.groups, 1) < :limit', limit: limit) + .where('notifications.id < grouped_notifications.id') + .where.not("COALESCE(notifications.group_key, 'ungrouped-' || notifications.id) = ANY(grouped_notifications.groups)") + .select('notifications.*', "array_append(grouped_notifications.groups, COALESCE(notifications.group_key, 'ungrouped-' || notifications.id))") + .limit(1), + ] + ) + .from('grouped_notifications AS notifications') + .order(id: :desc) + .limit(limit) + end + + # Differs from :paginate_groups_by_max_id in that it gives the results immediately following min_id, + # whereas since_id gives the items with largest id, but with since_id as a cutoff. + # Results will be in ascending order by id. + def paginate_groups_by_min_id(limit, max_id: nil, min_id: nil) + query = reorder(id: :asc) + query = query.where(id: (min_id + 1)...) if min_id.present? + query = query.where(id: ...max_id) if max_id.present? + + unscoped + .with_recursive( + grouped_notifications: [ + query + .select('notifications.*', "ARRAY[COALESCE(notifications.group_key, 'ungrouped-' || notifications.id)] groups") + .limit(1), + query + .joins('CROSS JOIN grouped_notifications') + .where('array_length(grouped_notifications.groups, 1) < :limit', limit: limit) + .where('notifications.id > grouped_notifications.id') + .where.not("COALESCE(notifications.group_key, 'ungrouped-' || notifications.id) = ANY(grouped_notifications.groups)") + .select('notifications.*', "array_append(grouped_notifications.groups, COALESCE(notifications.group_key, 'ungrouped-' || notifications.id))") + .limit(1), + ] + ) + .from('grouped_notifications AS notifications') + .order(id: :asc) + .limit(limit) + end + + def to_a_grouped_paginated_by_id(limit, options = {}) + if options[:min_id].present? + paginate_groups_by_min_id(limit, min_id: options[:min_id], max_id: options[:max_id]).reverse + else + paginate_groups_by_max_id(limit, max_id: options[:max_id], since_id: options[:since_id]).to_a + end + end + def preload_cache_collection_target_statuses(notifications, &_block) notifications.group_by(&:type).each do |type, grouped_notifications| associations = TARGET_STATUS_INCLUDES_BY_TYPE[type] @@ -198,7 +266,7 @@ class Notification < ApplicationRecord self.from_account_id = activity&.status&.account_id when 'Account' self.from_account_id = activity&.id - when 'AccountRelationshipSeveranceEvent' + when 'AccountRelationshipSeveranceEvent', 'AccountWarning' # These do not really have an originating account, but this is mandatory # in the data model, and the recipient's account will by definition # always exist diff --git a/app/models/notification_group.rb b/app/models/notification_group.rb new file mode 100644 index 0000000000..b1cbd7c19a --- /dev/null +++ b/app/models/notification_group.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +class NotificationGroup < ActiveModelSerializers::Model + attributes :group_key, :sample_accounts, :notifications_count, :notification, :most_recent_notification_id + + def self.from_notification(notification, max_id: nil) + if notification.group_key.present? + # TODO: caching and preloading + scope = notification.account.notifications.where(group_key: notification.group_key) + scope = scope.where(id: ..max_id) if max_id.present? + + most_recent_notifications = scope.order(id: :desc).take(3) + most_recent_id = most_recent_notifications.first.id + sample_accounts = most_recent_notifications.map(&:from_account) + notifications_count = scope.count + else + most_recent_id = notification.id + sample_accounts = [notification.from_account] + notifications_count = 1 + end + + NotificationGroup.new( + notification: notification, + group_key: notification.group_key || "ungrouped-#{notification.id}", + sample_accounts: sample_accounts, + notifications_count: notifications_count, + most_recent_notification_id: most_recent_id + ) + end + + delegate :type, + :target_status, + :report, + :account_relationship_severance_event, + :account_warning, + to: :notification, prefix: false +end diff --git a/app/models/notification_policy.rb b/app/models/notification_policy.rb index f10b0c2a81..2bb58004e3 100644 --- a/app/models/notification_policy.rb +++ b/app/models/notification_policy.rb @@ -31,6 +31,6 @@ class NotificationPolicy < ApplicationRecord private def pending_notification_requests - @pending_notification_requests ||= notification_requests.where(dismissed: false).limit(MAX_MEANINGFUL_COUNT).pick(Arel.sql('count(*), coalesce(sum(notifications_count), 0)::bigint')) + @pending_notification_requests ||= notification_requests.limit(MAX_MEANINGFUL_COUNT).pick(Arel.sql('count(*), coalesce(sum(notifications_count), 0)::bigint')) end end diff --git a/app/models/notification_request.rb b/app/models/notification_request.rb index 6e9cae6625..2f601ac36b 100644 --- a/app/models/notification_request.rb +++ b/app/models/notification_request.rb @@ -9,12 +9,13 @@ # from_account_id :bigint(8) not null # last_status_id :bigint(8) # notifications_count :bigint(8) default(0), not null -# dismissed :boolean default(FALSE), not null # created_at :datetime not null # updated_at :datetime not null # class NotificationRequest < ApplicationRecord + self.ignored_columns += %w(dismissed) + include Paginable MAX_MEANINGFUL_COUNT = 100 @@ -34,8 +35,6 @@ class NotificationRequest < ApplicationRecord end def reconsider_existence! - return if dismissed? - prepare_notifications_count if notifications_count.positive? diff --git a/app/models/preview_card.rb b/app/models/preview_card.rb index 9fe02bd168..5a11351e58 100644 --- a/app/models/preview_card.rb +++ b/app/models/preview_card.rb @@ -32,6 +32,7 @@ # link_type :integer # published_at :datetime # image_description :string default(""), not null +# author_account_id :bigint(8) # class PreviewCard < ApplicationRecord @@ -45,6 +46,11 @@ class PreviewCard < ApplicationRecord y_comp: 4, }.freeze + # URL size limit to safely store in PosgreSQL's unique indexes + # Technically this is a byte-size limit but we use it as a + # character limit to work with length validation + URL_CHARACTER_LIMIT = 2692 + self.inheritance_column = false enum :type, { link: 0, photo: 1, video: 2, rich: 3 } @@ -54,10 +60,15 @@ class PreviewCard < ApplicationRecord has_many :statuses, through: :preview_cards_statuses has_one :trend, class_name: 'PreviewCardTrend', inverse_of: :preview_card, dependent: :destroy + belongs_to :author_account, class_name: 'Account', optional: true - has_attached_file :image, processors: [:thumbnail, :blurhash_transcoder], styles: ->(f) { image_styles(f) }, convert_options: { all: '-quality 90 +profile "!icc,*" +set date:modify +set date:create +set date:timestamp' }, validate_media_type: false + has_attached_file :image, + processors: [Rails.configuration.x.use_vips ? :lazy_thumbnail : :thumbnail, :blurhash_transcoder], + styles: ->(f) { image_styles(f) }, + convert_options: { all: '-quality 90 +profile "!icc,*" +set date:modify +set date:create +set date:timestamp' }, + validate_media_type: false - validates :url, presence: true, uniqueness: true, url: true + validates :url, presence: true, uniqueness: true, url: true, length: { maximum: URL_CHARACTER_LIMIT } validates_attachment_content_type :image, content_type: IMAGE_MIME_TYPES validates_attachment_size :image, less_than: LIMIT remotable_attachment :image, LIMIT @@ -122,6 +133,22 @@ class PreviewCard < ApplicationRecord @history ||= Trends::History.new('links', id) end + def authors + @authors ||= [PreviewCard::Author.new(self)] + end + + class Author < ActiveModelSerializers::Model + attributes :name, :url, :account + + def initialize(preview_card) + super( + name: preview_card.author_name, + url: preview_card.author_url, + account: preview_card.author_account, + ) + end + end + class << self private diff --git a/app/models/report.rb b/app/models/report.rb index df7e3d2efc..3df5a20e18 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -26,6 +26,8 @@ class Report < ApplicationRecord include Paginable include RateLimitable + COMMENT_SIZE_LIMIT = 1_000 + rate_limit by: :account, family: :reports belongs_to :account @@ -46,7 +48,7 @@ class Report < ApplicationRecord # A report is considered local if the reporter is local delegate :local?, to: :account - validates :comment, length: { maximum: 1_000 }, if: :local? + validates :comment, length: { maximum: COMMENT_SIZE_LIMIT }, if: :local? validates :rule_ids, absence: true, if: -> { (category_changed? || rule_ids_changed?) && !violation? } validate :validate_rule_ids, if: -> { (category_changed? || rule_ids_changed?) && violation? } diff --git a/app/models/report_note.rb b/app/models/report_note.rb index 74b46027e8..7361c97e67 100644 --- a/app/models/report_note.rb +++ b/app/models/report_note.rb @@ -13,10 +13,12 @@ # class ReportNote < ApplicationRecord + CONTENT_SIZE_LIMIT = 2_000 + belongs_to :account belongs_to :report, inverse_of: :notes, touch: true scope :latest, -> { reorder(created_at: :desc) } - validates :content, presence: true, length: { maximum: 500 } + validates :content, presence: true, length: { maximum: CONTENT_SIZE_LIMIT } end diff --git a/app/models/rule.rb b/app/models/rule.rb index f28dc2ffeb..99a36397aa 100644 --- a/app/models/rule.rb +++ b/app/models/rule.rb @@ -15,9 +15,11 @@ class Rule < ApplicationRecord include Discard::Model + TEXT_SIZE_LIMIT = 300 + self.discard_column = :deleted_at - validates :text, presence: true, length: { maximum: 300 } + validates :text, presence: true, length: { maximum: TEXT_SIZE_LIMIT } scope :ordered, -> { kept.order(priority: :asc, id: :asc) } end diff --git a/app/models/site_upload.rb b/app/models/site_upload.rb index 03d472cdb2..273dd6de9f 100644 --- a/app/models/site_upload.rb +++ b/app/models/site_upload.rb @@ -19,7 +19,23 @@ class SiteUpload < ApplicationRecord include Attachmentable + FAVICON_SIZES = [16, 32, 48].freeze + APPLE_ICON_SIZES = [57, 60, 72, 76, 114, 120, 144, 152, 167, 180, 1024].freeze + ANDROID_ICON_SIZES = [36, 48, 72, 96, 144, 192, 256, 384, 512].freeze + + APP_ICON_SIZES = (APPLE_ICON_SIZES + ANDROID_ICON_SIZES).uniq.freeze + STYLES = { + app_icon: + APP_ICON_SIZES.to_h do |size| + [:"#{size}", { format: 'png', geometry: "#{size}x#{size}#", file_geometry_parser: FastGeometryParser }] + end.freeze, + + favicon: + FAVICON_SIZES.to_h do |size| + [:"#{size}", { format: 'png', geometry: "#{size}x#{size}#", file_geometry_parser: FastGeometryParser }] + end.freeze, + thumbnail: { '@1x': { format: 'png', diff --git a/app/models/status.rb b/app/models/status.rb index 76fe3e48d1..b4c488d85f 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -41,6 +41,8 @@ class Status < ApplicationRecord include Status::SnapshotConcern include Status::ThreadingConcern + MEDIA_ATTACHMENTS_LIMIT = 4 + rate_limit by: :account, family: :statuses self.discard_column = :deleted_at @@ -85,7 +87,7 @@ class Status < ApplicationRecord has_many :local_reblogged, -> { merge(Account.local) }, through: :reblogs, source: :account has_many :local_bookmarked, -> { merge(Account.local) }, through: :bookmarks, source: :account - has_and_belongs_to_many :tags + has_and_belongs_to_many :tags # rubocop:disable Rails/HasAndBelongsToMany has_one :preview_cards_status, inverse_of: :status, dependent: :delete @@ -110,7 +112,9 @@ class Status < ApplicationRecord scope :remote, -> { where(local: false).where.not(uri: nil) } scope :local, -> { where(local: true).or(where(uri: nil)) } scope :with_accounts, ->(ids) { where(id: ids).includes(:account) } - scope :without_replies, -> { where('statuses.reply = FALSE OR statuses.in_reply_to_account_id = statuses.account_id') } + scope :without_replies, -> { not_reply.or(reply_to_account) } + scope :not_reply, -> { where(reply: false) } + scope :reply_to_account, -> { where(arel_table[:in_reply_to_account_id].eq arel_table[:account_id]) } scope :without_reblogs, -> { where(statuses: { reblog_of_id: nil }) } scope :tagged_with, ->(tag_ids) { joins(:statuses_tags).where(statuses_tags: { tag_id: tag_ids }) } scope :not_excluded_by_account, ->(account) { where.not(account_id: account.excluded_from_timeline_account_ids) } @@ -161,9 +165,9 @@ class Status < ApplicationRecord :status_stat, :tags, :preloadable_poll, - preview_cards_status: [:preview_card], + preview_cards_status: { preview_card: { author_account: [:account_stat, user: :role] } }, account: [:account_stat, user: :role], - active_mentions: { account: :account_stat }, + active_mentions: :account, reblog: [ :application, :tags, @@ -171,11 +175,11 @@ class Status < ApplicationRecord :conversation, :status_stat, :preloadable_poll, - preview_cards_status: [:preview_card], + preview_cards_status: { preview_card: { author_account: [:account_stat, user: :role] } }, account: [:account_stat, user: :role], - active_mentions: { account: :account_stat }, + active_mentions: :account, ], - thread: { account: :account_stat } + thread: :account delegate :domain, to: :account, prefix: true @@ -272,7 +276,7 @@ class Status < ApplicationRecord end def reported? - @reported ||= Report.where(target_account: account).unresolved.exists?(['? = ANY(status_ids)', id]) + @reported ||= account.targeted_reports.unresolved.exists?(['? = ANY(status_ids)', id]) || account.strikes.exists?(['? = ANY(status_ids)', id.to_s]) end def emojis @@ -302,7 +306,7 @@ class Status < ApplicationRecord else map = media_attachments.index_by(&:id) ordered_media_attachment_ids.filter_map { |media_attachment_id| map[media_attachment_id] } - end + end.take(MEDIA_ATTACHMENTS_LIMIT) end def replies_count @@ -353,23 +357,23 @@ class Status < ApplicationRecord # _from_me part does not require any timeline filters query_from_me = where(account_id: account.id) - .where(Status.arel_table[:visibility].eq(3)) + .direct_visibility .limit(limit) - .order('statuses.id DESC') + .order(id: :desc) # _to_me part requires mute and block filter. # FIXME: may we check mutes.hide_notifications? query_to_me = Status + .direct_visibility .joins(:mentions) - .merge(Mention.where(account_id: account.id)) - .where(Status.arel_table[:visibility].eq(3)) + .where(mentions: { account_id: account.id }) .limit(limit) .order('mentions.status_id DESC') .not_excluded_by_account(account) if max_id.present? - query_from_me = query_from_me.where('statuses.id < ?', max_id) - query_to_me = query_to_me.where('mentions.status_id < ?', max_id) + query_from_me = query_from_me.where(id: ...max_id) + query_to_me = query_to_me.where(mentions: { status_id: ...max_id }) end if since_id.present? @@ -377,9 +381,9 @@ class Status < ApplicationRecord query_to_me = query_to_me.where('mentions.status_id > ?', since_id) end - # returns ActiveRecord.Relation - items = (query_from_me.select(:id).to_a + query_to_me.select(:id).to_a).uniq(&:id).sort_by(&:id).reverse.take(limit) - Status.where(id: items.map(&:id)) + # TODO: use a single query? + ids = (query_from_me.pluck(:id) + query_to_me.pluck(:id)).sort.uniq.reverse.take(limit) + Status.where(id: ids) end def favourites_map(status_ids, account_id) diff --git a/app/models/status_edit.rb b/app/models/status_edit.rb index c6f282d00f..165b5403ec 100644 --- a/app/models/status_edit.rb +++ b/app/models/status_edit.rb @@ -43,7 +43,7 @@ class StatusEdit < ApplicationRecord scope :ordered, -> { order(id: :asc) } delegate :local?, :application, :edited?, :edited_at, - :discarded?, :visibility, to: :status + :discarded?, :visibility, :language, to: :status def emojis return @emojis if defined?(@emojis) @@ -54,12 +54,14 @@ class StatusEdit < ApplicationRecord def ordered_media_attachments return @ordered_media_attachments if defined?(@ordered_media_attachments) - @ordered_media_attachments = if ordered_media_attachment_ids.nil? - [] - else - map = status.media_attachments.index_by(&:id) - ordered_media_attachment_ids.map.with_index { |media_attachment_id, index| PreservedMediaAttachment.new(media_attachment: map[media_attachment_id], description: media_descriptions[index]) } - end + @ordered_media_attachments = begin + if ordered_media_attachment_ids.nil? + [] + else + map = status.media_attachments.index_by(&:id) + ordered_media_attachment_ids.map.with_index { |media_attachment_id, index| PreservedMediaAttachment.new(media_attachment: map[media_attachment_id], description: media_descriptions[index]) } + end + end.take(Status::MEDIA_ATTACHMENTS_LIMIT) end def proper diff --git a/app/models/tag.rb b/app/models/tag.rb index 58baa48c05..3f88cb0680 100644 --- a/app/models/tag.rb +++ b/app/models/tag.rb @@ -21,8 +21,10 @@ class Tag < ApplicationRecord include Paginable + # rubocop:disable Rails/HasAndBelongsToMany has_and_belongs_to_many :statuses has_and_belongs_to_many :accounts + # rubocop:enable Rails/HasAndBelongsToMany has_many :passive_relationships, class_name: 'TagFollow', inverse_of: :tag, dependent: :destroy has_many :featured_tags, dependent: :destroy, inverse_of: :tag @@ -35,7 +37,7 @@ class Tag < ApplicationRecord HASHTAG_LAST_SEQUENCE = '([[:word:]_]*[[:alpha:]][[:word:]_]*)' HASHTAG_NAME_PAT = "#{HASHTAG_FIRST_SEQUENCE}|#{HASHTAG_LAST_SEQUENCE}" - HASHTAG_RE = %r{(?(time_zone) { ActiveSupport::TimeZone[time_zone].nil? ? nil : time_zone } normalizes :chosen_languages, with: ->(chosen_languages) { chosen_languages.compact_blank.presence } - # This avoids a deprecation warning from Rails 5.1 - # It seems possible that a future release of devise-two-factor will - # handle this itself, and this can be removed from our User class. - attribute :otp_secret - has_many :session_activations, dependent: :destroy delegate :can?, to: :role diff --git a/app/models/user_invite_request.rb b/app/models/user_invite_request.rb index 2b76c88b94..9dd6775166 100644 --- a/app/models/user_invite_request.rb +++ b/app/models/user_invite_request.rb @@ -12,6 +12,8 @@ # class UserInviteRequest < ApplicationRecord + TEXT_SIZE_LIMIT = 420 + belongs_to :user, inverse_of: :invite_request - validates :text, presence: true, length: { maximum: 420 } + validates :text, presence: true, length: { maximum: TEXT_SIZE_LIMIT } end diff --git a/app/models/user_settings.rb b/app/models/user_settings.rb index 9801f1a85d..e0ecd22fa2 100644 --- a/app/models/user_settings.rb +++ b/app/models/user_settings.rb @@ -30,6 +30,7 @@ class UserSettings setting :use_pending_items, default: false setting :use_system_font, default: false setting :disable_swiping, default: false + setting :disable_hover_cards, default: false setting :delete_modal, default: true setting :reblog_modal, default: false setting :favourite_modal, default: false diff --git a/app/models/web/push_subscription.rb b/app/models/web/push_subscription.rb index a3a2ec3f03..ddfd08146e 100644 --- a/app/models/web/push_subscription.rb +++ b/app/models/web/push_subscription.rb @@ -21,10 +21,12 @@ class Web::PushSubscription < ApplicationRecord has_one :session_activation, foreign_key: 'web_push_subscription_id', inverse_of: :web_push_subscription, dependent: nil - validates :endpoint, presence: true + validates :endpoint, presence: true, url: true validates :key_p256dh, presence: true validates :key_auth, presence: true + validates_with WebPushKeyValidator + delegate :locale, to: :associated_user def encrypt(payload) @@ -73,7 +75,7 @@ class Web::PushSubscription < ApplicationRecord class << self def unsubscribe_for(application_id, resource_owner) - access_token_ids = Doorkeeper::AccessToken.where(application_id: application_id, resource_owner_id: resource_owner.id, revoked_at: nil).pluck(:id) + access_token_ids = Doorkeeper::AccessToken.where(application_id: application_id, resource_owner_id: resource_owner.id).not_revoked.pluck(:id) where(access_token_id: access_token_ids).delete_all end end diff --git a/app/models/webauthn_credential.rb b/app/models/webauthn_credential.rb index 4fa31ece52..d7ed1b9d40 100644 --- a/app/models/webauthn_credential.rb +++ b/app/models/webauthn_credential.rb @@ -15,9 +15,11 @@ # class WebauthnCredential < ApplicationRecord + SIGN_COUNT_LIMIT = (2**63) + validates :external_id, :public_key, :nickname, :sign_count, presence: true validates :external_id, uniqueness: true validates :nickname, uniqueness: { scope: :user_id } validates :sign_count, - numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: (2**63) - 1 } + numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: SIGN_COUNT_LIMIT - 1 } end diff --git a/app/policies/backup_policy.rb b/app/policies/backup_policy.rb index 86b8efbe96..7a4c5b4347 100644 --- a/app/policies/backup_policy.rb +++ b/app/policies/backup_policy.rb @@ -4,6 +4,6 @@ class BackupPolicy < ApplicationPolicy MIN_AGE = 6.days def create? - user_signed_in? && current_user.backups.where('created_at >= ?', MIN_AGE.ago).count.zero? + user_signed_in? && current_user.backups.where(created_at: MIN_AGE.ago..).count.zero? end end diff --git a/app/presenters/instance_presenter.rb b/app/presenters/instance_presenter.rb index 25df4d85aa..92415a6903 100644 --- a/app/presenters/instance_presenter.rb +++ b/app/presenters/instance_presenter.rb @@ -81,4 +81,16 @@ class InstancePresenter < ActiveModelSerializers::Model def mascot @mascot ||= Rails.cache.fetch('site_uploads/mascot') { SiteUpload.find_by(var: 'mascot') } end + + def favicon + return @favicon if defined?(@favicon) + + @favicon ||= Rails.cache.fetch('site_uploads/favicon') { SiteUpload.find_by(var: 'favicon') } + end + + def app_icon + return @app_icon if defined?(@app_icon) + + @app_icon ||= Rails.cache.fetch('site_uploads/app_icon') { SiteUpload.find_by(var: 'app_icon') } + end end diff --git a/app/presenters/oauth_metadata_presenter.rb b/app/presenters/oauth_metadata_presenter.rb new file mode 100644 index 0000000000..546503bfcc --- /dev/null +++ b/app/presenters/oauth_metadata_presenter.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +class OauthMetadataPresenter < ActiveModelSerializers::Model + include RoutingHelper + + attributes :issuer, :authorization_endpoint, :token_endpoint, + :revocation_endpoint, :scopes_supported, + :response_types_supported, :response_modes_supported, + :grant_types_supported, :token_endpoint_auth_methods_supported, + :service_documentation, :app_registration_endpoint + + def issuer + root_url + end + + def service_documentation + 'https://docs.joinmastodon.org/' + end + + def authorization_endpoint + oauth_authorization_url + end + + def token_endpoint + oauth_token_url + end + + # As the api_v1_apps route doesn't technically conform to the specification + # for OAuth 2.0 Dynamic Client Registration defined in RFC 7591 we use a + # non-standard property for now to indicate the mastodon specific registration + # endpoint. See: https://datatracker.ietf.org/doc/html/rfc7591 + def app_registration_endpoint + api_v1_apps_url + end + + def revocation_endpoint + oauth_revoke_url + end + + def scopes_supported + doorkeeper.scopes + end + + def response_types_supported + doorkeeper.authorization_response_types + end + + def response_modes_supported + doorkeeper.authorization_response_flows.flat_map(&:response_mode_matches).uniq + end + + def grant_types_supported + grant_types_supported = doorkeeper.grant_flows.dup + grant_types_supported << 'refresh_token' if doorkeeper.refresh_token_enabled? + grant_types_supported + end + + def token_endpoint_auth_methods_supported + %w(client_secret_basic client_secret_post) + end + + private + + def doorkeeper + @doorkeeper ||= Doorkeeper.configuration + end +end diff --git a/app/serializers/activitypub/outbox_serializer.rb b/app/serializers/activitypub/outbox_serializer.rb index 4f4f950a5a..4d3d9706de 100644 --- a/app/serializers/activitypub/outbox_serializer.rb +++ b/app/serializers/activitypub/outbox_serializer.rb @@ -2,7 +2,7 @@ class ActivityPub::OutboxSerializer < ActivityPub::CollectionSerializer def self.serializer_for(model, options) - if model.class.name == 'ActivityPub::ActivityPresenter' + if model.instance_of?(::ActivityPub::ActivityPresenter) ActivityPub::ActivitySerializer else super diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index 0009e9ceba..b5b2d043f4 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -43,6 +43,7 @@ class InitialStateSerializer < ActiveModel::Serializer store[:expand_spoilers] = object_account_user.setting_expand_spoilers store[:reduce_motion] = object_account_user.setting_reduce_motion store[:disable_swiping] = object_account_user.setting_disable_swiping + store[:disable_hover_cards] = object_account_user.setting_disable_hover_cards store[:advanced_layout] = object_account_user.setting_advanced_layout store[:use_blurhash] = object_account_user.setting_use_blurhash store[:use_pending_items] = object_account_user.setting_use_pending_items diff --git a/app/serializers/manifest_serializer.rb b/app/serializers/manifest_serializer.rb index 1c1f7d0ad5..a39fb5ef54 100644 --- a/app/serializers/manifest_serializer.rb +++ b/app/serializers/manifest_serializer.rb @@ -1,21 +1,10 @@ # frozen_string_literal: true class ManifestSerializer < ActiveModel::Serializer + include ApplicationHelper include RoutingHelper include ActionView::Helpers::TextHelper - ICON_SIZES = %w( - 36 - 48 - 72 - 96 - 144 - 192 - 256 - 384 - 512 - ).freeze - attributes :id, :name, :short_name, :icons, :theme_color, :background_color, :display, :start_url, :scope, @@ -37,9 +26,12 @@ class ManifestSerializer < ActiveModel::Serializer end def icons - ICON_SIZES.map do |size| + SiteUpload::ANDROID_ICON_SIZES.map do |size| + src = app_icon_path(size.to_i) + src = URI.join(root_url, src).to_s if src.present? + { - src: frontend_asset_url("icons/android-chrome-#{size}x#{size}.png"), + src: src || frontend_asset_url("icons/android-chrome-#{size}x#{size}.png"), sizes: "#{size}x#{size}", type: 'image/png', purpose: 'any maskable', diff --git a/app/serializers/oauth_metadata_serializer.rb b/app/serializers/oauth_metadata_serializer.rb new file mode 100644 index 0000000000..5f3dc7b87e --- /dev/null +++ b/app/serializers/oauth_metadata_serializer.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class OauthMetadataSerializer < ActiveModel::Serializer + attributes :issuer, :authorization_endpoint, :token_endpoint, + :revocation_endpoint, :scopes_supported, + :response_types_supported, :response_modes_supported, + :grant_types_supported, :token_endpoint_auth_methods_supported, + :service_documentation, :app_registration_endpoint +end diff --git a/app/serializers/rest/account_warning_serializer.rb b/app/serializers/rest/account_warning_serializer.rb new file mode 100644 index 0000000000..a0ef341d25 --- /dev/null +++ b/app/serializers/rest/account_warning_serializer.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class REST::AccountWarningSerializer < ActiveModel::Serializer + attributes :id, :action, :text, :status_ids, :created_at + + has_one :target_account, serializer: REST::AccountSerializer + has_one :appeal, serializer: REST::AppealSerializer + + def id + object.id.to_s + end + + def status_ids + object&.status_ids&.map(&:to_s) + end +end diff --git a/app/serializers/rest/appeal_serializer.rb b/app/serializers/rest/appeal_serializer.rb new file mode 100644 index 0000000000..a24cabc272 --- /dev/null +++ b/app/serializers/rest/appeal_serializer.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class REST::AppealSerializer < ActiveModel::Serializer + attributes :text, :state + + def state + if object.approved? + 'approved' + elsif object.rejected? + 'rejected' + else + 'pending' + end + end +end diff --git a/app/serializers/rest/application_serializer.rb b/app/serializers/rest/application_serializer.rb index 635508a17c..1a7b9265f1 100644 --- a/app/serializers/rest/application_serializer.rb +++ b/app/serializers/rest/application_serializer.rb @@ -1,24 +1,18 @@ # frozen_string_literal: true class REST::ApplicationSerializer < ActiveModel::Serializer - attributes :id, :name, :website, :scopes, :redirect_uri, - :client_id, :client_secret + attributes :id, :name, :website, :scopes, :redirect_uris # NOTE: Deprecated in 4.3.0, needs to be removed in 5.0.0 attribute :vapid_key + # We should consider this property deprecated for 4.3.0 + attribute :redirect_uri + def id object.id.to_s end - def client_id - object.uid - end - - def client_secret - object.secret - end - def website object.website.presence end diff --git a/app/serializers/rest/credential_application_serializer.rb b/app/serializers/rest/credential_application_serializer.rb new file mode 100644 index 0000000000..bfec7d03e8 --- /dev/null +++ b/app/serializers/rest/credential_application_serializer.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class REST::CredentialApplicationSerializer < REST::ApplicationSerializer + attributes :client_id, :client_secret + + def client_id + object.uid + end + + def client_secret + object.secret + end +end diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb index 32b36ccd3b..48c6122a65 100644 --- a/app/serializers/rest/instance_serializer.rb +++ b/app/serializers/rest/instance_serializer.rb @@ -59,7 +59,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer statuses: { max_characters: StatusLengthValidator::MAX_CHARS, - max_media_attachments: 4, + max_media_attachments: Status::MEDIA_ATTACHMENTS_LIMIT, characters_reserved_per_url: StatusLengthValidator::URL_PLACEHOLDER_CHARS, supported_mime_types: HtmlAwareFormatter::STATUS_MIME_TYPES, }, diff --git a/app/serializers/rest/notification_group_serializer.rb b/app/serializers/rest/notification_group_serializer.rb new file mode 100644 index 0000000000..9aa5663f4e --- /dev/null +++ b/app/serializers/rest/notification_group_serializer.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +class REST::NotificationGroupSerializer < ActiveModel::Serializer + attributes :group_key, :notifications_count, :type, :most_recent_notification_id + + attribute :page_min_id, if: :paginated? + attribute :page_max_id, if: :paginated? + attribute :latest_page_notification_at, if: :paginated? + + has_many :sample_accounts, serializer: REST::AccountSerializer + belongs_to :target_status, key: :status, if: :status_type?, serializer: REST::StatusSerializer + belongs_to :report, if: :report_type?, serializer: REST::ReportSerializer + belongs_to :account_relationship_severance_event, key: :event, if: :relationship_severance_event?, serializer: REST::AccountRelationshipSeveranceEventSerializer + belongs_to :account_warning, key: :moderation_warning, if: :moderation_warning_event?, serializer: REST::AccountWarningSerializer + + def status_type? + [:favourite, :reblog, :status, :mention, :poll, :update].include?(object.type) + end + + def report_type? + object.type == :'admin.report' + end + + def relationship_severance_event? + object.type == :severed_relationships + end + + def moderation_warning_event? + object.type == :moderation_warning + end + + def page_min_id + range = instance_options[:group_metadata][object.group_key] + range.present? ? range[:min_id].to_s : object.notification.id.to_s + end + + def page_max_id + range = instance_options[:group_metadata][object.group_key] + range.present? ? range[:max_id].to_s : object.notification.id.to_s + end + + def latest_page_notification_at + range = instance_options[:group_metadata][object.group_key] + range.present? ? range[:latest_notification_at] : object.notification.created_at + end + + def paginated? + !instance_options[:group_metadata].nil? + end +end diff --git a/app/serializers/rest/notification_policy_serializer.rb b/app/serializers/rest/notification_policy_serializer.rb index a50ba9e66b..8bf85250fa 100644 --- a/app/serializers/rest/notification_policy_serializer.rb +++ b/app/serializers/rest/notification_policy_serializer.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class REST::NotificationPolicySerializer < ActiveModel::Serializer + # Please update `app/javascript/mastodon/api_types/notification_policies.ts` when making changes to the attributes + attributes :filter_not_following, :filter_not_followers, :filter_new_accounts, diff --git a/app/serializers/rest/notification_serializer.rb b/app/serializers/rest/notification_serializer.rb index 45205786e3..1d867421bb 100644 --- a/app/serializers/rest/notification_serializer.rb +++ b/app/serializers/rest/notification_serializer.rb @@ -1,17 +1,22 @@ # frozen_string_literal: true class REST::NotificationSerializer < ActiveModel::Serializer - attributes :id, :type, :created_at + attributes :id, :type, :created_at, :group_key belongs_to :from_account, key: :account, serializer: REST::AccountSerializer belongs_to :target_status, key: :status, if: :status_type?, serializer: REST::StatusSerializer belongs_to :report, if: :report_type?, serializer: REST::ReportSerializer belongs_to :account_relationship_severance_event, key: :event, if: :relationship_severance_event?, serializer: REST::AccountRelationshipSeveranceEventSerializer + belongs_to :account_warning, key: :moderation_warning, if: :moderation_warning_event?, serializer: REST::AccountWarningSerializer def id object.id.to_s end + def group_key + object.group_key || "ungrouped-#{object.id}" + end + def status_type? [:favourite, :reaction, :reblog, :status, :mention, :poll, :update].include?(object.type) end @@ -23,4 +28,8 @@ class REST::NotificationSerializer < ActiveModel::Serializer def relationship_severance_event? object.type == :severed_relationships end + + def moderation_warning_event? + object.type == :moderation_warning + end end diff --git a/app/serializers/rest/preview_card_serializer.rb b/app/serializers/rest/preview_card_serializer.rb index 039262cd5f..f73a051ac0 100644 --- a/app/serializers/rest/preview_card_serializer.rb +++ b/app/serializers/rest/preview_card_serializer.rb @@ -1,6 +1,11 @@ # frozen_string_literal: true class REST::PreviewCardSerializer < ActiveModel::Serializer + class AuthorSerializer < ActiveModel::Serializer + attributes :name, :url + has_one :account, serializer: REST::AccountSerializer + end + include RoutingHelper attributes :url, :title, :description, :language, :type, @@ -8,6 +13,8 @@ class REST::PreviewCardSerializer < ActiveModel::Serializer :provider_url, :html, :width, :height, :image, :image_description, :embed_url, :blurhash, :published_at + has_many :authors, serializer: AuthorSerializer + def url object.original_url.presence || object.url end diff --git a/app/serializers/rest/v1/instance_serializer.rb b/app/serializers/rest/v1/instance_serializer.rb index 074c7dfa06..fd62426618 100644 --- a/app/serializers/rest/v1/instance_serializer.rb +++ b/app/serializers/rest/v1/instance_serializer.rb @@ -77,7 +77,7 @@ class REST::V1::InstanceSerializer < ActiveModel::Serializer statuses: { max_characters: StatusLengthValidator::MAX_CHARS, - max_media_attachments: 4, + max_media_attachments: Status::MEDIA_ATTACHMENTS_LIMIT, characters_reserved_per_url: StatusLengthValidator::URL_PLACEHOLDER_CHARS, supported_mime_types: HtmlAwareFormatter::STATUS_MIME_TYPES, }, diff --git a/app/services/account_search_service.rb b/app/services/account_search_service.rb index 571a0fa57d..dab5f748bf 100644 --- a/app/services/account_search_service.rb +++ b/app/services/account_search_service.rb @@ -28,9 +28,7 @@ class AccountSearchService < BaseService }, functions: [ - reputation_score_function, followers_score_function, - time_distance_function, ], }, }, @@ -81,36 +79,12 @@ class AccountSearchService < BaseService } end - # This function deranks accounts that follow more people than follow them - def reputation_score_function - { - script_score: { - script: { - source: "(Math.max(doc['followers_count'].value, 0) + 0.0) / (Math.max(doc['followers_count'].value, 0) + Math.max(doc['following_count'].value, 0) + 1)", - }, - }, - } - end - # This function promotes accounts that have more followers def followers_score_function { script_score: { script: { - source: "(Math.max(doc['followers_count'].value, 0) / (Math.max(doc['followers_count'].value, 0) + 1))", - }, - }, - } - end - - # This function deranks accounts that haven't posted in a long time - def time_distance_function - { - gauss: { - last_status_at: { - scale: '30d', - offset: '30d', - decay: 0.3, + source: "Math.log10((Math.max(doc['followers_count'].value, 0) + 1))", }, }, } @@ -126,10 +100,24 @@ class AccountSearchService < BaseService def core_query { - multi_match: { - query: @query, - type: 'bool_prefix', - fields: %w(username^2 username.*^2 display_name display_name.*), + dis_max: { + queries: [ + { + multi_match: { + query: @query, + type: 'most_fields', + fields: %w(username username.*), + }, + }, + + { + multi_match: { + query: @query, + type: 'most_fields', + fields: %w(display_name display_name.*), + }, + }, + ], }, } end @@ -142,7 +130,7 @@ class AccountSearchService < BaseService { multi_match: { query: @query, - type: 'most_fields', + type: 'best_fields', fields: %w(username^2 display_name^2 text text.*), operator: 'and', }, @@ -151,13 +139,23 @@ class AccountSearchService < BaseService end def call(query, account = nil, options = {}) - @query = query&.strip&.gsub(/\A@/, '') - @limit = options[:limit].to_i - @offset = options[:offset].to_i - @options = options - @account = account + MastodonOTELTracer.in_span('AccountSearchService#call') do |span| + @query = query&.strip&.gsub(/\A@/, '') + @limit = options[:limit].to_i + @offset = options[:offset].to_i + @options = options + @account = account - search_service_results.compact.uniq + span.add_attributes( + 'search.offset' => @offset, + 'search.limit' => @limit, + 'search.backend' => Chewy.enabled? ? 'elasticsearch' : 'database' + ) + + search_service_results.compact.uniq.tap do |results| + span.set_attribute('search.results.count', results.size) + end + end end private diff --git a/app/services/activitypub/process_status_update_service.rb b/app/services/activitypub/process_status_update_service.rb index fb2b33114e..1dbed27f28 100644 --- a/app/services/activitypub/process_status_update_service.rb +++ b/app/services/activitypub/process_status_update_service.rb @@ -73,7 +73,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService as_array(@json['attachment']).each do |attachment| media_attachment_parser = ActivityPub::Parser::MediaAttachmentParser.new(attachment) - next if media_attachment_parser.remote_url.blank? || @next_media_attachments.size > 4 + next if media_attachment_parser.remote_url.blank? || @next_media_attachments.size > Status::MEDIA_ATTACHMENTS_LIMIT begin media_attachment = previous_media_attachments.find { |previous_media_attachment| previous_media_attachment.remote_url == media_attachment_parser.remote_url } diff --git a/app/services/backup_service.rb b/app/services/backup_service.rb index 4552fc0977..94be214eb2 100644 --- a/app/services/backup_service.rb +++ b/app/services/backup_service.rb @@ -19,8 +19,8 @@ class BackupService < BaseService def build_outbox_json!(file) skeleton = serialize(collection_presenter, ActivityPub::CollectionSerializer) - skeleton[:@context] = full_context - skeleton[:orderedItems] = ['!PLACEHOLDER!'] + skeleton['@context'] = full_context + skeleton['orderedItems'] = ['!PLACEHOLDER!'] skeleton = Oj.dump(skeleton) prepend, append = skeleton.split('"!PLACEHOLDER!"') add_comma = false diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb index c6b600dd7c..adabb1096e 100644 --- a/app/services/fetch_link_card_service.rb +++ b/app/services/fetch_link_card_service.rb @@ -29,7 +29,7 @@ class FetchLinkCardService < BaseService end attach_card if @card&.persisted? - rescue HTTP::Error, OpenSSL::SSL::SSLError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError => e + rescue HTTP::Error, OpenSSL::SSL::SSLError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError, EncodingError, ActiveRecord::RecordInvalid => e Rails.logger.debug { "Error fetching link #{@original_url}: #{e}" } nil end @@ -56,7 +56,7 @@ class FetchLinkCardService < BaseService @html_charset = res.charset - res.body_with_limit + res.truncated_body end end @@ -147,9 +147,12 @@ class FetchLinkCardService < BaseService return if html.nil? link_details_extractor = LinkDetailsExtractor.new(@url, @html, @html_charset) + provider = PreviewCardProvider.matching_domain(Addressable::URI.parse(link_details_extractor.canonical_url).normalized_host) + linked_account = ResolveAccountService.new.call(link_details_extractor.author_account, suppress_errors: true) if link_details_extractor.author_account.present? && provider&.trendable? @card = PreviewCard.find_or_initialize_by(url: link_details_extractor.canonical_url) if link_details_extractor.canonical_url != @card.url @card.assign_attributes(link_details_extractor.to_preview_card_attributes) + @card.author_account = linked_account @card.save_with_optional_image! unless @card.title.blank? && @card.html.blank? end end diff --git a/app/services/notify_service.rb b/app/services/notify_service.rb index c83e4c017f..d69b5af141 100644 --- a/app/services/notify_service.rb +++ b/app/services/notify_service.rb @@ -3,12 +3,16 @@ class NotifyService < BaseService include Redisable + MAXIMUM_GROUP_SPAN_HOURS = 12 + MAXIMUM_GROUP_GAP_TIME = 4.hours.to_i + NON_EMAIL_TYPES = %i( admin.report admin.sign_up update poll status + moderation_warning # TODO: this probably warrants an email notification severed_relationships ).freeze @@ -22,7 +26,7 @@ class NotifyService < BaseService def dismiss? blocked = @recipient.unavailable? - blocked ||= from_self? && @notification.type != :poll && @notification.type != :severed_relationships + blocked ||= from_self? && %i(poll severed_relationships moderation_warning).exclude?(@notification.type) return blocked if message? && from_staff? @@ -75,6 +79,7 @@ class NotifyService < BaseService admin.report poll update + account_warning ).freeze def initialize(notification) @@ -145,6 +150,9 @@ class NotifyService < BaseService end def statuses_that_mention_sender + # This queries private mentions from the recipient to the sender up in the thread. + # This allows up to 100 messages that do not match in the thread, allowing conversations + # involving multiple people. Status.count_by_sql([<<-SQL.squish, id: @notification.target_status.in_reply_to_id, recipient_id: @recipient.id, sender_id: @sender.id, depth_limit: 100]) WITH RECURSIVE ancestors(id, in_reply_to_id, mention_id, path, depth) AS ( SELECT s.id, s.in_reply_to_id, m.id, ARRAY[s.id], 0 @@ -152,16 +160,17 @@ class NotifyService < BaseService LEFT JOIN mentions m ON m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id WHERE s.id = :id UNION ALL - SELECT s.id, s.in_reply_to_id, m.id, st.path || s.id, st.depth + 1 - FROM ancestors st - JOIN statuses s ON s.id = st.in_reply_to_id - LEFT JOIN mentions m ON m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id - WHERE st.mention_id IS NULL AND NOT s.id = ANY(path) AND st.depth < :depth_limit + SELECT s.id, s.in_reply_to_id, m.id, ancestors.path || s.id, ancestors.depth + 1 + FROM ancestors + JOIN statuses s ON s.id = ancestors.in_reply_to_id + /* early exit if we already have a mention matching our requirements */ + LEFT JOIN mentions m ON m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id AND s.account_id = :recipient_id + WHERE ancestors.mention_id IS NULL AND NOT s.id = ANY(path) AND ancestors.depth < :depth_limit ) SELECT COUNT(*) - FROM ancestors st - JOIN statuses s ON s.id = st.id - WHERE st.mention_id IS NOT NULL AND s.visibility = 3 + FROM ancestors + JOIN statuses s ON s.id = ancestors.id + WHERE ancestors.mention_id IS NOT NULL AND s.account_id = :recipient_id AND s.visibility = 3 SQL end end @@ -177,6 +186,7 @@ class NotifyService < BaseService return if dismiss? @notification.filtered = filter? + @notification.group_key = notification_group_key @notification.save! # It's possible the underlying activity has been deleted @@ -196,6 +206,24 @@ class NotifyService < BaseService private + def notification_group_key + return nil if @notification.filtered || %i(favourite reblog).exclude?(@notification.type) + + type_prefix = "#{@notification.type}-#{@notification.target_status.id}" + redis_key = "notif-group/#{@recipient.id}/#{type_prefix}" + hour_bucket = @notification.activity.created_at.utc.to_i / 1.hour.to_i + + # Reuse previous group if it does not span too large an amount of time + previous_bucket = redis.get(redis_key).to_i + hour_bucket = previous_bucket if hour_bucket < previous_bucket + MAXIMUM_GROUP_SPAN_HOURS + + # Do not track groups past a given inactivity time + # We do not concern ourselves with race conditions since we use hour buckets + redis.set(redis_key, hour_bucket, ex: MAXIMUM_GROUP_GAP_TIME) + + "#{type_prefix}-#{hour_bucket}" + end + def dismiss? DismissCondition.new(@notification).dismiss? end diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index a437feb0fd..005fd263c2 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -146,9 +146,9 @@ class PostStatusService < BaseService return end - raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if @options[:media_ids].size > 4 + raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if @options[:media_ids].size > Status::MEDIA_ATTACHMENTS_LIMIT - @media = @account.media_attachments.where(status_id: nil).where(id: @options[:media_ids].take(4).map(&:to_i)) + @media = @account.media_attachments.where(status_id: nil).where(id: @options[:media_ids].take(Status::MEDIA_ATTACHMENTS_LIMIT).map(&:to_i)) raise Mastodon::ValidationError, I18n.t('media_attachments.validations.images_and_video') if @media.size > 1 && @media.find(&:audio_or_video?) raise Mastodon::ValidationError, I18n.t('media_attachments.validations.not_ready') if @media.any?(&:not_processed?) @@ -176,7 +176,7 @@ class PostStatusService < BaseService def idempotency_duplicate if scheduled? - @account.schedule_statuses.find(@idempotency_duplicate) + @account.scheduled_statuses.find(@idempotency_duplicate) else @account.statuses.find(@idempotency_duplicate) end @@ -187,7 +187,7 @@ class PostStatusService < BaseService end def scheduled_in_the_past? - @scheduled_at.present? && @scheduled_at <= Time.now.utc + MIN_SCHEDULE_OFFSET + @scheduled_at.present? && @scheduled_at <= Time.now.utc end def bump_potential_friendship! @@ -228,7 +228,7 @@ class PostStatusService < BaseService end def scheduled_options - @options.tap do |options_hash| + @options.dup.tap do |options_hash| options_hash[:in_reply_to_id] = options_hash.delete(:thread)&.id options_hash[:application_id] = options_hash.delete(:application)&.id options_hash[:scheduled_at] = nil diff --git a/app/services/report_service.rb b/app/services/report_service.rb index fe546c383e..dea6df7b0a 100644 --- a/app/services/report_service.rb +++ b/app/services/report_service.rb @@ -81,7 +81,7 @@ class ReportService < BaseService # If the account making reports is remote, it is likely anonymized so we have to relax the requirements for attaching statuses. domain = @source_account.domain.to_s.downcase - has_followers = @target_account.followers.where(Account.arel_table[:domain].lower.eq(domain)).exists? + has_followers = @target_account.followers.with_domain(domain).exists? visibility = has_followers ? %i(public unlisted private) : %i(public unlisted) scope = @target_account.statuses.with_discarded scope.merge!(scope.where(visibility: visibility).or(scope.where('EXISTS (SELECT 1 FROM mentions m JOIN accounts a ON m.account_id = a.id WHERE lower(a.domain) = ?)', domain))) diff --git a/app/services/statuses_search_service.rb b/app/services/statuses_search_service.rb index 7d5b0203a0..ab8e28f61c 100644 --- a/app/services/statuses_search_service.rb +++ b/app/services/statuses_search_service.rb @@ -2,14 +2,24 @@ class StatusesSearchService < BaseService def call(query, account = nil, options = {}) - @query = query&.strip - @account = account - @options = options - @limit = options[:limit].to_i - @offset = options[:offset].to_i + MastodonOTELTracer.in_span('StatusesSearchService#call') do |span| + @query = query&.strip + @account = account + @options = options + @limit = options[:limit].to_i + @offset = options[:offset].to_i + convert_deprecated_options! - convert_deprecated_options! - status_search_results + span.add_attributes( + 'search.offset' => @offset, + 'search.limit' => @limit, + 'search.backend' => Chewy.enabled? ? 'elasticsearch' : 'database' + ) + + status_search_results.tap do |results| + span.set_attribute('search.results.count', results.size) + end + end end private diff --git a/app/services/tag_search_service.rb b/app/services/tag_search_service.rb index 929cfd884f..57400b76ad 100644 --- a/app/services/tag_search_service.rb +++ b/app/services/tag_search_service.rb @@ -2,15 +2,25 @@ class TagSearchService < BaseService def call(query, options = {}) - @query = query.strip.delete_prefix('#') - @offset = options.delete(:offset).to_i - @limit = options.delete(:limit).to_i - @options = options + MastodonOTELTracer.in_span('TagSearchService#call') do |span| + @query = query.strip.delete_prefix('#') + @offset = options.delete(:offset).to_i + @limit = options.delete(:limit).to_i + @options = options - results = from_elasticsearch if Chewy.enabled? - results ||= from_database + span.add_attributes( + 'search.offset' => @offset, + 'search.limit' => @limit, + 'search.backend' => Chewy.enabled? ? 'elasticsearch' : 'database' + ) - results + results = from_elasticsearch if Chewy.enabled? + results ||= from_database + + span.set_attribute('search.results.count', results.size) + + results + end end private diff --git a/app/services/update_status_service.rb b/app/services/update_status_service.rb index 2c479afff8..7f7f389bee 100644 --- a/app/services/update_status_service.rb +++ b/app/services/update_status_service.rb @@ -70,9 +70,9 @@ class UpdateStatusService < BaseService def validate_media! return [] if @options[:media_ids].blank? || !@options[:media_ids].is_a?(Enumerable) - raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if @options[:media_ids].size > 4 + raise Mastodon::ValidationError, I18n.t('media_attachments.validations.too_many') if @options[:media_ids].size > Status::MEDIA_ATTACHMENTS_LIMIT - media_attachments = @status.account.media_attachments.where(status_id: [nil, @status.id]).where(scheduled_status_id: nil).where(id: @options[:media_ids].take(4).map(&:to_i)).to_a + media_attachments = @status.account.media_attachments.where(status_id: [nil, @status.id]).where(scheduled_status_id: nil).where(id: @options[:media_ids].take(Status::MEDIA_ATTACHMENTS_LIMIT).map(&:to_i)).to_a raise Mastodon::ValidationError, I18n.t('media_attachments.validations.images_and_video') if media_attachments.size > 1 && media_attachments.find(&:audio_or_video?) raise Mastodon::ValidationError, I18n.t('media_attachments.validations.not_ready') if media_attachments.any?(&:not_processed?) diff --git a/app/validators/unique_username_validator.rb b/app/validators/unique_username_validator.rb index 09c8fadb55..c417e2f696 100644 --- a/app/validators/unique_username_validator.rb +++ b/app/validators/unique_username_validator.rb @@ -6,10 +6,7 @@ class UniqueUsernameValidator < ActiveModel::Validator def validate(account) return if account.username.blank? - normalized_username = account.username.downcase - normalized_domain = account.domain&.downcase - - scope = Account.where(Account.arel_table[:username].lower.eq normalized_username).where(Account.arel_table[:domain].lower.eq normalized_domain) + scope = Account.with_username(account.username).with_domain(account.domain) scope = scope.where.not(id: account.id) if account.persisted? account.errors.add(:username, :taken) if scope.exists? diff --git a/app/validators/web_push_key_validator.rb b/app/validators/web_push_key_validator.rb new file mode 100644 index 0000000000..a8ad5c9c6b --- /dev/null +++ b/app/validators/web_push_key_validator.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class WebPushKeyValidator < ActiveModel::Validator + def validate(subscription) + begin + Webpush::Encryption.encrypt('validation_test', subscription.key_p256dh, subscription.key_auth) + rescue ArgumentError, OpenSSL::PKey::EC::Point::Error + subscription.errors.add(:base, I18n.t('crypto.errors.invalid_key')) + end + end +end diff --git a/app/views/admin/account_warnings/_account_warning.html.haml b/app/views/admin/account_warnings/_account_warning.html.haml index 5702e4f6d2..368e69e63e 100644 --- a/app/views/admin/account_warnings/_account_warning.html.haml +++ b/app/views/admin/account_warnings/_account_warning.html.haml @@ -2,7 +2,7 @@ .log-entry__header .log-entry__avatar .indicator-icon{ class: account_warning.overruled? ? 'success' : 'failure' } - = fa_icon 'warning' + = material_symbol 'warning' .log-entry__content .log-entry__title = t(account_warning.action, diff --git a/app/views/admin/accounts/_remote_account.html.haml b/app/views/admin/accounts/_remote_account.html.haml index 99996e1d46..6755af2496 100644 --- a/app/views/admin/accounts/_remote_account.html.haml +++ b/app/views/admin/accounts/_remote_account.html.haml @@ -2,14 +2,14 @@ %th= t('admin.accounts.inbox_url') %td = account.inbox_url - = fa_icon DeliveryFailureTracker.available?(account.inbox_url) ? 'check' : 'times' + = material_symbol DeliveryFailureTracker.available?(account.inbox_url) ? 'check' : 'close' %td = table_link_to 'search', domain_block.present? ? t('admin.domain_blocks.view') : t('admin.accounts.view_domain'), admin_instance_path(account.domain) %tr %th= t('admin.accounts.shared_inbox_url') %td = account.shared_inbox_url - = fa_icon DeliveryFailureTracker.available?(account.shared_inbox_url) ? 'check' : 'times' + = material_symbol DeliveryFailureTracker.available?(account.shared_inbox_url) ? 'check' : 'close' %td - if domain_block.nil? = table_link_to 'ban', t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: account.domain) diff --git a/app/views/admin/accounts/index.html.haml b/app/views/admin/accounts/index.html.haml index 0ca457f39e..9dd4f0e4e4 100644 --- a/app/views/admin/accounts/index.html.haml +++ b/app/views/admin/accounts/index.html.haml @@ -1,38 +1,41 @@ - content_for :page_title do = t('admin.accounts.title') -= form_tag admin_accounts_url, method: 'GET', class: 'simple_form' do += form_with url: admin_accounts_url, method: :get, class: :simple_form do |form| .filters .filter-subset.filter-subset--with-select %strong= t('admin.accounts.location.title') .input.select.optional - = select_tag :origin, - options_for_select([[t('admin.accounts.location.local'), 'local'], [t('admin.accounts.location.remote'), 'remote']], params[:origin]), - prompt: I18n.t('generic.all') + = form.select :origin, + options_for_select([[t('admin.accounts.location.local'), 'local'], [t('admin.accounts.location.remote'), 'remote']], params[:origin]), + prompt: I18n.t('generic.all') .filter-subset.filter-subset--with-select %strong= t('admin.accounts.moderation.title') .input.select.optional - = select_tag :status, - options_for_select(admin_accounts_moderation_options, params[:status]), - prompt: I18n.t('generic.all') + = form.select :status, + options_for_select(admin_accounts_moderation_options, params[:status]), + prompt: I18n.t('generic.all') .filter-subset.filter-subset--with-select %strong= t('admin.accounts.role') .input.select.optional - = select_tag :role_ids, - options_from_collection_for_select(UserRole.assignable, :id, :name, params[:role_ids]), - prompt: I18n.t('admin.accounts.moderation.all') + = form.select :role_ids, + options_from_collection_for_select(UserRole.assignable, :id, :name, params[:role_ids]), + prompt: I18n.t('admin.accounts.moderation.all') .filter-subset.filter-subset--with-select %strong= t 'generic.order_by' .input.select - = select_tag :order, - options_for_select([[t('relationships.most_recent'), 'recent'], [t('relationships.last_active'), 'active']], params[:order]) + = form.select :order, + options_for_select([[t('relationships.most_recent'), 'recent'], [t('relationships.last_active'), 'active']], params[:order]) .fields-group - %i(username by_domain display_name email ip).each do |key| - next if key == :by_domain && params[:origin] != 'remote' .input.string.optional - = text_field_tag key, params[key], class: 'string optional', placeholder: I18n.t("admin.accounts.#{key}") + = form.text_field key, + value: params[key], + class: 'string optional', + placeholder: I18n.t("admin.accounts.#{key}") .actions %button.button= t('admin.accounts.search') @@ -40,7 +43,7 @@ %hr.spacer/ -= form_for(@form, url: batch_admin_accounts_path) do |f| += form_with model: @form, url: batch_admin_accounts_path do |f| = hidden_field_tag :page, params[:page] || 1 = hidden_field_tag :select_all_matching, '0' @@ -53,19 +56,19 @@ = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - if @accounts.any?(&:user_pending?) - = f.button safe_join([fa_icon('check'), t('admin.accounts.approve')]), + = f.button safe_join([material_symbol('check'), t('admin.accounts.approve')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :approve, type: :submit - = f.button safe_join([fa_icon('times'), t('admin.accounts.reject')]), + = f.button safe_join([material_symbol('close'), t('admin.accounts.reject')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :reject, type: :submit - = f.button safe_join([fa_icon('lock'), t('admin.accounts.perform_full_suspension')]), + = f.button safe_join([material_symbol('lock'), t('admin.accounts.perform_full_suspension')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :suspend, diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml index d380d807a3..f148b9a082 100644 --- a/app/views/admin/accounts/show.html.haml +++ b/app/views/admin/accounts/show.html.haml @@ -20,7 +20,7 @@ %dd{ title: field.value, class: custom_field_classes(field) } - if field.verified? %span.verified__mark{ title: t('accounts.link_verified_on', date: l(field.verified_at)) } - = fa_icon 'check' + = material_symbol 'check' = prerender_custom_emojis(account_field_value_format(field, with_rel_me: false), account.emojis) - if account.note.present? @@ -30,7 +30,7 @@ = render 'admin/accounts/counters', account: @account - if @account.local? && @account.user.nil? - = link_to t('admin.accounts.unblock_email'), unblock_email_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unblock_email, @account) && CanonicalEmailBlock.matching_account(@account).exists? + = link_to t('admin.accounts.unblock_email'), unblock_email_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unblock_email, @account) && CanonicalEmailBlock.exists?(reference_account_id: @account.id) - else .table-wrapper %table.table.inline-table @@ -62,14 +62,16 @@ .report-notes = render partial: 'admin/report_notes/report_note', collection: @moderation_notes - = simple_form_for @account_moderation_note, url: admin_account_moderation_notes_path do |f| - = f.hidden_field :target_account_id + = simple_form_for @account_moderation_note, url: admin_account_moderation_notes_path do |form| + = form.hidden_field :target_account_id + + = render 'shared/error_messages', object: @account_moderation_note .field-group - = f.input :content, placeholder: t('admin.reports.notes.placeholder'), rows: 6 + = form.input :content, input_html: { placeholder: t('admin.reports.notes.placeholder'), maxlength: AccountModerationNote::CONTENT_SIZE_LIMIT, rows: 6, autofocus: @account_moderation_note.errors.any? } .actions - = f.button :button, t('admin.account_moderation_notes.create'), type: :submit + = form.button :button, t('admin.account_moderation_notes.create'), type: :submit %hr.spacer/ diff --git a/app/views/admin/action_logs/index.html.haml b/app/views/admin/action_logs/index.html.haml index c4929cc422..c02c8f0ad4 100644 --- a/app/views/admin/action_logs/index.html.haml +++ b/app/views/admin/action_logs/index.html.haml @@ -1,19 +1,23 @@ - content_for :page_title do = t('admin.action_logs.title') -= form_tag admin_action_logs_url, method: 'GET', class: 'simple_form' do += form_with url: admin_action_logs_url, method: :get, class: :simple_form do |form| = hidden_field_tag :target_account_id, params[:target_account_id] if params[:target_account_id].present? .filters .filter-subset.filter-subset--with-select %strong= t('admin.action_logs.filter_by_user') .input.select.optional - = select_tag :account_id, options_from_collection_for_select(@auditable_accounts, :id, :username, params[:account_id]), prompt: I18n.t('admin.accounts.moderation.all') + = form.select :account_id, + options_from_collection_for_select(@auditable_accounts, :id, :username, params[:account_id]), + prompt: I18n.t('admin.accounts.moderation.all') .filter-subset.filter-subset--with-select %strong= t('admin.action_logs.filter_by_action') .input.select.optional - = select_tag :action_type, options_for_select(Admin::ActionLogFilter::ACTION_TYPE_MAP.keys.map { |key| [I18n.t("admin.action_logs.action_types.#{key}"), key] }, params[:action_type]), prompt: I18n.t('admin.accounts.moderation.all') + = form.select :action_type, + options_for_select(Admin::ActionLogFilter::ACTION_TYPE_MAP.keys.map { |key| [I18n.t("admin.action_logs.action_types.#{key}"), key] }, params[:action_type]), + prompt: I18n.t('admin.accounts.moderation.all') - if @action_logs.empty? .muted-hint.center-text diff --git a/app/views/admin/custom_emojis/index.html.haml b/app/views/admin/custom_emojis/index.html.haml index bea6a7cd21..82fec554b0 100644 --- a/app/views/admin/custom_emojis/index.html.haml +++ b/app/views/admin/custom_emojis/index.html.haml @@ -21,20 +21,23 @@ - else = filter_link_to t('admin.accounts.location.remote'), remote: '1', local: nil -= form_tag admin_custom_emojis_url, method: 'GET', class: 'simple_form' do += form_with url: admin_custom_emojis_url, method: :get, class: :simple_form do |form| .fields-group - CustomEmojiFilter::KEYS.each do |key| - = hidden_field_tag key, params[key] if params[key].present? + = form.hidden_field key, value: params[key] if params[key].present? - %i(shortcode by_domain).each do |key| .input.string.optional - = text_field_tag key, params[key], class: 'string optional', placeholder: I18n.t("admin.custom_emojis.#{key}") + = form.text_field key, + value: params[key], + class: 'string optional', + placeholder: I18n.t("admin.custom_emojis.#{key}") .actions %button.button= t('admin.accounts.search') = link_to t('admin.accounts.reset'), admin_custom_emojis_path, class: 'button negative' -= form_for(@form, url: batch_admin_custom_emojis_path) do |f| += form_with model: @form, url: batch_admin_custom_emojis_path do |f| = hidden_field_tag :page, params[:page] || 1 - CustomEmojiFilter::KEYS.each do |key| @@ -48,19 +51,19 @@ - if params[:local] == '1' = f.button safe_join([fa_icon('save'), t('generic.save_changes')]), name: :update, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } - = f.button safe_join([fa_icon('eye'), t('admin.custom_emojis.list')]), name: :list, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + = f.button safe_join([material_symbol('visibility'), t('admin.custom_emojis.list')]), name: :list, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } - = f.button safe_join([fa_icon('eye-slash'), t('admin.custom_emojis.unlist')]), name: :unlist, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + = f.button safe_join([material_symbol('visibility_off'), t('admin.custom_emojis.unlist')]), name: :unlist, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } = f.button safe_join([fa_icon('power-off'), t('admin.custom_emojis.enable')]), name: :enable, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } = f.button safe_join([fa_icon('power-off'), t('admin.custom_emojis.disable')]), name: :disable, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } - if can?(:destroy, :custom_emoji) - = f.button safe_join([fa_icon('times'), t('admin.custom_emojis.delete')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + = f.button safe_join([material_symbol('close'), t('admin.custom_emojis.delete')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } - if can?(:copy, :custom_emoji) && params[:local] != '1' - = f.button safe_join([fa_icon('copy'), t('admin.custom_emojis.copy')]), name: :copy, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } + = f.button safe_join([material_symbol('content_copy'), t('admin.custom_emojis.copy')]), name: :copy, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } - if params[:local] == '1' .batch-table__form.simple_form diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 8a80992785..8430dd3c4f 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -57,19 +57,19 @@ .dashboard__item = link_to admin_reports_path, class: 'dashboard__quick-access' do %span= t('admin.dashboard.pending_reports_html', count: @pending_reports_count) - = fa_icon 'chevron-right fw' + = material_symbol 'chevron_right' = link_to admin_accounts_path(status: 'pending'), class: 'dashboard__quick-access' do %span= t('admin.dashboard.pending_users_html', count: @pending_users_count) - = fa_icon 'chevron-right fw' + = material_symbol 'chevron_right' = link_to admin_trends_tags_path(status: 'pending_review'), class: 'dashboard__quick-access' do %span= t('admin.dashboard.pending_tags_html', count: @pending_tags_count) - = fa_icon 'chevron-right fw' + = material_symbol 'chevron_right' = link_to admin_disputes_appeals_path(status: 'pending'), class: 'dashboard__quick-access' do %span= t('admin.dashboard.pending_appeals_html', count: @pending_appeals_count) - = fa_icon 'chevron-right fw' + = material_symbol 'chevron_right' .dashboard__item = react_admin_component :dimension, dimension: 'sources', diff --git a/app/views/admin/email_domain_blocks/index.html.haml b/app/views/admin/email_domain_blocks/index.html.haml index 59036f899f..4fae6557a5 100644 --- a/app/views/admin/email_domain_blocks/index.html.haml +++ b/app/views/admin/email_domain_blocks/index.html.haml @@ -4,7 +4,7 @@ - content_for :heading_actions do = link_to t('admin.email_domain_blocks.add_new'), new_admin_email_domain_block_path, class: 'button' -= form_for(@form, url: batch_admin_email_domain_blocks_path) do |f| += form_with model: @form, url: batch_admin_email_domain_blocks_path do |f| = hidden_field_tag :page, params[:page] || 1 .batch-table @@ -12,7 +12,7 @@ %label.batch-table__toolbar__select.batch-checkbox-all = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - = f.button safe_join([fa_icon('times'), t('admin.email_domain_blocks.delete')]), + = f.button safe_join([material_symbol('close'), t('admin.email_domain_blocks.delete')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :delete, diff --git a/app/views/admin/export_domain_blocks/_domain_block.html.haml b/app/views/admin/export_domain_blocks/_domain_block.html.haml index cdce4fd28a..79cc5595ca 100644 --- a/app/views/admin/export_domain_blocks/_domain_block.html.haml +++ b/app/views/admin/export_domain_blocks/_domain_block.html.haml @@ -23,5 +23,5 @@ = f.object.public_comment - if existing_relationships ยท - = fa_icon 'warning fw' + = material_symbol 'warning' = t('admin.export_domain_blocks.import.existing_relationships_warning') diff --git a/app/views/admin/export_domain_blocks/import.html.haml b/app/views/admin/export_domain_blocks/import.html.haml index 48016a9abe..2b0d2c5eb3 100644 --- a/app/views/admin/export_domain_blocks/import.html.haml +++ b/app/views/admin/export_domain_blocks/import.html.haml @@ -6,13 +6,13 @@ - if defined?(@global_private_comment) && @global_private_comment.present? %p= t('admin.export_domain_blocks.import.private_comment_description_html', comment: @global_private_comment) -= form_for(@form, url: batch_admin_domain_blocks_path) do |f| += form_with model: @form, url: batch_admin_domain_blocks_path do |f| .batch-table .batch-table__toolbar %label.batch-table__toolbar__select.batch-checkbox-all = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - = f.button safe_join([fa_icon('copy'), t('admin.domain_blocks.import')]), + = f.button safe_join([material_symbol('content_copy'), t('admin.domain_blocks.import')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :save, diff --git a/app/views/admin/follow_recommendations/show.html.haml b/app/views/admin/follow_recommendations/show.html.haml index 9d23f9ba51..62cd315725 100644 --- a/app/views/admin/follow_recommendations/show.html.haml +++ b/app/views/admin/follow_recommendations/show.html.haml @@ -5,23 +5,23 @@ %hr.spacer/ -= form_tag admin_follow_recommendations_path, method: 'GET', class: 'simple_form' do += form_with url: admin_follow_recommendations_path, method: :get, class: :simple_form do |form| - RelationshipFilter::KEYS.each do |key| - = hidden_field_tag key, params[key] if params[key].present? + = form.hidden_field key, value: params[key] if params[key].present? .filters .filter-subset.filter-subset--with-select %strong= t('admin.follow_recommendations.language') .input.select.optional - = select_tag :language, - options_for_select(Trends.available_locales.map { |key| [standard_locale_name(key), key] }, @language) + = form.select :language, + options_for_select(Trends.available_locales.map { |key| [standard_locale_name(key), key] }, @language) .filter-subset %strong= t('admin.follow_recommendations.status') %ul %li= filter_link_to t('admin.accounts.moderation.active'), status: nil %li= filter_link_to t('admin.follow_recommendations.suppressed'), status: 'suppressed' -= form_for(@form, url: admin_follow_recommendations_path, method: :patch) do |f| += form_with model: @form, url: admin_follow_recommendations_path, method: :patch do |f| - RelationshipFilter::KEYS.each do |key| = hidden_field_tag key, params[key] if params[key].present? @@ -31,13 +31,13 @@ = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - if params[:status].blank? && can?(:suppress, :follow_recommendation) - = f.button safe_join([fa_icon('times'), t('admin.follow_recommendations.suppress')]), + = f.button safe_join([material_symbol('close'), t('admin.follow_recommendations.suppress')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :suppress, type: :submit - if params[:status] == 'suppressed' && can?(:unsuppress, :follow_recommendation) - = f.button safe_join([fa_icon('plus'), t('admin.follow_recommendations.unsuppress')]), + = f.button safe_join([material_symbol('add'), t('admin.follow_recommendations.unsuppress')]), class: 'table-action-link', name: :unsuppress, type: :submit diff --git a/app/views/admin/instances/index.html.haml b/app/views/admin/instances/index.html.haml index 7e43b4c538..b5f084f880 100644 --- a/app/views/admin/instances/index.html.haml +++ b/app/views/admin/instances/index.html.haml @@ -28,14 +28,17 @@ %li= filter_link_to t('admin.instances.delivery.unavailable'), availability: 'unavailable' - unless limited_federation_mode? - = form_tag admin_instances_url, method: 'GET', class: 'simple_form' do + = form_with url: admin_instances_url, method: :get, class: :simple_form do |form| .fields-group - InstanceFilter::KEYS.each do |key| - = hidden_field_tag key, params[key] if params[key].present? + = form.hidden_field key, value: params[key] if params[key].present? - %i(by_domain).each do |key| .input.string.optional - = text_field_tag key, params[key], class: 'string optional', placeholder: I18n.t("admin.instances.#{key}") + = form.text_field key, + value: params[key], + class: 'string optional', + placeholder: I18n.t("admin.instances.#{key}") .actions %button.button= t('admin.accounts.search') diff --git a/app/views/admin/instances/show.html.haml b/app/views/admin/instances/show.html.haml index 5bf4e899f3..d916203d0c 100644 --- a/app/views/admin/instances/show.html.haml +++ b/app/views/admin/instances/show.html.haml @@ -9,7 +9,7 @@ - if @instance.persisted? %p - = fa_icon 'info fw' + = material_symbol 'info' = t('admin.instances.totals_time_period_hint_html') .dashboard diff --git a/app/views/admin/invites/_invite.html.haml b/app/views/admin/invites/_invite.html.haml index e6ad9de34c..f9cd6003f3 100644 --- a/app/views/admin/invites/_invite.html.haml +++ b/app/views/admin/invites/_invite.html.haml @@ -12,7 +12,7 @@ - if invite.valid_for_use? %td - = fa_icon 'user fw' + = material_symbol 'person' = invite.uses = " / #{invite.max_uses}" unless invite.max_uses.nil? %td diff --git a/app/views/admin/ip_blocks/index.html.haml b/app/views/admin/ip_blocks/index.html.haml index f1d2b3dc47..207d23aeeb 100644 --- a/app/views/admin/ip_blocks/index.html.haml +++ b/app/views/admin/ip_blocks/index.html.haml @@ -5,7 +5,7 @@ - content_for :heading_actions do = link_to t('admin.ip_blocks.add_new'), new_admin_ip_block_path, class: 'button' -= form_for(@form, url: batch_admin_ip_blocks_path) do |f| += form_with model: @form, url: batch_admin_ip_blocks_path do |f| = hidden_field_tag :page, params[:page] || 1 .batch-table @@ -14,7 +14,7 @@ = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - if can?(:destroy, :ip_block) - = f.button safe_join([fa_icon('times'), t('admin.ip_blocks.delete')]), + = f.button safe_join([material_symbol('close'), t('admin.ip_blocks.delete')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :delete, diff --git a/app/views/admin/relationships/index.html.haml b/app/views/admin/relationships/index.html.haml index 8260430d80..83ffd139de 100644 --- a/app/views/admin/relationships/index.html.haml +++ b/app/views/admin/relationships/index.html.haml @@ -19,18 +19,18 @@ .back-link = link_to admin_account_path(@account.id) do - = fa_icon 'chevron-left fw' + = material_symbol 'chevron_left' = t('admin.statuses.back_to_account') %hr.spacer/ -= form_for(@form, url: batch_admin_accounts_path) do |f| += form_with model: @form, url: batch_admin_accounts_path do |f| .batch-table .batch-table__toolbar %label.batch-table__toolbar__select.batch-checkbox-all = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - = f.button safe_join([fa_icon('lock'), t('admin.accounts.perform_full_suspension')]), + = f.button safe_join([material_symbol('lock'), t('admin.accounts.perform_full_suspension')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :suspend, diff --git a/app/views/admin/relays/_relay.html.haml b/app/views/admin/relays/_relay.html.haml index f1dd2b2dd1..0960124ccf 100644 --- a/app/views/admin/relays/_relay.html.haml +++ b/app/views/admin/relays/_relay.html.haml @@ -4,7 +4,7 @@ %td - if relay.accepted? %span.positive-hint - = fa_icon('check') + = material_symbol('check')   = t 'admin.relays.enabled' - elsif relay.pending? @@ -13,7 +13,7 @@ = t 'admin.relays.pending' - else %span.negative-hint - = fa_icon('times') + = material_symbol('close')   = t 'admin.relays.disabled' %td diff --git a/app/views/admin/reports/_actions.html.haml b/app/views/admin/reports/_actions.html.haml index da9ac89315..5fb540931b 100644 --- a/app/views/admin/reports/_actions.html.haml +++ b/app/views/admin/reports/_actions.html.haml @@ -1,4 +1,4 @@ -= form_tag preview_admin_report_actions_path(report), method: :post do += form_with url: preview_admin_report_actions_path(report) do |form| .report-actions .report-actions__item .report-actions__item__button @@ -8,26 +8,36 @@ - if statuses.any? { |status| (status.with_media? || status.with_preview_card?) && !status.discarded? } .report-actions__item .report-actions__item__button - = button_tag t('admin.reports.mark_as_sensitive'), name: :mark_as_sensitive, class: 'button' + = form.button t('admin.reports.mark_as_sensitive'), + name: :mark_as_sensitive, + class: 'button' .report-actions__item__description = t('admin.reports.actions.mark_as_sensitive_description_html') .report-actions__item .report-actions__item__button - = button_tag t('admin.reports.delete_and_resolve'), name: :delete, class: 'button button--destructive' + = form.button t('admin.reports.delete_and_resolve'), + name: :delete, + class: 'button button--destructive' .report-actions__item__description = t('admin.reports.actions.delete_description_html') .report-actions__item .report-actions__item__button - = button_tag t('admin.accounts.silence'), name: :silence, class: 'button button--destructive' + = form.button t('admin.accounts.silence'), + name: :silence, + class: 'button button--destructive' .report-actions__item__description = t('admin.reports.actions.silence_description_html') .report-actions__item .report-actions__item__button - = button_tag t('admin.accounts.suspend'), name: :suspend, class: 'button button--destructive' + = form.button t('admin.accounts.suspend'), + name: :suspend, + class: 'button button--destructive' .report-actions__item__description = t('admin.reports.actions.suspend_description_html') .report-actions__item .report-actions__item__button - = link_to t('admin.accounts.custom'), new_admin_account_action_path(report.target_account_id, report_id: report.id), class: 'button' + = link_to t('admin.accounts.custom'), + new_admin_account_action_path(report.target_account_id, report_id: report.id), + class: 'button' .report-actions__item__description = t('admin.reports.actions.other_description_html') diff --git a/app/views/admin/reports/_header_card.html.haml b/app/views/admin/reports/_header_card.html.haml index e90e3f9c90..52e62b4499 100644 --- a/app/views/admin/reports/_header_card.html.haml +++ b/app/views/admin/reports/_header_card.html.haml @@ -16,7 +16,7 @@ %strong.emojify.p-name= display_name(report.target_account, custom_emojify: true) %span = acct(report.target_account) - = fa_icon('lock') if report.target_account.locked? + = material_symbol('lock') if report.target_account.locked? - if report.target_account.note.present? .account-card__bio.emojify = prerender_custom_emojis(account_bio_format(report.target_account), report.target_account.emojis) diff --git a/app/views/admin/reports/_status.html.haml b/app/views/admin/reports/_status.html.haml index 3775a1101c..66820f0a6e 100644 --- a/app/views/admin/reports/_status.html.haml +++ b/app/views/admin/reports/_status.html.haml @@ -37,5 +37,5 @@ = t("statuses.visibilities.#{status.visibility}") - if status.proper.sensitive? ยท - = fa_icon('eye-slash fw') + = material_symbol('visibility_off') = t('stream_entries.sensitive_content') diff --git a/app/views/admin/reports/actions/preview.html.haml b/app/views/admin/reports/actions/preview.html.haml index 8634bb215c..79c444453f 100644 --- a/app/views/admin/reports/actions/preview.html.haml +++ b/app/views/admin/reports/actions/preview.html.haml @@ -4,8 +4,8 @@ - content_for :page_title do = t('admin.reports.confirm_action', acct: target_acct) -= form_tag admin_report_actions_path(@report), class: 'simple_form', method: :post do - = hidden_field_tag :moderation_action, @moderation_action += form_with url: admin_report_actions_path(@report), class: :simple_form do |form| + = form.hidden_field :moderation_action, value: @moderation_action %p.hint= t("admin.reports.summary.action_preambles.#{@moderation_action}_html", acct: target_acct) %ul.hint @@ -30,7 +30,9 @@ %p= t "user_mailer.warning.explanation.#{warning_action}", instance: Rails.configuration.x.local_domain .fields-group - = text_area_tag :text, nil, placeholder: t('admin.reports.summary.warning_placeholder') + = form.text_area :text, + value: nil, + placeholder: t('admin.reports.summary.warning_placeholder') - unless @report.other? %p @@ -58,7 +60,7 @@ - status.ordered_media_attachments.each do |media_attachment| %abbr{ title: media_attachment.description } - = fa_icon 'link' + = material_symbol 'link' = media_attachment.file_file_name .strike-card__statuses-list__item__meta = link_to ActivityPub::TagManager.instance.url_for(status), target: '_blank', rel: 'noopener noreferrer' do @@ -75,4 +77,7 @@ .actions = link_to t('admin.reports.cancel'), admin_report_path(@report), class: 'button button-tertiary' - = button_tag t('admin.reports.confirm'), name: :confirm, class: 'button', type: :submit + = form.button t('admin.reports.confirm'), + name: :confirm, + class: 'button', + type: :submit diff --git a/app/views/admin/reports/index.html.haml b/app/views/admin/reports/index.html.haml index e2a9868aa5..dae2c1aa5b 100644 --- a/app/views/admin/reports/index.html.haml +++ b/app/views/admin/reports/index.html.haml @@ -14,14 +14,17 @@ %li= filter_link_to t('admin.accounts.location.local'), target_origin: 'local' %li= filter_link_to t('admin.accounts.location.remote'), target_origin: 'remote' -= form_tag admin_reports_url, method: 'GET', class: 'simple_form' do += form_with url: admin_reports_url, method: :get, class: :simple_form do |form| .fields-group - ReportFilter::KEYS.each do |key| - = hidden_field_tag key, params[key] if params[key].present? + = form.hidden_field key, value: params[key] if params[key].present? - %i(by_target_domain).each do |key| .input.string.optional - = text_field_tag key, params[key], class: 'string optional', placeholder: I18n.t("admin.reports.#{key}") + = form.text_field key, + value: params[key], + class: 'string optional', + placeholder: I18n.t("admin.reports.#{key}") .actions %button.button= t('admin.accounts.search') diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml index e37fa2590a..ca1edea0fe 100644 --- a/app/views/admin/reports/show.html.haml +++ b/app/views/admin/reports/show.html.haml @@ -41,18 +41,18 @@ %p = t 'admin.reports.statuses_description_html' โ€” - = link_to safe_join([fa_icon('plus'), t('admin.reports.add_to_report')]), + = link_to safe_join([material_symbol('add'), t('admin.reports.add_to_report')]), admin_account_statuses_path(@report.target_account_id, report_id: @report.id), class: 'table-action-link' -= form_for(@form, url: batch_admin_account_statuses_path(@report.target_account_id, report_id: @report.id)) do |f| += form_with model: @form, url: batch_admin_account_statuses_path(@report.target_account_id, report_id: @report.id) do |f| .batch-table .batch-table__toolbar %label.batch-table__toolbar__select.batch-checkbox-all = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - if !@statuses.empty? && @report.unresolved? - = f.button safe_join([fa_icon('times'), t('admin.statuses.batch.remove_from_report')]), name: :remove_from_report, class: 'table-action-link', type: :submit + = f.button safe_join([material_symbol('close'), t('admin.statuses.batch.remove_from_report')]), name: :remove_from_report, class: 'table-action-link', type: :submit .batch-table__body - if @statuses.empty? = nothing_here 'nothing-here--under-tabs' @@ -83,15 +83,17 @@ .report-notes = render @report_notes -= simple_form_for @report_note, url: admin_report_notes_path do |f| - = f.input :report_id, as: :hidden += simple_form_for @report_note, url: admin_report_notes_path do |form| + = form.input :report_id, as: :hidden + + = render 'shared/error_messages', object: @report_note .field-group - = f.input :content, placeholder: t('admin.reports.notes.placeholder'), rows: 6 + = form.input :content, input_html: { placeholder: t('admin.reports.notes.placeholder'), maxlength: ReportNote::CONTENT_SIZE_LIMIT, rows: 6, autofocus: @report_note.errors.any? } .actions - if @report.unresolved? - = f.button :button, t('admin.reports.notes.create_and_resolve'), name: :create_and_resolve, type: :submit + = form.button :button, t('admin.reports.notes.create_and_resolve'), name: :create_and_resolve, type: :submit - else - = f.button :button, t('admin.reports.notes.create_and_unresolve'), name: :create_and_unresolve, type: :submit - = f.button :button, t('admin.reports.notes.create'), type: :submit + = form.button :button, t('admin.reports.notes.create_and_unresolve'), name: :create_and_unresolve, type: :submit + = form.button :button, t('admin.reports.notes.create'), type: :submit diff --git a/app/views/admin/roles/_role.html.haml b/app/views/admin/roles/_role.html.haml index d6c6b62c81..fd37644c83 100644 --- a/app/views/admin/roles/_role.html.haml +++ b/app/views/admin/roles/_role.html.haml @@ -2,7 +2,7 @@ - if can?(:update, role) = link_to edit_admin_role_path(role), class: 'announcements-list__item__title' do %span.user-role{ class: "user-role-#{role.id}" } - = fa_icon 'users fw' + = material_symbol 'group' - if role.everyone? = t('admin.roles.everyone') @@ -11,7 +11,7 @@ - else %span.announcements-list__item__title %span.user-role{ class: "user-role-#{role.id}" } - = fa_icon 'users fw' + = material_symbol 'group' - if role.everyone? = t('admin.roles.everyone') diff --git a/app/views/admin/rules/_form.html.haml b/app/views/admin/rules/_form.html.haml new file mode 100644 index 0000000000..9fc54e2887 --- /dev/null +++ b/app/views/admin/rules/_form.html.haml @@ -0,0 +1,7 @@ +.fields-group + = form.input :text, + wrapper: :with_block_label + +.fields-group + = form.input :hint, + wrapper: :with_block_label diff --git a/app/views/admin/rules/edit.html.haml b/app/views/admin/rules/edit.html.haml index 77815588d2..9e3c915812 100644 --- a/app/views/admin/rules/edit.html.haml +++ b/app/views/admin/rules/edit.html.haml @@ -1,14 +1,10 @@ - content_for :page_title do = t('admin.rules.edit') -= simple_form_for @rule, url: admin_rule_path(@rule) do |f| += simple_form_for @rule, url: admin_rule_path(@rule) do |form| = render 'shared/error_messages', object: @rule - .fields-group - = f.input :text, wrapper: :with_block_label - - .fields-group - = f.input :hint, wrapper: :with_block_label + = render form .actions - = f.button :button, t('generic.save_changes'), type: :submit + = form.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/rules/index.html.haml b/app/views/admin/rules/index.html.haml index dd15ce03c0..5a2789edcf 100644 --- a/app/views/admin/rules/index.html.haml +++ b/app/views/admin/rules/index.html.haml @@ -6,17 +6,13 @@ %hr.spacer/ - if can? :create, :rule - = simple_form_for @rule, url: admin_rules_path do |f| + = simple_form_for @rule, url: admin_rules_path do |form| = render 'shared/error_messages', object: @rule - .fields-group - = f.input :text, wrapper: :with_block_label - - .fields-group - = f.input :hint, wrapper: :with_block_label + = render form .actions - = f.button :button, t('admin.rules.add_new'), type: :submit + = form.button :button, t('admin.rules.add_new'), type: :submit %hr.spacer/ diff --git a/app/views/admin/settings/branding/show.html.haml b/app/views/admin/settings/branding/show.html.haml index 769c0dafe8..71aac5ead1 100644 --- a/app/views/admin/settings/branding/show.html.haml +++ b/app/views/admin/settings/branding/show.html.haml @@ -40,5 +40,33 @@ = fa_icon 'trash fw' = t('admin.site_uploads.delete') + .fields-row + .fields-row__column.fields-row__column-6.fields-group + = f.input :favicon, + as: :file, + input_html: { accept: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].join(',') }, + wrapper: :with_block_label + + .fields-row__column.fields-row__column-6.fields-group + - if @admin_settings.favicon.persisted? + = image_tag @admin_settings.favicon.file.url('48'), class: 'fields-group__thumbnail' + = link_to admin_site_upload_path(@admin_settings.favicon), data: { method: :delete }, class: 'link-button link-button--destructive' do + = fa_icon 'trash fw' + = t('admin.site_uploads.delete') + + .fields-row + .fields-row__column.fields-row__column-6.fields-group + = f.input :app_icon, + as: :file, + input_html: { accept: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].join(',') }, + wrapper: :with_block_label + + .fields-row__column.fields-row__column-6.fields-group + - if @admin_settings.app_icon.persisted? + = image_tag @admin_settings.app_icon.file.url('48'), class: 'fields-group__thumbnail' + = link_to admin_site_upload_path(@admin_settings.app_icon), data: { method: :delete }, class: 'link-button link-button--destructive' do + = fa_icon 'trash fw' + = t('admin.site_uploads.delete') + .actions = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/settings/content_retention/show.html.haml b/app/views/admin/settings/content_retention/show.html.haml index 4b2ee572e3..57485c1108 100644 --- a/app/views/admin/settings/content_retention/show.html.haml +++ b/app/views/admin/settings/content_retention/show.html.haml @@ -14,14 +14,18 @@ = f.input :media_cache_retention_period, input_html: { pattern: '[0-9]+' }, wrapper: :with_block_label + = f.input :backups_retention_period, + input_html: { pattern: '[0-9]+' }, + wrapper: :with_block_label + + %h4= t('admin.settings.content_retention.danger_zone') + + .fields-group = f.input :content_cache_retention_period, hint: false, input_html: { pattern: '[0-9]+' }, warning_hint: t('simple_form.hints.form_admin_settings.content_cache_retention_period'), wrapper: :with_block_label - = f.input :backups_retention_period, - input_html: { pattern: '[0-9]+' }, - wrapper: :with_block_label .actions = f.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/settings/shared/_links.html.haml b/app/views/admin/settings/shared/_links.html.haml index 86513e9aaf..f9b41b5814 100644 --- a/app/views/admin/settings/shared/_links.html.haml +++ b/app/views/admin/settings/shared/_links.html.haml @@ -3,7 +3,7 @@ :ruby primary.item :branding, safe_join([fa_icon('pencil fw'), t('admin.settings.branding.title')]), admin_settings_branding_path primary.item :about, safe_join([fa_icon('file-text fw'), t('admin.settings.about.title')]), admin_settings_about_path - primary.item :registrations, safe_join([fa_icon('users fw'), t('admin.settings.registrations.title')]), admin_settings_registrations_path + primary.item :registrations, safe_join([material_symbol('group'), t('admin.settings.registrations.title')]), admin_settings_registrations_path primary.item :discovery, safe_join([fa_icon('search fw'), t('admin.settings.discovery.title')]), admin_settings_discovery_path primary.item :content_retention, safe_join([fa_icon('history fw'), t('admin.settings.content_retention.title')]), admin_settings_content_retention_path primary.item :appearance, safe_join([fa_icon('desktop fw'), t('admin.settings.appearance.title')]), admin_settings_appearance_path diff --git a/app/views/admin/status_edits/_status_edit.html.haml b/app/views/admin/status_edits/_status_edit.html.haml index 7254777213..0bec0159ee 100644 --- a/app/views/admin/status_edits/_status_edit.html.haml +++ b/app/views/admin/status_edits/_status_edit.html.haml @@ -26,5 +26,5 @@ - if status_edit.sensitive? ยท - = fa_icon('eye-slash fw') + = material_symbol('visibility_off') = t('stream_entries.sensitive_content') diff --git a/app/views/admin/statuses/index.html.haml b/app/views/admin/statuses/index.html.haml index 33a41bd365..b03b8ac51b 100644 --- a/app/views/admin/statuses/index.html.haml +++ b/app/views/admin/statuses/index.html.haml @@ -8,20 +8,20 @@ %strong= t('admin.statuses.media.title') %ul %li= filter_link_to t('generic.all'), media: nil, id: nil - %li= filter_link_to t('admin.statuses.with_media'), media: '1' + %li= filter_link_to t('admin.statuses.with_media'), media: true .back-link - if params[:report_id] = link_to admin_report_path(params[:report_id].to_i) do - = fa_icon 'chevron-left fw' + = material_symbol 'chevron_left' = t('admin.statuses.back_to_report') - else = link_to admin_account_path(@account.id) do - = fa_icon 'chevron-left fw' + = material_symbol 'chevron_left' = t('admin.statuses.back_to_account') %hr.spacer/ -= form_for(@status_batch_action, url: batch_admin_account_statuses_path(@account.id)) do |f| += form_with model: @status_batch_action, url: batch_admin_account_statuses_path(@account.id) do |f| = hidden_field_tag :page, params[:page] || 1 - Admin::StatusFilter::KEYS.each do |key| diff --git a/app/views/admin/tags/show.html.haml b/app/views/admin/tags/show.html.haml index 2e4424bec6..f2d87b54b0 100644 --- a/app/views/admin/tags/show.html.haml +++ b/app/views/admin/tags/show.html.haml @@ -52,26 +52,26 @@ = link_to admin_tag_path(@tag.id), class: ['dashboard__quick-access', @tag.usable? ? 'positive' : 'negative'] do - if @tag.usable? %span= t('admin.trends.tags.usable') - = fa_icon 'check fw' + = material_symbol 'check' - else %span= t('admin.trends.tags.not_usable') - = fa_icon 'lock fw' + = material_symbol 'lock' = link_to admin_tag_path(@tag.id), class: ['dashboard__quick-access', @tag.trendable? ? 'positive' : 'negative'] do - if @tag.trendable? %span= t('admin.trends.tags.trendable') - = fa_icon 'check fw' + = material_symbol 'check' - else %span= t('admin.trends.tags.not_trendable') - = fa_icon 'lock fw' + = material_symbol 'lock' = link_to admin_tag_path(@tag.id), class: ['dashboard__quick-access', @tag.listable? ? 'positive' : 'negative'] do - if @tag.listable? %span= t('admin.trends.tags.listable') - = fa_icon 'check fw' + = material_symbol 'check' - else %span= t('admin.trends.tags.not_listable') - = fa_icon 'lock fw' + = material_symbol 'lock' %hr.spacer/ diff --git a/app/views/admin/trends/links/index.html.haml b/app/views/admin/trends/links/index.html.haml index 965d2b2e56..647c24b1e9 100644 --- a/app/views/admin/trends/links/index.html.haml +++ b/app/views/admin/trends/links/index.html.haml @@ -5,17 +5,17 @@ %hr.spacer/ -= form_tag admin_trends_links_path, method: 'GET', class: 'simple_form' do += form_with url: admin_trends_links_path, method: :get, class: :simple_form do |form| - Trends::PreviewCardFilter::KEYS.each do |key| - = hidden_field_tag key, params[key] if params[key].present? + = form.hidden_field key, value: params[key] if params[key].present? .filters .filter-subset.filter-subset--with-select %strong= t('admin.follow_recommendations.language') .input.select.optional - = select_tag :locale, - options_for_select(@locales.map { |key| [standard_locale_name(key), key] }, params[:locale]), - include_blank: true + = form.select :locale, + options_for_select(@locales.map { |key| [standard_locale_name(key), key] }, params[:locale]), + include_blank: true .filter-subset %strong= t('admin.trends.trending') %ul @@ -24,9 +24,9 @@ .back-link = link_to admin_trends_links_preview_card_providers_path do = t('admin.trends.preview_card_providers.title') - = fa_icon 'chevron-right fw' + = material_symbol 'chevron_right' -= form_for(@form, url: batch_admin_trends_links_path) do |f| += form_with model: @form, url: batch_admin_trends_links_path do |f| = hidden_field_tag :page, params[:page] || 1 - Trends::PreviewCardFilter::KEYS.each do |key| @@ -37,22 +37,22 @@ %label.batch-table__toolbar__select.batch-checkbox-all = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - = f.button safe_join([fa_icon('check'), t('admin.trends.links.allow')]), + = f.button safe_join([material_symbol('check'), t('admin.trends.links.allow')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :approve, type: :submit - = f.button safe_join([fa_icon('check'), t('admin.trends.links.allow_provider')]), + = f.button safe_join([material_symbol('check'), t('admin.trends.links.allow_provider')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :approve_providers, type: :submit - = f.button safe_join([fa_icon('times'), t('admin.trends.links.disallow')]), + = f.button safe_join([material_symbol('close'), t('admin.trends.links.disallow')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :reject, type: :submit - = f.button safe_join([fa_icon('times'), t('admin.trends.links.disallow_provider')]), + = f.button safe_join([material_symbol('close'), t('admin.trends.links.disallow_provider')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :reject_providers, diff --git a/app/views/admin/trends/links/preview_card_providers/index.html.haml b/app/views/admin/trends/links/preview_card_providers/index.html.haml index c91822fb74..b43b8dfff9 100644 --- a/app/views/admin/trends/links/preview_card_providers/index.html.haml +++ b/app/views/admin/trends/links/preview_card_providers/index.html.haml @@ -15,12 +15,12 @@ %li= filter_link_to safe_join([t('admin.accounts.moderation.pending'), "(#{PreviewCardProvider.pending_review.count})"], ' '), status: 'pending_review' .back-link = link_to admin_trends_links_path do - = fa_icon 'chevron-left fw' + = material_symbol 'chevron_left' = t('admin.trends.links.title') %hr.spacer/ -= form_for(@form, url: batch_admin_trends_links_preview_card_providers_path) do |f| += form_with model: @form, url: batch_admin_trends_links_preview_card_providers_path do |f| = hidden_field_tag :page, params[:page] || 1 - Trends::PreviewCardProviderFilter::KEYS.each do |key| @@ -31,12 +31,12 @@ %label.batch-table__toolbar__select.batch-checkbox-all = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - = f.button safe_join([fa_icon('check'), t('admin.trends.allow')]), + = f.button safe_join([material_symbol('check'), t('admin.trends.allow')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :approve, type: :submit - = f.button safe_join([fa_icon('times'), t('admin.trends.disallow')]), + = f.button safe_join([material_symbol('close'), t('admin.trends.disallow')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :reject, diff --git a/app/views/admin/trends/statuses/_status.html.haml b/app/views/admin/trends/statuses/_status.html.haml index 095f3f2187..09547ff036 100644 --- a/app/views/admin/trends/statuses/_status.html.haml +++ b/app/views/admin/trends/statuses/_status.html.haml @@ -11,7 +11,7 @@ - status.ordered_media_attachments.each do |media_attachment| %abbr{ title: media_attachment.description } - = fa_icon 'link' + = material_symbol 'link' = media_attachment.file_file_name = t 'admin.trends.statuses.shared_by', diff --git a/app/views/admin/trends/statuses/index.html.haml b/app/views/admin/trends/statuses/index.html.haml index 0891d15fcf..4713f8c2ae 100644 --- a/app/views/admin/trends/statuses/index.html.haml +++ b/app/views/admin/trends/statuses/index.html.haml @@ -5,22 +5,24 @@ %hr.spacer/ -= form_tag admin_trends_statuses_path, method: 'GET', class: 'simple_form' do += form_with url: admin_trends_statuses_path, method: :get, class: :simple_form do |form| - Trends::StatusFilter::KEYS.each do |key| - = hidden_field_tag key, params[key] if params[key].present? + = form.hidden_field key, value: params[key] if params[key].present? .filters .filter-subset.filter-subset--with-select %strong= t('admin.follow_recommendations.language') .input.select.optional - = select_tag :locale, options_for_select(@locales.map { |key| [standard_locale_name(key), key] }, params[:locale]), include_blank: true + = form.select :locale, + options_for_select(@locales.map { |key| [standard_locale_name(key), key] }, params[:locale]), + include_blank: true .filter-subset %strong= t('admin.trends.trending') %ul %li= filter_link_to t('generic.all'), trending: nil %li= filter_link_to t('admin.trends.only_allowed'), trending: 'allowed' -= form_for(@form, url: batch_admin_trends_statuses_path) do |f| += form_with model: @form, url: batch_admin_trends_statuses_path do |f| = hidden_field_tag :page, params[:page] || 1 - Trends::StatusFilter::KEYS.each do |key| @@ -31,22 +33,22 @@ %label.batch-table__toolbar__select.batch-checkbox-all = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - = f.button safe_join([fa_icon('check'), t('admin.trends.statuses.allow')]), + = f.button safe_join([material_symbol('check'), t('admin.trends.statuses.allow')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :approve, type: :submit - = f.button safe_join([fa_icon('check'), t('admin.trends.statuses.allow_account')]), + = f.button safe_join([material_symbol('check'), t('admin.trends.statuses.allow_account')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :approve_accounts, type: :submit - = f.button safe_join([fa_icon('times'), t('admin.trends.statuses.disallow')]), + = f.button safe_join([material_symbol('close'), t('admin.trends.statuses.disallow')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :reject, type: :submit - = f.button safe_join([fa_icon('times'), t('admin.trends.statuses.disallow_account')]), + = f.button safe_join([material_symbol('close'), t('admin.trends.statuses.disallow_account')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :reject_accounts, diff --git a/app/views/admin/trends/tags/_tag.html.haml b/app/views/admin/trends/tags/_tag.html.haml index 70c7d8dbd4..8cc0d713b9 100644 --- a/app/views/admin/trends/tags/_tag.html.haml +++ b/app/views/admin/trends/tags/_tag.html.haml @@ -5,7 +5,7 @@ .batch-table__row__content.pending-account .pending-account__header = link_to admin_tag_path(tag.id) do - = fa_icon 'hashtag' + = material_symbol 'tag' = tag.display_name %br/ diff --git a/app/views/admin/trends/tags/index.html.haml b/app/views/admin/trends/tags/index.html.haml index effde7b0ec..3a44cf3a70 100644 --- a/app/views/admin/trends/tags/index.html.haml +++ b/app/views/admin/trends/tags/index.html.haml @@ -14,7 +14,7 @@ %li= filter_link_to t('admin.trends.rejected'), status: 'rejected' %li= filter_link_to safe_join([t('admin.accounts.moderation.pending'), "(#{Tag.pending_review.count})"], ' '), status: 'pending_review' -= form_for(@form, url: batch_admin_trends_tags_path) do |f| += form_with model: @form, url: batch_admin_trends_tags_path do |f| = hidden_field_tag :page, params[:page] || 1 - Trends::TagFilter::KEYS.each do |key| @@ -25,12 +25,12 @@ %label.batch-table__toolbar__select.batch-checkbox-all = check_box_tag :batch_checkbox_all, nil, false .batch-table__toolbar__actions - = f.button safe_join([fa_icon('check'), t('admin.trends.allow')]), + = f.button safe_join([material_symbol('check'), t('admin.trends.allow')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :approve, type: :submit - = f.button safe_join([fa_icon('times'), t('admin.trends.disallow')]), + = f.button safe_join([material_symbol('close'), t('admin.trends.disallow')]), class: 'table-action-link', data: { confirm: t('admin.reports.are_you_sure') }, name: :reject, diff --git a/app/views/admin/warning_presets/_form.html.haml b/app/views/admin/warning_presets/_form.html.haml new file mode 100644 index 0000000000..cba74163c5 --- /dev/null +++ b/app/views/admin/warning_presets/_form.html.haml @@ -0,0 +1,7 @@ +.fields-group + = form.input :title, + wrapper: :with_block_label + +.fields-group + = form.input :text, + wrapper: :with_block_label diff --git a/app/views/admin/warning_presets/edit.html.haml b/app/views/admin/warning_presets/edit.html.haml index b5c5107ef4..f0bd9c12ec 100644 --- a/app/views/admin/warning_presets/edit.html.haml +++ b/app/views/admin/warning_presets/edit.html.haml @@ -1,14 +1,10 @@ - content_for :page_title do = t('admin.warning_presets.edit_preset') -= simple_form_for @warning_preset, url: admin_warning_preset_path(@warning_preset) do |f| += simple_form_for @warning_preset, url: admin_warning_preset_path(@warning_preset) do |form| = render 'shared/error_messages', object: @warning_preset - .fields-group - = f.input :title, wrapper: :with_block_label - - .fields-group - = f.input :text, wrapper: :with_block_label + = render form .actions - = f.button :button, t('generic.save_changes'), type: :submit + = form.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/warning_presets/index.html.haml b/app/views/admin/warning_presets/index.html.haml index b26a13d966..22fee21050 100644 --- a/app/views/admin/warning_presets/index.html.haml +++ b/app/views/admin/warning_presets/index.html.haml @@ -2,17 +2,13 @@ = t('admin.warning_presets.title') - if can? :create, :account_warning_preset - = simple_form_for @warning_preset, url: admin_warning_presets_path do |f| + = simple_form_for @warning_preset, url: admin_warning_presets_path do |form| = render 'shared/error_messages', object: @warning_preset - .fields-group - = f.input :title, wrapper: :with_block_label - - .fields-group - = f.input :text, wrapper: :with_block_label + = render form .actions - = f.button :button, t('admin.warning_presets.add_new'), type: :submit + = form.button :button, t('admin.warning_presets.add_new'), type: :submit %hr.spacer/ diff --git a/app/views/admin/webhooks/edit.html.haml b/app/views/admin/webhooks/edit.html.haml index 2c2a7aa034..abc9bdfabc 100644 --- a/app/views/admin/webhooks/edit.html.haml +++ b/app/views/admin/webhooks/edit.html.haml @@ -2,6 +2,6 @@ = t('admin.webhooks.edit') = simple_form_for @webhook, url: admin_webhook_path(@webhook) do |form| - = render partial: 'form', object: form + = render form .actions = form.button :button, t('generic.save_changes'), type: :submit diff --git a/app/views/admin/webhooks/new.html.haml b/app/views/admin/webhooks/new.html.haml index f51b039ce8..50fcdc2be7 100644 --- a/app/views/admin/webhooks/new.html.haml +++ b/app/views/admin/webhooks/new.html.haml @@ -2,6 +2,6 @@ = t('admin.webhooks.new') = simple_form_for @webhook, url: admin_webhooks_path do |form| - = render partial: 'form', object: form + = render form .actions = form.button :button, t('admin.webhooks.add_new'), type: :submit diff --git a/app/views/auth/confirmations/captcha.html.haml b/app/views/auth/confirmations/captcha.html.haml index 964d0e63e7..035ac3a86a 100644 --- a/app/views/auth/confirmations/captcha.html.haml +++ b/app/views/auth/confirmations/captcha.html.haml @@ -1,11 +1,13 @@ - content_for :page_title do = t('auth.captcha_confirmation.title') -= form_tag auth_captcha_confirmation_url, method: 'POST', class: 'simple_form' do += form_with url: auth_captcha_confirmation_url, class: :simple_form do |form| = render 'auth/shared/progress', stage: 'confirm' - = hidden_field_tag :confirmation_token, params[:confirmation_token] - = hidden_field_tag :redirect_to_app, params[:redirect_to_app] + = form.hidden_field :confirmation_token, + value: params[:confirmation_token] + = form.hidden_field :redirect_to_app, + value: params[:redirect_to_app] %h1.title= t('auth.captcha_confirmation.title') %p.lead= t('auth.captcha_confirmation.hint_html') @@ -15,4 +17,6 @@ %p.lead= t('auth.captcha_confirmation.help_html', email: mail_to(Setting.site_contact_email, nil)) .actions - = button_tag t('challenge.confirm'), class: 'button', type: :submit + = form.button t('challenge.confirm'), + class: 'button', + type: :submit diff --git a/app/views/auth/registrations/edit.html.haml b/app/views/auth/registrations/edit.html.haml index 48350f478e..07d6c1af51 100644 --- a/app/views/auth/registrations/edit.html.haml +++ b/app/views/auth/registrations/edit.html.haml @@ -3,7 +3,7 @@ - if self_destruct? .flash-message.warning - = t('auth.status.self_destruct', domain: ENV.fetch('LOCAL_DOMAIN')) + = t('auth.status.self_destruct', domain: Rails.configuration.x.local_domain) - else = render partial: 'status', locals: { user: @user, strikes: @strikes } diff --git a/app/views/errors/self_destruct.html.haml b/app/views/errors/self_destruct.html.haml index 09b17a5a94..b9ff48f684 100644 --- a/app/views/errors/self_destruct.html.haml +++ b/app/views/errors/self_destruct.html.haml @@ -3,7 +3,7 @@ .simple_form %h1.title= t('self_destruct.title') - %p.lead= t('self_destruct.lead_html', domain: ENV.fetch('LOCAL_DOMAIN')) + %p.lead= t('self_destruct.lead_html', domain: Rails.configuration.x.local_domain) .form-footer %ul.no-list diff --git a/app/views/filters/_filter_fields.html.haml b/app/views/filters/_filter_fields.html.haml index 5b297a6a9e..0f4049ffb6 100644 --- a/app/views/filters/_filter_fields.html.haml +++ b/app/views/filters/_filter_fields.html.haml @@ -6,7 +6,7 @@ wrapper: :with_label .fields-row__column.fields-row__column-6.fields-group = f.input :expires_in, - collection: [30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week].map(&:to_i), + collection: CustomFilter::EXPIRATION_DURATIONS.map(&:to_i), include_blank: I18n.t('invites.expires_in_prompt'), label_method: ->(i) { I18n.t("invites.expires_in.#{i}") }, wrapper: :with_label diff --git a/app/views/filters/statuses/index.html.haml b/app/views/filters/statuses/index.html.haml index eaa39e170f..915ec59caf 100644 --- a/app/views/filters/statuses/index.html.haml +++ b/app/views/filters/statuses/index.html.haml @@ -13,7 +13,7 @@ %hr.spacer/ -= form_for(@status_filter_batch_action, url: batch_filter_statuses_path(@filter.id)) do |f| += form_with model: @status_filter_batch_action, url: batch_filter_statuses_path(@filter.id) do |f| = hidden_field_tag :page, params[:page] || 1 - Admin::StatusFilter::KEYS.each do |key| diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index f9c86aeb07..f022c3f516 100755 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -11,15 +11,14 @@ - if storage_host? %link{ rel: 'dns-prefetch', href: storage_host }/ - %link{ rel: 'icon', href: '/favicon.ico', type: 'image/x-icon' }/ + - SiteUpload::FAVICON_SIZES.each do |size| + %link{ rel: 'icon', sizes: "#{size}x#{size}", href: favicon_path(size.to_i) || frontend_asset_path("icons/favicon-#{size}x#{size}.png"), type: 'image/png' }/ - - %w(16 32 48).each do |size| - %link{ rel: 'icon', sizes: "#{size}x#{size}", href: frontend_asset_path("icons/favicon-#{size}x#{size}.png"), type: 'image/png' }/ + - SiteUpload::APPLE_ICON_SIZES.each do |size| + %link{ rel: 'apple-touch-icon', sizes: "#{size}x#{size}", href: app_icon_path(size.to_i) || frontend_asset_path("icons/apple-touch-icon-#{size}x#{size}.png") }/ - - %w(57 60 72 76 114 120 144 152 167 180 1024).each do |size| - %link{ rel: 'apple-touch-icon', sizes: "#{size}x#{size}", href: frontend_asset_path("icons/apple-touch-icon-#{size}x#{size}.png") }/ - - %link{ rel: 'mask-icon', href: frontend_asset_path('images/logo-symbol-icon.svg'), color: '#6364FF' }/ + - if use_mask_icon? + %link{ rel: 'mask-icon', href: frontend_asset_path('images/logo-symbol-icon.svg'), color: '#6364FF' }/ %link{ rel: 'manifest', href: manifest_path(format: :json) }/ = theme_color_tags current_theme %meta{ name: 'apple-mobile-web-app-capable', content: 'yes' }/ @@ -30,7 +29,7 @@ = theme_style_tags current_theme -# Needed for the wicg-inert polyfill. It needs to be on it's own