Merge commit '425d77f8124a50fc033e8fb3bdf7b89a6a25f4fa' into glitch-soc/merge-upstream
Conflicts: - `.rubocop_todo.yml`: Upstream regenerated this file, glitch-soc had a specific ignore. - `README.md`: Upstream updated its README, but glitch-soc has a completely different one. Kept glitch-soc's README
This commit is contained in:
commit
44a5f1b64a
|
@ -153,3 +153,100 @@ jobs:
|
||||||
run: './bin/rails db:create db:schema:load db:seed'
|
run: './bin/rails db:create db:schema:load db:seed'
|
||||||
|
|
||||||
- run: bundle exec rake rspec_chunked
|
- run: bundle exec rake rspec_chunked
|
||||||
|
|
||||||
|
test-e2e:
|
||||||
|
name: End to End testing
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
needs:
|
||||||
|
- build
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:14-alpine
|
||||||
|
env:
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
options: >-
|
||||||
|
--health-cmd pg_isready
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
options: >-
|
||||||
|
--health-cmd "redis-cli ping"
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
ports:
|
||||||
|
- 6379:6379
|
||||||
|
|
||||||
|
env:
|
||||||
|
DB_HOST: localhost
|
||||||
|
DB_USER: postgres
|
||||||
|
DB_PASS: postgres
|
||||||
|
DISABLE_SIMPLECOV: true
|
||||||
|
RAILS_ENV: test
|
||||||
|
BUNDLE_WITH: test
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
ruby-version:
|
||||||
|
- '3.0'
|
||||||
|
- '3.1'
|
||||||
|
- '.ruby-version'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
path: './public'
|
||||||
|
name: ${{ github.sha }}
|
||||||
|
|
||||||
|
- name: Update package index
|
||||||
|
run: sudo apt-get update
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
cache: yarn
|
||||||
|
node-version-file: '.nvmrc'
|
||||||
|
|
||||||
|
- name: Install native Ruby dependencies
|
||||||
|
run: sudo apt-get install -y libicu-dev libidn11-dev
|
||||||
|
|
||||||
|
- name: Install additional system dependencies
|
||||||
|
run: sudo apt-get install -y ffmpeg imagemagick
|
||||||
|
|
||||||
|
- name: Set up bundler cache
|
||||||
|
uses: ruby/setup-ruby@v1
|
||||||
|
with:
|
||||||
|
ruby-version: ${{ matrix.ruby-version}}
|
||||||
|
bundler-cache: true
|
||||||
|
|
||||||
|
- run: yarn --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Load database schema
|
||||||
|
run: './bin/rails db:create db:schema:load db:seed'
|
||||||
|
|
||||||
|
- run: bundle exec rake spec:system
|
||||||
|
|
||||||
|
- name: Archive logs
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
if: failure()
|
||||||
|
with:
|
||||||
|
name: e2e-logs-${{ matrix.ruby-version }}
|
||||||
|
path: log/
|
||||||
|
|
||||||
|
- name: Archive test screenshots
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
if: failure()
|
||||||
|
with:
|
||||||
|
name: e2e-screenshots
|
||||||
|
path: tmp/screenshots/
|
||||||
|
|
|
@ -38,14 +38,7 @@ Layout/FirstHashElementIndentation:
|
||||||
# Reason: Currently disabled in .rubocop_todo.yml
|
# Reason: Currently disabled in .rubocop_todo.yml
|
||||||
# https://docs.rubocop.org/rubocop/cops_layout.html#layoutlinelength
|
# https://docs.rubocop.org/rubocop/cops_layout.html#layoutlinelength
|
||||||
Layout/LineLength:
|
Layout/LineLength:
|
||||||
AllowedPatterns:
|
Max: 320 # Default of 120 causes a duplicate entry in generated todo file
|
||||||
# Allow comments to be long lines
|
|
||||||
- !ruby/regexp / \# .*$/
|
|
||||||
- !ruby/regexp /^\# .*$/
|
|
||||||
Exclude:
|
|
||||||
- 'lib/mastodon/cli/*.rb'
|
|
||||||
- db/*migrate/**/*
|
|
||||||
- db/seeds/**/*
|
|
||||||
|
|
||||||
# Reason:
|
# Reason:
|
||||||
# https://docs.rubocop.org/rubocop/cops_lint.html#lintuselessaccessmodifier
|
# https://docs.rubocop.org/rubocop/cops_lint.html#lintuselessaccessmodifier
|
||||||
|
|
|
@ -39,6 +39,13 @@ Layout/LeadingCommentSpace:
|
||||||
- 'config/application.rb'
|
- 'config/application.rb'
|
||||||
- 'config/initializers/omniauth.rb'
|
- 'config/initializers/omniauth.rb'
|
||||||
|
|
||||||
|
# This cop supports safe autocorrection (--autocorrect).
|
||||||
|
# Configuration parameters: Max, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns.
|
||||||
|
# URISchemes: http, https
|
||||||
|
Layout/LineLength:
|
||||||
|
Exclude:
|
||||||
|
- 'app/models/account.rb'
|
||||||
|
|
||||||
# This cop supports safe autocorrection (--autocorrect).
|
# This cop supports safe autocorrection (--autocorrect).
|
||||||
# Configuration parameters: EnforcedStyle.
|
# Configuration parameters: EnforcedStyle.
|
||||||
# SupportedStyles: require_no_space, require_space
|
# SupportedStyles: require_no_space, require_space
|
||||||
|
@ -112,7 +119,6 @@ Lint/UselessAssignment:
|
||||||
- 'config/initializers/omniauth.rb'
|
- 'config/initializers/omniauth.rb'
|
||||||
- 'db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb'
|
- 'db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb'
|
||||||
- 'db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb'
|
- 'db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb'
|
||||||
- 'spec/controllers/api/v1/bookmarks_controller_spec.rb'
|
|
||||||
- 'spec/controllers/api/v1/favourites_controller_spec.rb'
|
- 'spec/controllers/api/v1/favourites_controller_spec.rb'
|
||||||
- 'spec/controllers/concerns/account_controller_concern_spec.rb'
|
- 'spec/controllers/concerns/account_controller_concern_spec.rb'
|
||||||
- 'spec/helpers/jsonld_helper_spec.rb'
|
- 'spec/helpers/jsonld_helper_spec.rb'
|
||||||
|
@ -129,7 +135,7 @@ Lint/UselessAssignment:
|
||||||
|
|
||||||
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
|
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
|
||||||
Metrics/AbcSize:
|
Metrics/AbcSize:
|
||||||
Max: 150
|
Max: 143
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'app/serializers/initial_state_serializer.rb'
|
- 'app/serializers/initial_state_serializer.rb'
|
||||||
|
|
||||||
|
@ -160,14 +166,6 @@ Naming/VariableNumber:
|
||||||
- 'spec/models/domain_block_spec.rb'
|
- 'spec/models/domain_block_spec.rb'
|
||||||
- 'spec/models/user_spec.rb'
|
- 'spec/models/user_spec.rb'
|
||||||
|
|
||||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
||||||
Performance/UnfreezeString:
|
|
||||||
Exclude:
|
|
||||||
- 'app/lib/rss/builder.rb'
|
|
||||||
- 'app/lib/text_formatter.rb'
|
|
||||||
- 'app/validators/status_length_validator.rb'
|
|
||||||
- 'lib/tasks/mastodon.rake'
|
|
||||||
|
|
||||||
RSpec/AnyInstance:
|
RSpec/AnyInstance:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'spec/controllers/activitypub/inboxes_controller_spec.rb'
|
- 'spec/controllers/activitypub/inboxes_controller_spec.rb'
|
||||||
|
@ -187,41 +185,6 @@ RSpec/AnyInstance:
|
||||||
- 'spec/workers/activitypub/delivery_worker_spec.rb'
|
- 'spec/workers/activitypub/delivery_worker_spec.rb'
|
||||||
- 'spec/workers/web/push_notification_worker_spec.rb'
|
- 'spec/workers/web/push_notification_worker_spec.rb'
|
||||||
|
|
||||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
||||||
RSpec/EmptyExampleGroup:
|
|
||||||
Exclude:
|
|
||||||
- 'spec/helpers/admin/action_logs_helper_spec.rb'
|
|
||||||
- 'spec/models/account_alias_spec.rb'
|
|
||||||
- 'spec/models/account_deletion_request_spec.rb'
|
|
||||||
- 'spec/models/account_moderation_note_spec.rb'
|
|
||||||
- 'spec/models/announcement_mute_spec.rb'
|
|
||||||
- 'spec/models/announcement_reaction_spec.rb'
|
|
||||||
- 'spec/models/announcement_spec.rb'
|
|
||||||
- 'spec/models/backup_spec.rb'
|
|
||||||
- 'spec/models/conversation_mute_spec.rb'
|
|
||||||
- 'spec/models/custom_filter_keyword_spec.rb'
|
|
||||||
- 'spec/models/custom_filter_spec.rb'
|
|
||||||
- 'spec/models/device_spec.rb'
|
|
||||||
- 'spec/models/encrypted_message_spec.rb'
|
|
||||||
- 'spec/models/featured_tag_spec.rb'
|
|
||||||
- 'spec/models/follow_recommendation_suppression_spec.rb'
|
|
||||||
- 'spec/models/list_account_spec.rb'
|
|
||||||
- 'spec/models/list_spec.rb'
|
|
||||||
- 'spec/models/login_activity_spec.rb'
|
|
||||||
- 'spec/models/mute_spec.rb'
|
|
||||||
- 'spec/models/preview_card_spec.rb'
|
|
||||||
- 'spec/models/preview_card_trend_spec.rb'
|
|
||||||
- 'spec/models/relay_spec.rb'
|
|
||||||
- 'spec/models/scheduled_status_spec.rb'
|
|
||||||
- 'spec/models/status_stat_spec.rb'
|
|
||||||
- 'spec/models/status_trend_spec.rb'
|
|
||||||
- 'spec/models/system_key_spec.rb'
|
|
||||||
- 'spec/models/tag_follow_spec.rb'
|
|
||||||
- 'spec/models/unavailable_domain_spec.rb'
|
|
||||||
- 'spec/models/user_invite_request_spec.rb'
|
|
||||||
- 'spec/models/web/setting_spec.rb'
|
|
||||||
- 'spec/services/unmute_service_spec.rb'
|
|
||||||
|
|
||||||
# Configuration parameters: CountAsOne.
|
# Configuration parameters: CountAsOne.
|
||||||
RSpec/ExampleLength:
|
RSpec/ExampleLength:
|
||||||
Max: 22
|
Max: 22
|
||||||
|
@ -354,43 +317,6 @@ Rails/ApplicationController:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'app/controllers/health_controller.rb'
|
- 'app/controllers/health_controller.rb'
|
||||||
|
|
||||||
# Configuration parameters: Database, Include.
|
|
||||||
# SupportedDatabases: mysql, postgresql
|
|
||||||
# Include: db/**/*.rb
|
|
||||||
Rails/BulkChangeTable:
|
|
||||||
Exclude:
|
|
||||||
- 'db/migrate/20160222143943_add_profile_fields_to_accounts.rb'
|
|
||||||
- 'db/migrate/20160223162837_add_metadata_to_statuses.rb'
|
|
||||||
- 'db/migrate/20160305115639_add_devise_to_users.rb'
|
|
||||||
- 'db/migrate/20160314164231_add_owner_to_application.rb'
|
|
||||||
- 'db/migrate/20160926213048_remove_owner_from_application.rb'
|
|
||||||
- 'db/migrate/20161003142332_add_confirmable_to_users.rb'
|
|
||||||
- 'db/migrate/20170112154826_migrate_settings.rb'
|
|
||||||
- 'db/migrate/20170127165745_add_devise_two_factor_to_users.rb'
|
|
||||||
- 'db/migrate/20170322143850_change_primary_key_to_bigint_on_statuses.rb'
|
|
||||||
- 'db/migrate/20170330021336_add_counter_caches.rb'
|
|
||||||
- 'db/migrate/20170425202925_add_oembed_to_preview_cards.rb'
|
|
||||||
- 'db/migrate/20170427011934_re_add_owner_to_application.rb'
|
|
||||||
- 'db/migrate/20170520145338_change_language_filter_to_opt_out.rb'
|
|
||||||
- 'db/migrate/20170624134742_add_description_to_session_activations.rb'
|
|
||||||
- 'db/migrate/20170718211102_add_activitypub_to_accounts.rb'
|
|
||||||
- 'db/migrate/20171006142024_add_uri_to_custom_emojis.rb'
|
|
||||||
- 'db/migrate/20180812123222_change_relays_enabled.rb'
|
|
||||||
- 'db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb'
|
|
||||||
- 'db/migrate/20190805123746_add_capabilities_to_tags.rb'
|
|
||||||
- 'db/migrate/20190807135426_add_comments_to_domain_blocks.rb'
|
|
||||||
- 'db/migrate/20190815225426_add_last_status_at_to_tags.rb'
|
|
||||||
- 'db/migrate/20190901035623_add_max_score_to_tags.rb'
|
|
||||||
- 'db/migrate/20200417125749_add_storage_schema_version.rb'
|
|
||||||
- 'db/migrate/20200608113046_add_sign_in_token_to_users.rb'
|
|
||||||
- 'db/migrate/20211112011713_add_language_to_preview_cards.rb'
|
|
||||||
- 'db/migrate/20211231080958_add_category_to_reports.rb'
|
|
||||||
- 'db/migrate/20220202200743_add_trendable_to_accounts.rb'
|
|
||||||
- 'db/migrate/20220224010024_add_ips_to_email_domain_blocks.rb'
|
|
||||||
- 'db/migrate/20220227041951_add_last_used_at_to_oauth_access_tokens.rb'
|
|
||||||
- 'db/migrate/20220303000827_add_ordered_media_attachment_ids_to_status_edits.rb'
|
|
||||||
- 'db/migrate/20220824164433_add_human_identifier_to_admin_action_logs.rb'
|
|
||||||
|
|
||||||
# Configuration parameters: Include.
|
# Configuration parameters: Include.
|
||||||
# Include: db/**/*.rb
|
# Include: db/**/*.rb
|
||||||
Rails/CreateTableWithTimestamps:
|
Rails/CreateTableWithTimestamps:
|
||||||
|
@ -666,7 +592,7 @@ Style/FetchEnvVar:
|
||||||
- 'app/lib/translation_service.rb'
|
- 'app/lib/translation_service.rb'
|
||||||
- 'config/environments/development.rb'
|
- 'config/environments/development.rb'
|
||||||
- 'config/environments/production.rb'
|
- 'config/environments/production.rb'
|
||||||
- 'config/initializers/2_whitelist_mode.rb'
|
- 'config/initializers/2_limited_federation_mode.rb'
|
||||||
- 'config/initializers/blacklists.rb'
|
- 'config/initializers/blacklists.rb'
|
||||||
- 'config/initializers/cache_buster.rb'
|
- 'config/initializers/cache_buster.rb'
|
||||||
- 'config/initializers/content_security_policy.rb'
|
- 'config/initializers/content_security_policy.rb'
|
||||||
|
@ -929,9 +855,3 @@ Style/WordArray:
|
||||||
- 'config/initializers/cors.rb'
|
- 'config/initializers/cors.rb'
|
||||||
- 'spec/controllers/settings/imports_controller_spec.rb'
|
- 'spec/controllers/settings/imports_controller_spec.rb'
|
||||||
- 'spec/models/form/import_spec.rb'
|
- 'spec/models/form/import_spec.rb'
|
||||||
|
|
||||||
# This cop supports safe autocorrection (--autocorrect).
|
|
||||||
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns.
|
|
||||||
# URISchemes: http, https
|
|
||||||
Layout/LineLength:
|
|
||||||
Max: 701
|
|
||||||
|
|
|
@ -2,6 +2,14 @@
|
||||||
|
|
||||||
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.1.6] - 2023-07-31
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix memory leak in streaming server ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/26228))
|
||||||
|
- Fix wrong filters sometimes applying in streaming ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26159), [ThisIsMissEm](https://github.com/mastodon/mastodon/pull/26213), [renchap](https://github.com/mastodon/mastodon/pull/26233))
|
||||||
|
- Fix incorrect connect timeout in outgoing requests ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26116))
|
||||||
|
|
||||||
## [4.1.5] - 2023-07-21
|
## [4.1.5] - 2023-07-21
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
16
Gemfile
16
Gemfile
|
@ -99,9 +99,6 @@ gem 'rdf-normalize', '~> 0.5'
|
||||||
gem 'private_address_check', '~> 0.5'
|
gem 'private_address_check', '~> 0.5'
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
# RSpec runner for rails
|
|
||||||
gem 'rspec-rails', '~> 6.0'
|
|
||||||
|
|
||||||
# Used to split testing into chunks in CI
|
# Used to split testing into chunks in CI
|
||||||
gem 'rspec_chunked', '~> 0.6'
|
gem 'rspec_chunked', '~> 0.6'
|
||||||
|
|
||||||
|
@ -113,6 +110,10 @@ group :test do
|
||||||
|
|
||||||
# Browser integration testing
|
# Browser integration testing
|
||||||
gem 'capybara', '~> 3.39'
|
gem 'capybara', '~> 3.39'
|
||||||
|
gem 'selenium-webdriver'
|
||||||
|
|
||||||
|
# Used to reset the database between system tests
|
||||||
|
gem 'database_cleaner-active_record'
|
||||||
|
|
||||||
# Used to mock environment variables
|
# Used to mock environment variables
|
||||||
gem 'climate_control', '~> 0.2'
|
gem 'climate_control', '~> 0.2'
|
||||||
|
@ -173,10 +174,19 @@ group :development do
|
||||||
|
|
||||||
# Validate missing i18n keys
|
# Validate missing i18n keys
|
||||||
gem 'i18n-tasks', '~> 1.0', require: false
|
gem 'i18n-tasks', '~> 1.0', require: false
|
||||||
|
end
|
||||||
|
|
||||||
|
group :development, :test do
|
||||||
# Profiling tools
|
# Profiling tools
|
||||||
gem 'memory_profiler', require: false
|
gem 'memory_profiler', require: false
|
||||||
|
gem 'ruby-prof', require: false
|
||||||
gem 'stackprof', require: false
|
gem 'stackprof', require: false
|
||||||
|
gem 'test-prof'
|
||||||
|
end
|
||||||
|
|
||||||
|
group :development, :test do
|
||||||
|
# RSpec runner for rails
|
||||||
|
gem 'rspec-rails', '~> 6.0'
|
||||||
end
|
end
|
||||||
|
|
||||||
group :production do
|
group :production do
|
||||||
|
|
15
Gemfile.lock
15
Gemfile.lock
|
@ -199,6 +199,10 @@ GEM
|
||||||
crass (1.0.6)
|
crass (1.0.6)
|
||||||
css_parser (1.14.0)
|
css_parser (1.14.0)
|
||||||
addressable
|
addressable
|
||||||
|
database_cleaner-active_record (2.1.0)
|
||||||
|
activerecord (>= 5.a)
|
||||||
|
database_cleaner-core (~> 2.0.0)
|
||||||
|
database_cleaner-core (2.0.1)
|
||||||
date (3.3.3)
|
date (3.3.3)
|
||||||
debug_inspector (1.1.0)
|
debug_inspector (1.1.0)
|
||||||
devise (4.9.2)
|
devise (4.9.2)
|
||||||
|
@ -640,6 +644,7 @@ GEM
|
||||||
rubocop (~> 1.33)
|
rubocop (~> 1.33)
|
||||||
rubocop-capybara (~> 2.17)
|
rubocop-capybara (~> 2.17)
|
||||||
rubocop-factory_bot (~> 2.22)
|
rubocop-factory_bot (~> 2.22)
|
||||||
|
ruby-prof (1.6.3)
|
||||||
ruby-progressbar (1.13.0)
|
ruby-progressbar (1.13.0)
|
||||||
ruby-saml (1.15.0)
|
ruby-saml (1.15.0)
|
||||||
nokogiri (>= 1.13.10)
|
nokogiri (>= 1.13.10)
|
||||||
|
@ -656,6 +661,10 @@ GEM
|
||||||
scenic (1.7.0)
|
scenic (1.7.0)
|
||||||
activerecord (>= 4.0.0)
|
activerecord (>= 4.0.0)
|
||||||
railties (>= 4.0.0)
|
railties (>= 4.0.0)
|
||||||
|
selenium-webdriver (4.9.1)
|
||||||
|
rexml (~> 3.2, >= 3.2.5)
|
||||||
|
rubyzip (>= 1.2.2, < 3.0)
|
||||||
|
websocket (~> 1.0)
|
||||||
semantic_range (3.0.0)
|
semantic_range (3.0.0)
|
||||||
sidekiq (6.5.9)
|
sidekiq (6.5.9)
|
||||||
connection_pool (>= 2.2.5, < 3)
|
connection_pool (>= 2.2.5, < 3)
|
||||||
|
@ -710,6 +719,7 @@ GEM
|
||||||
unicode-display_width (>= 1.1.1, < 3)
|
unicode-display_width (>= 1.1.1, < 3)
|
||||||
terrapin (0.6.0)
|
terrapin (0.6.0)
|
||||||
climate_control (>= 0.0.3, < 1.0)
|
climate_control (>= 0.0.3, < 1.0)
|
||||||
|
test-prof (1.2.1)
|
||||||
thor (1.2.2)
|
thor (1.2.2)
|
||||||
tilt (2.2.0)
|
tilt (2.2.0)
|
||||||
timeout (0.4.0)
|
timeout (0.4.0)
|
||||||
|
@ -768,6 +778,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)
|
||||||
|
websocket (1.2.9)
|
||||||
websocket-driver (0.7.5)
|
websocket-driver (0.7.5)
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
websocket-extensions (0.1.5)
|
websocket-extensions (0.1.5)
|
||||||
|
@ -804,6 +815,7 @@ DEPENDENCIES
|
||||||
color_diff (~> 0.1)
|
color_diff (~> 0.1)
|
||||||
concurrent-ruby
|
concurrent-ruby
|
||||||
connection_pool
|
connection_pool
|
||||||
|
database_cleaner-active_record
|
||||||
devise (~> 4.9)
|
devise (~> 4.9)
|
||||||
devise-two-factor (~> 4.1)
|
devise-two-factor (~> 4.1)
|
||||||
devise_pam_authenticatable2 (~> 9.2)
|
devise_pam_authenticatable2 (~> 9.2)
|
||||||
|
@ -881,10 +893,12 @@ DEPENDENCIES
|
||||||
rubocop-performance
|
rubocop-performance
|
||||||
rubocop-rails
|
rubocop-rails
|
||||||
rubocop-rspec
|
rubocop-rspec
|
||||||
|
ruby-prof
|
||||||
ruby-progressbar (~> 1.13)
|
ruby-progressbar (~> 1.13)
|
||||||
rubyzip (~> 2.3)
|
rubyzip (~> 2.3)
|
||||||
sanitize (~> 6.0)
|
sanitize (~> 6.0)
|
||||||
scenic (~> 1.7)
|
scenic (~> 1.7)
|
||||||
|
selenium-webdriver
|
||||||
sidekiq (~> 6.5)
|
sidekiq (~> 6.5)
|
||||||
sidekiq-bulk (~> 0.2.0)
|
sidekiq-bulk (~> 0.2.0)
|
||||||
sidekiq-scheduler (~> 5.0)
|
sidekiq-scheduler (~> 5.0)
|
||||||
|
@ -897,6 +911,7 @@ DEPENDENCIES
|
||||||
stackprof
|
stackprof
|
||||||
stoplight (~> 3.0.1)
|
stoplight (~> 3.0.1)
|
||||||
strong_migrations (~> 0.8)
|
strong_migrations (~> 0.8)
|
||||||
|
test-prof
|
||||||
thor (~> 1.2)
|
thor (~> 1.2)
|
||||||
tty-prompt (~> 0.23)
|
tty-prompt (~> 0.23)
|
||||||
twitter-text (~> 3.1.0)
|
twitter-text (~> 3.1.0)
|
||||||
|
|
|
@ -12,7 +12,7 @@ class AccountsController < ApplicationController
|
||||||
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||||
|
|
||||||
skip_around_action :set_locale, if: -> { [:json, :rss].include?(request.format&.to_sym) }
|
skip_around_action :set_locale, if: -> { [:json, :rss].include?(request.format&.to_sym) }
|
||||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
skip_before_action :require_functional!, unless: :limited_federation_mode?
|
||||||
|
|
||||||
def show
|
def show
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
|
|
|
@ -65,7 +65,7 @@ module Admin
|
||||||
end
|
end
|
||||||
|
|
||||||
def filtered_instances
|
def filtered_instances
|
||||||
InstanceFilter.new(whitelist_mode? ? { allowed: true } : filter_params).results
|
InstanceFilter.new(limited_federation_mode? ? { allowed: true } : filter_params).results
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter_params
|
def filter_params
|
||||||
|
|
|
@ -8,7 +8,7 @@ class Api::BaseController < ApplicationController
|
||||||
include AccessTokenTrackingConcern
|
include AccessTokenTrackingConcern
|
||||||
include ApiCachingConcern
|
include ApiCachingConcern
|
||||||
|
|
||||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
skip_before_action :require_functional!, unless: :limited_federation_mode?
|
||||||
|
|
||||||
before_action :require_authenticated_user!, if: :disallow_unauthenticated_api_access?
|
before_action :require_authenticated_user!, if: :disallow_unauthenticated_api_access?
|
||||||
before_action :require_not_suspended!
|
before_action :require_not_suspended!
|
||||||
|
@ -150,7 +150,7 @@ class Api::BaseController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def disallow_unauthenticated_api_access?
|
def disallow_unauthenticated_api_access?
|
||||||
ENV['DISALLOW_UNAUTHENTICATED_API_ACCESS'] == 'true' || Rails.configuration.x.whitelist_mode
|
ENV['DISALLOW_UNAUTHENTICATED_API_ACCESS'] == 'true' || Rails.configuration.x.limited_federation_mode
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
class Api::V1::Instances::ActivityController < Api::BaseController
|
class Api::V1::Instances::ActivityController < Api::BaseController
|
||||||
before_action :require_enabled_api!
|
before_action :require_enabled_api!
|
||||||
|
|
||||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
|
||||||
|
|
||||||
vary_by ''
|
vary_by ''
|
||||||
|
|
||||||
|
@ -33,6 +33,6 @@ class Api::V1::Instances::ActivityController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def require_enabled_api!
|
def require_enabled_api!
|
||||||
head 404 unless Setting.activity_api_enabled && !whitelist_mode?
|
head 404 unless Setting.activity_api_enabled && !limited_federation_mode?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Instances::DomainBlocksController < Api::BaseController
|
class Api::V1::Instances::DomainBlocksController < Api::BaseController
|
||||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
|
||||||
|
|
||||||
before_action :require_enabled_api!
|
before_action :require_enabled_api!
|
||||||
before_action :set_domain_blocks
|
before_action :set_domain_blocks
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Instances::ExtendedDescriptionsController < Api::BaseController
|
class Api::V1::Instances::ExtendedDescriptionsController < Api::BaseController
|
||||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
|
||||||
skip_around_action :set_locale
|
skip_around_action :set_locale
|
||||||
|
|
||||||
before_action :set_extended_description
|
before_action :set_extended_description
|
||||||
|
@ -10,7 +10,7 @@ class Api::V1::Instances::ExtendedDescriptionsController < Api::BaseController
|
||||||
|
|
||||||
# Override `current_user` to avoid reading session cookies unless in whitelist mode
|
# Override `current_user` to avoid reading session cookies unless in whitelist mode
|
||||||
def current_user
|
def current_user
|
||||||
super if whitelist_mode?
|
super if limited_federation_mode?
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
|
|
@ -3,14 +3,14 @@
|
||||||
class Api::V1::Instances::PeersController < Api::BaseController
|
class Api::V1::Instances::PeersController < Api::BaseController
|
||||||
before_action :require_enabled_api!
|
before_action :require_enabled_api!
|
||||||
|
|
||||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
|
||||||
skip_around_action :set_locale
|
skip_around_action :set_locale
|
||||||
|
|
||||||
vary_by ''
|
vary_by ''
|
||||||
|
|
||||||
# Override `current_user` to avoid reading session cookies unless in whitelist mode
|
# Override `current_user` to avoid reading session cookies unless in whitelist mode
|
||||||
def current_user
|
def current_user
|
||||||
super if whitelist_mode?
|
super if limited_federation_mode?
|
||||||
end
|
end
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
@ -21,6 +21,6 @@ class Api::V1::Instances::PeersController < Api::BaseController
|
||||||
private
|
private
|
||||||
|
|
||||||
def require_enabled_api!
|
def require_enabled_api!
|
||||||
head 404 unless Setting.peers_api_enabled && !whitelist_mode?
|
head 404 unless Setting.peers_api_enabled && !limited_federation_mode?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Instances::PrivacyPoliciesController < Api::BaseController
|
class Api::V1::Instances::PrivacyPoliciesController < Api::BaseController
|
||||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
|
||||||
|
|
||||||
before_action :set_privacy_policy
|
before_action :set_privacy_policy
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Instances::RulesController < Api::BaseController
|
class Api::V1::Instances::RulesController < Api::BaseController
|
||||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
|
||||||
skip_around_action :set_locale
|
skip_around_action :set_locale
|
||||||
|
|
||||||
before_action :set_rules
|
before_action :set_rules
|
||||||
|
@ -10,7 +10,7 @@ class Api::V1::Instances::RulesController < Api::BaseController
|
||||||
|
|
||||||
# Override `current_user` to avoid reading session cookies unless in whitelist mode
|
# Override `current_user` to avoid reading session cookies unless in whitelist mode
|
||||||
def current_user
|
def current_user
|
||||||
super if whitelist_mode?
|
super if limited_federation_mode?
|
||||||
end
|
end
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Instances::TranslationLanguagesController < Api::BaseController
|
class Api::V1::Instances::TranslationLanguagesController < Api::BaseController
|
||||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
|
||||||
|
|
||||||
before_action :set_languages
|
before_action :set_languages
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::InstancesController < Api::BaseController
|
class Api::V1::InstancesController < Api::BaseController
|
||||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
|
||||||
skip_around_action :set_locale
|
skip_around_action :set_locale
|
||||||
|
|
||||||
vary_by ''
|
vary_by ''
|
||||||
|
|
||||||
# Override `current_user` to avoid reading session cookies unless in whitelist mode
|
# Override `current_user` to avoid reading session cookies unless in whitelist mode
|
||||||
def current_user
|
def current_user
|
||||||
super if whitelist_mode?
|
super if limited_federation_mode?
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
|
|
@ -4,7 +4,7 @@ class Api::V1::Peers::SearchController < Api::BaseController
|
||||||
before_action :require_enabled_api!
|
before_action :require_enabled_api!
|
||||||
before_action :set_domains
|
before_action :set_domains
|
||||||
|
|
||||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
|
||||||
skip_around_action :set_locale
|
skip_around_action :set_locale
|
||||||
|
|
||||||
vary_by ''
|
vary_by ''
|
||||||
|
@ -17,7 +17,7 @@ class Api::V1::Peers::SearchController < Api::BaseController
|
||||||
private
|
private
|
||||||
|
|
||||||
def require_enabled_api!
|
def require_enabled_api!
|
||||||
head 404 unless Setting.peers_api_enabled && !whitelist_mode?
|
head 404 unless Setting.peers_api_enabled && !limited_federation_mode?
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_domains
|
def set_domains
|
||||||
|
@ -27,7 +27,7 @@ class Api::V1::Peers::SearchController < Api::BaseController
|
||||||
@domains = InstancesIndex.query(function_score: {
|
@domains = InstancesIndex.query(function_score: {
|
||||||
query: {
|
query: {
|
||||||
prefix: {
|
prefix: {
|
||||||
domain: params[:q],
|
domain: TagManager.instance.normalize_domain(params[:q].strip),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ class ApplicationController < ActionController::Base
|
||||||
helper_method :use_seamless_external_login?
|
helper_method :use_seamless_external_login?
|
||||||
helper_method :omniauth_only?
|
helper_method :omniauth_only?
|
||||||
helper_method :sso_account_settings
|
helper_method :sso_account_settings
|
||||||
helper_method :whitelist_mode?
|
helper_method :limited_federation_mode?
|
||||||
helper_method :body_class_string
|
helper_method :body_class_string
|
||||||
helper_method :skip_csrf_meta_tags?
|
helper_method :skip_csrf_meta_tags?
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ class ApplicationController < ActionController::Base
|
||||||
private
|
private
|
||||||
|
|
||||||
def authorized_fetch_mode?
|
def authorized_fetch_mode?
|
||||||
ENV['AUTHORIZED_FETCH'] == 'true' || Rails.configuration.x.whitelist_mode
|
ENV['AUTHORIZED_FETCH'] == 'true' || Rails.configuration.x.limited_federation_mode
|
||||||
end
|
end
|
||||||
|
|
||||||
def public_fetch_mode?
|
def public_fetch_mode?
|
||||||
|
|
|
@ -4,7 +4,7 @@ module AccountOwnedConcern
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
included do
|
included do
|
||||||
before_action :authenticate_user!, if: -> { whitelist_mode? && request.format != :json }
|
before_action :authenticate_user!, if: -> { limited_federation_mode? && request.format != :json }
|
||||||
before_action :set_account, if: :account_required?
|
before_action :set_account, if: :account_required?
|
||||||
before_action :check_account_approval, if: :account_required?
|
before_action :check_account_approval, if: :account_required?
|
||||||
before_action :check_account_suspension, if: :account_required?
|
before_action :check_account_suspension, if: :account_required?
|
||||||
|
|
|
@ -8,6 +8,6 @@ module ApiCachingConcern
|
||||||
end
|
end
|
||||||
|
|
||||||
def cache_even_if_authenticated!
|
def cache_even_if_authenticated!
|
||||||
expires_in(5.minutes, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless whitelist_mode?
|
expires_in(5.minutes, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless limited_federation_mode?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,7 +10,7 @@ class FollowerAccountsController < ApplicationController
|
||||||
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||||
|
|
||||||
skip_around_action :set_locale, if: -> { request.format == :json }
|
skip_around_action :set_locale, if: -> { request.format == :json }
|
||||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
skip_before_action :require_functional!, unless: :limited_federation_mode?
|
||||||
|
|
||||||
def index
|
def index
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
|
|
|
@ -10,7 +10,7 @@ class FollowingAccountsController < ApplicationController
|
||||||
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||||
|
|
||||||
skip_around_action :set_locale, if: -> { request.format == :json }
|
skip_around_action :set_locale, if: -> { request.format == :json }
|
||||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
skip_before_action :require_functional!, unless: :limited_federation_mode?
|
||||||
|
|
||||||
def index
|
def index
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
|
|
|
@ -9,6 +9,8 @@ class MailSubscriptionsController < ApplicationController
|
||||||
before_action :set_user
|
before_action :set_user
|
||||||
before_action :set_type
|
before_action :set_type
|
||||||
|
|
||||||
|
protect_from_forgery with: :null_session
|
||||||
|
|
||||||
def show; end
|
def show; end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
@ -20,6 +22,7 @@ class MailSubscriptionsController < ApplicationController
|
||||||
|
|
||||||
def set_user
|
def set_user
|
||||||
@user = GlobalID::Locator.locate_signed(params[:token], for: 'unsubscribe')
|
@user = GlobalID::Locator.locate_signed(params[:token], for: 'unsubscribe')
|
||||||
|
not_found unless @user
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_body_classes
|
def set_body_classes
|
||||||
|
@ -35,7 +38,7 @@ class MailSubscriptionsController < ApplicationController
|
||||||
when 'follow', 'reblog', 'favourite', 'mention', 'follow_request'
|
when 'follow', 'reblog', 'favourite', 'mention', 'follow_request'
|
||||||
"notification_emails.#{params[:type]}"
|
"notification_emails.#{params[:type]}"
|
||||||
else
|
else
|
||||||
raise ArgumentError
|
not_found
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
class MediaController < ApplicationController
|
class MediaController < ApplicationController
|
||||||
include Authorization
|
include Authorization
|
||||||
|
|
||||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
skip_before_action :require_functional!, unless: :limited_federation_mode?
|
||||||
|
|
||||||
before_action :authenticate_user!, if: :whitelist_mode?
|
before_action :authenticate_user!, if: :limited_federation_mode?
|
||||||
before_action :set_media_attachment
|
before_action :set_media_attachment
|
||||||
before_action :verify_permitted_status!
|
before_action :verify_permitted_status!
|
||||||
before_action :check_playable, only: :player
|
before_action :check_playable, only: :player
|
||||||
|
|
|
@ -8,7 +8,7 @@ class MediaProxyController < ApplicationController
|
||||||
|
|
||||||
skip_before_action :require_functional!
|
skip_before_action :require_functional!
|
||||||
|
|
||||||
before_action :authenticate_user!, if: :whitelist_mode?
|
before_action :authenticate_user!, if: :limited_federation_mode?
|
||||||
|
|
||||||
rescue_from ActiveRecord::RecordInvalid, with: :not_found
|
rescue_from ActiveRecord::RecordInvalid, with: :not_found
|
||||||
rescue_from Mastodon::UnexpectedResponseError, with: :not_found
|
rescue_from Mastodon::UnexpectedResponseError, with: :not_found
|
||||||
|
|
|
@ -17,7 +17,7 @@ class StatusesController < ApplicationController
|
||||||
after_action :set_link_headers
|
after_action :set_link_headers
|
||||||
|
|
||||||
skip_around_action :set_locale, if: -> { request.format == :json }
|
skip_around_action :set_locale, if: -> { request.format == :json }
|
||||||
skip_before_action :require_functional!, only: [:show, :embed], unless: :whitelist_mode?
|
skip_before_action :require_functional!, only: [:show, :embed], unless: :limited_federation_mode?
|
||||||
|
|
||||||
content_security_policy only: :embed do |policy|
|
content_security_policy only: :embed do |policy|
|
||||||
policy.frame_ancestors(false)
|
policy.frame_ancestors(false)
|
||||||
|
|
|
@ -10,13 +10,13 @@ class TagsController < ApplicationController
|
||||||
vary_by -> { public_fetch_mode? ? 'Accept, Accept-Language, Cookie' : 'Accept, Accept-Language, Cookie, Signature' }
|
vary_by -> { public_fetch_mode? ? 'Accept, Accept-Language, Cookie' : 'Accept, Accept-Language, Cookie, Signature' }
|
||||||
|
|
||||||
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||||
before_action :authenticate_user!, if: :whitelist_mode?
|
before_action :authenticate_user!, if: :limited_federation_mode?
|
||||||
before_action :set_local
|
before_action :set_local
|
||||||
before_action :set_tag
|
before_action :set_tag
|
||||||
before_action :set_statuses, if: -> { request.format == :rss }
|
before_action :set_statuses, if: -> { request.format == :rss }
|
||||||
before_action :set_instance_presenter
|
before_action :set_instance_presenter
|
||||||
|
|
||||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
skip_before_action :require_functional!, unless: :limited_federation_mode?
|
||||||
|
|
||||||
def show
|
def show
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
|
|
|
@ -10,14 +10,14 @@ module DomainControlHelper
|
||||||
uri_or_domain
|
uri_or_domain
|
||||||
end
|
end
|
||||||
|
|
||||||
if whitelist_mode?
|
if limited_federation_mode?
|
||||||
!DomainAllow.allowed?(domain)
|
!DomainAllow.allowed?(domain)
|
||||||
else
|
else
|
||||||
DomainBlock.blocked?(domain)
|
DomainBlock.blocked?(domain)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def whitelist_mode?
|
def limited_federation_mode?
|
||||||
Rails.configuration.x.whitelist_mode
|
Rails.configuration.x.limited_federation_mode
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -204,7 +204,17 @@ module LanguagesHelper
|
||||||
zgh: ['Standard Moroccan Tamazight', 'ⵜⴰⵎⴰⵣⵉⵖⵜ'].freeze,
|
zgh: ['Standard Moroccan Tamazight', 'ⵜⴰⵎⴰⵣⵉⵖⵜ'].freeze,
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
SUPPORTED_LOCALES = {}.merge(ISO_639_1).merge(ISO_639_3).freeze
|
# e.g. For Chinese, which is not a language,
|
||||||
|
# but a language family in spite of sharing the main locale code
|
||||||
|
# We need to be able to filter these
|
||||||
|
ISO_639_1_REGIONAL = {
|
||||||
|
'zh-CN': ['Chinese (China)', '简体中文'].freeze,
|
||||||
|
'zh-HK': ['Chinese (Hong Kong)', '繁體中文(香港)'].freeze,
|
||||||
|
'zh-TW': ['Chinese (Taiwan)', '繁體中文(臺灣)'].freeze,
|
||||||
|
'zh-YUE': ['Cantonese', '廣東話'].freeze,
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
SUPPORTED_LOCALES = {}.merge(ISO_639_1).merge(ISO_639_1_REGIONAL).merge(ISO_639_3).freeze
|
||||||
|
|
||||||
# For ISO-639-1 and ISO-639-3 language codes, we have their official
|
# For ISO-639-1 and ISO-639-3 language codes, we have their official
|
||||||
# names, but for some translations, we need the names of the
|
# names, but for some translations, we need the names of the
|
||||||
|
@ -217,9 +227,6 @@ module LanguagesHelper
|
||||||
'pt-BR': 'Português (Brasil)',
|
'pt-BR': 'Português (Brasil)',
|
||||||
'pt-PT': 'Português (Portugal)',
|
'pt-PT': 'Português (Portugal)',
|
||||||
'sr-Latn': 'Srpski (latinica)',
|
'sr-Latn': 'Srpski (latinica)',
|
||||||
'zh-CN': '简体中文',
|
|
||||||
'zh-HK': '繁體中文(香港)',
|
|
||||||
'zh-TW': '繁體中文(臺灣)',
|
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
def native_locale_name(locale)
|
def native_locale_name(locale)
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import { ReactComponent as GroupsIcon } from '@material-design-icons/svg/outlined/group.svg';
|
||||||
|
import { ReactComponent as PersonIcon } from '@material-design-icons/svg/outlined/person.svg';
|
||||||
|
import { ReactComponent as SmartToyIcon } from '@material-design-icons/svg/outlined/smart_toy.svg';
|
||||||
|
|
||||||
|
|
||||||
|
export const Badge = ({ icon, label, domain }) => (
|
||||||
|
<div className='account-role'>
|
||||||
|
{icon}
|
||||||
|
{label}
|
||||||
|
{domain && <span className='account-role__domain'>{domain}</span>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
Badge.propTypes = {
|
||||||
|
icon: PropTypes.node,
|
||||||
|
label: PropTypes.node,
|
||||||
|
domain: PropTypes.node,
|
||||||
|
};
|
||||||
|
|
||||||
|
Badge.defaultProps = {
|
||||||
|
icon: <PersonIcon />,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GroupBadge = () => (
|
||||||
|
<Badge icon={<GroupsIcon />} label={<FormattedMessage id='account.badges.group' defaultMessage='Group' />} />
|
||||||
|
);
|
||||||
|
|
||||||
|
export const AutomatedBadge = () => (
|
||||||
|
<Badge icon={<SmartToyIcon />} label={<FormattedMessage id='account.badges.bot' defaultMessage='Automated' />} />
|
||||||
|
);
|
|
@ -10,6 +10,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
|
||||||
import { Avatar } from 'mastodon/components/avatar';
|
import { Avatar } from 'mastodon/components/avatar';
|
||||||
|
import { Badge, AutomatedBadge, GroupBadge } from 'mastodon/components/badge';
|
||||||
import Button from 'mastodon/components/button';
|
import Button from 'mastodon/components/button';
|
||||||
import { FollowersCounter, FollowingCounter, StatusesCounter } from 'mastodon/components/counters';
|
import { FollowersCounter, FollowingCounter, StatusesCounter } from 'mastodon/components/counters';
|
||||||
import { Icon } from 'mastodon/components/icon';
|
import { Icon } from 'mastodon/components/icon';
|
||||||
|
@ -373,28 +374,13 @@ class Header extends ImmutablePureComponent {
|
||||||
const badges = [];
|
const badges = [];
|
||||||
|
|
||||||
if (account.get('bot')) {
|
if (account.get('bot')) {
|
||||||
badges.push(
|
badges.push(<AutomatedBadge key='bot-badge' />);
|
||||||
<div key='bot-badge' className='account-role bot'>
|
|
||||||
<Icon id='cogs' /> { ' ' }
|
|
||||||
<FormattedMessage id='account.badges.bot' defaultMessage='Automated' />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else if (account.get('group')) {
|
} else if (account.get('group')) {
|
||||||
badges.push(
|
badges.push(<GroupBadge key='group-badge' />);
|
||||||
<div key='group-badge' className='account-role group'>
|
|
||||||
<Icon id='users' /> { ' ' }
|
|
||||||
<FormattedMessage id='account.badges.group' defaultMessage='Group' />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
account.get('roles', []).forEach((role) => {
|
account.get('roles', []).forEach((role) => {
|
||||||
badges.push(
|
badges.push(<Badge key={`role-badge-${role.get('id')}`} label={<span>{role.get('name')}</span>} domain={domain} />);
|
||||||
<div key={`role-badge-${role.get('id')}`} className={`account-role user-role-${account.getIn(['roles', 0, 'id'])}`}>
|
|
||||||
<Icon id='circle' /> { ' ' }
|
|
||||||
<span>{role.get('name')} ({domain})</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -250,6 +250,9 @@ class LoginForm extends React.PureComponent {
|
||||||
onFocus={this.handleFocus}
|
onFocus={this.handleFocus}
|
||||||
onBlur={this.handleBlur}
|
onBlur={this.handleBlur}
|
||||||
onKeyDown={this.handleKeyDown}
|
onKeyDown={this.handleKeyDown}
|
||||||
|
autocomplete='off'
|
||||||
|
autocapitalize='off'
|
||||||
|
spellcheck='false'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button onClick={this.handleSubmit} disabled={isSubmitting}><FormattedMessage id='interaction_modal.login.action' defaultMessage='Take me home' /></Button>
|
<Button onClick={this.handleSubmit} disabled={isSubmitting}><FormattedMessage id='interaction_modal.login.action' defaultMessage='Take me home' /></Button>
|
||||||
|
|
|
@ -29,7 +29,7 @@ const mapStateToProps = (state, { columnId }) => {
|
||||||
const index = columns.findIndex(c => c.get('uuid') === uuid);
|
const index = columns.findIndex(c => c.get('uuid') === uuid);
|
||||||
const onlyMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'public', 'other', 'onlyMedia']);
|
const onlyMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'public', 'other', 'onlyMedia']);
|
||||||
const onlyRemote = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyRemote']) : state.getIn(['settings', 'public', 'other', 'onlyRemote']);
|
const onlyRemote = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyRemote']) : state.getIn(['settings', 'public', 'other', 'onlyRemote']);
|
||||||
const timelineState = state.getIn(['timelines', `public${onlyMedia ? ':media' : ''}`]);
|
const timelineState = state.getIn(['timelines', `public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hasUnread: !!timelineState && timelineState.get('unread') > 0,
|
hasUnread: !!timelineState && timelineState.get('unread') > 0,
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { PureComponent } from 'react';
|
||||||
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import classnames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import Immutable from 'immutable';
|
import Immutable from 'immutable';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
@ -71,6 +71,7 @@ export default class Card extends PureComponent {
|
||||||
if (!Immutable.is(this.props.card, nextProps.card)) {
|
if (!Immutable.is(this.props.card, nextProps.card)) {
|
||||||
this.setState({ embedded: false, previewLoaded: false });
|
this.setState({ embedded: false, previewLoaded: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.sensitive !== nextProps.sensitive) {
|
if (this.props.sensitive !== nextProps.sensitive) {
|
||||||
this.setState({ revealed: !nextProps.sensitive });
|
this.setState({ revealed: !nextProps.sensitive });
|
||||||
}
|
}
|
||||||
|
@ -84,35 +85,8 @@ export default class Card extends PureComponent {
|
||||||
window.removeEventListener('resize', this.handleResize);
|
window.removeEventListener('resize', this.handleResize);
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePhotoClick = () => {
|
|
||||||
const { card, onOpenMedia } = this.props;
|
|
||||||
|
|
||||||
onOpenMedia(
|
|
||||||
Immutable.fromJS([
|
|
||||||
{
|
|
||||||
type: 'image',
|
|
||||||
url: card.get('embed_url'),
|
|
||||||
description: card.get('title'),
|
|
||||||
meta: {
|
|
||||||
original: {
|
|
||||||
width: card.get('width'),
|
|
||||||
height: card.get('height'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleEmbedClick = () => {
|
handleEmbedClick = () => {
|
||||||
const { card } = this.props;
|
|
||||||
|
|
||||||
if (card.get('type') === 'photo') {
|
|
||||||
this.handlePhotoClick();
|
|
||||||
} else {
|
|
||||||
this.setState({ embedded: true });
|
this.setState({ embedded: true });
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
setRef = c => {
|
setRef = c => {
|
||||||
|
@ -138,7 +112,7 @@ export default class Card extends PureComponent {
|
||||||
ref={this.setRef}
|
ref={this.setRef}
|
||||||
className='status-card__image status-card-video'
|
className='status-card__image status-card-video'
|
||||||
dangerouslySetInnerHTML={content}
|
dangerouslySetInnerHTML={content}
|
||||||
style={{ aspectRatio: `${card.get('width')} / ${card.get('height')}` }}
|
style={{ aspectRatio: '16 / 9' }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -152,8 +126,9 @@ export default class Card extends PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
const provider = card.get('provider_name').length === 0 ? decodeIDNA(getHostname(card.get('url'))) : card.get('provider_name');
|
const provider = card.get('provider_name').length === 0 ? decodeIDNA(getHostname(card.get('url'))) : card.get('provider_name');
|
||||||
const interactive = card.get('type') !== 'link';
|
const interactive = card.get('type') === 'video';
|
||||||
const language = card.get('language') || '';
|
const language = card.get('language') || '';
|
||||||
|
const largeImage = (card.get('image')?.length > 0 && card.get('width') > card.get('height')) || interactive;
|
||||||
|
|
||||||
const description = (
|
const description = (
|
||||||
<div className='status-card__content'>
|
<div className='status-card__content'>
|
||||||
|
@ -161,21 +136,30 @@ export default class Card extends PureComponent {
|
||||||
<span lang={language}>{provider}</span>
|
<span lang={language}>{provider}</span>
|
||||||
{card.get('published_at') && <> · <RelativeTimestamp timestamp={card.get('published_at')} /></>}
|
{card.get('published_at') && <> · <RelativeTimestamp timestamp={card.get('published_at')} /></>}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<strong className='status-card__title' title={card.get('title')} lang={language}>{card.get('title')}</strong>
|
<strong className='status-card__title' title={card.get('title')} lang={language}>{card.get('title')}</strong>
|
||||||
{card.get('author_name').length > 0 && <span className='status-card__author'><FormattedMessage id='link_preview.author' defaultMessage='By {name}' values={{ name: <strong>{card.get('author_name')}</strong> }} /></span>}
|
|
||||||
|
{card.get('author_name').length > 0 ? <span className='status-card__author'><FormattedMessage id='link_preview.author' defaultMessage='By {name}' values={{ name: <strong>{card.get('author_name')}</strong> }} /></span> : <span className='status-card__description'>{card.get('description')}</span>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const thumbnailStyle = {
|
const thumbnailStyle = {
|
||||||
visibility: revealed ? null : 'hidden',
|
visibility: revealed ? null : 'hidden',
|
||||||
aspectRatio: `${card.get('width')} / ${card.get('height')}`
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (largeImage && card.get('type') === 'video') {
|
||||||
|
thumbnailStyle.aspectRatio = `16 / 9`;
|
||||||
|
} else if (largeImage) {
|
||||||
|
thumbnailStyle.aspectRatio = '1.91 / 1';
|
||||||
|
} else {
|
||||||
|
thumbnailStyle.aspectRatio = 1;
|
||||||
|
}
|
||||||
|
|
||||||
let embed;
|
let embed;
|
||||||
|
|
||||||
let canvas = (
|
let canvas = (
|
||||||
<Blurhash
|
<Blurhash
|
||||||
className={classnames('status-card__image-preview', {
|
className={classNames('status-card__image-preview', {
|
||||||
'status-card__image-preview--hidden': revealed && this.state.previewLoaded,
|
'status-card__image-preview--hidden': revealed && this.state.previewLoaded,
|
||||||
})}
|
})}
|
||||||
hash={card.get('blurhash')}
|
hash={card.get('blurhash')}
|
||||||
|
@ -195,7 +179,7 @@ export default class Card extends PureComponent {
|
||||||
);
|
);
|
||||||
|
|
||||||
spoilerButton = (
|
spoilerButton = (
|
||||||
<div className={classnames('spoiler-button', { 'spoiler-button--minified': revealed })}>
|
<div className={classNames('spoiler-button', { 'spoiler-button--minified': revealed })}>
|
||||||
{spoilerButton}
|
{spoilerButton}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -204,33 +188,25 @@ export default class Card extends PureComponent {
|
||||||
if (embedded) {
|
if (embedded) {
|
||||||
embed = this.renderVideo();
|
embed = this.renderVideo();
|
||||||
} else {
|
} else {
|
||||||
let iconVariant = 'play';
|
|
||||||
|
|
||||||
if (card.get('type') === 'photo') {
|
|
||||||
iconVariant = 'search-plus';
|
|
||||||
}
|
|
||||||
|
|
||||||
embed = (
|
embed = (
|
||||||
<div className='status-card__image'>
|
<div className='status-card__image'>
|
||||||
{canvas}
|
{canvas}
|
||||||
{thumbnail}
|
{thumbnail}
|
||||||
|
|
||||||
{revealed && (
|
{revealed ? (
|
||||||
<div className='status-card__actions'>
|
<div className='status-card__actions' onClick={this.handleEmbedClick} role='none'>
|
||||||
<div>
|
<div>
|
||||||
<button type='button' onClick={this.handleEmbedClick}><Icon id={iconVariant} /></button>
|
<button type='button' onClick={this.handleEmbedClick}><Icon id='play' /></button>
|
||||||
<a href={card.get('url')} target='_blank' rel='noopener noreferrer'><Icon id='external-link' /></a>
|
<a href={card.get('url')} target='_blank' rel='noopener noreferrer'><Icon id='external-link' /></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
) : spoilerButton}
|
||||||
|
|
||||||
{!revealed && spoilerButton}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='status-card' ref={this.setRef} onClick={revealed ? null : this.handleReveal} role={revealed ? 'button' : null}>
|
<div className={classNames('status-card', { expanded: largeImage })} ref={this.setRef} onClick={revealed ? null : this.handleReveal} role={revealed ? 'button' : null}>
|
||||||
{embed}
|
{embed}
|
||||||
<a href={card.get('url')} target='_blank' rel='noopener noreferrer'>{description}</a>
|
<a href={card.get('url')} target='_blank' rel='noopener noreferrer'>{description}</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -244,14 +220,14 @@ export default class Card extends PureComponent {
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
embed = (
|
embed = (
|
||||||
<div className='status-card__image' style={{ aspectRatio: '1.9 / 1' }}>
|
<div className='status-card__image'>
|
||||||
<Icon id='file-text' />
|
<Icon id='file-text' />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a href={card.get('url')} className='status-card' target='_blank' rel='noopener noreferrer' ref={this.setRef}>
|
<a href={card.get('url')} className={classNames('status-card', { expanded: largeImage })} target='_blank' rel='noopener noreferrer' ref={this.setRef}>
|
||||||
{embed}
|
{embed}
|
||||||
{description}
|
{description}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -187,7 +187,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.account-role,
|
|
||||||
.information-badge,
|
.information-badge,
|
||||||
.simple_form .recommended,
|
.simple_form .recommended,
|
||||||
.simple_form .not_recommended {
|
.simple_form .not_recommended {
|
||||||
|
@ -212,10 +211,30 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.account-role {
|
.account-role {
|
||||||
|
display: inline-flex;
|
||||||
|
padding: 4px;
|
||||||
|
padding-inline-end: 8px;
|
||||||
border: 1px solid $highlight-text-color;
|
border: 1px solid $highlight-text-color;
|
||||||
|
color: $highlight-text-color;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
line-height: 16px;
|
||||||
|
gap: 4px;
|
||||||
|
border-radius: 6px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
.fa {
|
svg {
|
||||||
color: var(--user-role-accent, $highlight-text-color);
|
width: auto;
|
||||||
|
height: 15px;
|
||||||
|
opacity: 0.85;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__domain {
|
||||||
|
font-weight: 400;
|
||||||
|
opacity: 0.75;
|
||||||
|
letter-spacing: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -164,7 +164,7 @@ body {
|
||||||
a {
|
a {
|
||||||
&:focus {
|
&:focus {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
outline: $ui-button-icon-focus-outline;
|
outline: $ui-button-focus-outline;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus:not(:focus-visible) {
|
&:focus:not(:focus-visible) {
|
||||||
|
|
|
@ -3283,6 +3283,8 @@ $ui-header-height: 55px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
border: 0;
|
||||||
|
border-left: 4px solid transparent;
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus,
|
&:focus,
|
||||||
|
@ -3294,6 +3296,11 @@ $ui-header-height: 55px;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
border-color: $ui-button-focus-outline-color;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&--transparent {
|
&--transparent {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: $ui-secondary-color;
|
color: $ui-secondary-color;
|
||||||
|
@ -3510,13 +3517,16 @@ button.icon-button.active i.fa-retweet {
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-card {
|
.status-card {
|
||||||
display: block;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: $darker-text-color;
|
color: $darker-text-color;
|
||||||
margin-top: 14px;
|
margin-top: 14px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
border: 1px solid lighten($ui-base-color, 8%);
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
&__actions {
|
&__actions {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
@ -3527,11 +3537,13 @@ button.icon-button.active i.fa-retweet {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
& > div {
|
& > div {
|
||||||
background: rgba($base-shadow-color, 0.6);
|
background: rgba($base-shadow-color, 0.6);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 12px 9px;
|
padding: 12px 9px;
|
||||||
|
backdrop-filter: blur(10px) saturate(180%) contrast(75%) brightness(70%);
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@ -3572,7 +3584,8 @@ a.status-card {
|
||||||
&:active {
|
&:active {
|
||||||
.status-card__title,
|
.status-card__title,
|
||||||
.status-card__host,
|
.status-card__host,
|
||||||
.status-card__author {
|
.status-card__author,
|
||||||
|
.status-card__description {
|
||||||
color: $highlight-text-color;
|
color: $highlight-text-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3587,7 +3600,8 @@ a.status-card {
|
||||||
&:active {
|
&:active {
|
||||||
.status-card__title,
|
.status-card__title,
|
||||||
.status-card__host,
|
.status-card__host,
|
||||||
.status-card__author {
|
.status-card__author,
|
||||||
|
.status-card__description {
|
||||||
color: $highlight-text-color;
|
color: $highlight-text-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3620,19 +3634,32 @@ a.status-card {
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
color: $primary-text-color;
|
color: $primary-text-color;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-card.expanded .status-card__title {
|
||||||
|
white-space: normal;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-card__content {
|
.status-card__content {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 15px 0;
|
padding: 15px;
|
||||||
padding-bottom: 0;
|
box-sizing: border-box;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-card__host {
|
.status-card__host {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-card__author {
|
.status-card__author {
|
||||||
|
@ -3640,17 +3667,30 @@ a.status-card {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: $primary-text-color;
|
color: $primary-text-color;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
strong {
|
strong {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status-card__description {
|
||||||
|
display: block;
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
.status-card__image {
|
.status-card__image {
|
||||||
width: 100%;
|
flex: 0 0 auto;
|
||||||
|
width: 120px;
|
||||||
|
aspect-ratio: 1;
|
||||||
background: lighten($ui-base-color, 8%);
|
background: lighten($ui-base-color, 8%);
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 8px;
|
|
||||||
|
|
||||||
& > .fa {
|
& > .fa {
|
||||||
font-size: 21px;
|
font-size: 21px;
|
||||||
|
@ -3663,7 +3703,6 @@ a.status-card {
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-card__image-image {
|
.status-card__image-image {
|
||||||
border-radius: 8px;
|
|
||||||
display: block;
|
display: block;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -3674,7 +3713,6 @@ a.status-card {
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-card__image-preview {
|
.status-card__image-preview {
|
||||||
border-radius: 8px;
|
|
||||||
display: block;
|
display: block;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -3691,6 +3729,37 @@ a.status-card {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status-card.expanded {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-card.expanded .status-card__image {
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-card__image,
|
||||||
|
.status-card__image-image,
|
||||||
|
.status-card__image-preview {
|
||||||
|
border-start-start-radius: 8px;
|
||||||
|
border-start-end-radius: 0;
|
||||||
|
border-end-end-radius: 0;
|
||||||
|
border-end-start-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-card.expanded .status-card__image,
|
||||||
|
.status-card.expanded .status-card__image-image,
|
||||||
|
.status-card.expanded .status-card__image-preview {
|
||||||
|
border-start-end-radius: 8px;
|
||||||
|
border-end-end-radius: 0;
|
||||||
|
border-end-start-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-card.expanded > a {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.load-more {
|
.load-more {
|
||||||
display: block;
|
display: block;
|
||||||
color: $dark-text-color;
|
color: $dark-text-color;
|
||||||
|
@ -3896,7 +3965,7 @@ a.status-card {
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus-visible {
|
&:focus-visible {
|
||||||
outline: $ui-button-icon-focus-outline;
|
outline: $ui-button-focus-outline;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
|
@ -4902,7 +4971,7 @@ a.status-card {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: $ui-base-color;
|
background: $ui-base-color;
|
||||||
border-radius: 0 0 4px 4px;
|
border-radius: 0 0 4px 4px;
|
||||||
box-shadow: 4px 4px 6px rgba($base-shadow-color, 0.4);
|
box-shadow: var(--dropdown-shadow);
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
padding: 15px 5px;
|
padding: 15px 5px;
|
||||||
|
@ -8218,7 +8287,7 @@ noscript {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 120px;
|
width: 120px;
|
||||||
height: 120px;
|
aspect-ratio: 1;
|
||||||
|
|
||||||
.skeleton {
|
.skeleton {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -43,6 +43,8 @@ $ui-highlight-color: $classic-highlight-color !default;
|
||||||
$ui-button-color: $white !default;
|
$ui-button-color: $white !default;
|
||||||
$ui-button-background-color: $blurple-500 !default;
|
$ui-button-background-color: $blurple-500 !default;
|
||||||
$ui-button-focus-background-color: $blurple-600 !default;
|
$ui-button-focus-background-color: $blurple-600 !default;
|
||||||
|
$ui-button-focus-outline-color: $blurple-400 !default;
|
||||||
|
$ui-button-focus-outline: solid 2px $ui-button-focus-outline-color !default;
|
||||||
|
|
||||||
$ui-button-secondary-color: $grey-100 !default;
|
$ui-button-secondary-color: $grey-100 !default;
|
||||||
$ui-button-secondary-border-color: $grey-100 !default;
|
$ui-button-secondary-border-color: $grey-100 !default;
|
||||||
|
@ -57,7 +59,7 @@ $ui-button-tertiary-focus-color: $white !default;
|
||||||
$ui-button-destructive-background-color: $red-500 !default;
|
$ui-button-destructive-background-color: $red-500 !default;
|
||||||
$ui-button-destructive-focus-background-color: $red-600 !default;
|
$ui-button-destructive-focus-background-color: $red-600 !default;
|
||||||
|
|
||||||
$ui-button-icon-focus-outline: solid 2px $blurple-400 !default;
|
$ui-button-icon-focus-outline: $ui-button-focus-outline !default;
|
||||||
$ui-button-icon-hover-background-color: rgba(140, 141, 255, 40%) !default;
|
$ui-button-icon-hover-background-color: rgba(140, 141, 255, 40%) !default;
|
||||||
|
|
||||||
// Variables for texts
|
// Variables for texts
|
||||||
|
|
|
@ -45,8 +45,11 @@ class Importer::BaseImporter
|
||||||
# Remove documents from the index that no longer exist in the database
|
# Remove documents from the index that no longer exist in the database
|
||||||
def clean_up!
|
def clean_up!
|
||||||
index.scroll_batches do |documents|
|
index.scroll_batches do |documents|
|
||||||
|
primary_key = index.adapter.target.primary_key
|
||||||
|
raise ActiveRecord::UnknownPrimaryKey, index.adapter.target if primary_key.nil?
|
||||||
|
|
||||||
ids = documents.pluck('_id')
|
ids = documents.pluck('_id')
|
||||||
existence_map = index.adapter.target.where(id: ids).pluck(:id).each_with_object({}) { |id, map| map[id.to_s] = true }
|
existence_map = index.adapter.target.where(primary_key => ids).pluck(primary_key).each_with_object({}) { |id, map| map[id.to_s] = true }
|
||||||
tmp = ids.reject { |id| existence_map[id] }
|
tmp = ids.reject { |id| existence_map[id] }
|
||||||
|
|
||||||
next if tmp.empty?
|
next if tmp.empty?
|
||||||
|
|
|
@ -68,13 +68,26 @@ class Request
|
||||||
# about 15s in total
|
# about 15s in total
|
||||||
TIMEOUT = { connect_timeout: 5, read_timeout: 10, write_timeout: 10, read_deadline: 30 }.freeze
|
TIMEOUT = { connect_timeout: 5, read_timeout: 10, write_timeout: 10, read_deadline: 30 }.freeze
|
||||||
|
|
||||||
|
# Workaround for overly-eager decoding of percent-encoded characters in Addressable::URI#normalized_path
|
||||||
|
# https://github.com/sporkmonger/addressable/issues/366
|
||||||
|
URI_NORMALIZER = lambda do |uri|
|
||||||
|
uri = HTTP::URI.parse(uri)
|
||||||
|
|
||||||
|
HTTP::URI.new(
|
||||||
|
scheme: uri.normalized_scheme,
|
||||||
|
authority: uri.normalized_authority,
|
||||||
|
path: Addressable::URI.normalize_path(encode_non_ascii(uri.path)).presence || '/',
|
||||||
|
query: encode_non_ascii(uri.query)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
include RoutingHelper
|
include RoutingHelper
|
||||||
|
|
||||||
def initialize(verb, url, **options)
|
def initialize(verb, url, **options)
|
||||||
raise ArgumentError if url.blank?
|
raise ArgumentError if url.blank?
|
||||||
|
|
||||||
@verb = verb
|
@verb = verb
|
||||||
@url = Addressable::URI.parse(url).normalize
|
@url = URI_NORMALIZER.call(url)
|
||||||
@http_client = options.delete(:http_client)
|
@http_client = options.delete(:http_client)
|
||||||
@allow_local = options.delete(:allow_local)
|
@allow_local = options.delete(:allow_local)
|
||||||
@options = options.merge(socket_class: use_proxy? || @allow_local ? ProxySocket : Socket)
|
@options = options.merge(socket_class: use_proxy? || @allow_local ? ProxySocket : Socket)
|
||||||
|
@ -138,8 +151,14 @@ class Request
|
||||||
%w(http https).include?(parsed_url.scheme) && parsed_url.host.present?
|
%w(http https).include?(parsed_url.scheme) && parsed_url.host.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
NON_ASCII_PATTERN = /[^\x00-\x7F]+/
|
||||||
|
|
||||||
|
def encode_non_ascii(str)
|
||||||
|
str&.gsub(NON_ASCII_PATTERN) { |substr| CGI.escape(substr.encode(Encoding::UTF_8)) }
|
||||||
|
end
|
||||||
|
|
||||||
def http_client
|
def http_client
|
||||||
HTTP.use(:auto_inflate).follow(max_hops: 3)
|
HTTP.use(:auto_inflate).use(normalize_uri: { normalizer: URI_NORMALIZER }).follow(max_hops: 3)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -14,13 +14,14 @@ class RSS::Builder
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_xml
|
def to_xml
|
||||||
('<?xml version="1.0" encoding="UTF-8"?>'.dup << Ox.dump(wrap_in_document, effort: :tolerant)).force_encoding('UTF-8')
|
Ox.dump(wrap_in_document, effort: :tolerant).force_encoding('UTF-8')
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def wrap_in_document
|
def wrap_in_document
|
||||||
Ox::Document.new(version: '1.0').tap do |document|
|
Ox::Document.new(version: '1.0').tap do |document|
|
||||||
|
document << xml_instruct
|
||||||
document << Ox::Element.new('rss').tap do |rss|
|
document << Ox::Element.new('rss').tap do |rss|
|
||||||
rss['version'] = '2.0'
|
rss['version'] = '2.0'
|
||||||
rss['xmlns:webfeeds'] = 'http://webfeeds.org/rss/1.0'
|
rss['xmlns:webfeeds'] = 'http://webfeeds.org/rss/1.0'
|
||||||
|
@ -30,4 +31,11 @@ class RSS::Builder
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def xml_instruct
|
||||||
|
Ox::Instruct.new(:xml).tap do |instruct|
|
||||||
|
instruct[:version] = '1.0'
|
||||||
|
instruct[:encoding] = 'UTF-8'
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -75,7 +75,7 @@ class TextFormatter
|
||||||
entity[:indices].first
|
entity[:indices].first
|
||||||
end
|
end
|
||||||
|
|
||||||
result = ''.dup
|
result = +''
|
||||||
|
|
||||||
last_index = entities.reduce(0) do |index, entity|
|
last_index = entities.reduce(0) do |index, entity|
|
||||||
indices = entity[:indices]
|
indices = entity[:indices]
|
||||||
|
|
|
@ -8,6 +8,7 @@ class NotificationMailer < ApplicationMailer
|
||||||
before_action :process_params
|
before_action :process_params
|
||||||
before_action :set_status, only: [:mention, :favourite, :reblog]
|
before_action :set_status, only: [:mention, :favourite, :reblog]
|
||||||
before_action :set_account, only: [:follow, :favourite, :reblog, :follow_request]
|
before_action :set_account, only: [:follow, :favourite, :reblog, :follow_request]
|
||||||
|
after_action :set_list_headers!
|
||||||
|
|
||||||
default to: -> { email_address_with_name(@user.email, @me.username) }
|
default to: -> { email_address_with_name(@user.email, @me.username) }
|
||||||
|
|
||||||
|
@ -61,6 +62,7 @@ class NotificationMailer < ApplicationMailer
|
||||||
@me = params[:recipient]
|
@me = params[:recipient]
|
||||||
@user = @me.user
|
@user = @me.user
|
||||||
@type = action_name
|
@type = action_name
|
||||||
|
@unsubscribe_url = unsubscribe_url(token: @user.to_sgid(for: 'unsubscribe').to_s, type: @type)
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_status
|
def set_status
|
||||||
|
@ -71,6 +73,12 @@ class NotificationMailer < ApplicationMailer
|
||||||
@account = @notification.from_account
|
@account = @notification.from_account
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_list_headers!
|
||||||
|
headers['List-ID'] = "<#{@type}.#{@me.username}.#{Rails.configuration.x.local_domain}>"
|
||||||
|
headers['List-Unsubscribe'] = "<#{@unsubscribe_url}>"
|
||||||
|
headers['List-Unsubscribe-Post'] = 'List-Unsubscribe=One-Click'
|
||||||
|
end
|
||||||
|
|
||||||
def thread_by_conversation(conversation)
|
def thread_by_conversation(conversation)
|
||||||
return if conversation.nil?
|
return if conversation.nil?
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ class MediaAttachment < ApplicationRecord
|
||||||
).freeze
|
).freeze
|
||||||
|
|
||||||
IMAGE_MIME_TYPES = %w(image/jpeg image/png image/gif image/heic image/heif image/webp image/avif).freeze
|
IMAGE_MIME_TYPES = %w(image/jpeg image/png image/gif image/heic image/heif image/webp image/avif).freeze
|
||||||
IMAGE_CONVERTIBLE_MIME_TYPES = %w(image/heic image/heif).freeze
|
IMAGE_CONVERTIBLE_MIME_TYPES = %w(image/heic image/heif image/avif).freeze
|
||||||
VIDEO_MIME_TYPES = %w(video/webm video/mp4 video/quicktime video/ogg).freeze
|
VIDEO_MIME_TYPES = %w(video/webm video/mp4 video/quicktime video/ogg).freeze
|
||||||
VIDEO_CONVERTIBLE_MIME_TYPES = %w(video/webm video/quicktime).freeze
|
VIDEO_CONVERTIBLE_MIME_TYPES = %w(video/webm video/quicktime).freeze
|
||||||
AUDIO_MIME_TYPES = %w(audio/wave audio/wav audio/x-wav audio/x-pn-wave audio/vnd.wave audio/ogg audio/vorbis audio/mpeg audio/mp3 audio/webm audio/flac audio/aac audio/m4a audio/x-m4a audio/mp4 audio/3gpp video/x-ms-asf).freeze
|
AUDIO_MIME_TYPES = %w(audio/wave audio/wav audio/x-wav audio/x-pn-wave audio/vnd.wave audio/ogg audio/vorbis audio/mpeg audio/mp3 audio/webm audio/flac audio/aac audio/m4a audio/x-m4a audio/mp4 audio/3gpp video/x-ms-asf).freeze
|
||||||
|
|
|
@ -36,7 +36,7 @@ class InitialStateSerializer < ActiveModel::Serializer
|
||||||
repository: Mastodon::Version.repository,
|
repository: Mastodon::Version.repository,
|
||||||
source_url: instance_presenter.source_url,
|
source_url: instance_presenter.source_url,
|
||||||
version: instance_presenter.version,
|
version: instance_presenter.version,
|
||||||
limited_federation_mode: Rails.configuration.x.whitelist_mode,
|
limited_federation_mode: Rails.configuration.x.limited_federation_mode,
|
||||||
mascot: instance_presenter.mascot&.file&.url,
|
mascot: instance_presenter.mascot&.file&.url,
|
||||||
profile_directory: Setting.profile_directory,
|
profile_directory: Setting.profile_directory,
|
||||||
trends_enabled: Setting.trends,
|
trends_enabled: Setting.trends,
|
||||||
|
|
|
@ -23,6 +23,6 @@ module Payloadable
|
||||||
end
|
end
|
||||||
|
|
||||||
def signing_enabled?
|
def signing_enabled?
|
||||||
ENV['AUTHORIZED_FETCH'] != 'true' && !Rails.configuration.x.whitelist_mode
|
ENV['AUTHORIZED_FETCH'] != 'true' && !Rails.configuration.x.limited_federation_mode
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -45,20 +45,18 @@ class FetchLinkCardService < BaseService
|
||||||
def html
|
def html
|
||||||
return @html if defined?(@html)
|
return @html if defined?(@html)
|
||||||
|
|
||||||
Request.new(:get, @url).add_headers('Accept' => 'text/html', 'User-Agent' => "#{Mastodon::Version.user_agent} Bot").perform do |res|
|
@html = Request.new(:get, @url).add_headers('Accept' => 'text/html', 'User-Agent' => "#{Mastodon::Version.user_agent} Bot").perform do |res|
|
||||||
|
next unless res.code == 200 && res.mime_type == 'text/html'
|
||||||
|
|
||||||
# We follow redirects, and ideally we want to save the preview card for
|
# We follow redirects, and ideally we want to save the preview card for
|
||||||
# the destination URL and not any link shortener in-between, so here
|
# the destination URL and not any link shortener in-between, so here
|
||||||
# we set the URL to the one of the last response in the redirect chain
|
# we set the URL to the one of the last response in the redirect chain
|
||||||
@url = res.request.uri.to_s
|
@url = res.request.uri.to_s
|
||||||
@card = PreviewCard.find_or_initialize_by(url: @url) if @card.url != @url
|
@card = PreviewCard.find_or_initialize_by(url: @url) if @card.url != @url
|
||||||
|
|
||||||
if res.code == 200 && res.mime_type == 'text/html'
|
|
||||||
@html_charset = res.charset
|
@html_charset = res.charset
|
||||||
@html = res.body_with_limit
|
|
||||||
else
|
res.body_with_limit
|
||||||
@html_charset = nil
|
|
||||||
@html = nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ class UnallowDomainService < BaseService
|
||||||
include DomainControlHelper
|
include DomainControlHelper
|
||||||
|
|
||||||
def call(domain_allow)
|
def call(domain_allow)
|
||||||
suspend_accounts!(domain_allow.domain) if whitelist_mode?
|
suspend_accounts!(domain_allow.domain) if limited_federation_mode?
|
||||||
|
|
||||||
domain_allow.destroy
|
domain_allow.destroy
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,18 +4,20 @@ class LanguageValidator < ActiveModel::EachValidator
|
||||||
include LanguagesHelper
|
include LanguagesHelper
|
||||||
|
|
||||||
def validate_each(record, attribute, value)
|
def validate_each(record, attribute, value)
|
||||||
record.errors.add(attribute, :invalid) unless valid?(value)
|
@value = value
|
||||||
|
|
||||||
|
record.errors.add(attribute, :invalid) unless valid_locale_value?
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def valid?(str)
|
def valid_locale_value?
|
||||||
if str.nil?
|
if @value.nil?
|
||||||
true
|
true
|
||||||
elsif str.is_a?(Array)
|
elsif @value.is_a?(Array)
|
||||||
str.all? { |x| valid_locale?(x) }
|
@value.all? { |x| valid_locale?(x) }
|
||||||
else
|
else
|
||||||
valid_locale?(str)
|
valid_locale?(@value)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -45,7 +45,7 @@ class StatusLengthValidator < ActiveModel::Validator
|
||||||
|
|
||||||
def rewrite_entities(str, entities)
|
def rewrite_entities(str, entities)
|
||||||
entities.sort_by! { |entity| entity[:indices].first }
|
entities.sort_by! { |entity| entity[:indices].first }
|
||||||
result = ''.dup
|
result = +''
|
||||||
|
|
||||||
last_index = entities.reduce(0) do |index, entity|
|
last_index = entities.reduce(0) do |index, entity|
|
||||||
result << str[index...entity[:indices].first]
|
result << str[index...entity[:indices].first]
|
||||||
|
|
|
@ -1,16 +1,31 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class URLValidator < ActiveModel::EachValidator
|
class URLValidator < ActiveModel::EachValidator
|
||||||
|
VALID_SCHEMES = %w(http https).freeze
|
||||||
|
|
||||||
def validate_each(record, attribute, value)
|
def validate_each(record, attribute, value)
|
||||||
record.errors.add(attribute, :invalid) unless compliant?(value)
|
@value = value
|
||||||
|
|
||||||
|
record.errors.add(attribute, :invalid) unless compliant_url?
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def compliant?(url)
|
def compliant_url?
|
||||||
parsed_url = Addressable::URI.parse(url)
|
parsed_url.present? && valid_url_scheme? && valid_url_host?
|
||||||
parsed_url && %w(http https).include?(parsed_url.scheme) && parsed_url.host
|
end
|
||||||
|
|
||||||
|
def parsed_url
|
||||||
|
Addressable::URI.parse(@value)
|
||||||
rescue Addressable::URI::InvalidURIError
|
rescue Addressable::URI::InvalidURIError
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def valid_url_scheme?
|
||||||
|
VALID_SCHEMES.include?(parsed_url.scheme)
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid_url_host?
|
||||||
|
parsed_url.host.present?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
= t('admin.instances.title')
|
= t('admin.instances.title')
|
||||||
|
|
||||||
- content_for :heading_actions do
|
- content_for :heading_actions do
|
||||||
- if whitelist_mode?
|
- if limited_federation_mode?
|
||||||
= link_to t('admin.domain_allows.add_new'), new_admin_domain_allow_path, class: 'button', id: 'add-instance-button'
|
= link_to t('admin.domain_allows.add_new'), new_admin_domain_allow_path, class: 'button', id: 'add-instance-button'
|
||||||
= link_to t('admin.domain_allows.export'), export_admin_export_domain_allows_path(format: :csv), class: 'button'
|
= link_to t('admin.domain_allows.export'), export_admin_export_domain_allows_path(format: :csv), class: 'button'
|
||||||
= link_to t('admin.domain_allows.import'), new_admin_export_domain_allow_path, class: 'button'
|
= link_to t('admin.domain_allows.import'), new_admin_export_domain_allow_path, class: 'button'
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
%ul
|
%ul
|
||||||
%li= filter_link_to t('admin.instances.moderation.all'), limited: nil
|
%li= filter_link_to t('admin.instances.moderation.all'), limited: nil
|
||||||
|
|
||||||
- unless whitelist_mode?
|
- unless limited_federation_mode?
|
||||||
%li= filter_link_to t('admin.instances.moderation.limited'), limited: '1'
|
%li= filter_link_to t('admin.instances.moderation.limited'), limited: '1'
|
||||||
|
|
||||||
.filter-subset
|
.filter-subset
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
%li= filter_link_to t('admin.instances.delivery.failing'), availability: 'failing'
|
%li= filter_link_to t('admin.instances.delivery.failing'), availability: 'failing'
|
||||||
%li= filter_link_to t('admin.instances.delivery.unavailable'), availability: 'unavailable'
|
%li= filter_link_to t('admin.instances.delivery.unavailable'), availability: 'unavailable'
|
||||||
|
|
||||||
- unless whitelist_mode?
|
- unless limited_federation_mode?
|
||||||
= form_tag admin_instances_url, method: 'GET', class: 'simple_form' do
|
= form_tag admin_instances_url, method: 'GET', class: 'simple_form' do
|
||||||
.fields-group
|
.fields-group
|
||||||
- InstanceFilter::KEYS.each do |key|
|
- InstanceFilter::KEYS.each do |key|
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
|
|
||||||
%h3= t('admin.instances.content_policies.title')
|
%h3= t('admin.instances.content_policies.title')
|
||||||
|
|
||||||
- if whitelist_mode?
|
- if limited_federation_mode?
|
||||||
%p= t('admin.instances.content_policies.limited_federation_mode_description_html')
|
%p= t('admin.instances.content_policies.limited_federation_mode_description_html')
|
||||||
|
|
||||||
- if @instance.domain_allow
|
- if @instance.domain_allow
|
||||||
|
|
|
@ -46,9 +46,9 @@
|
||||||
%p= t 'about.hosted_on', domain: site_hostname
|
%p= t 'about.hosted_on', domain: site_hostname
|
||||||
%p
|
%p
|
||||||
= link_to t('application_mailer.notification_preferences'), settings_preferences_notifications_url
|
= link_to t('application_mailer.notification_preferences'), settings_preferences_notifications_url
|
||||||
- if defined?(@type)
|
- if defined?(@unsubscribe_url)
|
||||||
·
|
·
|
||||||
= link_to t('application_mailer.unsubscribe'), unsubscribe_url(token: @user.to_sgid(for: 'unsubscribe').to_s, type: @type)
|
= link_to t('application_mailer.unsubscribe'), @unsubscribe_url
|
||||||
%td.column-cell.text-right
|
%td.column-cell.text-right
|
||||||
= link_to root_url do
|
= link_to root_url do
|
||||||
= image_tag full_pack_url('media/images/mailer/logo.png'), alt: 'Mastodon', height: 24
|
= image_tag full_pack_url('media/images/mailer/logo.png'), alt: 'Mastodon', height: 24
|
||||||
|
|
|
@ -2,6 +2,13 @@
|
||||||
|
|
||||||
doc = Ox::Document.new(version: '1.0')
|
doc = Ox::Document.new(version: '1.0')
|
||||||
|
|
||||||
|
ins = Ox::Instruct.new(:xml).tap do |instruct|
|
||||||
|
instruct[:version] = '1.0'
|
||||||
|
instruct[:encoding] = 'UTF-8'
|
||||||
|
end
|
||||||
|
|
||||||
|
doc << ins
|
||||||
|
|
||||||
doc << Ox::Element.new('XRD').tap do |xrd|
|
doc << Ox::Element.new('XRD').tap do |xrd|
|
||||||
xrd['xmlns'] = 'http://docs.oasis-open.org/ns/xri/xrd-1.0'
|
xrd['xmlns'] = 'http://docs.oasis-open.org/ns/xri/xrd-1.0'
|
||||||
|
|
||||||
|
@ -11,4 +18,4 @@ doc << Ox::Element.new('XRD').tap do |xrd|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>#{Ox.dump(doc, effort: :tolerant)}".force_encoding('UTF-8')
|
Ox.dump(doc, effort: :tolerant).force_encoding('UTF-8')
|
||||||
|
|
|
@ -46,7 +46,6 @@ require_relative '../lib/chewy/strategy/bypass_with_warning'
|
||||||
require_relative '../lib/webpacker/manifest_extensions'
|
require_relative '../lib/webpacker/manifest_extensions'
|
||||||
require_relative '../lib/webpacker/helper_extensions'
|
require_relative '../lib/webpacker/helper_extensions'
|
||||||
require_relative '../lib/rails/engine_extensions'
|
require_relative '../lib/rails/engine_extensions'
|
||||||
require_relative '../lib/action_controller/conditional_get_extensions'
|
|
||||||
require_relative '../lib/active_record/database_tasks_extensions'
|
require_relative '../lib/active_record/database_tasks_extensions'
|
||||||
require_relative '../lib/active_record/batches'
|
require_relative '../lib/active_record/batches'
|
||||||
require_relative '../lib/simple_navigation/item_extensions'
|
require_relative '../lib/simple_navigation/item_extensions'
|
||||||
|
@ -199,7 +198,7 @@ module Mastodon
|
||||||
# We use our own middleware for this
|
# We use our own middleware for this
|
||||||
config.public_file_server.enabled = false
|
config.public_file_server.enabled = false
|
||||||
|
|
||||||
config.middleware.use PublicFileServerMiddleware if Rails.env.development? || ENV['RAILS_SERVE_STATIC_FILES'] == 'true'
|
config.middleware.use PublicFileServerMiddleware if Rails.env.development? || Rails.env.test? || ENV['RAILS_SERVE_STATIC_FILES'] == 'true'
|
||||||
config.middleware.use Rack::Attack
|
config.middleware.use Rack::Attack
|
||||||
config.middleware.use Mastodon::RackMiddleware
|
config.middleware.use Mastodon::RackMiddleware
|
||||||
|
|
||||||
|
|
|
@ -57,75 +57,6 @@
|
||||||
],
|
],
|
||||||
"note": ""
|
"note": ""
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"warning_type": "Mass Assignment",
|
|
||||||
"warning_code": 105,
|
|
||||||
"fingerprint": "874be88fedf4c680926845e9a588d3197765a6ccbfdd76466b44cc00151c612e",
|
|
||||||
"check_name": "PermitAttributes",
|
|
||||||
"message": "Potentially dangerous key allowed for mass assignment",
|
|
||||||
"file": "app/controllers/api/v1/admin/reports_controller.rb",
|
|
||||||
"line": 88,
|
|
||||||
"link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
|
|
||||||
"code": "params.permit(:resolved, :account_id, :target_account_id)",
|
|
||||||
"render_path": null,
|
|
||||||
"location": {
|
|
||||||
"type": "method",
|
|
||||||
"class": "Api::V1::Admin::ReportsController",
|
|
||||||
"method": "filter_params"
|
|
||||||
},
|
|
||||||
"user_input": ":account_id",
|
|
||||||
"confidence": "High",
|
|
||||||
"cwe_id": [
|
|
||||||
915
|
|
||||||
],
|
|
||||||
"note": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"warning_type": "Mass Assignment",
|
|
||||||
"warning_code": 105,
|
|
||||||
"fingerprint": "ab5035dd1a9f8c3a8d92fb2c37e8fe86fede4f87c91b71aa32e89c9eede602fc",
|
|
||||||
"check_name": "PermitAttributes",
|
|
||||||
"message": "Potentially dangerous key allowed for mass assignment",
|
|
||||||
"file": "app/controllers/api/v1/notifications_controller.rb",
|
|
||||||
"line": 77,
|
|
||||||
"link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
|
|
||||||
"code": "params.permit(:account_id, :types => ([]), :exclude_types => ([]))",
|
|
||||||
"render_path": null,
|
|
||||||
"location": {
|
|
||||||
"type": "method",
|
|
||||||
"class": "Api::V1::NotificationsController",
|
|
||||||
"method": "browserable_params"
|
|
||||||
},
|
|
||||||
"user_input": ":account_id",
|
|
||||||
"confidence": "High",
|
|
||||||
"cwe_id": [
|
|
||||||
915
|
|
||||||
],
|
|
||||||
"note": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"warning_type": "Mass Assignment",
|
|
||||||
"warning_code": 105,
|
|
||||||
"fingerprint": "b0dd0a26d24f5ede9713fe49210e9638be5f5548af9eee0b5a16fe9dbc80ffcd",
|
|
||||||
"check_name": "PermitAttributes",
|
|
||||||
"message": "Potentially dangerous key allowed for mass assignment",
|
|
||||||
"file": "app/controllers/api/v2/search_controller.rb",
|
|
||||||
"line": 42,
|
|
||||||
"link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
|
|
||||||
"code": "params.permit(:type, :offset, :min_id, :max_id, :account_id, :following)",
|
|
||||||
"render_path": null,
|
|
||||||
"location": {
|
|
||||||
"type": "method",
|
|
||||||
"class": "Api::V2::SearchController",
|
|
||||||
"method": "search_params"
|
|
||||||
},
|
|
||||||
"user_input": ":account_id",
|
|
||||||
"confidence": "High",
|
|
||||||
"cwe_id": [
|
|
||||||
915
|
|
||||||
],
|
|
||||||
"note": ""
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"warning_type": "Cross-Site Scripting",
|
"warning_type": "Cross-Site Scripting",
|
||||||
"warning_code": 4,
|
"warning_code": 4,
|
||||||
|
@ -158,29 +89,6 @@
|
||||||
79
|
79
|
||||||
],
|
],
|
||||||
"note": ""
|
"note": ""
|
||||||
},
|
|
||||||
{
|
|
||||||
"warning_type": "Mass Assignment",
|
|
||||||
"warning_code": 105,
|
|
||||||
"fingerprint": "d0511f0287aea4ed9511f5a744f880cb15af77a8ec88f81b7365b00b642cf427",
|
|
||||||
"check_name": "PermitAttributes",
|
|
||||||
"message": "Potentially dangerous key allowed for mass assignment",
|
|
||||||
"file": "app/controllers/api/v1/reports_controller.rb",
|
|
||||||
"line": 26,
|
|
||||||
"link": "https://brakemanscanner.org/docs/warning_types/mass_assignment/",
|
|
||||||
"code": "params.permit(:account_id, :comment, :category, :forward, :forward_to_domains => ([]), :status_ids => ([]), :rule_ids => ([]))",
|
|
||||||
"render_path": null,
|
|
||||||
"location": {
|
|
||||||
"type": "method",
|
|
||||||
"class": "Api::V1::ReportsController",
|
|
||||||
"method": "report_params"
|
|
||||||
},
|
|
||||||
"user_input": ":account_id",
|
|
||||||
"confidence": "High",
|
|
||||||
"cwe_id": [
|
|
||||||
915
|
|
||||||
],
|
|
||||||
"note": ""
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated": "2023-07-12 11:20:51 -0400",
|
"updated": "2023-07-12 11:20:51 -0400",
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
---
|
||||||
|
:skip_checks:
|
||||||
|
- CheckPermitAttributes
|
|
@ -22,6 +22,6 @@
|
||||||
|
|
||||||
<!-- Disallow any coder by default, and only enable ones required by Mastodon -->
|
<!-- Disallow any coder by default, and only enable ones required by Mastodon -->
|
||||||
<policy domain="coder" rights="none" pattern="*" />
|
<policy domain="coder" rights="none" pattern="*" />
|
||||||
<policy domain="coder" rights="read | write" pattern="{PNG,JPEG,GIF,HEIC,WEBP}" />
|
<policy domain="coder" rights="read | write" pattern="{JPEG,PNG,GIF,WEBP,HEIC,AVIF}" />
|
||||||
<policy domain="coder" rights="write" pattern="{HISTOGRAM,RGB,INFO}" />
|
<policy domain="coder" rights="write" pattern="{HISTOGRAM,RGB,INFO}" />
|
||||||
</policymap>
|
</policymap>
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
Rails.application.configure do
|
||||||
|
config.x.limited_federation_mode = (ENV['LIMITED_FEDERATION_MODE'] || ENV['WHITELIST_MODE']) == 'true'
|
||||||
|
|
||||||
|
warn 'WARN: The environment variable WHITELIST_MODE has been replaced with LIMITED_FEDERATION_MODE, you should rename this environment variable in your configuration.' if ENV.key?('WHITELIST_MODE')
|
||||||
|
end
|
|
@ -1,5 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
Rails.application.configure do
|
|
||||||
config.x.whitelist_mode = (ENV['LIMITED_FEDERATION_MODE'] || ENV['WHITELIST_MODE']) == 'true'
|
|
||||||
end
|
|
|
@ -26,9 +26,9 @@ module Twitter::TwitterText
|
||||||
)
|
)
|
||||||
\)
|
\)
|
||||||
/iox
|
/iox
|
||||||
# rubocop:disable
|
# rubocop:disable Layout/LineLength
|
||||||
UCHARS = '\u{A0}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFEF}\u{10000}-\u{1FFFD}\u{20000}-\u{2FFFD}\u{30000}-\u{3FFFD}\u{40000}-\u{4FFFD}\u{50000}-\u{5FFFD}\u{60000}-\u{6FFFD}\u{70000}-\u{7FFFD}\u{80000}-\u{8FFFD}\u{90000}-\u{9FFFD}\u{A0000}-\u{AFFFD}\u{B0000}-\u{BFFFD}\u{C0000}-\u{CFFFD}\u{D0000}-\u{DFFFD}\u{E1000}-\u{EFFFD}\u{E000}-\u{F8FF}\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}'
|
UCHARS = '\u{A0}-\u{D7FF}\u{F900}-\u{FDCF}\u{FDF0}-\u{FFEF}\u{10000}-\u{1FFFD}\u{20000}-\u{2FFFD}\u{30000}-\u{3FFFD}\u{40000}-\u{4FFFD}\u{50000}-\u{5FFFD}\u{60000}-\u{6FFFD}\u{70000}-\u{7FFFD}\u{80000}-\u{8FFFD}\u{90000}-\u{9FFFD}\u{A0000}-\u{AFFFD}\u{B0000}-\u{BFFFD}\u{C0000}-\u{CFFFD}\u{D0000}-\u{DFFFD}\u{E1000}-\u{EFFFD}\u{E000}-\u{F8FF}\u{F0000}-\u{FFFFD}\u{100000}-\u{10FFFD}'
|
||||||
# rubocop:enable
|
# rubocop:enable Layout/LineLength
|
||||||
REGEXEN[:valid_url_query_chars] = %r{[a-z0-9!?*'();:&=+$/%#\[\]\-_.,~|@\^#{UCHARS}]}iou
|
REGEXEN[:valid_url_query_chars] = %r{[a-z0-9!?*'();:&=+$/%#\[\]\-_.,~|@\^#{UCHARS}]}iou
|
||||||
REGEXEN[:valid_url_query_ending_chars] = %r{[a-z0-9_&=#/\-#{UCHARS}]}iou
|
REGEXEN[:valid_url_query_ending_chars] = %r{[a-z0-9_&=#/\-#{UCHARS}]}iou
|
||||||
REGEXEN[:valid_url_path] = %r{(?:
|
REGEXEN[:valid_url_path] = %r{(?:
|
||||||
|
|
|
@ -46,7 +46,7 @@ SimpleNavigation::Configuration.run do |navigation|
|
||||||
s.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_path(origin: 'local'), highlights_on: %r{/admin/accounts|/admin/pending_accounts|/admin/disputes|/admin/users}, if: -> { current_user.can?(:manage_users) }
|
s.item :accounts, safe_join([fa_icon('users fw'), t('admin.accounts.title')]), admin_accounts_path(origin: 'local'), highlights_on: %r{/admin/accounts|/admin/pending_accounts|/admin/disputes|/admin/users}, if: -> { current_user.can?(:manage_users) }
|
||||||
s.item :invites, safe_join([fa_icon('user-plus fw'), t('admin.invites.title')]), admin_invites_path, if: -> { current_user.can?(:manage_invites) }
|
s.item :invites, safe_join([fa_icon('user-plus fw'), t('admin.invites.title')]), admin_invites_path, if: -> { current_user.can?(:manage_invites) }
|
||||||
s.item :follow_recommendations, safe_join([fa_icon('user-plus fw'), t('admin.follow_recommendations.title')]), admin_follow_recommendations_path, highlights_on: %r{/admin/follow_recommendations}, if: -> { current_user.can?(:manage_taxonomies) }
|
s.item :follow_recommendations, safe_join([fa_icon('user-plus fw'), t('admin.follow_recommendations.title')]), admin_follow_recommendations_path, highlights_on: %r{/admin/follow_recommendations}, if: -> { current_user.can?(:manage_taxonomies) }
|
||||||
s.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_path(limited: whitelist_mode? ? nil : '1'), highlights_on: %r{/admin/instances|/admin/domain_blocks|/admin/domain_allows}, if: -> { current_user.can?(:manage_federation) }
|
s.item :instances, safe_join([fa_icon('cloud fw'), t('admin.instances.title')]), admin_instances_path(limited: limited_federation_mode? ? nil : '1'), highlights_on: %r{/admin/instances|/admin/domain_blocks|/admin/domain_allows}, if: -> { current_user.can?(:manage_federation) }
|
||||||
s.item :email_domain_blocks, safe_join([fa_icon('envelope fw'), t('admin.email_domain_blocks.title')]), admin_email_domain_blocks_path, highlights_on: %r{/admin/email_domain_blocks}, if: -> { current_user.can?(:manage_blocks) }
|
s.item :email_domain_blocks, safe_join([fa_icon('envelope fw'), t('admin.email_domain_blocks.title')]), admin_email_domain_blocks_path, highlights_on: %r{/admin/email_domain_blocks}, if: -> { current_user.can?(:manage_blocks) }
|
||||||
s.item :ip_blocks, safe_join([fa_icon('ban fw'), t('admin.ip_blocks.title')]), admin_ip_blocks_path, highlights_on: %r{/admin/ip_blocks}, if: -> { current_user.can?(:manage_blocks) }
|
s.item :ip_blocks, safe_join([fa_icon('ban fw'), t('admin.ip_blocks.title')]), admin_ip_blocks_path, highlights_on: %r{/admin/ip_blocks}, if: -> { current_user.can?(:manage_blocks) }
|
||||||
s.item :action_logs, safe_join([fa_icon('bars fw'), t('admin.action_logs.title')]), admin_action_logs_path, if: -> { current_user.can?(:view_audit_log) }
|
s.item :action_logs, safe_join([fa_icon('bars fw'), t('admin.action_logs.title')]), admin_action_logs_path, if: -> { current_user.can?(:view_audit_log) }
|
||||||
|
@ -60,7 +60,7 @@ SimpleNavigation::Configuration.run do |navigation|
|
||||||
s.item :announcements, safe_join([fa_icon('bullhorn fw'), t('admin.announcements.title')]), admin_announcements_path, highlights_on: %r{/admin/announcements}, if: -> { current_user.can?(:manage_announcements) }
|
s.item :announcements, safe_join([fa_icon('bullhorn fw'), t('admin.announcements.title')]), admin_announcements_path, highlights_on: %r{/admin/announcements}, if: -> { current_user.can?(:manage_announcements) }
|
||||||
s.item :custom_emojis, safe_join([fa_icon('smile-o fw'), t('admin.custom_emojis.title')]), admin_custom_emojis_path, highlights_on: %r{/admin/custom_emojis}, if: -> { current_user.can?(:manage_custom_emojis) }
|
s.item :custom_emojis, safe_join([fa_icon('smile-o fw'), t('admin.custom_emojis.title')]), admin_custom_emojis_path, highlights_on: %r{/admin/custom_emojis}, if: -> { current_user.can?(:manage_custom_emojis) }
|
||||||
s.item :webhooks, safe_join([fa_icon('inbox fw'), t('admin.webhooks.title')]), admin_webhooks_path, highlights_on: %r{/admin/webhooks}, if: -> { current_user.can?(:manage_webhooks) }
|
s.item :webhooks, safe_join([fa_icon('inbox fw'), t('admin.webhooks.title')]), admin_webhooks_path, highlights_on: %r{/admin/webhooks}, if: -> { current_user.can?(:manage_webhooks) }
|
||||||
s.item :relays, safe_join([fa_icon('exchange fw'), t('admin.relays.title')]), admin_relays_path, highlights_on: %r{/admin/relays}, if: -> { !whitelist_mode? && current_user.can?(:manage_federation) }
|
s.item :relays, safe_join([fa_icon('exchange fw'), t('admin.relays.title')]), admin_relays_path, highlights_on: %r{/admin/relays}, if: -> { !limited_federation_mode? && current_user.can?(:manage_federation) }
|
||||||
end
|
end
|
||||||
|
|
||||||
n.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_path, link_html: { target: 'sidekiq' }, if: -> { current_user.can?(:view_devops) }
|
n.item :sidekiq, safe_join([fa_icon('diamond fw'), 'Sidekiq']), sidekiq_path, link_html: { target: 'sidekiq' }, if: -> { current_user.can?(:view_devops) }
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const babel = require('./babel');
|
const babel = require('./babel');
|
||||||
const css = require('./css');
|
const css = require('./css');
|
||||||
const file = require('./file');
|
const file = require('./file');
|
||||||
|
const materialIcons = require('./material_icons');
|
||||||
const nodeModules = require('./node_modules');
|
const nodeModules = require('./node_modules');
|
||||||
const tesseract = require('./tesseract');
|
const tesseract = require('./tesseract');
|
||||||
|
|
||||||
|
@ -8,6 +9,7 @@ const tesseract = require('./tesseract');
|
||||||
// https://webpack.js.org/concepts/loaders/#loader-features
|
// https://webpack.js.org/concepts/loaders/#loader-features
|
||||||
// Lastly, process static files using file loader
|
// Lastly, process static files using file loader
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
materialIcons,
|
||||||
file,
|
file,
|
||||||
tesseract,
|
tesseract,
|
||||||
css,
|
css,
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
module.exports = {
|
||||||
|
test: /\.svg$/,
|
||||||
|
include: /node_modules\/@material-design-icons/,
|
||||||
|
issuer: /\.[jt]sx?$/,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: '@svgr/webpack',
|
||||||
|
options: {
|
||||||
|
svgo: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
|
@ -5,5 +5,5 @@ const { merge } = require('webpack-merge');
|
||||||
const sharedConfig = require('./shared');
|
const sharedConfig = require('./shared');
|
||||||
|
|
||||||
module.exports = merge(sharedConfig, {
|
module.exports = merge(sharedConfig, {
|
||||||
mode: 'development',
|
mode: 'production',
|
||||||
});
|
});
|
||||||
|
|
|
@ -45,7 +45,7 @@ class CopyStatusStats < ActiveRecord::Migration[5.2]
|
||||||
# We cannot use bulk INSERT or overarching transactions here because of possible
|
# We cannot use bulk INSERT or overarching transactions here because of possible
|
||||||
# uniqueness violations that we need to skip over
|
# uniqueness violations that we need to skip over
|
||||||
Status.unscoped.select('id, reblogs_count, favourites_count, created_at, updated_at').find_each do |status|
|
Status.unscoped.select('id, reblogs_count, favourites_count, created_at, updated_at').find_each do |status|
|
||||||
params = [[nil, status.id], [nil, status.reblogs_count], [nil, status.favourites_count], [nil, status.created_at], [nil, status.updated_at]]
|
params = [status.id, status.reblogs_count, status.favourites_count, status.created_at, status.updated_at]
|
||||||
exec_insert('INSERT INTO status_stats (status_id, reblogs_count, favourites_count, created_at, updated_at) VALUES ($1, $2, $3, $4, $5)', nil, params)
|
exec_insert('INSERT INTO status_stats (status_id, reblogs_count, favourites_count, created_at, updated_at) VALUES ($1, $2, $3, $4, $5)', nil, params)
|
||||||
rescue ActiveRecord::RecordNotUnique
|
rescue ActiveRecord::RecordNotUnique
|
||||||
next
|
next
|
||||||
|
|
|
@ -45,7 +45,7 @@ class CopyAccountStats < ActiveRecord::Migration[5.2]
|
||||||
# We cannot use bulk INSERT or overarching transactions here because of possible
|
# We cannot use bulk INSERT or overarching transactions here because of possible
|
||||||
# uniqueness violations that we need to skip over
|
# uniqueness violations that we need to skip over
|
||||||
Account.unscoped.select('id, statuses_count, following_count, followers_count, created_at, updated_at').find_each do |account|
|
Account.unscoped.select('id, statuses_count, following_count, followers_count, created_at, updated_at').find_each do |account|
|
||||||
params = [[nil, account.id], [nil, account[:statuses_count]], [nil, account[:following_count]], [nil, account[:followers_count]], [nil, account.created_at], [nil, account.updated_at]]
|
params = [account.id, account[:statuses_count], account[:following_count], account[:followers_count], account.created_at, account.updated_at]
|
||||||
exec_insert('INSERT INTO account_stats (account_id, statuses_count, following_count, followers_count, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6)', nil, params)
|
exec_insert('INSERT INTO account_stats (account_id, statuses_count, following_count, followers_count, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6)', nil, params)
|
||||||
rescue ActiveRecord::RecordNotUnique
|
rescue ActiveRecord::RecordNotUnique
|
||||||
next
|
next
|
||||||
|
|
|
@ -56,7 +56,7 @@ services:
|
||||||
|
|
||||||
web:
|
web:
|
||||||
build: .
|
build: .
|
||||||
image: ghcr.io/mastodon/mastodon:v4.1.5
|
image: ghcr.io/mastodon/mastodon:v4.1.6
|
||||||
restart: always
|
restart: always
|
||||||
env_file: .env.production
|
env_file: .env.production
|
||||||
command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"
|
command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"
|
||||||
|
@ -77,7 +77,7 @@ services:
|
||||||
|
|
||||||
streaming:
|
streaming:
|
||||||
build: .
|
build: .
|
||||||
image: ghcr.io/mastodon/mastodon:v4.1.5
|
image: ghcr.io/mastodon/mastodon:v4.1.6
|
||||||
restart: always
|
restart: always
|
||||||
env_file: .env.production
|
env_file: .env.production
|
||||||
command: node ./streaming
|
command: node ./streaming
|
||||||
|
@ -95,7 +95,7 @@ services:
|
||||||
|
|
||||||
sidekiq:
|
sidekiq:
|
||||||
build: .
|
build: .
|
||||||
image: ghcr.io/mastodon/mastodon:v4.1.5
|
image: ghcr.io/mastodon/mastodon:v4.1.6
|
||||||
restart: always
|
restart: always
|
||||||
env_file: .env.production
|
env_file: .env.production
|
||||||
command: bundle exec sidekiq
|
command: bundle exec sidekiq
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module ActionController
|
|
||||||
module ConditionalGetExtensions
|
|
||||||
def expires_in(*)
|
|
||||||
# This backports a fix from Rails 7 so that a more private Cache-Control
|
|
||||||
# can be overriden by calling expires_in on a specific controller action
|
|
||||||
response.cache_control.delete(:no_store)
|
|
||||||
|
|
||||||
super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
ActionController::ConditionalGet.prepend(ActionController::ConditionalGetExtensions)
|
|
|
@ -61,7 +61,7 @@ module Mastodon::CLI
|
||||||
# Skip accounts followed by local accounts
|
# Skip accounts followed by local accounts
|
||||||
clean_followed_sql = 'AND NOT EXISTS (SELECT 1 FROM follows WHERE statuses.account_id = follows.target_account_id)' unless options[:clean_followed]
|
clean_followed_sql = 'AND NOT EXISTS (SELECT 1 FROM follows WHERE statuses.account_id = follows.target_account_id)' unless options[:clean_followed]
|
||||||
|
|
||||||
ActiveRecord::Base.connection.exec_insert(<<-SQL.squish, 'SQL', [[nil, max_id]])
|
ActiveRecord::Base.connection.exec_insert(<<-SQL.squish, 'SQL', [max_id])
|
||||||
INSERT INTO statuses_to_be_deleted (id)
|
INSERT INTO statuses_to_be_deleted (id)
|
||||||
SELECT statuses.id FROM statuses WHERE deleted_at IS NULL AND NOT local AND uri IS NOT NULL AND (id < $1)
|
SELECT statuses.id FROM statuses WHERE deleted_at IS NULL AND NOT local AND uri IS NOT NULL AND (id < $1)
|
||||||
AND NOT EXISTS (SELECT 1 FROM statuses AS statuses1 WHERE statuses.id = statuses1.in_reply_to_id)
|
AND NOT EXISTS (SELECT 1 FROM statuses AS statuses1 WHERE statuses.id = statuses1.in_reply_to_id)
|
||||||
|
|
|
@ -13,7 +13,7 @@ module Mastodon
|
||||||
end
|
end
|
||||||
|
|
||||||
def patch
|
def patch
|
||||||
5
|
6
|
||||||
end
|
end
|
||||||
|
|
||||||
def flags
|
def flags
|
||||||
|
|
|
@ -2,13 +2,15 @@
|
||||||
|
|
||||||
module Paperclip
|
module Paperclip
|
||||||
module MediaTypeSpoofDetectorExtensions
|
module MediaTypeSpoofDetectorExtensions
|
||||||
|
MARCEL_MIME_TYPES = %w(audio/mpeg image/avif).freeze
|
||||||
|
|
||||||
def calculated_content_type
|
def calculated_content_type
|
||||||
return @calculated_content_type if defined?(@calculated_content_type)
|
return @calculated_content_type if defined?(@calculated_content_type)
|
||||||
|
|
||||||
@calculated_content_type = type_from_file_command.chomp
|
@calculated_content_type = type_from_file_command.chomp
|
||||||
|
|
||||||
# The `file` command fails to recognize some MP3 files as such
|
# The `file` command fails to recognize some MP3 files as such
|
||||||
@calculated_content_type = type_from_marcel if @calculated_content_type == 'application/octet-stream' && type_from_marcel == 'audio/mpeg'
|
@calculated_content_type = type_from_marcel if @calculated_content_type == 'application/octet-stream' && type_from_marcel.in?(MARCEL_MIME_TYPES)
|
||||||
@calculated_content_type
|
@calculated_content_type
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -438,12 +438,7 @@ namespace :mastodon do
|
||||||
"#{key}=#{escaped}"
|
"#{key}=#{escaped}"
|
||||||
end.join("\n")
|
end.join("\n")
|
||||||
|
|
||||||
generated_header = "# Generated with mastodon:setup on #{Time.now.utc}\n\n".dup
|
generated_header = generate_header(incompatible_syntax)
|
||||||
|
|
||||||
if incompatible_syntax
|
|
||||||
generated_header << "# Some variables in this file will be interpreted differently whether you are\n"
|
|
||||||
generated_header << "# using docker-compose or not.\n\n"
|
|
||||||
end
|
|
||||||
|
|
||||||
Rails.root.join('.env.production').write("#{generated_header}#{env_contents}\n")
|
Rails.root.join('.env.production').write("#{generated_header}#{env_contents}\n")
|
||||||
|
|
||||||
|
@ -538,6 +533,19 @@ namespace :mastodon do
|
||||||
puts "VAPID_PUBLIC_KEY=#{vapid_key.public_key}"
|
puts "VAPID_PUBLIC_KEY=#{vapid_key.public_key}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def generate_header(include_warning)
|
||||||
|
default_message = "# Generated with mastodon:setup on #{Time.now.utc}\n\n"
|
||||||
|
|
||||||
|
default_message.tap do |string|
|
||||||
|
if include_warning
|
||||||
|
string << "# Some variables in this file will be interpreted differently whether you are\n"
|
||||||
|
string << "# using docker-compose or not.\n\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def disable_log_stdout!
|
def disable_log_stdout!
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
if Rake::Task.task_defined?('spec:system')
|
||||||
|
namespace :spec do
|
||||||
|
task :enable_system_specs do # rubocop:disable Rails/RakeEnvironment
|
||||||
|
ENV['RUN_SYSTEM_SPECS'] = 'true'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Rake::Task['spec:system'].enhance ['spec:enable_system_specs']
|
||||||
|
end
|
|
@ -44,8 +44,10 @@
|
||||||
"@formatjs/intl-pluralrules": "^5.2.2",
|
"@formatjs/intl-pluralrules": "^5.2.2",
|
||||||
"@gamestdio/websocket": "^0.3.2",
|
"@gamestdio/websocket": "^0.3.2",
|
||||||
"@github/webauthn-json": "^2.1.1",
|
"@github/webauthn-json": "^2.1.1",
|
||||||
|
"@material-design-icons/svg": "^0.14.10",
|
||||||
"@rails/ujs": "^7.0.6",
|
"@rails/ujs": "^7.0.6",
|
||||||
"@reduxjs/toolkit": "^1.9.5",
|
"@reduxjs/toolkit": "^1.9.5",
|
||||||
|
"@svgr/webpack": "^5.5.0",
|
||||||
"abortcontroller-polyfill": "^1.7.5",
|
"abortcontroller-polyfill": "^1.7.5",
|
||||||
"atrament": "0.2.4",
|
"atrament": "0.2.4",
|
||||||
"arrow-key-navigation": "^1.2.0",
|
"arrow-key-navigation": "^1.2.0",
|
||||||
|
|
|
@ -129,6 +129,37 @@ describe SignatureVerification do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with non-normalized URL' do
|
||||||
|
before do
|
||||||
|
get :success
|
||||||
|
|
||||||
|
fake_request = Request.new(:get, 'http://test.host/subdir/../success')
|
||||||
|
fake_request.on_behalf_of(author)
|
||||||
|
|
||||||
|
request.headers.merge!(fake_request.headers)
|
||||||
|
|
||||||
|
allow(controller).to receive(:actor_refresh_key!).and_return(author)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#build_signed_string' do
|
||||||
|
it 'includes the normalized request path' do
|
||||||
|
expect(controller.send(:build_signed_string)).to start_with "(request-target): get /success\n"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#signed_request?' do
|
||||||
|
it 'returns true' do
|
||||||
|
expect(controller.signed_request?).to be true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#signed_request_actor' do
|
||||||
|
it 'returns an account' do
|
||||||
|
expect(controller.signed_request_account).to eq author
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'with request with unparsable Date header' do
|
context 'with request with unparsable Date header' do
|
||||||
before do
|
before do
|
||||||
get :success
|
get :success
|
||||||
|
@ -202,7 +233,7 @@ describe SignatureVerification do
|
||||||
|
|
||||||
request.headers.merge!(fake_request.headers)
|
request.headers.merge!(fake_request.headers)
|
||||||
|
|
||||||
stub_request(:get, 'http://localhost:5000/actor#main-key').to_raise(Mastodon::HostValidationError)
|
stub_request(:get, 'http://localhost:5000/actor').to_raise(Mastodon::HostValidationError)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#signed_request?' do
|
describe '#signed_request?' do
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
After Width: | Height: | Size: 8.8 KiB |
|
@ -1,483 +0,0 @@
|
||||||
HTTP/1.1 200 OK
|
|
||||||
Server: nginx
|
|
||||||
Date: Sun, 23 Apr 2017 19:37:13 GMT
|
|
||||||
Content-Type: text/html
|
|
||||||
Content-Length: 38111
|
|
||||||
Last-Modified: Wed, 20 Jul 2016 02:50:52 GMT
|
|
||||||
Connection: keep-alive
|
|
||||||
Accept-Ranges: bytes
|
|
||||||
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" />
|
|
||||||
<meta name="viewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
|
|
||||||
<script>
|
|
||||||
var _hmt = _hmt || [];
|
|
||||||
(function() {
|
|
||||||
var hm = document.createElement("script");
|
|
||||||
hm.src = "http://hm.baidu.com/hm.js?746c3f6346fae8612933e5921803ee32";
|
|
||||||
var s = document.getElementsByTagName("script")[0];
|
|
||||||
s.parentNode.insertBefore(hm, s);
|
|
||||||
})();
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<link rel="stylesheet" type="text/css" href="css/common.css"/>
|
|
||||||
<script src="js/jquery-1.11.1.min.js" type="text/javascript" charset="utf-8"></script>
|
|
||||||
<script src="js/common.js" type="text/javascript" charset="utf-8"></script>
|
|
||||||
<script src="js/carousel.js" type="text/javascript" charset="utf-8"></script>
|
|
||||||
<title>中国域名网站</title>
|
|
||||||
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="head-tips" id="headTip">
|
|
||||||
<span class="close" id="headtips-close"><img src="css/img/close.png" alt="" /></span>
|
|
||||||
</div>
|
|
||||||
<div class="banner-bg"></div>
|
|
||||||
<div class="container">
|
|
||||||
<div class="banner">
|
|
||||||
<img src="css/img/banner.png" alt="" />
|
|
||||||
</div>
|
|
||||||
<div class="nav">
|
|
||||||
<h1>名站导航</h1>
|
|
||||||
<div class="left-btn" id="pre">
|
|
||||||
<img src="css/img/arrow-left.png" alt="" />
|
|
||||||
</div>
|
|
||||||
<div class="carousel">
|
|
||||||
<ul class="carousel-content">
|
|
||||||
<li>
|
|
||||||
<a href="http://中央电视台.中国" target="_blank">
|
|
||||||
<img src="css/img/p10.png" alt="" />
|
|
||||||
<p>中央电视台.中国</p>
|
|
||||||
</a><a href="http://平安北京.中国" target="_blank" class="mt-4">
|
|
||||||
<img src="css/img/p5.png" alt="" />
|
|
||||||
<p>平安北京.中国</p>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="http://人民网.中国" target="_blank">
|
|
||||||
<img src="css/img/p6.png" alt="" />
|
|
||||||
<p>人民网.中国</p>
|
|
||||||
</a><a href="http://招商银行.中国" target="_blank" class="mt-4">
|
|
||||||
<img src="css/img/p8.png" alt="" />
|
|
||||||
<p>招商银行.中国</p>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="http://必胜客宅急送.中国" target="_blank">
|
|
||||||
<img src="css/img/p1.png" alt="" />
|
|
||||||
<p>必胜客宅急送.中国</p>
|
|
||||||
</a><a href="http://创业咖啡.中国" target="_blank" class="mt-4">
|
|
||||||
<img src="css/img/p2.png" alt="" />
|
|
||||||
<p>创业咖啡.中国</p>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="http://中国移动.中国" target="_blank">
|
|
||||||
<img src="css/img/p9.png" alt="" />
|
|
||||||
<p>中国移动.中国</p>
|
|
||||||
</a><a href="http://海盟.中国" target="_blank" class="mt-4">
|
|
||||||
<img src="css/img/p3.png" alt="" />
|
|
||||||
<p>海盟.中国</p>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="http://艺龙.中国" target="_blank">
|
|
||||||
<img src="css/img/p7.png" alt="" />
|
|
||||||
<p>艺龙.中国</p>
|
|
||||||
</a><a href="http://和讯.中国" target="_blank" class="mt-4">
|
|
||||||
<img src="css/img/p4.png" alt="" />
|
|
||||||
<p>和讯.中国</p>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="right-btn" id="next">
|
|
||||||
<img src="css/img/arrow-right.png" alt="" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="all-url">
|
|
||||||
<div class="container">
|
|
||||||
<h1>网址大全</h1>
|
|
||||||
<ul class="url">
|
|
||||||
<li><a href="http://人民网.中国" target="_blank">人民网.中国</a></li>
|
|
||||||
<li><a href="http://新华网.中国" target="_blank">新华网.中国</a></li>
|
|
||||||
<li><a href="http://中央电视台.中国" target="_blank">中央电视台.中国</a></li>
|
|
||||||
<li><a href="http://光明网.中国" target="_blank">光明网.中国</a></li>
|
|
||||||
<li><a href="http://平安北京.中国" target="_blank">平安北京.中国</a></li>
|
|
||||||
<li><a href="http://联想微博.中国" target="_blank">联想微博.中国</a></li>
|
|
||||||
<li><a href="http://首都网警.中国" target="_blank">首都网警.中国</a></li>
|
|
||||||
<li><a href="http://北京消防.中国" target="_blank">北京消防.中国</a></li>
|
|
||||||
<li><a href="http://海淀公安.中国" target="_blank">海淀公安.中国</a></li>
|
|
||||||
<li><a href="http://通州警方.中国" target="_blank">通州警方.中国</a></li>
|
|
||||||
<li><a href="http://门头沟禁毒.中国" target="_blank">门头沟禁毒.中国</a></li>
|
|
||||||
<li><a href="http://西部数码.中国" target="_blank">西部数码.中国</a></li>
|
|
||||||
<li><a href="http://中央电视台.中国" target="_blank">中央电视台.中国</a></li>
|
|
||||||
<li><a href="http://中国移动.中国" target="_blank">中国移动.中国</a></li>
|
|
||||||
<li><a href="http://必胜宅急送.中国" target="_blank">必胜宅急送.中国</a></li>
|
|
||||||
<li><a href="http://老正兴.中国" target="_blank">老正兴.中国</a></li>
|
|
||||||
<li><a href="http://广州酒家.中国" target="_blank">广州酒家.中国</a></li>
|
|
||||||
<li><a href="http://格力.中国" target="_blank">格力.中国</a></li>
|
|
||||||
<li><a href="http://福建金爵.中国" target="_blank">福建金爵.中国</a></li>
|
|
||||||
<li><a href="http://和信房产.中国" target="_blank">和信房产.中国</a></li>
|
|
||||||
<li><a href="http://金爵房地产.中国" target="_blank">金爵房地产.中国</a></li>
|
|
||||||
<li><a href="http://联泰地产.中国" target="_blank">联泰地产.中国</a></li>
|
|
||||||
<li><a href="http://鲁商置业.中国" target="_blank">鲁商置业.中国</a></li>
|
|
||||||
<li><a href="http://鲁商置业股份.中国" target="_blank">鲁商置业股份.中国</a></li>
|
|
||||||
<li><a href="http://美佳华.中国" target="_blank">美佳华.中国</a></li>
|
|
||||||
<li><a href="http://金世纪工程.中国" target="_blank">金世纪工程.中国</a></li>
|
|
||||||
<li><a href="http://金世纪集团.中国" target="_blank">金世纪集团.中国</a></li>
|
|
||||||
<li><a href="http://深圳金世纪.中国" target="_blank">深圳金世纪.中国</a></li>
|
|
||||||
<li><a href="http://总部基地.中国" target="_blank">总部基地.中国</a></li>
|
|
||||||
<li><a href="http://德律风.中国" target="_blank">德律风.中国</a></li>
|
|
||||||
<li><a href="http://德律风物业.中国" target="_blank">德律风物业.中国</a></li>
|
|
||||||
<li><a href="http://柯林.中国" target="_blank">柯林.中国</a></li>
|
|
||||||
<li><a href="http://上海德律风物业.中国" target="_blank">上海德律风物业.中国</a></li>
|
|
||||||
<li><a href="http://广东海印集团股份.中国" target="_blank">广东海印集团股份.中国</a></li>
|
|
||||||
<li><a href="http://广东海印集团股份有限公司.中国" target="_blank">广东海印集团股份有限公司.中国</a></li>
|
|
||||||
<li><a href="http://艺龙.中国" target="_blank">艺龙.中国</a></li>
|
|
||||||
<li><a href="http://北京旅游信息网.中国" target="_blank">北京旅游信息网.中国</a></li>
|
|
||||||
<li><a href="http://北京故宫博物院.中国" target="_blank">北京故宫博物院.中国</a></li>
|
|
||||||
<li><a href="http://旅行张家界.中国" target="_blank">旅行张家界.中国</a></li>
|
|
||||||
<li><a href="http://张家界旅游.中国" target="_blank">张家界旅游.中国</a></li>
|
|
||||||
<li><a href="http://广州市旅游局.中国" target="_blank">广州市旅游局.中国</a></li>
|
|
||||||
<li><a href="http://旅游在线.中国" target="_blank">旅游在线.中国</a></li>
|
|
||||||
<li><a href="http://威海旅游集散中心.中国" target="_blank">威海旅游集散中心.中国</a></li>
|
|
||||||
<li><a href="http://锦州旅游.中国" target="_blank">锦州旅游.中国</a></li>
|
|
||||||
<li><a href="http://金牛湖风景旅游度假区.中国" target="_blank">金牛湖风景旅游度假区.中国</a></li>
|
|
||||||
<li><a href="http://环球旅行社.中国" target="_blank">环球旅行社.中国</a></li>
|
|
||||||
<li><a href="http://养鹿场.中国" target="_blank">养鹿场.中国</a></li>
|
|
||||||
<li><a href="http://东瀛游.中国" target="_blank">东瀛游.中国</a></li>
|
|
||||||
<li><a href="http://东瀛游旅行社.中国" target="_blank">东瀛游旅行社.中国</a></li>
|
|
||||||
<li><a href="http://桂林游.中国" target="_blank">桂林游.中国</a></li>
|
|
||||||
<li><a href="http://桂林之旅.中国" target="_blank">桂林之旅.中国</a></li>
|
|
||||||
<li><a href="http://美国环球旅行社.中国" target="_blank">美国环球旅行社.中国</a></li>
|
|
||||||
<li><a href="http://东天目山.中国" target="_blank">东天目山.中国</a></li>
|
|
||||||
<li><a href="http://凤山寺.中国" target="_blank">凤山寺.中国</a></li>
|
|
||||||
<li><a href="http://黄沙古渡.中国" target="_blank">黄沙古渡.中国</a></li>
|
|
||||||
<li><a href="http://城头山.中国" target="_blank">城头山.中国</a></li>
|
|
||||||
<li><a href="http://港游网.中国" target="_blank">港游网.中国</a></li>
|
|
||||||
<li><a href="http://一起游.中国" target="_blank">一起游.中国</a></li>
|
|
||||||
<li><a href="http://山水家园.中国" target="_blank">山水家园.中国</a></li>
|
|
||||||
<li><a href="http://蒋巷村.中国" target="_blank">蒋巷村.中国</a></li>
|
|
||||||
<li><a href="http://蒋巷村农业生态旅游.中国" target="_blank">蒋巷村农业生态旅游.中国</a></li>
|
|
||||||
<li><a href="http://厦门海峡旅行社.中国" target="_blank">厦门海峡旅行社.中国</a></li>
|
|
||||||
<li><a href="http://姜堰宾馆.中国" target="_blank">姜堰宾馆.中国</a></li>
|
|
||||||
<li><a href="http://上海远洋宾馆.中国" target="_blank">上海远洋宾馆.中国</a></li>
|
|
||||||
<li><a href="http://红栌山庄.中国" target="_blank">红栌山庄.中国</a></li>
|
|
||||||
<li><a href="http://金牛湖风景旅游度假区.中国" target="_blank">金牛湖风景旅游度假区.中国</a></li>
|
|
||||||
<li><a href="http://金牛湖风景区.中国" target="_blank">金牛湖风景区.中国</a></li>
|
|
||||||
<li><a href="http://北京半岛酒店.中国" target="_blank">北京半岛酒店.中国</a></li>
|
|
||||||
<li><a href="http://比华利山半岛酒店.中国" target="_blank">比华利山半岛酒店.中国</a></li>
|
|
||||||
<li><a href="http://东京半岛酒店.中国" target="_blank">东京半岛酒店.中国</a></li>
|
|
||||||
<li><a href="http://君乐酒店.中国" target="_blank">君乐酒店.中国</a></li>
|
|
||||||
<li><a href="http://凯迪威酒店.中国" target="_blank">凯迪威酒店.中国</a></li>
|
|
||||||
<li><a href="http://莱州酒店.中国" target="_blank">莱州酒店.中国</a></li>
|
|
||||||
<li><a href="http://曼谷半岛酒店.中国" target="_blank">曼谷半岛酒店.中国</a></li>
|
|
||||||
<li><a href="http://上海半岛酒店.中国" target="_blank">上海半岛酒店.中国</a></li>
|
|
||||||
<li><a href="http://上虞国际大酒店.中国" target="_blank">上虞国际大酒店.中国</a></li>
|
|
||||||
<li><a href="http://王府半島酒店.中国" target="_blank">王府半島酒店.中国</a></li>
|
|
||||||
<li><a href="http://香港半岛酒店.中国" target="_blank">香港半岛酒店.中国</a></li>
|
|
||||||
<li><a href="http://银河大酒店.中国" target="_blank">银河大酒店.中国</a></li>
|
|
||||||
<li><a href="http://健康365.中国" target="_blank">健康365.中国</a></li>
|
|
||||||
<li><a href="http://家天下.中国" target="_blank">家天下.中国</a></li>
|
|
||||||
<li><a href="http://北京大学第三医院.中国" target="_blank">北京大学第三医院.中国</a></li>
|
|
||||||
<li><a href="http://西藏阜康医药.中国" target="_blank">西藏阜康医药.中国</a></li>
|
|
||||||
<li><a href="http://沈阳妇婴医院.中国" target="_blank">沈阳妇婴医院.中国</a></li>
|
|
||||||
<li><a href="http://福建医科大学附属第一医院.中国" target="_blank">福建医科大学附属第一医院.中国</a></li>
|
|
||||||
<li><a href="http://北方药业.中国" target="_blank">北方药业.中国</a></li>
|
|
||||||
<li><a href="http://医药导报.中国" target="_blank">医药导报.中国</a></li>
|
|
||||||
<li><a href="http://中国医药导报.中国" target="_blank">中国医药导报.中国</a></li>
|
|
||||||
<li><a href="http://云南省医药有限公司.中国" target="_blank">云南省医药有限公司.中国</a></li>
|
|
||||||
<li><a href="http://云南省医药.中国" target="_blank">云南省医药.中国</a></li>
|
|
||||||
<li><a href="http://必胜宅急送.中国" target="_blank">必胜宅急送.中国</a></li>
|
|
||||||
<li><a href="http://青岛啤酒股份有限公司.中国" target="_blank">青岛啤酒股份有限公司.中国</a></li>
|
|
||||||
<li><a href="http://火锅面.中国" target="_blank">火锅面.中国</a></li>
|
|
||||||
<li><a href="http://57度湘.中国" target="_blank">57度湘.中国</a></li>
|
|
||||||
<li><a href="http://澳門佳景集團.中国" target="_blank">澳門佳景集團.中国</a></li>
|
|
||||||
<li><a href="http://澳門佳景飲食集團.中国" target="_blank">澳門佳景飲食集團.中国</a></li>
|
|
||||||
<li><a href="http://赤峰陈曲.中国" target="_blank">赤峰陈曲.中国</a></li>
|
|
||||||
<li><a href="http://春宝.中国" target="_blank">春宝.中国</a></li>
|
|
||||||
<li><a href="http://富农水稻.中国" target="_blank">富农水稻.中国</a></li>
|
|
||||||
<li><a href="http://功德林.中国" target="_blank">功德林.中国</a></li>
|
|
||||||
<li><a href="http://古船.中国" target="_blank">古船.中国</a></li>
|
|
||||||
<li><a href="http://古船食品.中国" target="_blank">古船食品.中国</a></li>
|
|
||||||
<li><a href="http://红岩村.中国" target="_blank">红岩村.中国</a></li>
|
|
||||||
<li><a href="http://佳景飲食集團.中国" target="_blank">佳景飲食集團.中国</a></li>
|
|
||||||
<li><a href="http://赖永初酒业.中国" target="_blank">赖永初酒业.中国</a></li>
|
|
||||||
<li><a href="http://厉家菜.中国" target="_blank">厉家菜.中国</a></li>
|
|
||||||
<li><a href="http://莲花岛.中国" target="_blank">莲花岛.中国</a></li>
|
|
||||||
<li><a href="http://廖平一两酒.中国" target="_blank">廖平一两酒.中国</a></li>
|
|
||||||
<li><a href="http://龙轩.中国" target="_blank">龙轩.中国</a></li>
|
|
||||||
<li><a href="http://迈德乐.中国" target="_blank">迈德乐.中国</a></li>
|
|
||||||
<li><a href="http://明记炖品.中国" target="_blank">明记炖品.中国</a></li>
|
|
||||||
<li><a href="http://明记炖品世家.中国" target="_blank">明记炖品世家.中国</a></li>
|
|
||||||
<li><a href="http://黔江鸡杂.中国" target="_blank">黔江鸡杂.中国</a></li>
|
|
||||||
<li><a href="http://聖安娜餅屋.中国" target="_blank">聖安娜餅屋.中国</a></li>
|
|
||||||
<li><a href="http://华夏茶业网.中国" target="_blank">华夏茶业网.中国</a></li>
|
|
||||||
<li><a href="http://宅香锅.中国" target="_blank">宅香锅.中国</a></li>
|
|
||||||
<li><a href="http://荞麦面.中国" target="_blank">荞麦面.中国</a></li>
|
|
||||||
<li><a href="http://宅面坊.中国" target="_blank">宅面坊.中国</a></li>
|
|
||||||
<li><a href="http://宅豆坊.中国" target="_blank">宅豆坊.中国</a></li>
|
|
||||||
<li><a href="http://草原羔羊肉.中国" target="_blank">草原羔羊肉.中国</a></li>
|
|
||||||
<li><a href="http://火锅饺.中国" target="_blank">火锅饺.中国</a></li>
|
|
||||||
<li><a href="http://鸟鸡蛋.中国" target="_blank">鸟鸡蛋.中国</a></li>
|
|
||||||
<li><a href="http://宅米饭.中国" target="_blank">宅米饭.中国</a></li>
|
|
||||||
<li><a href="http://白野猪肉.中国" target="_blank">白野猪肉.中国</a></li>
|
|
||||||
<li><a href="http://黑野猪肉.中国" target="_blank">黑野猪肉.中国</a></li>
|
|
||||||
<li><a href="http://特色野猪肉.中国" target="_blank">特色野猪肉.中国</a></li>
|
|
||||||
<li><a href="http://生态畜牧.中国" target="_blank">生态畜牧.中国</a></li>
|
|
||||||
<li><a href="http://野豆坊.中国" target="_blank">野豆坊.中国</a></li>
|
|
||||||
<li><a href="http://野猪牧.中国" target="_blank">野猪牧.中国</a></li>
|
|
||||||
<li><a href="http://野猪网.中国" target="_blank">野猪网.中国</a></li>
|
|
||||||
<li><a href="http://酷牛肉.中国" target="_blank">酷牛肉.中国</a></li>
|
|
||||||
<li><a href="http://羔羊网.中国" target="_blank">羔羊网.中国</a></li>
|
|
||||||
<li><a href="http://野猪肉.中国" target="_blank">野猪肉.中国</a></li>
|
|
||||||
<li><a href="http://鸟鸡肉.中国" target="_blank">鸟鸡肉.中国</a></li>
|
|
||||||
<li><a href="http://藏羔羊.中国" target="_blank">藏羔羊.中国</a></li>
|
|
||||||
<li><a href="http://酷牛牧场.中国" target="_blank">酷牛牧场.中国</a></li>
|
|
||||||
<li><a href="http://鸟鸡牧场.中国" target="_blank">鸟鸡牧场.中国</a></li>
|
|
||||||
<li><a href="http://鸟鸡网.中国" target="_blank">鸟鸡网.中国</a></li>
|
|
||||||
<li><a href="http://家餐馆.中国" target="_blank">家餐馆.中国</a></li>
|
|
||||||
<li><a href="http://宅火锅.中国" target="_blank">宅火锅.中国</a></li>
|
|
||||||
<li><a href="http://食品饮料网.中国" target="_blank">食品饮料网.中国</a></li>
|
|
||||||
<li><a href="http://中国湿巾.中国" target="_blank">中国湿巾.中国</a></li>
|
|
||||||
<li><a href="http://海特果菜.中国" target="_blank">海特果菜.中国</a></li>
|
|
||||||
<li><a href="http://果菜.中国" target="_blank">果菜.中国</a></li>
|
|
||||||
<li><a href="http://宏鑫德.中国" target="_blank">宏鑫德.中国</a></li>
|
|
||||||
<li><a href="http://北方烧酒.中国" target="_blank">北方烧酒.中国</a></li>
|
|
||||||
<li><a href="http://欧兰娑曼.中国" target="_blank">欧兰娑曼.中国</a></li>
|
|
||||||
<li><a href="http://威尔富.中国" target="_blank">威尔富.中国</a></li>
|
|
||||||
<li><a href="http://虎林老窖.中国" target="_blank">虎林老窖.中国</a></li>
|
|
||||||
<li><a href="http://唐记食品.中国" target="_blank">唐记食品.中国</a></li>
|
|
||||||
<li><a href="http://津恺食品.中国" target="_blank">津恺食品.中国</a></li>
|
|
||||||
<li><a href="http://津恺.中国" target="_blank">津恺.中国</a></li>
|
|
||||||
<li><a href="http://老中医养生.中国" target="_blank">老中医养生.中国</a></li>
|
|
||||||
<li><a href="http://山东伟龙食品公司.中国" target="_blank">山东伟龙食品公司.中国</a></li>
|
|
||||||
<li><a href="http://太泉蜂业.中国" target="_blank">太泉蜂业.中国</a></li>
|
|
||||||
<li><a href="http://天鹅肉.中国" target="_blank">天鹅肉.中国</a></li>
|
|
||||||
<li><a href="http://望湘园.中国" target="_blank">望湘园.中国</a></li>
|
|
||||||
<li><a href="http://伟龙饼干.中国" target="_blank">伟龙饼干.中国</a></li>
|
|
||||||
<li><a href="http://沃根葡萄酒.中国" target="_blank">沃根葡萄酒.中国</a></li>
|
|
||||||
<li><a href="http://亚坤集团.中国" target="_blank">亚坤集团.中国</a></li>
|
|
||||||
<li><a href="http://鱼丸.中国" target="_blank">鱼丸.中国</a></li>
|
|
||||||
<li><a href="http://真美集团.中国" target="_blank">真美集团.中国</a></li>
|
|
||||||
<li><a href="http://真美食品.中国" target="_blank">真美食品.中国</a></li>
|
|
||||||
<li><a href="http://中国餐饮标识.中国" target="_blank">中国餐饮标识.中国</a></li>
|
|
||||||
<li><a href="http://迷奇.中国" target="_blank">迷奇.中国</a></li>
|
|
||||||
<li><a href="http://乐隆隆.中国" target="_blank">乐隆隆.中国</a></li>
|
|
||||||
<li><a href="http://绞股蓝.中国" target="_blank">绞股蓝.中国</a></li>
|
|
||||||
<li><a href="http://瀑布仙茗.中国" target="_blank">瀑布仙茗.中国</a></li>
|
|
||||||
<li><a href="http://金记食品.中国" target="_blank">金记食品.中国</a></li>
|
|
||||||
<li><a href="http://朱老六.中国" target="_blank">朱老六.中国</a></li>
|
|
||||||
<li><a href="http://嘉太.中国" target="_blank">嘉太.中国</a></li>
|
|
||||||
<li><a href="http://顺德堂.中国" target="_blank">顺德堂.中国</a></li>
|
|
||||||
<li><a href="http://广味源.中国" target="_blank">广味源.中国</a></li>
|
|
||||||
<li><a href="http://德辉食品.中国" target="_blank">德辉食品.中国</a></li>
|
|
||||||
<li><a href="http://金龙船.中国" target="_blank">金龙船.中国</a></li>
|
|
||||||
<li><a href="http://东方即白.中国" target="_blank">东方即白.中国</a></li>
|
|
||||||
<li><a href="http://中山华美实业.中国" target="_blank">中山华美实业.中国</a></li>
|
|
||||||
<li><a href="http://富士亭.中国" target="_blank">富士亭.中国</a></li>
|
|
||||||
<li><a href="http://三安科技.中国" target="_blank">三安科技.中国</a></li>
|
|
||||||
<li><a href="http://供美香食品.中国" target="_blank">供美香食品.中国</a></li>
|
|
||||||
<li><a href="http://丰德天元.中国" target="_blank">丰德天元.中国</a></li>
|
|
||||||
<li><a href="http://老藏医.中国" target="_blank">老藏医.中国</a></li>
|
|
||||||
<li><a href="http://新农仓.中国" target="_blank">新农仓.中国</a></li>
|
|
||||||
<li><a href="http://濠吉.中国" target="_blank">濠吉.中国</a></li>
|
|
||||||
<li><a href="http://品味爽.中国" target="_blank">品味爽.中国</a></li>
|
|
||||||
<li><a href="http://坤育.中国" target="_blank">坤育.中国</a></li>
|
|
||||||
<li><a href="http://皇宫食品.中国" target="_blank">皇宫食品.中国</a></li>
|
|
||||||
<li><a href="http://依海.中国" target="_blank">依海.中国</a></li>
|
|
||||||
<li><a href="http://广州凯虹.中国" target="_blank">广州凯虹.中国</a></li>
|
|
||||||
<li><a href="http://宝姿日化.中国" target="_blank">宝姿日化.中国</a></li>
|
|
||||||
<li><a href="http://乐高乐.中国" target="_blank">乐高乐.中国</a></li>
|
|
||||||
<li><a href="http://茂华食品.中国" target="_blank">茂华食品.中国</a></li>
|
|
||||||
<li><a href="http://白鹿集团.中国" target="_blank">白鹿集团.中国</a></li>
|
|
||||||
<li><a href="http://好丽友集团.中国" target="_blank">好丽友集团.中国</a></li>
|
|
||||||
<li><a href="http://法兰红.中国" target="_blank">法兰红.中国</a></li>
|
|
||||||
<li><a href="http://教育部.中国" target="_blank">教育部.中国</a></li>
|
|
||||||
<li><a href="http://国家民委.中国" target="_blank">国家民委.中国</a></li>
|
|
||||||
<li><a href="http://人口计生委.中国" target="_blank">人口计生委.中国</a></li>
|
|
||||||
<li><a href="http://工商总局.中国" target="_blank">工商总局.中国</a></li>
|
|
||||||
<li><a href="http://监察部.中国" target="_blank">监察部.中国</a></li>
|
|
||||||
<li><a href="http://农业部.中国" target="_blank">农业部.中国</a></li>
|
|
||||||
<li><a href="http://人民银行.中国" target="_blank">人民银行.中国</a></li>
|
|
||||||
<li><a href="http://侨办.中国" target="_blank">侨办.中国</a></li>
|
|
||||||
<li><a href="http://食品药品监督局.中国" target="_blank">食品药品监督局.中国</a></li>
|
|
||||||
<li><a href="http://科技部.中国" target="_blank">科技部.中国</a></li>
|
|
||||||
<li><a href="http://财政部.中国" target="_blank">财政部.中国</a></li>
|
|
||||||
<li><a href="http://文化部.中国" target="_blank">文化部.中国</a></li>
|
|
||||||
<li><a href="http://审计署.中国" target="_blank">审计署.中国</a></li>
|
|
||||||
<li><a href="http://体育总局.中国" target="_blank">体育总局.中国</a></li>
|
|
||||||
<li><a href="http://知识产权局.中国" target="_blank">知识产权局.中国</a></li>
|
|
||||||
<li><a href="http://国研网.中国" target="_blank">国研网.中国</a></li>
|
|
||||||
<li><a href="http://电监会.中国" target="_blank">电监会.中国</a></li>
|
|
||||||
<li><a href="http://民航总局.中国" target="_blank">民航总局.中国</a></li>
|
|
||||||
<li><a href="http://卫生部.中国" target="_blank">卫生部.中国</a></li>
|
|
||||||
<li><a href="http://安全监察总局.中国" target="_blank">安全监察总局.中国</a></li>
|
|
||||||
<li><a href="http://国家行政学院.中国" target="_blank">国家行政学院.中国</a></li>
|
|
||||||
<li><a href="http://申银万国.中国" target="_blank">申银万国.中国</a></li>
|
|
||||||
<li><a href="http://保定保险协会.中国" target="_blank">保定保险协会.中国</a></li>
|
|
||||||
<li><a href="http://和讯.中国" target="_blank">和讯.中国</a></li>
|
|
||||||
<li><a href="http://招商证券.中国" target="_blank">招商证券.中国</a></li>
|
|
||||||
<li><a href="http://中投证券.中国" target="_blank">中投证券.中国</a></li>
|
|
||||||
<li><a href="http://鹏元征信.中国" target="_blank">鹏元征信.中国</a></li>
|
|
||||||
<li><a href="http://中融联合.中国" target="_blank">中融联合.中国</a></li>
|
|
||||||
<li><a href="http://长城资产.中国" target="_blank">长城资产.中国</a></li>
|
|
||||||
<li><a href="http://周生生證券.中国" target="_blank">周生生證券.中国</a></li>
|
|
||||||
<li><a href="http://福建湄洲湾控股.中国" target="_blank">福建湄洲湾控股.中国</a></li>
|
|
||||||
<li><a href="http://中安现金.中国" target="_blank">中安现金.中国</a></li>
|
|
||||||
<li><a href="http://中安信业.中国" target="_blank">中安信业.中国</a></li>
|
|
||||||
<li><a href="http://聯訊證券.中国" target="_blank">聯訊證券.中国</a></li>
|
|
||||||
<li><a href="http://元富理財网.中国" target="_blank">元富理財网.中国</a></li>
|
|
||||||
<li><a href="http://金立方资本.中国" target="_blank">金立方资本.中国</a></li>
|
|
||||||
<li><a href="http://安信证券.中国" target="_blank">安信证券.中国</a></li>
|
|
||||||
<li><a href="http://中国创业投资网.中国" target="_blank">中国创业投资网.中国</a></li>
|
|
||||||
<li><a href="http://進邦匯理.中国" target="_blank">進邦匯理.中国</a></li>
|
|
||||||
<li><a href="http://中再集团.中国" target="_blank">中再集团.中国</a></li>
|
|
||||||
<li><a href="http://交通银行.中国" target="_blank">交通银行.中国</a></li>
|
|
||||||
<li><a href="http://农业银行.中国" target="_blank">农业银行.中国</a></li>
|
|
||||||
<li><a href="http://民生银行.中国" target="_blank">民生银行.中国</a></li>
|
|
||||||
<li><a href="http://招商银行.中国" target="_blank">招商银行.中国</a></li>
|
|
||||||
<li><a href="http://黄河银行.中国" target="_blank">黄河银行.中国</a></li>
|
|
||||||
<li><a href="http://周口市商业银行.中国" target="_blank">周口市商业银行.中国</a></li>
|
|
||||||
<li><a href="http://金融快线.中国" target="_blank">金融快线.中国</a></li>
|
|
||||||
<li><a href="http://农信银.中国" target="_blank">农信银.中国</a></li>
|
|
||||||
<li><a href="http://乐pad微博.中国" target="_blank">乐pad微博.中国</a></li>
|
|
||||||
<li><a href="http://联想显示器.中国" target="_blank">联想显示器.中国</a></li>
|
|
||||||
<li><a href="http://联想打印.中国" target="_blank">联想打印.中国</a></li>
|
|
||||||
<li><a href="http://联想Z流行.中国" target="_blank">联想Z流行.中国</a></li>
|
|
||||||
<li><a href="http://中国国际新闻网.中国" target="_blank">中国国际新闻网.中国</a></li>
|
|
||||||
<li><a href="http://洛阳电视台.中国" target="_blank">洛阳电视台.中国</a></li>
|
|
||||||
<li><a href="http://崇左新闻网.中国" target="_blank">崇左新闻网.中国</a></li>
|
|
||||||
<li><a href="http://超越之路.中国" target="_blank">超越之路.中国</a></li>
|
|
||||||
<li><a href="http://长安教育网.中国" target="_blank">长安教育网.中国</a></li>
|
|
||||||
<li><a href="http://唐密茶道.中国" target="_blank">唐密茶道.中国</a></li>
|
|
||||||
<li><a href="http://雷峰陪练.中国" target="_blank">雷峰陪练.中国</a></li>
|
|
||||||
<li><a href="http://考研.中国" target="_blank">考研.中国</a></li>
|
|
||||||
<li><a href="http://世界大学城.中国" target="_blank">世界大学城.中国</a></li>
|
|
||||||
<li><a href="http://路正驾校.中国" target="_blank">路正驾校.中国</a></li>
|
|
||||||
<li><a href="http://比特威.中国" target="_blank">比特威.中国</a></li>
|
|
||||||
<li><a href="http://吉林省农业科学院.中国" target="_blank">吉林省农业科学院.中国</a></li>
|
|
||||||
<li><a href="http://普通话审音.中国" target="_blank">普通话审音.中国</a></li>
|
|
||||||
<li><a href="http://童帅国际教育.中国" target="_blank">童帅国际教育.中国</a></li>
|
|
||||||
<li><a href="http://成功之钥.中国" target="_blank">成功之钥.中国</a></li>
|
|
||||||
<li><a href="http://西安理工大学.中国" target="_blank">西安理工大学.中国</a></li>
|
|
||||||
<li><a href="http://贵阳电脑学校.中国" target="_blank">贵阳电脑学校.中国</a></li>
|
|
||||||
<li><a href="http://黑龙江省实验中学.中国" target="_blank">黑龙江省实验中学.中国</a></li>
|
|
||||||
<li><a href="http://浙江艺术职业学院.中国" target="_blank">浙江艺术职业学院.中国</a></li>
|
|
||||||
<li><a href="http://萃忆学堂.中国" target="_blank">萃忆学堂.中国</a></li>
|
|
||||||
<li><a href="http://闽南科技学院.中国" target="_blank">闽南科技学院.中国</a></li>
|
|
||||||
<li><a href="http://普通话语音.中国" target="_blank">普通话语音.中国</a></li>
|
|
||||||
<li><a href="http://鞍山师范大学.中国" target="_blank">鞍山师范大学.中国</a></li>
|
|
||||||
<li><a href="http://北京电影学院.中国" target="_blank">北京电影学院.中国</a></li>
|
|
||||||
<li><a href="http://成都理工大学.中国" target="_blank">成都理工大学.中国</a></li>
|
|
||||||
<li><a href="http://东北大学.中国" target="_blank">东北大学.中国</a></li>
|
|
||||||
<li><a href="http://赣南师范学院.中国" target="_blank">赣南师范学院.中国</a></li>
|
|
||||||
<li><a href="http://广州大学.中国" target="_blank">广州大学.中国</a></li>
|
|
||||||
<li><a href="http://河北大学.中国" target="_blank">河北大学.中国</a></li>
|
|
||||||
<li><a href="http://河北科技师范学院.中国" target="_blank">河北科技师范学院.中国</a></li>
|
|
||||||
<li><a href="http://河南农业大学.中国" target="_blank">河南农业大学.中国</a></li>
|
|
||||||
<li><a href="http://江西师范大学.中国" target="_blank">江西师范大学.中国</a></li>
|
|
||||||
<li><a href="http://辽宁大学.中国" target="_blank">辽宁大学.中国</a></li>
|
|
||||||
<li><a href="http://南昌大学.中国" target="_blank">南昌大学.中国</a></li>
|
|
||||||
<li><a href="http://南京理工大学.中国" target="_blank">南京理工大学.中国</a></li>
|
|
||||||
<li><a href="http://青岛大学.中国" target="_blank">青岛大学.中国</a></li>
|
|
||||||
<li><a href="http://山东大学.中国" target="_blank">山东大学.中国</a></li>
|
|
||||||
<li><a href="http://汕头大学.中国" target="_blank">汕头大学.中国</a></li>
|
|
||||||
<li><a href="http://上海交通大学.中国" target="_blank">上海交通大学.中国</a></li>
|
|
||||||
<li><a href="http://首都经济贸易大学.中国" target="_blank">首都经济贸易大学.中国</a></li>
|
|
||||||
<li><a href="http://四川文理学院.中国" target="_blank">四川文理学院.中国</a></li>
|
|
||||||
<li><a href="http://天津大学.中国" target="_blank">天津大学.中国</a></li>
|
|
||||||
<li><a href="http://五邑大学.中国" target="_blank">五邑大学.中国</a></li>
|
|
||||||
<li><a href="http://百色学院.中国" target="_blank">百色学院.中国</a></li>
|
|
||||||
<li><a href="http://北京化工大学.中国" target="_blank">北京化工大学.中国</a></li>
|
|
||||||
<li><a href="http://大连理工大学.中国" target="_blank">大连理工大学.中国</a></li>
|
|
||||||
<li><a href="http://福建医科大学.中国" target="_blank">福建医科大学.中国</a></li>
|
|
||||||
<li><a href="http://广东工业大学.中国" target="_blank">广东工业大学.中国</a></li>
|
|
||||||
<li><a href="http://海南师范大学.中国" target="_blank">海南师范大学.中国</a></li>
|
|
||||||
<li><a href="http://淮海工学院.中国" target="_blank">淮海工学院.中国</a></li>
|
|
||||||
<li><a href="http://辽宁对外经贸学院.中国" target="_blank">辽宁对外经贸学院.中国</a></li>
|
|
||||||
<li><a href="http://青海师范大学.中国" target="_blank">青海师范大学.中国</a></li>
|
|
||||||
<li><a href="http://山东农业大学.中国" target="_blank">山东农业大学.中国</a></li>
|
|
||||||
<li><a href="http://上海财经大学.中国" target="_blank">上海财经大学.中国</a></li>
|
|
||||||
<li><a href="http://上海中医药大学.中国" target="_blank">上海中医药大学.中国</a></li>
|
|
||||||
<li><a href="http://首都师范大学.中国" target="_blank">首都师范大学.中国</a></li>
|
|
||||||
<li><a href="http://塔里木大学.中国" target="_blank">塔里木大学.中国</a></li>
|
|
||||||
<li><a href="http://西安电子科技大学.中国" target="_blank">西安电子科技大学.中国</a></li>
|
|
||||||
<li><a href="http://清华大学.中国" target="_blank">清华大学.中国</a></li>
|
|
||||||
<li><a href="http://大连医科大学.中国" target="_blank">大连医科大学.中国</a></li>
|
|
||||||
<li><a href="http://贵州大学.中国" target="_blank">贵州大学.中国</a></li>
|
|
||||||
<li><a href="http://哈尔滨学院.中国" target="_blank">哈尔滨学院.中国</a></li>
|
|
||||||
<li><a href="http://海南医学院.中国" target="_blank">海南医学院.中国</a></li>
|
|
||||||
<li><a href="http://黑龙江大学.中国" target="_blank">黑龙江大学.中国</a></li>
|
|
||||||
<li><a href="http://集美大学.中国" target="_blank">集美大学.中国</a></li>
|
|
||||||
<li><a href="http://南京邮电大学.中国" target="_blank">南京邮电大学.中国</a></li>
|
|
||||||
<li><a href="http://上海大学.中国" target="_blank">上海大学.中国</a></li>
|
|
||||||
<li><a href="http://深圳大学.中国" target="_blank">深圳大学.中国</a></li>
|
|
||||||
<li><a href="http://四川大学.中国" target="_blank">四川大学.中国</a></li>
|
|
||||||
<li><a href="http://天津师范大学.中国" target="_blank">天津师范大学.中国</a></li>
|
|
||||||
<li><a href="http://西安工业大学.中国" target="_blank">西安工业大学.中国</a></li>
|
|
||||||
<li><a href="http://北华大学.中国" target="_blank">北华大学.中国</a></li>
|
|
||||||
<li><a href="http://防灾科技学院.中国" target="_blank">防灾科技学院.中国</a></li>
|
|
||||||
<li><a href="http://甘肃农业大学.中国" target="_blank">甘肃农业大学.中国</a></li>
|
|
||||||
<li><a href="http://广西师范学院.中国" target="_blank">广西师范学院.中国</a></li>
|
|
||||||
<li><a href="http://哈尔滨医科大学.中国" target="_blank">哈尔滨医科大学.中国</a></li>
|
|
||||||
<li><a href="http://河北科技大学.中国" target="_blank">河北科技大学.中国</a></li>
|
|
||||||
<li><a href="http://内蒙古大学.中国" target="_blank">内蒙古大学.中国</a></li>
|
|
||||||
<li><a href="http://宁夏大学.中国" target="_blank">宁夏大学.中国</a></li>
|
|
||||||
<li><a href="http://山东财经大学.中国" target="_blank">山东财经大学.中国</a></li>
|
|
||||||
<li><a href="http://陕西师范大学.中国" target="_blank">陕西师范大学.中国</a></li>
|
|
||||||
<li><a href="http://上海对外贸易学院.中国" target="_blank">上海对外贸易学院.中国</a></li>
|
|
||||||
<li><a href="http://四川警察学院.中国" target="_blank">四川警察学院.中国</a></li>
|
|
||||||
<li><a href="http://西华大学.中国" target="_blank">西华大学.中国</a></li>
|
|
||||||
<li><a href="http://许昌学院.中国" target="_blank">许昌学院.中国</a></li>
|
|
||||||
<li><a href="http://扬州大学.中国" target="_blank">扬州大学.中国</a></li>
|
|
||||||
<li><a href="http://中国矿业大学.中国" target="_blank">中国矿业大学.中国</a></li>
|
|
||||||
<li><a href="http://中南大学.中国" target="_blank">中南大学.中国</a></li>
|
|
||||||
<li><a href="http://西安理工大学.中国" target="_blank">西安理工大学.中国</a></li>
|
|
||||||
<li><a href="http://烟台大学.中国" target="_blank">烟台大学.中国</a></li>
|
|
||||||
<li><a href="http://漳州师范学院.中国" target="_blank">漳州师范学院.中国</a></li>
|
|
||||||
<li><a href="http://郑州大学.中国" target="_blank">郑州大学.中国</a></li>
|
|
||||||
<li><a href="http://中国农业大学.中国" target="_blank">中国农业大学.中国</a></li>
|
|
||||||
<li><a href="http://中国医药大学.中国" target="_blank">中国医药大学.中国</a></li>
|
|
||||||
<li><a href="http://西安邮电学院.中国" target="_blank">西安邮电学院.中国</a></li>
|
|
||||||
<li><a href="http://新疆大学.中国" target="_blank">新疆大学.中国</a></li>
|
|
||||||
<li><a href="http://云南师范大学.中国" target="_blank">云南师范大学.中国</a></li>
|
|
||||||
<li><a href="http://中国政法大学.中国" target="_blank">中国政法大学.中国</a></li>
|
|
||||||
<li><a href="http://西昌学院.中国" target="_blank">西昌学院.中国</a></li>
|
|
||||||
<li><a href="http://新疆农业大学.中国" target="_blank">新疆农业大学.中国</a></li>
|
|
||||||
<li><a href="http://浙江万里学院.中国" target="_blank">浙江万里学院.中国</a></li>
|
|
||||||
<li><a href="http://重庆大学.中国" target="_blank">重庆大学.中国</a></li>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="open">
|
|
||||||
</div>
|
|
||||||
<div class="container">
|
|
||||||
<h1 class="Chinese-domain">中文域名简介</h1>
|
|
||||||
<p class="Chinese-domain-content">
|
|
||||||
“中国域名”是中文域名的一种,特指以“中国”为后缀的中文域名,是我国域名体系和全球互联网域名体系的重要组成部分。“中国”是在全球互联网上代表中国的中文顶级域名,于2010年7月正式纳入全球互联网域名体系,全球互联网域名体系,全球网民可通过联网计算机在世界任何国家和地区实现无障碍访问。“中国”域名在使用上和 .CN,相似属于互联网上的基础服务,基于域名可以提供WWW.EMAIL FTP等应用服务。
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="footer">
|
|
||||||
<p>ICP备案编号:京ICP 备09112257号-68 版权所有中国互联网信息中心</p>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
<script>
|
|
||||||
$("#headTip").hide()
|
|
||||||
var hostname = window.location.hostname || "";
|
|
||||||
|
|
||||||
var tips = "您所访问的域名 <font size='' color='#ff0000'>" + hostname +"</font> 无法到达,您可以尝试重新访问,或使用搜索相关信息"
|
|
||||||
if (hostname != "导航.中国") {
|
|
||||||
$("#headTip").html(tips);
|
|
||||||
$("#headTip").delay(500).slideDown();
|
|
||||||
$('#headTip').delay(5000).slideUp();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</html>
|
|
|
@ -1,6 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe Admin::ActionLogsHelper do
|
|
||||||
end
|
|
|
@ -4,7 +4,9 @@ require 'rails_helper'
|
||||||
require 'securerandom'
|
require 'securerandom'
|
||||||
|
|
||||||
describe Request do
|
describe Request do
|
||||||
subject { described_class.new(:get, 'http://example.com') }
|
subject { described_class.new(:get, url) }
|
||||||
|
|
||||||
|
let(:url) { 'http://example.com' }
|
||||||
|
|
||||||
describe '#headers' do
|
describe '#headers' do
|
||||||
it 'returns user agent' do
|
it 'returns user agent' do
|
||||||
|
@ -92,6 +94,152 @@ describe Request do
|
||||||
expect { subject.perform }.to raise_error Mastodon::ValidationError
|
expect { subject.perform }.to raise_error Mastodon::ValidationError
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with bare domain URL' do
|
||||||
|
let(:url) { 'http://example.com' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_request(:get, 'http://example.com')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'normalizes path' do
|
||||||
|
subject.perform do |response|
|
||||||
|
expect(response.request.uri.path).to eq '/'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'normalizes path used for request signing' do
|
||||||
|
subject.perform
|
||||||
|
|
||||||
|
headers = subject.instance_variable_get(:@headers)
|
||||||
|
expect(headers[Request::REQUEST_TARGET]).to eq 'get /'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'normalizes path used in request line' do
|
||||||
|
subject.perform do |response|
|
||||||
|
expect(response.request.headline).to eq 'GET / HTTP/1.1'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with unnormalized URL' do
|
||||||
|
let(:url) { 'HTTP://EXAMPLE.com:80/foo%41%3A?bar=%41%3A#baz' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_request(:get, 'http://example.com/foo%41%3A?bar=%41%3A')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'normalizes scheme' do
|
||||||
|
subject.perform do |response|
|
||||||
|
expect(response.request.uri.scheme).to eq 'http'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'normalizes host' do
|
||||||
|
subject.perform do |response|
|
||||||
|
expect(response.request.uri.authority).to eq 'example.com'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not modify path' do
|
||||||
|
subject.perform do |response|
|
||||||
|
expect(response.request.uri.path).to eq '/foo%41%3A'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not modify query string' do
|
||||||
|
subject.perform do |response|
|
||||||
|
expect(response.request.uri.query).to eq 'bar=%41%3A'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not modify path used for request signing' do
|
||||||
|
subject.perform
|
||||||
|
|
||||||
|
headers = subject.instance_variable_get(:@headers)
|
||||||
|
expect(headers[Request::REQUEST_TARGET]).to eq 'get /foo%41%3A'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not modify path used in request line' do
|
||||||
|
subject.perform do |response|
|
||||||
|
expect(response.request.headline).to eq 'GET /foo%41%3A?bar=%41%3A HTTP/1.1'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'strips fragment' do
|
||||||
|
subject.perform do |response|
|
||||||
|
expect(response.request.uri.fragment).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with non-ASCII URL' do
|
||||||
|
let(:url) { 'http://éxample.com:81/föo?bär=1' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_request(:get, 'http://xn--xample-9ua.com:81/f%C3%B6o?b%C3%A4r=1')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'IDN-encodes host' do
|
||||||
|
subject.perform do |response|
|
||||||
|
expect(response.request.uri.authority).to eq 'xn--xample-9ua.com:81'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'IDN-encodes host in Host header' do
|
||||||
|
subject.perform do |response|
|
||||||
|
expect(response.request.headers['Host']).to eq 'xn--xample-9ua.com'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'percent-escapes path used for request signing' do
|
||||||
|
subject.perform
|
||||||
|
|
||||||
|
headers = subject.instance_variable_get(:@headers)
|
||||||
|
expect(headers[Request::REQUEST_TARGET]).to eq 'get /f%C3%B6o'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'normalizes path used in request line' do
|
||||||
|
subject.perform do |response|
|
||||||
|
expect(response.request.headline).to eq 'GET /f%C3%B6o?b%C3%A4r=1 HTTP/1.1'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with redirecting URL' do
|
||||||
|
let(:url) { 'http://example.com/foo' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_request(:get, 'http://example.com/foo').to_return(status: 302, headers: { 'Location' => 'HTTPS://EXAMPLE.net/Bar' })
|
||||||
|
stub_request(:get, 'https://example.net/Bar').to_return(body: 'Lorem ipsum')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'resolves redirect' do
|
||||||
|
subject.perform do |response|
|
||||||
|
expect(response.body.to_s).to eq 'Lorem ipsum'
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(a_request(:get, 'https://example.net/Bar')).to have_been_made
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'normalizes destination scheme' do
|
||||||
|
subject.perform do |response|
|
||||||
|
expect(response.request.uri.scheme).to eq 'https'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'normalizes destination host' do
|
||||||
|
subject.perform do |response|
|
||||||
|
expect(response.request.uri.authority).to eq 'example.net'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does modify path' do
|
||||||
|
subject.perform do |response|
|
||||||
|
expect(response.request.uri.path).to eq '/Bar'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "response's body_with_limit method" do
|
describe "response's body_with_limit method" do
|
||||||
|
|
|
@ -3,21 +3,42 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe NotificationMailer do
|
RSpec.describe NotificationMailer do
|
||||||
let(:receiver) { Fabricate(:user) }
|
let(:receiver) { Fabricate(:user, account_attributes: { username: 'alice' }) }
|
||||||
let(:sender) { Fabricate(:account, username: 'bob') }
|
let(:sender) { Fabricate(:account, username: 'bob') }
|
||||||
let(:foreign_status) { Fabricate(:status, account: sender, text: 'The body of the foreign status') }
|
let(:foreign_status) { Fabricate(:status, account: sender, text: 'The body of the foreign status') }
|
||||||
let(:own_status) { Fabricate(:status, account: receiver.account, text: 'The body of the own status') }
|
let(:own_status) { Fabricate(:status, account: receiver.account, text: 'The body of the own status') }
|
||||||
|
|
||||||
|
shared_examples 'headers' do |type, thread|
|
||||||
|
it 'renders the to and from headers' do
|
||||||
|
expect(mail[:to].value).to eq "#{receiver.account.username} <#{receiver.email}>"
|
||||||
|
expect(mail.from).to eq ['notifications@localhost']
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'renders the list headers' do
|
||||||
|
expect(mail['List-ID'].value).to eq "<#{type}.alice.cb6e6126.ngrok.io>"
|
||||||
|
expect(mail['List-Unsubscribe'].value).to match(%r{<https://cb6e6126.ngrok.io/unsubscribe\?token=.+>})
|
||||||
|
expect(mail['List-Unsubscribe'].value).to match("&type=#{type}")
|
||||||
|
expect(mail['List-Unsubscribe-Post'].value).to eq 'List-Unsubscribe=One-Click'
|
||||||
|
end
|
||||||
|
|
||||||
|
if thread
|
||||||
|
it 'renders the thread headers' do
|
||||||
|
expect(mail['In-Reply-To'].value).to match(/<conversation-\d+.\d\d\d\d-\d\d-\d\d@cb6e6126.ngrok.io>/)
|
||||||
|
expect(mail['References'].value).to match(/<conversation-\d+.\d\d\d\d-\d\d-\d\d@cb6e6126.ngrok.io>/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'mention' do
|
describe 'mention' do
|
||||||
let(:mention) { Mention.create!(account: receiver.account, status: foreign_status) }
|
let(:mention) { Mention.create!(account: receiver.account, status: foreign_status) }
|
||||||
let(:notification) { Notification.create!(account: receiver.account, activity: mention) }
|
let(:notification) { Notification.create!(account: receiver.account, activity: mention) }
|
||||||
let(:mail) { prepared_mailer_for(receiver.account).mention }
|
let(:mail) { prepared_mailer_for(receiver.account).mention }
|
||||||
|
|
||||||
include_examples 'localized subject', 'notification_mailer.mention.subject', name: 'bob'
|
include_examples 'localized subject', 'notification_mailer.mention.subject', name: 'bob'
|
||||||
|
include_examples 'headers', 'mention', true
|
||||||
|
|
||||||
it 'renders the headers' do
|
it 'renders the subject' do
|
||||||
expect(mail.subject).to eq('You were mentioned by bob')
|
expect(mail.subject).to eq('You were mentioned by bob')
|
||||||
expect(mail[:to].value).to eq("#{receiver.account.username} <#{receiver.email}>")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'renders the body' do
|
it 'renders the body' do
|
||||||
|
@ -32,10 +53,10 @@ RSpec.describe NotificationMailer do
|
||||||
let(:mail) { prepared_mailer_for(receiver.account).follow }
|
let(:mail) { prepared_mailer_for(receiver.account).follow }
|
||||||
|
|
||||||
include_examples 'localized subject', 'notification_mailer.follow.subject', name: 'bob'
|
include_examples 'localized subject', 'notification_mailer.follow.subject', name: 'bob'
|
||||||
|
include_examples 'headers', 'follow', false
|
||||||
|
|
||||||
it 'renders the headers' do
|
it 'renders the subject' do
|
||||||
expect(mail.subject).to eq('bob is now following you')
|
expect(mail.subject).to eq('bob is now following you')
|
||||||
expect(mail[:to].value).to eq("#{receiver.account.username} <#{receiver.email}>")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'renders the body' do
|
it 'renders the body' do
|
||||||
|
@ -49,10 +70,10 @@ RSpec.describe NotificationMailer do
|
||||||
let(:mail) { prepared_mailer_for(own_status.account).favourite }
|
let(:mail) { prepared_mailer_for(own_status.account).favourite }
|
||||||
|
|
||||||
include_examples 'localized subject', 'notification_mailer.favourite.subject', name: 'bob'
|
include_examples 'localized subject', 'notification_mailer.favourite.subject', name: 'bob'
|
||||||
|
include_examples 'headers', 'favourite', true
|
||||||
|
|
||||||
it 'renders the headers' do
|
it 'renders the subject' do
|
||||||
expect(mail.subject).to eq('bob favorited your post')
|
expect(mail.subject).to eq('bob favorited your post')
|
||||||
expect(mail[:to].value).to eq("#{receiver.account.username} <#{receiver.email}>")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'renders the body' do
|
it 'renders the body' do
|
||||||
|
@ -67,10 +88,10 @@ RSpec.describe NotificationMailer do
|
||||||
let(:mail) { prepared_mailer_for(own_status.account).reblog }
|
let(:mail) { prepared_mailer_for(own_status.account).reblog }
|
||||||
|
|
||||||
include_examples 'localized subject', 'notification_mailer.reblog.subject', name: 'bob'
|
include_examples 'localized subject', 'notification_mailer.reblog.subject', name: 'bob'
|
||||||
|
include_examples 'headers', 'reblog', true
|
||||||
|
|
||||||
it 'renders the headers' do
|
it 'renders the subject' do
|
||||||
expect(mail.subject).to eq('bob boosted your post')
|
expect(mail.subject).to eq('bob boosted your post')
|
||||||
expect(mail[:to].value).to eq("#{receiver.account.username} <#{receiver.email}>")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'renders the body' do
|
it 'renders the body' do
|
||||||
|
@ -85,10 +106,10 @@ RSpec.describe NotificationMailer do
|
||||||
let(:mail) { prepared_mailer_for(receiver.account).follow_request }
|
let(:mail) { prepared_mailer_for(receiver.account).follow_request }
|
||||||
|
|
||||||
include_examples 'localized subject', 'notification_mailer.follow_request.subject', name: 'bob'
|
include_examples 'localized subject', 'notification_mailer.follow_request.subject', name: 'bob'
|
||||||
|
include_examples 'headers', 'follow_request', false
|
||||||
|
|
||||||
it 'renders the headers' do
|
it 'renders the subject' do
|
||||||
expect(mail.subject).to eq('Pending follower: bob')
|
expect(mail.subject).to eq('Pending follower: bob')
|
||||||
expect(mail[:to].value).to eq("#{receiver.account.username} <#{receiver.email}>")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'renders the body' do
|
it 'renders the body' do
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe AccountAlias do
|
|
||||||
end
|
|
|
@ -1,6 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe AccountDeletionRequest do
|
|
||||||
end
|
|
|
@ -1,6 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe AccountModerationNote do
|
|
||||||
end
|
|
|
@ -1,6 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe AnnouncementMute do
|
|
||||||
end
|
|
|
@ -1,6 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe AnnouncementReaction do
|
|
||||||
end
|
|
|
@ -1,6 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe Announcement do
|
|
||||||
end
|
|
|
@ -1,6 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe Backup do
|
|
||||||
end
|
|
|
@ -1,6 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe ConversationMute do
|
|
||||||
end
|
|
|
@ -1,6 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe CustomFilterKeyword do
|
|
||||||
end
|
|
|
@ -1,6 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe CustomFilter do
|
|
||||||
end
|
|
|
@ -1,6 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe Device do
|
|
||||||
end
|
|
|
@ -1,6 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe EncryptedMessage do
|
|
||||||
end
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue