mirror of
https://github.com/mastodon/mastodon.git
synced 2025-01-08 19:35:11 +01:00
Merge branch 'main' into search-warnings
# Conflicts: # app/javascript/mastodon/features/compose/components/search.jsx # app/javascript/mastodon/features/explore/components/search_section.jsx
This commit is contained in:
commit
ae5af957a1
59
.annotaterb.yml
Normal file
59
.annotaterb.yml
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
---
|
||||||
|
:position: before
|
||||||
|
:position_in_additional_file_patterns: before
|
||||||
|
:position_in_class: before
|
||||||
|
:position_in_factory: before
|
||||||
|
:position_in_fixture: before
|
||||||
|
:position_in_routes: before
|
||||||
|
:position_in_serializer: before
|
||||||
|
:position_in_test: before
|
||||||
|
:classified_sort: true
|
||||||
|
:exclude_controllers: true
|
||||||
|
:exclude_factories: true
|
||||||
|
:exclude_fixtures: true
|
||||||
|
:exclude_helpers: true
|
||||||
|
:exclude_scaffolds: true
|
||||||
|
:exclude_serializers: true
|
||||||
|
:exclude_sti_subclasses: true
|
||||||
|
:exclude_tests: true
|
||||||
|
:force: false
|
||||||
|
:format_markdown: false
|
||||||
|
:format_rdoc: false
|
||||||
|
:format_yard: false
|
||||||
|
:frozen: false
|
||||||
|
:ignore_model_sub_dir: false
|
||||||
|
:ignore_unknown_models: false
|
||||||
|
:include_version: false
|
||||||
|
:show_complete_foreign_keys: false
|
||||||
|
:show_foreign_keys: false
|
||||||
|
:show_indexes: false
|
||||||
|
:simple_indexes: false
|
||||||
|
:sort: false
|
||||||
|
:timestamp: false
|
||||||
|
:trace: false
|
||||||
|
:with_comment: true
|
||||||
|
:with_column_comments: true
|
||||||
|
:with_table_comments: true
|
||||||
|
:active_admin: false
|
||||||
|
:command:
|
||||||
|
:debug: false
|
||||||
|
:hide_default_column_types: ''
|
||||||
|
:hide_limit_column_types: 'integer,boolean'
|
||||||
|
:ignore_columns:
|
||||||
|
:ignore_routes:
|
||||||
|
:models: true
|
||||||
|
:routes: false
|
||||||
|
:skip_on_db_migrate: false
|
||||||
|
:target_action: :do_annotations
|
||||||
|
:wrapper:
|
||||||
|
:wrapper_close:
|
||||||
|
:wrapper_open:
|
||||||
|
:classes_default_to_s: []
|
||||||
|
:additional_file_patterns: []
|
||||||
|
:model_dir:
|
||||||
|
- app/models
|
||||||
|
:require: []
|
||||||
|
:root_dir:
|
||||||
|
- ''
|
||||||
|
|
||||||
|
:show_check_constraints: false
|
@ -69,7 +69,7 @@ services:
|
|||||||
hard: -1
|
hard: -1
|
||||||
|
|
||||||
libretranslate:
|
libretranslate:
|
||||||
image: libretranslate/libretranslate:v1.6.1
|
image: libretranslate/libretranslate:v1.6.2
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- lt-data:/home/libretranslate/.local
|
- lt-data:/home/libretranslate/.local
|
||||||
|
@ -109,7 +109,7 @@ module.exports = defineConfig({
|
|||||||
'react/jsx-equals-spacing': 'error',
|
'react/jsx-equals-spacing': 'error',
|
||||||
'react/jsx-no-bind': 'error',
|
'react/jsx-no-bind': 'error',
|
||||||
'react/jsx-no-useless-fragment': 'error',
|
'react/jsx-no-useless-fragment': 'error',
|
||||||
'react/jsx-no-target-blank': 'off',
|
'react/jsx-no-target-blank': ['error', { allowReferrer: true }],
|
||||||
'react/jsx-tag-spacing': 'error',
|
'react/jsx-tag-spacing': 'error',
|
||||||
'react/jsx-uses-react': 'off', // not needed with new JSX transform
|
'react/jsx-uses-react': 'off', // not needed with new JSX transform
|
||||||
'react/jsx-wrap-multilines': 'error',
|
'react/jsx-wrap-multilines': 'error',
|
||||||
|
1
.github/workflows/build-container-image.yml
vendored
1
.github/workflows/build-container-image.yml
vendored
@ -92,6 +92,7 @@ jobs:
|
|||||||
build-args: |
|
build-args: |
|
||||||
MASTODON_VERSION_PRERELEASE=${{ inputs.version_prerelease }}
|
MASTODON_VERSION_PRERELEASE=${{ inputs.version_prerelease }}
|
||||||
MASTODON_VERSION_METADATA=${{ inputs.version_metadata }}
|
MASTODON_VERSION_METADATA=${{ inputs.version_metadata }}
|
||||||
|
SOURCE_COMMIT=${{ github.sha }}
|
||||||
platforms: ${{ inputs.platforms }}
|
platforms: ${{ inputs.platforms }}
|
||||||
provenance: false
|
provenance: false
|
||||||
builder: ${{ steps.buildx.outputs.name || steps.buildx-native.outputs.name }}
|
builder: ${{ steps.buildx.outputs.name || steps.buildx-native.outputs.name }}
|
||||||
|
2
.github/workflows/bundler-audit.yml
vendored
2
.github/workflows/bundler-audit.yml
vendored
@ -36,4 +36,4 @@ jobs:
|
|||||||
bundler-cache: true
|
bundler-cache: true
|
||||||
|
|
||||||
- name: Run bundler-audit
|
- name: Run bundler-audit
|
||||||
run: bundle exec bundler-audit check --update
|
run: bin/bundler-audit check --update
|
||||||
|
10
.github/workflows/check-i18n.yml
vendored
10
.github/workflows/check-i18n.yml
vendored
@ -35,18 +35,18 @@ jobs:
|
|||||||
git diff --exit-code
|
git diff --exit-code
|
||||||
|
|
||||||
- name: Check locale file normalization
|
- name: Check locale file normalization
|
||||||
run: bundle exec i18n-tasks check-normalized
|
run: bin/i18n-tasks check-normalized
|
||||||
|
|
||||||
- name: Check for unused strings
|
- name: Check for unused strings
|
||||||
run: bundle exec i18n-tasks unused
|
run: bin/i18n-tasks unused
|
||||||
|
|
||||||
- name: Check for missing strings in English YML
|
- name: Check for missing strings in English YML
|
||||||
run: |
|
run: |
|
||||||
bundle exec i18n-tasks add-missing -l en
|
bin/i18n-tasks add-missing -l en
|
||||||
git diff --exit-code
|
git diff --exit-code
|
||||||
|
|
||||||
- name: Check for wrong string interpolations
|
- name: Check for wrong string interpolations
|
||||||
run: bundle exec i18n-tasks check-consistent-interpolations
|
run: bin/i18n-tasks check-consistent-interpolations
|
||||||
|
|
||||||
- name: Check that all required locale files exist
|
- name: Check that all required locale files exist
|
||||||
run: bundle exec rake repo:check_locales_files
|
run: bin/rake repo:check_locales_files
|
||||||
|
@ -46,7 +46,7 @@ jobs:
|
|||||||
uses: ./.github/actions/setup-ruby
|
uses: ./.github/actions/setup-ruby
|
||||||
|
|
||||||
- name: Run i18n normalize task
|
- name: Run i18n normalize task
|
||||||
run: bundle exec i18n-tasks normalize
|
run: bin/i18n-tasks normalize
|
||||||
|
|
||||||
# Create or update the pull request
|
# Create or update the pull request
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
|
2
.github/workflows/crowdin-download.yml
vendored
2
.github/workflows/crowdin-download.yml
vendored
@ -48,7 +48,7 @@ jobs:
|
|||||||
uses: ./.github/actions/setup-ruby
|
uses: ./.github/actions/setup-ruby
|
||||||
|
|
||||||
- name: Run i18n normalize task
|
- name: Run i18n normalize task
|
||||||
run: bundle exec i18n-tasks normalize
|
run: bin/i18n-tasks normalize
|
||||||
|
|
||||||
# Create or update the pull request
|
# Create or update the pull request
|
||||||
- name: Create Pull Request
|
- name: Create Pull Request
|
||||||
|
2
.github/workflows/lint-css.yml
vendored
2
.github/workflows/lint-css.yml
vendored
@ -40,4 +40,4 @@ jobs:
|
|||||||
uses: ./.github/actions/setup-javascript
|
uses: ./.github/actions/setup-javascript
|
||||||
|
|
||||||
- name: Stylelint
|
- name: Stylelint
|
||||||
run: yarn lint:css -f github
|
run: yarn lint:css --custom-formatter @csstools/stylelint-formatter-github
|
||||||
|
2
.github/workflows/lint-haml.yml
vendored
2
.github/workflows/lint-haml.yml
vendored
@ -43,4 +43,4 @@ jobs:
|
|||||||
- name: Run haml-lint
|
- name: Run haml-lint
|
||||||
run: |
|
run: |
|
||||||
echo "::add-matcher::.github/workflows/haml-lint-problem-matcher.json"
|
echo "::add-matcher::.github/workflows/haml-lint-problem-matcher.json"
|
||||||
bundle exec haml-lint --reporter github
|
bin/haml-lint --reporter github
|
||||||
|
2
.github/workflows/lint-ruby.yml
vendored
2
.github/workflows/lint-ruby.yml
vendored
@ -9,6 +9,7 @@ on:
|
|||||||
- 'Gemfile*'
|
- 'Gemfile*'
|
||||||
- '.rubocop*.yml'
|
- '.rubocop*.yml'
|
||||||
- '.ruby-version'
|
- '.ruby-version'
|
||||||
|
- 'bin/rubocop'
|
||||||
- 'config/brakeman.ignore'
|
- 'config/brakeman.ignore'
|
||||||
- '**/*.rb'
|
- '**/*.rb'
|
||||||
- '**/*.rake'
|
- '**/*.rake'
|
||||||
@ -19,6 +20,7 @@ on:
|
|||||||
- 'Gemfile*'
|
- 'Gemfile*'
|
||||||
- '.rubocop*.yml'
|
- '.rubocop*.yml'
|
||||||
- '.ruby-version'
|
- '.ruby-version'
|
||||||
|
- 'bin/rubocop'
|
||||||
- 'config/brakeman.ignore'
|
- 'config/brakeman.ignore'
|
||||||
- '**/*.rb'
|
- '**/*.rb'
|
||||||
- '**/*.rake'
|
- '**/*.rake'
|
||||||
|
6
.github/workflows/test-migrations.yml
vendored
6
.github/workflows/test-migrations.yml
vendored
@ -12,6 +12,7 @@ on:
|
|||||||
- '**/*.rb'
|
- '**/*.rb'
|
||||||
- '.github/workflows/test-migrations.yml'
|
- '.github/workflows/test-migrations.yml'
|
||||||
- 'lib/tasks/tests.rake'
|
- 'lib/tasks/tests.rake'
|
||||||
|
- 'lib/tasks/db.rake'
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
@ -90,6 +91,11 @@ jobs:
|
|||||||
bin/rails db:drop
|
bin/rails db:drop
|
||||||
bin/rails db:create
|
bin/rails db:create
|
||||||
SKIP_POST_DEPLOYMENT_MIGRATIONS=true bin/rails tests:migrations:prepare_database
|
SKIP_POST_DEPLOYMENT_MIGRATIONS=true bin/rails tests:migrations:prepare_database
|
||||||
|
|
||||||
|
# Migrate up to v4.2.0 breakpoint
|
||||||
|
bin/rails db:migrate VERSION=20230907150100
|
||||||
|
|
||||||
|
# Migrate the rest
|
||||||
SKIP_POST_DEPLOYMENT_MIGRATIONS=true bin/rails db:migrate
|
SKIP_POST_DEPLOYMENT_MIGRATIONS=true bin/rails db:migrate
|
||||||
bin/rails db:migrate
|
bin/rails db:migrate
|
||||||
bin/rails tests:migrations:check_database
|
bin/rails tests:migrations:check_database
|
||||||
|
4
.github/workflows/test-ruby.yml
vendored
4
.github/workflows/test-ruby.yml
vendored
@ -166,7 +166,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload coverage reports to Codecov
|
- name: Upload coverage reports to Codecov
|
||||||
if: matrix.ruby-version == '.ruby-version'
|
if: matrix.ruby-version == '.ruby-version'
|
||||||
uses: codecov/codecov-action@v4
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
with:
|
||||||
files: coverage/lcov/*.lcov
|
files: coverage/lcov/*.lcov
|
||||||
env:
|
env:
|
||||||
@ -252,7 +252,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload coverage reports to Codecov
|
- name: Upload coverage reports to Codecov
|
||||||
if: matrix.ruby-version == '.ruby-version'
|
if: matrix.ruby-version == '.ruby-version'
|
||||||
uses: codecov/codecov-action@v4
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
with:
|
||||||
files: coverage/lcov/mastodon.lcov
|
files: coverage/lcov/mastodon.lcov
|
||||||
env:
|
env:
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
---
|
---
|
||||||
|
Style/ArrayIntersect:
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
Style/ClassAndModuleChildren:
|
Style/ClassAndModuleChildren:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
@ -19,6 +22,16 @@ Style/HashSyntax:
|
|||||||
EnforcedShorthandSyntax: either
|
EnforcedShorthandSyntax: either
|
||||||
EnforcedStyle: ruby19_no_mixed_keys
|
EnforcedStyle: ruby19_no_mixed_keys
|
||||||
|
|
||||||
|
Style/IfUnlessModifier:
|
||||||
|
Exclude:
|
||||||
|
- '**/*.haml'
|
||||||
|
|
||||||
|
Style/KeywordArgumentsMerging:
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
|
Style/MultipleComparison:
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
Style/NumericLiterals:
|
Style/NumericLiterals:
|
||||||
AllowedPatterns:
|
AllowedPatterns:
|
||||||
- \d{4}_\d{2}_\d{2}_\d{6}
|
- \d{4}_\d{2}_\d{2}_\d{6}
|
||||||
@ -37,6 +50,9 @@ Style/RedundantFetchBlock:
|
|||||||
Style/RescueStandardError:
|
Style/RescueStandardError:
|
||||||
EnforcedStyle: implicit
|
EnforcedStyle: implicit
|
||||||
|
|
||||||
|
Style/SafeNavigationChainLength:
|
||||||
|
Enabled: false
|
||||||
|
|
||||||
Style/SymbolArray:
|
Style/SymbolArray:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# This configuration was generated by
|
# This configuration was generated by
|
||||||
# `rubocop --auto-gen-config --auto-gen-only-exclude --no-offense-counts --no-auto-gen-timestamp`
|
# `rubocop --auto-gen-config --auto-gen-only-exclude --no-offense-counts --no-auto-gen-timestamp`
|
||||||
# using RuboCop version 1.66.1.
|
# using RuboCop version 1.69.1.
|
||||||
# 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
|
||||||
@ -35,7 +35,6 @@ Rails/OutputSafety:
|
|||||||
# Configuration parameters: AllowedVars.
|
# Configuration parameters: AllowedVars.
|
||||||
Style/FetchEnvVar:
|
Style/FetchEnvVar:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'app/lib/translation_service.rb'
|
|
||||||
- 'config/environments/production.rb'
|
- 'config/environments/production.rb'
|
||||||
- 'config/initializers/2_limited_federation_mode.rb'
|
- 'config/initializers/2_limited_federation_mode.rb'
|
||||||
- 'config/initializers/3_omniauth.rb'
|
- 'config/initializers/3_omniauth.rb'
|
||||||
|
@ -1 +1 @@
|
|||||||
3.3.5
|
3.3.6
|
||||||
|
88
CHANGELOG.md
88
CHANGELOG.md
@ -2,6 +2,90 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [4.3.2] - 2024-12-03
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add `tootctl feeds vacuum` (#33065 by @ClearlyClaire)
|
||||||
|
- Add error message when user tries to follow their own account (#31910 by @lenikadali)
|
||||||
|
- Add client_secret_expires_at to OAuth Applications (#30317 by @ThisIsMissEm)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Change design of Content Warnings and filters (#32543 by @ClearlyClaire)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix processing incoming post edits with mentions to unresolvable accounts (#33129 by @ClearlyClaire)
|
||||||
|
- Fix error when including multiple instances of `embed.js` (#33107 by @YKWeyer)
|
||||||
|
- Fix inactive users' timelines being backfilled on follow and unsuspend (#33094 by @ClearlyClaire)
|
||||||
|
- Fix direct inbox delivery pushing posts into inactive followers' timelines (#33067 by @ClearlyClaire)
|
||||||
|
- Fix `TagFollow` records not being correctly handled in account operations (#33063 by @ClearlyClaire)
|
||||||
|
- Fix pushing hashtag-followed posts to feeds of inactive users (#33018 by @Gargron)
|
||||||
|
- Fix duplicate notifications in notification groups when using slow mode (#33014 by @ClearlyClaire)
|
||||||
|
- Fix posts made in the future being allowed to trend (#32996 by @ClearlyClaire)
|
||||||
|
- Fix uploading higher-than-wide GIF profile picture with libvips enabled (#32911 by @ClearlyClaire)
|
||||||
|
- Fix domain attribution field having autocorrect and autocapitalize enabled (#32903 by @ClearlyClaire)
|
||||||
|
- Fix titles being escaped twice (#32889 by @ClearlyClaire)
|
||||||
|
- Fix list creation limit check (#32869 by @ClearlyClaire)
|
||||||
|
- Fix error in `tootctl email_domain_blocks` when supplying `--with-dns-records` (#32863 by @mjankowski)
|
||||||
|
- Fix `min_id` and `max_id` causing error in search API (#32857 by @Gargron)
|
||||||
|
- Fix inefficiencies when processing removal of posts that use featured tags (#32787 by @ClearlyClaire)
|
||||||
|
- Fix alt-text pop-in not using the translated description (#32766 by @ClearlyClaire)
|
||||||
|
- Fix preview cards with long titles erroneously causing layout changes (#32678 by @ClearlyClaire)
|
||||||
|
- Fix embed modal layout on mobile (#32641 by @DismalShadowX)
|
||||||
|
- Fix and improve batch attachment deletion handling when using OpenStack Swift (#32637 by @hugogameiro)
|
||||||
|
- Fix blocks not being applied on link timeline (#32625 by @tribela)
|
||||||
|
- Fix follow counters being incorrectly changed (#32622 by @oneiros)
|
||||||
|
- Fix 'unknown' media attachment type rendering (#32613 and #32713 by @ThisIsMissEm and @renatolond)
|
||||||
|
- Fix tl language native name (#32606 by @seav)
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Update dependencies
|
||||||
|
|
||||||
|
## [4.3.1] - 2024-10-21
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add more explicit explanations about author attribution and `fediverse:creator` (#32383 by @ClearlyClaire)
|
||||||
|
- Add ability to group follow notifications in WebUI, can be disabled in the column settings (#32520 by @renchap)
|
||||||
|
- Add back a 6 hours mute duration option (#32522 by @renchap)
|
||||||
|
- Add note about not changing ActiveRecord encryption secrets once they are set (#32413, #32476, #32512, and #32537 by @ClearlyClaire and @mjankowski)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Change translation feature to translate to selected regional variant (e.g. pt-BR) if available (#32428 by @c960657)
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Remove ability to get embed code for remote posts (#32578 by @ClearlyClaire)\
|
||||||
|
Getting the embed code is only reliable for local posts.\
|
||||||
|
It never worked for non-Mastodon servers, and stopped working correctly with the changes made in 4.3.0.\
|
||||||
|
We have therefore decided to remove the menu entry while we investigate solutions.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix follow recommendation moderation page default language when using regional variant (#32580 by @ClearlyClaire)
|
||||||
|
- Fix column-settings spacing in local timeline in advanced view (#32567 by @lindwurm)
|
||||||
|
- Fix broken i18n in text welcome mailer tags area (#32571 by @mjankowski)
|
||||||
|
- Fix missing or incorrect cache-control headers for Streaming server (#32551 by @ThisIsMissEm)
|
||||||
|
- Fix only the first paragraph being displayed in some notifications (#32348 by @ClearlyClaire)
|
||||||
|
- Fix reblog icons on account media view (#32506 by @tribela)
|
||||||
|
- Fix Content-Security-Policy not allowing OpenStack SWIFT object storage URI (#32439 by @kenkiku1021)
|
||||||
|
- Fix back arrow pointing to the incorrect direction in RTL languages (#32485 by @renchap)
|
||||||
|
- Fix streaming server using `REDIS_USERNAME` instead of `REDIS_USER` (#32493 by @ThisIsMissEm)
|
||||||
|
- Fix follow recommendation carrousel scrolling on RTL layouts (#32462 and #32505 by @ClearlyClaire)
|
||||||
|
- Fix follow recommendation suppressions not applying immediately (#32392 by @ClearlyClaire)
|
||||||
|
- Fix language of push notifications (#32415 by @ClearlyClaire)
|
||||||
|
- Fix mute duration not being shown in list of muted accounts in web UI (#32388 by @ClearlyClaire)
|
||||||
|
- Fix “Mark every notification as read” not updating the read marker if scrolled down (#32385 by @ClearlyClaire)
|
||||||
|
- Fix “Mention” appearing for otherwise filtered posts (#32356 by @ClearlyClaire)
|
||||||
|
- Fix notification requests from suspended accounts still being listed (#32354 by @ClearlyClaire)
|
||||||
|
- Fix list edition modal styling (#32358 and #32367 by @ClearlyClaire and @vmstan)
|
||||||
|
- Fix 4 columns barely not fitting on 1920px screen (#32361 by @ClearlyClaire)
|
||||||
|
- Fix icon alignment in applications list (#32293 by @mjankowski)
|
||||||
|
|
||||||
## [4.3.0] - 2024-10-08
|
## [4.3.0] - 2024-10-08
|
||||||
|
|
||||||
The following changelog entries focus on changes visible to users, administrators, client developers or federated software developers, but there has also been a lot of code modernization, refactoring, and tooling work, in particular by @mjankowski.
|
The following changelog entries focus on changes visible to users, administrators, client developers or federated software developers, but there has also been a lot of code modernization, refactoring, and tooling work, in particular by @mjankowski.
|
||||||
@ -26,7 +110,7 @@ The following changelog entries focus on changes visible to users, administrator
|
|||||||
- `GET /api/v2/notifications`: https://docs.joinmastodon.org/methods/grouped_notifications/#get-grouped
|
- `GET /api/v2/notifications`: https://docs.joinmastodon.org/methods/grouped_notifications/#get-grouped
|
||||||
- `GET /api/v2/notifications/:group_key`: https://docs.joinmastodon.org/methods/grouped_notifications/#get-notification-group
|
- `GET /api/v2/notifications/:group_key`: https://docs.joinmastodon.org/methods/grouped_notifications/#get-notification-group
|
||||||
- `GET /api/v2/notifications/:group_key/accounts`: https://docs.joinmastodon.org/methods/grouped_notifications/#get-group-accounts
|
- `GET /api/v2/notifications/:group_key/accounts`: https://docs.joinmastodon.org/methods/grouped_notifications/#get-group-accounts
|
||||||
- `POST /api/v2/notifications/:group_key/dimsiss`: https://docs.joinmastodon.org/methods/grouped_notifications/#dismiss-group
|
- `POST /api/v2/notifications/:group_key/dismiss`: https://docs.joinmastodon.org/methods/grouped_notifications/#dismiss-group
|
||||||
- `GET /api/v2/notifications/:unread_count`: https://docs.joinmastodon.org/methods/grouped_notifications/#unread-group-count
|
- `GET /api/v2/notifications/:unread_count`: https://docs.joinmastodon.org/methods/grouped_notifications/#unread-group-count
|
||||||
- **Add notification policies, filtered notifications and notification requests** (#29366, #29529, #29433, #29565, #29567, #29572, #29575, #29588, #29646, #29652, #29658, #29666, #29693, #29699, #29737, #29706, #29570, #29752, #29810, #29826, #30114, #30251, #30559, #29868, #31008, #31011, #30996, #31149, #31220, #31222, #31225, #31242, #31262, #31250, #31273, #31310, #31316, #31322, #31329, #31324, #31331, #31343, #31342, #31309, #31358, #31378, #31406, #31256, #31456, #31419, #31457, #31508, #31540, #31541, #31723, #32062 and #32281 by @ClearlyClaire, @Gargron, @TheEssem, @mgmn, @oneiros, and @renchap)\
|
- **Add notification policies, filtered notifications and notification requests** (#29366, #29529, #29433, #29565, #29567, #29572, #29575, #29588, #29646, #29652, #29658, #29666, #29693, #29699, #29737, #29706, #29570, #29752, #29810, #29826, #30114, #30251, #30559, #29868, #31008, #31011, #30996, #31149, #31220, #31222, #31225, #31242, #31262, #31250, #31273, #31310, #31316, #31322, #31329, #31324, #31331, #31343, #31342, #31309, #31358, #31378, #31406, #31256, #31456, #31419, #31457, #31508, #31540, #31541, #31723, #32062 and #32281 by @ClearlyClaire, @Gargron, @TheEssem, @mgmn, @oneiros, and @renchap)\
|
||||||
The old “Block notifications from non-followers”, “Block notifications from people you don't follow” and “Block direct messages from people you don't follow” notification settings have been replaced by a new set of settings found directly in the notification column.\
|
The old “Block notifications from non-followers”, “Block notifications from people you don't follow” and “Block direct messages from people you don't follow” notification settings have been replaced by a new set of settings found directly in the notification column.\
|
||||||
@ -357,7 +441,7 @@ The following changelog entries focus on changes visible to users, administrator
|
|||||||
- Fix empty environment variables not using default nil value (#27400 by @renchap)
|
- Fix empty environment variables not using default nil value (#27400 by @renchap)
|
||||||
- Fix language sorting in settings (#27158 by @gunchleoc)
|
- Fix language sorting in settings (#27158 by @gunchleoc)
|
||||||
|
|
||||||
## |4.2.11] - 2024-08-16
|
## [4.2.11] - 2024-08-16
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
103
Dockerfile
103
Dockerfile
@ -1,4 +1,4 @@
|
|||||||
# syntax=docker/dockerfile:1.10
|
# syntax=docker/dockerfile:1.12
|
||||||
|
|
||||||
# This file is designed for production server deployment, not local development work
|
# This file is designed for production server deployment, not local development work
|
||||||
# For a containerized local dev environment, see: https://github.com/mastodon/mastodon/blob/main/README.md#docker
|
# For a containerized local dev environment, see: https://github.com/mastodon/mastodon/blob/main/README.md#docker
|
||||||
@ -12,7 +12,7 @@ ARG BUILDPLATFORM=${BUILDPLATFORM}
|
|||||||
|
|
||||||
# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.3.x"]
|
# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.3.x"]
|
||||||
# renovate: datasource=docker depName=docker.io/ruby
|
# renovate: datasource=docker depName=docker.io/ruby
|
||||||
ARG RUBY_VERSION="3.3.5"
|
ARG RUBY_VERSION="3.3.6"
|
||||||
# # Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"]
|
# # Node version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"]
|
||||||
# renovate: datasource=node-version depName=node
|
# renovate: datasource=node-version depName=node
|
||||||
ARG NODE_MAJOR_VERSION="22"
|
ARG NODE_MAJOR_VERSION="22"
|
||||||
@ -29,6 +29,8 @@ FROM docker.io/ruby:${RUBY_VERSION}-slim-${DEBIAN_VERSION} AS ruby
|
|||||||
ARG MASTODON_VERSION_PRERELEASE=""
|
ARG MASTODON_VERSION_PRERELEASE=""
|
||||||
# Append build metadata or fork information to version.rb [--build-arg MASTODON_VERSION_METADATA="pr-123456"]
|
# Append build metadata or fork information to version.rb [--build-arg MASTODON_VERSION_METADATA="pr-123456"]
|
||||||
ARG MASTODON_VERSION_METADATA=""
|
ARG MASTODON_VERSION_METADATA=""
|
||||||
|
# Will be available as Mastodon::Version.source_commit
|
||||||
|
ARG SOURCE_COMMIT=""
|
||||||
|
|
||||||
# Allow Ruby on Rails to serve static files
|
# Allow Ruby on Rails to serve static files
|
||||||
# See: https://docs.joinmastodon.org/admin/config/#rails_serve_static_files
|
# See: https://docs.joinmastodon.org/admin/config/#rails_serve_static_files
|
||||||
@ -45,30 +47,31 @@ ARG GID="991"
|
|||||||
|
|
||||||
# Apply Mastodon build options based on options above
|
# Apply Mastodon build options based on options above
|
||||||
ENV \
|
ENV \
|
||||||
# Apply Mastodon version information
|
# Apply Mastodon version information
|
||||||
MASTODON_VERSION_PRERELEASE="${MASTODON_VERSION_PRERELEASE}" \
|
MASTODON_VERSION_PRERELEASE="${MASTODON_VERSION_PRERELEASE}" \
|
||||||
MASTODON_VERSION_METADATA="${MASTODON_VERSION_METADATA}" \
|
MASTODON_VERSION_METADATA="${MASTODON_VERSION_METADATA}" \
|
||||||
# Apply Mastodon static files and YJIT options
|
SOURCE_COMMIT="${SOURCE_COMMIT}" \
|
||||||
|
# Apply Mastodon static files and YJIT options
|
||||||
RAILS_SERVE_STATIC_FILES=${RAILS_SERVE_STATIC_FILES} \
|
RAILS_SERVE_STATIC_FILES=${RAILS_SERVE_STATIC_FILES} \
|
||||||
RUBY_YJIT_ENABLE=${RUBY_YJIT_ENABLE} \
|
RUBY_YJIT_ENABLE=${RUBY_YJIT_ENABLE} \
|
||||||
# Apply timezone
|
# Apply timezone
|
||||||
TZ=${TZ}
|
TZ=${TZ}
|
||||||
|
|
||||||
ENV \
|
ENV \
|
||||||
# Configure the IP to bind Mastodon to when serving traffic
|
# Configure the IP to bind Mastodon to when serving traffic
|
||||||
BIND="0.0.0.0" \
|
BIND="0.0.0.0" \
|
||||||
# Use production settings for Yarn, Node and related nodejs based tools
|
# Use production settings for Yarn, Node and related nodejs based tools
|
||||||
NODE_ENV="production" \
|
NODE_ENV="production" \
|
||||||
# Use production settings for Ruby on Rails
|
# Use production settings for Ruby on Rails
|
||||||
RAILS_ENV="production" \
|
RAILS_ENV="production" \
|
||||||
# Add Ruby and Mastodon installation to the PATH
|
# Add Ruby and Mastodon installation to the PATH
|
||||||
DEBIAN_FRONTEND="noninteractive" \
|
DEBIAN_FRONTEND="noninteractive" \
|
||||||
PATH="${PATH}:/opt/ruby/bin:/opt/mastodon/bin" \
|
PATH="${PATH}:/opt/ruby/bin:/opt/mastodon/bin" \
|
||||||
# Optimize jemalloc 5.x performance
|
# Optimize jemalloc 5.x performance
|
||||||
MALLOC_CONF="narenas:2,background_thread:true,thp:never,dirty_decay_ms:1000,muzzy_decay_ms:0" \
|
MALLOC_CONF="narenas:2,background_thread:true,thp:never,dirty_decay_ms:1000,muzzy_decay_ms:0" \
|
||||||
# Enable libvips, should not be changed
|
# Enable libvips, should not be changed
|
||||||
MASTODON_USE_LIBVIPS=true \
|
MASTODON_USE_LIBVIPS=true \
|
||||||
# Sidekiq will touch tmp/sidekiq_process_has_started_and_will_begin_processing_jobs to indicate it is ready. This can be used for a readiness check in Kubernetes
|
# Sidekiq will touch tmp/sidekiq_process_has_started_and_will_begin_processing_jobs to indicate it is ready. This can be used for a readiness check in Kubernetes
|
||||||
MASTODON_SIDEKIQ_READY_FILENAME=sidekiq_process_has_started_and_will_begin_processing_jobs
|
MASTODON_SIDEKIQ_READY_FILENAME=sidekiq_process_has_started_and_will_begin_processing_jobs
|
||||||
|
|
||||||
# Set default shell used for running commands
|
# Set default shell used for running commands
|
||||||
@ -79,14 +82,14 @@ ARG TARGETPLATFORM
|
|||||||
RUN echo "Target platform is $TARGETPLATFORM"
|
RUN echo "Target platform is $TARGETPLATFORM"
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
# Remove automatic apt cache Docker cleanup scripts
|
# Remove automatic apt cache Docker cleanup scripts
|
||||||
rm -f /etc/apt/apt.conf.d/docker-clean; \
|
rm -f /etc/apt/apt.conf.d/docker-clean; \
|
||||||
# Sets timezone
|
# Sets timezone
|
||||||
echo "${TZ}" > /etc/localtime; \
|
echo "${TZ}" > /etc/localtime; \
|
||||||
# Creates mastodon user/group and sets home directory
|
# Creates mastodon user/group and sets home directory
|
||||||
groupadd -g "${GID}" mastodon; \
|
groupadd -g "${GID}" mastodon; \
|
||||||
useradd -l -u "${UID}" -g "${GID}" -m -d /opt/mastodon mastodon; \
|
useradd -l -u "${UID}" -g "${GID}" -m -d /opt/mastodon mastodon; \
|
||||||
# Creates /mastodon symlink to /opt/mastodon
|
# Creates /mastodon symlink to /opt/mastodon
|
||||||
ln -s /opt/mastodon /mastodon;
|
ln -s /opt/mastodon /mastodon;
|
||||||
|
|
||||||
# Set /opt/mastodon as working directory
|
# Set /opt/mastodon as working directory
|
||||||
@ -94,13 +97,13 @@ WORKDIR /opt/mastodon
|
|||||||
|
|
||||||
# hadolint ignore=DL3008,DL3005
|
# hadolint ignore=DL3008,DL3005
|
||||||
RUN \
|
RUN \
|
||||||
# Mount Apt cache and lib directories from Docker buildx caches
|
# Mount Apt cache and lib directories from Docker buildx caches
|
||||||
--mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \
|
--mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \
|
||||||
--mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
|
--mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
|
||||||
# Apt update & upgrade to check for security updates to Debian image
|
# Apt update & upgrade to check for security updates to Debian image
|
||||||
apt-get update; \
|
apt-get update; \
|
||||||
apt-get dist-upgrade -yq; \
|
apt-get dist-upgrade -yq; \
|
||||||
# Install jemalloc, curl and other necessary components
|
# Install jemalloc, curl and other necessary components
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
curl \
|
curl \
|
||||||
file \
|
file \
|
||||||
@ -111,9 +114,9 @@ RUN \
|
|||||||
tzdata \
|
tzdata \
|
||||||
wget \
|
wget \
|
||||||
; \
|
; \
|
||||||
# Patch Ruby to use jemalloc
|
# Patch Ruby to use jemalloc
|
||||||
patchelf --add-needed libjemalloc.so.2 /usr/local/bin/ruby; \
|
patchelf --add-needed libjemalloc.so.2 /usr/local/bin/ruby; \
|
||||||
# Discard patchelf after use
|
# Discard patchelf after use
|
||||||
apt-get purge -y \
|
apt-get purge -y \
|
||||||
patchelf \
|
patchelf \
|
||||||
;
|
;
|
||||||
@ -132,10 +135,10 @@ ARG TARGETPLATFORM
|
|||||||
|
|
||||||
# hadolint ignore=DL3008
|
# hadolint ignore=DL3008
|
||||||
RUN \
|
RUN \
|
||||||
# Mount Apt cache and lib directories from Docker buildx caches
|
# Mount Apt cache and lib directories from Docker buildx caches
|
||||||
--mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \
|
--mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \
|
||||||
--mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
|
--mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
|
||||||
# Install build tools and bundler dependencies from APT
|
# Install build tools and bundler dependencies from APT
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
autoconf \
|
autoconf \
|
||||||
automake \
|
automake \
|
||||||
@ -181,7 +184,7 @@ RUN \
|
|||||||
;
|
;
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
# Configure Corepack
|
# Configure Corepack
|
||||||
rm /usr/local/bin/yarn*; \
|
rm /usr/local/bin/yarn*; \
|
||||||
corepack enable; \
|
corepack enable; \
|
||||||
corepack prepare --activate;
|
corepack prepare --activate;
|
||||||
@ -263,17 +266,17 @@ ARG TARGETPLATFORM
|
|||||||
COPY Gemfile* /opt/mastodon/
|
COPY Gemfile* /opt/mastodon/
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
# Mount Ruby Gem caches
|
# Mount Ruby Gem caches
|
||||||
--mount=type=cache,id=gem-cache-${TARGETPLATFORM},target=/usr/local/bundle/cache/,sharing=locked \
|
--mount=type=cache,id=gem-cache-${TARGETPLATFORM},target=/usr/local/bundle/cache/,sharing=locked \
|
||||||
# Configure bundle to prevent changes to Gemfile and Gemfile.lock
|
# Configure bundle to prevent changes to Gemfile and Gemfile.lock
|
||||||
bundle config set --global frozen "true"; \
|
bundle config set --global frozen "true"; \
|
||||||
# Configure bundle to not cache downloaded Gems
|
# Configure bundle to not cache downloaded Gems
|
||||||
bundle config set --global cache_all "false"; \
|
bundle config set --global cache_all "false"; \
|
||||||
# Configure bundle to only process production Gems
|
# Configure bundle to only process production Gems
|
||||||
bundle config set --local without "development test"; \
|
bundle config set --local without "development test"; \
|
||||||
# Configure bundle to not warn about root user
|
# Configure bundle to not warn about root user
|
||||||
bundle config set silence_root_warning "true"; \
|
bundle config set silence_root_warning "true"; \
|
||||||
# Download and install required Gems
|
# Download and install required Gems
|
||||||
bundle install -j"$(nproc)";
|
bundle install -j"$(nproc)";
|
||||||
|
|
||||||
# Create temporary node specific build layer from build layer
|
# Create temporary node specific build layer from build layer
|
||||||
@ -288,9 +291,9 @@ COPY .yarn /opt/mastodon/.yarn
|
|||||||
|
|
||||||
# hadolint ignore=DL3008
|
# hadolint ignore=DL3008
|
||||||
RUN \
|
RUN \
|
||||||
--mount=type=cache,id=corepack-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/corepack,sharing=locked \
|
--mount=type=cache,id=corepack-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/corepack,sharing=locked \
|
||||||
--mount=type=cache,id=yarn-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/yarn,sharing=locked \
|
--mount=type=cache,id=yarn-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/yarn,sharing=locked \
|
||||||
# Install Node packages
|
# Install Node packages
|
||||||
yarn workspaces focus --production @mastodon/mastodon;
|
yarn workspaces focus --production @mastodon/mastodon;
|
||||||
|
|
||||||
# Create temporary assets build layer from build layer
|
# Create temporary assets build layer from build layer
|
||||||
@ -311,10 +314,10 @@ ARG TARGETPLATFORM
|
|||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
ldconfig; \
|
ldconfig; \
|
||||||
# Use Ruby on Rails to create Mastodon assets
|
# Use Ruby on Rails to create Mastodon assets
|
||||||
SECRET_KEY_BASE_DUMMY=1 \
|
SECRET_KEY_BASE_DUMMY=1 \
|
||||||
bundle exec rails assets:precompile; \
|
bundle exec rails assets:precompile; \
|
||||||
# Cleanup temporary files
|
# Cleanup temporary files
|
||||||
rm -fr /opt/mastodon/tmp;
|
rm -fr /opt/mastodon/tmp;
|
||||||
|
|
||||||
# Prep final Mastodon Ruby layer
|
# Prep final Mastodon Ruby layer
|
||||||
@ -324,13 +327,13 @@ ARG TARGETPLATFORM
|
|||||||
|
|
||||||
# hadolint ignore=DL3008
|
# hadolint ignore=DL3008
|
||||||
RUN \
|
RUN \
|
||||||
# Mount Apt cache and lib directories from Docker buildx caches
|
# Mount Apt cache and lib directories from Docker buildx caches
|
||||||
--mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \
|
--mount=type=cache,id=apt-cache-${TARGETPLATFORM},target=/var/cache/apt,sharing=locked \
|
||||||
--mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
|
--mount=type=cache,id=apt-lib-${TARGETPLATFORM},target=/var/lib/apt,sharing=locked \
|
||||||
# Mount Corepack and Yarn caches from Docker buildx caches
|
# Mount Corepack and Yarn caches from Docker buildx caches
|
||||||
--mount=type=cache,id=corepack-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/corepack,sharing=locked \
|
--mount=type=cache,id=corepack-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/corepack,sharing=locked \
|
||||||
--mount=type=cache,id=yarn-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/yarn,sharing=locked \
|
--mount=type=cache,id=yarn-cache-${TARGETPLATFORM},target=/usr/local/share/.cache/yarn,sharing=locked \
|
||||||
# Apt update install non-dev versions of necessary components
|
# Apt update install non-dev versions of necessary components
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
libexpat1 \
|
libexpat1 \
|
||||||
libglib2.0-0 \
|
libglib2.0-0 \
|
||||||
@ -386,7 +389,7 @@ COPY --from=ffmpeg /usr/local/ffmpeg/lib /usr/local/lib
|
|||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
ldconfig; \
|
ldconfig; \
|
||||||
# Smoketest media processors
|
# Smoketest media processors
|
||||||
vips -v; \
|
vips -v; \
|
||||||
ffmpeg -version; \
|
ffmpeg -version; \
|
||||||
ffprobe -version;
|
ffprobe -version;
|
||||||
@ -396,10 +399,10 @@ RUN \
|
|||||||
bundle exec bootsnap precompile --gemfile app/ lib/;
|
bundle exec bootsnap precompile --gemfile app/ lib/;
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
# Pre-create and chown system volume to Mastodon user
|
# Pre-create and chown system volume to Mastodon user
|
||||||
mkdir -p /opt/mastodon/public/system; \
|
mkdir -p /opt/mastodon/public/system; \
|
||||||
chown mastodon:mastodon /opt/mastodon/public/system; \
|
chown mastodon:mastodon /opt/mastodon/public/system; \
|
||||||
# Set Mastodon user as owner of tmp folder
|
# Set Mastodon user as owner of tmp folder
|
||||||
chown -R mastodon:mastodon /opt/mastodon/tmp;
|
chown -R mastodon:mastodon /opt/mastodon/tmp;
|
||||||
|
|
||||||
# Set the running user for resulting container
|
# Set the running user for resulting container
|
||||||
|
16
Gemfile
16
Gemfile
@ -6,7 +6,7 @@ ruby '>= 3.2.0'
|
|||||||
gem 'propshaft'
|
gem 'propshaft'
|
||||||
gem 'puma', '~> 6.3'
|
gem 'puma', '~> 6.3'
|
||||||
gem 'rack', '~> 2.2.7'
|
gem 'rack', '~> 2.2.7'
|
||||||
gem 'rails', '~> 7.1.1'
|
gem 'rails', '~> 7.2.0'
|
||||||
gem 'thor', '~> 1.2'
|
gem 'thor', '~> 1.2'
|
||||||
|
|
||||||
gem 'dotenv'
|
gem 'dotenv'
|
||||||
@ -25,7 +25,7 @@ gem 'ruby-vips', '~> 2.2', require: false
|
|||||||
gem 'active_model_serializers', '~> 0.10'
|
gem 'active_model_serializers', '~> 0.10'
|
||||||
gem 'addressable', '~> 2.8'
|
gem 'addressable', '~> 2.8'
|
||||||
gem 'bootsnap', '~> 1.18.0', require: false
|
gem 'bootsnap', '~> 1.18.0', require: false
|
||||||
gem 'browser', '< 6' # https://github.com/fnando/browser/issues/543
|
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'
|
||||||
@ -47,13 +47,14 @@ gem 'color_diff', '~> 0.1'
|
|||||||
gem 'csv', '~> 3.2'
|
gem 'csv', '~> 3.2'
|
||||||
gem 'discard', '~> 1.2'
|
gem 'discard', '~> 1.2'
|
||||||
gem 'doorkeeper', '~> 5.6'
|
gem 'doorkeeper', '~> 5.6'
|
||||||
|
gem 'faraday-httpclient'
|
||||||
gem 'fast_blank', '~> 1.0'
|
gem 'fast_blank', '~> 1.0'
|
||||||
gem 'fastimage'
|
gem 'fastimage'
|
||||||
gem 'hiredis', '~> 0.6'
|
gem 'hiredis', '~> 0.6'
|
||||||
gem 'htmlentities', '~> 4.3'
|
gem 'htmlentities', '~> 4.3'
|
||||||
gem 'http', '~> 5.2.0'
|
gem 'http', '~> 5.2.0'
|
||||||
gem 'http_accept_language', '~> 2.1'
|
gem 'http_accept_language', '~> 2.1'
|
||||||
gem 'httplog', '~> 1.7.0'
|
gem 'httplog', '~> 1.7.0', require: false
|
||||||
gem 'i18n'
|
gem 'i18n'
|
||||||
gem 'idn-ruby', require: 'idn'
|
gem 'idn-ruby', require: 'idn'
|
||||||
gem 'inline_svg'
|
gem 'inline_svg'
|
||||||
@ -62,6 +63,7 @@ gem 'kaminari', '~> 1.2'
|
|||||||
gem 'link_header', '~> 0.0'
|
gem 'link_header', '~> 0.0'
|
||||||
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
|
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
|
||||||
gem 'mime-types', '~> 3.6.0', require: 'mime/types/columnar'
|
gem 'mime-types', '~> 3.6.0', require: 'mime/types/columnar'
|
||||||
|
gem 'mutex_m'
|
||||||
gem 'nokogiri', '~> 1.15'
|
gem 'nokogiri', '~> 1.15'
|
||||||
gem 'oj', '~> 3.14'
|
gem 'oj', '~> 3.14'
|
||||||
gem 'ox', '~> 2.14'
|
gem 'ox', '~> 2.14'
|
||||||
@ -103,7 +105,7 @@ gem 'opentelemetry-api', '~> 1.4.0'
|
|||||||
group :opentelemetry do
|
group :opentelemetry do
|
||||||
gem 'opentelemetry-exporter-otlp', '~> 0.29.0', require: false
|
gem 'opentelemetry-exporter-otlp', '~> 0.29.0', require: false
|
||||||
gem 'opentelemetry-instrumentation-active_job', '~> 0.7.1', require: false
|
gem 'opentelemetry-instrumentation-active_job', '~> 0.7.1', require: false
|
||||||
gem 'opentelemetry-instrumentation-active_model_serializers', '~> 0.20.1', require: false
|
gem 'opentelemetry-instrumentation-active_model_serializers', '~> 0.21.0', require: false
|
||||||
gem 'opentelemetry-instrumentation-concurrent_ruby', '~> 0.21.2', require: false
|
gem 'opentelemetry-instrumentation-concurrent_ruby', '~> 0.21.2', require: false
|
||||||
gem 'opentelemetry-instrumentation-excon', '~> 0.22.0', require: false
|
gem 'opentelemetry-instrumentation-excon', '~> 0.22.0', require: false
|
||||||
gem 'opentelemetry-instrumentation-faraday', '~> 0.24.1', require: false
|
gem 'opentelemetry-instrumentation-faraday', '~> 0.24.1', require: false
|
||||||
@ -112,7 +114,7 @@ group :opentelemetry do
|
|||||||
gem 'opentelemetry-instrumentation-net_http', '~> 0.22.4', require: false
|
gem 'opentelemetry-instrumentation-net_http', '~> 0.22.4', require: false
|
||||||
gem 'opentelemetry-instrumentation-pg', '~> 0.29.0', require: false
|
gem 'opentelemetry-instrumentation-pg', '~> 0.29.0', require: false
|
||||||
gem 'opentelemetry-instrumentation-rack', '~> 0.25.0', require: false
|
gem 'opentelemetry-instrumentation-rack', '~> 0.25.0', require: false
|
||||||
gem 'opentelemetry-instrumentation-rails', '~> 0.32.0', require: false
|
gem 'opentelemetry-instrumentation-rails', '~> 0.34.0', require: false
|
||||||
gem 'opentelemetry-instrumentation-redis', '~> 0.25.3', require: false
|
gem 'opentelemetry-instrumentation-redis', '~> 0.25.3', require: false
|
||||||
gem 'opentelemetry-instrumentation-sidekiq', '~> 0.25.2', require: false
|
gem 'opentelemetry-instrumentation-sidekiq', '~> 0.25.2', require: false
|
||||||
gem 'opentelemetry-sdk', '~> 1.4', require: false
|
gem 'opentelemetry-sdk', '~> 1.4', require: false
|
||||||
@ -170,7 +172,7 @@ group :development do
|
|||||||
gem 'rubocop-rspec_rails', require: false
|
gem 'rubocop-rspec_rails', require: false
|
||||||
|
|
||||||
# Annotates modules with schema
|
# Annotates modules with schema
|
||||||
gem 'annotate', '~> 3.2'
|
gem 'annotaterb', '~> 4.13'
|
||||||
|
|
||||||
# Enhanced error message pages for development
|
# Enhanced error message pages for development
|
||||||
gem 'better_errors', '~> 2.9'
|
gem 'better_errors', '~> 2.9'
|
||||||
@ -220,7 +222,7 @@ gem 'concurrent-ruby', require: false
|
|||||||
gem 'connection_pool', require: false
|
gem 'connection_pool', require: false
|
||||||
gem 'xorcist', '~> 1.1'
|
gem 'xorcist', '~> 1.1'
|
||||||
|
|
||||||
gem 'net-http', '~> 0.4.0'
|
gem 'net-http', '~> 0.5.0'
|
||||||
gem 'rubyzip', '~> 2.3'
|
gem 'rubyzip', '~> 2.3'
|
||||||
|
|
||||||
gem 'hcaptcha', '~> 7.1'
|
gem 'hcaptcha', '~> 7.1'
|
||||||
|
383
Gemfile.lock
383
Gemfile.lock
@ -10,116 +10,111 @@ GIT
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actioncable (7.1.4.2)
|
actioncable (7.2.2.1)
|
||||||
actionpack (= 7.1.4.2)
|
actionpack (= 7.2.2.1)
|
||||||
activesupport (= 7.1.4.2)
|
activesupport (= 7.2.2.1)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
zeitwerk (~> 2.6)
|
zeitwerk (~> 2.6)
|
||||||
actionmailbox (7.1.4.2)
|
actionmailbox (7.2.2.1)
|
||||||
actionpack (= 7.1.4.2)
|
actionpack (= 7.2.2.1)
|
||||||
activejob (= 7.1.4.2)
|
activejob (= 7.2.2.1)
|
||||||
activerecord (= 7.1.4.2)
|
activerecord (= 7.2.2.1)
|
||||||
activestorage (= 7.1.4.2)
|
activestorage (= 7.2.2.1)
|
||||||
activesupport (= 7.1.4.2)
|
activesupport (= 7.2.2.1)
|
||||||
mail (>= 2.7.1)
|
mail (>= 2.8.0)
|
||||||
net-imap
|
actionmailer (7.2.2.1)
|
||||||
net-pop
|
actionpack (= 7.2.2.1)
|
||||||
net-smtp
|
actionview (= 7.2.2.1)
|
||||||
actionmailer (7.1.4.2)
|
activejob (= 7.2.2.1)
|
||||||
actionpack (= 7.1.4.2)
|
activesupport (= 7.2.2.1)
|
||||||
actionview (= 7.1.4.2)
|
mail (>= 2.8.0)
|
||||||
activejob (= 7.1.4.2)
|
|
||||||
activesupport (= 7.1.4.2)
|
|
||||||
mail (~> 2.5, >= 2.5.4)
|
|
||||||
net-imap
|
|
||||||
net-pop
|
|
||||||
net-smtp
|
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
actionpack (7.1.4.2)
|
actionpack (7.2.2.1)
|
||||||
actionview (= 7.1.4.2)
|
actionview (= 7.2.2.1)
|
||||||
activesupport (= 7.1.4.2)
|
activesupport (= 7.2.2.1)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
racc
|
racc
|
||||||
rack (>= 2.2.4)
|
rack (>= 2.2.4, < 3.2)
|
||||||
rack-session (>= 1.0.1)
|
rack-session (>= 1.0.1)
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
rails-html-sanitizer (~> 1.6)
|
rails-html-sanitizer (~> 1.6)
|
||||||
actiontext (7.1.4.2)
|
useragent (~> 0.16)
|
||||||
actionpack (= 7.1.4.2)
|
actiontext (7.2.2.1)
|
||||||
activerecord (= 7.1.4.2)
|
actionpack (= 7.2.2.1)
|
||||||
activestorage (= 7.1.4.2)
|
activerecord (= 7.2.2.1)
|
||||||
activesupport (= 7.1.4.2)
|
activestorage (= 7.2.2.1)
|
||||||
|
activesupport (= 7.2.2.1)
|
||||||
globalid (>= 0.6.0)
|
globalid (>= 0.6.0)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (7.1.4.2)
|
actionview (7.2.2.1)
|
||||||
activesupport (= 7.1.4.2)
|
activesupport (= 7.2.2.1)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.11)
|
erubi (~> 1.11)
|
||||||
rails-dom-testing (~> 2.2)
|
rails-dom-testing (~> 2.2)
|
||||||
rails-html-sanitizer (~> 1.6)
|
rails-html-sanitizer (~> 1.6)
|
||||||
active_model_serializers (0.10.14)
|
active_model_serializers (0.10.15)
|
||||||
actionpack (>= 4.1)
|
actionpack (>= 4.1)
|
||||||
activemodel (>= 4.1)
|
activemodel (>= 4.1)
|
||||||
case_transform (>= 0.2)
|
case_transform (>= 0.2)
|
||||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||||
activejob (7.1.4.2)
|
activejob (7.2.2.1)
|
||||||
activesupport (= 7.1.4.2)
|
activesupport (= 7.2.2.1)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (7.1.4.2)
|
activemodel (7.2.2.1)
|
||||||
activesupport (= 7.1.4.2)
|
activesupport (= 7.2.2.1)
|
||||||
activerecord (7.1.4.2)
|
activerecord (7.2.2.1)
|
||||||
activemodel (= 7.1.4.2)
|
activemodel (= 7.2.2.1)
|
||||||
activesupport (= 7.1.4.2)
|
activesupport (= 7.2.2.1)
|
||||||
timeout (>= 0.4.0)
|
timeout (>= 0.4.0)
|
||||||
activestorage (7.1.4.2)
|
activestorage (7.2.2.1)
|
||||||
actionpack (= 7.1.4.2)
|
actionpack (= 7.2.2.1)
|
||||||
activejob (= 7.1.4.2)
|
activejob (= 7.2.2.1)
|
||||||
activerecord (= 7.1.4.2)
|
activerecord (= 7.2.2.1)
|
||||||
activesupport (= 7.1.4.2)
|
activesupport (= 7.2.2.1)
|
||||||
marcel (~> 1.0)
|
marcel (~> 1.0)
|
||||||
activesupport (7.1.4.2)
|
activesupport (7.2.2.1)
|
||||||
base64
|
base64
|
||||||
|
benchmark (>= 0.3)
|
||||||
bigdecimal
|
bigdecimal
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.3.1)
|
||||||
connection_pool (>= 2.2.5)
|
connection_pool (>= 2.2.5)
|
||||||
drb
|
drb
|
||||||
i18n (>= 1.6, < 2)
|
i18n (>= 1.6, < 2)
|
||||||
|
logger (>= 1.4.2)
|
||||||
minitest (>= 5.1)
|
minitest (>= 5.1)
|
||||||
mutex_m
|
securerandom (>= 0.3)
|
||||||
tzinfo (~> 2.0)
|
tzinfo (~> 2.0, >= 2.0.5)
|
||||||
addressable (2.8.7)
|
addressable (2.8.7)
|
||||||
public_suffix (>= 2.0.2, < 7.0)
|
public_suffix (>= 2.0.2, < 7.0)
|
||||||
aes_key_wrap (1.1.0)
|
aes_key_wrap (1.1.0)
|
||||||
android_key_attestation (0.3.0)
|
android_key_attestation (0.3.0)
|
||||||
annotate (3.2.0)
|
annotaterb (4.13.0)
|
||||||
activerecord (>= 3.2, < 8.0)
|
|
||||||
rake (>= 10.4, < 14.0)
|
|
||||||
ast (2.4.2)
|
ast (2.4.2)
|
||||||
attr_required (1.0.2)
|
attr_required (1.0.2)
|
||||||
awrence (1.2.1)
|
|
||||||
aws-eventstream (1.3.0)
|
aws-eventstream (1.3.0)
|
||||||
aws-partitions (1.997.0)
|
aws-partitions (1.1025.0)
|
||||||
aws-sdk-core (3.211.0)
|
aws-sdk-core (3.214.0)
|
||||||
aws-eventstream (~> 1, >= 1.3.0)
|
aws-eventstream (~> 1, >= 1.3.0)
|
||||||
aws-partitions (~> 1, >= 1.992.0)
|
aws-partitions (~> 1, >= 1.992.0)
|
||||||
aws-sigv4 (~> 1.9)
|
aws-sigv4 (~> 1.9)
|
||||||
jmespath (~> 1, >= 1.6.1)
|
jmespath (~> 1, >= 1.6.1)
|
||||||
aws-sdk-kms (1.95.0)
|
aws-sdk-kms (1.96.0)
|
||||||
aws-sdk-core (~> 3, >= 3.210.0)
|
aws-sdk-core (~> 3, >= 3.210.0)
|
||||||
aws-sigv4 (~> 1.5)
|
aws-sigv4 (~> 1.5)
|
||||||
aws-sdk-s3 (1.169.0)
|
aws-sdk-s3 (1.176.1)
|
||||||
aws-sdk-core (~> 3, >= 3.210.0)
|
aws-sdk-core (~> 3, >= 3.210.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.5)
|
aws-sigv4 (~> 1.5)
|
||||||
aws-sigv4 (1.10.1)
|
aws-sigv4 (1.10.1)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
azure-blob (0.5.2)
|
azure-blob (0.5.4)
|
||||||
rexml
|
rexml
|
||||||
base64 (0.2.0)
|
base64 (0.2.0)
|
||||||
bcp47_spec (0.2.1)
|
bcp47_spec (0.2.1)
|
||||||
bcrypt (3.1.20)
|
bcrypt (3.1.20)
|
||||||
|
benchmark (0.4.0)
|
||||||
better_errors (2.10.1)
|
better_errors (2.10.1)
|
||||||
erubi (>= 1.0.0)
|
erubi (>= 1.0.0)
|
||||||
rack (>= 0.9.0)
|
rack (>= 0.9.0)
|
||||||
@ -133,7 +128,7 @@ GEM
|
|||||||
msgpack (~> 1.2)
|
msgpack (~> 1.2)
|
||||||
brakeman (6.2.2)
|
brakeman (6.2.2)
|
||||||
racc
|
racc
|
||||||
browser (5.3.1)
|
browser (6.2.0)
|
||||||
brpoplpush-redis_script (0.1.3)
|
brpoplpush-redis_script (0.1.3)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.5)
|
concurrent-ruby (~> 1.0, >= 1.0.5)
|
||||||
redis (>= 1.0, < 6)
|
redis (>= 1.0, < 6)
|
||||||
@ -173,15 +168,15 @@ GEM
|
|||||||
bigdecimal
|
bigdecimal
|
||||||
rexml
|
rexml
|
||||||
crass (1.0.6)
|
crass (1.0.6)
|
||||||
css_parser (1.19.1)
|
css_parser (1.21.0)
|
||||||
addressable
|
addressable
|
||||||
csv (3.3.0)
|
csv (3.3.1)
|
||||||
database_cleaner-active_record (2.2.0)
|
database_cleaner-active_record (2.2.0)
|
||||||
activerecord (>= 5.a)
|
activerecord (>= 5.a)
|
||||||
database_cleaner-core (~> 2.0.0)
|
database_cleaner-core (~> 2.0.0)
|
||||||
database_cleaner-core (2.0.1)
|
database_cleaner-core (2.0.1)
|
||||||
date (3.3.4)
|
date (3.4.1)
|
||||||
debug (1.9.2)
|
debug (1.10.0)
|
||||||
irb (~> 1.10)
|
irb (~> 1.10)
|
||||||
reline (>= 0.3.8)
|
reline (>= 0.3.8)
|
||||||
debug_inspector (1.2.0)
|
debug_inspector (1.2.0)
|
||||||
@ -191,22 +186,22 @@ GEM
|
|||||||
railties (>= 4.1.0)
|
railties (>= 4.1.0)
|
||||||
responders
|
responders
|
||||||
warden (~> 1.2.3)
|
warden (~> 1.2.3)
|
||||||
devise-two-factor (6.0.0)
|
devise-two-factor (6.1.0)
|
||||||
activesupport (~> 7.0)
|
activesupport (>= 7.0, < 8.1)
|
||||||
devise (~> 4.0)
|
devise (~> 4.0)
|
||||||
railties (~> 7.0)
|
railties (>= 7.0, < 8.1)
|
||||||
rotp (~> 6.0)
|
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)
|
||||||
diff-lcs (1.5.1)
|
diff-lcs (1.5.1)
|
||||||
discard (1.3.0)
|
discard (1.4.0)
|
||||||
activerecord (>= 4.2, < 8)
|
activerecord (>= 4.2, < 9.0)
|
||||||
docile (1.4.1)
|
docile (1.4.1)
|
||||||
domain_name (0.6.20240107)
|
domain_name (0.6.20240107)
|
||||||
doorkeeper (5.7.1)
|
doorkeeper (5.8.1)
|
||||||
railties (>= 5)
|
railties (>= 5)
|
||||||
dotenv (3.1.4)
|
dotenv (3.1.7)
|
||||||
drb (2.2.1)
|
drb (2.2.1)
|
||||||
elasticsearch (7.17.11)
|
elasticsearch (7.17.11)
|
||||||
elasticsearch-api (= 7.17.11)
|
elasticsearch-api (= 7.17.11)
|
||||||
@ -229,29 +224,14 @@ GEM
|
|||||||
fabrication (2.31.0)
|
fabrication (2.31.0)
|
||||||
faker (3.5.1)
|
faker (3.5.1)
|
||||||
i18n (>= 1.8.11, < 2)
|
i18n (>= 1.8.11, < 2)
|
||||||
faraday (1.10.3)
|
faraday (2.12.2)
|
||||||
faraday-em_http (~> 1.0)
|
faraday-net_http (>= 2.0, < 3.5)
|
||||||
faraday-em_synchrony (~> 1.0)
|
json
|
||||||
faraday-excon (~> 1.1)
|
logger
|
||||||
faraday-httpclient (~> 1.0)
|
faraday-httpclient (2.0.1)
|
||||||
faraday-multipart (~> 1.0)
|
httpclient (>= 2.2)
|
||||||
faraday-net_http (~> 1.0)
|
faraday-net_http (3.4.0)
|
||||||
faraday-net_http_persistent (~> 1.0)
|
net-http (>= 0.5.0)
|
||||||
faraday-patron (~> 1.0)
|
|
||||||
faraday-rack (~> 1.0)
|
|
||||||
faraday-retry (~> 1.0)
|
|
||||||
ruby2_keywords (>= 0.0.4)
|
|
||||||
faraday-em_http (1.0.0)
|
|
||||||
faraday-em_synchrony (1.0.0)
|
|
||||||
faraday-excon (1.1.0)
|
|
||||||
faraday-httpclient (1.0.1)
|
|
||||||
faraday-multipart (1.0.4)
|
|
||||||
multipart-post (~> 2)
|
|
||||||
faraday-net_http (1.0.2)
|
|
||||||
faraday-net_http_persistent (1.2.0)
|
|
||||||
faraday-patron (1.0.0)
|
|
||||||
faraday-rack (1.0.0)
|
|
||||||
faraday-retry (1.0.3)
|
|
||||||
fast_blank (1.0.1)
|
fast_blank (1.0.1)
|
||||||
fastimage (2.3.1)
|
fastimage (2.3.1)
|
||||||
ffi (1.17.0)
|
ffi (1.17.0)
|
||||||
@ -299,7 +279,7 @@ GEM
|
|||||||
rainbow
|
rainbow
|
||||||
rubocop (>= 1.0)
|
rubocop (>= 1.0)
|
||||||
sysexits (~> 1.1)
|
sysexits (~> 1.1)
|
||||||
hashdiff (1.1.1)
|
hashdiff (1.1.2)
|
||||||
hashie (5.0.0)
|
hashie (5.0.0)
|
||||||
hcaptcha (7.1.0)
|
hcaptcha (7.1.0)
|
||||||
json
|
json
|
||||||
@ -314,7 +294,7 @@ GEM
|
|||||||
http-cookie (~> 1.0)
|
http-cookie (~> 1.0)
|
||||||
http-form_data (~> 2.2)
|
http-form_data (~> 2.2)
|
||||||
llhttp-ffi (~> 0.5.0)
|
llhttp-ffi (~> 0.5.0)
|
||||||
http-cookie (1.0.5)
|
http-cookie (1.0.8)
|
||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
http-form_data (2.3.0)
|
http-form_data (2.3.0)
|
||||||
http_accept_language (2.1.1)
|
http_accept_language (2.1.1)
|
||||||
@ -338,8 +318,8 @@ GEM
|
|||||||
inline_svg (1.10.0)
|
inline_svg (1.10.0)
|
||||||
activesupport (>= 3.0)
|
activesupport (>= 3.0)
|
||||||
nokogiri (>= 1.6)
|
nokogiri (>= 1.6)
|
||||||
io-console (0.7.2)
|
io-console (0.8.0)
|
||||||
irb (1.14.1)
|
irb (1.14.3)
|
||||||
rdoc (>= 4.0.0)
|
rdoc (>= 4.0.0)
|
||||||
reline (>= 0.4.2)
|
reline (>= 0.4.2)
|
||||||
jd-paperclip-azure (3.0.0)
|
jd-paperclip-azure (3.0.0)
|
||||||
@ -347,7 +327,7 @@ GEM
|
|||||||
azure-blob (~> 0.5.2)
|
azure-blob (~> 0.5.2)
|
||||||
hashie (~> 5.0)
|
hashie (~> 5.0)
|
||||||
jmespath (1.6.2)
|
jmespath (1.6.2)
|
||||||
json (2.7.4)
|
json (2.9.1)
|
||||||
json-canonicalization (1.0.0)
|
json-canonicalization (1.0.0)
|
||||||
json-jwt (1.15.3.1)
|
json-jwt (1.15.3.1)
|
||||||
activesupport (>= 4.2)
|
activesupport (>= 4.2)
|
||||||
@ -362,13 +342,15 @@ GEM
|
|||||||
rack (>= 2.2, < 4)
|
rack (>= 2.2, < 4)
|
||||||
rdf (~> 3.3)
|
rdf (~> 3.3)
|
||||||
rexml (~> 3.2)
|
rexml (~> 3.2)
|
||||||
json-ld-preloaded (3.3.0)
|
json-ld-preloaded (3.3.1)
|
||||||
json-ld (~> 3.3)
|
json-ld (~> 3.3)
|
||||||
rdf (~> 3.3)
|
rdf (~> 3.3)
|
||||||
json-schema (5.0.1)
|
json-schema (5.1.1)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
|
bigdecimal (~> 3.1)
|
||||||
jsonapi-renderer (0.2.2)
|
jsonapi-renderer (0.2.2)
|
||||||
jwt (2.7.1)
|
jwt (2.9.3)
|
||||||
|
base64
|
||||||
kaminari (1.2.2)
|
kaminari (1.2.2)
|
||||||
activesupport (>= 4.1.0)
|
activesupport (>= 4.1.0)
|
||||||
kaminari-actionview (= 1.2.2)
|
kaminari-actionview (= 1.2.2)
|
||||||
@ -402,7 +384,7 @@ GEM
|
|||||||
llhttp-ffi (0.5.0)
|
llhttp-ffi (0.5.0)
|
||||||
ffi-compiler (~> 1.0)
|
ffi-compiler (~> 1.0)
|
||||||
rake (~> 13.0)
|
rake (~> 13.0)
|
||||||
logger (1.6.1)
|
logger (1.6.3)
|
||||||
lograge (0.14.0)
|
lograge (0.14.0)
|
||||||
actionpack (>= 4)
|
actionpack (>= 4)
|
||||||
activesupport (>= 4)
|
activesupport (>= 4)
|
||||||
@ -424,17 +406,16 @@ GEM
|
|||||||
mime-types (3.6.0)
|
mime-types (3.6.0)
|
||||||
logger
|
logger
|
||||||
mime-types-data (~> 3.2015)
|
mime-types-data (~> 3.2015)
|
||||||
mime-types-data (3.2024.1001)
|
mime-types-data (3.2024.1203)
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
mini_portile2 (2.8.7)
|
mini_portile2 (2.8.8)
|
||||||
minitest (5.25.1)
|
minitest (5.25.4)
|
||||||
msgpack (1.7.3)
|
msgpack (1.7.5)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
multipart-post (2.4.1)
|
mutex_m (0.3.0)
|
||||||
mutex_m (0.2.0)
|
net-http (0.5.0)
|
||||||
net-http (0.4.1)
|
|
||||||
uri
|
uri
|
||||||
net-imap (0.5.0)
|
net-imap (0.5.2)
|
||||||
date
|
date
|
||||||
net-protocol
|
net-protocol
|
||||||
net-ldap (0.19.0)
|
net-ldap (0.19.0)
|
||||||
@ -444,11 +425,11 @@ GEM
|
|||||||
timeout
|
timeout
|
||||||
net-smtp (0.5.0)
|
net-smtp (0.5.0)
|
||||||
net-protocol
|
net-protocol
|
||||||
nio4r (2.7.3)
|
nio4r (2.7.4)
|
||||||
nokogiri (1.16.7)
|
nokogiri (1.17.2)
|
||||||
mini_portile2 (~> 2.8.2)
|
mini_portile2 (~> 2.8.2)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
oj (3.16.6)
|
oj (3.16.8)
|
||||||
bigdecimal (>= 3.0)
|
bigdecimal (>= 3.0)
|
||||||
ostruct (>= 0.2)
|
ostruct (>= 0.2)
|
||||||
omniauth (2.1.2)
|
omniauth (2.1.2)
|
||||||
@ -479,43 +460,44 @@ GEM
|
|||||||
validate_email
|
validate_email
|
||||||
validate_url
|
validate_url
|
||||||
webfinger (~> 1.2)
|
webfinger (~> 1.2)
|
||||||
openssl (3.2.0)
|
openssl (3.2.1)
|
||||||
openssl-signature_algorithm (1.3.0)
|
openssl-signature_algorithm (1.3.0)
|
||||||
openssl (> 2.0)
|
openssl (> 2.0)
|
||||||
opentelemetry-api (1.4.0)
|
opentelemetry-api (1.4.0)
|
||||||
opentelemetry-common (0.21.0)
|
opentelemetry-common (0.21.0)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-exporter-otlp (0.29.0)
|
opentelemetry-exporter-otlp (0.29.1)
|
||||||
google-protobuf (>= 3.18)
|
google-protobuf (>= 3.18)
|
||||||
googleapis-common-protos-types (~> 1.3)
|
googleapis-common-protos-types (~> 1.3)
|
||||||
opentelemetry-api (~> 1.1)
|
opentelemetry-api (~> 1.1)
|
||||||
opentelemetry-common (~> 0.20)
|
opentelemetry-common (~> 0.20)
|
||||||
opentelemetry-sdk (~> 1.2)
|
opentelemetry-sdk (~> 1.2)
|
||||||
opentelemetry-semantic_conventions
|
opentelemetry-semantic_conventions
|
||||||
opentelemetry-helpers-sql-obfuscation (0.2.0)
|
opentelemetry-helpers-sql-obfuscation (0.2.1)
|
||||||
opentelemetry-common (~> 0.21)
|
opentelemetry-common (~> 0.21)
|
||||||
opentelemetry-instrumentation-action_mailer (0.2.0)
|
opentelemetry-instrumentation-action_mailer (0.3.0)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-active_support (~> 0.1)
|
opentelemetry-instrumentation-active_support (~> 0.7)
|
||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
opentelemetry-instrumentation-action_pack (0.9.0)
|
opentelemetry-instrumentation-action_pack (0.10.0)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
opentelemetry-instrumentation-rack (~> 0.21)
|
opentelemetry-instrumentation-rack (~> 0.21)
|
||||||
opentelemetry-instrumentation-action_view (0.7.2)
|
opentelemetry-instrumentation-action_view (0.8.0)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-active_support (~> 0.1)
|
opentelemetry-instrumentation-active_support (~> 0.7)
|
||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
opentelemetry-instrumentation-active_job (0.7.8)
|
opentelemetry-instrumentation-active_job (0.7.8)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
opentelemetry-instrumentation-active_model_serializers (0.20.2)
|
opentelemetry-instrumentation-active_model_serializers (0.21.0)
|
||||||
|
opentelemetry-api (~> 1.0)
|
||||||
|
opentelemetry-instrumentation-active_support (>= 0.7.0)
|
||||||
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
|
opentelemetry-instrumentation-active_record (0.8.1)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
opentelemetry-instrumentation-active_record (0.8.0)
|
opentelemetry-instrumentation-active_support (0.7.0)
|
||||||
opentelemetry-api (~> 1.0)
|
|
||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
|
||||||
opentelemetry-instrumentation-active_support (0.6.0)
|
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
opentelemetry-instrumentation-base (0.22.6)
|
opentelemetry-instrumentation-base (0.22.6)
|
||||||
@ -525,36 +507,36 @@ GEM
|
|||||||
opentelemetry-instrumentation-concurrent_ruby (0.21.4)
|
opentelemetry-instrumentation-concurrent_ruby (0.21.4)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
opentelemetry-instrumentation-excon (0.22.4)
|
opentelemetry-instrumentation-excon (0.22.5)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
opentelemetry-instrumentation-faraday (0.24.6)
|
opentelemetry-instrumentation-faraday (0.24.8)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
opentelemetry-instrumentation-http (0.23.4)
|
opentelemetry-instrumentation-http (0.23.5)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
opentelemetry-instrumentation-http_client (0.22.7)
|
opentelemetry-instrumentation-http_client (0.22.8)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
opentelemetry-instrumentation-net_http (0.22.7)
|
opentelemetry-instrumentation-net_http (0.22.8)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
opentelemetry-instrumentation-pg (0.29.0)
|
opentelemetry-instrumentation-pg (0.29.1)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-helpers-sql-obfuscation
|
opentelemetry-helpers-sql-obfuscation
|
||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
opentelemetry-instrumentation-rack (0.25.0)
|
opentelemetry-instrumentation-rack (0.25.0)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
opentelemetry-instrumentation-rails (0.32.0)
|
opentelemetry-instrumentation-rails (0.34.0)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-action_mailer (~> 0.2.0)
|
opentelemetry-instrumentation-action_mailer (~> 0.3.0)
|
||||||
opentelemetry-instrumentation-action_pack (~> 0.9.0)
|
opentelemetry-instrumentation-action_pack (~> 0.10.0)
|
||||||
opentelemetry-instrumentation-action_view (~> 0.7.0)
|
opentelemetry-instrumentation-action_view (~> 0.8.0)
|
||||||
opentelemetry-instrumentation-active_job (~> 0.7.0)
|
opentelemetry-instrumentation-active_job (~> 0.7.0)
|
||||||
opentelemetry-instrumentation-active_record (~> 0.8.0)
|
opentelemetry-instrumentation-active_record (~> 0.8.0)
|
||||||
opentelemetry-instrumentation-active_support (~> 0.6.0)
|
opentelemetry-instrumentation-active_support (~> 0.7.0)
|
||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
opentelemetry-instrumentation-redis (0.25.7)
|
opentelemetry-instrumentation-redis (0.25.7)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
@ -564,7 +546,7 @@ GEM
|
|||||||
opentelemetry-instrumentation-base (~> 0.22.1)
|
opentelemetry-instrumentation-base (~> 0.22.1)
|
||||||
opentelemetry-registry (0.3.1)
|
opentelemetry-registry (0.3.1)
|
||||||
opentelemetry-api (~> 1.1)
|
opentelemetry-api (~> 1.1)
|
||||||
opentelemetry-sdk (1.5.0)
|
opentelemetry-sdk (1.6.0)
|
||||||
opentelemetry-api (~> 1.1)
|
opentelemetry-api (~> 1.1)
|
||||||
opentelemetry-common (~> 0.20)
|
opentelemetry-common (~> 0.20)
|
||||||
opentelemetry-registry (~> 0.2)
|
opentelemetry-registry (~> 0.2)
|
||||||
@ -572,10 +554,10 @@ GEM
|
|||||||
opentelemetry-semantic_conventions (1.10.1)
|
opentelemetry-semantic_conventions (1.10.1)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
ostruct (0.6.0)
|
ostruct (0.6.1)
|
||||||
ox (2.14.18)
|
ox (2.14.18)
|
||||||
parallel (1.26.3)
|
parallel (1.26.3)
|
||||||
parser (3.3.5.0)
|
parser (3.3.6.0)
|
||||||
ast (~> 2.4.1)
|
ast (~> 2.4.1)
|
||||||
racc
|
racc
|
||||||
parslet (2.0.0)
|
parslet (2.0.0)
|
||||||
@ -597,10 +579,11 @@ GEM
|
|||||||
activesupport (>= 7.0.0)
|
activesupport (>= 7.0.0)
|
||||||
rack
|
rack
|
||||||
railties (>= 7.0.0)
|
railties (>= 7.0.0)
|
||||||
psych (5.1.2)
|
psych (5.2.2)
|
||||||
|
date
|
||||||
stringio
|
stringio
|
||||||
public_suffix (6.0.1)
|
public_suffix (6.0.1)
|
||||||
puma (6.4.3)
|
puma (6.5.0)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
pundit (2.4.0)
|
pundit (2.4.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
@ -626,23 +609,23 @@ GEM
|
|||||||
rack (< 3)
|
rack (< 3)
|
||||||
rack-test (2.1.0)
|
rack-test (2.1.0)
|
||||||
rack (>= 1.3)
|
rack (>= 1.3)
|
||||||
rackup (1.0.0)
|
rackup (1.0.1)
|
||||||
rack (< 3)
|
rack (< 3)
|
||||||
webrick
|
webrick
|
||||||
rails (7.1.4.2)
|
rails (7.2.2.1)
|
||||||
actioncable (= 7.1.4.2)
|
actioncable (= 7.2.2.1)
|
||||||
actionmailbox (= 7.1.4.2)
|
actionmailbox (= 7.2.2.1)
|
||||||
actionmailer (= 7.1.4.2)
|
actionmailer (= 7.2.2.1)
|
||||||
actionpack (= 7.1.4.2)
|
actionpack (= 7.2.2.1)
|
||||||
actiontext (= 7.1.4.2)
|
actiontext (= 7.2.2.1)
|
||||||
actionview (= 7.1.4.2)
|
actionview (= 7.2.2.1)
|
||||||
activejob (= 7.1.4.2)
|
activejob (= 7.2.2.1)
|
||||||
activemodel (= 7.1.4.2)
|
activemodel (= 7.2.2.1)
|
||||||
activerecord (= 7.1.4.2)
|
activerecord (= 7.2.2.1)
|
||||||
activestorage (= 7.1.4.2)
|
activestorage (= 7.2.2.1)
|
||||||
activesupport (= 7.1.4.2)
|
activesupport (= 7.2.2.1)
|
||||||
bundler (>= 1.15.0)
|
bundler (>= 1.15.0)
|
||||||
railties (= 7.1.4.2)
|
railties (= 7.2.2.1)
|
||||||
rails-controller-testing (1.0.5)
|
rails-controller-testing (1.0.5)
|
||||||
actionpack (>= 5.0.1.rc1)
|
actionpack (>= 5.0.1.rc1)
|
||||||
actionview (>= 5.0.1.rc1)
|
actionview (>= 5.0.1.rc1)
|
||||||
@ -651,16 +634,16 @@ GEM
|
|||||||
activesupport (>= 5.0.0)
|
activesupport (>= 5.0.0)
|
||||||
minitest
|
minitest
|
||||||
nokogiri (>= 1.6)
|
nokogiri (>= 1.6)
|
||||||
rails-html-sanitizer (1.6.0)
|
rails-html-sanitizer (1.6.2)
|
||||||
loofah (~> 2.21)
|
loofah (~> 2.21)
|
||||||
nokogiri (~> 1.14)
|
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
||||||
rails-i18n (7.0.10)
|
rails-i18n (7.0.10)
|
||||||
i18n (>= 0.7, < 2)
|
i18n (>= 0.7, < 2)
|
||||||
railties (>= 6.0.0, < 8)
|
railties (>= 6.0.0, < 8)
|
||||||
railties (7.1.4.2)
|
railties (7.2.2.1)
|
||||||
actionpack (= 7.1.4.2)
|
actionpack (= 7.2.2.1)
|
||||||
activesupport (= 7.1.4.2)
|
activesupport (= 7.2.2.1)
|
||||||
irb
|
irb (~> 1.13)
|
||||||
rackup (>= 1.0.0)
|
rackup (>= 1.0.0)
|
||||||
rake (>= 12.2)
|
rake (>= 12.2)
|
||||||
thor (~> 1.0, >= 1.2.2)
|
thor (~> 1.0, >= 1.2.2)
|
||||||
@ -673,7 +656,7 @@ GEM
|
|||||||
link_header (~> 0.0, >= 0.0.8)
|
link_header (~> 0.0, >= 0.0.8)
|
||||||
rdf-normalize (0.7.0)
|
rdf-normalize (0.7.0)
|
||||||
rdf (~> 3.3)
|
rdf (~> 3.3)
|
||||||
rdoc (6.7.0)
|
rdoc (6.10.0)
|
||||||
psych (>= 4.0.0)
|
psych (>= 4.0.0)
|
||||||
redcarpet (3.6.0)
|
redcarpet (3.6.0)
|
||||||
redis (4.8.1)
|
redis (4.8.1)
|
||||||
@ -681,17 +664,17 @@ 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.9.2)
|
regexp_parser (2.9.3)
|
||||||
reline (0.5.10)
|
reline (0.6.0)
|
||||||
io-console (~> 0.5)
|
io-console (~> 0.5)
|
||||||
request_store (1.6.0)
|
request_store (1.7.0)
|
||||||
rack (>= 1.4)
|
rack (>= 1.4)
|
||||||
responders (3.1.1)
|
responders (3.1.1)
|
||||||
actionpack (>= 5.2)
|
actionpack (>= 5.2)
|
||||||
railties (>= 5.2)
|
railties (>= 5.2)
|
||||||
rexml (3.3.9)
|
rexml (3.4.0)
|
||||||
rotp (6.3.0)
|
rotp (6.3.0)
|
||||||
rouge (4.4.0)
|
rouge (4.5.1)
|
||||||
rpam2 (4.0.2)
|
rpam2 (4.0.2)
|
||||||
rqrcode (2.2.0)
|
rqrcode (2.2.0)
|
||||||
chunky_png (~> 1.0)
|
chunky_png (~> 1.0)
|
||||||
@ -711,7 +694,7 @@ GEM
|
|||||||
rspec-mocks (3.13.2)
|
rspec-mocks (3.13.2)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.13.0)
|
rspec-support (~> 3.13.0)
|
||||||
rspec-rails (7.0.1)
|
rspec-rails (7.1.0)
|
||||||
actionpack (>= 7.0)
|
actionpack (>= 7.0)
|
||||||
activesupport (>= 7.0)
|
activesupport (>= 7.0)
|
||||||
railties (>= 7.0)
|
railties (>= 7.0)
|
||||||
@ -724,22 +707,22 @@ GEM
|
|||||||
rspec-expectations (~> 3.0)
|
rspec-expectations (~> 3.0)
|
||||||
rspec-mocks (~> 3.0)
|
rspec-mocks (~> 3.0)
|
||||||
sidekiq (>= 5, < 8)
|
sidekiq (>= 5, < 8)
|
||||||
rspec-support (3.13.1)
|
rspec-support (3.13.2)
|
||||||
rubocop (1.66.1)
|
rubocop (1.69.2)
|
||||||
json (~> 2.3)
|
json (~> 2.3)
|
||||||
language_server-protocol (>= 3.17.0)
|
language_server-protocol (>= 3.17.0)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
parser (>= 3.3.0.2)
|
parser (>= 3.3.0.2)
|
||||||
rainbow (>= 2.2.2, < 4.0)
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
regexp_parser (>= 2.4, < 3.0)
|
regexp_parser (>= 2.9.3, < 3.0)
|
||||||
rubocop-ast (>= 1.32.2, < 2.0)
|
rubocop-ast (>= 1.36.2, < 2.0)
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (>= 2.4.0, < 3.0)
|
unicode-display_width (>= 2.4.0, < 4.0)
|
||||||
rubocop-ast (1.32.3)
|
rubocop-ast (1.37.0)
|
||||||
parser (>= 3.3.1.0)
|
parser (>= 3.3.1.0)
|
||||||
rubocop-capybara (2.21.0)
|
rubocop-capybara (2.21.0)
|
||||||
rubocop (~> 1.41)
|
rubocop (~> 1.41)
|
||||||
rubocop-performance (1.22.1)
|
rubocop-performance (1.23.0)
|
||||||
rubocop (>= 1.48.1, < 2.0)
|
rubocop (>= 1.48.1, < 2.0)
|
||||||
rubocop-ast (>= 1.31.1, < 2.0)
|
rubocop-ast (>= 1.31.1, < 2.0)
|
||||||
rubocop-rails (2.27.0)
|
rubocop-rails (2.27.0)
|
||||||
@ -747,7 +730,7 @@ GEM
|
|||||||
rack (>= 1.1)
|
rack (>= 1.1)
|
||||||
rubocop (>= 1.52.0, < 2.0)
|
rubocop (>= 1.52.0, < 2.0)
|
||||||
rubocop-ast (>= 1.31.1, < 2.0)
|
rubocop-ast (>= 1.31.1, < 2.0)
|
||||||
rubocop-rspec (3.2.0)
|
rubocop-rspec (3.3.0)
|
||||||
rubocop (~> 1.61)
|
rubocop (~> 1.61)
|
||||||
rubocop-rspec_rails (2.30.0)
|
rubocop-rspec_rails (2.30.0)
|
||||||
rubocop (~> 1.61)
|
rubocop (~> 1.61)
|
||||||
@ -760,10 +743,9 @@ GEM
|
|||||||
ruby-vips (2.2.2)
|
ruby-vips (2.2.2)
|
||||||
ffi (~> 1.12)
|
ffi (~> 1.12)
|
||||||
logger
|
logger
|
||||||
ruby2_keywords (0.0.5)
|
|
||||||
rubyzip (2.3.2)
|
rubyzip (2.3.2)
|
||||||
rufus-scheduler (3.9.1)
|
rufus-scheduler (3.9.2)
|
||||||
fugit (~> 1.1, >= 1.1.6)
|
fugit (~> 1.1, >= 1.11.1)
|
||||||
safety_net_attestation (0.4.0)
|
safety_net_attestation (0.4.0)
|
||||||
jwt (~> 2.0)
|
jwt (~> 2.0)
|
||||||
sanitize (6.1.3)
|
sanitize (6.1.3)
|
||||||
@ -772,13 +754,14 @@ GEM
|
|||||||
scenic (1.8.0)
|
scenic (1.8.0)
|
||||||
activerecord (>= 4.0.0)
|
activerecord (>= 4.0.0)
|
||||||
railties (>= 4.0.0)
|
railties (>= 4.0.0)
|
||||||
selenium-webdriver (4.26.0)
|
securerandom (0.4.1)
|
||||||
|
selenium-webdriver (4.27.0)
|
||||||
base64 (~> 0.2)
|
base64 (~> 0.2)
|
||||||
logger (~> 1.4)
|
logger (~> 1.4)
|
||||||
rexml (~> 3.2, >= 3.2.5)
|
rexml (~> 3.2, >= 3.2.5)
|
||||||
rubyzip (>= 1.2.2, < 3.0)
|
rubyzip (>= 1.2.2, < 3.0)
|
||||||
websocket (~> 1.0)
|
websocket (~> 1.0)
|
||||||
semantic_range (3.0.0)
|
semantic_range (3.1.0)
|
||||||
shoulda-matchers (6.4.0)
|
shoulda-matchers (6.4.0)
|
||||||
activesupport (>= 5.2.0)
|
activesupport (>= 5.2.0)
|
||||||
sidekiq (6.5.12)
|
sidekiq (6.5.12)
|
||||||
@ -812,8 +795,8 @@ GEM
|
|||||||
stackprof (0.2.26)
|
stackprof (0.2.26)
|
||||||
stoplight (4.1.0)
|
stoplight (4.1.0)
|
||||||
redlock (~> 1.0)
|
redlock (~> 1.0)
|
||||||
stringio (3.1.1)
|
stringio (3.1.2)
|
||||||
strong_migrations (2.0.2)
|
strong_migrations (2.1.0)
|
||||||
activerecord (>= 6.1)
|
activerecord (>= 6.1)
|
||||||
swd (1.3.0)
|
swd (1.3.0)
|
||||||
activesupport (>= 3)
|
activesupport (>= 3)
|
||||||
@ -825,10 +808,10 @@ GEM
|
|||||||
unicode-display_width (>= 1.1.1, < 3)
|
unicode-display_width (>= 1.1.1, < 3)
|
||||||
terrapin (1.0.1)
|
terrapin (1.0.1)
|
||||||
climate_control
|
climate_control
|
||||||
test-prof (1.4.2)
|
test-prof (1.4.3)
|
||||||
thor (1.3.2)
|
thor (1.3.2)
|
||||||
tilt (2.4.0)
|
tilt (2.4.0)
|
||||||
timeout (0.4.1)
|
timeout (0.4.3)
|
||||||
tpm-key_attestation (0.12.1)
|
tpm-key_attestation (0.12.1)
|
||||||
bindata (~> 2.4)
|
bindata (~> 2.4)
|
||||||
openssl (> 2.0)
|
openssl (> 2.0)
|
||||||
@ -854,7 +837,8 @@ GEM
|
|||||||
unf_ext
|
unf_ext
|
||||||
unf_ext (0.0.9.1)
|
unf_ext (0.0.9.1)
|
||||||
unicode-display_width (2.6.0)
|
unicode-display_width (2.6.0)
|
||||||
uri (0.13.1)
|
uri (1.0.2)
|
||||||
|
useragent (0.16.11)
|
||||||
validate_email (0.1.6)
|
validate_email (0.1.6)
|
||||||
activemodel (>= 3.0)
|
activemodel (>= 3.0)
|
||||||
mail (>= 2.2.5)
|
mail (>= 2.2.5)
|
||||||
@ -863,9 +847,8 @@ GEM
|
|||||||
public_suffix
|
public_suffix
|
||||||
warden (1.2.9)
|
warden (1.2.9)
|
||||||
rack (>= 2.0.9)
|
rack (>= 2.0.9)
|
||||||
webauthn (3.1.0)
|
webauthn (3.2.2)
|
||||||
android_key_attestation (~> 0.3.0)
|
android_key_attestation (~> 0.3.0)
|
||||||
awrence (~> 1.1)
|
|
||||||
bindata (~> 2.4)
|
bindata (~> 2.4)
|
||||||
cbor (~> 0.5.9)
|
cbor (~> 0.5.9)
|
||||||
cose (~> 1.1)
|
cose (~> 1.1)
|
||||||
@ -884,7 +867,7 @@ GEM
|
|||||||
rack-proxy (>= 0.6.1)
|
rack-proxy (>= 0.6.1)
|
||||||
railties (>= 5.2)
|
railties (>= 5.2)
|
||||||
semantic_range (>= 2.3.0)
|
semantic_range (>= 2.3.0)
|
||||||
webrick (1.8.2)
|
webrick (1.9.1)
|
||||||
websocket (1.2.11)
|
websocket (1.2.11)
|
||||||
websocket-driver (0.7.6)
|
websocket-driver (0.7.6)
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
@ -901,14 +884,14 @@ PLATFORMS
|
|||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
active_model_serializers (~> 0.10)
|
active_model_serializers (~> 0.10)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
annotate (~> 3.2)
|
annotaterb (~> 4.13)
|
||||||
aws-sdk-s3 (~> 1.123)
|
aws-sdk-s3 (~> 1.123)
|
||||||
better_errors (~> 2.9)
|
better_errors (~> 2.9)
|
||||||
binding_of_caller (~> 1.0)
|
binding_of_caller (~> 1.0)
|
||||||
blurhash (~> 0.1)
|
blurhash (~> 0.1)
|
||||||
bootsnap (~> 1.18.0)
|
bootsnap (~> 1.18.0)
|
||||||
brakeman (~> 6.0)
|
brakeman (~> 6.0)
|
||||||
browser (< 6)
|
browser
|
||||||
bundler-audit (~> 0.9)
|
bundler-audit (~> 0.9)
|
||||||
capybara (~> 3.39)
|
capybara (~> 3.39)
|
||||||
charlock_holmes (~> 0.7.7)
|
charlock_holmes (~> 0.7.7)
|
||||||
@ -930,6 +913,7 @@ DEPENDENCIES
|
|||||||
email_spec
|
email_spec
|
||||||
fabrication (~> 2.30)
|
fabrication (~> 2.30)
|
||||||
faker (~> 3.2)
|
faker (~> 3.2)
|
||||||
|
faraday-httpclient
|
||||||
fast_blank (~> 1.0)
|
fast_blank (~> 1.0)
|
||||||
fastimage
|
fastimage
|
||||||
flatware-rspec
|
flatware-rspec
|
||||||
@ -962,7 +946,8 @@ DEPENDENCIES
|
|||||||
mario-redis-lock (~> 1.2)
|
mario-redis-lock (~> 1.2)
|
||||||
memory_profiler
|
memory_profiler
|
||||||
mime-types (~> 3.6.0)
|
mime-types (~> 3.6.0)
|
||||||
net-http (~> 0.4.0)
|
mutex_m
|
||||||
|
net-http (~> 0.5.0)
|
||||||
net-ldap (~> 0.18)
|
net-ldap (~> 0.18)
|
||||||
nokogiri (~> 1.15)
|
nokogiri (~> 1.15)
|
||||||
oj (~> 3.14)
|
oj (~> 3.14)
|
||||||
@ -974,7 +959,7 @@ DEPENDENCIES
|
|||||||
opentelemetry-api (~> 1.4.0)
|
opentelemetry-api (~> 1.4.0)
|
||||||
opentelemetry-exporter-otlp (~> 0.29.0)
|
opentelemetry-exporter-otlp (~> 0.29.0)
|
||||||
opentelemetry-instrumentation-active_job (~> 0.7.1)
|
opentelemetry-instrumentation-active_job (~> 0.7.1)
|
||||||
opentelemetry-instrumentation-active_model_serializers (~> 0.20.1)
|
opentelemetry-instrumentation-active_model_serializers (~> 0.21.0)
|
||||||
opentelemetry-instrumentation-concurrent_ruby (~> 0.21.2)
|
opentelemetry-instrumentation-concurrent_ruby (~> 0.21.2)
|
||||||
opentelemetry-instrumentation-excon (~> 0.22.0)
|
opentelemetry-instrumentation-excon (~> 0.22.0)
|
||||||
opentelemetry-instrumentation-faraday (~> 0.24.1)
|
opentelemetry-instrumentation-faraday (~> 0.24.1)
|
||||||
@ -983,7 +968,7 @@ DEPENDENCIES
|
|||||||
opentelemetry-instrumentation-net_http (~> 0.22.4)
|
opentelemetry-instrumentation-net_http (~> 0.22.4)
|
||||||
opentelemetry-instrumentation-pg (~> 0.29.0)
|
opentelemetry-instrumentation-pg (~> 0.29.0)
|
||||||
opentelemetry-instrumentation-rack (~> 0.25.0)
|
opentelemetry-instrumentation-rack (~> 0.25.0)
|
||||||
opentelemetry-instrumentation-rails (~> 0.32.0)
|
opentelemetry-instrumentation-rails (~> 0.34.0)
|
||||||
opentelemetry-instrumentation-redis (~> 0.25.3)
|
opentelemetry-instrumentation-redis (~> 0.25.3)
|
||||||
opentelemetry-instrumentation-sidekiq (~> 0.25.2)
|
opentelemetry-instrumentation-sidekiq (~> 0.25.2)
|
||||||
opentelemetry-sdk (~> 1.4)
|
opentelemetry-sdk (~> 1.4)
|
||||||
@ -1000,7 +985,7 @@ DEPENDENCIES
|
|||||||
rack-attack (~> 6.6)
|
rack-attack (~> 6.6)
|
||||||
rack-cors (~> 2.0)
|
rack-cors (~> 2.0)
|
||||||
rack-test (~> 2.1)
|
rack-test (~> 2.1)
|
||||||
rails (~> 7.1.1)
|
rails (~> 7.2.0)
|
||||||
rails-controller-testing (~> 1.0)
|
rails-controller-testing (~> 1.0)
|
||||||
rails-i18n (~> 7.0)
|
rails-i18n (~> 7.0)
|
||||||
rdf-normalize (~> 0.5)
|
rdf-normalize (~> 0.5)
|
||||||
@ -1048,7 +1033,7 @@ DEPENDENCIES
|
|||||||
xorcist (~> 1.1)
|
xorcist (~> 1.1)
|
||||||
|
|
||||||
RUBY VERSION
|
RUBY VERSION
|
||||||
ruby 3.3.5p100
|
ruby 3.3.6p108
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.5.22
|
2.6.1
|
||||||
|
2
Rakefile
2
Rakefile
@ -3,6 +3,6 @@
|
|||||||
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
||||||
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
||||||
|
|
||||||
require File.expand_path('config/application', __dir__)
|
require_relative 'config/application'
|
||||||
|
|
||||||
Rails.application.load_tasks
|
Rails.application.load_tasks
|
||||||
|
2
Vagrantfile
vendored
2
Vagrantfile
vendored
@ -174,7 +174,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
|||||||
if config.vm.networks.any? { |type, options| type == :private_network }
|
if config.vm.networks.any? { |type, options| type == :private_network }
|
||||||
config.vm.synced_folder ".", "/vagrant", type: "nfs", mount_options: ['rw', 'actimeo=1']
|
config.vm.synced_folder ".", "/vagrant", type: "nfs", mount_options: ['rw', 'actimeo=1']
|
||||||
else
|
else
|
||||||
config.vm.synced_folder ".", "/vagrant"
|
config.vm.synced_folder ".", "/vagrant", type: "rsync", create: true, rsync__args: ["--verbose", "--archive", "--delete", "-z"]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Otherwise, you can access the site at http://localhost:3000 and http://localhost:4000 , http://localhost:8080
|
# Otherwise, you can access the site at http://localhost:3000 and http://localhost:4000 , http://localhost:8080
|
||||||
|
@ -8,6 +8,7 @@ module Admin
|
|||||||
layout 'admin'
|
layout 'admin'
|
||||||
|
|
||||||
before_action :set_cache_headers
|
before_action :set_cache_headers
|
||||||
|
before_action :set_referrer_policy_header
|
||||||
|
|
||||||
after_action :verify_authorized
|
after_action :verify_authorized
|
||||||
|
|
||||||
@ -17,6 +18,10 @@ module Admin
|
|||||||
response.cache_control.replace(private: true, no_store: true)
|
response.cache_control.replace(private: true, no_store: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_referrer_policy_header
|
||||||
|
response.headers['Referrer-Policy'] = 'same-origin'
|
||||||
|
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
|
||||||
|
@ -5,7 +5,7 @@ module Admin
|
|||||||
def index
|
def index
|
||||||
authorize :email_domain_block, :index?
|
authorize :email_domain_block, :index?
|
||||||
|
|
||||||
@email_domain_blocks = EmailDomainBlock.where(parent_id: nil).includes(:children).order(id: :desc).page(params[:page])
|
@email_domain_blocks = EmailDomainBlock.parents.includes(:children).order(id: :desc).page(params[:page])
|
||||||
@form = Form::EmailDomainBlockBatch.new
|
@form = Form::EmailDomainBlockBatch.new
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -58,10 +58,7 @@ module Admin
|
|||||||
private
|
private
|
||||||
|
|
||||||
def set_resolved_records
|
def set_resolved_records
|
||||||
Resolv::DNS.open do |dns|
|
@resolved_records = DomainResource.new(@email_domain_block.domain).mx
|
||||||
dns.timeouts = 5
|
|
||||||
@resolved_records = dns.getresources(@email_domain_block.domain, Resolv::DNS::Resource::IN::MX).to_a
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def resource_params
|
def resource_params
|
||||||
|
@ -5,6 +5,8 @@ module Admin
|
|||||||
before_action :set_instances, only: :index
|
before_action :set_instances, only: :index
|
||||||
before_action :set_instance, except: :index
|
before_action :set_instance, except: :index
|
||||||
|
|
||||||
|
LOGS_LIMIT = 5
|
||||||
|
|
||||||
def index
|
def index
|
||||||
authorize :instance, :index?
|
authorize :instance, :index?
|
||||||
preload_delivery_failures!
|
preload_delivery_failures!
|
||||||
@ -13,7 +15,7 @@ module Admin
|
|||||||
def show
|
def show
|
||||||
authorize :instance, :show?
|
authorize :instance, :show?
|
||||||
@time_period = (6.days.ago.to_date...Time.now.utc.to_date)
|
@time_period = (6.days.ago.to_date...Time.now.utc.to_date)
|
||||||
@action_logs = Admin::ActionLogFilter.new(target_domain: @instance.domain).results.limit(5)
|
@action_logs = Admin::ActionLogFilter.new(target_domain: @instance.domain).results.limit(LOGS_LIMIT)
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
|
@ -32,7 +32,7 @@ module Admin
|
|||||||
|
|
||||||
def deactivate_all
|
def deactivate_all
|
||||||
authorize :invite, :deactivate_all?
|
authorize :invite, :deactivate_all?
|
||||||
Invite.available.in_batches.update_all(expires_at: Time.now.utc)
|
Invite.available.in_batches.touch_all(:expires_at)
|
||||||
redirect_to admin_invites_path
|
redirect_to admin_invites_path
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ module Admin
|
|||||||
@relay = Relay.new(resource_params)
|
@relay = Relay.new(resource_params)
|
||||||
|
|
||||||
if @relay.save
|
if @relay.save
|
||||||
|
log_action :create, @relay
|
||||||
@relay.enable!
|
@relay.enable!
|
||||||
redirect_to admin_relays_path
|
redirect_to admin_relays_path
|
||||||
else
|
else
|
||||||
@ -31,18 +32,21 @@ module Admin
|
|||||||
def destroy
|
def destroy
|
||||||
authorize :relay, :update?
|
authorize :relay, :update?
|
||||||
@relay.destroy
|
@relay.destroy
|
||||||
|
log_action :destroy, @relay
|
||||||
redirect_to admin_relays_path
|
redirect_to admin_relays_path
|
||||||
end
|
end
|
||||||
|
|
||||||
def enable
|
def enable
|
||||||
authorize :relay, :update?
|
authorize :relay, :update?
|
||||||
@relay.enable!
|
@relay.enable!
|
||||||
|
log_action :enable, @relay
|
||||||
redirect_to admin_relays_path
|
redirect_to admin_relays_path
|
||||||
end
|
end
|
||||||
|
|
||||||
def disable
|
def disable
|
||||||
authorize :relay, :update?
|
authorize :relay, :update?
|
||||||
@relay.disable!
|
@relay.disable!
|
||||||
|
log_action :disable, @relay
|
||||||
redirect_to admin_relays_path
|
redirect_to admin_relays_path
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -16,6 +16,8 @@ module Admin
|
|||||||
|
|
||||||
def show
|
def show
|
||||||
authorize [:admin, @status], :show?
|
authorize [:admin, @status], :show?
|
||||||
|
|
||||||
|
@status_batch_action = Admin::StatusBatchAction.new
|
||||||
end
|
end
|
||||||
|
|
||||||
def batch
|
def batch
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Admin::TermsOfService::DistributionsController < Admin::BaseController
|
||||||
|
before_action :set_terms_of_service
|
||||||
|
|
||||||
|
def create
|
||||||
|
authorize @terms_of_service, :distribute?
|
||||||
|
@terms_of_service.touch(:notification_sent_at)
|
||||||
|
Admin::DistributeTermsOfServiceNotificationWorker.perform_async(@terms_of_service.id)
|
||||||
|
redirect_to admin_terms_of_service_index_path
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_terms_of_service
|
||||||
|
@terms_of_service = TermsOfService.find(params[:terms_of_service_id])
|
||||||
|
end
|
||||||
|
end
|
36
app/controllers/admin/terms_of_service/drafts_controller.rb
Normal file
36
app/controllers/admin/terms_of_service/drafts_controller.rb
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Admin::TermsOfService::DraftsController < Admin::BaseController
|
||||||
|
before_action :set_terms_of_service
|
||||||
|
|
||||||
|
def show
|
||||||
|
authorize :terms_of_service, :create?
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
authorize @terms_of_service, :update?
|
||||||
|
|
||||||
|
@terms_of_service.published_at = Time.now.utc if params[:action_type] == 'publish'
|
||||||
|
|
||||||
|
if @terms_of_service.update(resource_params)
|
||||||
|
log_action(:publish, @terms_of_service) if @terms_of_service.published?
|
||||||
|
redirect_to @terms_of_service.published? ? admin_terms_of_service_index_path : admin_terms_of_service_draft_path
|
||||||
|
else
|
||||||
|
render :show
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_terms_of_service
|
||||||
|
@terms_of_service = TermsOfService.draft.first || TermsOfService.new(text: current_terms_of_service&.text)
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_terms_of_service
|
||||||
|
TermsOfService.live.first
|
||||||
|
end
|
||||||
|
|
||||||
|
def resource_params
|
||||||
|
params.require(:terms_of_service).permit(:text, :changelog)
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,37 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Admin::TermsOfService::GeneratesController < Admin::BaseController
|
||||||
|
before_action :set_instance_presenter
|
||||||
|
|
||||||
|
def show
|
||||||
|
authorize :terms_of_service, :create?
|
||||||
|
|
||||||
|
@generator = TermsOfService::Generator.new(
|
||||||
|
domain: @instance_presenter.domain,
|
||||||
|
admin_email: @instance_presenter.contact.email
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
authorize :terms_of_service, :create?
|
||||||
|
|
||||||
|
@generator = TermsOfService::Generator.new(resource_params)
|
||||||
|
|
||||||
|
if @generator.valid?
|
||||||
|
TermsOfService.create!(text: @generator.render)
|
||||||
|
redirect_to admin_terms_of_service_draft_path
|
||||||
|
else
|
||||||
|
render :show
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_instance_presenter
|
||||||
|
@instance_presenter = InstancePresenter.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def resource_params
|
||||||
|
params.require(:terms_of_service_generator).permit(*TermsOfService::Generator::VARIABLES)
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,8 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Admin::TermsOfService::HistoriesController < Admin::BaseController
|
||||||
|
def show
|
||||||
|
authorize :terms_of_service, :index?
|
||||||
|
@terms_of_service = TermsOfService.published.all
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,16 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Admin::TermsOfService::PreviewsController < Admin::BaseController
|
||||||
|
before_action :set_terms_of_service
|
||||||
|
|
||||||
|
def show
|
||||||
|
authorize @terms_of_service, :distribute?
|
||||||
|
@user_count = @terms_of_service.scope_for_notification.count
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_terms_of_service
|
||||||
|
@terms_of_service = TermsOfService.find(params[:terms_of_service_id])
|
||||||
|
end
|
||||||
|
end
|
17
app/controllers/admin/terms_of_service/tests_controller.rb
Normal file
17
app/controllers/admin/terms_of_service/tests_controller.rb
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Admin::TermsOfService::TestsController < Admin::BaseController
|
||||||
|
before_action :set_terms_of_service
|
||||||
|
|
||||||
|
def create
|
||||||
|
authorize @terms_of_service, :distribute?
|
||||||
|
UserMailer.terms_of_service_changed(current_user, @terms_of_service).deliver_later!
|
||||||
|
redirect_to admin_terms_of_service_preview_path(@terms_of_service)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_terms_of_service
|
||||||
|
@terms_of_service = TermsOfService.find(params[:terms_of_service_id])
|
||||||
|
end
|
||||||
|
end
|
8
app/controllers/admin/terms_of_service_controller.rb
Normal file
8
app/controllers/admin/terms_of_service_controller.rb
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Admin::TermsOfServiceController < Admin::BaseController
|
||||||
|
def index
|
||||||
|
authorize :terms_of_service, :index?
|
||||||
|
@terms_of_service = TermsOfService.live.first
|
||||||
|
end
|
||||||
|
end
|
@ -12,7 +12,7 @@ class Api::V1::Accounts::FamiliarFollowersController < Api::BaseController
|
|||||||
private
|
private
|
||||||
|
|
||||||
def set_accounts
|
def set_accounts
|
||||||
@accounts = Account.without_suspended.where(id: account_ids).select('id, hide_collections')
|
@accounts = Account.without_suspended.where(id: account_ids).select(:id, :hide_collections)
|
||||||
end
|
end
|
||||||
|
|
||||||
def familiar_followers
|
def familiar_followers
|
||||||
|
@ -17,6 +17,17 @@ class Api::V1::AnnualReportsController < Api::BaseController
|
|||||||
relationships: @relationships
|
relationships: @relationships
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
with_read_replica do
|
||||||
|
@presenter = AnnualReportsPresenter.new([@annual_report])
|
||||||
|
@relationships = StatusRelationshipsPresenter.new(@presenter.statuses, current_account.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
render json: @presenter,
|
||||||
|
serializer: REST::AnnualReportsSerializer,
|
||||||
|
relationships: @relationships
|
||||||
|
end
|
||||||
|
|
||||||
def read
|
def read
|
||||||
@annual_report.view!
|
@annual_report.view!
|
||||||
render_empty
|
render_empty
|
||||||
|
@ -5,6 +5,8 @@ class Api::V1::FeaturedTags::SuggestionsController < Api::BaseController
|
|||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
before_action :set_recently_used_tags, only: :index
|
before_action :set_recently_used_tags, only: :index
|
||||||
|
|
||||||
|
RECENT_TAGS_LIMIT = 10
|
||||||
|
|
||||||
def index
|
def index
|
||||||
render json: @recently_used_tags, each_serializer: REST::TagSerializer, relationships: TagRelationshipsPresenter.new(@recently_used_tags, current_user&.account_id)
|
render json: @recently_used_tags, each_serializer: REST::TagSerializer, relationships: TagRelationshipsPresenter.new(@recently_used_tags, current_user&.account_id)
|
||||||
end
|
end
|
||||||
@ -12,6 +14,6 @@ class Api::V1::FeaturedTags::SuggestionsController < Api::BaseController
|
|||||||
private
|
private
|
||||||
|
|
||||||
def set_recently_used_tags
|
def set_recently_used_tags
|
||||||
@recently_used_tags = Tag.suggestions_for_account(current_account).limit(10)
|
@recently_used_tags = Tag.suggestions_for_account(current_account).limit(RECENT_TAGS_LIMIT)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Instances::TermsOfServicesController < Api::V1::Instances::BaseController
|
||||||
|
before_action :set_terms_of_service
|
||||||
|
|
||||||
|
def show
|
||||||
|
cache_even_if_authenticated!
|
||||||
|
render json: @terms_of_service, serializer: REST::PrivacyPolicySerializer
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_terms_of_service
|
||||||
|
@terms_of_service = TermsOfService.live.first!
|
||||||
|
end
|
||||||
|
end
|
@ -15,17 +15,12 @@ class Api::V1::Lists::AccountsController < Api::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
ApplicationRecord.transaction do
|
AddAccountsToListService.new.call(@list, Account.find(account_ids))
|
||||||
list_accounts.each do |account|
|
|
||||||
@list.accounts << account
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
render_empty
|
render_empty
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
ListAccount.where(list: @list, account_id: account_ids).destroy_all
|
RemoveAccountsFromListService.new.call(@list, Account.where(id: account_ids))
|
||||||
render_empty
|
render_empty
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -43,10 +38,6 @@ class Api::V1::Lists::AccountsController < Api::BaseController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def list_accounts
|
|
||||||
Account.find(account_ids)
|
|
||||||
end
|
|
||||||
|
|
||||||
def account_ids
|
def account_ids
|
||||||
Array(resource_params[:account_ids])
|
Array(resource_params[:account_ids])
|
||||||
end
|
end
|
||||||
|
@ -15,7 +15,7 @@ class Api::V1::Polls::VotesController < Api::BaseController
|
|||||||
private
|
private
|
||||||
|
|
||||||
def set_poll
|
def set_poll
|
||||||
@poll = Poll.attached.find(params[:poll_id])
|
@poll = Poll.find(params[:poll_id])
|
||||||
authorize @poll.status, :show?
|
authorize @poll.status, :show?
|
||||||
rescue Mastodon::NotPermittedError
|
rescue Mastodon::NotPermittedError
|
||||||
not_found
|
not_found
|
||||||
|
@ -15,7 +15,7 @@ class Api::V1::PollsController < Api::BaseController
|
|||||||
private
|
private
|
||||||
|
|
||||||
def set_poll
|
def set_poll
|
||||||
@poll = Poll.attached.find(params[:id])
|
@poll = Poll.find(params[:id])
|
||||||
authorize @poll.status, :show?
|
authorize @poll.status, :show?
|
||||||
rescue Mastodon::NotPermittedError
|
rescue Mastodon::NotPermittedError
|
||||||
not_found
|
not_found
|
||||||
|
@ -27,7 +27,9 @@ class Api::V1::Trends::TagsController < Api::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def tags_from_trends
|
def tags_from_trends
|
||||||
Trends.tags.query.allowed
|
scope = Trends.tags.query.allowed.in_locale(content_locale)
|
||||||
|
scope = scope.filtered_for(current_account) if user_signed_in?
|
||||||
|
scope
|
||||||
end
|
end
|
||||||
|
|
||||||
def next_path
|
def next_path
|
||||||
|
@ -22,7 +22,6 @@ class ApplicationController < ActionController::Base
|
|||||||
helper_method :use_seamless_external_login?
|
helper_method :use_seamless_external_login?
|
||||||
helper_method :sso_account_settings
|
helper_method :sso_account_settings
|
||||||
helper_method :limited_federation_mode?
|
helper_method :limited_federation_mode?
|
||||||
helper_method :body_class_string
|
|
||||||
helper_method :skip_csrf_meta_tags?
|
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
|
||||||
@ -71,7 +70,13 @@ class ApplicationController < ActionController::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def require_functional!
|
def require_functional!
|
||||||
redirect_to edit_user_registration_path unless current_user.functional?
|
return if current_user.functional?
|
||||||
|
|
||||||
|
if current_user.confirmed?
|
||||||
|
redirect_to edit_user_registration_path
|
||||||
|
else
|
||||||
|
redirect_to auth_setup_path
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def skip_csrf_meta_tags?
|
def skip_csrf_meta_tags?
|
||||||
@ -158,10 +163,6 @@ class ApplicationController < ActionController::Base
|
|||||||
current_user.setting_theme
|
current_user.setting_theme
|
||||||
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 { render "errors/#{code}", layout: 'error', status: code, formats: [:html] }
|
format.any { render "errors/#{code}", layout: 'error', status: code, formats: [:html] }
|
||||||
|
@ -142,4 +142,12 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||||||
def set_cache_headers
|
def set_cache_headers
|
||||||
response.cache_control.replace(private: true, no_store: true)
|
response.cache_control.replace(private: true, no_store: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def is_flashing_format? # rubocop:disable Naming/PredicateName
|
||||||
|
if params[:action] == 'create'
|
||||||
|
false # Disable flash messages for sign-up
|
||||||
|
else
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -28,7 +28,7 @@ module CacheConcern
|
|||||||
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]&.join(',')].compact.join(':')
|
||||||
expires_in = options.delete(:expires_in) || 3.minutes
|
expires_in = options.delete(:expires_in) || 3.minutes
|
||||||
body = Rails.cache.read(key, raw: true)
|
body = Rails.cache.read(key, raw: true)
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ module WebAppControllerConcern
|
|||||||
vary_by 'Accept, Accept-Language, Cookie'
|
vary_by 'Accept, Accept-Language, Cookie'
|
||||||
|
|
||||||
before_action :redirect_unauthenticated_to_permalinks!
|
before_action :redirect_unauthenticated_to_permalinks!
|
||||||
before_action :set_app_body_class
|
before_action :set_referer_header
|
||||||
|
|
||||||
content_security_policy do |p|
|
content_security_policy do |p|
|
||||||
policy = ContentSecurityPolicy.new
|
policy = ContentSecurityPolicy.new
|
||||||
@ -24,10 +24,6 @@ module WebAppControllerConcern
|
|||||||
!(ENV['ONE_CLICK_SSO_LOGIN'] == 'true' && ENV['OMNIAUTH_ONLY'] == 'true' && Devise.omniauth_providers.length == 1) && current_user.nil?
|
!(ENV['ONE_CLICK_SSO_LOGIN'] == 'true' && ENV['OMNIAUTH_ONLY'] == 'true' && Devise.omniauth_providers.length == 1) && current_user.nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_app_body_class
|
|
||||||
@body_classes = 'app-body'
|
|
||||||
end
|
|
||||||
|
|
||||||
def redirect_unauthenticated_to_permalinks!
|
def redirect_unauthenticated_to_permalinks!
|
||||||
return if user_signed_in? && current_account.moved_to_account_id.nil?
|
return if user_signed_in? && current_account.moved_to_account_id.nil?
|
||||||
|
|
||||||
@ -46,4 +42,10 @@ module WebAppControllerConcern
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def set_referer_header
|
||||||
|
response.set_header('Referrer-Policy', Setting.allow_referrer_origin ? 'origin' : 'same-origin')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -35,12 +35,6 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
|
|||||||
end
|
end
|
||||||
|
|
||||||
def set_last_used_at_by_app
|
def set_last_used_at_by_app
|
||||||
@last_used_at_by_app = Doorkeeper::AccessToken
|
@last_used_at_by_app = current_resource_owner.applications_last_used
|
||||||
.select('DISTINCT ON (application_id) application_id, last_used_at')
|
|
||||||
.where(resource_owner_id: current_resource_owner.id)
|
|
||||||
.where.not(last_used_at: nil)
|
|
||||||
.order(application_id: :desc, last_used_at: :desc)
|
|
||||||
.pluck(:application_id, :last_used_at)
|
|
||||||
.to_h
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -5,6 +5,8 @@ class Settings::FeaturedTagsController < Settings::BaseController
|
|||||||
before_action :set_featured_tag, except: [:index, :create]
|
before_action :set_featured_tag, except: [:index, :create]
|
||||||
before_action :set_recently_used_tags, only: :index
|
before_action :set_recently_used_tags, only: :index
|
||||||
|
|
||||||
|
RECENT_TAGS_LIMIT = 10
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@featured_tag = FeaturedTag.new
|
@featured_tag = FeaturedTag.new
|
||||||
end
|
end
|
||||||
@ -38,7 +40,7 @@ class Settings::FeaturedTagsController < Settings::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def set_recently_used_tags
|
def set_recently_used_tags
|
||||||
@recently_used_tags = Tag.suggestions_for_account(current_account).limit(10)
|
@recently_used_tags = Tag.suggestions_for_account(current_account).limit(RECENT_TAGS_LIMIT)
|
||||||
end
|
end
|
||||||
|
|
||||||
def featured_tag_params
|
def featured_tag_params
|
||||||
|
@ -24,6 +24,8 @@ class Settings::ImportsController < Settings::BaseController
|
|||||||
lists: false,
|
lists: false,
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
|
RECENT_IMPORTS_LIMIT = 10
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@import = Form::Import.new(current_account: current_account)
|
@import = Form::Import.new(current_account: current_account)
|
||||||
end
|
end
|
||||||
@ -96,6 +98,6 @@ class Settings::ImportsController < Settings::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def set_recent_imports
|
def set_recent_imports
|
||||||
@recent_imports = current_account.bulk_imports.reorder(id: :desc).limit(10)
|
@recent_imports = current_account.bulk_imports.reorder(id: :desc).limit(RECENT_IMPORTS_LIMIT)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
11
app/controllers/terms_of_service_controller.rb
Normal file
11
app/controllers/terms_of_service_controller.rb
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class TermsOfServiceController < ApplicationController
|
||||||
|
include WebAppControllerConcern
|
||||||
|
|
||||||
|
skip_before_action :require_functional!
|
||||||
|
|
||||||
|
def show
|
||||||
|
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless user_signed_in?
|
||||||
|
end
|
||||||
|
end
|
@ -12,12 +12,12 @@ module Admin::AccountModerationNotesHelper
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def admin_account_inline_link_to(account)
|
def admin_account_inline_link_to(account, path: nil)
|
||||||
return if account.nil?
|
return if account.nil?
|
||||||
|
|
||||||
link_to(
|
link_to(
|
||||||
account_inline_text(account),
|
account_inline_text(account),
|
||||||
admin_account_path(account.id),
|
path || admin_account_path(account.id),
|
||||||
class: class_names('inline-name-tag', suspended: suspended_account?(account)),
|
class: class_names('inline-name-tag', suspended: suspended_account?(account)),
|
||||||
title: account.acct
|
title: account.acct
|
||||||
)
|
)
|
||||||
|
@ -33,6 +33,8 @@ module Admin::ActionLogsHelper
|
|||||||
else
|
else
|
||||||
I18n.t('admin.action_logs.deleted_account')
|
I18n.t('admin.action_logs.deleted_account')
|
||||||
end
|
end
|
||||||
|
when 'Relay'
|
||||||
|
link_to log.human_identifier, admin_relays_path
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ module ApplicationHelper
|
|||||||
|
|
||||||
def html_title
|
def html_title
|
||||||
safe_join(
|
safe_join(
|
||||||
[content_for(:page_title).to_s.chomp, title]
|
[content_for(:page_title), title]
|
||||||
.compact_blank,
|
.compact_blank,
|
||||||
' - '
|
' - '
|
||||||
)
|
)
|
||||||
@ -143,10 +143,11 @@ module ApplicationHelper
|
|||||||
end
|
end
|
||||||
|
|
||||||
def body_classes
|
def body_classes
|
||||||
output = body_class_string.split
|
output = []
|
||||||
output << content_for(:body_classes)
|
output << content_for(:body_classes)
|
||||||
output << "theme-#{current_theme.parameterize}"
|
output << "theme-#{current_theme.parameterize}"
|
||||||
output << 'system-font' if current_account&.user&.setting_system_font_ui
|
output << 'system-font' if current_account&.user&.setting_system_font_ui
|
||||||
|
output << 'custom-scrollbars' unless current_account&.user&.setting_system_scrollbars_ui
|
||||||
output << (current_account&.user&.setting_reduce_motion ? 'reduce-motion' : 'no-reduce-motion')
|
output << (current_account&.user&.setting_reduce_motion ? 'reduce-motion' : 'no-reduce-motion')
|
||||||
output << 'rtl' if locale_direction == 'rtl'
|
output << 'rtl' if locale_direction == 'rtl'
|
||||||
output.compact_blank.join(' ')
|
output.compact_blank.join(' ')
|
||||||
|
@ -64,6 +64,10 @@ module FormattingHelper
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def markdown(text)
|
||||||
|
Redcarpet::Markdown.new(Redcarpet::Render::HTML, escape_html: true, no_images: true).render(text).html_safe # rubocop:disable Rails/OutputSafety
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def wrapped_status_content_format(status)
|
def wrapped_status_content_format(status)
|
||||||
|
@ -16,6 +16,6 @@ module RegistrationHelper
|
|||||||
end
|
end
|
||||||
|
|
||||||
def ip_blocked?(remote_ip)
|
def ip_blocked?(remote_ip)
|
||||||
IpBlock.where(severity: :sign_up_block).exists?(['ip >>= ?', remote_ip.to_s])
|
IpBlock.severity_sign_up_block.containing(remote_ip.to_s).exists?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module SelfDestructHelper
|
module SelfDestructHelper
|
||||||
|
VERIFY_PURPOSE = 'self-destruct'
|
||||||
|
|
||||||
def self.self_destruct?
|
def self.self_destruct?
|
||||||
value = ENV.fetch('SELF_DESTRUCT', nil)
|
value = Rails.configuration.x.mastodon.self_destruct_value
|
||||||
value.present? && Rails.application.message_verifier('self-destruct').verify(value) == ENV['LOCAL_DOMAIN']
|
value.present? && Rails.application.message_verifier(VERIFY_PURPOSE).verify(value) == ENV['LOCAL_DOMAIN']
|
||||||
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module StatusesHelper
|
module StatusesHelper
|
||||||
EMBEDDED_CONTROLLER = 'statuses'
|
|
||||||
EMBEDDED_ACTION = 'embed'
|
|
||||||
|
|
||||||
VISIBLITY_ICONS = {
|
VISIBLITY_ICONS = {
|
||||||
public: 'globe',
|
public: 'globe',
|
||||||
unlisted: 'lock_open',
|
unlisted: 'lock_open',
|
||||||
@ -60,18 +57,10 @@ module StatusesHelper
|
|||||||
components.compact_blank.join("\n\n")
|
components.compact_blank.join("\n\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
def stream_link_target
|
|
||||||
embedded_view? ? '_blank' : nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def visibility_icon(status)
|
def visibility_icon(status)
|
||||||
VISIBLITY_ICONS[status.visibility.to_sym]
|
VISIBLITY_ICONS[status.visibility.to_sym]
|
||||||
end
|
end
|
||||||
|
|
||||||
def embedded_view?
|
|
||||||
params[:controller] == EMBEDDED_CONTROLLER && params[:action] == EMBEDDED_ACTION
|
|
||||||
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
|
||||||
|
@ -60,6 +60,10 @@ window.addEventListener('message', (e) => {
|
|||||||
|
|
||||||
const data = e.data;
|
const data = e.data;
|
||||||
|
|
||||||
|
// Only set overflow to `hidden` once we got the expected `message` so the post can still be scrolled if
|
||||||
|
// embedded without parent Javascript support
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
|
||||||
// We use a timeout to allow for the React page to render before calculating the height
|
// We use a timeout to allow for the React page to render before calculating the height
|
||||||
afterInitialRender(() => {
|
afterInitialRender(() => {
|
||||||
window.parent.postMessage(
|
window.parent.postMessage(
|
||||||
|
@ -230,62 +230,6 @@ function loaded() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
Rails.delegate(
|
|
||||||
document,
|
|
||||||
'button.status__content__spoiler-link',
|
|
||||||
'click',
|
|
||||||
function () {
|
|
||||||
if (!(this instanceof HTMLButtonElement)) return;
|
|
||||||
|
|
||||||
const statusEl = this.parentNode?.parentNode;
|
|
||||||
|
|
||||||
if (
|
|
||||||
!(
|
|
||||||
statusEl instanceof HTMLDivElement &&
|
|
||||||
statusEl.classList.contains('.status__content')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (statusEl.dataset.spoiler === 'expanded') {
|
|
||||||
statusEl.dataset.spoiler = 'folded';
|
|
||||||
this.textContent = new IntlMessageFormat(
|
|
||||||
localeData['status.show_more'] ?? 'Show more',
|
|
||||||
locale,
|
|
||||||
).format() as string;
|
|
||||||
} else {
|
|
||||||
statusEl.dataset.spoiler = 'expanded';
|
|
||||||
this.textContent = new IntlMessageFormat(
|
|
||||||
localeData['status.show_less'] ?? 'Show less',
|
|
||||||
locale,
|
|
||||||
).format() as string;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
document
|
|
||||||
.querySelectorAll<HTMLButtonElement>('button.status__content__spoiler-link')
|
|
||||||
.forEach((spoilerLink) => {
|
|
||||||
const statusEl = spoilerLink.parentNode?.parentNode;
|
|
||||||
|
|
||||||
if (
|
|
||||||
!(
|
|
||||||
statusEl instanceof HTMLDivElement &&
|
|
||||||
statusEl.classList.contains('.status__content')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
|
|
||||||
const message =
|
|
||||||
statusEl.dataset.spoiler === 'expanded'
|
|
||||||
? (localeData['status.show_less'] ?? 'Show less')
|
|
||||||
: (localeData['status.show_more'] ?? 'Show more');
|
|
||||||
spoilerLink.textContent = new IntlMessageFormat(
|
|
||||||
message,
|
|
||||||
locale,
|
|
||||||
).format() as string;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rails.delegate(
|
Rails.delegate(
|
||||||
@ -439,6 +383,24 @@ Rails.delegate(document, '#registration_new_user,#new_user', 'submit', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Rails.delegate(document, '.rules-list button', 'click', ({ target }) => {
|
||||||
|
if (!(target instanceof HTMLElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const button = target.closest('button');
|
||||||
|
|
||||||
|
if (!button) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (button.ariaExpanded === 'true') {
|
||||||
|
button.ariaExpanded = 'false';
|
||||||
|
} else {
|
||||||
|
button.ariaExpanded = 'true';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
ready(loaded).catch((error: unknown) => {
|
ready(loaded).catch((error: unknown) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
@ -2,6 +2,8 @@ import { useCallback } from 'react';
|
|||||||
|
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { isFulfilled, isRejected } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
import { openURL } from 'mastodon/actions/search';
|
import { openURL } from 'mastodon/actions/search';
|
||||||
import { useAppDispatch } from 'mastodon/store';
|
import { useAppDispatch } from 'mastodon/store';
|
||||||
|
|
||||||
@ -28,12 +30,22 @@ export const useLinks = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleMentionClick = useCallback(
|
const handleMentionClick = useCallback(
|
||||||
(element: HTMLAnchorElement) => {
|
async (element: HTMLAnchorElement) => {
|
||||||
dispatch(
|
const result = await dispatch(openURL({ url: element.href }));
|
||||||
openURL(element.href, history, () => {
|
|
||||||
window.location.href = element.href;
|
if (isFulfilled(result)) {
|
||||||
}),
|
if (result.payload.accounts[0]) {
|
||||||
|
history.push(`/@${result.payload.accounts[0].acct}`);
|
||||||
|
} else if (result.payload.statuses[0]) {
|
||||||
|
history.push(
|
||||||
|
`/@${result.payload.statuses[0].account.acct}/${result.payload.statuses[0].id}`,
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
window.location.href = element.href;
|
||||||
|
}
|
||||||
|
} else if (isRejected(result)) {
|
||||||
|
window.location.href = element.href;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[dispatch, history],
|
[dispatch, history],
|
||||||
);
|
);
|
||||||
@ -48,7 +60,7 @@ export const useLinks = () => {
|
|||||||
|
|
||||||
if (isMentionClick(target)) {
|
if (isMentionClick(target)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleMentionClick(target);
|
void handleMentionClick(target);
|
||||||
} else if (isHashtagClick(target)) {
|
} else if (isHashtagClick(target)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleHashtagClick(target);
|
handleHashtagClick(target);
|
||||||
|
BIN
app/javascript/images/archetypes/booster.png
Executable file
BIN
app/javascript/images/archetypes/booster.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 620 KiB |
BIN
app/javascript/images/archetypes/lurker.png
Executable file
BIN
app/javascript/images/archetypes/lurker.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 1.0 MiB |
BIN
app/javascript/images/archetypes/oracle.png
Executable file
BIN
app/javascript/images/archetypes/oracle.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 1.2 MiB |
BIN
app/javascript/images/archetypes/pollster.png
Executable file
BIN
app/javascript/images/archetypes/pollster.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 710 KiB |
BIN
app/javascript/images/archetypes/replier.png
Executable file
BIN
app/javascript/images/archetypes/replier.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 786 KiB |
@ -1,66 +0,0 @@
|
|||||||
import { defineMessages } from 'react-intl';
|
|
||||||
|
|
||||||
import { AxiosError } from 'axios';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
unexpectedTitle: { id: 'alert.unexpected.title', defaultMessage: 'Oops!' },
|
|
||||||
unexpectedMessage: { id: 'alert.unexpected.message', defaultMessage: 'An unexpected error occurred.' },
|
|
||||||
rateLimitedTitle: { id: 'alert.rate_limited.title', defaultMessage: 'Rate limited' },
|
|
||||||
rateLimitedMessage: { id: 'alert.rate_limited.message', defaultMessage: 'Please retry after {retry_time, time, medium}.' },
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ALERT_SHOW = 'ALERT_SHOW';
|
|
||||||
export const ALERT_DISMISS = 'ALERT_DISMISS';
|
|
||||||
export const ALERT_CLEAR = 'ALERT_CLEAR';
|
|
||||||
export const ALERT_NOOP = 'ALERT_NOOP';
|
|
||||||
|
|
||||||
export const dismissAlert = alert => ({
|
|
||||||
type: ALERT_DISMISS,
|
|
||||||
alert,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const clearAlert = () => ({
|
|
||||||
type: ALERT_CLEAR,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const showAlert = alert => ({
|
|
||||||
type: ALERT_SHOW,
|
|
||||||
alert,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const showAlertForError = (error, skipNotFound = false) => {
|
|
||||||
if (error.response) {
|
|
||||||
const { data, status, statusText, headers } = error.response;
|
|
||||||
|
|
||||||
// Skip these errors as they are reflected in the UI
|
|
||||||
if (skipNotFound && (status === 404 || status === 410)) {
|
|
||||||
return { type: ALERT_NOOP };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rate limit errors
|
|
||||||
if (status === 429 && headers['x-ratelimit-reset']) {
|
|
||||||
return showAlert({
|
|
||||||
title: messages.rateLimitedTitle,
|
|
||||||
message: messages.rateLimitedMessage,
|
|
||||||
values: { 'retry_time': new Date(headers['x-ratelimit-reset']) },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return showAlert({
|
|
||||||
title: `${status}`,
|
|
||||||
message: data.error || statusText,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// An aborted request, e.g. due to reloading the browser window, it not really error
|
|
||||||
if (error.code === AxiosError.ECONNABORTED) {
|
|
||||||
return { type: ALERT_NOOP };
|
|
||||||
}
|
|
||||||
|
|
||||||
console.error(error);
|
|
||||||
|
|
||||||
return showAlert({
|
|
||||||
title: messages.unexpectedTitle,
|
|
||||||
message: messages.unexpectedMessage,
|
|
||||||
});
|
|
||||||
};
|
|
90
app/javascript/mastodon/actions/alerts.ts
Normal file
90
app/javascript/mastodon/actions/alerts.ts
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import { defineMessages } from 'react-intl';
|
||||||
|
import type { MessageDescriptor } from 'react-intl';
|
||||||
|
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import type { AxiosResponse } from 'axios';
|
||||||
|
|
||||||
|
interface Alert {
|
||||||
|
title: string | MessageDescriptor;
|
||||||
|
message: string | MessageDescriptor;
|
||||||
|
values?: Record<string, string | number | Date>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ApiErrorResponse {
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
unexpectedTitle: { id: 'alert.unexpected.title', defaultMessage: 'Oops!' },
|
||||||
|
unexpectedMessage: {
|
||||||
|
id: 'alert.unexpected.message',
|
||||||
|
defaultMessage: 'An unexpected error occurred.',
|
||||||
|
},
|
||||||
|
rateLimitedTitle: {
|
||||||
|
id: 'alert.rate_limited.title',
|
||||||
|
defaultMessage: 'Rate limited',
|
||||||
|
},
|
||||||
|
rateLimitedMessage: {
|
||||||
|
id: 'alert.rate_limited.message',
|
||||||
|
defaultMessage: 'Please retry after {retry_time, time, medium}.',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ALERT_SHOW = 'ALERT_SHOW';
|
||||||
|
export const ALERT_DISMISS = 'ALERT_DISMISS';
|
||||||
|
export const ALERT_CLEAR = 'ALERT_CLEAR';
|
||||||
|
export const ALERT_NOOP = 'ALERT_NOOP';
|
||||||
|
|
||||||
|
export const dismissAlert = (alert: Alert) => ({
|
||||||
|
type: ALERT_DISMISS,
|
||||||
|
alert,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const clearAlert = () => ({
|
||||||
|
type: ALERT_CLEAR,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const showAlert = (alert: Alert) => ({
|
||||||
|
type: ALERT_SHOW,
|
||||||
|
alert,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const showAlertForError = (error: unknown, skipNotFound = false) => {
|
||||||
|
if (error instanceof AxiosError && error.response) {
|
||||||
|
const { status, statusText, headers } = error.response;
|
||||||
|
const { data } = error.response as AxiosResponse<ApiErrorResponse>;
|
||||||
|
|
||||||
|
// Skip these errors as they are reflected in the UI
|
||||||
|
if (skipNotFound && (status === 404 || status === 410)) {
|
||||||
|
return { type: ALERT_NOOP };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rate limit errors
|
||||||
|
if (status === 429 && headers['x-ratelimit-reset']) {
|
||||||
|
return showAlert({
|
||||||
|
title: messages.rateLimitedTitle,
|
||||||
|
message: messages.rateLimitedMessage,
|
||||||
|
values: {
|
||||||
|
retry_time: new Date(headers['x-ratelimit-reset'] as string),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return showAlert({
|
||||||
|
title: `${status}`,
|
||||||
|
message: data.error ?? statusText,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// An aborted request, e.g. due to reloading the browser window, it not really error
|
||||||
|
if (error instanceof AxiosError && error.code === AxiosError.ECONNABORTED) {
|
||||||
|
return { type: ALERT_NOOP };
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
|
return showAlert({
|
||||||
|
title: messages.unexpectedTitle,
|
||||||
|
message: messages.unexpectedMessage,
|
||||||
|
});
|
||||||
|
};
|
@ -1,10 +1,12 @@
|
|||||||
|
import { createPollFromServerJSON } from 'mastodon/models/poll';
|
||||||
|
|
||||||
import { importAccounts } from '../accounts_typed';
|
import { importAccounts } from '../accounts_typed';
|
||||||
|
|
||||||
import { normalizeStatus, normalizePoll } from './normalizer';
|
import { normalizeStatus } from './normalizer';
|
||||||
|
import { importPolls } from './polls';
|
||||||
|
|
||||||
export const STATUS_IMPORT = 'STATUS_IMPORT';
|
export const STATUS_IMPORT = 'STATUS_IMPORT';
|
||||||
export const STATUSES_IMPORT = 'STATUSES_IMPORT';
|
export const STATUSES_IMPORT = 'STATUSES_IMPORT';
|
||||||
export const POLLS_IMPORT = 'POLLS_IMPORT';
|
|
||||||
export const FILTERS_IMPORT = 'FILTERS_IMPORT';
|
export const FILTERS_IMPORT = 'FILTERS_IMPORT';
|
||||||
|
|
||||||
function pushUnique(array, object) {
|
function pushUnique(array, object) {
|
||||||
@ -25,10 +27,6 @@ export function importFilters(filters) {
|
|||||||
return { type: FILTERS_IMPORT, filters };
|
return { type: FILTERS_IMPORT, filters };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function importPolls(polls) {
|
|
||||||
return { type: POLLS_IMPORT, polls };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function importFetchedAccount(account) {
|
export function importFetchedAccount(account) {
|
||||||
return importFetchedAccounts([account]);
|
return importFetchedAccounts([account]);
|
||||||
}
|
}
|
||||||
@ -73,7 +71,7 @@ export function importFetchedStatuses(statuses) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (status.poll?.id) {
|
if (status.poll?.id) {
|
||||||
pushUnique(polls, normalizePoll(status.poll, getState().getIn(['polls', status.poll.id])));
|
pushUnique(polls, createPollFromServerJSON(status.poll, getState().polls.get(status.poll.id)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.card) {
|
if (status.card) {
|
||||||
@ -83,15 +81,9 @@ export function importFetchedStatuses(statuses) {
|
|||||||
|
|
||||||
statuses.forEach(processStatus);
|
statuses.forEach(processStatus);
|
||||||
|
|
||||||
dispatch(importPolls(polls));
|
dispatch(importPolls({ polls }));
|
||||||
dispatch(importFetchedAccounts(accounts));
|
dispatch(importFetchedAccounts(accounts));
|
||||||
dispatch(importStatuses(normalStatuses));
|
dispatch(importStatuses(normalStatuses));
|
||||||
dispatch(importFilters(filters));
|
dispatch(importFilters(filters));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function importFetchedPoll(poll) {
|
|
||||||
return (dispatch, getState) => {
|
|
||||||
dispatch(importPolls([normalizePoll(poll, getState().getIn(['polls', poll.id]))]));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
import escapeTextContentForBrowser from 'escape-html';
|
import escapeTextContentForBrowser from 'escape-html';
|
||||||
|
|
||||||
|
import { makeEmojiMap } from 'mastodon/models/custom_emoji';
|
||||||
|
|
||||||
import emojify from '../../features/emoji/emoji';
|
import emojify from '../../features/emoji/emoji';
|
||||||
import { expandSpoilers } from '../../initial_state';
|
import { expandSpoilers } from '../../initial_state';
|
||||||
|
|
||||||
const domParser = new DOMParser();
|
const domParser = new DOMParser();
|
||||||
|
|
||||||
const makeEmojiMap = emojis => emojis.reduce((obj, emoji) => {
|
|
||||||
obj[`:${emoji.shortcode}:`] = emoji;
|
|
||||||
return obj;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
export function searchTextFromRawStatus (status) {
|
export function searchTextFromRawStatus (status) {
|
||||||
const spoilerText = status.spoiler_text || '';
|
const spoilerText = status.spoiler_text || '';
|
||||||
const searchContent = ([spoilerText, status.content].concat((status.poll && status.poll.options) ? status.poll.options.map(option => option.title) : [])).concat(status.media_attachments.map(att => att.description)).join('\n\n').replace(/<br\s*\/?>/g, '\n').replace(/<\/p><p>/g, '\n\n');
|
const searchContent = ([spoilerText, status.content].concat((status.poll && status.poll.options) ? status.poll.options.map(option => option.title) : [])).concat(status.media_attachments.map(att => att.description)).join('\n\n').replace(/<br\s*\/?>/g, '\n').replace(/<\/p><p>/g, '\n\n');
|
||||||
@ -112,38 +109,6 @@ export function normalizeStatusTranslation(translation, status) {
|
|||||||
return normalTranslation;
|
return normalTranslation;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizePoll(poll, normalOldPoll) {
|
|
||||||
const normalPoll = { ...poll };
|
|
||||||
const emojiMap = makeEmojiMap(poll.emojis);
|
|
||||||
|
|
||||||
normalPoll.options = poll.options.map((option, index) => {
|
|
||||||
const normalOption = {
|
|
||||||
...option,
|
|
||||||
voted: poll.own_votes && poll.own_votes.includes(index),
|
|
||||||
titleHtml: emojify(escapeTextContentForBrowser(option.title), emojiMap),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (normalOldPoll && normalOldPoll.getIn(['options', index, 'title']) === option.title) {
|
|
||||||
normalOption.translation = normalOldPoll.getIn(['options', index, 'translation']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return normalOption;
|
|
||||||
});
|
|
||||||
|
|
||||||
return normalPoll;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function normalizePollOptionTranslation(translation, poll) {
|
|
||||||
const emojiMap = makeEmojiMap(poll.get('emojis').toJS());
|
|
||||||
|
|
||||||
const normalTranslation = {
|
|
||||||
...translation,
|
|
||||||
titleHtml: emojify(escapeTextContentForBrowser(translation.title), emojiMap),
|
|
||||||
};
|
|
||||||
|
|
||||||
return normalTranslation;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function normalizeAnnouncement(announcement) {
|
export function normalizeAnnouncement(announcement) {
|
||||||
const normalAnnouncement = { ...announcement };
|
const normalAnnouncement = { ...announcement };
|
||||||
const emojiMap = makeEmojiMap(normalAnnouncement.emojis);
|
const emojiMap = makeEmojiMap(normalAnnouncement.emojis);
|
||||||
|
7
app/javascript/mastodon/actions/importer/polls.ts
Normal file
7
app/javascript/mastodon/actions/importer/polls.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
import type { Poll } from 'mastodon/models/poll';
|
||||||
|
|
||||||
|
export const importPolls = createAction<{ polls: Poll[] }>(
|
||||||
|
'poll/importMultiple',
|
||||||
|
);
|
@ -1,8 +1,5 @@
|
|||||||
import api from '../api';
|
import api from '../api';
|
||||||
|
|
||||||
import { showAlertForError } from './alerts';
|
|
||||||
import { importFetchedAccounts } from './importer';
|
|
||||||
|
|
||||||
export const LIST_FETCH_REQUEST = 'LIST_FETCH_REQUEST';
|
export const LIST_FETCH_REQUEST = 'LIST_FETCH_REQUEST';
|
||||||
export const LIST_FETCH_SUCCESS = 'LIST_FETCH_SUCCESS';
|
export const LIST_FETCH_SUCCESS = 'LIST_FETCH_SUCCESS';
|
||||||
export const LIST_FETCH_FAIL = 'LIST_FETCH_FAIL';
|
export const LIST_FETCH_FAIL = 'LIST_FETCH_FAIL';
|
||||||
@ -11,45 +8,10 @@ export const LISTS_FETCH_REQUEST = 'LISTS_FETCH_REQUEST';
|
|||||||
export const LISTS_FETCH_SUCCESS = 'LISTS_FETCH_SUCCESS';
|
export const LISTS_FETCH_SUCCESS = 'LISTS_FETCH_SUCCESS';
|
||||||
export const LISTS_FETCH_FAIL = 'LISTS_FETCH_FAIL';
|
export const LISTS_FETCH_FAIL = 'LISTS_FETCH_FAIL';
|
||||||
|
|
||||||
export const LIST_EDITOR_TITLE_CHANGE = 'LIST_EDITOR_TITLE_CHANGE';
|
|
||||||
export const LIST_EDITOR_RESET = 'LIST_EDITOR_RESET';
|
|
||||||
export const LIST_EDITOR_SETUP = 'LIST_EDITOR_SETUP';
|
|
||||||
|
|
||||||
export const LIST_CREATE_REQUEST = 'LIST_CREATE_REQUEST';
|
|
||||||
export const LIST_CREATE_SUCCESS = 'LIST_CREATE_SUCCESS';
|
|
||||||
export const LIST_CREATE_FAIL = 'LIST_CREATE_FAIL';
|
|
||||||
|
|
||||||
export const LIST_UPDATE_REQUEST = 'LIST_UPDATE_REQUEST';
|
|
||||||
export const LIST_UPDATE_SUCCESS = 'LIST_UPDATE_SUCCESS';
|
|
||||||
export const LIST_UPDATE_FAIL = 'LIST_UPDATE_FAIL';
|
|
||||||
|
|
||||||
export const LIST_DELETE_REQUEST = 'LIST_DELETE_REQUEST';
|
export const LIST_DELETE_REQUEST = 'LIST_DELETE_REQUEST';
|
||||||
export const LIST_DELETE_SUCCESS = 'LIST_DELETE_SUCCESS';
|
export const LIST_DELETE_SUCCESS = 'LIST_DELETE_SUCCESS';
|
||||||
export const LIST_DELETE_FAIL = 'LIST_DELETE_FAIL';
|
export const LIST_DELETE_FAIL = 'LIST_DELETE_FAIL';
|
||||||
|
|
||||||
export const LIST_ACCOUNTS_FETCH_REQUEST = 'LIST_ACCOUNTS_FETCH_REQUEST';
|
|
||||||
export const LIST_ACCOUNTS_FETCH_SUCCESS = 'LIST_ACCOUNTS_FETCH_SUCCESS';
|
|
||||||
export const LIST_ACCOUNTS_FETCH_FAIL = 'LIST_ACCOUNTS_FETCH_FAIL';
|
|
||||||
|
|
||||||
export const LIST_EDITOR_SUGGESTIONS_CHANGE = 'LIST_EDITOR_SUGGESTIONS_CHANGE';
|
|
||||||
export const LIST_EDITOR_SUGGESTIONS_READY = 'LIST_EDITOR_SUGGESTIONS_READY';
|
|
||||||
export const LIST_EDITOR_SUGGESTIONS_CLEAR = 'LIST_EDITOR_SUGGESTIONS_CLEAR';
|
|
||||||
|
|
||||||
export const LIST_EDITOR_ADD_REQUEST = 'LIST_EDITOR_ADD_REQUEST';
|
|
||||||
export const LIST_EDITOR_ADD_SUCCESS = 'LIST_EDITOR_ADD_SUCCESS';
|
|
||||||
export const LIST_EDITOR_ADD_FAIL = 'LIST_EDITOR_ADD_FAIL';
|
|
||||||
|
|
||||||
export const LIST_EDITOR_REMOVE_REQUEST = 'LIST_EDITOR_REMOVE_REQUEST';
|
|
||||||
export const LIST_EDITOR_REMOVE_SUCCESS = 'LIST_EDITOR_REMOVE_SUCCESS';
|
|
||||||
export const LIST_EDITOR_REMOVE_FAIL = 'LIST_EDITOR_REMOVE_FAIL';
|
|
||||||
|
|
||||||
export const LIST_ADDER_RESET = 'LIST_ADDER_RESET';
|
|
||||||
export const LIST_ADDER_SETUP = 'LIST_ADDER_SETUP';
|
|
||||||
|
|
||||||
export const LIST_ADDER_LISTS_FETCH_REQUEST = 'LIST_ADDER_LISTS_FETCH_REQUEST';
|
|
||||||
export const LIST_ADDER_LISTS_FETCH_SUCCESS = 'LIST_ADDER_LISTS_FETCH_SUCCESS';
|
|
||||||
export const LIST_ADDER_LISTS_FETCH_FAIL = 'LIST_ADDER_LISTS_FETCH_FAIL';
|
|
||||||
|
|
||||||
export const fetchList = id => (dispatch, getState) => {
|
export const fetchList = id => (dispatch, getState) => {
|
||||||
if (getState().getIn(['lists', id])) {
|
if (getState().getIn(['lists', id])) {
|
||||||
return;
|
return;
|
||||||
@ -100,89 +62,6 @@ export const fetchListsFail = error => ({
|
|||||||
error,
|
error,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const submitListEditor = shouldReset => (dispatch, getState) => {
|
|
||||||
const listId = getState().getIn(['listEditor', 'listId']);
|
|
||||||
const title = getState().getIn(['listEditor', 'title']);
|
|
||||||
|
|
||||||
if (listId === null) {
|
|
||||||
dispatch(createList(title, shouldReset));
|
|
||||||
} else {
|
|
||||||
dispatch(updateList(listId, title, shouldReset));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setupListEditor = listId => (dispatch, getState) => {
|
|
||||||
dispatch({
|
|
||||||
type: LIST_EDITOR_SETUP,
|
|
||||||
list: getState().getIn(['lists', listId]),
|
|
||||||
});
|
|
||||||
|
|
||||||
dispatch(fetchListAccounts(listId));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const changeListEditorTitle = value => ({
|
|
||||||
type: LIST_EDITOR_TITLE_CHANGE,
|
|
||||||
value,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const createList = (title, shouldReset) => (dispatch) => {
|
|
||||||
dispatch(createListRequest());
|
|
||||||
|
|
||||||
api().post('/api/v1/lists', { title }).then(({ data }) => {
|
|
||||||
dispatch(createListSuccess(data));
|
|
||||||
|
|
||||||
if (shouldReset) {
|
|
||||||
dispatch(resetListEditor());
|
|
||||||
}
|
|
||||||
}).catch(err => dispatch(createListFail(err)));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createListRequest = () => ({
|
|
||||||
type: LIST_CREATE_REQUEST,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const createListSuccess = list => ({
|
|
||||||
type: LIST_CREATE_SUCCESS,
|
|
||||||
list,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const createListFail = error => ({
|
|
||||||
type: LIST_CREATE_FAIL,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const updateList = (id, title, shouldReset, isExclusive, replies_policy) => (dispatch) => {
|
|
||||||
dispatch(updateListRequest(id));
|
|
||||||
|
|
||||||
api().put(`/api/v1/lists/${id}`, { title, replies_policy, exclusive: typeof isExclusive === 'undefined' ? undefined : !!isExclusive }).then(({ data }) => {
|
|
||||||
dispatch(updateListSuccess(data));
|
|
||||||
|
|
||||||
if (shouldReset) {
|
|
||||||
dispatch(resetListEditor());
|
|
||||||
}
|
|
||||||
}).catch(err => dispatch(updateListFail(id, err)));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const updateListRequest = id => ({
|
|
||||||
type: LIST_UPDATE_REQUEST,
|
|
||||||
id,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const updateListSuccess = list => ({
|
|
||||||
type: LIST_UPDATE_SUCCESS,
|
|
||||||
list,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const updateListFail = (id, error) => ({
|
|
||||||
type: LIST_UPDATE_FAIL,
|
|
||||||
id,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const resetListEditor = () => ({
|
|
||||||
type: LIST_EDITOR_RESET,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const deleteList = id => (dispatch) => {
|
export const deleteList = id => (dispatch) => {
|
||||||
dispatch(deleteListRequest(id));
|
dispatch(deleteListRequest(id));
|
||||||
|
|
||||||
@ -206,167 +85,3 @@ export const deleteListFail = (id, error) => ({
|
|||||||
id,
|
id,
|
||||||
error,
|
error,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const fetchListAccounts = listId => (dispatch) => {
|
|
||||||
dispatch(fetchListAccountsRequest(listId));
|
|
||||||
|
|
||||||
api().get(`/api/v1/lists/${listId}/accounts`, { params: { limit: 0 } }).then(({ data }) => {
|
|
||||||
dispatch(importFetchedAccounts(data));
|
|
||||||
dispatch(fetchListAccountsSuccess(listId, data));
|
|
||||||
}).catch(err => dispatch(fetchListAccountsFail(listId, err)));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fetchListAccountsRequest = id => ({
|
|
||||||
type: LIST_ACCOUNTS_FETCH_REQUEST,
|
|
||||||
id,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const fetchListAccountsSuccess = (id, accounts, next) => ({
|
|
||||||
type: LIST_ACCOUNTS_FETCH_SUCCESS,
|
|
||||||
id,
|
|
||||||
accounts,
|
|
||||||
next,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const fetchListAccountsFail = (id, error) => ({
|
|
||||||
type: LIST_ACCOUNTS_FETCH_FAIL,
|
|
||||||
id,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const fetchListSuggestions = q => (dispatch) => {
|
|
||||||
const params = {
|
|
||||||
q,
|
|
||||||
resolve: false,
|
|
||||||
limit: 4,
|
|
||||||
following: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
api().get('/api/v1/accounts/search', { params }).then(({ data }) => {
|
|
||||||
dispatch(importFetchedAccounts(data));
|
|
||||||
dispatch(fetchListSuggestionsReady(q, data));
|
|
||||||
}).catch(error => dispatch(showAlertForError(error)));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fetchListSuggestionsReady = (query, accounts) => ({
|
|
||||||
type: LIST_EDITOR_SUGGESTIONS_READY,
|
|
||||||
query,
|
|
||||||
accounts,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const clearListSuggestions = () => ({
|
|
||||||
type: LIST_EDITOR_SUGGESTIONS_CLEAR,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const changeListSuggestions = value => ({
|
|
||||||
type: LIST_EDITOR_SUGGESTIONS_CHANGE,
|
|
||||||
value,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const addToListEditor = accountId => (dispatch, getState) => {
|
|
||||||
dispatch(addToList(getState().getIn(['listEditor', 'listId']), accountId));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addToList = (listId, accountId) => (dispatch) => {
|
|
||||||
dispatch(addToListRequest(listId, accountId));
|
|
||||||
|
|
||||||
api().post(`/api/v1/lists/${listId}/accounts`, { account_ids: [accountId] })
|
|
||||||
.then(() => dispatch(addToListSuccess(listId, accountId)))
|
|
||||||
.catch(err => dispatch(addToListFail(listId, accountId, err)));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const addToListRequest = (listId, accountId) => ({
|
|
||||||
type: LIST_EDITOR_ADD_REQUEST,
|
|
||||||
listId,
|
|
||||||
accountId,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const addToListSuccess = (listId, accountId) => ({
|
|
||||||
type: LIST_EDITOR_ADD_SUCCESS,
|
|
||||||
listId,
|
|
||||||
accountId,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const addToListFail = (listId, accountId, error) => ({
|
|
||||||
type: LIST_EDITOR_ADD_FAIL,
|
|
||||||
listId,
|
|
||||||
accountId,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const removeFromListEditor = accountId => (dispatch, getState) => {
|
|
||||||
dispatch(removeFromList(getState().getIn(['listEditor', 'listId']), accountId));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const removeFromList = (listId, accountId) => (dispatch) => {
|
|
||||||
dispatch(removeFromListRequest(listId, accountId));
|
|
||||||
|
|
||||||
api().delete(`/api/v1/lists/${listId}/accounts`, { params: { account_ids: [accountId] } })
|
|
||||||
.then(() => dispatch(removeFromListSuccess(listId, accountId)))
|
|
||||||
.catch(err => dispatch(removeFromListFail(listId, accountId, err)));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const removeFromListRequest = (listId, accountId) => ({
|
|
||||||
type: LIST_EDITOR_REMOVE_REQUEST,
|
|
||||||
listId,
|
|
||||||
accountId,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const removeFromListSuccess = (listId, accountId) => ({
|
|
||||||
type: LIST_EDITOR_REMOVE_SUCCESS,
|
|
||||||
listId,
|
|
||||||
accountId,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const removeFromListFail = (listId, accountId, error) => ({
|
|
||||||
type: LIST_EDITOR_REMOVE_FAIL,
|
|
||||||
listId,
|
|
||||||
accountId,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const resetListAdder = () => ({
|
|
||||||
type: LIST_ADDER_RESET,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const setupListAdder = accountId => (dispatch, getState) => {
|
|
||||||
dispatch({
|
|
||||||
type: LIST_ADDER_SETUP,
|
|
||||||
account: getState().getIn(['accounts', accountId]),
|
|
||||||
});
|
|
||||||
dispatch(fetchLists());
|
|
||||||
dispatch(fetchAccountLists(accountId));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fetchAccountLists = accountId => (dispatch) => {
|
|
||||||
dispatch(fetchAccountListsRequest(accountId));
|
|
||||||
|
|
||||||
api().get(`/api/v1/accounts/${accountId}/lists`)
|
|
||||||
.then(({ data }) => dispatch(fetchAccountListsSuccess(accountId, data)))
|
|
||||||
.catch(err => dispatch(fetchAccountListsFail(accountId, err)));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fetchAccountListsRequest = id => ({
|
|
||||||
type:LIST_ADDER_LISTS_FETCH_REQUEST,
|
|
||||||
id,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const fetchAccountListsSuccess = (id, lists) => ({
|
|
||||||
type: LIST_ADDER_LISTS_FETCH_SUCCESS,
|
|
||||||
id,
|
|
||||||
lists,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const fetchAccountListsFail = (id, err) => ({
|
|
||||||
type: LIST_ADDER_LISTS_FETCH_FAIL,
|
|
||||||
id,
|
|
||||||
err,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const addToListAdder = listId => (dispatch, getState) => {
|
|
||||||
dispatch(addToList(listId, getState().getIn(['listAdder', 'accountId'])));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const removeFromListAdder = listId => (dispatch, getState) => {
|
|
||||||
dispatch(removeFromList(listId, getState().getIn(['listAdder', 'accountId'])));
|
|
||||||
};
|
|
||||||
|
13
app/javascript/mastodon/actions/lists_typed.ts
Normal file
13
app/javascript/mastodon/actions/lists_typed.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { apiCreate, apiUpdate } from 'mastodon/api/lists';
|
||||||
|
import type { List } from 'mastodon/models/list';
|
||||||
|
import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
|
||||||
|
|
||||||
|
export const createList = createDataLoadingThunk(
|
||||||
|
'list/create',
|
||||||
|
(list: Partial<List>) => apiCreate(list),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const updateList = createDataLoadingThunk(
|
||||||
|
'list/update',
|
||||||
|
(list: Partial<List>) => apiUpdate(list),
|
||||||
|
);
|
@ -141,6 +141,9 @@ export const pollRecentNotifications = createDataLoadingThunk(
|
|||||||
|
|
||||||
return { notifications };
|
return { notifications };
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
useLoadingBar: false,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export const processNewNotificationForGroups = createAppAsyncThunk(
|
export const processNewNotificationForGroups = createAppAsyncThunk(
|
||||||
|
@ -1,61 +0,0 @@
|
|||||||
import api from '../api';
|
|
||||||
|
|
||||||
import { importFetchedPoll } from './importer';
|
|
||||||
|
|
||||||
export const POLL_VOTE_REQUEST = 'POLL_VOTE_REQUEST';
|
|
||||||
export const POLL_VOTE_SUCCESS = 'POLL_VOTE_SUCCESS';
|
|
||||||
export const POLL_VOTE_FAIL = 'POLL_VOTE_FAIL';
|
|
||||||
|
|
||||||
export const POLL_FETCH_REQUEST = 'POLL_FETCH_REQUEST';
|
|
||||||
export const POLL_FETCH_SUCCESS = 'POLL_FETCH_SUCCESS';
|
|
||||||
export const POLL_FETCH_FAIL = 'POLL_FETCH_FAIL';
|
|
||||||
|
|
||||||
export const vote = (pollId, choices) => (dispatch) => {
|
|
||||||
dispatch(voteRequest());
|
|
||||||
|
|
||||||
api().post(`/api/v1/polls/${pollId}/votes`, { choices })
|
|
||||||
.then(({ data }) => {
|
|
||||||
dispatch(importFetchedPoll(data));
|
|
||||||
dispatch(voteSuccess(data));
|
|
||||||
})
|
|
||||||
.catch(err => dispatch(voteFail(err)));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fetchPoll = pollId => (dispatch) => {
|
|
||||||
dispatch(fetchPollRequest());
|
|
||||||
|
|
||||||
api().get(`/api/v1/polls/${pollId}`)
|
|
||||||
.then(({ data }) => {
|
|
||||||
dispatch(importFetchedPoll(data));
|
|
||||||
dispatch(fetchPollSuccess(data));
|
|
||||||
})
|
|
||||||
.catch(err => dispatch(fetchPollFail(err)));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const voteRequest = () => ({
|
|
||||||
type: POLL_VOTE_REQUEST,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const voteSuccess = poll => ({
|
|
||||||
type: POLL_VOTE_SUCCESS,
|
|
||||||
poll,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const voteFail = error => ({
|
|
||||||
type: POLL_VOTE_FAIL,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const fetchPollRequest = () => ({
|
|
||||||
type: POLL_FETCH_REQUEST,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const fetchPollSuccess = poll => ({
|
|
||||||
type: POLL_FETCH_SUCCESS,
|
|
||||||
poll,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const fetchPollFail = error => ({
|
|
||||||
type: POLL_FETCH_FAIL,
|
|
||||||
error,
|
|
||||||
});
|
|
40
app/javascript/mastodon/actions/polls.ts
Normal file
40
app/javascript/mastodon/actions/polls.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { apiGetPoll, apiPollVote } from 'mastodon/api/polls';
|
||||||
|
import type { ApiPollJSON } from 'mastodon/api_types/polls';
|
||||||
|
import { createPollFromServerJSON } from 'mastodon/models/poll';
|
||||||
|
import {
|
||||||
|
createAppAsyncThunk,
|
||||||
|
createDataLoadingThunk,
|
||||||
|
} from 'mastodon/store/typed_functions';
|
||||||
|
|
||||||
|
import { importPolls } from './importer/polls';
|
||||||
|
|
||||||
|
export const importFetchedPoll = createAppAsyncThunk(
|
||||||
|
'poll/importFetched',
|
||||||
|
(args: { poll: ApiPollJSON }, { dispatch, getState }) => {
|
||||||
|
const { poll } = args;
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
importPolls({
|
||||||
|
polls: [createPollFromServerJSON(poll, getState().polls.get(poll.id))],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const vote = createDataLoadingThunk(
|
||||||
|
'poll/vote',
|
||||||
|
({ pollId, choices }: { pollId: string; choices: string[] }) =>
|
||||||
|
apiPollVote(pollId, choices),
|
||||||
|
async (poll, { dispatch, discardLoadData }) => {
|
||||||
|
await dispatch(importFetchedPoll({ poll }));
|
||||||
|
return discardLoadData;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const fetchPoll = createDataLoadingThunk(
|
||||||
|
'poll/fetch',
|
||||||
|
({ pollId }: { pollId: string }) => apiGetPoll(pollId),
|
||||||
|
async (poll, { dispatch }) => {
|
||||||
|
await dispatch(importFetchedPoll({ poll }));
|
||||||
|
},
|
||||||
|
);
|
@ -1,215 +0,0 @@
|
|||||||
import { fromJS } from 'immutable';
|
|
||||||
|
|
||||||
import { searchHistory } from 'mastodon/settings';
|
|
||||||
|
|
||||||
import api from '../api';
|
|
||||||
|
|
||||||
import { fetchRelationships } from './accounts';
|
|
||||||
import { importFetchedAccounts, importFetchedStatuses } from './importer';
|
|
||||||
|
|
||||||
export const SEARCH_CHANGE = 'SEARCH_CHANGE';
|
|
||||||
export const SEARCH_CLEAR = 'SEARCH_CLEAR';
|
|
||||||
export const SEARCH_SHOW = 'SEARCH_SHOW';
|
|
||||||
|
|
||||||
export const SEARCH_FETCH_REQUEST = 'SEARCH_FETCH_REQUEST';
|
|
||||||
export const SEARCH_FETCH_SUCCESS = 'SEARCH_FETCH_SUCCESS';
|
|
||||||
export const SEARCH_FETCH_FAIL = 'SEARCH_FETCH_FAIL';
|
|
||||||
|
|
||||||
export const SEARCH_EXPAND_REQUEST = 'SEARCH_EXPAND_REQUEST';
|
|
||||||
export const SEARCH_EXPAND_SUCCESS = 'SEARCH_EXPAND_SUCCESS';
|
|
||||||
export const SEARCH_EXPAND_FAIL = 'SEARCH_EXPAND_FAIL';
|
|
||||||
|
|
||||||
export const SEARCH_HISTORY_UPDATE = 'SEARCH_HISTORY_UPDATE';
|
|
||||||
|
|
||||||
export function changeSearch(value) {
|
|
||||||
return {
|
|
||||||
type: SEARCH_CHANGE,
|
|
||||||
value,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function clearSearch() {
|
|
||||||
return {
|
|
||||||
type: SEARCH_CLEAR,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function submitSearch(type) {
|
|
||||||
return (dispatch, getState) => {
|
|
||||||
const value = getState().getIn(['search', 'value']);
|
|
||||||
const signedIn = !!getState().getIn(['meta', 'me']);
|
|
||||||
|
|
||||||
if (value.length === 0) {
|
|
||||||
dispatch(fetchSearchSuccess({ accounts: [], statuses: [], hashtags: [] }, '', type));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(fetchSearchRequest(type));
|
|
||||||
|
|
||||||
api().get('/api/v2/search', {
|
|
||||||
params: {
|
|
||||||
q: value,
|
|
||||||
resolve: signedIn,
|
|
||||||
limit: 11,
|
|
||||||
type,
|
|
||||||
},
|
|
||||||
}).then(response => {
|
|
||||||
if (response.data.accounts) {
|
|
||||||
dispatch(importFetchedAccounts(response.data.accounts));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.data.statuses) {
|
|
||||||
dispatch(importFetchedStatuses(response.data.statuses));
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(fetchSearchSuccess(response.data, value, type));
|
|
||||||
dispatch(fetchRelationships(response.data.accounts.map(item => item.id)));
|
|
||||||
}).catch(error => {
|
|
||||||
dispatch(fetchSearchFail(error));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchSearchRequest(searchType) {
|
|
||||||
return {
|
|
||||||
type: SEARCH_FETCH_REQUEST,
|
|
||||||
searchType,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchSearchSuccess(results, searchTerm, searchType) {
|
|
||||||
return {
|
|
||||||
type: SEARCH_FETCH_SUCCESS,
|
|
||||||
results,
|
|
||||||
searchType,
|
|
||||||
searchTerm,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchSearchFail(error) {
|
|
||||||
return {
|
|
||||||
type: SEARCH_FETCH_FAIL,
|
|
||||||
error,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const expandSearch = type => (dispatch, getState) => {
|
|
||||||
const value = getState().getIn(['search', 'value']);
|
|
||||||
const offset = getState().getIn(['search', 'results', type]).size - 1;
|
|
||||||
|
|
||||||
dispatch(expandSearchRequest(type));
|
|
||||||
|
|
||||||
api().get('/api/v2/search', {
|
|
||||||
params: {
|
|
||||||
q: value,
|
|
||||||
type,
|
|
||||||
offset,
|
|
||||||
limit: 11,
|
|
||||||
},
|
|
||||||
}).then(({ data }) => {
|
|
||||||
if (data.accounts) {
|
|
||||||
dispatch(importFetchedAccounts(data.accounts));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.statuses) {
|
|
||||||
dispatch(importFetchedStatuses(data.statuses));
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(expandSearchSuccess(data, value, type));
|
|
||||||
dispatch(fetchRelationships(data.accounts.map(item => item.id)));
|
|
||||||
}).catch(error => {
|
|
||||||
dispatch(expandSearchFail(error));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const expandSearchRequest = (searchType) => ({
|
|
||||||
type: SEARCH_EXPAND_REQUEST,
|
|
||||||
searchType,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const expandSearchSuccess = (results, searchTerm, searchType) => ({
|
|
||||||
type: SEARCH_EXPAND_SUCCESS,
|
|
||||||
results,
|
|
||||||
searchTerm,
|
|
||||||
searchType,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const expandSearchFail = error => ({
|
|
||||||
type: SEARCH_EXPAND_FAIL,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const showSearch = () => ({
|
|
||||||
type: SEARCH_SHOW,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const openURL = (value, history, onFailure) => (dispatch, getState) => {
|
|
||||||
const signedIn = !!getState().getIn(['meta', 'me']);
|
|
||||||
|
|
||||||
if (!signedIn) {
|
|
||||||
if (onFailure) {
|
|
||||||
onFailure();
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(fetchSearchRequest());
|
|
||||||
|
|
||||||
api().get('/api/v2/search', { params: { q: value, resolve: true } }).then(response => {
|
|
||||||
if (response.data.accounts?.length > 0) {
|
|
||||||
dispatch(importFetchedAccounts(response.data.accounts));
|
|
||||||
history.push(`/@${response.data.accounts[0].acct}`);
|
|
||||||
} else if (response.data.statuses?.length > 0) {
|
|
||||||
dispatch(importFetchedStatuses(response.data.statuses));
|
|
||||||
history.push(`/@${response.data.statuses[0].account.acct}/${response.data.statuses[0].id}`);
|
|
||||||
} else if (onFailure) {
|
|
||||||
onFailure();
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(fetchSearchSuccess(response.data, value));
|
|
||||||
}).catch(err => {
|
|
||||||
dispatch(fetchSearchFail(err));
|
|
||||||
|
|
||||||
if (onFailure) {
|
|
||||||
onFailure();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const clickSearchResult = (q, type) => (dispatch, getState) => {
|
|
||||||
const previous = getState().getIn(['search', 'recent']);
|
|
||||||
|
|
||||||
if (previous.some(x => x.get('q') === q && x.get('type') === type)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const me = getState().getIn(['meta', 'me']);
|
|
||||||
const current = previous.add(fromJS({ type, q })).takeLast(4);
|
|
||||||
|
|
||||||
searchHistory.set(me, current.toJS());
|
|
||||||
dispatch(updateSearchHistory(current));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const forgetSearchResult = q => (dispatch, getState) => {
|
|
||||||
const previous = getState().getIn(['search', 'recent']);
|
|
||||||
const me = getState().getIn(['meta', 'me']);
|
|
||||||
const current = previous.filterNot(result => result.get('q') === q);
|
|
||||||
|
|
||||||
searchHistory.set(me, current.toJS());
|
|
||||||
dispatch(updateSearchHistory(current));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const updateSearchHistory = recent => ({
|
|
||||||
type: SEARCH_HISTORY_UPDATE,
|
|
||||||
recent,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const hydrateSearch = () => (dispatch, getState) => {
|
|
||||||
const me = getState().getIn(['meta', 'me']);
|
|
||||||
const history = searchHistory.get(me);
|
|
||||||
|
|
||||||
if (history !== null) {
|
|
||||||
dispatch(updateSearchHistory(history));
|
|
||||||
}
|
|
||||||
};
|
|
148
app/javascript/mastodon/actions/search.ts
Normal file
148
app/javascript/mastodon/actions/search.ts
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
import { apiGetSearch } from 'mastodon/api/search';
|
||||||
|
import type { ApiSearchType } from 'mastodon/api_types/search';
|
||||||
|
import type {
|
||||||
|
RecentSearch,
|
||||||
|
SearchType as RecentSearchType,
|
||||||
|
} from 'mastodon/models/search';
|
||||||
|
import { searchHistory } from 'mastodon/settings';
|
||||||
|
import {
|
||||||
|
createDataLoadingThunk,
|
||||||
|
createAppAsyncThunk,
|
||||||
|
} from 'mastodon/store/typed_functions';
|
||||||
|
|
||||||
|
import { fetchRelationships } from './accounts';
|
||||||
|
import { importFetchedAccounts, importFetchedStatuses } from './importer';
|
||||||
|
|
||||||
|
export const SEARCH_HISTORY_UPDATE = 'SEARCH_HISTORY_UPDATE';
|
||||||
|
|
||||||
|
export const submitSearch = createDataLoadingThunk(
|
||||||
|
'search/submit',
|
||||||
|
async ({ q, type }: { q: string; type?: ApiSearchType }, { getState }) => {
|
||||||
|
const signedIn = !!getState().meta.get('me');
|
||||||
|
|
||||||
|
return apiGetSearch({
|
||||||
|
q,
|
||||||
|
type,
|
||||||
|
resolve: signedIn,
|
||||||
|
limit: 11,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(data, { dispatch }) => {
|
||||||
|
if (data.accounts.length > 0) {
|
||||||
|
dispatch(importFetchedAccounts(data.accounts));
|
||||||
|
dispatch(fetchRelationships(data.accounts.map((account) => account.id)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.statuses.length > 0) {
|
||||||
|
dispatch(importFetchedStatuses(data.statuses));
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
useLoadingBar: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const expandSearch = createDataLoadingThunk(
|
||||||
|
'search/expand',
|
||||||
|
async ({ type }: { type: ApiSearchType }, { getState }) => {
|
||||||
|
const q = getState().search.q;
|
||||||
|
const results = getState().search.results;
|
||||||
|
const offset = results?.[type].length;
|
||||||
|
|
||||||
|
return apiGetSearch({
|
||||||
|
q,
|
||||||
|
type,
|
||||||
|
limit: 11,
|
||||||
|
offset,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
(data, { dispatch }) => {
|
||||||
|
if (data.accounts.length > 0) {
|
||||||
|
dispatch(importFetchedAccounts(data.accounts));
|
||||||
|
dispatch(fetchRelationships(data.accounts.map((account) => account.id)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.statuses.length > 0) {
|
||||||
|
dispatch(importFetchedStatuses(data.statuses));
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
useLoadingBar: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const openURL = createDataLoadingThunk(
|
||||||
|
'search/openURL',
|
||||||
|
({ url }: { url: string }) =>
|
||||||
|
apiGetSearch({
|
||||||
|
q: url,
|
||||||
|
resolve: true,
|
||||||
|
limit: 1,
|
||||||
|
}),
|
||||||
|
(data, { dispatch }) => {
|
||||||
|
if (data.accounts.length > 0) {
|
||||||
|
dispatch(importFetchedAccounts(data.accounts));
|
||||||
|
} else if (data.statuses.length > 0) {
|
||||||
|
dispatch(importFetchedStatuses(data.statuses));
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
useLoadingBar: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const clickSearchResult = createAppAsyncThunk(
|
||||||
|
'search/clickResult',
|
||||||
|
(
|
||||||
|
{ q, type }: { q: string; type?: RecentSearchType },
|
||||||
|
{ dispatch, getState },
|
||||||
|
) => {
|
||||||
|
const previous = getState().search.recent;
|
||||||
|
|
||||||
|
if (previous.some((x) => x.q === q && x.type === type)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const me = getState().meta.get('me') as string;
|
||||||
|
const current = [{ type, q }, ...previous].slice(0, 4);
|
||||||
|
|
||||||
|
searchHistory.set(me, current);
|
||||||
|
dispatch(updateSearchHistory(current));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const forgetSearchResult = createAppAsyncThunk(
|
||||||
|
'search/forgetResult',
|
||||||
|
(q: string, { dispatch, getState }) => {
|
||||||
|
const previous = getState().search.recent;
|
||||||
|
const me = getState().meta.get('me') as string;
|
||||||
|
const current = previous.filter((result) => result.q !== q);
|
||||||
|
|
||||||
|
searchHistory.set(me, current);
|
||||||
|
dispatch(updateSearchHistory(current));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const updateSearchHistory = createAction<RecentSearch[]>(
|
||||||
|
'search/updateHistory',
|
||||||
|
);
|
||||||
|
|
||||||
|
export const hydrateSearch = createAppAsyncThunk(
|
||||||
|
'search/hydrate',
|
||||||
|
(_args, { dispatch, getState }) => {
|
||||||
|
const me = getState().meta.get('me') as string;
|
||||||
|
const history = searchHistory.get(me) as RecentSearch[] | null;
|
||||||
|
|
||||||
|
if (history !== null) {
|
||||||
|
dispatch(updateSearchHistory(history));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
@ -1,58 +0,0 @@
|
|||||||
import api from '../api';
|
|
||||||
|
|
||||||
import { fetchRelationships } from './accounts';
|
|
||||||
import { importFetchedAccounts } from './importer';
|
|
||||||
|
|
||||||
export const SUGGESTIONS_FETCH_REQUEST = 'SUGGESTIONS_FETCH_REQUEST';
|
|
||||||
export const SUGGESTIONS_FETCH_SUCCESS = 'SUGGESTIONS_FETCH_SUCCESS';
|
|
||||||
export const SUGGESTIONS_FETCH_FAIL = 'SUGGESTIONS_FETCH_FAIL';
|
|
||||||
|
|
||||||
export const SUGGESTIONS_DISMISS = 'SUGGESTIONS_DISMISS';
|
|
||||||
|
|
||||||
export function fetchSuggestions(withRelationships = false) {
|
|
||||||
return (dispatch) => {
|
|
||||||
dispatch(fetchSuggestionsRequest());
|
|
||||||
|
|
||||||
api().get('/api/v2/suggestions', { params: { limit: 20 } }).then(response => {
|
|
||||||
dispatch(importFetchedAccounts(response.data.map(x => x.account)));
|
|
||||||
dispatch(fetchSuggestionsSuccess(response.data));
|
|
||||||
|
|
||||||
if (withRelationships) {
|
|
||||||
dispatch(fetchRelationships(response.data.map(item => item.account.id)));
|
|
||||||
}
|
|
||||||
}).catch(error => dispatch(fetchSuggestionsFail(error)));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchSuggestionsRequest() {
|
|
||||||
return {
|
|
||||||
type: SUGGESTIONS_FETCH_REQUEST,
|
|
||||||
skipLoading: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchSuggestionsSuccess(suggestions) {
|
|
||||||
return {
|
|
||||||
type: SUGGESTIONS_FETCH_SUCCESS,
|
|
||||||
suggestions,
|
|
||||||
skipLoading: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchSuggestionsFail(error) {
|
|
||||||
return {
|
|
||||||
type: SUGGESTIONS_FETCH_FAIL,
|
|
||||||
error,
|
|
||||||
skipLoading: true,
|
|
||||||
skipAlert: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const dismissSuggestion = accountId => (dispatch) => {
|
|
||||||
dispatch({
|
|
||||||
type: SUGGESTIONS_DISMISS,
|
|
||||||
id: accountId,
|
|
||||||
});
|
|
||||||
|
|
||||||
api().delete(`/api/v1/suggestions/${accountId}`).catch(() => {});
|
|
||||||
};
|
|
24
app/javascript/mastodon/actions/suggestions.ts
Normal file
24
app/javascript/mastodon/actions/suggestions.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import {
|
||||||
|
apiGetSuggestions,
|
||||||
|
apiDeleteSuggestion,
|
||||||
|
} from 'mastodon/api/suggestions';
|
||||||
|
import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
|
||||||
|
|
||||||
|
import { fetchRelationships } from './accounts';
|
||||||
|
import { importFetchedAccounts } from './importer';
|
||||||
|
|
||||||
|
export const fetchSuggestions = createDataLoadingThunk(
|
||||||
|
'suggestions/fetch',
|
||||||
|
() => apiGetSuggestions(20),
|
||||||
|
(data, { dispatch }) => {
|
||||||
|
dispatch(importFetchedAccounts(data.map((x) => x.account)));
|
||||||
|
dispatch(fetchRelationships(data.map((x) => x.account.id)));
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const dismissSuggestion = createDataLoadingThunk(
|
||||||
|
'suggestions/dismiss',
|
||||||
|
({ accountId }: { accountId: string }) => apiDeleteSuggestion(accountId),
|
||||||
|
);
|
@ -1,9 +1,5 @@
|
|||||||
import api, { getLinks } from '../api';
|
import api, { getLinks } from '../api';
|
||||||
|
|
||||||
export const HASHTAG_FETCH_REQUEST = 'HASHTAG_FETCH_REQUEST';
|
|
||||||
export const HASHTAG_FETCH_SUCCESS = 'HASHTAG_FETCH_SUCCESS';
|
|
||||||
export const HASHTAG_FETCH_FAIL = 'HASHTAG_FETCH_FAIL';
|
|
||||||
|
|
||||||
export const FOLLOWED_HASHTAGS_FETCH_REQUEST = 'FOLLOWED_HASHTAGS_FETCH_REQUEST';
|
export const FOLLOWED_HASHTAGS_FETCH_REQUEST = 'FOLLOWED_HASHTAGS_FETCH_REQUEST';
|
||||||
export const FOLLOWED_HASHTAGS_FETCH_SUCCESS = 'FOLLOWED_HASHTAGS_FETCH_SUCCESS';
|
export const FOLLOWED_HASHTAGS_FETCH_SUCCESS = 'FOLLOWED_HASHTAGS_FETCH_SUCCESS';
|
||||||
export const FOLLOWED_HASHTAGS_FETCH_FAIL = 'FOLLOWED_HASHTAGS_FETCH_FAIL';
|
export const FOLLOWED_HASHTAGS_FETCH_FAIL = 'FOLLOWED_HASHTAGS_FETCH_FAIL';
|
||||||
@ -12,39 +8,6 @@ export const FOLLOWED_HASHTAGS_EXPAND_REQUEST = 'FOLLOWED_HASHTAGS_EXPAND_REQUES
|
|||||||
export const FOLLOWED_HASHTAGS_EXPAND_SUCCESS = 'FOLLOWED_HASHTAGS_EXPAND_SUCCESS';
|
export const FOLLOWED_HASHTAGS_EXPAND_SUCCESS = 'FOLLOWED_HASHTAGS_EXPAND_SUCCESS';
|
||||||
export const FOLLOWED_HASHTAGS_EXPAND_FAIL = 'FOLLOWED_HASHTAGS_EXPAND_FAIL';
|
export const FOLLOWED_HASHTAGS_EXPAND_FAIL = 'FOLLOWED_HASHTAGS_EXPAND_FAIL';
|
||||||
|
|
||||||
export const HASHTAG_FOLLOW_REQUEST = 'HASHTAG_FOLLOW_REQUEST';
|
|
||||||
export const HASHTAG_FOLLOW_SUCCESS = 'HASHTAG_FOLLOW_SUCCESS';
|
|
||||||
export const HASHTAG_FOLLOW_FAIL = 'HASHTAG_FOLLOW_FAIL';
|
|
||||||
|
|
||||||
export const HASHTAG_UNFOLLOW_REQUEST = 'HASHTAG_UNFOLLOW_REQUEST';
|
|
||||||
export const HASHTAG_UNFOLLOW_SUCCESS = 'HASHTAG_UNFOLLOW_SUCCESS';
|
|
||||||
export const HASHTAG_UNFOLLOW_FAIL = 'HASHTAG_UNFOLLOW_FAIL';
|
|
||||||
|
|
||||||
export const fetchHashtag = name => (dispatch) => {
|
|
||||||
dispatch(fetchHashtagRequest());
|
|
||||||
|
|
||||||
api().get(`/api/v1/tags/${name}`).then(({ data }) => {
|
|
||||||
dispatch(fetchHashtagSuccess(name, data));
|
|
||||||
}).catch(err => {
|
|
||||||
dispatch(fetchHashtagFail(err));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const fetchHashtagRequest = () => ({
|
|
||||||
type: HASHTAG_FETCH_REQUEST,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const fetchHashtagSuccess = (name, tag) => ({
|
|
||||||
type: HASHTAG_FETCH_SUCCESS,
|
|
||||||
name,
|
|
||||||
tag,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const fetchHashtagFail = error => ({
|
|
||||||
type: HASHTAG_FETCH_FAIL,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const fetchFollowedHashtags = () => (dispatch) => {
|
export const fetchFollowedHashtags = () => (dispatch) => {
|
||||||
dispatch(fetchFollowedHashtagsRequest());
|
dispatch(fetchFollowedHashtagsRequest());
|
||||||
|
|
||||||
@ -116,57 +79,3 @@ export function expandFollowedHashtagsFail(error) {
|
|||||||
error,
|
error,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const followHashtag = name => (dispatch) => {
|
|
||||||
dispatch(followHashtagRequest(name));
|
|
||||||
|
|
||||||
api().post(`/api/v1/tags/${name}/follow`).then(({ data }) => {
|
|
||||||
dispatch(followHashtagSuccess(name, data));
|
|
||||||
}).catch(err => {
|
|
||||||
dispatch(followHashtagFail(name, err));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const followHashtagRequest = name => ({
|
|
||||||
type: HASHTAG_FOLLOW_REQUEST,
|
|
||||||
name,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const followHashtagSuccess = (name, tag) => ({
|
|
||||||
type: HASHTAG_FOLLOW_SUCCESS,
|
|
||||||
name,
|
|
||||||
tag,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const followHashtagFail = (name, error) => ({
|
|
||||||
type: HASHTAG_FOLLOW_FAIL,
|
|
||||||
name,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const unfollowHashtag = name => (dispatch) => {
|
|
||||||
dispatch(unfollowHashtagRequest(name));
|
|
||||||
|
|
||||||
api().post(`/api/v1/tags/${name}/unfollow`).then(({ data }) => {
|
|
||||||
dispatch(unfollowHashtagSuccess(name, data));
|
|
||||||
}).catch(err => {
|
|
||||||
dispatch(unfollowHashtagFail(name, err));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const unfollowHashtagRequest = name => ({
|
|
||||||
type: HASHTAG_UNFOLLOW_REQUEST,
|
|
||||||
name,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const unfollowHashtagSuccess = (name, tag) => ({
|
|
||||||
type: HASHTAG_UNFOLLOW_SUCCESS,
|
|
||||||
name,
|
|
||||||
tag,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const unfollowHashtagFail = (name, error) => ({
|
|
||||||
type: HASHTAG_UNFOLLOW_FAIL,
|
|
||||||
name,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
|
17
app/javascript/mastodon/actions/tags_typed.ts
Normal file
17
app/javascript/mastodon/actions/tags_typed.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { apiGetTag, apiFollowTag, apiUnfollowTag } from 'mastodon/api/tags';
|
||||||
|
import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
|
||||||
|
|
||||||
|
export const fetchHashtag = createDataLoadingThunk(
|
||||||
|
'tags/fetch',
|
||||||
|
({ tagId }: { tagId: string }) => apiGetTag(tagId),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const followHashtag = createDataLoadingThunk(
|
||||||
|
'tags/follow',
|
||||||
|
({ tagId }: { tagId: string }) => apiFollowTag(tagId),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const unfollowHashtag = createDataLoadingThunk(
|
||||||
|
'tags/unfollow',
|
||||||
|
({ tagId }: { tagId: string }) => apiUnfollowTag(tagId),
|
||||||
|
);
|
@ -68,6 +68,7 @@ export async function apiRequest<ApiResponse = unknown>(
|
|||||||
method: Method,
|
method: Method,
|
||||||
url: string,
|
url: string,
|
||||||
args: {
|
args: {
|
||||||
|
signal?: AbortSignal;
|
||||||
params?: RequestParamsOrData;
|
params?: RequestParamsOrData;
|
||||||
data?: RequestParamsOrData;
|
data?: RequestParamsOrData;
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
|
@ -5,3 +5,16 @@ export const apiSubmitAccountNote = (id: string, value: string) =>
|
|||||||
apiRequestPost<ApiRelationshipJSON>(`v1/accounts/${id}/note`, {
|
apiRequestPost<ApiRelationshipJSON>(`v1/accounts/${id}/note`, {
|
||||||
comment: value,
|
comment: value,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const apiFollowAccount = (
|
||||||
|
id: string,
|
||||||
|
params?: {
|
||||||
|
reblogs: boolean;
|
||||||
|
},
|
||||||
|
) =>
|
||||||
|
apiRequestPost<ApiRelationshipJSON>(`v1/accounts/${id}/follow`, {
|
||||||
|
...params,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const apiUnfollowAccount = (id: string) =>
|
||||||
|
apiRequestPost<ApiRelationshipJSON>(`v1/accounts/${id}/unfollow`);
|
||||||
|
11
app/javascript/mastodon/api/instance.ts
Normal file
11
app/javascript/mastodon/api/instance.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { apiRequestGet } from 'mastodon/api';
|
||||||
|
import type {
|
||||||
|
ApiTermsOfServiceJSON,
|
||||||
|
ApiPrivacyPolicyJSON,
|
||||||
|
} from 'mastodon/api_types/instance';
|
||||||
|
|
||||||
|
export const apiGetTermsOfService = () =>
|
||||||
|
apiRequestGet<ApiTermsOfServiceJSON>('v1/instance/terms_of_service');
|
||||||
|
|
||||||
|
export const apiGetPrivacyPolicy = () =>
|
||||||
|
apiRequestGet<ApiPrivacyPolicyJSON>('v1/instance/privacy_policy');
|
32
app/javascript/mastodon/api/lists.ts
Normal file
32
app/javascript/mastodon/api/lists.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import {
|
||||||
|
apiRequestPost,
|
||||||
|
apiRequestPut,
|
||||||
|
apiRequestGet,
|
||||||
|
apiRequestDelete,
|
||||||
|
} from 'mastodon/api';
|
||||||
|
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
|
||||||
|
import type { ApiListJSON } from 'mastodon/api_types/lists';
|
||||||
|
|
||||||
|
export const apiCreate = (list: Partial<ApiListJSON>) =>
|
||||||
|
apiRequestPost<ApiListJSON>('v1/lists', list);
|
||||||
|
|
||||||
|
export const apiUpdate = (list: Partial<ApiListJSON>) =>
|
||||||
|
apiRequestPut<ApiListJSON>(`v1/lists/${list.id}`, list);
|
||||||
|
|
||||||
|
export const apiGetAccounts = (listId: string) =>
|
||||||
|
apiRequestGet<ApiAccountJSON[]>(`v1/lists/${listId}/accounts`, {
|
||||||
|
limit: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const apiGetAccountLists = (accountId: string) =>
|
||||||
|
apiRequestGet<ApiListJSON[]>(`v1/accounts/${accountId}/lists`);
|
||||||
|
|
||||||
|
export const apiAddAccountToList = (listId: string, accountId: string) =>
|
||||||
|
apiRequestPost(`v1/lists/${listId}/accounts`, {
|
||||||
|
account_ids: [accountId],
|
||||||
|
});
|
||||||
|
|
||||||
|
export const apiRemoveAccountFromList = (listId: string, accountId: string) =>
|
||||||
|
apiRequestDelete(`v1/lists/${listId}/accounts`, {
|
||||||
|
account_ids: [accountId],
|
||||||
|
});
|
10
app/javascript/mastodon/api/polls.ts
Normal file
10
app/javascript/mastodon/api/polls.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { apiRequestGet, apiRequestPost } from 'mastodon/api';
|
||||||
|
import type { ApiPollJSON } from 'mastodon/api_types/polls';
|
||||||
|
|
||||||
|
export const apiGetPoll = (pollId: string) =>
|
||||||
|
apiRequestGet<ApiPollJSON>(`/v1/polls/${pollId}`);
|
||||||
|
|
||||||
|
export const apiPollVote = (pollId: string, choices: string[]) =>
|
||||||
|
apiRequestPost<ApiPollJSON>(`/v1/polls/${pollId}/votes`, {
|
||||||
|
choices,
|
||||||
|
});
|
16
app/javascript/mastodon/api/search.ts
Normal file
16
app/javascript/mastodon/api/search.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { apiRequestGet } from 'mastodon/api';
|
||||||
|
import type {
|
||||||
|
ApiSearchType,
|
||||||
|
ApiSearchResultsJSON,
|
||||||
|
} from 'mastodon/api_types/search';
|
||||||
|
|
||||||
|
export const apiGetSearch = (params: {
|
||||||
|
q: string;
|
||||||
|
resolve?: boolean;
|
||||||
|
type?: ApiSearchType;
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
|
}) =>
|
||||||
|
apiRequestGet<ApiSearchResultsJSON>('v2/search', {
|
||||||
|
...params,
|
||||||
|
});
|
8
app/javascript/mastodon/api/suggestions.ts
Normal file
8
app/javascript/mastodon/api/suggestions.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { apiRequestGet, apiRequestDelete } from 'mastodon/api';
|
||||||
|
import type { ApiSuggestionJSON } from 'mastodon/api_types/suggestions';
|
||||||
|
|
||||||
|
export const apiGetSuggestions = (limit: number) =>
|
||||||
|
apiRequestGet<ApiSuggestionJSON[]>('v2/suggestions', { limit });
|
||||||
|
|
||||||
|
export const apiDeleteSuggestion = (accountId: string) =>
|
||||||
|
apiRequestDelete(`v1/suggestions/${accountId}`);
|
11
app/javascript/mastodon/api/tags.ts
Normal file
11
app/javascript/mastodon/api/tags.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { apiRequestPost, apiRequestGet } from 'mastodon/api';
|
||||||
|
import type { ApiHashtagJSON } from 'mastodon/api_types/tags';
|
||||||
|
|
||||||
|
export const apiGetTag = (tagId: string) =>
|
||||||
|
apiRequestGet<ApiHashtagJSON>(`v1/tags/${tagId}`);
|
||||||
|
|
||||||
|
export const apiFollowTag = (tagId: string) =>
|
||||||
|
apiRequestPost<ApiHashtagJSON>(`v1/tags/${tagId}/follow`);
|
||||||
|
|
||||||
|
export const apiUnfollowTag = (tagId: string) =>
|
||||||
|
apiRequestPost<ApiHashtagJSON>(`v1/tags/${tagId}/unfollow`);
|
9
app/javascript/mastodon/api_types/instance.ts
Normal file
9
app/javascript/mastodon/api_types/instance.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export interface ApiTermsOfServiceJSON {
|
||||||
|
updated_at: string;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiPrivacyPolicyJSON {
|
||||||
|
updated_at: string;
|
||||||
|
content: string;
|
||||||
|
}
|
10
app/javascript/mastodon/api_types/lists.ts
Normal file
10
app/javascript/mastodon/api_types/lists.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// See app/serializers/rest/list_serializer.rb
|
||||||
|
|
||||||
|
export type RepliesPolicyType = 'list' | 'followed' | 'none';
|
||||||
|
|
||||||
|
export interface ApiListJSON {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
exclusive: boolean;
|
||||||
|
replies_policy: RepliesPolicyType;
|
||||||
|
}
|
@ -20,6 +20,7 @@ export const allNotificationTypes = [
|
|||||||
'admin.report',
|
'admin.report',
|
||||||
'moderation_warning',
|
'moderation_warning',
|
||||||
'severed_relationships',
|
'severed_relationships',
|
||||||
|
'annual_report',
|
||||||
];
|
];
|
||||||
|
|
||||||
export type NotificationWithStatusType =
|
export type NotificationWithStatusType =
|
||||||
@ -37,7 +38,8 @@ export type NotificationType =
|
|||||||
| 'moderation_warning'
|
| 'moderation_warning'
|
||||||
| 'severed_relationships'
|
| 'severed_relationships'
|
||||||
| 'admin.sign_up'
|
| 'admin.sign_up'
|
||||||
| 'admin.report';
|
| 'admin.report'
|
||||||
|
| 'annual_report';
|
||||||
|
|
||||||
export interface BaseNotificationJSON {
|
export interface BaseNotificationJSON {
|
||||||
id: string;
|
id: string;
|
||||||
@ -130,6 +132,15 @@ interface AccountRelationshipSeveranceNotificationJSON
|
|||||||
event: ApiAccountRelationshipSeveranceEventJSON;
|
event: ApiAccountRelationshipSeveranceEventJSON;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ApiAnnualReportEventJSON {
|
||||||
|
year: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AnnualReportNotificationGroupJSON extends BaseNotificationGroupJSON {
|
||||||
|
type: 'annual_report';
|
||||||
|
annual_report: ApiAnnualReportEventJSON;
|
||||||
|
}
|
||||||
|
|
||||||
export type ApiNotificationJSON =
|
export type ApiNotificationJSON =
|
||||||
| SimpleNotificationJSON
|
| SimpleNotificationJSON
|
||||||
| ReportNotificationJSON
|
| ReportNotificationJSON
|
||||||
@ -142,7 +153,8 @@ export type ApiNotificationGroupJSON =
|
|||||||
| ReportNotificationGroupJSON
|
| ReportNotificationGroupJSON
|
||||||
| AccountRelationshipSeveranceNotificationGroupJSON
|
| AccountRelationshipSeveranceNotificationGroupJSON
|
||||||
| NotificationGroupWithStatusJSON
|
| NotificationGroupWithStatusJSON
|
||||||
| ModerationWarningNotificationGroupJSON;
|
| ModerationWarningNotificationGroupJSON
|
||||||
|
| AnnualReportNotificationGroupJSON;
|
||||||
|
|
||||||
export interface ApiNotificationGroupsResultJSON {
|
export interface ApiNotificationGroupsResultJSON {
|
||||||
accounts: ApiAccountJSON[];
|
accounts: ApiAccountJSON[];
|
||||||
|
@ -18,6 +18,6 @@ export interface ApiPollJSON {
|
|||||||
options: ApiPollOptionJSON[];
|
options: ApiPollOptionJSON[];
|
||||||
emojis: ApiCustomEmojiJSON[];
|
emojis: ApiCustomEmojiJSON[];
|
||||||
|
|
||||||
voted: boolean;
|
voted?: boolean;
|
||||||
own_votes: number[];
|
own_votes?: number[];
|
||||||
}
|
}
|
||||||
|
11
app/javascript/mastodon/api_types/search.ts
Normal file
11
app/javascript/mastodon/api_types/search.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import type { ApiAccountJSON } from './accounts';
|
||||||
|
import type { ApiStatusJSON } from './statuses';
|
||||||
|
import type { ApiHashtagJSON } from './tags';
|
||||||
|
|
||||||
|
export type ApiSearchType = 'accounts' | 'statuses' | 'hashtags';
|
||||||
|
|
||||||
|
export interface ApiSearchResultsJSON {
|
||||||
|
accounts: ApiAccountJSON[];
|
||||||
|
statuses: ApiStatusJSON[];
|
||||||
|
hashtags: ApiHashtagJSON[];
|
||||||
|
}
|
13
app/javascript/mastodon/api_types/suggestions.ts
Normal file
13
app/javascript/mastodon/api_types/suggestions.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
|
||||||
|
|
||||||
|
export type ApiSuggestionSourceJSON =
|
||||||
|
| 'featured'
|
||||||
|
| 'most_followed'
|
||||||
|
| 'most_interactions'
|
||||||
|
| 'similar_to_recently_followed'
|
||||||
|
| 'friends_of_friends';
|
||||||
|
|
||||||
|
export interface ApiSuggestionJSON {
|
||||||
|
sources: [ApiSuggestionSourceJSON, ...ApiSuggestionSourceJSON[]];
|
||||||
|
account: ApiAccountJSON;
|
||||||
|
}
|
13
app/javascript/mastodon/api_types/tags.ts
Normal file
13
app/javascript/mastodon/api_types/tags.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
interface ApiHistoryJSON {
|
||||||
|
day: string;
|
||||||
|
accounts: string;
|
||||||
|
uses: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiHashtagJSON {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
history: [ApiHistoryJSON, ...ApiHistoryJSON[]];
|
||||||
|
following?: boolean;
|
||||||
|
}
|
@ -1,181 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import { useCallback } from 'react';
|
|
||||||
|
|
||||||
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
|
|
||||||
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
|
||||||
import { EmptyAccount } from 'mastodon/components/empty_account';
|
|
||||||
import { ShortNumber } from 'mastodon/components/short_number';
|
|
||||||
import { VerifiedBadge } from 'mastodon/components/verified_badge';
|
|
||||||
|
|
||||||
import DropdownMenuContainer from '../containers/dropdown_menu_container';
|
|
||||||
import { me } from '../initial_state';
|
|
||||||
|
|
||||||
import { Avatar } from './avatar';
|
|
||||||
import { Button } from './button';
|
|
||||||
import { FollowersCounter } from './counters';
|
|
||||||
import { DisplayName } from './display_name';
|
|
||||||
import { RelativeTimestamp } from './relative_timestamp';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
|
||||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
|
||||||
cancel_follow_request: { id: 'account.cancel_follow_request', defaultMessage: 'Withdraw follow request' },
|
|
||||||
unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' },
|
|
||||||
unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' },
|
|
||||||
mute_notifications: { id: 'account.mute_notifications_short', defaultMessage: 'Mute notifications' },
|
|
||||||
unmute_notifications: { id: 'account.unmute_notifications_short', defaultMessage: 'Unmute notifications' },
|
|
||||||
mute: { id: 'account.mute_short', defaultMessage: 'Mute' },
|
|
||||||
block: { id: 'account.block_short', defaultMessage: 'Block' },
|
|
||||||
more: { id: 'status.more', defaultMessage: 'More' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const Account = ({ size = 46, account, onFollow, onBlock, onMute, onMuteNotifications, hidden, minimal, defaultAction, withBio }) => {
|
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
const handleFollow = useCallback(() => {
|
|
||||||
onFollow(account);
|
|
||||||
}, [onFollow, account]);
|
|
||||||
|
|
||||||
const handleBlock = useCallback(() => {
|
|
||||||
onBlock(account);
|
|
||||||
}, [onBlock, account]);
|
|
||||||
|
|
||||||
const handleMute = useCallback(() => {
|
|
||||||
onMute(account);
|
|
||||||
}, [onMute, account]);
|
|
||||||
|
|
||||||
const handleMuteNotifications = useCallback(() => {
|
|
||||||
onMuteNotifications(account, true);
|
|
||||||
}, [onMuteNotifications, account]);
|
|
||||||
|
|
||||||
const handleUnmuteNotifications = useCallback(() => {
|
|
||||||
onMuteNotifications(account, false);
|
|
||||||
}, [onMuteNotifications, account]);
|
|
||||||
|
|
||||||
if (!account) {
|
|
||||||
return <EmptyAccount size={size} minimal={minimal} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hidden) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{account.get('display_name')}
|
|
||||||
{account.get('username')}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let buttons;
|
|
||||||
|
|
||||||
if (account.get('id') !== me && account.get('relationship', null) !== null) {
|
|
||||||
const following = account.getIn(['relationship', 'following']);
|
|
||||||
const requested = account.getIn(['relationship', 'requested']);
|
|
||||||
const blocking = account.getIn(['relationship', 'blocking']);
|
|
||||||
const muting = account.getIn(['relationship', 'muting']);
|
|
||||||
|
|
||||||
if (requested) {
|
|
||||||
buttons = <Button text={intl.formatMessage(messages.cancel_follow_request)} onClick={handleFollow} />;
|
|
||||||
} else if (blocking) {
|
|
||||||
buttons = <Button text={intl.formatMessage(messages.unblock)} onClick={handleBlock} />;
|
|
||||||
} else if (muting) {
|
|
||||||
let menu;
|
|
||||||
|
|
||||||
if (account.getIn(['relationship', 'muting_notifications'])) {
|
|
||||||
menu = [{ text: intl.formatMessage(messages.unmute_notifications), action: handleUnmuteNotifications }];
|
|
||||||
} else {
|
|
||||||
menu = [{ text: intl.formatMessage(messages.mute_notifications), action: handleMuteNotifications }];
|
|
||||||
}
|
|
||||||
|
|
||||||
buttons = (
|
|
||||||
<>
|
|
||||||
<DropdownMenuContainer
|
|
||||||
items={menu}
|
|
||||||
icon='ellipsis-h'
|
|
||||||
iconComponent={MoreHorizIcon}
|
|
||||||
direction='right'
|
|
||||||
title={intl.formatMessage(messages.more)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button text={intl.formatMessage(messages.unmute)} onClick={handleMute} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
} else if (defaultAction === 'mute') {
|
|
||||||
buttons = <Button text={intl.formatMessage(messages.mute)} onClick={handleMute} />;
|
|
||||||
} else if (defaultAction === 'block') {
|
|
||||||
buttons = <Button text={intl.formatMessage(messages.block)} onClick={handleBlock} />;
|
|
||||||
} else if (!account.get('suspended') && !account.get('moved') || following) {
|
|
||||||
buttons = <Button text={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={handleFollow} />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let muteTimeRemaining;
|
|
||||||
|
|
||||||
if (account.get('mute_expires_at')) {
|
|
||||||
muteTimeRemaining = <>· <RelativeTimestamp timestamp={account.get('mute_expires_at')} futureDate /></>;
|
|
||||||
}
|
|
||||||
|
|
||||||
let verification;
|
|
||||||
|
|
||||||
const firstVerifiedField = account.get('fields').find(item => !!item.get('verified_at'));
|
|
||||||
|
|
||||||
if (firstVerifiedField) {
|
|
||||||
verification = <VerifiedBadge link={firstVerifiedField.get('value')} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={classNames('account', { 'account--minimal': minimal })}>
|
|
||||||
<div className='account__wrapper'>
|
|
||||||
<Link key={account.get('id')} className='account__display-name' title={account.get('acct')} to={`/@${account.get('acct')}`} data-hover-card-account={account.get('id')}>
|
|
||||||
<div className='account__avatar-wrapper'>
|
|
||||||
<Avatar account={account} size={size} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='account__contents'>
|
|
||||||
<DisplayName account={account} />
|
|
||||||
{!minimal && (
|
|
||||||
<div className='account__details'>
|
|
||||||
<ShortNumber value={account.get('followers_count')} renderer={FollowersCounter} /> {verification} {muteTimeRemaining}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
{!minimal && (
|
|
||||||
<div className='account__relationship'>
|
|
||||||
{buttons}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{withBio && (account.get('note').length > 0 ? (
|
|
||||||
<div
|
|
||||||
className='account__note translate'
|
|
||||||
dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className='account__note account__note--missing'><FormattedMessage id='account.no_bio' defaultMessage='No description provided.' /></div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Account.propTypes = {
|
|
||||||
size: PropTypes.number,
|
|
||||||
account: ImmutablePropTypes.record,
|
|
||||||
onFollow: PropTypes.func,
|
|
||||||
onBlock: PropTypes.func,
|
|
||||||
onMute: PropTypes.func,
|
|
||||||
onMuteNotifications: PropTypes.func,
|
|
||||||
hidden: PropTypes.bool,
|
|
||||||
minimal: PropTypes.bool,
|
|
||||||
defaultAction: PropTypes.string,
|
|
||||||
withBio: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Account;
|
|
235
app/javascript/mastodon/components/account.tsx
Normal file
235
app/javascript/mastodon/components/account.tsx
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
||||||
|
import {
|
||||||
|
blockAccount,
|
||||||
|
unblockAccount,
|
||||||
|
muteAccount,
|
||||||
|
unmuteAccount,
|
||||||
|
} from 'mastodon/actions/accounts';
|
||||||
|
import { initMuteModal } from 'mastodon/actions/mutes';
|
||||||
|
import { Avatar } from 'mastodon/components/avatar';
|
||||||
|
import { Button } from 'mastodon/components/button';
|
||||||
|
import { FollowersCounter } from 'mastodon/components/counters';
|
||||||
|
import { DisplayName } from 'mastodon/components/display_name';
|
||||||
|
import { FollowButton } from 'mastodon/components/follow_button';
|
||||||
|
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
|
||||||
|
import { ShortNumber } from 'mastodon/components/short_number';
|
||||||
|
import { Skeleton } from 'mastodon/components/skeleton';
|
||||||
|
import { VerifiedBadge } from 'mastodon/components/verified_badge';
|
||||||
|
import DropdownMenu from 'mastodon/containers/dropdown_menu_container';
|
||||||
|
import { me } from 'mastodon/initial_state';
|
||||||
|
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
||||||
|
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
||||||
|
cancel_follow_request: {
|
||||||
|
id: 'account.cancel_follow_request',
|
||||||
|
defaultMessage: 'Withdraw follow request',
|
||||||
|
},
|
||||||
|
unblock: { id: 'account.unblock_short', defaultMessage: 'Unblock' },
|
||||||
|
unmute: { id: 'account.unmute_short', defaultMessage: 'Unmute' },
|
||||||
|
mute_notifications: {
|
||||||
|
id: 'account.mute_notifications_short',
|
||||||
|
defaultMessage: 'Mute notifications',
|
||||||
|
},
|
||||||
|
unmute_notifications: {
|
||||||
|
id: 'account.unmute_notifications_short',
|
||||||
|
defaultMessage: 'Unmute notifications',
|
||||||
|
},
|
||||||
|
mute: { id: 'account.mute_short', defaultMessage: 'Mute' },
|
||||||
|
block: { id: 'account.block_short', defaultMessage: 'Block' },
|
||||||
|
more: { id: 'status.more', defaultMessage: 'More' },
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Account: React.FC<{
|
||||||
|
size?: number;
|
||||||
|
id: string;
|
||||||
|
hidden?: boolean;
|
||||||
|
minimal?: boolean;
|
||||||
|
defaultAction?: 'block' | 'mute';
|
||||||
|
withBio?: boolean;
|
||||||
|
}> = ({ id, size = 46, hidden, minimal, defaultAction, withBio }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const account = useAppSelector((state) => state.accounts.get(id));
|
||||||
|
const relationship = useAppSelector((state) => state.relationships.get(id));
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const handleBlock = useCallback(() => {
|
||||||
|
if (relationship?.blocking) {
|
||||||
|
dispatch(unblockAccount(id));
|
||||||
|
} else {
|
||||||
|
dispatch(blockAccount(id));
|
||||||
|
}
|
||||||
|
}, [dispatch, id, relationship]);
|
||||||
|
|
||||||
|
const handleMute = useCallback(() => {
|
||||||
|
if (relationship?.muting) {
|
||||||
|
dispatch(unmuteAccount(id));
|
||||||
|
} else {
|
||||||
|
dispatch(initMuteModal(account));
|
||||||
|
}
|
||||||
|
}, [dispatch, id, account, relationship]);
|
||||||
|
|
||||||
|
const handleMuteNotifications = useCallback(() => {
|
||||||
|
dispatch(muteAccount(id, true));
|
||||||
|
}, [dispatch, id]);
|
||||||
|
|
||||||
|
const handleUnmuteNotifications = useCallback(() => {
|
||||||
|
dispatch(muteAccount(id, false));
|
||||||
|
}, [dispatch, id]);
|
||||||
|
|
||||||
|
if (hidden) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{account?.display_name}
|
||||||
|
{account?.username}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let buttons;
|
||||||
|
|
||||||
|
if (account && account.id !== me && relationship) {
|
||||||
|
const { requested, blocking, muting } = relationship;
|
||||||
|
|
||||||
|
if (requested) {
|
||||||
|
buttons = <FollowButton accountId={id} />;
|
||||||
|
} else if (blocking) {
|
||||||
|
buttons = (
|
||||||
|
<Button
|
||||||
|
text={intl.formatMessage(messages.unblock)}
|
||||||
|
onClick={handleBlock}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (muting) {
|
||||||
|
const menu = [
|
||||||
|
{
|
||||||
|
text: intl.formatMessage(
|
||||||
|
relationship.muting_notifications
|
||||||
|
? messages.unmute_notifications
|
||||||
|
: messages.mute_notifications,
|
||||||
|
),
|
||||||
|
action: relationship.muting_notifications
|
||||||
|
? handleUnmuteNotifications
|
||||||
|
: handleMuteNotifications,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
buttons = (
|
||||||
|
<>
|
||||||
|
<DropdownMenu
|
||||||
|
items={menu}
|
||||||
|
icon='ellipsis-h'
|
||||||
|
iconComponent={MoreHorizIcon}
|
||||||
|
direction='right'
|
||||||
|
title={intl.formatMessage(messages.more)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
text={intl.formatMessage(messages.unmute)}
|
||||||
|
onClick={handleMute}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
} else if (defaultAction === 'mute') {
|
||||||
|
buttons = (
|
||||||
|
<Button text={intl.formatMessage(messages.mute)} onClick={handleMute} />
|
||||||
|
);
|
||||||
|
} else if (defaultAction === 'block') {
|
||||||
|
buttons = (
|
||||||
|
<Button
|
||||||
|
text={intl.formatMessage(messages.block)}
|
||||||
|
onClick={handleBlock}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
buttons = <FollowButton accountId={id} />;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buttons = <FollowButton accountId={id} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
let muteTimeRemaining;
|
||||||
|
|
||||||
|
if (account?.mute_expires_at) {
|
||||||
|
muteTimeRemaining = (
|
||||||
|
<>
|
||||||
|
· <RelativeTimestamp timestamp={account.mute_expires_at} futureDate />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let verification;
|
||||||
|
|
||||||
|
const firstVerifiedField = account?.fields.find((item) => !!item.verified_at);
|
||||||
|
|
||||||
|
if (firstVerifiedField) {
|
||||||
|
verification = <VerifiedBadge link={firstVerifiedField.value} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classNames('account', { 'account--minimal': minimal })}>
|
||||||
|
<div className='account__wrapper'>
|
||||||
|
<Link
|
||||||
|
className='account__display-name'
|
||||||
|
title={account?.acct}
|
||||||
|
to={`/@${account?.acct}`}
|
||||||
|
data-hover-card-account={id}
|
||||||
|
>
|
||||||
|
<div className='account__avatar-wrapper'>
|
||||||
|
{account ? (
|
||||||
|
<Avatar account={account} size={size} />
|
||||||
|
) : (
|
||||||
|
<Skeleton width={size} height={size} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='account__contents'>
|
||||||
|
<DisplayName account={account} />
|
||||||
|
|
||||||
|
{!minimal && (
|
||||||
|
<div className='account__details'>
|
||||||
|
{account ? (
|
||||||
|
<>
|
||||||
|
<ShortNumber
|
||||||
|
value={account.followers_count}
|
||||||
|
renderer={FollowersCounter}
|
||||||
|
/>{' '}
|
||||||
|
{verification} {muteTimeRemaining}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Skeleton width='7ch' />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{!minimal && <div className='account__relationship'>{buttons}</div>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{account &&
|
||||||
|
withBio &&
|
||||||
|
(account.note.length > 0 ? (
|
||||||
|
<div
|
||||||
|
className='account__note translate'
|
||||||
|
dangerouslySetInnerHTML={{ __html: account.note_emojified }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className='account__note account__note--missing'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='account.no_bio'
|
||||||
|
defaultMessage='No description provided.'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user