Merge pull request #2198 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changespull/59/head^2
commit
c18884de32
22
.eslintrc.js
22
.eslintrc.js
|
@ -27,6 +27,7 @@ module.exports = {
|
||||||
'import',
|
'import',
|
||||||
'promise',
|
'promise',
|
||||||
'@typescript-eslint',
|
'@typescript-eslint',
|
||||||
|
'formatjs',
|
||||||
],
|
],
|
||||||
|
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
|
@ -71,7 +72,7 @@ module.exports = {
|
||||||
'comma-style': ['warn', 'last'],
|
'comma-style': ['warn', 'last'],
|
||||||
'consistent-return': 'error',
|
'consistent-return': 'error',
|
||||||
'dot-notation': 'error',
|
'dot-notation': 'error',
|
||||||
eqeqeq: 'error',
|
eqeqeq: ['error', 'always', { 'null': 'ignore' }],
|
||||||
indent: ['warn', 2],
|
indent: ['warn', 2],
|
||||||
'jsx-quotes': ['error', 'prefer-single'],
|
'jsx-quotes': ['error', 'prefer-single'],
|
||||||
'no-case-declarations': 'off',
|
'no-case-declarations': 'off',
|
||||||
|
@ -218,6 +219,25 @@ module.exports = {
|
||||||
'promise/no-callback-in-promise': 'off',
|
'promise/no-callback-in-promise': 'off',
|
||||||
'promise/no-nesting': 'off',
|
'promise/no-nesting': 'off',
|
||||||
'promise/no-promise-in-callback': 'off',
|
'promise/no-promise-in-callback': 'off',
|
||||||
|
|
||||||
|
'formatjs/blocklist-elements': 'error',
|
||||||
|
'formatjs/enforce-default-message': ['error', 'literal'],
|
||||||
|
'formatjs/enforce-description': 'off', // description values not currently used
|
||||||
|
'formatjs/enforce-id': 'off', // Explicit IDs are used in the project
|
||||||
|
'formatjs/enforce-placeholders': 'off', // Issues in short_number.jsx
|
||||||
|
'formatjs/enforce-plural-rules': 'error',
|
||||||
|
'formatjs/no-camel-case': 'off', // disabledAccount is only non-conforming
|
||||||
|
'formatjs/no-complex-selectors': 'error',
|
||||||
|
'formatjs/no-emoji': 'error',
|
||||||
|
'formatjs/no-id': 'off', // IDs are used for translation keys
|
||||||
|
'formatjs/no-invalid-icu': 'error',
|
||||||
|
'formatjs/no-literal-string-in-jsx': 'off', // Should be looked at, but mainly flagging punctuation outside of strings
|
||||||
|
'formatjs/no-multiple-plurals': 'off', // Only used by hashtag.jsx
|
||||||
|
'formatjs/no-multiple-whitespaces': 'error',
|
||||||
|
'formatjs/no-offset': 'error',
|
||||||
|
'formatjs/no-useless-message': 'error',
|
||||||
|
'formatjs/prefer-formatted-message': 'error',
|
||||||
|
'formatjs/prefer-pound-in-plural': 'error',
|
||||||
},
|
},
|
||||||
|
|
||||||
overrides: [
|
overrides: [
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
name: Build nightly container image
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 2 * * *' # run at 2 AM UTC
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-nightly-image:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: hadolint/hadolint-action@v3.1.0
|
||||||
|
- uses: docker/setup-qemu-action@v2
|
||||||
|
- uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
|
- name: Log in to the Github Container registry
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- uses: docker/metadata-action@v4
|
||||||
|
id: meta
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
ghcr.io/mastodon/mastodon
|
||||||
|
flavor: |
|
||||||
|
latest=auto
|
||||||
|
tags: |
|
||||||
|
type=raw,value=nightly
|
||||||
|
type=schedule,pattern=nightly-{{date 'YYYY-MM-DD' tz='Etc/UTC'}}
|
||||||
|
labels: |
|
||||||
|
org.opencontainers.image.description=Nightly build image used for testing purposes
|
||||||
|
|
||||||
|
- uses: docker/build-push-action@v4
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
provenance: false
|
||||||
|
builder: ${{ steps.buildx.outputs.name }}
|
||||||
|
push: ${{ github.repository == 'mastodon/mastodon' && github.event_name != 'pull_request' }}
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha,mode=max
|
|
@ -104,7 +104,6 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
ruby-version:
|
ruby-version:
|
||||||
- '2.7'
|
|
||||||
- '3.0'
|
- '3.0'
|
||||||
- '3.1'
|
- '3.1'
|
||||||
- '.ruby-version'
|
- '.ruby-version'
|
||||||
|
@ -136,10 +135,6 @@ jobs:
|
||||||
ruby-version: ${{ matrix.ruby-version}}
|
ruby-version: ${{ matrix.ruby-version}}
|
||||||
bundler-cache: true
|
bundler-cache: true
|
||||||
|
|
||||||
- name: Update system gems
|
|
||||||
if: matrix.ruby-version == '2.7'
|
|
||||||
run: gem update --system
|
|
||||||
|
|
||||||
- name: Load database schema
|
- name: Load database schema
|
||||||
run: './bin/rails db:create db:schema:load db:seed'
|
run: './bin/rails db:create db:schema:load db:seed'
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ require:
|
||||||
- rubocop-capybara
|
- rubocop-capybara
|
||||||
|
|
||||||
AllCops:
|
AllCops:
|
||||||
TargetRubyVersion: 2.7 # Set to minimum supported version of CI
|
TargetRubyVersion: 3.0 # Set to minimum supported version of CI
|
||||||
DisplayCopNames: true
|
DisplayCopNames: true
|
||||||
DisplayStyleGuide: true
|
DisplayStyleGuide: true
|
||||||
ExtraDetails: true
|
ExtraDetails: true
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# This configuration was generated by
|
# This configuration was generated by
|
||||||
# `rubocop --auto-gen-config --auto-gen-only-exclude --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp`
|
# `rubocop --auto-gen-config --auto-gen-only-exclude --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp`
|
||||||
# using RuboCop version 1.48.1.
|
# using RuboCop version 1.50.2.
|
||||||
# The point is for the user to remove these configuration records
|
# The point is for the user to remove these configuration records
|
||||||
# one by one as the offenses are removed from the code base.
|
# one by one as the offenses are removed from the code base.
|
||||||
# Note that changes in the inspected code, or installation of new
|
# Note that changes in the inspected code, or installation of new
|
||||||
|
@ -132,7 +132,6 @@ Lint/DuplicateBranch:
|
||||||
Lint/EmptyBlock:
|
Lint/EmptyBlock:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'spec/controllers/api/v2/search_controller_spec.rb'
|
- 'spec/controllers/api/v2/search_controller_spec.rb'
|
||||||
- 'spec/controllers/application_controller_spec.rb'
|
|
||||||
- 'spec/fabricators/access_token_fabricator.rb'
|
- 'spec/fabricators/access_token_fabricator.rb'
|
||||||
- 'spec/fabricators/conversation_fabricator.rb'
|
- 'spec/fabricators/conversation_fabricator.rb'
|
||||||
- 'spec/fabricators/system_key_fabricator.rb'
|
- 'spec/fabricators/system_key_fabricator.rb'
|
||||||
|
@ -174,11 +173,6 @@ Lint/EmptyClass:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'spec/controllers/api/base_controller_spec.rb'
|
- 'spec/controllers/api/base_controller_spec.rb'
|
||||||
|
|
||||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
||||||
Lint/NonDeterministicRequireOrder:
|
|
||||||
Exclude:
|
|
||||||
- 'spec/rails_helper.rb'
|
|
||||||
|
|
||||||
Lint/NonLocalExitFromIterator:
|
Lint/NonLocalExitFromIterator:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'app/helpers/jsonld_helper.rb'
|
- 'app/helpers/jsonld_helper.rb'
|
||||||
|
@ -251,7 +245,6 @@ Metrics/ModuleLength:
|
||||||
- 'app/controllers/concerns/signature_verification.rb'
|
- 'app/controllers/concerns/signature_verification.rb'
|
||||||
- 'app/helpers/application_helper.rb'
|
- 'app/helpers/application_helper.rb'
|
||||||
- 'app/helpers/jsonld_helper.rb'
|
- 'app/helpers/jsonld_helper.rb'
|
||||||
- 'app/helpers/statuses_helper.rb'
|
|
||||||
- 'app/models/concerns/account_interactions.rb'
|
- 'app/models/concerns/account_interactions.rb'
|
||||||
- 'app/models/concerns/has_user_settings.rb'
|
- 'app/models/concerns/has_user_settings.rb'
|
||||||
|
|
||||||
|
@ -370,6 +363,7 @@ Performance/MethodObjectAsBlock:
|
||||||
- 'spec/models/export_spec.rb'
|
- 'spec/models/export_spec.rb'
|
||||||
|
|
||||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||||
|
# Configuration parameters: AllowRegexpMatch.
|
||||||
Performance/RedundantEqualityComparisonBlock:
|
Performance/RedundantEqualityComparisonBlock:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'spec/requests/link_headers_spec.rb'
|
- 'spec/requests/link_headers_spec.rb'
|
||||||
|
@ -699,7 +693,6 @@ RSpec/HookArgument:
|
||||||
RSpec/InstanceVariable:
|
RSpec/InstanceVariable:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'spec/controllers/api/v1/streaming_controller_spec.rb'
|
- 'spec/controllers/api/v1/streaming_controller_spec.rb'
|
||||||
- 'spec/controllers/application_controller_spec.rb'
|
|
||||||
- 'spec/controllers/auth/confirmations_controller_spec.rb'
|
- 'spec/controllers/auth/confirmations_controller_spec.rb'
|
||||||
- 'spec/controllers/auth/passwords_controller_spec.rb'
|
- 'spec/controllers/auth/passwords_controller_spec.rb'
|
||||||
- 'spec/controllers/auth/sessions_controller_spec.rb'
|
- 'spec/controllers/auth/sessions_controller_spec.rb'
|
||||||
|
@ -753,7 +746,6 @@ RSpec/LetSetup:
|
||||||
- 'spec/controllers/following_accounts_controller_spec.rb'
|
- 'spec/controllers/following_accounts_controller_spec.rb'
|
||||||
- 'spec/controllers/oauth/authorized_applications_controller_spec.rb'
|
- 'spec/controllers/oauth/authorized_applications_controller_spec.rb'
|
||||||
- 'spec/controllers/oauth/tokens_controller_spec.rb'
|
- 'spec/controllers/oauth/tokens_controller_spec.rb'
|
||||||
- 'spec/controllers/tags_controller_spec.rb'
|
|
||||||
- 'spec/lib/activitypub/activity/delete_spec.rb'
|
- 'spec/lib/activitypub/activity/delete_spec.rb'
|
||||||
- 'spec/lib/vacuum/preview_cards_vacuum_spec.rb'
|
- 'spec/lib/vacuum/preview_cards_vacuum_spec.rb'
|
||||||
- 'spec/models/account_spec.rb'
|
- 'spec/models/account_spec.rb'
|
||||||
|
@ -780,29 +772,6 @@ RSpec/LetSetup:
|
||||||
- 'spec/workers/scheduler/accounts_statuses_cleanup_scheduler_spec.rb'
|
- 'spec/workers/scheduler/accounts_statuses_cleanup_scheduler_spec.rb'
|
||||||
- 'spec/workers/scheduler/user_cleanup_scheduler_spec.rb'
|
- 'spec/workers/scheduler/user_cleanup_scheduler_spec.rb'
|
||||||
|
|
||||||
# This cop supports safe autocorrection (--autocorrect).
|
|
||||||
RSpec/MatchArray:
|
|
||||||
Exclude:
|
|
||||||
- 'spec/controllers/activitypub/followers_synchronizations_controller_spec.rb'
|
|
||||||
- 'spec/controllers/admin/export_domain_blocks_controller_spec.rb'
|
|
||||||
- 'spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb'
|
|
||||||
- 'spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb'
|
|
||||||
- 'spec/controllers/api/v1/accounts/statuses_controller_spec.rb'
|
|
||||||
- 'spec/controllers/api/v1/bookmarks_controller_spec.rb'
|
|
||||||
- 'spec/controllers/api/v1/favourites_controller_spec.rb'
|
|
||||||
- 'spec/controllers/api/v1/reports_controller_spec.rb'
|
|
||||||
- 'spec/controllers/api/v1/statuses/favourited_by_accounts_controller_spec.rb'
|
|
||||||
- 'spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb'
|
|
||||||
- 'spec/models/account_filter_spec.rb'
|
|
||||||
- 'spec/models/account_spec.rb'
|
|
||||||
- 'spec/models/account_statuses_cleanup_policy_spec.rb'
|
|
||||||
- 'spec/models/custom_emoji_filter_spec.rb'
|
|
||||||
- 'spec/models/status_spec.rb'
|
|
||||||
- 'spec/models/user_spec.rb'
|
|
||||||
- 'spec/presenters/familiar_followers_presenter_spec.rb'
|
|
||||||
- 'spec/services/activitypub/fetch_featured_collection_service_spec.rb'
|
|
||||||
- 'spec/services/update_status_service_spec.rb'
|
|
||||||
|
|
||||||
RSpec/MessageChain:
|
RSpec/MessageChain:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'spec/controllers/api/v1/media_controller_spec.rb'
|
- 'spec/controllers/api/v1/media_controller_spec.rb'
|
||||||
|
@ -842,7 +811,6 @@ RSpec/MissingExampleGroupArgument:
|
||||||
- 'spec/controllers/api/v1/admin/account_actions_controller_spec.rb'
|
- 'spec/controllers/api/v1/admin/account_actions_controller_spec.rb'
|
||||||
- 'spec/controllers/api/v1/admin/domain_allows_controller_spec.rb'
|
- 'spec/controllers/api/v1/admin/domain_allows_controller_spec.rb'
|
||||||
- 'spec/controllers/api/v1/statuses_controller_spec.rb'
|
- 'spec/controllers/api/v1/statuses_controller_spec.rb'
|
||||||
- 'spec/controllers/application_controller_spec.rb'
|
|
||||||
- 'spec/controllers/auth/registrations_controller_spec.rb'
|
- 'spec/controllers/auth/registrations_controller_spec.rb'
|
||||||
- 'spec/features/log_in_spec.rb'
|
- 'spec/features/log_in_spec.rb'
|
||||||
- 'spec/lib/activitypub/activity/undo_spec.rb'
|
- 'spec/lib/activitypub/activity/undo_spec.rb'
|
||||||
|
@ -1225,9 +1193,6 @@ Rails/ActiveRecordCallbacksOrder:
|
||||||
Rails/ApplicationController:
|
Rails/ApplicationController:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'app/controllers/health_controller.rb'
|
- 'app/controllers/health_controller.rb'
|
||||||
- 'app/controllers/well_known/host_meta_controller.rb'
|
|
||||||
- 'app/controllers/well_known/nodeinfo_controller.rb'
|
|
||||||
- 'app/controllers/well_known/webfinger_controller.rb'
|
|
||||||
|
|
||||||
# Configuration parameters: Database, Include.
|
# Configuration parameters: Database, Include.
|
||||||
# SupportedDatabases: mysql, postgresql
|
# SupportedDatabases: mysql, postgresql
|
||||||
|
@ -1405,14 +1370,6 @@ Rails/HasManyOrHasOneDependent:
|
||||||
- 'app/models/user.rb'
|
- 'app/models/user.rb'
|
||||||
- 'app/models/web/push_subscription.rb'
|
- 'app/models/web/push_subscription.rb'
|
||||||
|
|
||||||
# Configuration parameters: Include.
|
|
||||||
# Include: app/helpers/**/*.rb
|
|
||||||
Rails/HelperInstanceVariable:
|
|
||||||
Exclude:
|
|
||||||
- 'app/helpers/application_helper.rb'
|
|
||||||
- 'app/helpers/instance_helper.rb'
|
|
||||||
- 'app/helpers/jsonld_helper.rb'
|
|
||||||
|
|
||||||
# This cop supports safe autocorrection (--autocorrect).
|
# This cop supports safe autocorrection (--autocorrect).
|
||||||
# Configuration parameters: Include.
|
# Configuration parameters: Include.
|
||||||
# Include: spec/**/*, test/**/*
|
# Include: spec/**/*, test/**/*
|
||||||
|
@ -1502,15 +1459,6 @@ Rails/RakeEnvironment:
|
||||||
- 'lib/tasks/repo.rake'
|
- 'lib/tasks/repo.rake'
|
||||||
- 'lib/tasks/statistics.rake'
|
- 'lib/tasks/statistics.rake'
|
||||||
|
|
||||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
||||||
# Configuration parameters: Include.
|
|
||||||
# Include: spec/controllers/**/*.rb, spec/requests/**/*.rb, test/controllers/**/*.rb, test/integration/**/*.rb
|
|
||||||
Rails/ResponseParsedBody:
|
|
||||||
Exclude:
|
|
||||||
- 'spec/controllers/follower_accounts_controller_spec.rb'
|
|
||||||
- 'spec/controllers/following_accounts_controller_spec.rb'
|
|
||||||
- 'spec/controllers/settings/two_factor_authentication/webauthn_credentials_controller_spec.rb'
|
|
||||||
|
|
||||||
# Configuration parameters: Include.
|
# Configuration parameters: Include.
|
||||||
# Include: db/**/*.rb
|
# Include: db/**/*.rb
|
||||||
Rails/ReversibleMigration:
|
Rails/ReversibleMigration:
|
||||||
|
@ -2256,16 +2204,11 @@ Style/MapToHash:
|
||||||
# SupportedStyles: literals, strict
|
# SupportedStyles: literals, strict
|
||||||
Style/MutableConstant:
|
Style/MutableConstant:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'app/lib/link_details_extractor.rb'
|
|
||||||
- 'app/models/account.rb'
|
- 'app/models/account.rb'
|
||||||
- 'app/models/custom_emoji.rb'
|
|
||||||
- 'app/models/tag.rb'
|
- 'app/models/tag.rb'
|
||||||
- 'app/services/account_search_service.rb'
|
|
||||||
- 'app/services/delete_account_service.rb'
|
- 'app/services/delete_account_service.rb'
|
||||||
- 'app/services/fetch_link_card_service.rb'
|
|
||||||
- 'app/services/resolve_url_service.rb'
|
|
||||||
- 'config/initializers/twitter_regex.rb'
|
- 'config/initializers/twitter_regex.rb'
|
||||||
- 'lib/mastodon/snowflake.rb'
|
- 'lib/mastodon/migration_warning.rb'
|
||||||
- 'spec/controllers/api/base_controller_spec.rb'
|
- 'spec/controllers/api/base_controller_spec.rb'
|
||||||
|
|
||||||
# This cop supports safe autocorrection (--autocorrect).
|
# This cop supports safe autocorrection (--autocorrect).
|
||||||
|
@ -2273,12 +2216,6 @@ Style/NilLambda:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'config/initializers/paperclip.rb'
|
- 'config/initializers/paperclip.rb'
|
||||||
|
|
||||||
# This cop supports safe autocorrection (--autocorrect).
|
|
||||||
# Configuration parameters: MinDigits, Strict, AllowedNumbers, AllowedPatterns.
|
|
||||||
Style/NumericLiterals:
|
|
||||||
Exclude:
|
|
||||||
- 'config/initializers/strong_migrations.rb'
|
|
||||||
|
|
||||||
# Configuration parameters: AllowedMethods.
|
# Configuration parameters: AllowedMethods.
|
||||||
# AllowedMethods: respond_to_missing?
|
# AllowedMethods: respond_to_missing?
|
||||||
Style/OptionalBooleanParameter:
|
Style/OptionalBooleanParameter:
|
||||||
|
@ -2388,7 +2325,6 @@ Style/Semicolon:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'spec/services/activitypub/process_status_update_service_spec.rb'
|
- 'spec/services/activitypub/process_status_update_service_spec.rb'
|
||||||
- 'spec/validators/blacklisted_email_validator_spec.rb'
|
- 'spec/validators/blacklisted_email_validator_spec.rb'
|
||||||
- 'spec/workers/scheduler/accounts_statuses_cleanup_scheduler_spec.rb'
|
|
||||||
|
|
||||||
# This cop supports safe autocorrection (--autocorrect).
|
# This cop supports safe autocorrection (--autocorrect).
|
||||||
# Configuration parameters: EnforcedStyle.
|
# Configuration parameters: EnforcedStyle.
|
||||||
|
|
15
Gemfile
15
Gemfile
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
ruby '>= 2.7.0', '< 3.3.0'
|
ruby '>= 3.0.0'
|
||||||
|
|
||||||
gem 'pkg-config', '~> 1.5'
|
gem 'pkg-config', '~> 1.5'
|
||||||
|
|
||||||
|
@ -9,10 +9,10 @@ gem 'puma', '~> 6.2'
|
||||||
gem 'rails', '~> 6.1.7'
|
gem 'rails', '~> 6.1.7'
|
||||||
gem 'sprockets', '~> 3.7.2'
|
gem 'sprockets', '~> 3.7.2'
|
||||||
gem 'thor', '~> 1.2'
|
gem 'thor', '~> 1.2'
|
||||||
gem 'rack', '~> 2.2.6'
|
gem 'rack', '~> 2.2.7'
|
||||||
|
|
||||||
gem 'haml-rails', '~>2.0'
|
gem 'haml-rails', '~>2.0'
|
||||||
gem 'pg', '~> 1.4'
|
gem 'pg', '~> 1.5'
|
||||||
gem 'makara', '~> 0.5'
|
gem 'makara', '~> 0.5'
|
||||||
gem 'pghero'
|
gem 'pghero'
|
||||||
gem 'dotenv-rails', '~> 2.8'
|
gem 'dotenv-rails', '~> 2.8'
|
||||||
|
@ -30,7 +30,10 @@ gem 'browser'
|
||||||
gem 'charlock_holmes', '~> 0.7.7'
|
gem 'charlock_holmes', '~> 0.7.7'
|
||||||
gem 'chewy', '~> 7.3'
|
gem 'chewy', '~> 7.3'
|
||||||
gem 'devise', '~> 4.9'
|
gem 'devise', '~> 4.9'
|
||||||
gem 'devise-two-factor', '~> 4.0'
|
# The below `v4.x` branch allows attr_encrypted 4.x, which is required for Rails 7.
|
||||||
|
# Once a new gem version is pushed, we can go back to released gem and off of github branch.
|
||||||
|
gem 'devise-two-factor', github: 'tinfoil/devise-two-factor', branch: 'v4.x'
|
||||||
|
gem 'attr_encrypted', '~> 4.0'
|
||||||
|
|
||||||
group :pam_authentication, optional: true do
|
group :pam_authentication, optional: true do
|
||||||
gem 'devise_pam_authenticatable2', '~> 9.2'
|
gem 'devise_pam_authenticatable2', '~> 9.2'
|
||||||
|
@ -76,7 +79,7 @@ gem 'redcarpet', '~> 3.6'
|
||||||
gem 'redis', '~> 4.5', require: ['redis', 'redis/connection/hiredis']
|
gem 'redis', '~> 4.5', require: ['redis', 'redis/connection/hiredis']
|
||||||
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
|
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
|
||||||
gem 'rqrcode', '~> 2.1'
|
gem 'rqrcode', '~> 2.1'
|
||||||
gem 'ruby-progressbar', '~> 1.11'
|
gem 'ruby-progressbar', '~> 1.13'
|
||||||
gem 'sanitize', '~> 6.0'
|
gem 'sanitize', '~> 6.0'
|
||||||
gem 'scenic', '~> 1.7'
|
gem 'scenic', '~> 1.7'
|
||||||
gem 'sidekiq', '~> 6.5'
|
gem 'sidekiq', '~> 6.5'
|
||||||
|
@ -121,7 +124,7 @@ group :test do
|
||||||
gem 'capybara', '~> 3.39'
|
gem 'capybara', '~> 3.39'
|
||||||
gem 'climate_control'
|
gem 'climate_control'
|
||||||
gem 'faker', '~> 3.2'
|
gem 'faker', '~> 3.2'
|
||||||
gem 'json-schema', '~> 3.0'
|
gem 'json-schema', '~> 4.0'
|
||||||
gem 'rack-test', '~> 2.1'
|
gem 'rack-test', '~> 2.1'
|
||||||
gem 'rails-controller-testing', '~> 1.0'
|
gem 'rails-controller-testing', '~> 1.0'
|
||||||
gem 'rspec_junit_formatter', '~> 0.6'
|
gem 'rspec_junit_formatter', '~> 0.6'
|
||||||
|
|
91
Gemfile.lock
91
Gemfile.lock
|
@ -27,6 +27,18 @@ GIT
|
||||||
rails-settings-cached (0.6.6)
|
rails-settings-cached (0.6.6)
|
||||||
rails (>= 4.2.0)
|
rails (>= 4.2.0)
|
||||||
|
|
||||||
|
GIT
|
||||||
|
remote: https://github.com/tinfoil/devise-two-factor.git
|
||||||
|
revision: e685f91ce62d036259885fbe31fcb4fa930bcfcb
|
||||||
|
branch: v4.x
|
||||||
|
specs:
|
||||||
|
devise-two-factor (4.0.2)
|
||||||
|
activesupport (< 7.1)
|
||||||
|
attr_encrypted (>= 1.3, < 5, != 2)
|
||||||
|
devise (~> 4.0)
|
||||||
|
railties (< 7.1)
|
||||||
|
rotp (~> 6.0)
|
||||||
|
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
|
@ -104,12 +116,12 @@ GEM
|
||||||
activerecord (>= 3.2, < 8.0)
|
activerecord (>= 3.2, < 8.0)
|
||||||
rake (>= 10.4, < 14.0)
|
rake (>= 10.4, < 14.0)
|
||||||
ast (2.4.2)
|
ast (2.4.2)
|
||||||
attr_encrypted (3.1.0)
|
attr_encrypted (4.0.0)
|
||||||
encryptor (~> 3.0.0)
|
encryptor (~> 3.0.0)
|
||||||
attr_required (1.0.1)
|
attr_required (1.0.1)
|
||||||
awrence (1.2.1)
|
awrence (1.2.1)
|
||||||
aws-eventstream (1.2.0)
|
aws-eventstream (1.2.0)
|
||||||
aws-partitions (1.743.0)
|
aws-partitions (1.752.0)
|
||||||
aws-sdk-core (3.171.0)
|
aws-sdk-core (3.171.0)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
aws-partitions (~> 1, >= 1.651.0)
|
aws-partitions (~> 1, >= 1.651.0)
|
||||||
|
@ -118,7 +130,7 @@ GEM
|
||||||
aws-sdk-kms (1.63.0)
|
aws-sdk-kms (1.63.0)
|
||||||
aws-sdk-core (~> 3, >= 3.165.0)
|
aws-sdk-core (~> 3, >= 3.165.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.120.1)
|
aws-sdk-s3 (1.121.0)
|
||||||
aws-sdk-core (~> 3, >= 3.165.0)
|
aws-sdk-core (~> 3, >= 3.165.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.4)
|
aws-sigv4 (~> 1.4)
|
||||||
|
@ -142,7 +154,7 @@ GEM
|
||||||
blurhash (0.1.7)
|
blurhash (0.1.7)
|
||||||
bootsnap (1.16.0)
|
bootsnap (1.16.0)
|
||||||
msgpack (~> 1.2)
|
msgpack (~> 1.2)
|
||||||
brakeman (5.4.0)
|
brakeman (5.4.1)
|
||||||
browser (5.3.1)
|
browser (5.3.1)
|
||||||
brpoplpush-redis_script (0.1.3)
|
brpoplpush-redis_script (0.1.3)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.5)
|
concurrent-ruby (~> 1.0, >= 1.0.5)
|
||||||
|
@ -156,7 +168,7 @@ GEM
|
||||||
i18n
|
i18n
|
||||||
rake (>= 10.0.0)
|
rake (>= 10.0.0)
|
||||||
sshkit (>= 1.9.0)
|
sshkit (>= 1.9.0)
|
||||||
capistrano-bundler (2.0.1)
|
capistrano-bundler (2.1.0)
|
||||||
capistrano (~> 3.1)
|
capistrano (~> 3.1)
|
||||||
capistrano-rails (1.6.2)
|
capistrano-rails (1.6.2)
|
||||||
capistrano (~> 3.1)
|
capistrano (~> 3.1)
|
||||||
|
@ -179,7 +191,7 @@ GEM
|
||||||
activesupport
|
activesupport
|
||||||
cbor (0.5.9.6)
|
cbor (0.5.9.6)
|
||||||
charlock_holmes (0.7.7)
|
charlock_holmes (0.7.7)
|
||||||
chewy (7.3.0)
|
chewy (7.3.2)
|
||||||
activesupport (>= 5.2)
|
activesupport (>= 5.2)
|
||||||
elasticsearch (>= 7.12.0, < 7.14.0)
|
elasticsearch (>= 7.12.0, < 7.14.0)
|
||||||
elasticsearch-dsl
|
elasticsearch-dsl
|
||||||
|
@ -189,29 +201,23 @@ GEM
|
||||||
coderay (1.1.3)
|
coderay (1.1.3)
|
||||||
color_diff (0.1)
|
color_diff (0.1)
|
||||||
concurrent-ruby (1.2.2)
|
concurrent-ruby (1.2.2)
|
||||||
connection_pool (2.3.0)
|
connection_pool (2.4.0)
|
||||||
cose (1.3.0)
|
cose (1.3.0)
|
||||||
cbor (~> 0.5.9)
|
cbor (~> 0.5.9)
|
||||||
openssl-signature_algorithm (~> 1.0)
|
openssl-signature_algorithm (~> 1.0)
|
||||||
crack (0.4.5)
|
crack (0.4.5)
|
||||||
rexml
|
rexml
|
||||||
crass (1.0.6)
|
crass (1.0.6)
|
||||||
css_parser (1.12.0)
|
css_parser (1.14.0)
|
||||||
addressable
|
addressable
|
||||||
date (3.3.3)
|
date (3.3.3)
|
||||||
debug_inspector (1.0.0)
|
debug_inspector (1.1.0)
|
||||||
devise (4.9.2)
|
devise (4.9.2)
|
||||||
bcrypt (~> 3.0)
|
bcrypt (~> 3.0)
|
||||||
orm_adapter (~> 0.1)
|
orm_adapter (~> 0.1)
|
||||||
railties (>= 4.1.0)
|
railties (>= 4.1.0)
|
||||||
responders
|
responders
|
||||||
warden (~> 1.2.3)
|
warden (~> 1.2.3)
|
||||||
devise-two-factor (4.0.2)
|
|
||||||
activesupport (< 7.1)
|
|
||||||
attr_encrypted (>= 1.3, < 4, != 2)
|
|
||||||
devise (~> 4.0)
|
|
||||||
railties (< 7.1)
|
|
||||||
rotp (~> 6.0)
|
|
||||||
devise_pam_authenticatable2 (9.2.0)
|
devise_pam_authenticatable2 (9.2.0)
|
||||||
devise (>= 4.0.0)
|
devise (>= 4.0.0)
|
||||||
rpam2 (~> 4.0)
|
rpam2 (~> 4.0)
|
||||||
|
@ -241,7 +247,7 @@ GEM
|
||||||
erubi (1.12.0)
|
erubi (1.12.0)
|
||||||
et-orbi (1.2.7)
|
et-orbi (1.2.7)
|
||||||
tzinfo
|
tzinfo
|
||||||
excon (0.95.0)
|
excon (0.99.0)
|
||||||
fabrication (2.30.0)
|
fabrication (2.30.0)
|
||||||
faker (3.2.0)
|
faker (3.2.0)
|
||||||
i18n (>= 1.8.11, < 2)
|
i18n (>= 1.8.11, < 2)
|
||||||
|
@ -314,7 +320,7 @@ GEM
|
||||||
hashie (5.0.0)
|
hashie (5.0.0)
|
||||||
hcaptcha (7.1.0)
|
hcaptcha (7.1.0)
|
||||||
json
|
json
|
||||||
highline (2.0.3)
|
highline (2.1.0)
|
||||||
hiredis (0.6.3)
|
hiredis (0.6.3)
|
||||||
hkdf (0.3.0)
|
hkdf (0.3.0)
|
||||||
htmlentities (4.3.4)
|
htmlentities (4.3.4)
|
||||||
|
@ -364,7 +370,7 @@ GEM
|
||||||
json-ld-preloaded (3.2.2)
|
json-ld-preloaded (3.2.2)
|
||||||
json-ld (~> 3.2)
|
json-ld (~> 3.2)
|
||||||
rdf (~> 3.2)
|
rdf (~> 3.2)
|
||||||
json-schema (3.0.0)
|
json-schema (4.0.0)
|
||||||
addressable (>= 2.8)
|
addressable (>= 2.8)
|
||||||
jsonapi-renderer (0.2.2)
|
jsonapi-renderer (0.2.2)
|
||||||
jwt (2.7.0)
|
jwt (2.7.0)
|
||||||
|
@ -380,8 +386,8 @@ GEM
|
||||||
activerecord
|
activerecord
|
||||||
kaminari-core (= 1.2.2)
|
kaminari-core (= 1.2.2)
|
||||||
kaminari-core (1.2.2)
|
kaminari-core (1.2.2)
|
||||||
launchy (2.5.0)
|
launchy (2.5.2)
|
||||||
addressable (~> 2.7)
|
addressable (~> 2.8)
|
||||||
letter_opener (1.8.1)
|
letter_opener (1.8.1)
|
||||||
launchy (>= 2.2, < 3)
|
launchy (>= 2.2, < 3)
|
||||||
letter_opener_web (2.0.0)
|
letter_opener_web (2.0.0)
|
||||||
|
@ -416,11 +422,11 @@ GEM
|
||||||
method_source (1.0.0)
|
method_source (1.0.0)
|
||||||
mime-types (3.4.1)
|
mime-types (3.4.1)
|
||||||
mime-types-data (~> 3.2015)
|
mime-types-data (~> 3.2015)
|
||||||
mime-types-data (3.2022.0105)
|
mime-types-data (3.2023.0218.1)
|
||||||
mini_mime (1.1.2)
|
mini_mime (1.1.2)
|
||||||
mini_portile2 (2.8.1)
|
mini_portile2 (2.8.1)
|
||||||
minitest (5.18.0)
|
minitest (5.18.0)
|
||||||
msgpack (1.6.0)
|
msgpack (1.7.0)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
multipart-post (2.3.0)
|
multipart-post (2.3.0)
|
||||||
net-http (0.3.2)
|
net-http (0.3.2)
|
||||||
|
@ -437,7 +443,7 @@ GEM
|
||||||
net-ssh (>= 2.6.5, < 8.0.0)
|
net-ssh (>= 2.6.5, < 8.0.0)
|
||||||
net-smtp (0.3.3)
|
net-smtp (0.3.3)
|
||||||
net-protocol
|
net-protocol
|
||||||
net-ssh (7.0.1)
|
net-ssh (7.1.0)
|
||||||
nio4r (2.5.9)
|
nio4r (2.5.9)
|
||||||
nokogiri (1.14.3)
|
nokogiri (1.14.3)
|
||||||
mini_portile2 (~> 2.8.0)
|
mini_portile2 (~> 2.8.0)
|
||||||
|
@ -480,18 +486,18 @@ GEM
|
||||||
openssl (> 2.0)
|
openssl (> 2.0)
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
ox (2.14.16)
|
ox (2.14.16)
|
||||||
parallel (1.22.1)
|
parallel (1.23.0)
|
||||||
parser (3.2.2.0)
|
parser (3.2.2.1)
|
||||||
ast (~> 2.4.1)
|
ast (~> 2.4.1)
|
||||||
parslet (2.0.0)
|
parslet (2.0.0)
|
||||||
pastel (0.8.0)
|
pastel (0.8.0)
|
||||||
tty-color (~> 0.5)
|
tty-color (~> 0.5)
|
||||||
pg (1.4.6)
|
pg (1.5.2)
|
||||||
pghero (3.3.2)
|
pghero (3.3.3)
|
||||||
activerecord (>= 6)
|
activerecord (>= 6)
|
||||||
pkg-config (1.5.1)
|
pkg-config (1.5.1)
|
||||||
posix-spawn (0.3.15)
|
posix-spawn (0.3.15)
|
||||||
premailer (1.18.0)
|
premailer (1.21.0)
|
||||||
addressable
|
addressable
|
||||||
css_parser (>= 1.12.0)
|
css_parser (>= 1.12.0)
|
||||||
htmlentities (>= 4.0.0)
|
htmlentities (>= 4.0.0)
|
||||||
|
@ -501,13 +507,13 @@ GEM
|
||||||
premailer (~> 1.7, >= 1.7.9)
|
premailer (~> 1.7, >= 1.7.9)
|
||||||
private_address_check (0.5.0)
|
private_address_check (0.5.0)
|
||||||
public_suffix (5.0.1)
|
public_suffix (5.0.1)
|
||||||
puma (6.2.1)
|
puma (6.2.2)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
pundit (2.3.0)
|
pundit (2.3.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
raabro (1.4.0)
|
raabro (1.4.0)
|
||||||
racc (1.6.2)
|
racc (1.6.2)
|
||||||
rack (2.2.6.4)
|
rack (2.2.7)
|
||||||
rack-attack (6.6.1)
|
rack-attack (6.6.1)
|
||||||
rack (>= 1.0, < 3)
|
rack (>= 1.0, < 3)
|
||||||
rack-cors (2.0.1)
|
rack-cors (2.0.1)
|
||||||
|
@ -567,25 +573,25 @@ GEM
|
||||||
redis (>= 4)
|
redis (>= 4)
|
||||||
redlock (1.3.2)
|
redlock (1.3.2)
|
||||||
redis (>= 3.0.0, < 6.0)
|
redis (>= 3.0.0, < 6.0)
|
||||||
regexp_parser (2.7.0)
|
regexp_parser (2.8.0)
|
||||||
request_store (1.5.1)
|
request_store (1.5.1)
|
||||||
rack (>= 1.4)
|
rack (>= 1.4)
|
||||||
responders (3.1.0)
|
responders (3.1.0)
|
||||||
actionpack (>= 5.2)
|
actionpack (>= 5.2)
|
||||||
railties (>= 5.2)
|
railties (>= 5.2)
|
||||||
rexml (3.2.5)
|
rexml (3.2.5)
|
||||||
rotp (6.2.0)
|
rotp (6.2.2)
|
||||||
rpam2 (4.0.2)
|
rpam2 (4.0.2)
|
||||||
rqrcode (2.1.2)
|
rqrcode (2.1.2)
|
||||||
chunky_png (~> 1.0)
|
chunky_png (~> 1.0)
|
||||||
rqrcode_core (~> 1.0)
|
rqrcode_core (~> 1.0)
|
||||||
rqrcode_core (1.2.0)
|
rqrcode_core (1.2.0)
|
||||||
rspec-core (3.12.1)
|
rspec-core (3.12.2)
|
||||||
rspec-support (~> 3.12.0)
|
rspec-support (~> 3.12.0)
|
||||||
rspec-expectations (3.12.2)
|
rspec-expectations (3.12.3)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.12.0)
|
rspec-support (~> 3.12.0)
|
||||||
rspec-mocks (3.12.3)
|
rspec-mocks (3.12.5)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.12.0)
|
rspec-support (~> 3.12.0)
|
||||||
rspec-rails (6.0.1)
|
rspec-rails (6.0.1)
|
||||||
|
@ -603,7 +609,7 @@ GEM
|
||||||
rspec_chunked (0.6)
|
rspec_chunked (0.6)
|
||||||
rspec_junit_formatter (0.6.0)
|
rspec_junit_formatter (0.6.0)
|
||||||
rspec-core (>= 2, < 4, != 2.12.0)
|
rspec-core (>= 2, < 4, != 2.12.0)
|
||||||
rubocop (1.49.0)
|
rubocop (1.50.2)
|
||||||
json (~> 2.3)
|
json (~> 2.3)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
parser (>= 3.2.0.0)
|
parser (>= 3.2.0.0)
|
||||||
|
@ -615,7 +621,7 @@ GEM
|
||||||
unicode-display_width (>= 2.4.0, < 3.0)
|
unicode-display_width (>= 2.4.0, < 3.0)
|
||||||
rubocop-ast (1.28.0)
|
rubocop-ast (1.28.0)
|
||||||
parser (>= 3.2.1.0)
|
parser (>= 3.2.1.0)
|
||||||
rubocop-capybara (2.17.1)
|
rubocop-capybara (2.18.0)
|
||||||
rubocop (~> 1.41)
|
rubocop (~> 1.41)
|
||||||
rubocop-performance (1.17.1)
|
rubocop-performance (1.17.1)
|
||||||
rubocop (>= 1.7.0, < 2.0)
|
rubocop (>= 1.7.0, < 2.0)
|
||||||
|
@ -771,6 +777,7 @@ DEPENDENCIES
|
||||||
active_model_serializers (~> 0.10)
|
active_model_serializers (~> 0.10)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
annotate (~> 3.2)
|
annotate (~> 3.2)
|
||||||
|
attr_encrypted (~> 4.0)
|
||||||
aws-sdk-s3 (~> 1.120)
|
aws-sdk-s3 (~> 1.120)
|
||||||
better_errors (~> 2.9)
|
better_errors (~> 2.9)
|
||||||
binding_of_caller (~> 1.0)
|
binding_of_caller (~> 1.0)
|
||||||
|
@ -792,7 +799,7 @@ DEPENDENCIES
|
||||||
concurrent-ruby
|
concurrent-ruby
|
||||||
connection_pool
|
connection_pool
|
||||||
devise (~> 4.9)
|
devise (~> 4.9)
|
||||||
devise-two-factor (~> 4.0)
|
devise-two-factor!
|
||||||
devise_pam_authenticatable2 (~> 9.2)
|
devise_pam_authenticatable2 (~> 9.2)
|
||||||
discard (~> 1.2)
|
discard (~> 1.2)
|
||||||
doorkeeper (~> 5.6)
|
doorkeeper (~> 5.6)
|
||||||
|
@ -817,7 +824,7 @@ DEPENDENCIES
|
||||||
idn-ruby
|
idn-ruby
|
||||||
json-ld
|
json-ld
|
||||||
json-ld-preloaded (~> 3.2)
|
json-ld-preloaded (~> 3.2)
|
||||||
json-schema (~> 3.0)
|
json-schema (~> 4.0)
|
||||||
kaminari (~> 1.2)
|
kaminari (~> 1.2)
|
||||||
kt-paperclip (~> 7.1)!
|
kt-paperclip (~> 7.1)!
|
||||||
letter_opener (~> 1.8)
|
letter_opener (~> 1.8)
|
||||||
|
@ -840,7 +847,7 @@ DEPENDENCIES
|
||||||
omniauth_openid_connect (~> 0.6.1)
|
omniauth_openid_connect (~> 0.6.1)
|
||||||
ox (~> 2.14)
|
ox (~> 2.14)
|
||||||
parslet
|
parslet
|
||||||
pg (~> 1.4)
|
pg (~> 1.5)
|
||||||
pghero
|
pghero
|
||||||
pkg-config (~> 1.5)
|
pkg-config (~> 1.5)
|
||||||
posix-spawn
|
posix-spawn
|
||||||
|
@ -849,7 +856,7 @@ DEPENDENCIES
|
||||||
public_suffix (~> 5.0)
|
public_suffix (~> 5.0)
|
||||||
puma (~> 6.2)
|
puma (~> 6.2)
|
||||||
pundit (~> 2.3)
|
pundit (~> 2.3)
|
||||||
rack (~> 2.2.6)
|
rack (~> 2.2.7)
|
||||||
rack-attack (~> 6.6)
|
rack-attack (~> 6.6)
|
||||||
rack-cors (~> 2.0)
|
rack-cors (~> 2.0)
|
||||||
rack-test (~> 2.1)
|
rack-test (~> 2.1)
|
||||||
|
@ -871,7 +878,7 @@ DEPENDENCIES
|
||||||
rubocop-performance
|
rubocop-performance
|
||||||
rubocop-rails
|
rubocop-rails
|
||||||
rubocop-rspec
|
rubocop-rspec
|
||||||
ruby-progressbar (~> 1.11)
|
ruby-progressbar (~> 1.13)
|
||||||
sanitize (~> 6.0)
|
sanitize (~> 6.0)
|
||||||
scenic (~> 1.7)
|
scenic (~> 1.7)
|
||||||
sidekiq (~> 6.5)
|
sidekiq (~> 6.5)
|
||||||
|
|
|
@ -8,7 +8,7 @@ class AboutController < ApplicationController
|
||||||
before_action :set_instance_presenter
|
before_action :set_instance_presenter
|
||||||
|
|
||||||
def show
|
def show
|
||||||
expires_in 0, public: true unless user_signed_in?
|
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless user_signed_in?
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -7,8 +7,9 @@ class AccountsController < ApplicationController
|
||||||
include AccountControllerConcern
|
include AccountControllerConcern
|
||||||
include SignatureAuthentication
|
include SignatureAuthentication
|
||||||
|
|
||||||
|
vary_by -> { public_fetch_mode? ? 'Accept, Accept-Language, Cookie' : 'Accept, Accept-Language, Cookie, Signature' }
|
||||||
|
|
||||||
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||||
before_action :set_cache_headers
|
|
||||||
|
|
||||||
skip_around_action :set_locale, if: -> { [:json, :rss].include?(request.format&.to_sym) }
|
skip_around_action :set_locale, if: -> { [:json, :rss].include?(request.format&.to_sym) }
|
||||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
skip_before_action :require_functional!, unless: :whitelist_mode?
|
||||||
|
@ -16,7 +17,7 @@ class AccountsController < ApplicationController
|
||||||
def show
|
def show
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html do
|
format.html do
|
||||||
expires_in 0, public: true unless user_signed_in?
|
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.hour) unless user_signed_in?
|
||||||
|
|
||||||
@rss_url = rss_url
|
@rss_url = rss_url
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,10 +7,6 @@ class ActivityPub::BaseController < Api::BaseController
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_cache_headers
|
|
||||||
response.headers['Vary'] = 'Signature' if authorized_fetch_mode?
|
|
||||||
end
|
|
||||||
|
|
||||||
def skip_temporary_suspension_response?
|
def skip_temporary_suspension_response?
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,11 +4,12 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
|
||||||
include SignatureVerification
|
include SignatureVerification
|
||||||
include AccountOwnedConcern
|
include AccountOwnedConcern
|
||||||
|
|
||||||
|
vary_by -> { 'Signature' if authorized_fetch_mode? }
|
||||||
|
|
||||||
before_action :require_account_signature!, if: :authorized_fetch_mode?
|
before_action :require_account_signature!, if: :authorized_fetch_mode?
|
||||||
before_action :set_items
|
before_action :set_items
|
||||||
before_action :set_size
|
before_action :set_size
|
||||||
before_action :set_type
|
before_action :set_type
|
||||||
before_action :set_cache_headers
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
expires_in 3.minutes, public: public_fetch_mode?
|
expires_in 3.minutes, public: public_fetch_mode?
|
||||||
|
|
|
@ -4,9 +4,10 @@ class ActivityPub::FollowersSynchronizationsController < ActivityPub::BaseContro
|
||||||
include SignatureVerification
|
include SignatureVerification
|
||||||
include AccountOwnedConcern
|
include AccountOwnedConcern
|
||||||
|
|
||||||
|
vary_by -> { 'Signature' if authorized_fetch_mode? }
|
||||||
|
|
||||||
before_action :require_account_signature!
|
before_action :require_account_signature!
|
||||||
before_action :set_items
|
before_action :set_items
|
||||||
before_action :set_cache_headers
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
expires_in 0, public: false
|
expires_in 0, public: false
|
||||||
|
|
|
@ -6,9 +6,10 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
|
||||||
include SignatureVerification
|
include SignatureVerification
|
||||||
include AccountOwnedConcern
|
include AccountOwnedConcern
|
||||||
|
|
||||||
|
vary_by -> { 'Signature' if authorized_fetch_mode? || page_requested? }
|
||||||
|
|
||||||
before_action :require_account_signature!, if: :authorized_fetch_mode?
|
before_action :require_account_signature!, if: :authorized_fetch_mode?
|
||||||
before_action :set_statuses
|
before_action :set_statuses
|
||||||
before_action :set_cache_headers
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
if page_requested?
|
if page_requested?
|
||||||
|
@ -16,6 +17,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
|
||||||
else
|
else
|
||||||
expires_in(3.minutes, public: public_fetch_mode?)
|
expires_in(3.minutes, public: public_fetch_mode?)
|
||||||
end
|
end
|
||||||
|
|
||||||
render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
|
render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -80,8 +82,4 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
|
||||||
def set_account
|
def set_account
|
||||||
@account = params[:account_username].present? ? Account.find_local!(username_param) : Account.representative
|
@account = params[:account_username].present? ? Account.find_local!(username_param) : Account.representative
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_cache_headers
|
|
||||||
response.headers['Vary'] = 'Signature' if authorized_fetch_mode? || page_requested?
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,9 +7,10 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
|
||||||
|
|
||||||
DESCENDANTS_LIMIT = 60
|
DESCENDANTS_LIMIT = 60
|
||||||
|
|
||||||
|
vary_by -> { 'Signature' if authorized_fetch_mode? }
|
||||||
|
|
||||||
before_action :require_account_signature!, if: :authorized_fetch_mode?
|
before_action :require_account_signature!, if: :authorized_fetch_mode?
|
||||||
before_action :set_status
|
before_action :set_status
|
||||||
before_action :set_cache_headers
|
|
||||||
before_action :set_replies
|
before_action :set_replies
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
|
|
@ -9,6 +9,8 @@ module Admin
|
||||||
|
|
||||||
before_action :set_pack
|
before_action :set_pack
|
||||||
before_action :set_body_classes
|
before_action :set_body_classes
|
||||||
|
before_action :set_cache_headers
|
||||||
|
|
||||||
after_action :verify_authorized
|
after_action :verify_authorized
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -21,6 +23,10 @@ module Admin
|
||||||
use_pack 'admin'
|
use_pack 'admin'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_cache_headers
|
||||||
|
response.cache_control.replace(private: true, no_store: true)
|
||||||
|
end
|
||||||
|
|
||||||
def set_user
|
def set_user
|
||||||
@user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
|
@user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,13 +6,14 @@ class Api::BaseController < ApplicationController
|
||||||
|
|
||||||
include RateLimitHeaders
|
include RateLimitHeaders
|
||||||
include AccessTokenTrackingConcern
|
include AccessTokenTrackingConcern
|
||||||
|
include ApiCachingConcern
|
||||||
|
|
||||||
skip_before_action :store_current_location
|
|
||||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
skip_before_action :require_functional!, unless: :whitelist_mode?
|
||||||
|
|
||||||
before_action :require_authenticated_user!, if: :disallow_unauthenticated_api_access?
|
before_action :require_authenticated_user!, if: :disallow_unauthenticated_api_access?
|
||||||
before_action :require_not_suspended!
|
before_action :require_not_suspended!
|
||||||
before_action :set_cache_headers
|
|
||||||
|
vary_by 'Authorization'
|
||||||
|
|
||||||
protect_from_forgery with: :null_session
|
protect_from_forgery with: :null_session
|
||||||
|
|
||||||
|
@ -148,10 +149,6 @@ class Api::BaseController < ApplicationController
|
||||||
doorkeeper_authorize!(*scopes) if doorkeeper_token
|
doorkeeper_authorize!(*scopes) if doorkeeper_token
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_cache_headers
|
|
||||||
response.headers['Cache-Control'] = 'private, no-store'
|
|
||||||
end
|
|
||||||
|
|
||||||
def disallow_unauthenticated_api_access?
|
def disallow_unauthenticated_api_access?
|
||||||
ENV['DISALLOW_UNAUTHENTICATED_API_ACCESS'] == 'true' || Rails.configuration.x.whitelist_mode
|
ENV['DISALLOW_UNAUTHENTICATED_API_ACCESS'] == 'true' || Rails.configuration.x.whitelist_mode
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,6 +6,7 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
|
||||||
after_action :insert_pagination_headers
|
after_action :insert_pagination_headers
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
cache_if_unauthenticated!
|
||||||
@accounts = load_accounts
|
@accounts = load_accounts
|
||||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
render json: @accounts, each_serializer: REST::AccountSerializer
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,6 +6,7 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
|
||||||
after_action :insert_pagination_headers
|
after_action :insert_pagination_headers
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
cache_if_unauthenticated!
|
||||||
@accounts = load_accounts
|
@accounts = load_accounts
|
||||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
render json: @accounts, each_serializer: REST::AccountSerializer
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,6 +5,7 @@ class Api::V1::Accounts::LookupController < Api::BaseController
|
||||||
before_action :set_account
|
before_action :set_account
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
cache_if_unauthenticated!
|
||||||
render json: @account, serializer: REST::AccountSerializer
|
render json: @account, serializer: REST::AccountSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
|
||||||
after_action :insert_pagination_headers, unless: -> { truthy_param?(:pinned) }
|
after_action :insert_pagination_headers, unless: -> { truthy_param?(:pinned) }
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
cache_if_unauthenticated!
|
||||||
@statuses = load_statuses
|
@statuses = load_statuses
|
||||||
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
||||||
end
|
end
|
||||||
|
|
|
@ -18,6 +18,7 @@ class Api::V1::AccountsController < Api::BaseController
|
||||||
override_rate_limit_headers :follow, family: :follows
|
override_rate_limit_headers :follow, family: :follows
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
cache_if_unauthenticated!
|
||||||
render json: @account, serializer: REST::AccountSerializer
|
render json: @account, serializer: REST::AccountSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::CustomEmojisController < Api::BaseController
|
class Api::V1::CustomEmojisController < Api::BaseController
|
||||||
skip_before_action :set_cache_headers
|
vary_by '', unless: :disallow_unauthenticated_api_access?
|
||||||
|
|
||||||
def index
|
def index
|
||||||
expires_in 3.minutes, public: true
|
cache_even_if_authenticated! unless disallow_unauthenticated_api_access?
|
||||||
render_with_cache(each_serializer: REST::CustomEmojiSerializer) { CustomEmoji.listed.includes(:category) }
|
render_with_cache(each_serializer: REST::CustomEmojiSerializer) { CustomEmoji.listed.includes(:category) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,6 +5,7 @@ class Api::V1::DirectoriesController < Api::BaseController
|
||||||
before_action :set_accounts
|
before_action :set_accounts
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
cache_if_unauthenticated!
|
||||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
render json: @accounts, each_serializer: REST::AccountSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,12 @@
|
||||||
class Api::V1::Instances::ActivityController < Api::BaseController
|
class Api::V1::Instances::ActivityController < Api::BaseController
|
||||||
before_action :require_enabled_api!
|
before_action :require_enabled_api!
|
||||||
|
|
||||||
skip_before_action :set_cache_headers
|
|
||||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
||||||
|
|
||||||
|
vary_by ''
|
||||||
|
|
||||||
def show
|
def show
|
||||||
expires_in 1.day, public: true
|
cache_even_if_authenticated!
|
||||||
render_with_cache json: :activity, expires_in: 1.day
|
render_with_cache json: :activity, expires_in: 1.day
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,15 @@ class Api::V1::Instances::DomainBlocksController < Api::BaseController
|
||||||
before_action :require_enabled_api!
|
before_action :require_enabled_api!
|
||||||
before_action :set_domain_blocks
|
before_action :set_domain_blocks
|
||||||
|
|
||||||
|
vary_by '', if: -> { Setting.show_domain_blocks == 'all' }
|
||||||
|
|
||||||
def index
|
def index
|
||||||
expires_in 3.minutes, public: true
|
if Setting.show_domain_blocks == 'all'
|
||||||
|
cache_even_if_authenticated!
|
||||||
|
else
|
||||||
|
cache_if_unauthenticated!
|
||||||
|
end
|
||||||
|
|
||||||
render json: @domain_blocks, each_serializer: REST::DomainBlockSerializer, with_comment: (Setting.show_domain_blocks_rationale == 'all' || (Setting.show_domain_blocks_rationale == 'users' && user_signed_in?))
|
render json: @domain_blocks, each_serializer: REST::DomainBlockSerializer, with_comment: (Setting.show_domain_blocks_rationale == 'all' || (Setting.show_domain_blocks_rationale == 'users' && user_signed_in?))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,19 @@
|
||||||
|
|
||||||
class Api::V1::Instances::ExtendedDescriptionsController < Api::BaseController
|
class Api::V1::Instances::ExtendedDescriptionsController < Api::BaseController
|
||||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
||||||
|
skip_around_action :set_locale
|
||||||
|
|
||||||
before_action :set_extended_description
|
before_action :set_extended_description
|
||||||
|
|
||||||
|
vary_by ''
|
||||||
|
|
||||||
|
# Override `current_user` to avoid reading session cookies unless in whitelist mode
|
||||||
|
def current_user
|
||||||
|
super if whitelist_mode?
|
||||||
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
expires_in 3.minutes, public: true
|
cache_even_if_authenticated!
|
||||||
render json: @extended_description, serializer: REST::ExtendedDescriptionSerializer
|
render json: @extended_description, serializer: REST::ExtendedDescriptionSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,18 @@
|
||||||
class Api::V1::Instances::PeersController < Api::BaseController
|
class Api::V1::Instances::PeersController < Api::BaseController
|
||||||
before_action :require_enabled_api!
|
before_action :require_enabled_api!
|
||||||
|
|
||||||
skip_before_action :set_cache_headers
|
|
||||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
||||||
|
skip_around_action :set_locale
|
||||||
|
|
||||||
|
vary_by ''
|
||||||
|
|
||||||
|
# Override `current_user` to avoid reading session cookies unless in whitelist mode
|
||||||
|
def current_user
|
||||||
|
super if whitelist_mode?
|
||||||
|
end
|
||||||
|
|
||||||
def index
|
def index
|
||||||
expires_in 1.day, public: true
|
cache_even_if_authenticated!
|
||||||
render_with_cache(expires_in: 1.day) { Instance.where.not(domain: DomainBlock.select(:domain)).pluck(:domain) }
|
render_with_cache(expires_in: 1.day) { Instance.where.not(domain: DomainBlock.select(:domain)).pluck(:domain) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,10 @@ class Api::V1::Instances::PrivacyPoliciesController < Api::BaseController
|
||||||
|
|
||||||
before_action :set_privacy_policy
|
before_action :set_privacy_policy
|
||||||
|
|
||||||
|
vary_by ''
|
||||||
|
|
||||||
def show
|
def show
|
||||||
expires_in 1.day, public: true
|
cache_even_if_authenticated!
|
||||||
render json: @privacy_policy, serializer: REST::PrivacyPolicySerializer
|
render json: @privacy_policy, serializer: REST::PrivacyPolicySerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,19 @@
|
||||||
|
|
||||||
class Api::V1::Instances::RulesController < Api::BaseController
|
class Api::V1::Instances::RulesController < Api::BaseController
|
||||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
||||||
|
skip_around_action :set_locale
|
||||||
|
|
||||||
before_action :set_rules
|
before_action :set_rules
|
||||||
|
|
||||||
|
vary_by ''
|
||||||
|
|
||||||
|
# Override `current_user` to avoid reading session cookies unless in whitelist mode
|
||||||
|
def current_user
|
||||||
|
super if whitelist_mode?
|
||||||
|
end
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
cache_even_if_authenticated!
|
||||||
render json: @rules, each_serializer: REST::RuleSerializer
|
render json: @rules, each_serializer: REST::RuleSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,10 @@ class Api::V1::Instances::TranslationLanguagesController < Api::BaseController
|
||||||
|
|
||||||
before_action :set_languages
|
before_action :set_languages
|
||||||
|
|
||||||
|
vary_by ''
|
||||||
|
|
||||||
def show
|
def show
|
||||||
expires_in 1.day, public: true
|
cache_even_if_authenticated!
|
||||||
render json: @languages
|
render json: @languages
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::InstancesController < Api::BaseController
|
class Api::V1::InstancesController < Api::BaseController
|
||||||
skip_before_action :set_cache_headers
|
|
||||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
||||||
|
skip_around_action :set_locale
|
||||||
|
|
||||||
|
vary_by ''
|
||||||
|
|
||||||
|
# Override `current_user` to avoid reading session cookies unless in whitelist mode
|
||||||
|
def current_user
|
||||||
|
super if whitelist_mode?
|
||||||
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
expires_in 3.minutes, public: true
|
cache_even_if_authenticated!
|
||||||
render_with_cache json: InstancePresenter.new, serializer: REST::V1::InstanceSerializer, root: 'instance'
|
render_with_cache json: InstancePresenter.new, serializer: REST::V1::InstanceSerializer, root: 'instance'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,6 +8,7 @@ class Api::V1::PollsController < Api::BaseController
|
||||||
before_action :refresh_poll
|
before_action :refresh_poll
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
cache_if_unauthenticated!
|
||||||
render json: @poll, serializer: REST::PollSerializer, include_results: true
|
render json: @poll, serializer: REST::PollSerializer, include_results: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
|
||||||
after_action :insert_pagination_headers
|
after_action :insert_pagination_headers
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
cache_if_unauthenticated!
|
||||||
@accounts = load_accounts
|
@accounts = load_accounts
|
||||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
render json: @accounts, each_serializer: REST::AccountSerializer
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,6 +7,7 @@ class Api::V1::Statuses::HistoriesController < Api::BaseController
|
||||||
before_action :set_status
|
before_action :set_status
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
cache_if_unauthenticated!
|
||||||
render json: @status.edits.includes(:account, status: [:account]), each_serializer: REST::StatusEditSerializer
|
render json: @status.edits.includes(:account, status: [:account]), each_serializer: REST::StatusEditSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
|
||||||
after_action :insert_pagination_headers
|
after_action :insert_pagination_headers
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
cache_if_unauthenticated!
|
||||||
@accounts = load_accounts
|
@accounts = load_accounts
|
||||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
render json: @accounts, each_serializer: REST::AccountSerializer
|
||||||
end
|
end
|
||||||
|
|
|
@ -24,11 +24,14 @@ class Api::V1::StatusesController < Api::BaseController
|
||||||
DESCENDANTS_DEPTH_LIMIT = 20
|
DESCENDANTS_DEPTH_LIMIT = 20
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
cache_if_unauthenticated!
|
||||||
@status = cache_collection([@status], Status).first
|
@status = cache_collection([@status], Status).first
|
||||||
render json: @status, serializer: REST::StatusSerializer
|
render json: @status, serializer: REST::StatusSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
def context
|
def context
|
||||||
|
cache_if_unauthenticated!
|
||||||
|
|
||||||
ancestors_limit = CONTEXT_LIMIT
|
ancestors_limit = CONTEXT_LIMIT
|
||||||
descendants_limit = CONTEXT_LIMIT
|
descendants_limit = CONTEXT_LIMIT
|
||||||
descendants_depth_limit = nil
|
descendants_depth_limit = nil
|
||||||
|
|
|
@ -8,6 +8,7 @@ class Api::V1::TagsController < Api::BaseController
|
||||||
override_rate_limit_headers :follow, family: :follows
|
override_rate_limit_headers :follow, family: :follows
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
cache_if_unauthenticated!
|
||||||
render json: @tag, serializer: REST::TagSerializer
|
render json: @tag, serializer: REST::TagSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ class Api::V1::Timelines::PublicController < Api::BaseController
|
||||||
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
cache_if_unauthenticated!
|
||||||
@statuses = load_statuses
|
@statuses = load_statuses
|
||||||
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,6 +5,7 @@ class Api::V1::Timelines::TagController < Api::BaseController
|
||||||
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
cache_if_unauthenticated!
|
||||||
@statuses = load_statuses
|
@statuses = load_statuses
|
||||||
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Trends::LinksController < Api::BaseController
|
class Api::V1::Trends::LinksController < Api::BaseController
|
||||||
|
vary_by 'Authorization, Accept-Language'
|
||||||
|
|
||||||
before_action :set_links
|
before_action :set_links
|
||||||
|
|
||||||
after_action :insert_pagination_headers
|
after_action :insert_pagination_headers
|
||||||
|
@ -8,6 +10,7 @@ class Api::V1::Trends::LinksController < Api::BaseController
|
||||||
DEFAULT_LINKS_LIMIT = 10
|
DEFAULT_LINKS_LIMIT = 10
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
cache_if_unauthenticated!
|
||||||
render json: @links, each_serializer: REST::Trends::LinkSerializer
|
render json: @links, each_serializer: REST::Trends::LinkSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Trends::StatusesController < Api::BaseController
|
class Api::V1::Trends::StatusesController < Api::BaseController
|
||||||
|
vary_by 'Authorization, Accept-Language'
|
||||||
|
|
||||||
before_action :set_statuses
|
before_action :set_statuses
|
||||||
|
|
||||||
after_action :insert_pagination_headers
|
after_action :insert_pagination_headers
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
cache_if_unauthenticated!
|
||||||
render json: @statuses, each_serializer: REST::StatusSerializer
|
render json: @statuses, each_serializer: REST::StatusSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ class Api::V1::Trends::TagsController < Api::BaseController
|
||||||
DEFAULT_TAGS_LIMIT = (ENV['MAX_TRENDING_TAGS'] || 10).to_i
|
DEFAULT_TAGS_LIMIT = (ENV['MAX_TRENDING_TAGS'] || 10).to_i
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
cache_if_unauthenticated!
|
||||||
render json: @tags, each_serializer: REST::TagSerializer, relationships: TagRelationshipsPresenter.new(@tags, current_user&.account_id)
|
render json: @tags, each_serializer: REST::TagSerializer, relationships: TagRelationshipsPresenter.new(@tags, current_user&.account_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
class Api::V2::InstancesController < Api::V1::InstancesController
|
class Api::V2::InstancesController < Api::V1::InstancesController
|
||||||
def show
|
def show
|
||||||
expires_in 3.minutes, public: true
|
cache_even_if_authenticated!
|
||||||
render_with_cache json: InstancePresenter.new, serializer: REST::InstanceSerializer, root: 'instance'
|
render_with_cache json: InstancePresenter.new, serializer: REST::InstanceSerializer, root: 'instance'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,6 +21,8 @@ class ApplicationController < ActionController::Base
|
||||||
helper_method :omniauth_only?
|
helper_method :omniauth_only?
|
||||||
helper_method :sso_account_settings
|
helper_method :sso_account_settings
|
||||||
helper_method :whitelist_mode?
|
helper_method :whitelist_mode?
|
||||||
|
helper_method :body_class_string
|
||||||
|
helper_method :skip_csrf_meta_tags?
|
||||||
|
|
||||||
rescue_from ActionController::ParameterMissing, Paperclip::AdapterRegistry::NoHandlerError, with: :bad_request
|
rescue_from ActionController::ParameterMissing, Paperclip::AdapterRegistry::NoHandlerError, with: :bad_request
|
||||||
rescue_from Mastodon::NotPermittedError, with: :forbidden
|
rescue_from Mastodon::NotPermittedError, with: :forbidden
|
||||||
|
@ -37,9 +39,11 @@ class ApplicationController < ActionController::Base
|
||||||
service_unavailable
|
service_unavailable
|
||||||
end
|
end
|
||||||
|
|
||||||
before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?
|
before_action :store_referrer, except: :raise_not_found, if: :devise_controller?
|
||||||
before_action :require_functional!, if: :user_signed_in?
|
before_action :require_functional!, if: :user_signed_in?
|
||||||
|
|
||||||
|
before_action :set_cache_control_defaults
|
||||||
|
|
||||||
skip_before_action :verify_authenticity_token, only: :raise_not_found
|
skip_before_action :verify_authenticity_token, only: :raise_not_found
|
||||||
|
|
||||||
def raise_not_found
|
def raise_not_found
|
||||||
|
@ -56,14 +60,25 @@ class ApplicationController < ActionController::Base
|
||||||
!authorized_fetch_mode?
|
!authorized_fetch_mode?
|
||||||
end
|
end
|
||||||
|
|
||||||
def store_current_location
|
def store_referrer
|
||||||
store_location_for(:user, request.url) unless [:json, :rss].include?(request.format&.to_sym)
|
return if request.referer.blank?
|
||||||
|
|
||||||
|
redirect_uri = URI(request.referer)
|
||||||
|
return if redirect_uri.path.start_with?('/auth')
|
||||||
|
|
||||||
|
stored_url = redirect_uri.to_s if redirect_uri.host == request.host && redirect_uri.port == request.port
|
||||||
|
|
||||||
|
store_location_for(:user, stored_url)
|
||||||
end
|
end
|
||||||
|
|
||||||
def require_functional!
|
def require_functional!
|
||||||
redirect_to edit_user_registration_path unless current_user.functional?
|
redirect_to edit_user_registration_path unless current_user.functional?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def skip_csrf_meta_tags?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
def after_sign_out_path_for(_resource_or_scope)
|
def after_sign_out_path_for(_resource_or_scope)
|
||||||
if ENV['OMNIAUTH_ONLY'] == 'true' && ENV['OIDC_ENABLED'] == 'true'
|
if ENV['OMNIAUTH_ONLY'] == 'true' && ENV['OIDC_ENABLED'] == 'true'
|
||||||
'/auth/auth/openid_connect/logout'
|
'/auth/auth/openid_connect/logout'
|
||||||
|
@ -127,7 +142,7 @@ class ApplicationController < ActionController::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def sso_account_settings
|
def sso_account_settings
|
||||||
ENV.fetch('SSO_ACCOUNT_SETTINGS')
|
ENV.fetch('SSO_ACCOUNT_SETTINGS', nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_account
|
def current_account
|
||||||
|
@ -142,6 +157,10 @@ class ApplicationController < ActionController::Base
|
||||||
@current_session = SessionActivation.find_by(session_id: cookies.signed['_session_id']) if cookies.signed['_session_id'].present?
|
@current_session = SessionActivation.find_by(session_id: cookies.signed['_session_id']) if cookies.signed['_session_id'].present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def body_class_string
|
||||||
|
@body_classes || ''
|
||||||
|
end
|
||||||
|
|
||||||
def respond_with_error(code)
|
def respond_with_error(code)
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.any do
|
format.any do
|
||||||
|
@ -151,4 +170,8 @@ class ApplicationController < ActionController::Base
|
||||||
format.json { render json: { error: Rack::Utils::HTTP_STATUS_CODES[code] }, status: code }
|
format.json { render json: { error: Rack::Utils::HTTP_STATUS_CODES[code] }, status: code }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_cache_control_defaults
|
||||||
|
response.cache_control.replace(private: true, no_store: true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -157,6 +157,6 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_cache_headers
|
def set_cache_headers
|
||||||
response.headers['Cache-Control'] = 'private, no-store'
|
response.cache_control.replace(private: true, no_store: true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module ApiCachingConcern
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
def cache_if_unauthenticated!
|
||||||
|
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless user_signed_in?
|
||||||
|
end
|
||||||
|
|
||||||
|
def cache_even_if_authenticated!
|
||||||
|
expires_in(5.minutes, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless whitelist_mode?
|
||||||
|
end
|
||||||
|
end
|
|
@ -155,8 +155,30 @@ module CacheConcern
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
class_methods do
|
||||||
|
def vary_by(value, **kwargs)
|
||||||
|
before_action(**kwargs) do |controller|
|
||||||
|
response.headers['Vary'] = value.respond_to?(:call) ? controller.instance_exec(&value) : value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
included do
|
||||||
|
after_action :enforce_cache_control!
|
||||||
|
end
|
||||||
|
|
||||||
|
# Prevents high-entropy headers such as `Cookie`, `Signature` or `Authorization`
|
||||||
|
# from being used as cache keys, while allowing to `Vary` on them (to not serve
|
||||||
|
# anonymous cached data to authenticated requests when authentication matters)
|
||||||
|
def enforce_cache_control!
|
||||||
|
vary = response.headers['Vary']&.split&.map { |x| x.strip.downcase }
|
||||||
|
return unless vary.present? && %w(cookie authorization signature).any? { |header| vary.include?(header) && request.headers[header].present? }
|
||||||
|
|
||||||
|
response.cache_control.replace(private: true, no_store: true)
|
||||||
|
end
|
||||||
|
|
||||||
def render_with_cache(**options)
|
def render_with_cache(**options)
|
||||||
raise ArgumentError, 'only JSON render calls are supported' unless options.key?(:json) || block_given?
|
raise ArgumentError, 'Only JSON render calls are supported' unless options.key?(:json) || block_given?
|
||||||
|
|
||||||
key = options.delete(:key) || [[params[:controller], params[:action]].join('/'), options[:json].respond_to?(:cache_key) ? options[:json].cache_key : nil, options[:fields].nil? ? nil : options[:fields].join(',')].compact.join(':')
|
key = options.delete(:key) || [[params[:controller], params[:action]].join('/'), options[:json].respond_to?(:cache_key) ? options[:json].cache_key : nil, options[:fields].nil? ? nil : options[:fields].join(',')].compact.join(':')
|
||||||
expires_in = options.delete(:expires_in) || 3.minutes
|
expires_in = options.delete(:expires_in) || 3.minutes
|
||||||
|
@ -176,10 +198,6 @@ module CacheConcern
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_cache_headers
|
|
||||||
response.headers['Vary'] = public_fetch_mode? ? 'Accept' : 'Accept, Signature'
|
|
||||||
end
|
|
||||||
|
|
||||||
def cache_collection(raw, klass)
|
def cache_collection(raw, klass)
|
||||||
return raw unless klass.respond_to?(:with_includes)
|
return raw unless klass.respond_to?(:with_includes)
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,12 @@ module WebAppControllerConcern
|
||||||
prepend_before_action :redirect_unauthenticated_to_permalinks!
|
prepend_before_action :redirect_unauthenticated_to_permalinks!
|
||||||
before_action :set_pack
|
before_action :set_pack
|
||||||
before_action :set_app_body_class
|
before_action :set_app_body_class
|
||||||
|
|
||||||
|
vary_by 'Accept, Accept-Language, Cookie'
|
||||||
|
end
|
||||||
|
|
||||||
|
def skip_csrf_meta_tags?
|
||||||
|
current_user.nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_app_body_class
|
def set_app_body_class
|
||||||
|
|
|
@ -1,18 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class CustomCssController < ApplicationController
|
class CustomCssController < ActionController::Base # rubocop:disable Rails/ApplicationController
|
||||||
skip_before_action :store_current_location
|
|
||||||
skip_before_action :require_functional!
|
|
||||||
skip_before_action :update_user_sign_in
|
|
||||||
skip_before_action :set_session_activity
|
|
||||||
|
|
||||||
skip_around_action :set_locale
|
|
||||||
|
|
||||||
before_action :set_cache_headers
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
expires_in 3.minutes, public: true
|
expires_in 3.minutes, public: true
|
||||||
request.session_options[:skip] = true
|
|
||||||
render content_type: 'text/css'
|
render content_type: 'text/css'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,6 +10,7 @@ class Disputes::BaseController < ApplicationController
|
||||||
before_action :set_body_classes
|
before_action :set_body_classes
|
||||||
before_action :authenticate_user!
|
before_action :authenticate_user!
|
||||||
before_action :set_pack
|
before_action :set_pack
|
||||||
|
before_action :set_cache_headers
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
@ -20,4 +21,8 @@ class Disputes::BaseController < ApplicationController
|
||||||
def set_body_classes
|
def set_body_classes
|
||||||
@body_classes = 'admin'
|
@body_classes = 'admin'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_cache_headers
|
||||||
|
response.cache_control.replace(private: true, no_store: true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,16 +2,13 @@
|
||||||
|
|
||||||
class EmojisController < ApplicationController
|
class EmojisController < ApplicationController
|
||||||
before_action :set_emoji
|
before_action :set_emoji
|
||||||
before_action :set_cache_headers
|
|
||||||
|
vary_by -> { 'Signature' if authorized_fetch_mode? }
|
||||||
|
|
||||||
def show
|
def show
|
||||||
respond_to do |format|
|
|
||||||
format.json do
|
|
||||||
expires_in 3.minutes, public: true
|
expires_in 3.minutes, public: true
|
||||||
render_with_cache json: @emoji, content_type: 'application/activity+json', serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter
|
render_with_cache json: @emoji, content_type: 'application/activity+json', serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter
|
||||||
end
|
end
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ class Filters::StatusesController < ApplicationController
|
||||||
before_action :set_status_filters
|
before_action :set_status_filters
|
||||||
before_action :set_pack
|
before_action :set_pack
|
||||||
before_action :set_body_classes
|
before_action :set_body_classes
|
||||||
|
before_action :set_cache_headers
|
||||||
|
|
||||||
PER_PAGE = 20
|
PER_PAGE = 20
|
||||||
|
|
||||||
|
@ -49,4 +50,8 @@ class Filters::StatusesController < ApplicationController
|
||||||
def set_body_classes
|
def set_body_classes
|
||||||
@body_classes = 'admin'
|
@body_classes = 'admin'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_cache_headers
|
||||||
|
response.cache_control.replace(private: true, no_store: true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,6 +7,7 @@ class FiltersController < ApplicationController
|
||||||
before_action :set_filter, only: [:edit, :update, :destroy]
|
before_action :set_filter, only: [:edit, :update, :destroy]
|
||||||
before_action :set_pack
|
before_action :set_pack
|
||||||
before_action :set_body_classes
|
before_action :set_body_classes
|
||||||
|
before_action :set_cache_headers
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@filters = current_account.custom_filters.includes(:keywords, :statuses).order(:phrase)
|
@filters = current_account.custom_filters.includes(:keywords, :statuses).order(:phrase)
|
||||||
|
@ -59,4 +60,8 @@ class FiltersController < ApplicationController
|
||||||
def set_body_classes
|
def set_body_classes
|
||||||
@body_classes = 'admin'
|
@body_classes = 'admin'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_cache_headers
|
||||||
|
response.cache_control.replace(private: true, no_store: true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,8 +5,9 @@ class FollowerAccountsController < ApplicationController
|
||||||
include SignatureVerification
|
include SignatureVerification
|
||||||
include WebAppControllerConcern
|
include WebAppControllerConcern
|
||||||
|
|
||||||
|
vary_by -> { public_fetch_mode? ? 'Accept, Accept-Language, Cookie' : 'Accept, Accept-Language, Cookie, Signature' }
|
||||||
|
|
||||||
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||||
before_action :set_cache_headers
|
|
||||||
|
|
||||||
skip_around_action :set_locale, if: -> { request.format == :json }
|
skip_around_action :set_locale, if: -> { request.format == :json }
|
||||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
skip_before_action :require_functional!, unless: :whitelist_mode?
|
||||||
|
@ -14,7 +15,7 @@ class FollowerAccountsController < ApplicationController
|
||||||
def index
|
def index
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html do
|
format.html do
|
||||||
expires_in 0, public: true unless user_signed_in?
|
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.hour) unless user_signed_in?
|
||||||
end
|
end
|
||||||
|
|
||||||
format.json do
|
format.json do
|
||||||
|
|
|
@ -5,8 +5,9 @@ class FollowingAccountsController < ApplicationController
|
||||||
include SignatureVerification
|
include SignatureVerification
|
||||||
include WebAppControllerConcern
|
include WebAppControllerConcern
|
||||||
|
|
||||||
|
vary_by -> { public_fetch_mode? ? 'Accept, Accept-Language, Cookie' : 'Accept, Accept-Language, Cookie, Signature' }
|
||||||
|
|
||||||
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||||
before_action :set_cache_headers
|
|
||||||
|
|
||||||
skip_around_action :set_locale, if: -> { request.format == :json }
|
skip_around_action :set_locale, if: -> { request.format == :json }
|
||||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
skip_before_action :require_functional!, unless: :whitelist_mode?
|
||||||
|
@ -14,7 +15,7 @@ class FollowingAccountsController < ApplicationController
|
||||||
def index
|
def index
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html do
|
format.html do
|
||||||
expires_in 0, public: true unless user_signed_in?
|
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.hour) unless user_signed_in?
|
||||||
end
|
end
|
||||||
|
|
||||||
format.json do
|
format.json do
|
||||||
|
|
|
@ -6,7 +6,7 @@ class HomeController < ApplicationController
|
||||||
before_action :set_instance_presenter
|
before_action :set_instance_presenter
|
||||||
|
|
||||||
def index
|
def index
|
||||||
expires_in 0, public: true unless user_signed_in?
|
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless user_signed_in?
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class InstanceActorsController < ApplicationController
|
class InstanceActorsController < ActivityPub::BaseController
|
||||||
include AccountControllerConcern
|
vary_by ''
|
||||||
|
|
||||||
skip_before_action :check_account_confirmation
|
serialization_scope nil
|
||||||
skip_around_action :set_locale
|
|
||||||
|
before_action :set_account
|
||||||
|
skip_before_action :require_functional!
|
||||||
|
skip_before_action :update_user_sign_in
|
||||||
|
|
||||||
def show
|
def show
|
||||||
expires_in 10.minutes, public: true
|
expires_in 10.minutes, public: true
|
||||||
|
|
|
@ -8,6 +8,7 @@ class InvitesController < ApplicationController
|
||||||
before_action :authenticate_user!
|
before_action :authenticate_user!
|
||||||
before_action :set_pack
|
before_action :set_pack
|
||||||
before_action :set_body_classes
|
before_action :set_body_classes
|
||||||
|
before_action :set_cache_headers
|
||||||
|
|
||||||
def index
|
def index
|
||||||
authorize :invite, :create?
|
authorize :invite, :create?
|
||||||
|
@ -54,4 +55,8 @@ class InvitesController < ApplicationController
|
||||||
def set_body_classes
|
def set_body_classes
|
||||||
@body_classes = 'admin'
|
@body_classes = 'admin'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_cache_headers
|
||||||
|
response.cache_control.replace(private: true, no_store: true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class ManifestsController < ApplicationController
|
class ManifestsController < ActionController::Base # rubocop:disable Rails/ApplicationController
|
||||||
skip_before_action :store_current_location
|
# Prevent `active_model_serializer`'s `ActionController::Serialization` from calling `current_user`
|
||||||
skip_before_action :require_functional!
|
# and thus re-issuing session cookies
|
||||||
|
serialization_scope nil
|
||||||
|
|
||||||
def show
|
def show
|
||||||
expires_in 3.minutes, public: true
|
expires_in 3.minutes, public: true
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
class MediaController < ApplicationController
|
class MediaController < ApplicationController
|
||||||
include Authorization
|
include Authorization
|
||||||
|
|
||||||
skip_before_action :store_current_location
|
|
||||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
skip_before_action :require_functional!, unless: :whitelist_mode?
|
||||||
|
|
||||||
before_action :authenticate_user!, if: :whitelist_mode?
|
before_action :authenticate_user!, if: :whitelist_mode?
|
||||||
|
|
|
@ -6,7 +6,6 @@ class MediaProxyController < ApplicationController
|
||||||
include Redisable
|
include Redisable
|
||||||
include Lockable
|
include Lockable
|
||||||
|
|
||||||
skip_before_action :store_current_location
|
|
||||||
skip_before_action :require_functional!
|
skip_before_action :require_functional!
|
||||||
|
|
||||||
before_action :authenticate_user!, if: :whitelist_mode?
|
before_action :authenticate_user!, if: :whitelist_mode?
|
||||||
|
|
|
@ -39,6 +39,6 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_cache_headers
|
def set_cache_headers
|
||||||
response.headers['Cache-Control'] = 'private, no-store'
|
response.cache_control.replace(private: true, no_store: true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,6 +8,7 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
|
||||||
before_action :set_pack
|
before_action :set_pack
|
||||||
before_action :require_not_suspended!, only: :destroy
|
before_action :require_not_suspended!, only: :destroy
|
||||||
before_action :set_body_classes
|
before_action :set_body_classes
|
||||||
|
before_action :set_cache_headers
|
||||||
|
|
||||||
skip_before_action :require_functional!
|
skip_before_action :require_functional!
|
||||||
|
|
||||||
|
@ -35,4 +36,8 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
|
||||||
def require_not_suspended!
|
def require_not_suspended!
|
||||||
forbidden if current_account.suspended?
|
forbidden if current_account.suspended?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_cache_headers
|
||||||
|
response.cache_control.replace(private: true, no_store: true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,7 +8,7 @@ class PrivacyController < ApplicationController
|
||||||
before_action :set_instance_presenter
|
before_action :set_instance_presenter
|
||||||
|
|
||||||
def show
|
def show
|
||||||
expires_in 0, public: true if current_account.nil?
|
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless user_signed_in?
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -8,6 +8,7 @@ class RelationshipsController < ApplicationController
|
||||||
before_action :set_pack
|
before_action :set_pack
|
||||||
before_action :set_relationships, only: :show
|
before_action :set_relationships, only: :show
|
||||||
before_action :set_body_classes
|
before_action :set_body_classes
|
||||||
|
before_action :set_cache_headers
|
||||||
|
|
||||||
helper_method :following_relationship?, :followed_by_relationship?, :mutual_relationship?
|
helper_method :following_relationship?, :followed_by_relationship?, :mutual_relationship?
|
||||||
|
|
||||||
|
@ -75,4 +76,8 @@ class RelationshipsController < ApplicationController
|
||||||
def set_pack
|
def set_pack
|
||||||
use_pack 'admin'
|
use_pack 'admin'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_cache_headers
|
||||||
|
response.cache_control.replace(private: true, no_store: true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,7 +19,7 @@ class Settings::BaseController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_cache_headers
|
def set_cache_headers
|
||||||
response.headers['Cache-Control'] = 'private, no-store'
|
response.cache_control.replace(private: true, no_store: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def require_not_suspended!
|
def require_not_suspended!
|
||||||
|
|
|
@ -7,6 +7,7 @@ class StatusesCleanupController < ApplicationController
|
||||||
before_action :set_policy
|
before_action :set_policy
|
||||||
before_action :set_body_classes
|
before_action :set_body_classes
|
||||||
before_action :set_pack
|
before_action :set_pack
|
||||||
|
before_action :set_cache_headers
|
||||||
|
|
||||||
def show; end
|
def show; end
|
||||||
|
|
||||||
|
@ -41,4 +42,8 @@ class StatusesCleanupController < ApplicationController
|
||||||
def set_body_classes
|
def set_body_classes
|
||||||
@body_classes = 'admin'
|
@body_classes = 'admin'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_cache_headers
|
||||||
|
response.cache_control.replace(private: true, no_store: true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,11 +6,12 @@ class StatusesController < ApplicationController
|
||||||
include Authorization
|
include Authorization
|
||||||
include AccountOwnedConcern
|
include AccountOwnedConcern
|
||||||
|
|
||||||
|
vary_by -> { public_fetch_mode? ? 'Accept, Accept-Language, Cookie' : 'Accept, Accept-Language, Cookie, Signature' }
|
||||||
|
|
||||||
before_action :require_account_signature!, only: [:show, :activity], if: -> { request.format == :json && authorized_fetch_mode? }
|
before_action :require_account_signature!, only: [:show, :activity], if: -> { request.format == :json && authorized_fetch_mode? }
|
||||||
before_action :set_status
|
before_action :set_status
|
||||||
before_action :set_instance_presenter
|
before_action :set_instance_presenter
|
||||||
before_action :redirect_to_original, only: :show
|
before_action :redirect_to_original, only: :show
|
||||||
before_action :set_cache_headers
|
|
||||||
before_action :set_body_classes, only: :embed
|
before_action :set_body_classes, only: :embed
|
||||||
|
|
||||||
after_action :set_link_headers
|
after_action :set_link_headers
|
||||||
|
@ -29,7 +30,7 @@ class StatusesController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
format.json do
|
format.json do
|
||||||
expires_in 3.minutes, public: @status.distributable? && public_fetch_mode?
|
expires_in 3.minutes, public: true if @status.distributable? && public_fetch_mode?
|
||||||
render_with_cache json: @status, content_type: 'application/activity+json', serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter
|
render_with_cache json: @status, content_type: 'application/activity+json', serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,6 +7,8 @@ class TagsController < ApplicationController
|
||||||
PAGE_SIZE = 20
|
PAGE_SIZE = 20
|
||||||
PAGE_SIZE_MAX = 200
|
PAGE_SIZE_MAX = 200
|
||||||
|
|
||||||
|
vary_by -> { public_fetch_mode? ? 'Accept, Accept-Language, Cookie' : 'Accept, Accept-Language, Cookie, Signature' }
|
||||||
|
|
||||||
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||||
before_action :authenticate_user!, if: :whitelist_mode?
|
before_action :authenticate_user!, if: :whitelist_mode?
|
||||||
before_action :set_local
|
before_action :set_local
|
||||||
|
@ -19,7 +21,7 @@ class TagsController < ApplicationController
|
||||||
def show
|
def show
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html do
|
format.html do
|
||||||
expires_in 0, public: true unless user_signed_in?
|
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.hour) unless user_signed_in?
|
||||||
end
|
end
|
||||||
|
|
||||||
format.rss do
|
format.rss do
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WellKnown
|
module WellKnown
|
||||||
class HostMetaController < ActionController::Base
|
class HostMetaController < ActionController::Base # rubocop:disable Rails/ApplicationController
|
||||||
include RoutingHelper
|
include RoutingHelper
|
||||||
|
|
||||||
before_action { response.headers['Vary'] = 'Accept' }
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@webfinger_template = "#{webfinger_url}?resource={uri}"
|
@webfinger_template = "#{webfinger_url}?resource={uri}"
|
||||||
expires_in 3.days, public: true
|
expires_in 3.days, public: true
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WellKnown
|
module WellKnown
|
||||||
class NodeInfoController < ActionController::Base
|
class NodeInfoController < ActionController::Base # rubocop:disable Rails/ApplicationController
|
||||||
include CacheConcern
|
include CacheConcern
|
||||||
|
|
||||||
before_action { response.headers['Vary'] = 'Accept' }
|
# Prevent `active_model_serializer`'s `ActionController::Serialization` from calling `current_user`
|
||||||
|
# and thus re-issuing session cookies
|
||||||
|
serialization_scope nil
|
||||||
|
|
||||||
def index
|
def index
|
||||||
expires_in 3.days, public: true
|
expires_in 3.days, public: true
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module WellKnown
|
module WellKnown
|
||||||
class WebfingerController < ActionController::Base
|
class WebfingerController < ActionController::Base # rubocop:disable Rails/ApplicationController
|
||||||
include RoutingHelper
|
include RoutingHelper
|
||||||
|
|
||||||
before_action :set_account
|
before_action :set_account
|
||||||
|
@ -34,7 +34,12 @@ module WellKnown
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_account_suspension
|
def check_account_suspension
|
||||||
expires_in(3.minutes, public: true) && gone if @account.suspended_permanently?
|
gone if @account.suspended_permanently?
|
||||||
|
end
|
||||||
|
|
||||||
|
def gone
|
||||||
|
expires_in(3.minutes, public: true)
|
||||||
|
head 410
|
||||||
end
|
end
|
||||||
|
|
||||||
def bad_request
|
def bad_request
|
||||||
|
@ -46,9 +51,5 @@ module WellKnown
|
||||||
expires_in(3.minutes, public: true)
|
expires_in(3.minutes, public: true)
|
||||||
head 404
|
head 404
|
||||||
end
|
end
|
||||||
|
|
||||||
def gone
|
|
||||||
head 410
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -155,20 +155,8 @@ module ApplicationHelper
|
||||||
tag(:meta, content: content, property: property)
|
tag(:meta, content: content, property: property)
|
||||||
end
|
end
|
||||||
|
|
||||||
def react_component(name, props = {}, &block)
|
|
||||||
if block.nil?
|
|
||||||
content_tag(:div, nil, data: { component: name.to_s.camelcase, props: Oj.dump(props) })
|
|
||||||
else
|
|
||||||
content_tag(:div, data: { component: name.to_s.camelcase, props: Oj.dump(props) }, &block)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def react_admin_component(name, props = {})
|
|
||||||
content_tag(:div, nil, data: { 'admin-component': name.to_s.camelcase, props: Oj.dump({ locale: I18n.locale }.merge(props)) })
|
|
||||||
end
|
|
||||||
|
|
||||||
def body_classes
|
def body_classes
|
||||||
output = (@body_classes || '').split
|
output = body_class_string.split
|
||||||
output << "flavour-#{current_flavour.parameterize}"
|
output << "flavour-#{current_flavour.parameterize}"
|
||||||
output << "skin-#{current_skin.parameterize}"
|
output << "skin-#{current_skin.parameterize}"
|
||||||
output << 'system-font' if current_account&.user&.setting_system_font_ui
|
output << 'system-font' if current_account&.user&.setting_system_font_ui
|
||||||
|
|
|
@ -9,13 +9,17 @@ module InstanceHelper
|
||||||
@site_hostname ||= Addressable::URI.parse("//#{Rails.configuration.x.local_domain}").display_uri.host
|
@site_hostname ||= Addressable::URI.parse("//#{Rails.configuration.x.local_domain}").display_uri.host
|
||||||
end
|
end
|
||||||
|
|
||||||
def description_for_sign_up
|
def description_for_sign_up(invite = nil)
|
||||||
prefix = if @invite.present?
|
safe_join([description_prefix(invite), I18n.t('auth.description.suffix')], ' ')
|
||||||
I18n.t('auth.description.prefix_invited_by_user', name: @invite.user.account.username)
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def description_prefix(invite)
|
||||||
|
if invite.present?
|
||||||
|
I18n.t('auth.description.prefix_invited_by_user', name: invite.user.account.username)
|
||||||
else
|
else
|
||||||
I18n.t('auth.description.prefix_sign_up')
|
I18n.t('auth.description.prefix_sign_up')
|
||||||
end
|
end
|
||||||
|
|
||||||
safe_join([prefix, I18n.t('auth.description.suffix')], ' ')
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -63,11 +63,11 @@ module JsonLdHelper
|
||||||
uri.nil? || !uri.start_with?('http://', 'https://')
|
uri.nil? || !uri.start_with?('http://', 'https://')
|
||||||
end
|
end
|
||||||
|
|
||||||
def invalid_origin?(url)
|
def non_matching_uri_hosts?(base_url, comparison_url)
|
||||||
return true if unsupported_uri_scheme?(url)
|
return true if unsupported_uri_scheme?(comparison_url)
|
||||||
|
|
||||||
needle = Addressable::URI.parse(url).host
|
needle = Addressable::URI.parse(comparison_url).host
|
||||||
haystack = Addressable::URI.parse(@account.uri).host
|
haystack = Addressable::URI.parse(base_url).host
|
||||||
|
|
||||||
!haystack.casecmp(needle).zero?
|
!haystack.casecmp(needle).zero?
|
||||||
end
|
end
|
||||||
|
|
|
@ -201,7 +201,6 @@ module LanguagesHelper
|
||||||
sma: ['Southern Sami', 'Åarjelsaemien Gïele'].freeze,
|
sma: ['Southern Sami', 'Åarjelsaemien Gïele'].freeze,
|
||||||
smj: ['Lule Sami', 'Julevsámegiella'].freeze,
|
smj: ['Lule Sami', 'Julevsámegiella'].freeze,
|
||||||
szl: ['Silesian', 'ślůnsko godka'].freeze,
|
szl: ['Silesian', 'ślůnsko godka'].freeze,
|
||||||
tai: ['Tai', 'ภาษาไท or ภาษาไต'].freeze,
|
|
||||||
tok: ['Toki Pona', 'toki pona'].freeze,
|
tok: ['Toki Pona', 'toki pona'].freeze,
|
||||||
zba: ['Balaibalan', 'باليبلن'].freeze,
|
zba: ['Balaibalan', 'باليبلن'].freeze,
|
||||||
zgh: ['Standard Moroccan Tamazight', 'ⵜⴰⵎⴰⵣⵉⵖⵜ'].freeze,
|
zgh: ['Standard Moroccan Tamazight', 'ⵜⴰⵎⴰⵣⵉⵖⵜ'].freeze,
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module MediaComponentHelper
|
||||||
|
def render_video_component(status, **options)
|
||||||
|
video = status.ordered_media_attachments.first
|
||||||
|
|
||||||
|
meta = video.file.meta || {}
|
||||||
|
|
||||||
|
component_params = {
|
||||||
|
sensitive: sensitive_viewer?(status, current_account),
|
||||||
|
src: full_asset_url(video.file.url(:original)),
|
||||||
|
preview: full_asset_url(video.thumbnail.present? ? video.thumbnail.url : video.file.url(:small)),
|
||||||
|
alt: video.description,
|
||||||
|
blurhash: video.blurhash,
|
||||||
|
frameRate: meta.dig('original', 'frame_rate'),
|
||||||
|
inline: true,
|
||||||
|
media: [
|
||||||
|
serialize_media_attachment(video),
|
||||||
|
].as_json,
|
||||||
|
}.merge(**options)
|
||||||
|
|
||||||
|
react_component :video, component_params do
|
||||||
|
render partial: 'statuses/attachment_list', locals: { attachments: status.ordered_media_attachments }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_audio_component(status, **options)
|
||||||
|
audio = status.ordered_media_attachments.first
|
||||||
|
|
||||||
|
meta = audio.file.meta || {}
|
||||||
|
|
||||||
|
component_params = {
|
||||||
|
src: full_asset_url(audio.file.url(:original)),
|
||||||
|
poster: full_asset_url(audio.thumbnail.present? ? audio.thumbnail.url : status.account.avatar_static_url),
|
||||||
|
alt: audio.description,
|
||||||
|
backgroundColor: meta.dig('colors', 'background'),
|
||||||
|
foregroundColor: meta.dig('colors', 'foreground'),
|
||||||
|
accentColor: meta.dig('colors', 'accent'),
|
||||||
|
duration: meta.dig('original', 'duration'),
|
||||||
|
}.merge(**options)
|
||||||
|
|
||||||
|
react_component :audio, component_params do
|
||||||
|
render partial: 'statuses/attachment_list', locals: { attachments: status.ordered_media_attachments }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_media_gallery_component(status, **options)
|
||||||
|
component_params = {
|
||||||
|
sensitive: sensitive_viewer?(status, current_account),
|
||||||
|
autoplay: prefers_autoplay?,
|
||||||
|
media: status.ordered_media_attachments.map { |a| serialize_media_attachment(a).as_json },
|
||||||
|
}.merge(**options)
|
||||||
|
|
||||||
|
react_component :media_gallery, component_params do
|
||||||
|
render partial: 'statuses/attachment_list', locals: { attachments: status.ordered_media_attachments }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_card_component(status, **options)
|
||||||
|
component_params = {
|
||||||
|
sensitive: sensitive_viewer?(status, current_account),
|
||||||
|
card: serialize_status_card(status).as_json,
|
||||||
|
}.merge(**options)
|
||||||
|
|
||||||
|
react_component :card, component_params
|
||||||
|
end
|
||||||
|
|
||||||
|
def render_poll_component(status, **options)
|
||||||
|
component_params = {
|
||||||
|
disabled: true,
|
||||||
|
poll: serialize_status_poll(status).as_json,
|
||||||
|
}.merge(**options)
|
||||||
|
|
||||||
|
react_component :poll, component_params do
|
||||||
|
render partial: 'statuses/poll', locals: { status: status, poll: status.preloadable_poll, autoplay: prefers_autoplay? }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def serialize_media_attachment(attachment)
|
||||||
|
ActiveModelSerializers::SerializableResource.new(
|
||||||
|
attachment,
|
||||||
|
serializer: REST::MediaAttachmentSerializer
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize_status_card(status)
|
||||||
|
ActiveModelSerializers::SerializableResource.new(
|
||||||
|
status.preview_card,
|
||||||
|
serializer: REST::PreviewCardSerializer
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize_status_poll(status)
|
||||||
|
ActiveModelSerializers::SerializableResource.new(
|
||||||
|
status.preloadable_poll,
|
||||||
|
serializer: REST::PollSerializer,
|
||||||
|
scope: current_user,
|
||||||
|
scope_name: :current_user
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def sensitive_viewer?(status, account)
|
||||||
|
if !account.nil? && account.id == status.account_id
|
||||||
|
status.sensitive
|
||||||
|
else
|
||||||
|
status.account.sensitized? || status.sensitive
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,23 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module ReactComponentHelper
|
||||||
|
def react_component(name, props = {}, &block)
|
||||||
|
data = { component: name.to_s.camelcase, props: Oj.dump(props) }
|
||||||
|
if block.nil?
|
||||||
|
div_tag_with_data(data)
|
||||||
|
else
|
||||||
|
content_tag(:div, data: data, &block)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def react_admin_component(name, props = {})
|
||||||
|
data = { 'admin-component': name.to_s.camelcase, props: Oj.dump({ locale: I18n.locale }.merge(props)) }
|
||||||
|
div_tag_with_data(data)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def div_tag_with_data(data)
|
||||||
|
content_tag(:div, nil, data: data)
|
||||||
|
end
|
||||||
|
end
|
|
@ -105,94 +105,10 @@ module StatusesHelper
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def sensitized?(status, account)
|
|
||||||
if !account.nil? && account.id == status.account_id
|
|
||||||
status.sensitive
|
|
||||||
else
|
|
||||||
status.account.sensitized? || status.sensitive
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def embedded_view?
|
def embedded_view?
|
||||||
params[:controller] == EMBEDDED_CONTROLLER && params[:action] == EMBEDDED_ACTION
|
params[:controller] == EMBEDDED_CONTROLLER && params[:action] == EMBEDDED_ACTION
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_video_component(status, **options)
|
|
||||||
video = status.ordered_media_attachments.first
|
|
||||||
|
|
||||||
meta = video.file.meta || {}
|
|
||||||
|
|
||||||
component_params = {
|
|
||||||
sensitive: sensitized?(status, current_account),
|
|
||||||
src: full_asset_url(video.file.url(:original)),
|
|
||||||
preview: full_asset_url(video.thumbnail.present? ? video.thumbnail.url : video.file.url(:small)),
|
|
||||||
alt: video.description,
|
|
||||||
blurhash: video.blurhash,
|
|
||||||
frameRate: meta.dig('original', 'frame_rate'),
|
|
||||||
inline: true,
|
|
||||||
media: [
|
|
||||||
ActiveModelSerializers::SerializableResource.new(video, serializer: REST::MediaAttachmentSerializer),
|
|
||||||
].as_json,
|
|
||||||
}.merge(**options)
|
|
||||||
|
|
||||||
react_component :video, component_params do
|
|
||||||
render partial: 'statuses/attachment_list', locals: { attachments: status.ordered_media_attachments }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_audio_component(status, **options)
|
|
||||||
audio = status.ordered_media_attachments.first
|
|
||||||
|
|
||||||
meta = audio.file.meta || {}
|
|
||||||
|
|
||||||
component_params = {
|
|
||||||
src: full_asset_url(audio.file.url(:original)),
|
|
||||||
poster: full_asset_url(audio.thumbnail.present? ? audio.thumbnail.url : status.account.avatar_static_url),
|
|
||||||
alt: audio.description,
|
|
||||||
backgroundColor: meta.dig('colors', 'background'),
|
|
||||||
foregroundColor: meta.dig('colors', 'foreground'),
|
|
||||||
accentColor: meta.dig('colors', 'accent'),
|
|
||||||
duration: meta.dig('original', 'duration'),
|
|
||||||
}.merge(**options)
|
|
||||||
|
|
||||||
react_component :audio, component_params do
|
|
||||||
render partial: 'statuses/attachment_list', locals: { attachments: status.ordered_media_attachments }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_media_gallery_component(status, **options)
|
|
||||||
component_params = {
|
|
||||||
sensitive: sensitized?(status, current_account),
|
|
||||||
autoplay: prefers_autoplay?,
|
|
||||||
media: status.ordered_media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json },
|
|
||||||
}.merge(**options)
|
|
||||||
|
|
||||||
react_component :media_gallery, component_params do
|
|
||||||
render partial: 'statuses/attachment_list', locals: { attachments: status.ordered_media_attachments }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_card_component(status, **options)
|
|
||||||
component_params = {
|
|
||||||
sensitive: sensitized?(status, current_account),
|
|
||||||
maxDescription: 160,
|
|
||||||
card: ActiveModelSerializers::SerializableResource.new(status.preview_card, serializer: REST::PreviewCardSerializer).as_json,
|
|
||||||
}.merge(**options)
|
|
||||||
|
|
||||||
react_component :card, component_params
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_poll_component(status, **options)
|
|
||||||
component_params = {
|
|
||||||
disabled: true,
|
|
||||||
poll: ActiveModelSerializers::SerializableResource.new(status.preloadable_poll, serializer: REST::PollSerializer, scope: current_user, scope_name: :current_user).as_json,
|
|
||||||
}.merge(**options)
|
|
||||||
|
|
||||||
react_component :poll, component_params do
|
|
||||||
render partial: 'statuses/poll', locals: { status: status, poll: status.preloadable_poll, autoplay: prefers_autoplay? }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def prefers_autoplay?
|
def prefers_autoplay?
|
||||||
ActiveModel::Type::Boolean.new.cast(params[:autoplay]) || current_user&.setting_auto_play_gif
|
ActiveModel::Type::Boolean.new.cast(params[:autoplay]) || current_user&.setting_auto_play_gif
|
||||||
end
|
end
|
||||||
|
|
|
@ -32,7 +32,7 @@ class EditedTimestamp extends React.PureComponent {
|
||||||
|
|
||||||
renderHeader = items => {
|
renderHeader = items => {
|
||||||
return (
|
return (
|
||||||
<FormattedMessage id='status.edited_x_times' defaultMessage='Edited {count, plural, one {{count} time} other {{count} times}}' values={{ count: items.size - 1 }} />
|
<FormattedMessage id='status.edited_x_times' defaultMessage='Edited {count, plural, one {# time} other {# times}}' values={{ count: items.size - 1 }} />
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ class SilentErrorBoundary extends React.Component {
|
||||||
export const accountsCountRenderer = (displayNumber, pluralReady) => (
|
export const accountsCountRenderer = (displayNumber, pluralReady) => (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='trends.counter_by_accounts'
|
id='trends.counter_by_accounts'
|
||||||
defaultMessage='{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {{days} days}}'
|
defaultMessage='{count, plural, one {{counter} person} other {{counter} people}} in the past {days, plural, one {day} other {# days}}'
|
||||||
values={{
|
values={{
|
||||||
count: pluralReady,
|
count: pluralReady,
|
||||||
counter: <strong>{displayNumber}</strong>,
|
counter: <strong>{displayNumber}</strong>,
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import logo from 'mastodon/../images/logo.svg';
|
||||||
|
|
||||||
const Logo = () => (
|
export const WordmarkLogo = () => (
|
||||||
<svg viewBox='0 0 261 66' className='logo' role='img'>
|
<svg viewBox='0 0 261 66' className='logo logo--wordmark' role='img'>
|
||||||
<title>Mastodon</title>
|
<title>Mastodon</title>
|
||||||
<use xlinkHref='#logo-symbol-wordmark' />
|
<use xlinkHref='#logo-symbol-wordmark' />
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default Logo;
|
export const SymbolLogo = () => (
|
||||||
|
<img src={logo} alt='Mastodon' className='logo logo--icon' />
|
||||||
|
);
|
||||||
|
|
||||||
|
export default WordmarkLogo;
|
||||||
|
|
|
@ -32,17 +32,14 @@ function ShortNumber({ value, renderer, children }) {
|
||||||
const shortNumber = toShortNumber(value);
|
const shortNumber = toShortNumber(value);
|
||||||
const [, division] = shortNumber;
|
const [, division] = shortNumber;
|
||||||
|
|
||||||
// eslint-disable-next-line eqeqeq
|
|
||||||
if (children != null && renderer != null) {
|
if (children != null && renderer != null) {
|
||||||
console.warn('Both renderer prop and renderer as a child provided. This is a mistake and you really should fix that. Only renderer passed as a child will be used.');
|
console.warn('Both renderer prop and renderer as a child provided. This is a mistake and you really should fix that. Only renderer passed as a child will be used.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line eqeqeq
|
|
||||||
const customRenderer = children != null ? children : renderer;
|
const customRenderer = children != null ? children : renderer;
|
||||||
|
|
||||||
const displayNumber = <ShortNumberCounter value={shortNumber} />;
|
const displayNumber = <ShortNumberCounter value={shortNumber} />;
|
||||||
|
|
||||||
// eslint-disable-next-line eqeqeq
|
|
||||||
return customRenderer != null
|
return customRenderer != null
|
||||||
? customRenderer(displayNumber, pluralReady(value, division))
|
? customRenderer(displayNumber, pluralReady(value, division))
|
||||||
: displayNumber;
|
: displayNumber;
|
||||||
|
|
|
@ -67,6 +67,9 @@ class Status extends ImmutablePureComponent {
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
status: ImmutablePropTypes.map,
|
status: ImmutablePropTypes.map,
|
||||||
account: ImmutablePropTypes.map,
|
account: ImmutablePropTypes.map,
|
||||||
|
previousId: PropTypes.string,
|
||||||
|
nextInReplyToId: PropTypes.string,
|
||||||
|
rootId: PropTypes.string,
|
||||||
onReply: PropTypes.func,
|
onReply: PropTypes.func,
|
||||||
onFavourite: PropTypes.func,
|
onFavourite: PropTypes.func,
|
||||||
onReblog: PropTypes.func,
|
onReblog: PropTypes.func,
|
||||||
|
@ -518,6 +521,9 @@ class Status extends ImmutablePureComponent {
|
||||||
unread,
|
unread,
|
||||||
featured,
|
featured,
|
||||||
pictureInPicture,
|
pictureInPicture,
|
||||||
|
previousId,
|
||||||
|
nextInReplyToId,
|
||||||
|
rootId,
|
||||||
...other
|
...other
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { isCollapsed, forceFilter } = this.state;
|
const { isCollapsed, forceFilter } = this.state;
|
||||||
|
@ -561,6 +567,8 @@ class Status extends ImmutablePureComponent {
|
||||||
openMedia: this.handleHotkeyOpenMedia,
|
openMedia: this.handleHotkeyOpenMedia,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let prepend, rebloggedByText;
|
||||||
|
|
||||||
if (hidden) {
|
if (hidden) {
|
||||||
return (
|
return (
|
||||||
<HotKeys handlers={handlers}>
|
<HotKeys handlers={handlers}>
|
||||||
|
@ -572,7 +580,11 @@ class Status extends ImmutablePureComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const connectUp = previousId && previousId === status.get('in_reply_to_id');
|
||||||
|
const connectToRoot = rootId && rootId === status.get('in_reply_to_id');
|
||||||
|
const connectReply = nextInReplyToId && nextInReplyToId === status.get('id');
|
||||||
const matchedFilters = status.get('matched_filters');
|
const matchedFilters = status.get('matched_filters');
|
||||||
|
|
||||||
if (this.state.forceFilter === undefined ? matchedFilters : this.state.forceFilter) {
|
if (this.state.forceFilter === undefined ? matchedFilters : this.state.forceFilter) {
|
||||||
const minHandlers = this.props.muted ? {} : {
|
const minHandlers = this.props.muted ? {} : {
|
||||||
moveUp: this.handleHotkeyMoveUp,
|
moveUp: this.handleHotkeyMoveUp,
|
||||||
|
@ -663,7 +675,7 @@ class Status extends ImmutablePureComponent {
|
||||||
inline
|
inline
|
||||||
sensitive={status.get('sensitive')}
|
sensitive={status.get('sensitive')}
|
||||||
letterbox={settings.getIn(['media', 'letterbox'])}
|
letterbox={settings.getIn(['media', 'letterbox'])}
|
||||||
fullwidth={settings.getIn(['media', 'fullwidth'])}
|
fullwidth={!rootId && settings.getIn(['media', 'fullwidth'])}
|
||||||
preventPlayback={isCollapsed || !isExpanded}
|
preventPlayback={isCollapsed || !isExpanded}
|
||||||
onOpenVideo={this.handleOpenVideo}
|
onOpenVideo={this.handleOpenVideo}
|
||||||
width={this.props.cachedMediaWidth}
|
width={this.props.cachedMediaWidth}
|
||||||
|
@ -684,7 +696,7 @@ class Status extends ImmutablePureComponent {
|
||||||
lang={status.get('language')}
|
lang={status.get('language')}
|
||||||
sensitive={status.get('sensitive')}
|
sensitive={status.get('sensitive')}
|
||||||
letterbox={settings.getIn(['media', 'letterbox'])}
|
letterbox={settings.getIn(['media', 'letterbox'])}
|
||||||
fullwidth={settings.getIn(['media', 'fullwidth'])}
|
fullwidth={!rootId && settings.getIn(['media', 'fullwidth'])}
|
||||||
hidden={isCollapsed || !isExpanded}
|
hidden={isCollapsed || !isExpanded}
|
||||||
onOpenMedia={this.handleOpenMedia}
|
onOpenMedia={this.handleOpenMedia}
|
||||||
cacheWidth={this.props.cacheMediaWidth}
|
cacheWidth={this.props.cacheMediaWidth}
|
||||||
|
@ -726,8 +738,6 @@ class Status extends ImmutablePureComponent {
|
||||||
'data-status-by': `@${status.getIn(['account', 'acct'])}`,
|
'data-status-by': `@${status.getIn(['account', 'acct'])}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
let prepend;
|
|
||||||
|
|
||||||
if (this.props.prepend && account) {
|
if (this.props.prepend && account) {
|
||||||
const notifKind = {
|
const notifKind = {
|
||||||
favourite: 'favourited',
|
favourite: 'favourited',
|
||||||
|
@ -748,8 +758,6 @@ class Status extends ImmutablePureComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let rebloggedByText;
|
|
||||||
|
|
||||||
if (this.props.prepend === 'reblog') {
|
if (this.props.prepend === 'reblog') {
|
||||||
rebloggedByText = intl.formatMessage({ id: 'status.reblogged_by', defaultMessage: '{name} boosted' }, { name: account.get('acct') });
|
rebloggedByText = intl.formatMessage({ id: 'status.reblogged_by', defaultMessage: '{name} boosted' }, { name: account.get('acct') });
|
||||||
}
|
}
|
||||||
|
@ -758,6 +766,8 @@ class Status extends ImmutablePureComponent {
|
||||||
collapsed: isCollapsed,
|
collapsed: isCollapsed,
|
||||||
'has-background': isCollapsed && background,
|
'has-background': isCollapsed && background,
|
||||||
'status__wrapper-reply': !!status.get('in_reply_to_id'),
|
'status__wrapper-reply': !!status.get('in_reply_to_id'),
|
||||||
|
'status--in-thread': !!rootId,
|
||||||
|
'status--first-in-thread': previousId && (!connectUp || connectToRoot),
|
||||||
unread,
|
unread,
|
||||||
muted,
|
muted,
|
||||||
}, 'focusable');
|
}, 'focusable');
|
||||||
|
@ -774,6 +784,9 @@ class Status extends ImmutablePureComponent {
|
||||||
aria-label={textForScreenReader(intl, status, rebloggedByText, !status.get('hidden'))}
|
aria-label={textForScreenReader(intl, status, rebloggedByText, !status.get('hidden'))}
|
||||||
>
|
>
|
||||||
{!muted && prepend}
|
{!muted && prepend}
|
||||||
|
|
||||||
|
{(connectReply || connectUp || connectToRoot) && <div className={classNames('status__line', { 'status__line--full': connectReply, 'status__line--first': !status.get('in_reply_to_id') && !connectToRoot })} />}
|
||||||
|
|
||||||
<header className='status__info'>
|
<header className='status__info'>
|
||||||
<span>
|
<span>
|
||||||
{muted && prepend}
|
{muted && prepend}
|
||||||
|
|
|
@ -83,6 +83,7 @@ const makeMapStateToProps = () => {
|
||||||
return {
|
return {
|
||||||
containerId: props.containerId || props.id, // Should match reblogStatus's id for reblogs
|
containerId: props.containerId || props.id, // Should match reblogStatus's id for reblogs
|
||||||
status: status,
|
status: status,
|
||||||
|
nextInReplyToId: props.nextId ? state.getIn(['statuses', props.nextId, 'in_reply_to_id']) : null,
|
||||||
account: account || props.account,
|
account: account || props.account,
|
||||||
settings: state.get('local_settings'),
|
settings: state.get('local_settings'),
|
||||||
prepend: prepend || props.prepend,
|
prepend: prepend || props.prepend,
|
||||||
|
|
|
@ -127,7 +127,7 @@ class SearchResults extends ImmutablePureComponent {
|
||||||
<div className='drawer--results'>
|
<div className='drawer--results'>
|
||||||
<header className='search-results__header'>
|
<header className='search-results__header'>
|
||||||
<Icon id='search' fixedWidth />
|
<Icon id='search' fixedWidth />
|
||||||
<FormattedMessage id='search_results.total' defaultMessage='{count, number} {count, plural, one {result} other {results}}' values={{ count }} />
|
<FormattedMessage id='search_results.total' defaultMessage='{count, plural, one {# result} other {# results}}' values={{ count }} />
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{accounts}
|
{accounts}
|
||||||
|
|
|
@ -20,68 +20,88 @@ const emojiFilename = (filename) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const emojifyTextNode = (node, customEmojis) => {
|
const emojifyTextNode = (node, customEmojis) => {
|
||||||
|
const VS15 = 0xFE0E;
|
||||||
|
const VS16 = 0xFE0F;
|
||||||
|
|
||||||
let str = node.textContent;
|
let str = node.textContent;
|
||||||
|
|
||||||
const fragment = new DocumentFragment();
|
const fragment = new DocumentFragment();
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
let match, i = 0;
|
let unicode_emoji;
|
||||||
|
|
||||||
|
// Skip to the next potential emoji to replace (either custom emoji or custom emoji :shortcode:)
|
||||||
if (customEmojis === null) {
|
if (customEmojis === null) {
|
||||||
while (i < str.length && (useSystemEmojiFont || !(match = trie.search(str.slice(i))))) {
|
while (i < str.length && (useSystemEmojiFont || !(unicode_emoji = trie.search(str.slice(i))))) {
|
||||||
i += str.codePointAt(i) < 65536 ? 1 : 2;
|
i += str.codePointAt(i) < 65536 ? 1 : 2;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
while (i < str.length && str[i] !== ':' && (useSystemEmojiFont || !(match = trie.search(str.slice(i))))) {
|
while (i < str.length && str[i] !== ':' && (useSystemEmojiFont || !(unicode_emoji = trie.search(str.slice(i))))) {
|
||||||
i += str.codePointAt(i) < 65536 ? 1 : 2;
|
i += str.codePointAt(i) < 65536 ? 1 : 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let rend, replacement = null;
|
// We reached the end of the string, nothing to replace
|
||||||
if (i === str.length) {
|
if (i === str.length) {
|
||||||
break;
|
break;
|
||||||
} else if (str[i] === ':') {
|
}
|
||||||
if (!(() => {
|
|
||||||
|
let rend, replacement = null;
|
||||||
|
if (str[i] === ':') { // Potentially the start of a custom emoji :shortcode:
|
||||||
rend = str.indexOf(':', i + 1) + 1;
|
rend = str.indexOf(':', i + 1) + 1;
|
||||||
if (!rend) return false; // no pair of ':'
|
|
||||||
const shortname = str.slice(i, rend);
|
// no matching ending ':', skip
|
||||||
// now got a replacee as ':shortname:'
|
if (!rend) {
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const shortcode = str.slice(i, rend);
|
||||||
|
const custom_emoji = customEmojis[shortcode];
|
||||||
|
|
||||||
|
// not a recognized shortcode, skip
|
||||||
|
if (!custom_emoji) {
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// now got a replacee as ':shortcode:'
|
||||||
// if you want additional emoji handler, add statements below which set replacement and return true.
|
// if you want additional emoji handler, add statements below which set replacement and return true.
|
||||||
if (shortname in customEmojis) {
|
const filename = autoPlayGif ? custom_emoji.url : custom_emoji.static_url;
|
||||||
const filename = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url;
|
|
||||||
replacement = document.createElement('img');
|
replacement = document.createElement('img');
|
||||||
replacement.setAttribute('draggable', 'false');
|
replacement.setAttribute('draggable', 'false');
|
||||||
replacement.setAttribute('class', 'emojione custom-emoji');
|
replacement.setAttribute('class', 'emojione custom-emoji');
|
||||||
replacement.setAttribute('alt', shortname);
|
replacement.setAttribute('alt', shortcode);
|
||||||
replacement.setAttribute('title', shortname);
|
replacement.setAttribute('title', shortcode);
|
||||||
replacement.setAttribute('src', filename);
|
replacement.setAttribute('src', filename);
|
||||||
replacement.setAttribute('data-original', customEmojis[shortname].url);
|
replacement.setAttribute('data-original', custom_emoji.url);
|
||||||
replacement.setAttribute('data-static', customEmojis[shortname].static_url);
|
replacement.setAttribute('data-static', custom_emoji.static_url);
|
||||||
return true;
|
} else { // start of an unicode emoji
|
||||||
|
rend = i + unicode_emoji.length;
|
||||||
|
|
||||||
|
// If the matched character was followed by VS15 (for selecting text presentation), skip it.
|
||||||
|
if (str.codePointAt(rend - 1) !== VS16 && str.codePointAt(rend) === VS15) {
|
||||||
|
i = rend + 1;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
})()) rend = ++i;
|
const { filename, shortCode } = unicodeMapping[unicode_emoji];
|
||||||
} else if (!useSystemEmojiFont) { // matched to unicode emoji
|
|
||||||
const { filename, shortCode } = unicodeMapping[match];
|
|
||||||
const title = shortCode ? `:${shortCode}:` : '';
|
const title = shortCode ? `:${shortCode}:` : '';
|
||||||
|
|
||||||
replacement = document.createElement('img');
|
replacement = document.createElement('img');
|
||||||
replacement.setAttribute('draggable', 'false');
|
replacement.setAttribute('draggable', 'false');
|
||||||
replacement.setAttribute('class', 'emojione');
|
replacement.setAttribute('class', 'emojione');
|
||||||
replacement.setAttribute('alt', match);
|
replacement.setAttribute('alt', unicode_emoji);
|
||||||
replacement.setAttribute('title', title);
|
replacement.setAttribute('title', title);
|
||||||
replacement.setAttribute('src', `${assetHost}/emoji/${emojiFilename(filename)}.svg`);
|
replacement.setAttribute('src', `${assetHost}/emoji/${emojiFilename(filename)}.svg`);
|
||||||
rend = i + match.length;
|
|
||||||
// If the matched character was followed by VS15 (for selecting text presentation), skip it.
|
|
||||||
if (str.codePointAt(rend) === 65038) {
|
|
||||||
rend += 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add the processed-up-to-now string and the emoji replacement
|
||||||
fragment.append(document.createTextNode(str.slice(0, i)));
|
fragment.append(document.createTextNode(str.slice(0, i)));
|
||||||
if (replacement) {
|
|
||||||
fragment.append(replacement);
|
fragment.append(replacement);
|
||||||
}
|
|
||||||
str = str.slice(rend);
|
str = str.slice(rend);
|
||||||
|
i = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
fragment.append(document.createTextNode(str));
|
fragment.append(document.createTextNode(str));
|
||||||
|
|
|
@ -71,17 +71,20 @@ class Explore extends React.PureComponent {
|
||||||
<NavLink exact to='/explore'>
|
<NavLink exact to='/explore'>
|
||||||
<FormattedMessage tagName='div' id='explore.trending_statuses' defaultMessage='Posts' />
|
<FormattedMessage tagName='div' id='explore.trending_statuses' defaultMessage='Posts' />
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|
||||||
<NavLink exact to='/explore/tags'>
|
<NavLink exact to='/explore/tags'>
|
||||||
<FormattedMessage tagName='div' id='explore.trending_tags' defaultMessage='Hashtags' />
|
<FormattedMessage tagName='div' id='explore.trending_tags' defaultMessage='Hashtags' />
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|
||||||
|
{signedIn && (
|
||||||
|
<NavLink exact to='/explore/suggestions'>
|
||||||
|
<FormattedMessage tagName='div' id='explore.suggested_follows' defaultMessage='People' />
|
||||||
|
</NavLink>
|
||||||
|
)}
|
||||||
|
|
||||||
<NavLink exact to='/explore/links'>
|
<NavLink exact to='/explore/links'>
|
||||||
<FormattedMessage tagName='div' id='explore.trending_links' defaultMessage='News' />
|
<FormattedMessage tagName='div' id='explore.trending_links' defaultMessage='News' />
|
||||||
</NavLink>
|
</NavLink>
|
||||||
{signedIn && (
|
|
||||||
<NavLink exact to='/explore/suggestions'>
|
|
||||||
<FormattedMessage tagName='div' id='explore.suggested_follows' defaultMessage='For you' />
|
|
||||||
</NavLink>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Switch>
|
<Switch>
|
||||||
|
|
|
@ -45,7 +45,7 @@ class Report extends ImmutablePureComponent {
|
||||||
|
|
||||||
<div className='notification__report__details'>
|
<div className='notification__report__details'>
|
||||||
<div>
|
<div>
|
||||||
<RelativeTimestamp timestamp={report.get('created_at')} short={false} /> · <FormattedMessage id='report_notification.attached_statuses' defaultMessage='{count, plural, one {{count} post} other {{count} posts}} attached' values={{ count: report.get('status_ids').size }} />
|
<RelativeTimestamp timestamp={report.get('created_at')} short={false} /> · <FormattedMessage id='report_notification.attached_statuses' defaultMessage='{count, plural, one {# post} other {# posts}} attached' values={{ count: report.get('status_ids').size }} />
|
||||||
<br />
|
<br />
|
||||||
<strong>{intl.formatMessage(messages[report.get('category')])}</strong>
|
<strong>{intl.formatMessage(messages[report.get('category')])}</strong>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -17,16 +17,6 @@ const getHostname = url => {
|
||||||
return parser.hostname;
|
return parser.hostname;
|
||||||
};
|
};
|
||||||
|
|
||||||
const trim = (text, len) => {
|
|
||||||
const cut = text.indexOf(' ', len);
|
|
||||||
|
|
||||||
if (cut === -1) {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
return text.slice(0, cut) + (text.length > len ? '…' : '');
|
|
||||||
};
|
|
||||||
|
|
||||||
const domParser = new DOMParser();
|
const domParser = new DOMParser();
|
||||||
|
|
||||||
const addAutoPlay = html => {
|
const addAutoPlay = html => {
|
||||||
|
@ -54,7 +44,6 @@ export default class Card extends React.PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
card: ImmutablePropTypes.map,
|
card: ImmutablePropTypes.map,
|
||||||
maxDescription: PropTypes.number,
|
|
||||||
onOpenMedia: PropTypes.func.isRequired,
|
onOpenMedia: PropTypes.func.isRequired,
|
||||||
compact: PropTypes.bool,
|
compact: PropTypes.bool,
|
||||||
defaultWidth: PropTypes.number,
|
defaultWidth: PropTypes.number,
|
||||||
|
@ -63,7 +52,6 @@ export default class Card extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
maxDescription: 50,
|
|
||||||
compact: false,
|
compact: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -176,7 +164,7 @@ export default class Card extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { card, maxDescription, compact, defaultWidth } = this.props;
|
const { card, compact, defaultWidth } = this.props;
|
||||||
const { width, embedded, revealed } = this.state;
|
const { width, embedded, revealed } = this.state;
|
||||||
|
|
||||||
if (card === null) {
|
if (card === null) {
|
||||||
|
@ -195,7 +183,7 @@ export default class Card extends React.PureComponent {
|
||||||
const description = (
|
const description = (
|
||||||
<div className='status-card__content' lang={language}>
|
<div className='status-card__content' lang={language}>
|
||||||
{title}
|
{title}
|
||||||
{!(horizontal || compact) && <p className='status-card__description'>{trim(card.get('description') || '', maxDescription)}</p>}
|
{!(horizontal || compact) && <p className='status-card__description' title={card.get('description')}>{card.get('description')}</p>}
|
||||||
<span className='status-card__host'>{provider}</span>
|
<span className='status-card__host'>{provider}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -63,7 +63,7 @@ const messages = defineMessages({
|
||||||
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.' },
|
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? You will lose all replies, boosts and favourites to it.' },
|
||||||
revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' },
|
revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' },
|
||||||
hideAll: { id: 'status.show_less_all', defaultMessage: 'Show less for all' },
|
hideAll: { id: 'status.show_less_all', defaultMessage: 'Show less for all' },
|
||||||
statusTitleWithAttachments: { id: 'status.title.with_attachments', defaultMessage: '{user} posted {attachmentCount, plural, one {an attachment} other {{attachmentCount} attachments}}' },
|
statusTitleWithAttachments: { id: 'status.title.with_attachments', defaultMessage: '{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}' },
|
||||||
detailedStatus: { id: 'status.detailed_status', defaultMessage: 'Detailed conversation view' },
|
detailedStatus: { id: 'status.detailed_status', defaultMessage: 'Detailed conversation view' },
|
||||||
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
|
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
|
||||||
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
||||||
|
@ -559,8 +559,10 @@ class Status extends ImmutablePureComponent {
|
||||||
this.column.scrollTop();
|
this.column.scrollTop();
|
||||||
};
|
};
|
||||||
|
|
||||||
renderChildren (list) {
|
renderChildren (list, ancestors) {
|
||||||
return list.map(id => (
|
const { params: { statusId } } = this.props;
|
||||||
|
|
||||||
|
return list.map((id, i) => (
|
||||||
<StatusContainer
|
<StatusContainer
|
||||||
key={id}
|
key={id}
|
||||||
id={id}
|
id={id}
|
||||||
|
@ -568,6 +570,9 @@ class Status extends ImmutablePureComponent {
|
||||||
onMoveUp={this.handleMoveUp}
|
onMoveUp={this.handleMoveUp}
|
||||||
onMoveDown={this.handleMoveDown}
|
onMoveDown={this.handleMoveDown}
|
||||||
contextType='thread'
|
contextType='thread'
|
||||||
|
previousId={i > 0 && list.get(i - 1)}
|
||||||
|
nextId={list.get(i + 1) || (ancestors && statusId)}
|
||||||
|
rootId={statusId}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -628,7 +633,7 @@ class Status extends ImmutablePureComponent {
|
||||||
const isExpanded = settings.getIn(['content_warnings', 'shared_state']) ? !status.get('hidden') : this.state.isExpanded;
|
const isExpanded = settings.getIn(['content_warnings', 'shared_state']) ? !status.get('hidden') : this.state.isExpanded;
|
||||||
|
|
||||||
if (ancestorsIds && ancestorsIds.size > 0) {
|
if (ancestorsIds && ancestorsIds.size > 0) {
|
||||||
ancestors = <>{this.renderChildren(ancestorsIds)}</>;
|
ancestors = <>{this.renderChildren(ancestorsIds, true)}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (descendantsIds && descendantsIds.size > 0) {
|
if (descendantsIds && descendantsIds.size > 0) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Logo from 'flavours/glitch/components/logo';
|
import { WordmarkLogo, SymbolLogo } from 'flavours/glitch/components/logo';
|
||||||
import { Link, withRouter } from 'react-router-dom';
|
import { Link, withRouter } from 'react-router-dom';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { registrationsOpen, me } from 'flavours/glitch/initial_state';
|
import { registrationsOpen, me } from 'flavours/glitch/initial_state';
|
||||||
|
@ -74,7 +74,10 @@ class Header extends React.PureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='ui__header'>
|
<div className='ui__header'>
|
||||||
<Link to='/' className='ui__header__logo'><Logo /></Link>
|
<Link to='/' className='ui__header__logo'>
|
||||||
|
<WordmarkLogo />
|
||||||
|
<SymbolLogo />
|
||||||
|
</Link>
|
||||||
|
|
||||||
<div className='ui__header__links'>
|
<div className='ui__header__links'>
|
||||||
{content}
|
{content}
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
{
|
{
|
||||||
|
"empty_column.follow_recommendations": "Looks like no suggestions could be generated for you. You can try using search to look for people you might know or explore trending hashtags.",
|
||||||
|
"follow_recommendations.done": "Done",
|
||||||
|
"follow_recommendations.heading": "Follow people you'd like to see posts from! Here are some suggestions.",
|
||||||
|
"follow_recommendations.lead": "Plasings van mense wat jy volg, kom chronologies in jou tuisvoer verby. Moenie huiwer nie. Volg na hartelus. As daar mense is wie se plasings jy nie meer wil sien nie, ontvolg hulle net!",
|
||||||
"onboarding.page_one.federation": "{domain} is an \"instance\" of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
|
"onboarding.page_one.federation": "{domain} is an \"instance\" of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
|
||||||
"onboarding.page_six.github": "{domain} runs on Glitchsoc. Glitchsoc is a friendly {fork} of {Mastodon}. Glitchsoc is fully compatible with all Mastodon apps and instances. Glitchsoc is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
|
"onboarding.page_six.github": "{domain} runs on Glitchsoc. Glitchsoc is a friendly {fork} of {Mastodon}. Glitchsoc is fully compatible with all Mastodon apps and instances. Glitchsoc is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
|
||||||
"settings.content_warnings": "Content warnings",
|
"settings.content_warnings": "Content warnings",
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
{
|
{
|
||||||
|
"empty_column.follow_recommendations": "Pareixe que no s'ha puesto chenerar garra sucherencia pa tu. Puetz prebar a buscar a chent que talment conoixcas u explorar los hashtags que son en tendencia.",
|
||||||
|
"follow_recommendations.done": "Feito",
|
||||||
|
"follow_recommendations.heading": "Sigue a chent que publique cosetas que te faigan goyo! Aquí tiens qualques sucherencias.",
|
||||||
|
"follow_recommendations.lead": "Las publicacions d'a chent a la quala sigas amaneixerán ordenadas cronolochicament en Inicio. No tiengas miedo de cometer errors, puetz deixar-les de seguir en qualsequier momento con a mesma facilidat!",
|
||||||
"onboarding.page_one.federation": "{domain} is an \"instance\" of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
|
"onboarding.page_one.federation": "{domain} is an \"instance\" of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
|
||||||
"onboarding.page_six.github": "{domain} runs on Glitchsoc. Glitchsoc is a friendly {fork} of {Mastodon}. Glitchsoc is fully compatible with all Mastodon apps and instances. Glitchsoc is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
|
"onboarding.page_six.github": "{domain} runs on Glitchsoc. Glitchsoc is a friendly {fork} of {Mastodon}. Glitchsoc is fully compatible with all Mastodon apps and instances. Glitchsoc is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
|
||||||
"settings.content_warnings": "Content warnings",
|
"settings.content_warnings": "Content warnings",
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
{
|
{
|
||||||
|
"empty_column.follow_recommendations": "يبدو أنه لا يمكن إنشاء أي اقتراحات لك. يمكنك البحث عن أشخاص قد تعرفهم أو استكشاف الوسوم الرائجة.",
|
||||||
|
"follow_recommendations.done": "تم",
|
||||||
|
"follow_recommendations.heading": "تابع الأشخاص الذين ترغب في رؤية منشوراتهم! إليك بعض الاقتراحات.",
|
||||||
|
"follow_recommendations.lead": "ستظهر منشورات الأشخاص الذين تُتابعتهم بترتيب تسلسلي زمني على صفحتك الرئيسية. لا تخف إذا ارتكبت أي أخطاء، تستطيع إلغاء متابعة أي شخص في أي وقت تريد!",
|
||||||
"onboarding.page_one.federation": "{domain} is an \"instance\" of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
|
"onboarding.page_one.federation": "{domain} is an \"instance\" of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
|
||||||
"onboarding.page_six.github": "{domain} runs on Glitchsoc. Glitchsoc is a friendly {fork} of {Mastodon}. Glitchsoc is fully compatible with all Mastodon apps and instances. Glitchsoc is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
|
"onboarding.page_six.github": "{domain} runs on Glitchsoc. Glitchsoc is a friendly {fork} of {Mastodon}. Glitchsoc is fully compatible with all Mastodon apps and instances. Glitchsoc is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
|
||||||
"settings.content_warnings": "Content warnings",
|
"settings.content_warnings": "Content warnings",
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
{
|
{
|
||||||
|
"empty_column.follow_recommendations": "Paez que nun se puen xenerar suxerencies pa ti. Pues tentar d'usar la busca p'atopar perfiles que pues conocer o esplorar les etiquetes en tendencia.",
|
||||||
|
"follow_recommendations.done": "Fecho",
|
||||||
|
"follow_recommendations.heading": "¡Sigui a perfiles que te prestaría ver nel feed personal! Equí tienes dalgunes suxerencies.",
|
||||||
|
"follow_recommendations.lead": "Los artículos de los perfiles que sigas van apaecer n'orde cronolóxicu nel to feed d'aniciu. ¡Nun tengas mieu d'enquivocate, pues dexar de siguilos con facilidá en cualesquier momentu!",
|
||||||
"onboarding.page_one.federation": "{domain} is an \"instance\" of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
|
"onboarding.page_one.federation": "{domain} is an \"instance\" of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
|
||||||
"onboarding.page_six.github": "{domain} runs on Glitchsoc. Glitchsoc is a friendly {fork} of {Mastodon}. Glitchsoc is fully compatible with all Mastodon apps and instances. Glitchsoc is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
|
"onboarding.page_six.github": "{domain} runs on Glitchsoc. Glitchsoc is a friendly {fork} of {Mastodon}. Glitchsoc is fully compatible with all Mastodon apps and instances. Glitchsoc is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
|
||||||
"settings.content_warnings": "Content warnings",
|
"settings.content_warnings": "Content warnings",
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
{
|
{
|
||||||
|
"empty_column.follow_recommendations": "Здаецца, прапаноў для вас няма. Вы можаце паспрабаваць выкарыстаць пошук, каб знайсці людзей, якіх вы можаце ведаць, ці даследаваць папулярныя хэштэгі.",
|
||||||
|
"follow_recommendations.done": "Гатова",
|
||||||
|
"follow_recommendations.heading": "Падпісвайцеся на людзей, допісы якіх вам будуць цікавы! Вось некаторыя рэкамендацыі.",
|
||||||
|
"follow_recommendations.lead": "Допісы людзей, на якіх вы падпісаны, будуць паказаны ў храналагічным парадку на вашай хатняй старонцы. Не бойцеся памыляцца, вы лёгка зможаце адпісацца ў любы момант!",
|
||||||
"onboarding.page_one.federation": "{domain} is an \"instance\" of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
|
"onboarding.page_one.federation": "{domain} is an \"instance\" of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
|
||||||
"onboarding.page_six.github": "{domain} runs on Glitchsoc. Glitchsoc is a friendly {fork} of {Mastodon}. Glitchsoc is fully compatible with all Mastodon apps and instances. Glitchsoc is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
|
"onboarding.page_six.github": "{domain} runs on Glitchsoc. Glitchsoc is a friendly {fork} of {Mastodon}. Glitchsoc is fully compatible with all Mastodon apps and instances. Glitchsoc is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
|
||||||
"settings.content_warnings": "Content warnings",
|
"settings.content_warnings": "Content warnings",
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
{
|
{
|
||||||
|
"empty_column.follow_recommendations": "Изглежда, че няма предложения, които може да се породят за вас. Може да опитате да потърсите хора, които познавате или да разгледате налагащи се хаштагове.",
|
||||||
|
"follow_recommendations.done": "Готово",
|
||||||
|
"follow_recommendations.heading": "Следвайте хора, от които харесвате да виждате публикации! Ето някои предложения.",
|
||||||
|
"follow_recommendations.lead": "Публикациите от последваните, ще се показват в хронологичен ред в началния ви инфоканал. Не се страхувайте, че ще сгрешите, по всяко време много лесно може да спрете да ги следвате!",
|
||||||
"onboarding.page_one.federation": "{domain} is an \"instance\" of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
|
"onboarding.page_one.federation": "{domain} is an \"instance\" of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
|
||||||
"onboarding.page_six.github": "{domain} runs on Glitchsoc. Glitchsoc is a friendly {fork} of {Mastodon}. Glitchsoc is fully compatible with all Mastodon apps and instances. Glitchsoc is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
|
"onboarding.page_six.github": "{domain} runs on Glitchsoc. Glitchsoc is a friendly {fork} of {Mastodon}. Glitchsoc is fully compatible with all Mastodon apps and instances. Glitchsoc is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
|
||||||
"settings.content_warnings": "Content warnings",
|
"settings.content_warnings": "Content warnings",
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
{
|
{
|
||||||
|
"empty_column.follow_recommendations": "Looks like no suggestions could be generated for you. You can try using search to look for people you might know or explore trending hashtags.",
|
||||||
|
"follow_recommendations.done": "সম্পন্ন",
|
||||||
|
"follow_recommendations.heading": "Follow people you'd like to see posts from! Here are some suggestions.",
|
||||||
|
"follow_recommendations.lead": "Posts from people you follow will show up in chronological order on your home feed. Don't be afraid to make mistakes, you can unfollow people just as easily any time!",
|
||||||
"onboarding.page_one.federation": "{domain} is an \"instance\" of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
|
"onboarding.page_one.federation": "{domain} is an \"instance\" of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
|
||||||
"onboarding.page_six.github": "{domain} runs on Glitchsoc. Glitchsoc is a friendly {fork} of {Mastodon}. Glitchsoc is fully compatible with all Mastodon apps and instances. Glitchsoc is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
|
"onboarding.page_six.github": "{domain} runs on Glitchsoc. Glitchsoc is a friendly {fork} of {Mastodon}. Glitchsoc is fully compatible with all Mastodon apps and instances. Glitchsoc is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
|
||||||
"settings.content_warnings": "Content warnings",
|
"settings.content_warnings": "Content warnings",
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
{
|
{
|
||||||
|
"empty_column.follow_recommendations": "War a seblant ne c'hall ket bezañ savet erbedadenn ebet evidoc'h. Gallout a rit implijout un enklask evit kavout tud a anavezfec'h pe furchal ar gerioù-klik diouzh ar c'hiz.",
|
||||||
|
"follow_recommendations.done": "Graet",
|
||||||
|
"follow_recommendations.heading": "Heuilhit tud a blijfe deoc'h lenn o zoudoù ! Setu un nebeud erbedadennoù.",
|
||||||
|
"follow_recommendations.lead": "Toudoù gant tud a vez heuliet ganeoc'h a zeuio war wel en urzh kronologel war ho red degemer. Arabat kaout aon ober fazioù, diheuliañ tud a c'hellit ober aes ha forzh pegoulz !",
|
||||||
"onboarding.page_one.federation": "{domain} is an \"instance\" of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
|
"onboarding.page_one.federation": "{domain} is an \"instance\" of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
|
||||||
"onboarding.page_six.github": "{domain} runs on Glitchsoc. Glitchsoc is a friendly {fork} of {Mastodon}. Glitchsoc is fully compatible with all Mastodon apps and instances. Glitchsoc is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
|
"onboarding.page_six.github": "{domain} runs on Glitchsoc. Glitchsoc is a friendly {fork} of {Mastodon}. Glitchsoc is fully compatible with all Mastodon apps and instances. Glitchsoc is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
|
||||||
"settings.content_warnings": "Content warnings",
|
"settings.content_warnings": "Content warnings",
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue