Merge branch 'main' into glitch-soc/merge-upstream
Conflicts: - `.github/dependabot.yml`: Updated upstream, removed in glitch-soc to disable noise. Kept removed. - `CODE_OF_CONDUCT.md`: Upstream updated to a new version of the covenant, but I have not read it yet, so kept unchanged. - `Gemfile.lock`: Not a real conflict, one upstream dependency updated textually too close to the glitch-soc only `hcaptcha` dependency. Applied upstream changes. - `app/controllers/admin/base_controller.rb`: Minor conflict due to glitch-soc's theming system. Applied upstream changes. - `app/controllers/application_controller.rb`: Minor conflict due to glitch-soc's theming system. Applied upstream changes. - `app/controllers/disputes/base_controller.rb`: Minor conflict due to glitch-soc's theming system. Applied upstream changes. - `app/controllers/relationships_controller.rb`: Minor conflict due to glitch-soc's theming system. Applied upstream changes. - `app/controllers/statuses_cleanup_controller.rb`: Minor conflict due to glitch-soc's theming system. Applied upstream changes. - `app/helpers/application_helper.rb`: Minor conflict due to glitch-soc's theming system. Applied upstream changes. - `app/javascript/mastodon/features/compose/components/compose_form.jsx`: Upstream added a highlight animation for onboarding, while we changed the max character limit. Applied our local changes on top of upstream's new version. - `app/views/layouts/application.html.haml`: Minor conflict due to glitch-soc's theming system. Applied upstream changes. - `stylelint.config.js`: Upstream added ignore paths, glitch-soc had extra ignore paths. Added the same paths as upstream.shrike
commit
12b935fadf
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,15 +2,12 @@
|
||||||
|
|
||||||
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|
|
expires_in 3.minutes, public: true
|
||||||
format.json do
|
render_with_cache json: @emoji, content_type: 'application/activity+json', serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter
|
||||||
expires_in 3.minutes, public: true
|
|
||||||
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
|
||||||
else
|
|
||||||
I18n.t('auth.description.prefix_sign_up')
|
|
||||||
end
|
|
||||||
|
|
||||||
safe_join([prefix, I18n.t('auth.description.suffix')], ' ')
|
private
|
||||||
|
|
||||||
|
def description_prefix(invite)
|
||||||
|
if invite.present?
|
||||||
|
I18n.t('auth.description.prefix_invited_by_user', name: invite.user.account.username)
|
||||||
|
else
|
||||||
|
I18n.t('auth.description.prefix_sign_up')
|
||||||
|
end
|
||||||
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
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
<svg width="293" height="264" viewBox="0 0 293 264" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M34.1 204.9C28.1 210.7 22.3 209.9 15.9 204.3C13.4 202.1 11.3 205.2 15.3 209.2C19.3 213.2 28.8 217.5 37.3 210" fill="#E09C5C"/>
|
||||||
|
<path d="M34.1 204.9C28.1 210.7 22.3 209.9 15.9 204.3C13.4 202.1 11.3 205.2 15.3 209.2C19.3 213.2 28.8 217.5 37.3 210" stroke="#946F3A" stroke-width="2.2827" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M197.2 1.90002H137.4C122.4 1.90002 110.1 14.2 110.1 29.2V42.1C110.1 48.4 112.3 54.2 115.9 58.9C109.2 72.6 94.5 75.8 94.5 75.8C113.4 78.1 119.1 72.3 125.2 66.6C128.9 68.4 133 69.5 137.4 69.5H197.2C212.2 69.5 224.5 57.2 224.5 42.2V29.3C224.6 14.2 212.3 1.90002 197.2 1.90002Z" fill="white" stroke="#668794" stroke-width="2.2827" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M78.3 210.9C78.2 222.8 75.9 232 86.2 232.1C96.5 232.2 99.4 229.6 99.3 222C99.2 214.4 98.7 201 98.7 201" fill="#E09C5C"/>
|
||||||
|
<path d="M78.3 210.9C78.2 222.8 75.9 232 86.2 232.1C96.5 232.2 99.4 229.6 99.3 222C99.2 214.4 98.7 201 98.7 201" stroke="#946F3A" stroke-width="2.2827" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M63.8 104.2C43.6 104.2 28.7 111.8 28.7 140.2C28.7 168.6 28.7 186.5 28.7 194.5C28.7 202.5 33.1 216.3 50.5 216.3C67.9 216.3 66.1 216.3 74.6 216.3C83.1 216.3 90.9 213.5 97.1 206.9C103.3 200.3 106.3 193.6 106.3 181.4C106.3 169.2 106.3 134.8 106.3 134.8C106.3 134.8 126.7 134.3 135.7 121.5C144.6 108.7 142.6 93.3001 141.4 88.9001C141.4 88.9001 146.5 88.4 145.4 84.5C144.3 80.6 138.1 81.2001 135.3 83.4001C135.3 83.4001 133.7 81.6 128.3 81.7C122.9 81.8 124.6 87.9001 129 88.4001C129 88.4001 131.4 102.2 124.4 107.1C117.4 112 103 113.7 94.6 109.8C86.1 106.1 83.3 104.2 63.8 104.2Z" fill="#FBC16C" stroke="#946F3A" stroke-width="2.2827" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M50.5 215.2C30.4 215.2 29.9 196.7 29.9 194.6V171.5C42.9 171.6 48.7 173 48.7 183.9C48.7 197.1 50.9 203.7 62.6 203.7H90.4C91.1 203.7 91.7 203.7 92.3 203.7C92.9 203.7 93.4 203.7 94 203.7C95.7 203.7 97.4 203.6 99 203C98.2 204 97.3 205.1 96.3 206.2C90.7 212.2 83.4 215.2 74.7 215.2H50.5Z" fill="#E09C5C"/>
|
||||||
|
<path d="M56.8 110.5C47.2 107.6 42.1 105.6 45 97.2C47.9 88.8 62 90.6 69.7 94.5C69.7 94.5 73.7 92.1 76 91.2C78.3 90.3 82.6 89.9001 82.7 92.9001C82.7 92.9001 88.1 93.5001 87.1 97.8001C87.1 97.8001 93.5 101.5 79 108.5" fill="#FBC16C"/>
|
||||||
|
<path d="M56.8 110.5C47.2 107.6 42.1 105.6 45 97.2C47.9 88.8 62 90.6 69.7 94.5C69.7 94.5 73.7 92.1 76 91.2C78.3 90.3 82.6 89.9001 82.7 92.9001C82.7 92.9001 88.1 93.5001 87.1 97.8001C87.1 97.8001 93.5 101.5 79 108.5" stroke="#946F3A" stroke-width="2.2827" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M40.5 119.9C40.5 113.5 36.7 109.6 28.7 109.6C20.7 109.6 13.9 117.1 15.7 128.3C17.5 139.5 11.4 148.2 18.5 153.2C25.6 158.2 32.9 155.1 35.2 151.3C35.2 151.3 42.8 153.3 42.2 141.4" fill="#FBC16C"/>
|
||||||
|
<path d="M40.5 119.9C40.5 113.5 36.7 109.6 28.7 109.6C20.7 109.6 13.9 117.1 15.7 128.3C17.5 139.5 11.4 148.2 18.5 153.2C25.6 158.2 32.9 155.1 35.2 151.3C35.2 151.3 42.8 153.3 42.2 141.4" stroke="#946F3A" stroke-width="2.2827" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M69.3 134.2C63.6 134.2 60.5 138.6 61.8 144.4C63.1 150.1 66.8 154.4 73.2 154.6C79.6 154.7 85.7 152.5 87.6 143.2C89.5 133.9 86 133.8 83.2 133.8C80.4 133.8 69.3 134.2 69.3 134.2Z" fill="#544024" stroke="#3B3024" stroke-width="2.2827" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M78.8 147.3C78.8 140.6 73.4 135.1 66.6 135.1C66 135.1 65.5001 135.2 64.9001 135.2C62.1001 136.8 60.8 140.2 61.7 144.3C63 150 66.7 154.3 73.1 154.5C74.2 154.5 75.3001 154.5 76.4001 154.3C78.0001 152.3 78.8 149.9 78.8 147.3Z" fill="#693131" stroke="#381916" stroke-width="2.2827" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M83.1 140.9V133.7C80.1 133.7 69.3 134.1 69.3 134.1C64.8 134.1 61.9 136.9 61.5 140.9H83.1Z" fill="white" stroke="#7D7D65" stroke-width="2.2827" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M48.2 149.1C54.2 154.9 62.5 154.9 66.3 151.7C70.1 148.5 72.1 140.3 69 139.5C65.9 138.7 66.6 144.2 63.8 145.8C61 147.4 57.8 147 54.8 145.1C51.9 143.2 48.9 141.9 47.4 143.7C46.1 145.7 46.8 147.7 48.2 149.1Z" fill="white" stroke="#7D7D65" stroke-width="2.2827" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M251.4 179.1C245.7 185.3 237.4 185.7 233.4 182.7C229.4 179.7 227 171.7 230 170.7C233 169.7 232.7 175.2 235.5 176.7C238.3 178.2 241.6 177.6 244.4 175.6C247.2 173.6 250.1 172 251.7 173.9C253.3 175.8 252.8 177.7 251.4 179.1Z" stroke="black" stroke-width="0.5707" stroke-miterlimit="10"/>
|
||||||
|
<path d="M36.3 153.7L15.8 153.8C15.8 153.8 15.1 150.4 12 150.9C8.90001 151.4 8.19998 153.2 8.09998 154.3C8.09998 154.3 1.30002 154.7 1.20002 161.5C1.10002 168.3 6.1 171 13.3 170.6C20.5 170.2 37.7 170.6 37.7 170.6" fill="#FBC16C"/>
|
||||||
|
<path d="M36.3 153.7L15.8 153.8C15.8 153.8 15.1 150.4 12 150.9C8.90001 151.4 8.19998 153.2 8.09998 154.3C8.09998 154.3 1.30002 154.7 1.20002 161.5C1.10002 168.3 6.1 171 13.3 170.6C20.5 170.2 37.7 170.6 37.7 170.6" stroke="#946F3A" stroke-width="2.2827" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M37.4 210.9C37.3 222.8 35 232 45.3 232.1C55.6 232.2 58.5 229.6 58.4 222C58.3 214.4 58.2 211.6 58.2 211.6" fill="#E09C5C"/>
|
||||||
|
<path d="M37.4 210.9C37.3 222.8 35 232 45.3 232.1C55.6 232.2 58.5 229.6 58.4 222C58.3 214.4 58.2 211.6 58.2 211.6" stroke="#946F3A" stroke-width="2.2827" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M54.7 138.2C53.8 138.2 53.1 137.5 53.1 136.6V125.7C53.1 124.8 53.8 124.1 54.7 124.1C55.6 124.1 56.3 124.8 56.3 125.7V136.6C56.3 137.5 55.6 138.2 54.7 138.2Z" fill="#402F19"/>
|
||||||
|
<path d="M196.8 191.4C189.6 192.3 180.1 190.7 175.2 189.6C170.3 188.5 167.4 193.9 166.7 199.1C166 204.3 178.4 213.2 199.4 207.8" fill="#FBE6C6"/>
|
||||||
|
<path d="M196.8 191.4C189.6 192.3 180.1 190.7 175.2 189.6C170.3 188.5 167.4 193.9 166.7 199.1C166 204.3 178.4 213.2 199.4 207.8" stroke="#668794" stroke-width="2.2827" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M265 188.6C278.1 192.7 287.2 200.3 287 208.9C286.8 217.5 277.4 223.1 270.5 224.4C263.6 225.7 260.7 218.3 261.6 213.3C262.5 208.3 265 206.7 265 206.7" fill="#FBE6C6"/>
|
||||||
|
<path d="M265 188.6C278.1 192.7 287.2 200.3 287 208.9C286.8 217.5 277.4 223.1 270.5 224.4C263.6 225.7 260.7 218.3 261.6 213.3C262.5 208.3 265 206.7 265 206.7" stroke="#668794" stroke-width="2.2827" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M260 204C265 206.6 270.1 209.4 270.1 209.4Z" fill="#FBE6C6"/>
|
||||||
|
<path d="M260 204C265 206.6 270.1 209.4 270.1 209.4" stroke="#668794" stroke-width="2.2827" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M266 150.4C266 144 269.8 140.1 277.8 140.1C285.8 140.1 292.6 147.6 290.8 158.8C289 170 295.1 178.7 288 183.7C280.9 188.6 273.6 185.6 271.3 181.8C271.3 181.8 263.7 183.8 264.3 171.9" fill="#FBE6C6"/>
|
||||||
|
<path d="M266 150.4C266 144 269.8 140.1 277.8 140.1C285.8 140.1 292.6 147.6 290.8 158.8C289 170 295.1 178.7 288 183.7C280.9 188.6 273.6 185.6 271.3 181.8C271.3 181.8 263.7 183.8 264.3 171.9" stroke="#668794" stroke-width="2.2827" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M230.1 133.7C200.4 133.4 195.7 141.6 188.1 148.1C180.5 154.7 170.5 166.9 151.5 167.6C151.5 167.6 149 164.1 146.3 166.6C143.6 169.1 144.1 172.5 146 173.8C146 173.8 142.3 177.5 146.2 181.4C150.1 185.3 153.4 181.4 153.4 181.4C153.4 181.4 167.8 182.7 179.3 177.4C190.7 172 194.4 174.1 194.4 174.1C194.4 174.1 194.4 208.7 194.4 224.5C194.4 240.3 201.6 246.5 220.3 246.5C239 246.5 257.1 248.2 264.8 240.3C272.5 232.4 271.2 221.1 270.7 211.1C270.2 201.1 270.7 183 270.7 167.6C270.7 152.2 269.3 134 230.1 133.7Z" fill="#FBE6C6" stroke="#668794" stroke-width="2.2827" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M242.3 244C242.3 248.8 241.8 250.6 242.1 254.1C240.5 255.2 239.9 257.1 240.4 258.7C241 260.7 244 262.7 250.3 262.6C260.7 262.7 263.5 260.1 263.4 252.5C263.3 249 263.2 244.3 263.1 240.3" fill="#DCCEB5"/>
|
||||||
|
<path d="M242.3 244C242.3 248.8 241.8 250.6 242.1 254.1C240.5 255.2 239.9 257.1 240.4 258.7C241 260.7 244 262.7 250.3 262.6C260.7 262.7 263.5 260.1 263.4 252.5C263.3 249 263.2 244.3 263.1 240.3" stroke="#668794" stroke-width="2.2827" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M236.5 245.4C233.9 245.4 231.4 245.4 228.6 245.3C225.8 245.3 223 245.2 220.2 245.2C202.2 245.2 195.5 239.5 195.5 224.3C195.5 221.8 199.8 219.5 204.5 219.5C206.8 219.5 211.1 220.1 213.7 223.8C217.5 229 222.6 230.9 230 230.9C234.5 230.9 244.5 231.1 247.9 228.5C252.4 225.2 253.7 220.6 261.2 218.9C264.2 218.2 266.8 217.7 269.8 218.5C270 227.8 268.5 235.7 263.9 239.3C258.4 243.7 249.4 245.4 236.5 245.4Z" fill="#DCCEB5"/>
|
||||||
|
<path d="M243.9 141.2C250.9 141.1 254.6 136.8 254.1 132.6C253.5 128.5 250.6 122.7 237.5 123.4C231.7 123.7 228.8 127.5 226 127.5C223.2 127.4 217.7 119.4 212.9 119.9C208.1 120.4 207.8 123.8 208.6 125.4C208.6 125.4 204.3 126.7 206.7 132.3C209.1 137.9 214 139.8 218.8 140.4" fill="#FBE6C6"/>
|
||||||
|
<path d="M243.9 141.2C250.9 141.1 254.6 136.8 254.1 132.6C253.5 128.5 250.6 122.7 237.5 123.4C231.7 123.7 228.8 127.5 226 127.5C223.2 127.4 217.7 119.4 212.9 119.9C208.1 120.4 207.8 123.8 208.6 125.4C208.6 125.4 204.3 126.7 206.7 132.3C209.1 137.9 214 139.8 218.8 140.4" stroke="#668794" stroke-width="2.2827" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M203.7 241.5C203.7 246.3 203.2 250.7 203.5 254.2C201.9 255.3 201.3 257.2 201.8 258.8C202.4 260.8 205.4 262.8 211.7 262.7C222.1 262.8 224.9 260.2 224.8 252.6C224.8 249.7 224.7 248.9 224.6 245.4" fill="#DCCEB5"/>
|
||||||
|
<path d="M203.7 241.5C203.7 246.3 203.2 250.7 203.5 254.2C201.9 255.3 201.3 257.2 201.8 258.8C202.4 260.8 205.4 262.8 211.7 262.7C222.1 262.8 224.9 260.2 224.8 252.6C224.8 249.7 224.7 248.9 224.6 245.4" stroke="#668794" stroke-width="2.2827" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M194.8 22.9H140.2C138.3 22.9 136.8 21.4 136.8 19.5C136.8 17.6 138.3 16.1 140.2 16.1H194.8C196.7 16.1 198.2 17.6 198.2 19.5C198.2 21.4 196.7 22.9 194.8 22.9Z" fill="#4A4439"/>
|
||||||
|
<path d="M194.8 39.8H140.2C138.3 39.8 136.8 38.3 136.8 36.4C136.8 34.5 138.3 33 140.2 33H194.8C196.7 33 198.2 34.5 198.2 36.4C198.2 38.2 196.7 39.8 194.8 39.8Z" fill="#4A4439"/>
|
||||||
|
<path d="M194.8 56.6H140.2C138.3 56.6 136.8 55.1 136.8 53.2C136.8 51.3 138.3 49.8 140.2 49.8H194.8C196.7 49.8 198.2 51.3 198.2 53.2C198.2 55.1 196.7 56.6 194.8 56.6Z" fill="#4A4439"/>
|
||||||
|
<path d="M205.9 150.4C205.9 144 209.7 140.1 217.7 140.1C225.7 140.1 232.5 147.6 230.7 158.8C228.9 170 235 178.7 227.9 183.7C220.8 188.6 213.5 185.6 211.2 181.8C211.2 181.8 203.6 183.8 204.2 171.9" fill="#FBE6C6"/>
|
||||||
|
<path d="M205.9 150.4C205.9 144 209.7 140.1 217.7 140.1C225.7 140.1 232.5 147.6 230.7 158.8C228.9 170 235 178.7 227.9 183.7C220.8 188.6 213.5 185.6 211.2 181.8C211.2 181.8 203.6 183.8 204.2 171.9" stroke="#668794" stroke-width="2.2827" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M104.3 166C115.9 165.4 125.9 161.4 131 156.7C136.8 151.4 139.9 146.2 134.7 141.6C129.6 137.1 124.6 141.5 124.6 141.5C124.6 141.5 122.6 138.4 119.6 140.7C116.6 143 118.8 145.2 118.8 145.2C118.8 145.2 115.2 150.2 103.9 150.7" fill="#FBC16C"/>
|
||||||
|
<path d="M104.3 166C115.9 165.4 125.9 161.4 131 156.7C136.8 151.4 139.9 146.2 134.7 141.6C129.6 137.1 124.6 141.5 124.6 141.5C124.6 141.5 122.6 138.4 119.6 140.7C116.6 143 118.8 145.2 118.8 145.2C118.8 145.2 115.2 150.2 103.9 150.7" stroke="#946F3A" stroke-width="2.2827" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M49 139L45.5 139.6C44.5 139.8 43.5001 139.1 43.4001 138.1C43.2001 137.1 43.9001 136.1 44.9001 136L48.4001 135.4C49.4001 135.2 50.4 135.9 50.5 136.9C50.7 137.9 50.1 138.9 49 139Z" fill="#E68A4C"/>
|
||||||
|
<path d="M119.7 114.2C120.4 114 120.7 113.8 121.3 113.5" stroke="#FBD7A3" stroke-width="2.8533" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M114.5 115.6C115.4 115.4 114.9 115.6 115.7 115.4" stroke="#FBD7A3" stroke-width="2.8533" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M102.2 115.8C104.4 116.1 106.6 116.2 108.8 116.2" stroke="#FBD7A3" stroke-width="2.8533" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M88.1 111.8C90.5 112.9 93.1 113.8 95.8 114.5" stroke="#FBD7A3" stroke-width="2.8533" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M207.1 140.8C207.8 140.5 208.4 140.3 209 140" stroke="white" stroke-width="2.2827" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M201.6 143.5C202.5 143 202.5 142.9 203.4 142.4" stroke="white" stroke-width="2.2827" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M193.8 148.9C195.4 147.7 196.3 147 197.9 146" stroke="white" stroke-width="2.2827" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M186.1 156.2C187.3 154.9 188.7 153.6 190.1 152.3" stroke="white" stroke-width="2.2827" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M262.1 235.4C268.1 241.2 273.9 240.4 280.3 234.8C282.8 232.6 284.9 235.7 280.9 239.7C276.9 243.7 267.4 248 258.9 240.5" fill="#DCCEB5"/>
|
||||||
|
<path d="M262.1 235.4C268.1 241.2 273.9 240.4 280.3 234.8C282.8 232.6 284.9 235.7 280.9 239.7C276.9 243.7 267.4 248 258.9 240.5" stroke="#668794" stroke-width="2.2827" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 14 KiB |
|
@ -74,6 +74,7 @@ export const COMPOSE_CHANGE_MEDIA_DESCRIPTION = 'COMPOSE_CHANGE_MEDIA_DESCRIPTIO
|
||||||
export const COMPOSE_CHANGE_MEDIA_FOCUS = 'COMPOSE_CHANGE_MEDIA_FOCUS';
|
export const COMPOSE_CHANGE_MEDIA_FOCUS = 'COMPOSE_CHANGE_MEDIA_FOCUS';
|
||||||
|
|
||||||
export const COMPOSE_SET_STATUS = 'COMPOSE_SET_STATUS';
|
export const COMPOSE_SET_STATUS = 'COMPOSE_SET_STATUS';
|
||||||
|
export const COMPOSE_FOCUS = 'COMPOSE_FOCUS';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' },
|
uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' },
|
||||||
|
@ -125,6 +126,15 @@ export function resetCompose() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const focusCompose = (routerHistory, defaultText) => dispatch => {
|
||||||
|
dispatch({
|
||||||
|
type: COMPOSE_FOCUS,
|
||||||
|
defaultText,
|
||||||
|
});
|
||||||
|
|
||||||
|
ensureComposeIsVisible(routerHistory);
|
||||||
|
};
|
||||||
|
|
||||||
export function mentionCompose(account, routerHistory) {
|
export function mentionCompose(account, routerHistory) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
|
|
|
@ -135,8 +135,7 @@ export const showSearch = () => ({
|
||||||
type: SEARCH_SHOW,
|
type: SEARCH_SHOW,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const openURL = routerHistory => (dispatch, getState) => {
|
export const openURL = (value, history, onFailure) => (dispatch, getState) => {
|
||||||
const value = getState().getIn(['search', 'value']);
|
|
||||||
const signedIn = !!getState().getIn(['meta', 'me']);
|
const signedIn = !!getState().getIn(['meta', 'me']);
|
||||||
|
|
||||||
if (!signedIn) {
|
if (!signedIn) {
|
||||||
|
@ -148,15 +147,21 @@ export const openURL = routerHistory => (dispatch, getState) => {
|
||||||
api(getState).get('/api/v2/search', { params: { q: value, resolve: true } }).then(response => {
|
api(getState).get('/api/v2/search', { params: { q: value, resolve: true } }).then(response => {
|
||||||
if (response.data.accounts?.length > 0) {
|
if (response.data.accounts?.length > 0) {
|
||||||
dispatch(importFetchedAccounts(response.data.accounts));
|
dispatch(importFetchedAccounts(response.data.accounts));
|
||||||
routerHistory.push(`/@${response.data.accounts[0].acct}`);
|
history.push(`/@${response.data.accounts[0].acct}`);
|
||||||
} else if (response.data.statuses?.length > 0) {
|
} else if (response.data.statuses?.length > 0) {
|
||||||
dispatch(importFetchedStatuses(response.data.statuses));
|
dispatch(importFetchedStatuses(response.data.statuses));
|
||||||
routerHistory.push(`/@${response.data.statuses[0].account.acct}/${response.data.statuses[0].id}`);
|
history.push(`/@${response.data.statuses[0].account.acct}/${response.data.statuses[0].id}`);
|
||||||
|
} else if (onFailure) {
|
||||||
|
onFailure();
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(fetchSearchSuccess(response.data, value));
|
dispatch(fetchSearchSuccess(response.data, value));
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
dispatch(fetchSearchFail(err));
|
dispatch(fetchSearchFail(err));
|
||||||
|
|
||||||
|
if (onFailure) {
|
||||||
|
onFailure();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,8 @@ import Skeleton from 'mastodon/components/skeleton';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { counterRenderer } from 'mastodon/components/common_counter';
|
import { counterRenderer } from 'mastodon/components/common_counter';
|
||||||
import ShortNumber from 'mastodon/components/short_number';
|
import ShortNumber from 'mastodon/components/short_number';
|
||||||
import Icon from 'mastodon/components/icon';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import VerifiedBadge from 'mastodon/components/verified_badge';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
||||||
|
@ -27,26 +27,6 @@ const messages = defineMessages({
|
||||||
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
|
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
|
||||||
});
|
});
|
||||||
|
|
||||||
class VerifiedBadge extends React.PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
link: PropTypes.string.isRequired,
|
|
||||||
verifiedAt: PropTypes.string.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { link } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span className='verified-badge'>
|
|
||||||
<Icon id='check' className='verified-badge__mark' />
|
|
||||||
<span dangerouslySetInnerHTML={{ __html: link }} />
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class Account extends ImmutablePureComponent {
|
class Account extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const Check = () => (
|
const Check = () => (
|
||||||
<svg width='14' height='11' viewBox='0 0 14 11'>
|
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='currentColor'>
|
||||||
<path d='M11.264 0L5.26 6.004 2.103 2.847 0 4.95l5.26 5.26 8.108-8.107L11.264 0' fill='currentColor' fillRule='evenodd' />
|
<path fillRule='evenodd' d='M16.704 4.153a.75.75 0 01.143 1.052l-8 10.5a.75.75 0 01-1.127.075l-4.5-4.5a.75.75 0 011.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 011.05-.143z' clipRule='evenodd' />
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -12,13 +12,19 @@ export default class ColumnBackButton extends React.PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
multiColumn: PropTypes.bool,
|
multiColumn: PropTypes.bool,
|
||||||
|
onClick: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
handleClick = () => {
|
handleClick = () => {
|
||||||
if (window.history && window.history.state) {
|
const { router } = this.context;
|
||||||
this.context.router.history.goBack();
|
const { onClick } = this.props;
|
||||||
|
|
||||||
|
if (onClick) {
|
||||||
|
onClick();
|
||||||
|
} else if (window.history && window.history.state) {
|
||||||
|
router.history.goBack();
|
||||||
} else {
|
} else {
|
||||||
this.context.router.history.push('/');
|
router.history.push('/');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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 }} />
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,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;
|
||||||
|
|
|
@ -68,6 +68,9 @@ class Status extends ImmutablePureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
status: ImmutablePropTypes.map,
|
status: ImmutablePropTypes.map,
|
||||||
account: ImmutablePropTypes.map,
|
account: ImmutablePropTypes.map,
|
||||||
|
previousId: PropTypes.string,
|
||||||
|
nextInReplyToId: PropTypes.string,
|
||||||
|
rootId: PropTypes.string,
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
onReply: PropTypes.func,
|
onReply: PropTypes.func,
|
||||||
onFavourite: PropTypes.func,
|
onFavourite: PropTypes.func,
|
||||||
|
@ -309,10 +312,7 @@ class Status extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let media = null;
|
const { intl, hidden, featured, unread, showThread, scrollKey, pictureInPicture, previousId, nextInReplyToId, rootId } = this.props;
|
||||||
let statusAvatar, prepend, rebloggedByText;
|
|
||||||
|
|
||||||
const { intl, hidden, featured, unread, showThread, scrollKey, pictureInPicture } = this.props;
|
|
||||||
|
|
||||||
let { status, account, ...other } = this.props;
|
let { status, account, ...other } = this.props;
|
||||||
|
|
||||||
|
@ -334,6 +334,8 @@ class Status extends ImmutablePureComponent {
|
||||||
openMedia: this.handleHotkeyOpenMedia,
|
openMedia: this.handleHotkeyOpenMedia,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let media, statusAvatar, prepend, rebloggedByText;
|
||||||
|
|
||||||
if (hidden) {
|
if (hidden) {
|
||||||
return (
|
return (
|
||||||
<HotKeys handlers={handlers}>
|
<HotKeys handlers={handlers}>
|
||||||
|
@ -345,7 +347,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,
|
||||||
|
@ -519,7 +525,10 @@ class Status extends ImmutablePureComponent {
|
||||||
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef}>
|
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef}>
|
||||||
{prepend}
|
{prepend}
|
||||||
|
|
||||||
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), muted: this.props.muted })} data-id={status.get('id')}>
|
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), 'status--in-thread': !!rootId, 'status--first-in-thread': previousId && (!connectUp || connectToRoot), muted: this.props.muted })} data-id={status.get('id')}>
|
||||||
|
{(connectReply || connectUp || connectToRoot) && <div className={classNames('status__line', { 'status__line--full': connectReply, 'status__line--first': !status.get('in_reply_to_id') && !connectToRoot })} />}
|
||||||
|
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
|
||||||
<div onClick={this.handleClick} className='status__info'>
|
<div onClick={this.handleClick} className='status__info'>
|
||||||
<a href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} className='status__relative-time' target='_blank' rel='noopener noreferrer'>
|
<a href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} className='status__relative-time' target='_blank' rel='noopener noreferrer'>
|
||||||
<span className='status__visibility-icon'><Icon id={visibilityIcon.icon} title={visibilityIcon.text} /></span>
|
<span className='status__visibility-icon'><Icon id={visibilityIcon.icon} title={visibilityIcon.text} /></span>
|
||||||
|
|
|
@ -104,7 +104,7 @@ class StatusContent extends React.PureComponent {
|
||||||
link.setAttribute('href', `/@${mention.get('acct')}`);
|
link.setAttribute('href', `/@${mention.get('acct')}`);
|
||||||
} else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
|
} else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
|
||||||
link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
|
link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
|
||||||
link.setAttribute('href', `/tags/${link.text.slice(1)}`);
|
link.setAttribute('href', `/tags/${link.text.replace(/^#/, '')}`);
|
||||||
} else {
|
} else {
|
||||||
link.setAttribute('title', link.href);
|
link.setAttribute('title', link.href);
|
||||||
link.classList.add('unhandled-link');
|
link.classList.add('unhandled-link');
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import Icon from 'mastodon/components/icon';
|
||||||
|
|
||||||
|
class VerifiedBadge extends React.PureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
link: PropTypes.string.isRequired,
|
||||||
|
verifiedAt: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { link } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className='verified-badge'>
|
||||||
|
<Icon id='check' className='verified-badge__mark' />
|
||||||
|
<span dangerouslySetInnerHTML={{ __html: link }} />
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default VerifiedBadge;
|
|
@ -67,6 +67,7 @@ const makeMapStateToProps = () => {
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => ({
|
const mapStateToProps = (state, props) => ({
|
||||||
status: getStatus(state, props),
|
status: getStatus(state, props),
|
||||||
|
nextInReplyToId: props.nextId ? state.getIn(['statuses', props.nextId, 'in_reply_to_id']) : null,
|
||||||
pictureInPicture: getPictureInPicture(state, props),
|
pictureInPicture: getPictureInPicture(state, props),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -80,6 +80,7 @@ class Header extends ImmutablePureComponent {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
identity: PropTypes.object,
|
identity: PropTypes.object,
|
||||||
|
router: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -101,11 +102,16 @@ class Header extends ImmutablePureComponent {
|
||||||
onChangeLanguages: PropTypes.func.isRequired,
|
onChangeLanguages: PropTypes.func.isRequired,
|
||||||
onInteractionModal: PropTypes.func.isRequired,
|
onInteractionModal: PropTypes.func.isRequired,
|
||||||
onOpenAvatar: PropTypes.func.isRequired,
|
onOpenAvatar: PropTypes.func.isRequired,
|
||||||
|
onOpenURL: PropTypes.func.isRequired,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
domain: PropTypes.string.isRequired,
|
domain: PropTypes.string.isRequired,
|
||||||
hidden: PropTypes.bool,
|
hidden: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
setRef = c => {
|
||||||
|
this.node = c;
|
||||||
|
};
|
||||||
|
|
||||||
openEditProfile = () => {
|
openEditProfile = () => {
|
||||||
window.open('/settings/profile', '_blank');
|
window.open('/settings/profile', '_blank');
|
||||||
};
|
};
|
||||||
|
@ -162,6 +168,61 @@ class Header extends ImmutablePureComponent {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleHashtagClick = e => {
|
||||||
|
const { router } = this.context;
|
||||||
|
const value = e.currentTarget.textContent.replace(/^#/, '');
|
||||||
|
|
||||||
|
if (router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||||
|
e.preventDefault();
|
||||||
|
router.history.push(`/tags/${value}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleMentionClick = e => {
|
||||||
|
const { router } = this.context;
|
||||||
|
const { onOpenURL } = this.props;
|
||||||
|
|
||||||
|
if (router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const link = e.currentTarget;
|
||||||
|
|
||||||
|
onOpenURL(link.href, router.history, () => {
|
||||||
|
window.location = link.href;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_attachLinkEvents () {
|
||||||
|
const node = this.node;
|
||||||
|
|
||||||
|
if (!node) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const links = node.querySelectorAll('a');
|
||||||
|
|
||||||
|
let link;
|
||||||
|
|
||||||
|
for (var i = 0; i < links.length; ++i) {
|
||||||
|
link = links[i];
|
||||||
|
|
||||||
|
if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
|
||||||
|
link.addEventListener('click', this.handleHashtagClick, false);
|
||||||
|
} else if (link.classList.contains('mention')) {
|
||||||
|
link.addEventListener('click', this.handleMentionClick, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
this._attachLinkEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate () {
|
||||||
|
this._attachLinkEvents();
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { account, hidden, intl, domain } = this.props;
|
const { account, hidden, intl, domain } = this.props;
|
||||||
const { signedIn, permissions } = this.context.identity;
|
const { signedIn, permissions } = this.context.identity;
|
||||||
|
@ -360,7 +421,7 @@ class Header extends ImmutablePureComponent {
|
||||||
|
|
||||||
{!(suspended || hidden) && (
|
{!(suspended || hidden) && (
|
||||||
<div className='account__header__extra'>
|
<div className='account__header__extra'>
|
||||||
<div className='account__header__bio'>
|
<div className='account__header__bio' ref={this.setRef}>
|
||||||
{(account.get('id') !== me && signedIn) && <AccountNoteContainer account={account} />}
|
{(account.get('id') !== me && signedIn) && <AccountNoteContainer account={account} />}
|
||||||
|
|
||||||
{account.get('note').length > 0 && account.get('note') !== '<p></p>' && <div className='account__header__content translate' dangerouslySetInnerHTML={content} />}
|
{account.get('note').length > 0 && account.get('note') !== '<p></p>' && <div className='account__header__content translate' dangerouslySetInnerHTML={content} />}
|
||||||
|
|
|
@ -26,6 +26,7 @@ export default class Header extends ImmutablePureComponent {
|
||||||
onChangeLanguages: PropTypes.func.isRequired,
|
onChangeLanguages: PropTypes.func.isRequired,
|
||||||
onInteractionModal: PropTypes.func.isRequired,
|
onInteractionModal: PropTypes.func.isRequired,
|
||||||
onOpenAvatar: PropTypes.func.isRequired,
|
onOpenAvatar: PropTypes.func.isRequired,
|
||||||
|
onOpenURL: PropTypes.func.isRequired,
|
||||||
hideTabs: PropTypes.bool,
|
hideTabs: PropTypes.bool,
|
||||||
domain: PropTypes.string.isRequired,
|
domain: PropTypes.string.isRequired,
|
||||||
hidden: PropTypes.bool,
|
hidden: PropTypes.bool,
|
||||||
|
@ -137,6 +138,7 @@ export default class Header extends ImmutablePureComponent {
|
||||||
onChangeLanguages={this.handleChangeLanguages}
|
onChangeLanguages={this.handleChangeLanguages}
|
||||||
onInteractionModal={this.handleInteractionModal}
|
onInteractionModal={this.handleInteractionModal}
|
||||||
onOpenAvatar={this.handleOpenAvatar}
|
onOpenAvatar={this.handleOpenAvatar}
|
||||||
|
onOpenURL={this.props.onOpenURL}
|
||||||
domain={this.props.domain}
|
domain={this.props.domain}
|
||||||
hidden={hidden}
|
hidden={hidden}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
pinAccount,
|
pinAccount,
|
||||||
unpinAccount,
|
unpinAccount,
|
||||||
} from '../../../actions/accounts';
|
} from '../../../actions/accounts';
|
||||||
|
import { openURL } from 'mastodon/actions/search';
|
||||||
import {
|
import {
|
||||||
mentionCompose,
|
mentionCompose,
|
||||||
directCompose,
|
directCompose,
|
||||||
|
@ -159,6 +160,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onOpenURL (url, routerHistory, onFailure) {
|
||||||
|
dispatch(openURL(url, routerHistory, onFailure));
|
||||||
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Header));
|
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Header));
|
||||||
|
|
|
@ -20,6 +20,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { length } from 'stringz';
|
import { length } from 'stringz';
|
||||||
import { countableText } from '../util/counter';
|
import { countableText } from '../util/counter';
|
||||||
import Icon from 'mastodon/components/icon';
|
import Icon from 'mastodon/components/icon';
|
||||||
|
import classNames from 'classnames';
|
||||||
import { maxChars } from '../../../initial_state';
|
import { maxChars } from '../../../initial_state';
|
||||||
|
|
||||||
const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d';
|
const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d';
|
||||||
|
@ -71,6 +72,10 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
autoFocus: false,
|
autoFocus: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
highlighted: false,
|
||||||
|
};
|
||||||
|
|
||||||
handleChange = (e) => {
|
handleChange = (e) => {
|
||||||
this.props.onChange(e.target.value);
|
this.props.onChange(e.target.value);
|
||||||
};
|
};
|
||||||
|
@ -144,6 +149,10 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
this._updateFocusAndSelection({ });
|
this._updateFocusAndSelection({ });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
if (this.timeout) clearTimeout(this.timeout);
|
||||||
|
}
|
||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate (prevProps) {
|
||||||
this._updateFocusAndSelection(prevProps);
|
this._updateFocusAndSelection(prevProps);
|
||||||
}
|
}
|
||||||
|
@ -174,6 +183,8 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
Promise.resolve().then(() => {
|
Promise.resolve().then(() => {
|
||||||
this.autosuggestTextarea.textarea.setSelectionRange(selectionStart, selectionEnd);
|
this.autosuggestTextarea.textarea.setSelectionRange(selectionStart, selectionEnd);
|
||||||
this.autosuggestTextarea.textarea.focus();
|
this.autosuggestTextarea.textarea.focus();
|
||||||
|
this.setState({ highlighted: true });
|
||||||
|
this.timeout = setTimeout(() => this.setState({ highlighted: false }), 700);
|
||||||
}).catch(console.error);
|
}).catch(console.error);
|
||||||
} else if(prevProps.isSubmitting && !this.props.isSubmitting) {
|
} else if(prevProps.isSubmitting && !this.props.isSubmitting) {
|
||||||
this.autosuggestTextarea.textarea.focus();
|
this.autosuggestTextarea.textarea.focus();
|
||||||
|
@ -208,6 +219,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, onPaste, autoFocus } = this.props;
|
const { intl, onPaste, autoFocus } = this.props;
|
||||||
|
const { highlighted } = this.state;
|
||||||
const disabled = this.props.isSubmitting;
|
const disabled = this.props.isSubmitting;
|
||||||
|
|
||||||
let publishText = '';
|
let publishText = '';
|
||||||
|
@ -246,41 +258,43 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AutosuggestTextarea
|
<div className={classNames('compose-form__highlightable', { active: highlighted })}>
|
||||||
ref={this.setAutosuggestTextarea}
|
<AutosuggestTextarea
|
||||||
placeholder={intl.formatMessage(messages.placeholder)}
|
ref={this.setAutosuggestTextarea}
|
||||||
disabled={disabled}
|
placeholder={intl.formatMessage(messages.placeholder)}
|
||||||
value={this.props.text}
|
disabled={disabled}
|
||||||
onChange={this.handleChange}
|
value={this.props.text}
|
||||||
suggestions={this.props.suggestions}
|
onChange={this.handleChange}
|
||||||
onFocus={this.handleFocus}
|
suggestions={this.props.suggestions}
|
||||||
onKeyDown={this.handleKeyDown}
|
onFocus={this.handleFocus}
|
||||||
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
|
onKeyDown={this.handleKeyDown}
|
||||||
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
|
||||||
onSuggestionSelected={this.onSuggestionSelected}
|
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
||||||
onPaste={onPaste}
|
onSuggestionSelected={this.onSuggestionSelected}
|
||||||
autoFocus={autoFocus}
|
onPaste={onPaste}
|
||||||
lang={this.props.lang}
|
autoFocus={autoFocus}
|
||||||
>
|
lang={this.props.lang}
|
||||||
<EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} />
|
>
|
||||||
|
<EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} />
|
||||||
|
|
||||||
<div className='compose-form__modifiers'>
|
<div className='compose-form__modifiers'>
|
||||||
<UploadFormContainer />
|
<UploadFormContainer />
|
||||||
<PollFormContainer />
|
<PollFormContainer />
|
||||||
</div>
|
</div>
|
||||||
</AutosuggestTextarea>
|
</AutosuggestTextarea>
|
||||||
|
|
||||||
<div className='compose-form__buttons-wrapper'>
|
<div className='compose-form__buttons-wrapper'>
|
||||||
<div className='compose-form__buttons'>
|
<div className='compose-form__buttons'>
|
||||||
<UploadButtonContainer />
|
<UploadButtonContainer />
|
||||||
<PollButtonContainer />
|
<PollButtonContainer />
|
||||||
<PrivacyDropdownContainer disabled={this.props.isEditing} />
|
<PrivacyDropdownContainer disabled={this.props.isEditing} />
|
||||||
<SpoilerButtonContainer />
|
<SpoilerButtonContainer />
|
||||||
<LanguageDropdown />
|
<LanguageDropdown />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='character-counter__wrapper'>
|
<div className='character-counter__wrapper'>
|
||||||
<CharacterCounter max={maxChars} text={this.getFulltextForCharacterCounting()} />
|
<CharacterCounter max={maxChars} text={this.getFulltextForCharacterCounting()} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -161,9 +161,9 @@ class Search extends React.PureComponent {
|
||||||
|
|
||||||
handleURLClick = () => {
|
handleURLClick = () => {
|
||||||
const { router } = this.context;
|
const { router } = this.context;
|
||||||
const { onOpenURL } = this.props;
|
const { value, onOpenURL } = this.props;
|
||||||
|
|
||||||
onOpenURL(router.history);
|
onOpenURL(value, router.history);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleStatusSearch = () => {
|
handleStatusSearch = () => {
|
||||||
|
|
|
@ -126,7 +126,7 @@ class SearchResults extends ImmutablePureComponent {
|
||||||
<div className='search-results'>
|
<div className='search-results'>
|
||||||
<div className='search-results__header'>
|
<div 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 }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{accounts}
|
{accounts}
|
||||||
|
|
|
@ -34,8 +34,8 @@ const mapDispatchToProps = dispatch => ({
|
||||||
dispatch(showSearch());
|
dispatch(showSearch());
|
||||||
},
|
},
|
||||||
|
|
||||||
onOpenURL (routerHistory) {
|
onOpenURL (q, routerHistory) {
|
||||||
dispatch(openURL(routerHistory));
|
dispatch(openURL(q, routerHistory));
|
||||||
},
|
},
|
||||||
|
|
||||||
onClickSearchResult (q, type) {
|
onClickSearchResult (q, type) {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue