Merge remote-tracking branch 'upstream/main'
This commit is contained in:
commit
f426885fa2
458 changed files with 7200 additions and 6478 deletions
|
@ -1,5 +1,5 @@
|
||||||
# For details, see https://github.com/devcontainers/images/tree/main/src/ruby
|
# For details, see https://github.com/devcontainers/images/tree/main/src/ruby
|
||||||
FROM mcr.microsoft.com/devcontainers/ruby:0-3.2-bullseye
|
FROM mcr.microsoft.com/devcontainers/ruby:1-3.2-bullseye
|
||||||
|
|
||||||
# Install Rails
|
# Install Rails
|
||||||
# RUN gem install rails webdrivers
|
# RUN gem install rails webdrivers
|
||||||
|
|
|
@ -69,7 +69,7 @@ services:
|
||||||
hard: -1
|
hard: -1
|
||||||
|
|
||||||
libretranslate:
|
libretranslate:
|
||||||
image: libretranslate/libretranslate:v1.3.10
|
image: libretranslate/libretranslate:v1.3.11
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- lt-data:/home/libretranslate/.local
|
- lt-data:/home/libretranslate/.local
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# This configuration was generated by
|
# This configuration was generated by
|
||||||
# `rubocop --auto-gen-config --auto-gen-only-exclude --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp`
|
# `rubocop --auto-gen-config --auto-gen-only-exclude --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp`
|
||||||
# using RuboCop version 1.50.2.
|
# using RuboCop version 1.52.1.
|
||||||
# The point is for the user to remove these configuration records
|
# The point is for the user to remove these configuration records
|
||||||
# one by one as the offenses are removed from the code base.
|
# one by one as the offenses are removed from the code base.
|
||||||
# Note that changes in the inspected code, or installation of new
|
# Note that changes in the inspected code, or installation of new
|
||||||
|
@ -48,18 +48,14 @@ Layout/SpaceInLambdaLiteral:
|
||||||
- 'config/environments/production.rb'
|
- 'config/environments/production.rb'
|
||||||
- 'config/initializers/content_security_policy.rb'
|
- 'config/initializers/content_security_policy.rb'
|
||||||
|
|
||||||
|
# This cop supports safe autocorrection (--autocorrect).
|
||||||
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
||||||
Lint/AmbiguousBlockAssociation:
|
Lint/AmbiguousBlockAssociation:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'spec/controllers/admin/account_moderation_notes_controller_spec.rb'
|
|
||||||
- 'spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb'
|
- 'spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb'
|
||||||
- 'spec/controllers/settings/two_factor_authentication/otp_authentication_controller_spec.rb'
|
- 'spec/controllers/settings/two_factor_authentication/otp_authentication_controller_spec.rb'
|
||||||
- 'spec/controllers/settings/two_factor_authentication/webauthn_credentials_controller_spec.rb'
|
|
||||||
- 'spec/services/activitypub/process_status_update_service_spec.rb'
|
- 'spec/services/activitypub/process_status_update_service_spec.rb'
|
||||||
- 'spec/services/post_status_service_spec.rb'
|
- 'spec/services/post_status_service_spec.rb'
|
||||||
- 'spec/services/suspend_account_service_spec.rb'
|
|
||||||
- 'spec/services/unsuspend_account_service_spec.rb'
|
|
||||||
- 'spec/workers/scheduler/accounts_statuses_cleanup_scheduler_spec.rb'
|
|
||||||
|
|
||||||
# Configuration parameters: AllowComments, AllowEmptyLambdas.
|
# Configuration parameters: AllowComments, AllowEmptyLambdas.
|
||||||
Lint/EmptyBlock:
|
Lint/EmptyBlock:
|
||||||
|
@ -124,6 +120,7 @@ Lint/UnusedBlockArgument:
|
||||||
- 'config/initializers/paperclip.rb'
|
- 'config/initializers/paperclip.rb'
|
||||||
- 'config/initializers/simple_form.rb'
|
- 'config/initializers/simple_form.rb'
|
||||||
|
|
||||||
|
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||||
Lint/UselessAssignment:
|
Lint/UselessAssignment:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'app/services/activitypub/process_status_update_service.rb'
|
- 'app/services/activitypub/process_status_update_service.rb'
|
||||||
|
@ -145,6 +142,7 @@ Lint/UselessAssignment:
|
||||||
- 'spec/services/resolve_url_service_spec.rb'
|
- 'spec/services/resolve_url_service_spec.rb'
|
||||||
- 'spec/views/statuses/show.html.haml_spec.rb'
|
- 'spec/views/statuses/show.html.haml_spec.rb'
|
||||||
|
|
||||||
|
# This cop supports safe autocorrection (--autocorrect).
|
||||||
# Configuration parameters: CheckForMethodsWithNoSideEffects.
|
# Configuration parameters: CheckForMethodsWithNoSideEffects.
|
||||||
Lint/Void:
|
Lint/Void:
|
||||||
Exclude:
|
Exclude:
|
||||||
|
@ -167,7 +165,7 @@ Metrics/CyclomaticComplexity:
|
||||||
|
|
||||||
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
||||||
Metrics/PerceivedComplexity:
|
Metrics/PerceivedComplexity:
|
||||||
Max: 28
|
Max: 27
|
||||||
|
|
||||||
Naming/AccessorMethodName:
|
Naming/AccessorMethodName:
|
||||||
Exclude:
|
Exclude:
|
||||||
|
@ -180,6 +178,7 @@ Naming/FileName:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'config/locales/sr-Latn.rb'
|
- 'config/locales/sr-Latn.rb'
|
||||||
|
|
||||||
|
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||||
# Configuration parameters: EnforcedStyleForLeadingUnderscores.
|
# Configuration parameters: EnforcedStyleForLeadingUnderscores.
|
||||||
# SupportedStylesForLeadingUnderscores: disallowed, required, optional
|
# SupportedStylesForLeadingUnderscores: disallowed, required, optional
|
||||||
Naming/MemoizedInstanceVariableName:
|
Naming/MemoizedInstanceVariableName:
|
||||||
|
@ -202,14 +201,9 @@ Naming/VariableNumber:
|
||||||
- 'db/migrate/20190820003045_update_statuses_index.rb'
|
- 'db/migrate/20190820003045_update_statuses_index.rb'
|
||||||
- 'db/migrate/20190823221802_add_local_index_to_statuses.rb'
|
- 'db/migrate/20190823221802_add_local_index_to_statuses.rb'
|
||||||
- 'db/migrate/20200119112504_add_public_index_to_statuses.rb'
|
- 'db/migrate/20200119112504_add_public_index_to_statuses.rb'
|
||||||
- 'spec/controllers/activitypub/followers_synchronizations_controller_spec.rb'
|
|
||||||
- 'spec/lib/feed_manager_spec.rb'
|
|
||||||
- 'spec/models/account_spec.rb'
|
- 'spec/models/account_spec.rb'
|
||||||
- 'spec/models/concerns/account_interactions_spec.rb'
|
|
||||||
- 'spec/models/custom_emoji_filter_spec.rb'
|
|
||||||
- 'spec/models/domain_block_spec.rb'
|
- 'spec/models/domain_block_spec.rb'
|
||||||
- 'spec/models/user_spec.rb'
|
- 'spec/models/user_spec.rb'
|
||||||
- 'spec/services/activitypub/fetch_featured_collection_service_spec.rb'
|
|
||||||
|
|
||||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||||
Performance/UnfreezeString:
|
Performance/UnfreezeString:
|
||||||
|
@ -322,11 +316,8 @@ RSpec/LetSetup:
|
||||||
- 'spec/controllers/admin/statuses_controller_spec.rb'
|
- 'spec/controllers/admin/statuses_controller_spec.rb'
|
||||||
- 'spec/controllers/api/v1/accounts/statuses_controller_spec.rb'
|
- 'spec/controllers/api/v1/accounts/statuses_controller_spec.rb'
|
||||||
- 'spec/controllers/api/v1/admin/accounts_controller_spec.rb'
|
- 'spec/controllers/api/v1/admin/accounts_controller_spec.rb'
|
||||||
- 'spec/controllers/api/v1/admin/domain_allows_controller_spec.rb'
|
|
||||||
- 'spec/controllers/api/v1/admin/domain_blocks_controller_spec.rb'
|
|
||||||
- 'spec/controllers/api/v1/filters_controller_spec.rb'
|
- 'spec/controllers/api/v1/filters_controller_spec.rb'
|
||||||
- 'spec/controllers/api/v1/followed_tags_controller_spec.rb'
|
- 'spec/controllers/api/v1/followed_tags_controller_spec.rb'
|
||||||
- 'spec/controllers/api/v1/tags_controller_spec.rb'
|
|
||||||
- 'spec/controllers/api/v2/admin/accounts_controller_spec.rb'
|
- 'spec/controllers/api/v2/admin/accounts_controller_spec.rb'
|
||||||
- 'spec/controllers/api/v2/filters/keywords_controller_spec.rb'
|
- 'spec/controllers/api/v2/filters/keywords_controller_spec.rb'
|
||||||
- 'spec/controllers/api/v2/filters/statuses_controller_spec.rb'
|
- 'spec/controllers/api/v2/filters/statuses_controller_spec.rb'
|
||||||
|
@ -363,7 +354,6 @@ RSpec/LetSetup:
|
||||||
- 'spec/services/suspend_account_service_spec.rb'
|
- 'spec/services/suspend_account_service_spec.rb'
|
||||||
- 'spec/services/unallow_domain_service_spec.rb'
|
- 'spec/services/unallow_domain_service_spec.rb'
|
||||||
- 'spec/services/unsuspend_account_service_spec.rb'
|
- 'spec/services/unsuspend_account_service_spec.rb'
|
||||||
- 'spec/workers/scheduler/accounts_statuses_cleanup_scheduler_spec.rb'
|
|
||||||
- 'spec/workers/scheduler/user_cleanup_scheduler_spec.rb'
|
- 'spec/workers/scheduler/user_cleanup_scheduler_spec.rb'
|
||||||
|
|
||||||
RSpec/MessageChain:
|
RSpec/MessageChain:
|
||||||
|
@ -396,7 +386,7 @@ RSpec/MessageSpies:
|
||||||
- 'spec/validators/status_length_validator_spec.rb'
|
- 'spec/validators/status_length_validator_spec.rb'
|
||||||
|
|
||||||
RSpec/MultipleExpectations:
|
RSpec/MultipleExpectations:
|
||||||
Max: 19
|
Max: 8
|
||||||
|
|
||||||
# Configuration parameters: AllowSubject.
|
# Configuration parameters: AllowSubject.
|
||||||
RSpec/MultipleMemoizedHelpers:
|
RSpec/MultipleMemoizedHelpers:
|
||||||
|
@ -425,7 +415,6 @@ RSpec/StubbedMock:
|
||||||
RSpec/SubjectDeclaration:
|
RSpec/SubjectDeclaration:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'spec/controllers/admin/domain_blocks_controller_spec.rb'
|
- 'spec/controllers/admin/domain_blocks_controller_spec.rb'
|
||||||
- 'spec/controllers/api/v1/admin/domain_blocks_controller_spec.rb'
|
|
||||||
- 'spec/models/account_migration_spec.rb'
|
- 'spec/models/account_migration_spec.rb'
|
||||||
- 'spec/models/account_spec.rb'
|
- 'spec/models/account_spec.rb'
|
||||||
- 'spec/models/relationship_filter_spec.rb'
|
- 'spec/models/relationship_filter_spec.rb'
|
||||||
|
@ -451,45 +440,6 @@ RSpec/SubjectStub:
|
||||||
- 'spec/services/unallow_domain_service_spec.rb'
|
- 'spec/services/unallow_domain_service_spec.rb'
|
||||||
- 'spec/validators/blacklisted_email_validator_spec.rb'
|
- 'spec/validators/blacklisted_email_validator_spec.rb'
|
||||||
|
|
||||||
# Configuration parameters: IgnoreNameless, IgnoreSymbolicNames.
|
|
||||||
RSpec/VerifiedDoubles:
|
|
||||||
Exclude:
|
|
||||||
- 'spec/controllers/admin/change_emails_controller_spec.rb'
|
|
||||||
- 'spec/controllers/admin/confirmations_controller_spec.rb'
|
|
||||||
- 'spec/controllers/admin/disputes/appeals_controller_spec.rb'
|
|
||||||
- 'spec/controllers/admin/domain_allows_controller_spec.rb'
|
|
||||||
- 'spec/controllers/admin/domain_blocks_controller_spec.rb'
|
|
||||||
- 'spec/controllers/api/v1/reports_controller_spec.rb'
|
|
||||||
- 'spec/controllers/api/web/embeds_controller_spec.rb'
|
|
||||||
- 'spec/controllers/auth/sessions_controller_spec.rb'
|
|
||||||
- 'spec/controllers/disputes/appeals_controller_spec.rb'
|
|
||||||
- 'spec/helpers/statuses_helper_spec.rb'
|
|
||||||
- 'spec/lib/suspicious_sign_in_detector_spec.rb'
|
|
||||||
- 'spec/models/account/field_spec.rb'
|
|
||||||
- 'spec/models/session_activation_spec.rb'
|
|
||||||
- 'spec/models/setting_spec.rb'
|
|
||||||
- 'spec/services/account_search_service_spec.rb'
|
|
||||||
- 'spec/services/post_status_service_spec.rb'
|
|
||||||
- 'spec/services/search_service_spec.rb'
|
|
||||||
- 'spec/validators/blacklisted_email_validator_spec.rb'
|
|
||||||
- 'spec/validators/disallowed_hashtags_validator_spec.rb'
|
|
||||||
- 'spec/validators/email_mx_validator_spec.rb'
|
|
||||||
- 'spec/validators/follow_limit_validator_spec.rb'
|
|
||||||
- 'spec/validators/note_length_validator_spec.rb'
|
|
||||||
- 'spec/validators/poll_validator_spec.rb'
|
|
||||||
- 'spec/validators/status_length_validator_spec.rb'
|
|
||||||
- 'spec/validators/status_pin_validator_spec.rb'
|
|
||||||
- 'spec/validators/unique_username_validator_spec.rb'
|
|
||||||
- 'spec/validators/unreserved_username_validator_spec.rb'
|
|
||||||
- 'spec/validators/url_validator_spec.rb'
|
|
||||||
- 'spec/views/statuses/show.html.haml_spec.rb'
|
|
||||||
- 'spec/workers/activitypub/processing_worker_spec.rb'
|
|
||||||
- 'spec/workers/admin/domain_purge_worker_spec.rb'
|
|
||||||
- 'spec/workers/domain_block_worker_spec.rb'
|
|
||||||
- 'spec/workers/domain_clear_media_worker_spec.rb'
|
|
||||||
- 'spec/workers/feed_insert_worker_spec.rb'
|
|
||||||
- 'spec/workers/regeneration_worker_spec.rb'
|
|
||||||
|
|
||||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||||
Rails/ApplicationController:
|
Rails/ApplicationController:
|
||||||
Exclude:
|
Exclude:
|
||||||
|
@ -600,7 +550,6 @@ Rails/NegateInclude:
|
||||||
- 'app/models/concerns/attachmentable.rb'
|
- 'app/models/concerns/attachmentable.rb'
|
||||||
- 'app/models/concerns/remotable.rb'
|
- 'app/models/concerns/remotable.rb'
|
||||||
- 'app/models/custom_filter.rb'
|
- 'app/models/custom_filter.rb'
|
||||||
- 'app/models/webhook.rb'
|
|
||||||
- 'app/services/activitypub/process_status_update_service.rb'
|
- 'app/services/activitypub/process_status_update_service.rb'
|
||||||
- 'app/services/fetch_link_card_service.rb'
|
- 'app/services/fetch_link_card_service.rb'
|
||||||
- 'app/services/search_service.rb'
|
- 'app/services/search_service.rb'
|
||||||
|
@ -771,11 +720,8 @@ Rails/WhereExists:
|
||||||
- 'app/workers/move_worker.rb'
|
- 'app/workers/move_worker.rb'
|
||||||
- 'db/migrate/20190529143559_preserve_old_layout_for_existing_users.rb'
|
- 'db/migrate/20190529143559_preserve_old_layout_for_existing_users.rb'
|
||||||
- 'lib/tasks/tests.rake'
|
- 'lib/tasks/tests.rake'
|
||||||
- 'spec/controllers/api/v1/accounts/notes_controller_spec.rb'
|
|
||||||
- 'spec/controllers/api/v1/tags_controller_spec.rb'
|
|
||||||
- 'spec/models/account_spec.rb'
|
- 'spec/models/account_spec.rb'
|
||||||
- 'spec/services/activitypub/process_collection_service_spec.rb'
|
- 'spec/services/activitypub/process_collection_service_spec.rb'
|
||||||
- 'spec/services/post_status_service_spec.rb'
|
|
||||||
- 'spec/services/purge_domain_service_spec.rb'
|
- 'spec/services/purge_domain_service_spec.rb'
|
||||||
- 'spec/services/unallow_domain_service_spec.rb'
|
- 'spec/services/unallow_domain_service_spec.rb'
|
||||||
|
|
||||||
|
@ -797,6 +743,7 @@ Style/ClassVars:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'config/initializers/devise.rb'
|
- 'config/initializers/devise.rb'
|
||||||
|
|
||||||
|
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||||
Style/CombinableLoops:
|
Style/CombinableLoops:
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'app/models/form/custom_emoji_batch.rb'
|
- 'app/models/form/custom_emoji_batch.rb'
|
||||||
|
|
170
Gemfile.lock
170
Gemfile.lock
|
@ -18,40 +18,40 @@ GIT
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actioncable (6.1.7.3)
|
actioncable (6.1.7.4)
|
||||||
actionpack (= 6.1.7.3)
|
actionpack (= 6.1.7.4)
|
||||||
activesupport (= 6.1.7.3)
|
activesupport (= 6.1.7.4)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
actionmailbox (6.1.7.3)
|
actionmailbox (6.1.7.4)
|
||||||
actionpack (= 6.1.7.3)
|
actionpack (= 6.1.7.4)
|
||||||
activejob (= 6.1.7.3)
|
activejob (= 6.1.7.4)
|
||||||
activerecord (= 6.1.7.3)
|
activerecord (= 6.1.7.4)
|
||||||
activestorage (= 6.1.7.3)
|
activestorage (= 6.1.7.4)
|
||||||
activesupport (= 6.1.7.3)
|
activesupport (= 6.1.7.4)
|
||||||
mail (>= 2.7.1)
|
mail (>= 2.7.1)
|
||||||
actionmailer (6.1.7.3)
|
actionmailer (6.1.7.4)
|
||||||
actionpack (= 6.1.7.3)
|
actionpack (= 6.1.7.4)
|
||||||
actionview (= 6.1.7.3)
|
actionview (= 6.1.7.4)
|
||||||
activejob (= 6.1.7.3)
|
activejob (= 6.1.7.4)
|
||||||
activesupport (= 6.1.7.3)
|
activesupport (= 6.1.7.4)
|
||||||
mail (~> 2.5, >= 2.5.4)
|
mail (~> 2.5, >= 2.5.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
actionpack (6.1.7.3)
|
actionpack (6.1.7.4)
|
||||||
actionview (= 6.1.7.3)
|
actionview (= 6.1.7.4)
|
||||||
activesupport (= 6.1.7.3)
|
activesupport (= 6.1.7.4)
|
||||||
rack (~> 2.0, >= 2.0.9)
|
rack (~> 2.0, >= 2.0.9)
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||||
actiontext (6.1.7.3)
|
actiontext (6.1.7.4)
|
||||||
actionpack (= 6.1.7.3)
|
actionpack (= 6.1.7.4)
|
||||||
activerecord (= 6.1.7.3)
|
activerecord (= 6.1.7.4)
|
||||||
activestorage (= 6.1.7.3)
|
activestorage (= 6.1.7.4)
|
||||||
activesupport (= 6.1.7.3)
|
activesupport (= 6.1.7.4)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (6.1.7.3)
|
actionview (6.1.7.4)
|
||||||
activesupport (= 6.1.7.3)
|
activesupport (= 6.1.7.4)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.4)
|
erubi (~> 1.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
|
@ -61,22 +61,22 @@ GEM
|
||||||
activemodel (>= 4.1, < 7.1)
|
activemodel (>= 4.1, < 7.1)
|
||||||
case_transform (>= 0.2)
|
case_transform (>= 0.2)
|
||||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||||
activejob (6.1.7.3)
|
activejob (6.1.7.4)
|
||||||
activesupport (= 6.1.7.3)
|
activesupport (= 6.1.7.4)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (6.1.7.3)
|
activemodel (6.1.7.4)
|
||||||
activesupport (= 6.1.7.3)
|
activesupport (= 6.1.7.4)
|
||||||
activerecord (6.1.7.3)
|
activerecord (6.1.7.4)
|
||||||
activemodel (= 6.1.7.3)
|
activemodel (= 6.1.7.4)
|
||||||
activesupport (= 6.1.7.3)
|
activesupport (= 6.1.7.4)
|
||||||
activestorage (6.1.7.3)
|
activestorage (6.1.7.4)
|
||||||
actionpack (= 6.1.7.3)
|
actionpack (= 6.1.7.4)
|
||||||
activejob (= 6.1.7.3)
|
activejob (= 6.1.7.4)
|
||||||
activerecord (= 6.1.7.3)
|
activerecord (= 6.1.7.4)
|
||||||
activesupport (= 6.1.7.3)
|
activesupport (= 6.1.7.4)
|
||||||
marcel (~> 1.0)
|
marcel (~> 1.0)
|
||||||
mini_mime (>= 1.1.0)
|
mini_mime (>= 1.1.0)
|
||||||
activesupport (6.1.7.3)
|
activesupport (6.1.7.4)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
i18n (>= 1.6, < 2)
|
i18n (>= 1.6, < 2)
|
||||||
minitest (>= 5.1)
|
minitest (>= 5.1)
|
||||||
|
@ -97,26 +97,26 @@ GEM
|
||||||
attr_required (1.0.1)
|
attr_required (1.0.1)
|
||||||
awrence (1.2.1)
|
awrence (1.2.1)
|
||||||
aws-eventstream (1.2.0)
|
aws-eventstream (1.2.0)
|
||||||
aws-partitions (1.772.0)
|
aws-partitions (1.780.0)
|
||||||
aws-sdk-core (3.174.0)
|
aws-sdk-core (3.175.0)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
aws-partitions (~> 1, >= 1.651.0)
|
aws-partitions (~> 1, >= 1.651.0)
|
||||||
aws-sigv4 (~> 1.5)
|
aws-sigv4 (~> 1.5)
|
||||||
jmespath (~> 1, >= 1.6.1)
|
jmespath (~> 1, >= 1.6.1)
|
||||||
aws-sdk-kms (1.65.0)
|
aws-sdk-kms (1.67.0)
|
||||||
aws-sdk-core (~> 3, >= 3.174.0)
|
aws-sdk-core (~> 3, >= 3.174.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.123.0)
|
aws-sdk-s3 (1.126.0)
|
||||||
aws-sdk-core (~> 3, >= 3.174.0)
|
aws-sdk-core (~> 3, >= 3.174.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.4)
|
aws-sigv4 (~> 1.4)
|
||||||
aws-sigv4 (1.5.2)
|
aws-sigv4 (1.5.2)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
bcrypt (3.1.18)
|
bcrypt (3.1.18)
|
||||||
better_errors (2.9.1)
|
better_errors (2.10.1)
|
||||||
coderay (>= 1.0.0)
|
|
||||||
erubi (>= 1.0.0)
|
erubi (>= 1.0.0)
|
||||||
rack (>= 0.9.0)
|
rack (>= 0.9.0)
|
||||||
|
rouge (>= 1.0.0)
|
||||||
better_html (2.0.1)
|
better_html (2.0.1)
|
||||||
actionview (>= 6.0)
|
actionview (>= 6.0)
|
||||||
activesupport (>= 6.0)
|
activesupport (>= 6.0)
|
||||||
|
@ -154,7 +154,7 @@ GEM
|
||||||
sshkit (~> 1.3)
|
sshkit (~> 1.3)
|
||||||
capistrano-yarn (2.0.2)
|
capistrano-yarn (2.0.2)
|
||||||
capistrano (~> 3.0)
|
capistrano (~> 3.0)
|
||||||
capybara (3.39.1)
|
capybara (3.39.2)
|
||||||
addressable
|
addressable
|
||||||
matrix
|
matrix
|
||||||
mini_mime (>= 0.1.3)
|
mini_mime (>= 0.1.3)
|
||||||
|
@ -174,7 +174,6 @@ GEM
|
||||||
chunky_png (1.4.0)
|
chunky_png (1.4.0)
|
||||||
climate_control (0.2.0)
|
climate_control (0.2.0)
|
||||||
cocoon (1.2.15)
|
cocoon (1.2.15)
|
||||||
coderay (1.1.3)
|
|
||||||
color_diff (0.1)
|
color_diff (0.1)
|
||||||
concurrent-ruby (1.2.2)
|
concurrent-ruby (1.2.2)
|
||||||
connection_pool (2.4.1)
|
connection_pool (2.4.1)
|
||||||
|
@ -229,7 +228,7 @@ GEM
|
||||||
erubi (1.12.0)
|
erubi (1.12.0)
|
||||||
et-orbi (1.2.7)
|
et-orbi (1.2.7)
|
||||||
tzinfo
|
tzinfo
|
||||||
excon (0.99.0)
|
excon (0.100.0)
|
||||||
fabrication (2.30.0)
|
fabrication (2.30.0)
|
||||||
faker (3.2.0)
|
faker (3.2.0)
|
||||||
i18n (>= 1.8.11, < 2)
|
i18n (>= 1.8.11, < 2)
|
||||||
|
@ -319,7 +318,7 @@ GEM
|
||||||
httplog (1.6.2)
|
httplog (1.6.2)
|
||||||
rack (>= 2.0)
|
rack (>= 2.0)
|
||||||
rainbow (>= 2.0.0)
|
rainbow (>= 2.0.0)
|
||||||
i18n (1.13.0)
|
i18n (1.14.1)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
i18n-tasks (1.0.12)
|
i18n-tasks (1.0.12)
|
||||||
activesupport (>= 4.0.2)
|
activesupport (>= 4.0.2)
|
||||||
|
@ -355,7 +354,7 @@ GEM
|
||||||
json-schema (4.0.0)
|
json-schema (4.0.0)
|
||||||
addressable (>= 2.8)
|
addressable (>= 2.8)
|
||||||
jsonapi-renderer (0.2.2)
|
jsonapi-renderer (0.2.2)
|
||||||
jwt (2.7.0)
|
jwt (2.7.1)
|
||||||
kaminari (1.2.2)
|
kaminari (1.2.2)
|
||||||
activesupport (>= 4.1.0)
|
activesupport (>= 4.1.0)
|
||||||
kaminari-actionview (= 1.2.2)
|
kaminari-actionview (= 1.2.2)
|
||||||
|
@ -413,13 +412,13 @@ GEM
|
||||||
mime-types-data (3.2023.0218.1)
|
mime-types-data (3.2023.0218.1)
|
||||||
mini_mime (1.1.2)
|
mini_mime (1.1.2)
|
||||||
mini_portile2 (2.8.2)
|
mini_portile2 (2.8.2)
|
||||||
minitest (5.18.0)
|
minitest (5.18.1)
|
||||||
msgpack (1.7.0)
|
msgpack (1.7.1)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
multipart-post (2.3.0)
|
multipart-post (2.3.0)
|
||||||
net-http (0.3.2)
|
net-http (0.3.2)
|
||||||
uri
|
uri
|
||||||
net-imap (0.3.4)
|
net-imap (0.3.6)
|
||||||
date
|
date
|
||||||
net-protocol
|
net-protocol
|
||||||
net-ldap (0.18.0)
|
net-ldap (0.18.0)
|
||||||
|
@ -436,7 +435,7 @@ GEM
|
||||||
nokogiri (1.15.2)
|
nokogiri (1.15.2)
|
||||||
mini_portile2 (~> 2.8.2)
|
mini_portile2 (~> 2.8.2)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
oj (3.14.3)
|
oj (3.15.0)
|
||||||
omniauth (1.9.2)
|
omniauth (1.9.2)
|
||||||
hashie (>= 3.4.6)
|
hashie (>= 3.4.6)
|
||||||
rack (>= 1.6.2, < 3)
|
rack (>= 1.6.2, < 3)
|
||||||
|
@ -470,8 +469,9 @@ GEM
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
ox (2.14.16)
|
ox (2.14.16)
|
||||||
parallel (1.23.0)
|
parallel (1.23.0)
|
||||||
parser (3.2.2.1)
|
parser (3.2.2.3)
|
||||||
ast (~> 2.4.1)
|
ast (~> 2.4.1)
|
||||||
|
racc
|
||||||
parslet (2.0.0)
|
parslet (2.0.0)
|
||||||
pastel (0.8.0)
|
pastel (0.8.0)
|
||||||
tty-color (~> 0.5)
|
tty-color (~> 0.5)
|
||||||
|
@ -495,7 +495,7 @@ GEM
|
||||||
pundit (2.3.0)
|
pundit (2.3.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
raabro (1.4.0)
|
raabro (1.4.0)
|
||||||
racc (1.6.2)
|
racc (1.7.1)
|
||||||
rack (2.2.7)
|
rack (2.2.7)
|
||||||
rack-attack (6.6.1)
|
rack-attack (6.6.1)
|
||||||
rack (>= 1.0, < 3)
|
rack (>= 1.0, < 3)
|
||||||
|
@ -511,20 +511,20 @@ GEM
|
||||||
rack
|
rack
|
||||||
rack-test (2.1.0)
|
rack-test (2.1.0)
|
||||||
rack (>= 1.3)
|
rack (>= 1.3)
|
||||||
rails (6.1.7.3)
|
rails (6.1.7.4)
|
||||||
actioncable (= 6.1.7.3)
|
actioncable (= 6.1.7.4)
|
||||||
actionmailbox (= 6.1.7.3)
|
actionmailbox (= 6.1.7.4)
|
||||||
actionmailer (= 6.1.7.3)
|
actionmailer (= 6.1.7.4)
|
||||||
actionpack (= 6.1.7.3)
|
actionpack (= 6.1.7.4)
|
||||||
actiontext (= 6.1.7.3)
|
actiontext (= 6.1.7.4)
|
||||||
actionview (= 6.1.7.3)
|
actionview (= 6.1.7.4)
|
||||||
activejob (= 6.1.7.3)
|
activejob (= 6.1.7.4)
|
||||||
activemodel (= 6.1.7.3)
|
activemodel (= 6.1.7.4)
|
||||||
activerecord (= 6.1.7.3)
|
activerecord (= 6.1.7.4)
|
||||||
activestorage (= 6.1.7.3)
|
activestorage (= 6.1.7.4)
|
||||||
activesupport (= 6.1.7.3)
|
activesupport (= 6.1.7.4)
|
||||||
bundler (>= 1.15.0)
|
bundler (>= 1.15.0)
|
||||||
railties (= 6.1.7.3)
|
railties (= 6.1.7.4)
|
||||||
sprockets-rails (>= 2.0.0)
|
sprockets-rails (>= 2.0.0)
|
||||||
rails-controller-testing (1.0.5)
|
rails-controller-testing (1.0.5)
|
||||||
actionpack (>= 5.0.1.rc1)
|
actionpack (>= 5.0.1.rc1)
|
||||||
|
@ -539,25 +539,25 @@ GEM
|
||||||
rails-i18n (6.0.0)
|
rails-i18n (6.0.0)
|
||||||
i18n (>= 0.7, < 2)
|
i18n (>= 0.7, < 2)
|
||||||
railties (>= 6.0.0, < 7)
|
railties (>= 6.0.0, < 7)
|
||||||
railties (6.1.7.3)
|
railties (6.1.7.4)
|
||||||
actionpack (= 6.1.7.3)
|
actionpack (= 6.1.7.4)
|
||||||
activesupport (= 6.1.7.3)
|
activesupport (= 6.1.7.4)
|
||||||
method_source
|
method_source
|
||||||
rake (>= 12.2)
|
rake (>= 12.2)
|
||||||
thor (~> 1.0)
|
thor (~> 1.0)
|
||||||
rainbow (3.1.1)
|
rainbow (3.1.1)
|
||||||
rake (13.0.6)
|
rake (13.0.6)
|
||||||
rdf (3.2.10)
|
rdf (3.2.11)
|
||||||
link_header (~> 0.0, >= 0.0.8)
|
link_header (~> 0.0, >= 0.0.8)
|
||||||
rdf-normalize (0.5.1)
|
rdf-normalize (0.6.0)
|
||||||
rdf (~> 3.2)
|
rdf (~> 3.2)
|
||||||
redcarpet (3.6.0)
|
redcarpet (3.6.0)
|
||||||
redis (4.8.1)
|
redis (4.8.1)
|
||||||
redis-namespace (1.10.0)
|
redis-namespace (1.11.0)
|
||||||
redis (>= 4)
|
redis (>= 4)
|
||||||
redlock (1.3.2)
|
redlock (1.3.2)
|
||||||
redis (>= 3.0.0, < 6.0)
|
redis (>= 3.0.0, < 6.0)
|
||||||
regexp_parser (2.8.0)
|
regexp_parser (2.8.1)
|
||||||
request_store (1.5.1)
|
request_store (1.5.1)
|
||||||
rack (>= 1.4)
|
rack (>= 1.4)
|
||||||
responders (3.1.0)
|
responders (3.1.0)
|
||||||
|
@ -565,6 +565,7 @@ GEM
|
||||||
railties (>= 5.2)
|
railties (>= 5.2)
|
||||||
rexml (3.2.5)
|
rexml (3.2.5)
|
||||||
rotp (6.2.2)
|
rotp (6.2.2)
|
||||||
|
rouge (4.1.2)
|
||||||
rpam2 (4.0.2)
|
rpam2 (4.0.2)
|
||||||
rqrcode (2.2.0)
|
rqrcode (2.2.0)
|
||||||
chunky_png (~> 1.0)
|
chunky_png (~> 1.0)
|
||||||
|
@ -591,20 +592,22 @@ GEM
|
||||||
sidekiq (>= 2.4.0)
|
sidekiq (>= 2.4.0)
|
||||||
rspec-support (3.12.0)
|
rspec-support (3.12.0)
|
||||||
rspec_chunked (0.6)
|
rspec_chunked (0.6)
|
||||||
rubocop (1.51.0)
|
rubocop (1.52.1)
|
||||||
json (~> 2.3)
|
json (~> 2.3)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
parser (>= 3.2.0.0)
|
parser (>= 3.2.2.3)
|
||||||
rainbow (>= 2.2.2, < 4.0)
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
regexp_parser (>= 1.8, < 3.0)
|
regexp_parser (>= 1.8, < 3.0)
|
||||||
rexml (>= 3.2.5, < 4.0)
|
rexml (>= 3.2.5, < 4.0)
|
||||||
rubocop-ast (>= 1.28.0, < 2.0)
|
rubocop-ast (>= 1.28.0, < 2.0)
|
||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (>= 2.4.0, < 3.0)
|
unicode-display_width (>= 2.4.0, < 3.0)
|
||||||
rubocop-ast (1.28.1)
|
rubocop-ast (1.29.0)
|
||||||
parser (>= 3.2.1.0)
|
parser (>= 3.2.1.0)
|
||||||
rubocop-capybara (2.18.0)
|
rubocop-capybara (2.18.0)
|
||||||
rubocop (~> 1.41)
|
rubocop (~> 1.41)
|
||||||
|
rubocop-factory_bot (2.23.1)
|
||||||
|
rubocop (~> 1.33)
|
||||||
rubocop-performance (1.18.0)
|
rubocop-performance (1.18.0)
|
||||||
rubocop (>= 1.7.0, < 2.0)
|
rubocop (>= 1.7.0, < 2.0)
|
||||||
rubocop-ast (>= 0.4.0)
|
rubocop-ast (>= 0.4.0)
|
||||||
|
@ -612,16 +615,17 @@ GEM
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
rack (>= 1.1)
|
rack (>= 1.1)
|
||||||
rubocop (>= 1.33.0, < 2.0)
|
rubocop (>= 1.33.0, < 2.0)
|
||||||
rubocop-rspec (2.19.0)
|
rubocop-rspec (2.22.0)
|
||||||
rubocop (~> 1.33)
|
rubocop (~> 1.33)
|
||||||
rubocop-capybara (~> 2.17)
|
rubocop-capybara (~> 2.17)
|
||||||
|
rubocop-factory_bot (~> 2.22)
|
||||||
ruby-progressbar (1.13.0)
|
ruby-progressbar (1.13.0)
|
||||||
ruby-saml (1.13.0)
|
ruby-saml (1.15.0)
|
||||||
nokogiri (>= 1.10.5)
|
nokogiri (>= 1.13.10)
|
||||||
rexml
|
rexml
|
||||||
ruby2_keywords (0.0.5)
|
ruby2_keywords (0.0.5)
|
||||||
rubyzip (2.3.2)
|
rubyzip (2.3.2)
|
||||||
rufus-scheduler (3.8.2)
|
rufus-scheduler (3.9.1)
|
||||||
fugit (~> 1.1, >= 1.1.6)
|
fugit (~> 1.1, >= 1.1.6)
|
||||||
safety_net_attestation (0.4.0)
|
safety_net_attestation (0.4.0)
|
||||||
jwt (~> 2.0)
|
jwt (~> 2.0)
|
||||||
|
@ -680,13 +684,13 @@ GEM
|
||||||
attr_required (>= 0.0.5)
|
attr_required (>= 0.0.5)
|
||||||
httpclient (>= 2.4)
|
httpclient (>= 2.4)
|
||||||
sysexits (1.2.0)
|
sysexits (1.2.0)
|
||||||
temple (0.10.0)
|
temple (0.10.2)
|
||||||
terminal-table (3.0.2)
|
terminal-table (3.0.2)
|
||||||
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)
|
||||||
thor (1.2.2)
|
thor (1.2.2)
|
||||||
tilt (2.1.0)
|
tilt (2.2.0)
|
||||||
timeout (0.3.2)
|
timeout (0.3.2)
|
||||||
tpm-key_attestation (0.12.0)
|
tpm-key_attestation (0.12.0)
|
||||||
bindata (~> 2.4)
|
bindata (~> 2.4)
|
||||||
|
|
|
@ -28,6 +28,7 @@ module Admin
|
||||||
authorize :webhook, :create?
|
authorize :webhook, :create?
|
||||||
|
|
||||||
@webhook = Webhook.new(resource_params)
|
@webhook = Webhook.new(resource_params)
|
||||||
|
@webhook.current_account = current_account
|
||||||
|
|
||||||
if @webhook.save
|
if @webhook.save
|
||||||
redirect_to admin_webhook_path(@webhook)
|
redirect_to admin_webhook_path(@webhook)
|
||||||
|
@ -39,10 +40,12 @@ module Admin
|
||||||
def update
|
def update
|
||||||
authorize @webhook, :update?
|
authorize @webhook, :update?
|
||||||
|
|
||||||
|
@webhook.current_account = current_account
|
||||||
|
|
||||||
if @webhook.update(resource_params)
|
if @webhook.update(resource_params)
|
||||||
redirect_to admin_webhook_path(@webhook)
|
redirect_to admin_webhook_path(@webhook)
|
||||||
else
|
else
|
||||||
render :show
|
render :edit
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,11 @@ class Api::V1::ConversationsController < Api::BaseController
|
||||||
render json: @conversation, serializer: REST::ConversationSerializer
|
render json: @conversation, serializer: REST::ConversationSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def unread
|
||||||
|
@conversation.update!(unread: true)
|
||||||
|
render json: @conversation, serializer: REST::ConversationSerializer
|
||||||
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
@conversation.destroy!
|
@conversation.destroy!
|
||||||
render_empty
|
render_empty
|
||||||
|
@ -45,7 +50,7 @@ class Api::V1::ConversationsController < Api::BaseController
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
.to_a_paginated_by_id(limit_param(LIMIT), **params_slice(:max_id, :since_id, :min_id))
|
.to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
|
||||||
end
|
end
|
||||||
|
|
||||||
def insert_pagination_headers
|
def insert_pagination_headers
|
||||||
|
|
|
@ -8,11 +8,15 @@ class Api::V1::Statuses::HistoriesController < Api::BaseController
|
||||||
|
|
||||||
def show
|
def show
|
||||||
cache_if_unauthenticated!
|
cache_if_unauthenticated!
|
||||||
render json: @status.edits.includes(:account, status: [:account]), each_serializer: REST::StatusEditSerializer
|
render json: status_edits, each_serializer: REST::StatusEditSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def status_edits
|
||||||
|
@status.edits.includes(:account, status: [:account]).to_a.presence || [@status.build_snapshot(at_time: @status.edited_at || @status.created_at)]
|
||||||
|
end
|
||||||
|
|
||||||
def set_status
|
def set_status
|
||||||
@status = Status.find(params[:status_id])
|
@status = Status.find(params[:status_id])
|
||||||
authorize @status, :show?
|
authorize @status, :show?
|
||||||
|
|
|
@ -18,6 +18,14 @@ class Api::V2::Admin::AccountsController < Api::V1::Admin::AccountsController
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def next_path
|
||||||
|
api_v2_admin_accounts_url(pagination_params(max_id: pagination_max_id)) if records_continue?
|
||||||
|
end
|
||||||
|
|
||||||
|
def prev_path
|
||||||
|
api_v2_admin_accounts_url(pagination_params(min_id: pagination_since_id)) unless @accounts.empty?
|
||||||
|
end
|
||||||
|
|
||||||
def filtered_accounts
|
def filtered_accounts
|
||||||
AccountFilter.new(translated_filter_params).results
|
AccountFilter.new(translated_filter_params).results
|
||||||
end
|
end
|
||||||
|
|
|
@ -88,8 +88,10 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
|
||||||
def after_confirmation_path_for(_resource_name, user)
|
def after_confirmation_path_for(_resource_name, user)
|
||||||
if user.created_by_application && truthy_param?(:redirect_to_app)
|
if user.created_by_application && truthy_param?(:redirect_to_app)
|
||||||
user.created_by_application.confirmation_redirect_uri
|
user.created_by_application.confirmation_redirect_uri
|
||||||
|
elsif user_signed_in?
|
||||||
|
web_url('start')
|
||||||
else
|
else
|
||||||
super
|
new_user_session_path
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
module CaptchaConcern
|
module CaptchaConcern
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
include Hcaptcha::Adapters::ViewMethods
|
include Hcaptcha::Adapters::ViewMethods
|
||||||
|
|
||||||
included do
|
included do
|
||||||
|
@ -35,18 +36,22 @@ module CaptchaConcern
|
||||||
flash.delete(:hcaptcha_error)
|
flash.delete(:hcaptcha_error)
|
||||||
yield message
|
yield message
|
||||||
end
|
end
|
||||||
|
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def extend_csp_for_captcha!
|
def extend_csp_for_captcha!
|
||||||
policy = request.content_security_policy
|
policy = request.content_security_policy
|
||||||
|
|
||||||
return unless captcha_required? && policy.present?
|
return unless captcha_required? && policy.present?
|
||||||
|
|
||||||
%w(script_src frame_src style_src connect_src).each do |directive|
|
%w(script_src frame_src style_src connect_src).each do |directive|
|
||||||
values = policy.send(directive)
|
values = policy.send(directive)
|
||||||
|
|
||||||
values << 'https://hcaptcha.com' unless values.include?('https://hcaptcha.com') || values.include?('https:')
|
values << 'https://hcaptcha.com' unless values.include?('https://hcaptcha.com') || values.include?('https:')
|
||||||
values << 'https://*.hcaptcha.com' unless values.include?('https://*.hcaptcha.com') || values.include?('https:')
|
values << 'https://*.hcaptcha.com' unless values.include?('https://*.hcaptcha.com') || values.include?('https:')
|
||||||
|
|
||||||
policy.send(directive, *values)
|
policy.send(directive, *values)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
41
app/controllers/mail_subscriptions_controller.rb
Normal file
41
app/controllers/mail_subscriptions_controller.rb
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class MailSubscriptionsController < ApplicationController
|
||||||
|
layout 'auth'
|
||||||
|
|
||||||
|
skip_before_action :require_functional!
|
||||||
|
|
||||||
|
before_action :set_body_classes
|
||||||
|
before_action :set_user
|
||||||
|
before_action :set_type
|
||||||
|
|
||||||
|
def show; end
|
||||||
|
|
||||||
|
def create
|
||||||
|
@user.settings[email_type_from_param] = false
|
||||||
|
@user.save!
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_user
|
||||||
|
@user = GlobalID::Locator.locate_signed(params[:token], for: 'unsubscribe')
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_body_classes
|
||||||
|
@body_classes = 'lighter'
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_type
|
||||||
|
@type = email_type_from_param
|
||||||
|
end
|
||||||
|
|
||||||
|
def email_type_from_param
|
||||||
|
case params[:type]
|
||||||
|
when 'follow', 'reblog', 'favourite', 'mention', 'follow_request'
|
||||||
|
"notification_emails.#{params[:type]}"
|
||||||
|
else
|
||||||
|
raise ArgumentError
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
15
app/controllers/settings/verifications_controller.rb
Normal file
15
app/controllers/settings/verifications_controller.rb
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Settings::VerificationsController < Settings::BaseController
|
||||||
|
before_action :set_account
|
||||||
|
|
||||||
|
def show
|
||||||
|
@verified_links = @account.fields.select(&:verified?)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_account
|
||||||
|
@account = current_account
|
||||||
|
end
|
||||||
|
end
|
|
@ -24,13 +24,4 @@ module SettingsHelper
|
||||||
safe_join([image_tag(account.avatar.url, width: 15, height: 15, alt: display_name(account), class: 'avatar'), content_tag(:span, account.acct, class: 'username')], ' ')
|
safe_join([image_tag(account.avatar.url, width: 15, height: 15, alt: display_name(account), class: 'avatar'), content_tag(:span, account.acct, class: 'username')], ' ')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def picture_hint(hint, picture)
|
|
||||||
if picture.original_filename.nil?
|
|
||||||
hint
|
|
||||||
else
|
|
||||||
link = link_to t('generic.delete'), settings_profile_picture_path(picture.name.to_s), data: { method: :delete }
|
|
||||||
safe_join([hint, link], '<br/>'.html_safe)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { PureComponent } from 'react';
|
|
||||||
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
import ShortNumber from 'flavours/glitch/components/short_number';
|
|
||||||
|
|
||||||
export default class AutosuggestHashtag extends PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
tag: PropTypes.shape({
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
url: PropTypes.string,
|
|
||||||
history: PropTypes.array,
|
|
||||||
}).isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { tag } = this.props;
|
|
||||||
const weeklyUses = tag.history && (
|
|
||||||
<ShortNumber
|
|
||||||
value={tag.history.reduce((total, day) => total + day.uses * 1, 0)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='autosuggest-hashtag'>
|
|
||||||
<div className='autosuggest-hashtag__name'>
|
|
||||||
#<strong>{tag.name}</strong>
|
|
||||||
</div>
|
|
||||||
{tag.history !== undefined && (
|
|
||||||
<div className='autosuggest-hashtag__uses'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='autosuggest_hashtag.per_week'
|
|
||||||
defaultMessage='{count} per week'
|
|
||||||
values={{ count: weeklyUses }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import ShortNumber from 'flavours/glitch/components/short_number';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
tag: {
|
||||||
|
name: string;
|
||||||
|
url?: string;
|
||||||
|
history?: Array<{
|
||||||
|
uses: number;
|
||||||
|
accounts: string;
|
||||||
|
day: string;
|
||||||
|
}>;
|
||||||
|
following?: boolean;
|
||||||
|
type: 'hashtag';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AutosuggestHashtag: React.FC<Props> = ({ tag }) => {
|
||||||
|
const weeklyUses = tag.history && (
|
||||||
|
<ShortNumber
|
||||||
|
value={tag.history.reduce((total, day) => total + day.uses * 1, 0)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='autosuggest-hashtag'>
|
||||||
|
<div className='autosuggest-hashtag__name'>
|
||||||
|
#<strong>{tag.name}</strong>
|
||||||
|
</div>
|
||||||
|
{tag.history !== undefined && (
|
||||||
|
<div className='autosuggest-hashtag__uses'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='autosuggest_hashtag.per_week'
|
||||||
|
defaultMessage='{count} per week'
|
||||||
|
values={{ count: weeklyUses }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -8,9 +8,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import AutosuggestAccountContainer from 'flavours/glitch/features/compose/containers/autosuggest_account_container';
|
import AutosuggestAccountContainer from 'flavours/glitch/features/compose/containers/autosuggest_account_container';
|
||||||
|
|
||||||
import AutosuggestEmoji from './autosuggest_emoji';
|
import AutosuggestEmoji from './autosuggest_emoji';
|
||||||
import AutosuggestHashtag from './autosuggest_hashtag';
|
import { AutosuggestHashtag } from './autosuggest_hashtag';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const textAtCursorMatchesToken = (str, caretPosition, searchTokens) => {
|
const textAtCursorMatchesToken = (str, caretPosition, searchTokens) => {
|
||||||
let word;
|
let word;
|
||||||
|
|
|
@ -10,7 +10,7 @@ import Textarea from 'react-textarea-autosize';
|
||||||
import AutosuggestAccountContainer from 'flavours/glitch/features/compose/containers/autosuggest_account_container';
|
import AutosuggestAccountContainer from 'flavours/glitch/features/compose/containers/autosuggest_account_container';
|
||||||
|
|
||||||
import AutosuggestEmoji from './autosuggest_emoji';
|
import AutosuggestEmoji from './autosuggest_emoji';
|
||||||
import AutosuggestHashtag from './autosuggest_hashtag';
|
import { AutosuggestHashtag } from './autosuggest_hashtag';
|
||||||
|
|
||||||
const textAtCursorMatchesToken = (str, caretPosition) => {
|
const textAtCursorMatchesToken = (str, caretPosition) => {
|
||||||
let word;
|
let word;
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
interface Props {
|
||||||
|
size: number;
|
||||||
|
strokeWidth: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CircularProgress: React.FC<Props> = ({ size, strokeWidth }) => {
|
||||||
|
const viewBox = `0 0 ${size} ${size}`;
|
||||||
|
const radius = (size - strokeWidth) / 2;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
viewBox={viewBox}
|
||||||
|
className='circular-progress'
|
||||||
|
role='progressbar'
|
||||||
|
>
|
||||||
|
<circle
|
||||||
|
fill='none'
|
||||||
|
cx={size / 2}
|
||||||
|
cy={size / 2}
|
||||||
|
r={radius}
|
||||||
|
strokeWidth={`${strokeWidth}px`}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
|
@ -8,8 +8,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||||
import Overlay from 'react-overlays/Overlay';
|
import Overlay from 'react-overlays/Overlay';
|
||||||
|
|
||||||
import { CircularProgress } from 'flavours/glitch/components/loading_indicator';
|
import { CircularProgress } from "./circular_progress";
|
||||||
|
|
||||||
import { IconButton } from './icon_button';
|
import { IconButton } from './icon_button';
|
||||||
|
|
||||||
const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true;
|
const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true;
|
||||||
|
|
|
@ -113,7 +113,7 @@ export default class ErrorBoundary extends PureComponent {
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='web_app_crash.reload_page'
|
id='web_app_crash.reload_page'
|
||||||
defaultMessage='{reload} the current page'
|
defaultMessage='{reload} the current page'
|
||||||
values={{ reload: <a href='#' onClick={this.handleReload}><FormattedMessage id='web_app_crash.reload' defaultMessage='Reload' /></a> }}
|
values={{ reload: <button onClick={this.handleReload}><FormattedMessage id='web_app_crash.reload' defaultMessage='Reload' /></button> }}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
{ preferencesLink !== undefined && (
|
{ preferencesLink !== undefined && (
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { PureComponent } from 'react';
|
|
||||||
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
export default class LoadPending extends PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
onClick: PropTypes.func,
|
|
||||||
count: PropTypes.number,
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { count } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button className='load-more load-gap' onClick={this.props.onClick}>
|
|
||||||
<FormattedMessage id='load_pending' defaultMessage='{count, plural, one {# new item} other {# new items}}' values={{ count }} />
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
18
app/javascript/flavours/glitch/components/load_pending.tsx
Normal file
18
app/javascript/flavours/glitch/components/load_pending.tsx
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onClick: (event: React.MouseEvent) => void;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LoadPending: React.FC<Props> = ({ onClick, count }) => {
|
||||||
|
return (
|
||||||
|
<button className='load-more load-gap' onClick={onClick}>
|
||||||
|
<FormattedMessage
|
||||||
|
id='load_pending'
|
||||||
|
defaultMessage='{count, plural, one {# new item} other {# new items}}'
|
||||||
|
values={{ count }}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,31 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
export const CircularProgress = ({ size, strokeWidth }) => {
|
|
||||||
const viewBox = `0 0 ${size} ${size}`;
|
|
||||||
const radius = (size - strokeWidth) / 2;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<svg width={size} heigh={size} viewBox={viewBox} className='circular-progress' role='progressbar'>
|
|
||||||
<circle
|
|
||||||
fill='none'
|
|
||||||
cx={size / 2}
|
|
||||||
cy={size / 2}
|
|
||||||
r={radius}
|
|
||||||
strokeWidth={`${strokeWidth}px`}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
CircularProgress.propTypes = {
|
|
||||||
size: PropTypes.number.isRequired,
|
|
||||||
strokeWidth: PropTypes.number.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
const LoadingIndicator = () => (
|
|
||||||
<div className='loading-indicator'>
|
|
||||||
<CircularProgress size={50} strokeWidth={6} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default LoadingIndicator;
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { CircularProgress } from './circular_progress';
|
||||||
|
|
||||||
|
export const LoadingIndicator: React.FC = () => (
|
||||||
|
<div className='loading-indicator'>
|
||||||
|
<CircularProgress size={50} strokeWidth={6} />
|
||||||
|
</div>
|
||||||
|
);
|
|
@ -16,8 +16,8 @@ import IntersectionObserverWrapper from 'flavours/glitch/features/ui/util/inters
|
||||||
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../features/ui/util/fullscreen';
|
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../features/ui/util/fullscreen';
|
||||||
|
|
||||||
import { LoadMore } from './load_more';
|
import { LoadMore } from './load_more';
|
||||||
import LoadPending from './load_pending';
|
import { LoadPending } from './load_pending';
|
||||||
import LoadingIndicator from './loading_indicator';
|
import { LoadingIndicator } from './loading_indicator';
|
||||||
|
|
||||||
const MOUSE_IDLE_DELAY = 300;
|
const MOUSE_IDLE_DELAY = 300;
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
|
||||||
import EmojiPickerDropdown from 'flavours/glitch/features/compose/containers/emoji_picker_dropdown_container';
|
|
||||||
import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
|
import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
|
||||||
|
import EmojiPickerDropdown from 'flavours/glitch/features/compose/containers/emoji_picker_dropdown_container';
|
||||||
import { me, maxReactions } from 'flavours/glitch/initial_state';
|
import { me, maxReactions } from 'flavours/glitch/initial_state';
|
||||||
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions';
|
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions';
|
||||||
import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links';
|
import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links';
|
||||||
|
|
|
@ -1,170 +0,0 @@
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import { autoPlayGif, reduceMotion } from '../initial_state';
|
|
||||||
import spring from 'react-motion/lib/spring';
|
|
||||||
import TransitionMotion from 'react-motion/lib/TransitionMotion';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import React from 'react';
|
|
||||||
import unicodeMapping from '../features/emoji/emoji_unicode_mapping_light';
|
|
||||||
import { AnimatedNumber } from './animated_number';
|
|
||||||
import { assetHost } from '../utils/config';
|
|
||||||
|
|
||||||
export default class StatusReactions extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
statusId: PropTypes.string.isRequired,
|
|
||||||
reactions: ImmutablePropTypes.list.isRequired,
|
|
||||||
numVisible: PropTypes.number,
|
|
||||||
addReaction: PropTypes.func.isRequired,
|
|
||||||
canReact: PropTypes.bool.isRequired,
|
|
||||||
removeReaction: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
willEnter() {
|
|
||||||
return { scale: reduceMotion ? 1 : 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
willLeave() {
|
|
||||||
return { scale: reduceMotion ? 0 : spring(0, { stiffness: 170, damping: 26 }) };
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { reactions, numVisible } = this.props;
|
|
||||||
let visibleReactions = reactions
|
|
||||||
.filter(x => x.get('count') > 0)
|
|
||||||
.sort((a, b) => b.get('count') - a.get('count'));
|
|
||||||
|
|
||||||
if (numVisible >= 0) {
|
|
||||||
visibleReactions = visibleReactions.filter((_, i) => i < numVisible);
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = visibleReactions.map(reaction => ({
|
|
||||||
key: reaction.get('name'),
|
|
||||||
data: reaction,
|
|
||||||
style: { scale: reduceMotion ? 1 : spring(1, { stiffness: 150, damping: 13 }) },
|
|
||||||
})).toArray();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TransitionMotion styles={styles} willEnter={this.willEnter} willLeave={this.willLeave}>
|
|
||||||
{items => (
|
|
||||||
<div className={classNames('reactions-bar', { 'reactions-bar--empty': visibleReactions.isEmpty() })}>
|
|
||||||
{items.map(({ key, data, style }) => (
|
|
||||||
<Reaction
|
|
||||||
key={key}
|
|
||||||
statusId={this.props.statusId}
|
|
||||||
reaction={data}
|
|
||||||
style={{ transform: `scale(${style.scale})`, position: style.scale < 0.5 ? 'absolute' : 'static' }}
|
|
||||||
addReaction={this.props.addReaction}
|
|
||||||
removeReaction={this.props.removeReaction}
|
|
||||||
canReact={this.props.canReact}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</TransitionMotion>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class Reaction extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
statusId: PropTypes.string,
|
|
||||||
reaction: ImmutablePropTypes.map.isRequired,
|
|
||||||
addReaction: PropTypes.func.isRequired,
|
|
||||||
removeReaction: PropTypes.func.isRequired,
|
|
||||||
canReact: PropTypes.bool.isRequired,
|
|
||||||
style: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
hovered: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleClick = () => {
|
|
||||||
const { reaction, statusId, addReaction, removeReaction } = this.props;
|
|
||||||
|
|
||||||
if (reaction.get('me')) {
|
|
||||||
removeReaction(statusId, reaction.get('name'));
|
|
||||||
} else {
|
|
||||||
addReaction(statusId, reaction.get('name'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseEnter = () => this.setState({ hovered: true })
|
|
||||||
|
|
||||||
handleMouseLeave = () => this.setState({ hovered: false })
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { reaction } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
className={classNames('reactions-bar__item', { active: reaction.get('me') })}
|
|
||||||
onClick={this.handleClick}
|
|
||||||
onMouseEnter={this.handleMouseEnter}
|
|
||||||
onMouseLeave={this.handleMouseLeave}
|
|
||||||
disabled={!this.props.canReact}
|
|
||||||
style={this.props.style}
|
|
||||||
>
|
|
||||||
<span className='reactions-bar__item__emoji'>
|
|
||||||
<Emoji
|
|
||||||
hovered={this.state.hovered}
|
|
||||||
emoji={reaction.get('name')}
|
|
||||||
url={reaction.get('url')}
|
|
||||||
staticUrl={reaction.get('static_url')}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<span className='reactions-bar__item__count'>
|
|
||||||
<AnimatedNumber value={reaction.get('count')} />
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class Emoji extends React.PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
emoji: PropTypes.string.isRequired,
|
|
||||||
hovered: PropTypes.bool.isRequired,
|
|
||||||
url: PropTypes.string,
|
|
||||||
staticUrl: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { emoji, hovered, url, staticUrl } = this.props;
|
|
||||||
|
|
||||||
if (unicodeMapping[emoji]) {
|
|
||||||
const { filename, shortCode } = unicodeMapping[this.props.emoji];
|
|
||||||
const title = shortCode ? `:${shortCode}:` : '';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<img
|
|
||||||
draggable='false'
|
|
||||||
className='emojione'
|
|
||||||
alt={emoji}
|
|
||||||
title={title}
|
|
||||||
src={`${assetHost}/emoji/${filename}.svg`}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const filename = (autoPlayGif || hovered) ? url : staticUrl;
|
|
||||||
const shortCode = `:${emoji}:`;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<img
|
|
||||||
draggable='false'
|
|
||||||
className='emojione custom-emoji'
|
|
||||||
alt={shortCode}
|
|
||||||
title={shortCode}
|
|
||||||
src={filename}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,15 +1,20 @@
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import { autoPlayGif, reduceMotion } from '../initial_state';
|
|
||||||
import spring from 'react-motion/lib/spring';
|
|
||||||
import TransitionMotion from 'react-motion/lib/TransitionMotion';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
|
||||||
|
import TransitionMotion from 'react-motion/lib/TransitionMotion';
|
||||||
|
import spring from 'react-motion/lib/spring';
|
||||||
|
|
||||||
import unicodeMapping from '../features/emoji/emoji_unicode_mapping_light';
|
import unicodeMapping from '../features/emoji/emoji_unicode_mapping_light';
|
||||||
import { AnimatedNumber } from './animated_number';
|
import { autoPlayGif, reduceMotion } from '../initial_state';
|
||||||
import { assetHost } from '../utils/config';
|
import { assetHost } from '../utils/config';
|
||||||
|
|
||||||
|
import { AnimatedNumber } from './animated_number';
|
||||||
|
|
||||||
export default class StatusReactions extends ImmutablePureComponent {
|
export default class StatusReactions extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
|
|
@ -314,7 +314,7 @@ class Header extends ImmutablePureComponent {
|
||||||
let badge;
|
let badge;
|
||||||
|
|
||||||
if (account.get('bot')) {
|
if (account.get('bot')) {
|
||||||
badge = (<div className='account-role bot'><FormattedMessage id='account.badges.bot' defaultMessage='Bot' /></div>);
|
badge = (<div className='account-role bot'><FormattedMessage id='account.badges.bot' defaultMessage='Automated' /></div>);
|
||||||
} else if (account.get('group')) {
|
} else if (account.get('group')) {
|
||||||
badge = (<div className='account-role group'><FormattedMessage id='account.badges.group' defaultMessage='Group' /></div>);
|
badge = (<div className='account-role group'><FormattedMessage id='account.badges.group' defaultMessage='Group' /></div>);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { lookupAccount, fetchAccount } from 'flavours/glitch/actions/accounts';
|
||||||
import { openModal } from 'flavours/glitch/actions/modal';
|
import { openModal } from 'flavours/glitch/actions/modal';
|
||||||
import { expandAccountMediaTimeline } from 'flavours/glitch/actions/timelines';
|
import { expandAccountMediaTimeline } from 'flavours/glitch/actions/timelines';
|
||||||
import { LoadMore } from 'flavours/glitch/components/load_more';
|
import { LoadMore } from 'flavours/glitch/components/load_more';
|
||||||
import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
|
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
||||||
import ScrollContainer from 'flavours/glitch/containers/scroll_container';
|
import ScrollContainer from 'flavours/glitch/containers/scroll_container';
|
||||||
import ProfileColumnHeader from 'flavours/glitch/features/account/components/profile_column_header';
|
import ProfileColumnHeader from 'flavours/glitch/features/account/components/profile_column_header';
|
||||||
import HeaderContainer from 'flavours/glitch/features/account_timeline/containers/header_container';
|
import HeaderContainer from 'flavours/glitch/features/account_timeline/containers/header_container';
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { getAccountHidden } from 'flavours/glitch/selectors';
|
||||||
|
|
||||||
import { fetchFeaturedTags } from '../../actions/featured_tags';
|
import { fetchFeaturedTags } from '../../actions/featured_tags';
|
||||||
import { expandAccountFeaturedTimeline, expandAccountTimeline } from '../../actions/timelines';
|
import { expandAccountFeaturedTimeline, expandAccountTimeline } from '../../actions/timelines';
|
||||||
import LoadingIndicator from '../../components/loading_indicator';
|
import { LoadingIndicator } from '../../components/loading_indicator';
|
||||||
import StatusList from '../../components/status_list';
|
import StatusList from '../../components/status_list';
|
||||||
import Column from '../ui/components/column';
|
import Column from '../ui/components/column';
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { debounce } from 'lodash';
|
||||||
|
|
||||||
import { fetchBlocks, expandBlocks } from 'flavours/glitch/actions/blocks';
|
import { fetchBlocks, expandBlocks } from 'flavours/glitch/actions/blocks';
|
||||||
import ColumnBackButtonSlim from 'flavours/glitch/components/column_back_button_slim';
|
import ColumnBackButtonSlim from 'flavours/glitch/components/column_back_button_slim';
|
||||||
import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
|
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
||||||
import AccountContainer from 'flavours/glitch/containers/account_container';
|
import AccountContainer from 'flavours/glitch/containers/account_container';
|
||||||
import Column from 'flavours/glitch/features/ui/components/column';
|
import Column from 'flavours/glitch/features/ui/components/column';
|
||||||
|
|
||||||
|
|
|
@ -15,13 +15,14 @@ import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/col
|
||||||
import ColumnHeader from 'flavours/glitch/components/column_header';
|
import ColumnHeader from 'flavours/glitch/components/column_header';
|
||||||
import StatusList from 'flavours/glitch/components/status_list';
|
import StatusList from 'flavours/glitch/components/status_list';
|
||||||
import Column from 'flavours/glitch/features/ui/components/column';
|
import Column from 'flavours/glitch/features/ui/components/column';
|
||||||
|
import { getStatusList } from 'flavours/glitch/selectors';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
heading: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' },
|
heading: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
statusIds: state.getIn(['status_lists', 'bookmarks', 'items']),
|
statusIds: getStatusList(state, 'bookmarks'),
|
||||||
isLoading: state.getIn(['status_lists', 'bookmarks', 'isLoading'], true),
|
isLoading: state.getIn(['status_lists', 'bookmarks', 'isLoading'], true),
|
||||||
hasMore: !!state.getIn(['status_lists', 'bookmarks', 'next']),
|
hasMore: !!state.getIn(['status_lists', 'bookmarks', 'next']),
|
||||||
});
|
});
|
||||||
|
|
|
@ -142,11 +142,8 @@ class CommunityTimeline extends PureComponent {
|
||||||
<ColumnSettingsContainer columnId={columnId} />
|
<ColumnSettingsContainer columnId={columnId} />
|
||||||
</ColumnHeader>
|
</ColumnHeader>
|
||||||
|
|
||||||
<DismissableBanner id='community_timeline'>
|
|
||||||
<FormattedMessage id='dismissable_banner.community_timeline' defaultMessage='These are the most recent public posts from people whose accounts are hosted by {domain}.' values={{ domain }} />
|
|
||||||
</DismissableBanner>
|
|
||||||
|
|
||||||
<StatusListContainer
|
<StatusListContainer
|
||||||
|
prepend={<DismissableBanner id='community_timeline'><FormattedMessage id='dismissable_banner.community_timeline' defaultMessage='These are the most recent public posts from people whose accounts are hosted by {domain}.' values={{ domain }} /></DismissableBanner>}
|
||||||
trackScroll={!pinned}
|
trackScroll={!pinned}
|
||||||
scrollKey={`community_timeline-${columnId}`}
|
scrollKey={`community_timeline-${columnId}`}
|
||||||
timelineId={`community${onlyMedia ? ':media' : ''}`}
|
timelineId={`community${onlyMedia ? ':media' : ''}`}
|
||||||
|
|
|
@ -392,7 +392,7 @@ class EmojiPickerDropdown extends PureComponent {
|
||||||
{button || <img
|
{button || <img
|
||||||
className={classNames('emojione', { 'pulse-loading': active && loading })}
|
className={classNames('emojione', { 'pulse-loading': active && loading })}
|
||||||
alt='🙂'
|
alt='🙂'
|
||||||
src={`${assetHost}/emoji/1f602.svg`}
|
src={`${assetHost}/emoji/1f642.svg`}
|
||||||
/>}
|
/>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ export default class Upload extends ImmutablePureComponent {
|
||||||
const y = ((focusY / -2) + .5) * 100;
|
const y = ((focusY / -2) + .5) * 100;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='compose-form__upload' tabIndex={0} role='button'>
|
<div className='compose-form__upload'>
|
||||||
<Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
|
<Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
|
||||||
{({ scale }) => (
|
{({ scale }) => (
|
||||||
<div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
|
<div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { fetchDirectory, expandDirectory } from 'flavours/glitch/actions/directo
|
||||||
import Column from 'flavours/glitch/components/column';
|
import Column from 'flavours/glitch/components/column';
|
||||||
import ColumnHeader from 'flavours/glitch/components/column_header';
|
import ColumnHeader from 'flavours/glitch/components/column_header';
|
||||||
import { LoadMore } from 'flavours/glitch/components/load_more';
|
import { LoadMore } from 'flavours/glitch/components/load_more';
|
||||||
import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
|
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
||||||
import { RadioButton } from 'flavours/glitch/components/radio_button';
|
import { RadioButton } from 'flavours/glitch/components/radio_button';
|
||||||
import ScrollContainer from 'flavours/glitch/containers/scroll_container';
|
import ScrollContainer from 'flavours/glitch/containers/scroll_container';
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ import ScrollableList from 'flavours/glitch/components/scrollable_list';
|
||||||
|
|
||||||
import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks';
|
import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks';
|
||||||
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
|
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
|
||||||
import LoadingIndicator from '../../components/loading_indicator';
|
import { LoadingIndicator } from '../../components/loading_indicator';
|
||||||
import DomainContainer from '../../containers/domain_container';
|
import DomainContainer from '../../containers/domain_container';
|
||||||
import Column from '../ui/components/column';
|
import Column from '../ui/components/column';
|
||||||
|
|
||||||
|
|
51
app/javascript/flavours/glitch/features/emoji/emoji_compressed.d.ts
vendored
Normal file
51
app/javascript/flavours/glitch/features/emoji/emoji_compressed.d.ts
vendored
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
import type { BaseEmoji, EmojiData, NimbleEmojiIndex } from 'emoji-mart';
|
||||||
|
import type { Category, Data, Emoji } from 'emoji-mart/dist-es/utils/data';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The 'search' property, although not defined in the [`Emoji`]{@link node_modules/@types/emoji-mart/dist-es/utils/data.d.ts#Emoji} type,
|
||||||
|
* is used in the application.
|
||||||
|
* This could be due to an oversight by the library maintainer.
|
||||||
|
* The `search` property is defined and used [here]{@link node_modules/emoji-mart/dist/utils/data.js#uncompress}.
|
||||||
|
*/
|
||||||
|
export type Search = string;
|
||||||
|
/*
|
||||||
|
* The 'skins' property does not exist in the application data.
|
||||||
|
* This could be a potential area of refactoring or error handling.
|
||||||
|
* The non-existence of 'skins' property is evident at [this location]{@link app/javascript/flavours/glitch/features/emoji/emoji_compressed.js:121}.
|
||||||
|
*/
|
||||||
|
export type Skins = null;
|
||||||
|
|
||||||
|
export type FilenameData = string[] | string[][];
|
||||||
|
export type ShortCodesToEmojiDataKey =
|
||||||
|
| EmojiData['id']
|
||||||
|
| BaseEmoji['native']
|
||||||
|
| keyof NimbleEmojiIndex['emojis'];
|
||||||
|
|
||||||
|
export type SearchData = [
|
||||||
|
BaseEmoji['native'],
|
||||||
|
Emoji['short_names'],
|
||||||
|
Search,
|
||||||
|
Emoji['unified']
|
||||||
|
];
|
||||||
|
|
||||||
|
export interface ShortCodesToEmojiData {
|
||||||
|
[key: ShortCodesToEmojiDataKey]: [FilenameData, SearchData];
|
||||||
|
}
|
||||||
|
export type EmojisWithoutShortCodes = FilenameData[];
|
||||||
|
|
||||||
|
export type EmojiCompressed = [
|
||||||
|
ShortCodesToEmojiData,
|
||||||
|
Skins,
|
||||||
|
Category[],
|
||||||
|
Data['aliases'],
|
||||||
|
EmojisWithoutShortCodes
|
||||||
|
];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* `emoji_compressed.js` uses `babel-plugin-preval`, which makes it difficult to convert to TypeScript.
|
||||||
|
* As a temporary solution, we are allowing a default export here to apply the TypeScript type `EmojiCompressed` to the JS file export.
|
||||||
|
* - {@link app/javascript/flavours/glitch/features/emoji/emoji_compressed.js}
|
||||||
|
*/
|
||||||
|
declare const emojiCompressed: EmojiCompressed;
|
||||||
|
|
||||||
|
export default emojiCompressed; // eslint-disable-line import/no-default-export
|
|
@ -118,6 +118,16 @@ Object.keys(emojiIndex.emojis).forEach(key => {
|
||||||
// inconsistent behavior in dev mode
|
// inconsistent behavior in dev mode
|
||||||
module.exports = JSON.parse(JSON.stringify([
|
module.exports = JSON.parse(JSON.stringify([
|
||||||
shortCodesToEmojiData,
|
shortCodesToEmojiData,
|
||||||
|
/*
|
||||||
|
* The property `skins` is not found in the current context.
|
||||||
|
* This could potentially lead to issues when interacting with modules or data structures
|
||||||
|
* that expect the presence of `skins` property.
|
||||||
|
* Currently, no definitions or references to `skins` property can be found in:
|
||||||
|
* - {@link node_modules/emoji-mart/dist/utils/data.js}
|
||||||
|
* - {@link node_modules/emoji-mart/data/all.json}
|
||||||
|
* - {@link app/javascript/flavours/glitch/features/emoji/emoji_compressed.d.ts#Skins}
|
||||||
|
* Future refactorings or updates should consider adding definitions or handling for `skins` property.
|
||||||
|
*/
|
||||||
emojiMartData.skins,
|
emojiMartData.skins,
|
||||||
emojiMartData.categories,
|
emojiMartData.categories,
|
||||||
emojiMartData.aliases,
|
emojiMartData.aliases,
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
// The output of this module is designed to mimic emoji-mart's
|
|
||||||
// "data" object, such that we can use it for a light version of emoji-mart's
|
|
||||||
// emojiIndex.search functionality.
|
|
||||||
import emojiCompressed from './emoji_compressed';
|
|
||||||
import { unicodeToUnifiedName } from './unicode_to_unified_name';
|
|
||||||
|
|
||||||
const [ shortCodesToEmojiData, skins, categories, short_names ] = emojiCompressed;
|
|
||||||
|
|
||||||
const emojis = {};
|
|
||||||
|
|
||||||
// decompress
|
|
||||||
Object.keys(shortCodesToEmojiData).forEach((shortCode) => {
|
|
||||||
let [
|
|
||||||
filenameData, // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
||||||
searchData,
|
|
||||||
] = shortCodesToEmojiData[shortCode];
|
|
||||||
let [
|
|
||||||
native,
|
|
||||||
short_names,
|
|
||||||
search,
|
|
||||||
unified,
|
|
||||||
] = searchData;
|
|
||||||
|
|
||||||
if (!unified) {
|
|
||||||
// unified name can be derived from unicodeToUnifiedName
|
|
||||||
unified = unicodeToUnifiedName(native);
|
|
||||||
}
|
|
||||||
|
|
||||||
short_names = [shortCode].concat(short_names);
|
|
||||||
emojis[shortCode] = {
|
|
||||||
native,
|
|
||||||
search,
|
|
||||||
short_names,
|
|
||||||
unified,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
export {
|
|
||||||
emojis,
|
|
||||||
skins,
|
|
||||||
categories,
|
|
||||||
short_names,
|
|
||||||
};
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
// The output of this module is designed to mimic emoji-mart's
|
||||||
|
// "data" object, such that we can use it for a light version of emoji-mart's
|
||||||
|
// emojiIndex.search functionality.
|
||||||
|
import type { BaseEmoji } from 'emoji-mart';
|
||||||
|
import type { Emoji } from 'emoji-mart/dist-es/utils/data';
|
||||||
|
|
||||||
|
import type { Search, ShortCodesToEmojiData } from './emoji_compressed';
|
||||||
|
import emojiCompressed from './emoji_compressed';
|
||||||
|
import { unicodeToUnifiedName } from './unicode_to_unified_name';
|
||||||
|
|
||||||
|
type Emojis = {
|
||||||
|
[key in keyof ShortCodesToEmojiData]: {
|
||||||
|
native: BaseEmoji['native'];
|
||||||
|
search: Search;
|
||||||
|
short_names: Emoji['short_names'];
|
||||||
|
unified: Emoji['unified'];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const [
|
||||||
|
shortCodesToEmojiData,
|
||||||
|
skins,
|
||||||
|
categories,
|
||||||
|
short_names,
|
||||||
|
_emojisWithoutShortCodes,
|
||||||
|
] = emojiCompressed;
|
||||||
|
|
||||||
|
const emojis: Emojis = {};
|
||||||
|
|
||||||
|
// decompress
|
||||||
|
Object.keys(shortCodesToEmojiData).forEach((shortCode) => {
|
||||||
|
const [_filenameData, searchData] = shortCodesToEmojiData[shortCode];
|
||||||
|
const native = searchData[0];
|
||||||
|
let short_names = searchData[1];
|
||||||
|
const search = searchData[2];
|
||||||
|
let unified = searchData[3];
|
||||||
|
|
||||||
|
if (!unified) {
|
||||||
|
// unified name can be derived from unicodeToUnifiedName
|
||||||
|
unified = unicodeToUnifiedName(native);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (short_names) short_names = [shortCode].concat(short_names);
|
||||||
|
emojis[shortCode] = {
|
||||||
|
native,
|
||||||
|
search,
|
||||||
|
short_names,
|
||||||
|
unified,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export { emojis, skins, categories, short_names };
|
|
@ -8,7 +8,7 @@ import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { fetchTrendingLinks } from 'flavours/glitch/actions/trends';
|
import { fetchTrendingLinks } from 'flavours/glitch/actions/trends';
|
||||||
import DismissableBanner from 'flavours/glitch/components/dismissable_banner';
|
import DismissableBanner from 'flavours/glitch/components/dismissable_banner';
|
||||||
import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
|
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
||||||
|
|
||||||
import Story from './components/story';
|
import Story from './components/story';
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ class Links extends PureComponent {
|
||||||
|
|
||||||
const banner = (
|
const banner = (
|
||||||
<DismissableBanner id='explore/links'>
|
<DismissableBanner id='explore/links'>
|
||||||
<FormattedMessage id='dismissable_banner.explore_links' defaultMessage='These news stories are being talked about by people on this and other servers of the decentralized network right now.' />
|
<FormattedMessage id='dismissable_banner.explore_links' defaultMessage='These are news stories being shared the most on the social web today. Newer news stories posted by more different people are ranked higher.' />
|
||||||
</DismissableBanner>
|
</DismissableBanner>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { connect } from 'react-redux';
|
||||||
import { expandSearch } from 'flavours/glitch/actions/search';
|
import { expandSearch } from 'flavours/glitch/actions/search';
|
||||||
import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag';
|
import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag';
|
||||||
import { LoadMore } from 'flavours/glitch/components/load_more';
|
import { LoadMore } from 'flavours/glitch/components/load_more';
|
||||||
import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
|
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
||||||
import Account from 'flavours/glitch/containers/account_container';
|
import Account from 'flavours/glitch/containers/account_container';
|
||||||
import Status from 'flavours/glitch/containers/status_container';
|
import Status from 'flavours/glitch/containers/status_container';
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,10 @@ import { debounce } from 'lodash';
|
||||||
import { fetchTrendingStatuses, expandTrendingStatuses } from 'flavours/glitch/actions/trends';
|
import { fetchTrendingStatuses, expandTrendingStatuses } from 'flavours/glitch/actions/trends';
|
||||||
import DismissableBanner from 'flavours/glitch/components/dismissable_banner';
|
import DismissableBanner from 'flavours/glitch/components/dismissable_banner';
|
||||||
import StatusList from 'flavours/glitch/components/status_list';
|
import StatusList from 'flavours/glitch/components/status_list';
|
||||||
|
import { getStatusList } from 'flavours/glitch/selectors';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
statusIds: state.getIn(['status_lists', 'trending', 'items']),
|
statusIds: getStatusList(state, 'trending'),
|
||||||
isLoading: state.getIn(['status_lists', 'trending', 'isLoading'], true),
|
isLoading: state.getIn(['status_lists', 'trending', 'isLoading'], true),
|
||||||
hasMore: !!state.getIn(['status_lists', 'trending', 'next']),
|
hasMore: !!state.getIn(['status_lists', 'trending', 'next']),
|
||||||
});
|
});
|
||||||
|
@ -46,7 +47,7 @@ class Statuses extends PureComponent {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DismissableBanner id='explore/statuses'>
|
<DismissableBanner id='explore/statuses'>
|
||||||
<FormattedMessage id='dismissable_banner.explore_statuses' defaultMessage='These posts from this and other servers in the decentralized network are gaining traction on this server right now.' />
|
<FormattedMessage id='dismissable_banner.explore_statuses' defaultMessage='These are posts from across the social web that are gaining traction today. Newer posts with more boosts and favourites are ranked higher.' />
|
||||||
</DismissableBanner>
|
</DismissableBanner>
|
||||||
|
|
||||||
<StatusList
|
<StatusList
|
||||||
|
|
|
@ -7,7 +7,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { fetchSuggestions, dismissSuggestion } from 'flavours/glitch/actions/suggestions';
|
import { fetchSuggestions, dismissSuggestion } from 'flavours/glitch/actions/suggestions';
|
||||||
import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
|
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
||||||
import AccountCard from 'flavours/glitch/features/directory/components/account_card';
|
import AccountCard from 'flavours/glitch/features/directory/components/account_card';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { connect } from 'react-redux';
|
||||||
import { fetchTrendingHashtags } from 'flavours/glitch/actions/trends';
|
import { fetchTrendingHashtags } from 'flavours/glitch/actions/trends';
|
||||||
import DismissableBanner from 'flavours/glitch/components/dismissable_banner';
|
import DismissableBanner from 'flavours/glitch/components/dismissable_banner';
|
||||||
import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag';
|
import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag';
|
||||||
import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
|
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ class Tags extends PureComponent {
|
||||||
|
|
||||||
const banner = (
|
const banner = (
|
||||||
<DismissableBanner id='explore/tags'>
|
<DismissableBanner id='explore/tags'>
|
||||||
<FormattedMessage id='dismissable_banner.explore_tags' defaultMessage='These hashtags are gaining traction among people on this and other servers of the decentralized network right now.' />
|
<FormattedMessage id='dismissable_banner.explore_tags' defaultMessage='These are hashtags that are gaining traction on the social web today. Hashtags that are used by more different people are ranked higher.' />
|
||||||
</DismissableBanner>
|
</DismissableBanner>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -15,13 +15,14 @@ import { fetchFavouritedStatuses, expandFavouritedStatuses } from 'flavours/glit
|
||||||
import ColumnHeader from 'flavours/glitch/components/column_header';
|
import ColumnHeader from 'flavours/glitch/components/column_header';
|
||||||
import StatusList from 'flavours/glitch/components/status_list';
|
import StatusList from 'flavours/glitch/components/status_list';
|
||||||
import Column from 'flavours/glitch/features/ui/components/column';
|
import Column from 'flavours/glitch/features/ui/components/column';
|
||||||
|
import { getStatusList } from 'flavours/glitch/selectors';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
heading: { id: 'column.favourites', defaultMessage: 'Favourites' },
|
heading: { id: 'column.favourites', defaultMessage: 'Favourites' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
statusIds: state.getIn(['status_lists', 'favourites', 'items']),
|
statusIds: getStatusList(state, 'favourites'),
|
||||||
isLoading: state.getIn(['status_lists', 'favourites', 'isLoading'], true),
|
isLoading: state.getIn(['status_lists', 'favourites', 'isLoading'], true),
|
||||||
hasMore: !!state.getIn(['status_lists', 'favourites', 'next']),
|
hasMore: !!state.getIn(['status_lists', 'favourites', 'next']),
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { connect } from 'react-redux';
|
||||||
import { fetchFavourites } from 'flavours/glitch/actions/interactions';
|
import { fetchFavourites } from 'flavours/glitch/actions/interactions';
|
||||||
import ColumnHeader from 'flavours/glitch/components/column_header';
|
import ColumnHeader from 'flavours/glitch/components/column_header';
|
||||||
import { Icon } from 'flavours/glitch/components/icon';
|
import { Icon } from 'flavours/glitch/components/icon';
|
||||||
import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
|
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
||||||
import ScrollableList from 'flavours/glitch/components/scrollable_list';
|
import ScrollableList from 'flavours/glitch/components/scrollable_list';
|
||||||
import AccountContainer from 'flavours/glitch/containers/account_container';
|
import AccountContainer from 'flavours/glitch/containers/account_container';
|
||||||
import Column from 'flavours/glitch/features/ui/components/column';
|
import Column from 'flavours/glitch/features/ui/components/column';
|
||||||
|
|
|
@ -14,7 +14,7 @@ import {
|
||||||
fetchFollowers,
|
fetchFollowers,
|
||||||
expandFollowers,
|
expandFollowers,
|
||||||
} from 'flavours/glitch/actions/accounts';
|
} from 'flavours/glitch/actions/accounts';
|
||||||
import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
|
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
||||||
import ScrollableList from 'flavours/glitch/components/scrollable_list';
|
import ScrollableList from 'flavours/glitch/components/scrollable_list';
|
||||||
import { TimelineHint } from 'flavours/glitch/components/timeline_hint';
|
import { TimelineHint } from 'flavours/glitch/components/timeline_hint';
|
||||||
import AccountContainer from 'flavours/glitch/containers/account_container';
|
import AccountContainer from 'flavours/glitch/containers/account_container';
|
||||||
|
|
|
@ -14,7 +14,7 @@ import {
|
||||||
fetchFollowing,
|
fetchFollowing,
|
||||||
expandFollowing,
|
expandFollowing,
|
||||||
} from 'flavours/glitch/actions/accounts';
|
} from 'flavours/glitch/actions/accounts';
|
||||||
import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
|
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
||||||
import ScrollableList from 'flavours/glitch/components/scrollable_list';
|
import ScrollableList from 'flavours/glitch/components/scrollable_list';
|
||||||
import { TimelineHint } from 'flavours/glitch/components/timeline_hint';
|
import { TimelineHint } from 'flavours/glitch/components/timeline_hint';
|
||||||
import AccountContainer from 'flavours/glitch/containers/account_container';
|
import AccountContainer from 'flavours/glitch/containers/account_container';
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import DismissableBanner from 'flavours/glitch/components/dismissable_banner';
|
||||||
|
import background from 'mastodon/../images/friends-cropped.png';
|
||||||
|
|
||||||
|
|
||||||
|
export const ExplorePrompt = () => (
|
||||||
|
<DismissableBanner id='home.explore_prompt'>
|
||||||
|
<img src={background} alt='' className='dismissable-banner__background-image' />
|
||||||
|
|
||||||
|
<h1><FormattedMessage id='home.explore_prompt.title' defaultMessage='This is your home base within Mastodon.' /></h1>
|
||||||
|
<p><FormattedMessage id='home.explore_prompt.body' defaultMessage="Your home feed will have a mix of posts from the hashtags you've chosen to follow, the people you've chosen to follow, and the posts they boost. It's looking pretty quiet right now, so how about:" /></p>
|
||||||
|
|
||||||
|
<div className='dismissable-banner__message__actions__wrapper'>
|
||||||
|
<div className='dismissable-banner__message__actions'>
|
||||||
|
<Link to='/explore' className='button'><FormattedMessage id='home.actions.go_to_explore' defaultMessage="See what's trending" /></Link>
|
||||||
|
<Link to='/explore/suggestions' className='button button-tertiary'><FormattedMessage id='home.actions.go_to_suggestions' defaultMessage='Find people to follow' /></Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DismissableBanner>
|
||||||
|
);
|
|
@ -5,35 +5,66 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
|
|
||||||
|
import { List as ImmutableList } from 'immutable';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
import { fetchAnnouncements, toggleShowAnnouncements } from 'flavours/glitch/actions/announcements';
|
import { fetchAnnouncements, toggleShowAnnouncements } from 'flavours/glitch/actions/announcements';
|
||||||
import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns';
|
|
||||||
import { expandHomeTimeline } from 'flavours/glitch/actions/timelines';
|
|
||||||
import Column from 'flavours/glitch/components/column';
|
|
||||||
import ColumnHeader from 'flavours/glitch/components/column_header';
|
|
||||||
import { IconWithBadge } from 'flavours/glitch/components/icon_with_badge';
|
import { IconWithBadge } from 'flavours/glitch/components/icon_with_badge';
|
||||||
import { NotSignedInIndicator } from 'flavours/glitch/components/not_signed_in_indicator';
|
import { NotSignedInIndicator } from 'flavours/glitch/components/not_signed_in_indicator';
|
||||||
import AnnouncementsContainer from 'flavours/glitch/features/getting_started/containers/announcements_container';
|
import AnnouncementsContainer from 'flavours/glitch/features/getting_started/containers/announcements_container';
|
||||||
import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container';
|
import { me } from 'flavours/glitch/initial_state';
|
||||||
|
|
||||||
|
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
|
||||||
|
import { expandHomeTimeline } from '../../actions/timelines';
|
||||||
|
import Column from '../../components/column';
|
||||||
|
import ColumnHeader from '../../components/column_header';
|
||||||
|
import StatusListContainer from '../ui/containers/status_list_container';
|
||||||
|
|
||||||
|
import { ExplorePrompt } from './components/explore_prompt';
|
||||||
import ColumnSettingsContainer from './containers/column_settings_container';
|
import ColumnSettingsContainer from './containers/column_settings_container';
|
||||||
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
title: { id: 'column.home', defaultMessage: 'Home' },
|
title: { id: 'column.home', defaultMessage: 'Home' },
|
||||||
show_announcements: { id: 'home.show_announcements', defaultMessage: 'Show announcements' },
|
show_announcements: { id: 'home.show_announcements', defaultMessage: 'Show announcements' },
|
||||||
hide_announcements: { id: 'home.hide_announcements', defaultMessage: 'Hide announcements' },
|
hide_announcements: { id: 'home.hide_announcements', defaultMessage: 'Hide announcements' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getHomeFeedSpeed = createSelector([
|
||||||
|
state => state.getIn(['timelines', 'home', 'items'], ImmutableList()),
|
||||||
|
state => state.getIn(['timelines', 'home', 'pendingItems'], ImmutableList()),
|
||||||
|
state => state.get('statuses'),
|
||||||
|
], (statusIds, pendingStatusIds, statusMap) => {
|
||||||
|
const recentStatusIds = pendingStatusIds.size > 0 ? pendingStatusIds : statusIds;
|
||||||
|
const statuses = recentStatusIds.map(id => statusMap.get(id)).filter(status => status?.get('account') !== me).take(20);
|
||||||
|
const oldest = new Date(statuses.getIn([statuses.size - 1, 'created_at'], 0));
|
||||||
|
const newest = new Date(statuses.getIn([0, 'created_at'], 0));
|
||||||
|
const averageGap = (newest - oldest) / (1000 * (statuses.size + 1)); // Average gap between posts on first page in seconds
|
||||||
|
|
||||||
|
return {
|
||||||
|
gap: averageGap,
|
||||||
|
newest,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const homeTooSlow = createSelector([
|
||||||
|
state => state.getIn(['timelines', 'home', 'isLoading']),
|
||||||
|
state => state.getIn(['timelines', 'home', 'isPartial']),
|
||||||
|
getHomeFeedSpeed,
|
||||||
|
], (isLoading, isPartial, speed) =>
|
||||||
|
!isLoading && !isPartial // Only if the home feed has finished loading
|
||||||
|
&& (speed.gap > (30 * 60) // If the average gap between posts is more than 20 minutes
|
||||||
|
|| (Date.now() - speed.newest) > (1000 * 3600)) // If the most recent post is from over an hour ago
|
||||||
|
);
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0,
|
hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0,
|
||||||
isPartial: state.getIn(['timelines', 'home', 'isPartial']),
|
isPartial: state.getIn(['timelines', 'home', 'isPartial']),
|
||||||
hasAnnouncements: !state.getIn(['announcements', 'items']).isEmpty(),
|
hasAnnouncements: !state.getIn(['announcements', 'items']).isEmpty(),
|
||||||
unreadAnnouncements: state.getIn(['announcements', 'items']).count(item => !item.get('read')),
|
unreadAnnouncements: state.getIn(['announcements', 'items']).count(item => !item.get('read')),
|
||||||
showAnnouncements: state.getIn(['announcements', 'show']),
|
showAnnouncements: state.getIn(['announcements', 'show']),
|
||||||
|
tooSlow: homeTooSlow(state),
|
||||||
regex: state.getIn(['settings', 'home', 'regex', 'body']),
|
regex: state.getIn(['settings', 'home', 'regex', 'body']),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -53,6 +84,7 @@ class HomeTimeline extends PureComponent {
|
||||||
hasAnnouncements: PropTypes.bool,
|
hasAnnouncements: PropTypes.bool,
|
||||||
unreadAnnouncements: PropTypes.number,
|
unreadAnnouncements: PropTypes.number,
|
||||||
showAnnouncements: PropTypes.bool,
|
showAnnouncements: PropTypes.bool,
|
||||||
|
tooSlow: PropTypes.bool,
|
||||||
regex: PropTypes.string,
|
regex: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -123,11 +155,11 @@ class HomeTimeline extends PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props;
|
const { intl, hasUnread, columnId, multiColumn, tooSlow, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props;
|
||||||
const pinned = !!columnId;
|
const pinned = !!columnId;
|
||||||
const { signedIn } = this.context.identity;
|
const { signedIn } = this.context.identity;
|
||||||
|
|
||||||
let announcementsButton = null;
|
let announcementsButton, banner;
|
||||||
|
|
||||||
if (hasAnnouncements) {
|
if (hasAnnouncements) {
|
||||||
announcementsButton = (
|
announcementsButton = (
|
||||||
|
@ -142,6 +174,10 @@ class HomeTimeline extends PureComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tooSlow) {
|
||||||
|
banner = <ExplorePrompt />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column bindToDocument={!multiColumn} ref={this.setRef} name='home' label={intl.formatMessage(messages.title)}>
|
<Column bindToDocument={!multiColumn} ref={this.setRef} name='home' label={intl.formatMessage(messages.title)}>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
|
@ -161,11 +197,13 @@ class HomeTimeline extends PureComponent {
|
||||||
|
|
||||||
{signedIn ? (
|
{signedIn ? (
|
||||||
<StatusListContainer
|
<StatusListContainer
|
||||||
|
prepend={banner}
|
||||||
|
alwaysPrepend
|
||||||
trackScroll={!pinned}
|
trackScroll={!pinned}
|
||||||
scrollKey={`home_timeline-${columnId}`}
|
scrollKey={`home_timeline-${columnId}`}
|
||||||
onLoadMore={this.handleLoadMore}
|
onLoadMore={this.handleLoadMore}
|
||||||
timelineId='home'
|
timelineId='home'
|
||||||
emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage='Your home timeline is empty! Follow more people to fill it up. {suggestions}' values={{ suggestions: <Link to='/start'><FormattedMessage id='empty_column.home.suggestions' defaultMessage='See some suggestions' /></Link> }} />}
|
emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage='Your home timeline is empty! Follow more people to fill it up.' />}
|
||||||
bindToDocument={!multiColumn}
|
bindToDocument={!multiColumn}
|
||||||
regex={this.props.regex}
|
regex={this.props.regex}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { expandListTimeline } from 'flavours/glitch/actions/timelines';
|
||||||
import Column from 'flavours/glitch/components/column';
|
import Column from 'flavours/glitch/components/column';
|
||||||
import ColumnHeader from 'flavours/glitch/components/column_header';
|
import ColumnHeader from 'flavours/glitch/components/column_header';
|
||||||
import { Icon } from 'flavours/glitch/components/icon';
|
import { Icon } from 'flavours/glitch/components/icon';
|
||||||
import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
|
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
||||||
import { RadioButton } from 'flavours/glitch/components/radio_button';
|
import { RadioButton } from 'flavours/glitch/components/radio_button';
|
||||||
import BundleColumnError from 'flavours/glitch/features/ui/components/bundle_column_error';
|
import BundleColumnError from 'flavours/glitch/features/ui/components/bundle_column_error';
|
||||||
import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container';
|
import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container';
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { createSelector } from 'reselect';
|
||||||
|
|
||||||
import { fetchLists } from 'flavours/glitch/actions/lists';
|
import { fetchLists } from 'flavours/glitch/actions/lists';
|
||||||
import ColumnBackButtonSlim from 'flavours/glitch/components/column_back_button_slim';
|
import ColumnBackButtonSlim from 'flavours/glitch/components/column_back_button_slim';
|
||||||
import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
|
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
||||||
import ScrollableList from 'flavours/glitch/components/scrollable_list';
|
import ScrollableList from 'flavours/glitch/components/scrollable_list';
|
||||||
import Column from 'flavours/glitch/features/ui/components/column';
|
import Column from 'flavours/glitch/features/ui/components/column';
|
||||||
import ColumnLink from 'flavours/glitch/features/ui/components/column_link';
|
import ColumnLink from 'flavours/glitch/features/ui/components/column_link';
|
||||||
|
|
|
@ -58,16 +58,15 @@ export default class LocalSettingsPage extends PureComponent {
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
else if (onNavigate) return (
|
else if (onNavigate) return (
|
||||||
<a
|
<button
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
role='button'
|
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className={finalClassName}
|
className={finalClassName}
|
||||||
title={title}
|
title={title}
|
||||||
aria-label={title}
|
aria-label={title}
|
||||||
>
|
>
|
||||||
{iconElem} <span>{title}</span>
|
{iconElem} <span>{title}</span>
|
||||||
</a>
|
</button>
|
||||||
);
|
);
|
||||||
else return null;
|
else return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { debounce } from 'lodash';
|
||||||
|
|
||||||
import { fetchMutes, expandMutes } from 'flavours/glitch/actions/mutes';
|
import { fetchMutes, expandMutes } from 'flavours/glitch/actions/mutes';
|
||||||
import ColumnBackButtonSlim from 'flavours/glitch/components/column_back_button_slim';
|
import ColumnBackButtonSlim from 'flavours/glitch/components/column_back_button_slim';
|
||||||
import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
|
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
||||||
import ScrollableList from 'flavours/glitch/components/scrollable_list';
|
import ScrollableList from 'flavours/glitch/components/scrollable_list';
|
||||||
import AccountContainer from 'flavours/glitch/containers/account_container';
|
import AccountContainer from 'flavours/glitch/containers/account_container';
|
||||||
import Column from 'flavours/glitch/features/ui/components/column';
|
import Column from 'flavours/glitch/features/ui/components/column';
|
||||||
|
|
|
@ -8,10 +8,12 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import AvatarOverlay from 'flavours/glitch/components/avatar_overlay';
|
import AvatarOverlay from 'flavours/glitch/components/avatar_overlay';
|
||||||
import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp';
|
import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp';
|
||||||
|
|
||||||
|
// This needs to be kept in sync with app/models/report.rb
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
openReport: { id: 'report_notification.open', defaultMessage: 'Open report' },
|
openReport: { id: 'report_notification.open', defaultMessage: 'Open report' },
|
||||||
other: { id: 'report_notification.categories.other', defaultMessage: 'Other' },
|
other: { id: 'report_notification.categories.other', defaultMessage: 'Other' },
|
||||||
spam: { id: 'report_notification.categories.spam', defaultMessage: 'Spam' },
|
spam: { id: 'report_notification.categories.spam', defaultMessage: 'Spam' },
|
||||||
|
legal: { id: 'report_notification.categories.legal', defaultMessage: 'Legal' },
|
||||||
violation: { id: 'report_notification.categories.violation', defaultMessage: 'Rule violation' },
|
violation: { id: 'report_notification.categories.violation', defaultMessage: 'Rule violation' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -8,17 +8,19 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { fetchPinnedStatuses } from 'flavours/glitch/actions/pin_statuses';
|
import { getStatusList } from 'flavours/glitch/selectors';
|
||||||
import ColumnBackButtonSlim from 'flavours/glitch/components/column_back_button_slim';
|
|
||||||
import StatusList from 'flavours/glitch/components/status_list';
|
import { fetchPinnedStatuses } from '../../actions/pin_statuses';
|
||||||
import Column from 'flavours/glitch/features/ui/components/column';
|
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
|
||||||
|
import StatusList from '../../components/status_list';
|
||||||
|
import Column from '../ui/components/column';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
heading: { id: 'column.pins', defaultMessage: 'Pinned post' },
|
heading: { id: 'column.pins', defaultMessage: 'Pinned post' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
statusIds: state.getIn(['status_lists', 'pins', 'items']),
|
statusIds: getStatusList(state, 'pins'),
|
||||||
hasMore: !!state.getIn(['status_lists', 'pins', 'next']),
|
hasMore: !!state.getIn(['status_lists', 'pins', 'next']),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -146,11 +146,8 @@ class PublicTimeline extends PureComponent {
|
||||||
<ColumnSettingsContainer columnId={columnId} />
|
<ColumnSettingsContainer columnId={columnId} />
|
||||||
</ColumnHeader>
|
</ColumnHeader>
|
||||||
|
|
||||||
<DismissableBanner id='public_timeline'>
|
|
||||||
<FormattedMessage id='dismissable_banner.public_timeline' defaultMessage='These are the most recent public posts from people on this and other servers of the decentralized network that this server knows about.' />
|
|
||||||
</DismissableBanner>
|
|
||||||
|
|
||||||
<StatusListContainer
|
<StatusListContainer
|
||||||
|
prepend={<DismissableBanner id='public_timeline'><FormattedMessage id='dismissable_banner.public_timeline' defaultMessage='These are the most recent public posts from people on this and other servers of the decentralized network that this server knows about.' /></DismissableBanner>}
|
||||||
timelineId={`public${onlyRemote ? ':remote' : (allowLocalOnly ? ':allow_local_only' : '')}${onlyMedia ? ':media' : ''}`}
|
timelineId={`public${onlyRemote ? ':remote' : (allowLocalOnly ? ':allow_local_only' : '')}${onlyMedia ? ':media' : ''}`}
|
||||||
onLoadMore={this.handleLoadMore}
|
onLoadMore={this.handleLoadMore}
|
||||||
trackScroll={!pinned}
|
trackScroll={!pinned}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { connect } from 'react-redux';
|
||||||
import { fetchReblogs } from 'flavours/glitch/actions/interactions';
|
import { fetchReblogs } from 'flavours/glitch/actions/interactions';
|
||||||
import ColumnHeader from 'flavours/glitch/components/column_header';
|
import ColumnHeader from 'flavours/glitch/components/column_header';
|
||||||
import { Icon } from 'flavours/glitch/components/icon';
|
import { Icon } from 'flavours/glitch/components/icon';
|
||||||
import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
|
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
||||||
import ScrollableList from 'flavours/glitch/components/scrollable_list';
|
import ScrollableList from 'flavours/glitch/components/scrollable_list';
|
||||||
import AccountContainer from 'flavours/glitch/containers/account_container';
|
import AccountContainer from 'flavours/glitch/containers/account_container';
|
||||||
import Column from 'flavours/glitch/features/ui/components/column';
|
import Column from 'flavours/glitch/features/ui/components/column';
|
||||||
|
|
|
@ -8,7 +8,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import Button from 'flavours/glitch/components/button';
|
import Button from 'flavours/glitch/components/button';
|
||||||
import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
|
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
||||||
import StatusCheckBox from 'flavours/glitch/features/report/containers/status_check_box_container';
|
import StatusCheckBox from 'flavours/glitch/features/report/containers/status_check_box_container';
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,14 @@ import PropTypes from 'prop-types';
|
||||||
import { PureComponent } from 'react';
|
import { PureComponent } from 'react';
|
||||||
|
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
|
||||||
import { IconButton } from 'flavours/glitch/components/icon_button';
|
import { IconButton } from 'flavours/glitch/components/icon_button';
|
||||||
import EmojiPickerDropdown from 'flavours/glitch/features/compose/containers/emoji_picker_dropdown_container';
|
|
||||||
import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
|
import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
|
||||||
|
import EmojiPickerDropdown from 'flavours/glitch/features/compose/containers/emoji_picker_dropdown_container';
|
||||||
import { me, maxReactions } from 'flavours/glitch/initial_state';
|
import { me, maxReactions } from 'flavours/glitch/initial_state';
|
||||||
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions';
|
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions';
|
||||||
import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links';
|
import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links';
|
||||||
|
|
|
@ -48,7 +48,7 @@ import {
|
||||||
undoStatusTranslation,
|
undoStatusTranslation,
|
||||||
} from 'flavours/glitch/actions/statuses';
|
} from 'flavours/glitch/actions/statuses';
|
||||||
import { Icon } from 'flavours/glitch/components/icon';
|
import { Icon } from 'flavours/glitch/components/icon';
|
||||||
import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
|
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
||||||
import { textForScreenReader, defaultMediaVisibility } from 'flavours/glitch/components/status';
|
import { textForScreenReader, defaultMediaVisibility } from 'flavours/glitch/components/status';
|
||||||
import ScrollContainer from 'flavours/glitch/containers/scroll_container';
|
import ScrollContainer from 'flavours/glitch/containers/scroll_container';
|
||||||
import StatusContainer from 'flavours/glitch/containers/status_container';
|
import StatusContainer from 'flavours/glitch/containers/status_container';
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { PureComponent } from 'react';
|
import { PureComponent } from 'react';
|
||||||
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||||
|
|
||||||
import { Link, withRouter } from 'react-router-dom';
|
import { Link, withRouter } from 'react-router-dom';
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { openModal } from 'flavours/glitch/actions/modal';
|
import { openModal } from 'flavours/glitch/actions/modal';
|
||||||
|
import { fetchServer } from 'flavours/glitch/actions/server';
|
||||||
import { Avatar } from 'flavours/glitch/components/avatar';
|
import { Avatar } from 'flavours/glitch/components/avatar';
|
||||||
|
import { Icon } from 'flavours/glitch/components/icon';
|
||||||
import { WordmarkLogo, SymbolLogo } from 'flavours/glitch/components/logo';
|
import { WordmarkLogo, SymbolLogo } from 'flavours/glitch/components/logo';
|
||||||
import Permalink from 'flavours/glitch/components/permalink';
|
import Permalink from 'flavours/glitch/components/permalink';
|
||||||
import { registrationsOpen, me } from 'flavours/glitch/initial_state';
|
import { registrationsOpen, me } from 'flavours/glitch/initial_state';
|
||||||
|
@ -21,6 +23,10 @@ const Account = connect(state => ({
|
||||||
</Permalink>
|
</Permalink>
|
||||||
));
|
));
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
search: { id: 'navigation_bar.search', defaultMessage: 'Search' },
|
||||||
|
});
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = (state) => ({
|
||||||
signupUrl: state.getIn(['server', 'server', 'registrations', 'url'], null) || '/auth/sign_up',
|
signupUrl: state.getIn(['server', 'server', 'registrations', 'url'], null) || '/auth/sign_up',
|
||||||
});
|
});
|
||||||
|
@ -29,6 +35,9 @@ const mapDispatchToProps = (dispatch) => ({
|
||||||
openClosedRegistrationsModal() {
|
openClosedRegistrationsModal() {
|
||||||
dispatch(openModal({ modalType: 'CLOSED_REGISTRATIONS' }));
|
dispatch(openModal({ modalType: 'CLOSED_REGISTRATIONS' }));
|
||||||
},
|
},
|
||||||
|
dispatchServer() {
|
||||||
|
dispatch(fetchServer());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
class Header extends PureComponent {
|
class Header extends PureComponent {
|
||||||
|
@ -41,18 +50,26 @@ class Header extends PureComponent {
|
||||||
openClosedRegistrationsModal: PropTypes.func,
|
openClosedRegistrationsModal: PropTypes.func,
|
||||||
location: PropTypes.object,
|
location: PropTypes.object,
|
||||||
signupUrl: PropTypes.string.isRequired,
|
signupUrl: PropTypes.string.isRequired,
|
||||||
|
dispatchServer: PropTypes.func,
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
const { dispatchServer } = this.props;
|
||||||
|
dispatchServer();
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { signedIn } = this.context.identity;
|
const { signedIn } = this.context.identity;
|
||||||
const { location, openClosedRegistrationsModal, signupUrl } = this.props;
|
const { location, openClosedRegistrationsModal, signupUrl, intl } = this.props;
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
|
|
||||||
if (signedIn) {
|
if (signedIn) {
|
||||||
content = (
|
content = (
|
||||||
<>
|
<>
|
||||||
{location.pathname !== '/publish' && <Link to='/publish' className='button'><FormattedMessage id='compose_form.publish_form' defaultMessage='Publish' /></Link>}
|
{location.pathname !== '/search' && <Link to='/search' className='button button-secondary' aria-label={intl.formatMessage(messages.search)}><Icon id='search' /></Link>}
|
||||||
|
{location.pathname !== '/publish' && <Link to='/publish' className='button button-secondary'><FormattedMessage id='compose_form.publish_form' defaultMessage='New post' /></Link>}
|
||||||
<Account />
|
<Account />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -75,6 +92,7 @@ class Header extends PureComponent {
|
||||||
|
|
||||||
content = (
|
content = (
|
||||||
<>
|
<>
|
||||||
|
{location.pathname !== '/search' && <Link to='/search' className='button button-secondary' aria-label={intl.formatMessage(messages.search)}><Icon id='search' /></Link>}
|
||||||
{signupButton}
|
{signupButton}
|
||||||
<a href='/auth/sign_in' className='button button-tertiary'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Login' /></a>
|
<a href='/auth/sign_in' className='button button-tertiary'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Login' /></a>
|
||||||
</>
|
</>
|
||||||
|
@ -97,4 +115,4 @@ class Header extends PureComponent {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Header));
|
export default injectIntl(withRouter(connect(mapStateToProps, mapDispatchToProps)(Header)));
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
|
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
||||||
|
|
||||||
// Keep the markup in sync with <BundleModalError />
|
// Keep the markup in sync with <BundleModalError />
|
||||||
// (make sure they have the same dimensions)
|
// (make sure they have the same dimensions)
|
||||||
|
|
|
@ -137,3 +137,7 @@ export const getAccountHidden = createSelector([
|
||||||
], (hidden, followingOrRequested, isSelf) => {
|
], (hidden, followingOrRequested, isSelf) => {
|
||||||
return hidden && !(isSelf || followingOrRequested);
|
return hidden && !(isSelf || followingOrRequested);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const getStatusList = createSelector([
|
||||||
|
(state, type) => state.getIn(['status_lists', type, 'items']),
|
||||||
|
], (items) => items.toList());
|
||||||
|
|
|
@ -960,26 +960,79 @@ $ui-header-height: 55px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dismissable-banner {
|
.dismissable-banner {
|
||||||
background: $ui-base-color;
|
position: relative;
|
||||||
border-bottom: 1px solid lighten($ui-base-color, 8%);
|
margin: 10px;
|
||||||
display: flex;
|
margin-bottom: 5px;
|
||||||
align-items: center;
|
border-radius: 8px;
|
||||||
gap: 30px;
|
border: 1px solid $highlight-text-color;
|
||||||
|
background: rgba($highlight-text-color, 0.15);
|
||||||
|
padding-inline-end: 45px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&__background-image {
|
||||||
|
width: 125%;
|
||||||
|
position: absolute;
|
||||||
|
bottom: -25%;
|
||||||
|
inset-inline-end: -25%;
|
||||||
|
z-index: -1;
|
||||||
|
opacity: 0.15;
|
||||||
|
mix-blend-mode: luminosity;
|
||||||
|
}
|
||||||
|
|
||||||
&__message {
|
&__message {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
padding: 20px 15px;
|
padding: 15px;
|
||||||
cursor: default;
|
font-size: 15px;
|
||||||
font-size: 14px;
|
line-height: 22px;
|
||||||
line-height: 18px;
|
font-weight: 500;
|
||||||
color: $primary-text-color;
|
color: $primary-text-color;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: $highlight-text-color;
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 33px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
&__wrapper {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
display: block;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-tertiary {
|
||||||
|
background: rgba($ui-base-color, 0.15);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__action {
|
&__action {
|
||||||
padding: 15px;
|
position: absolute;
|
||||||
flex: 0 0 auto;
|
inset-inline-end: 0;
|
||||||
display: flex;
|
top: 0;
|
||||||
align-items: center;
|
padding: 10px;
|
||||||
justify-content: center;
|
|
||||||
|
.icon-button {
|
||||||
|
color: $highlight-text-color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,12 +108,13 @@
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
padding: 6px 17px;
|
padding: 6px 17px;
|
||||||
border: 1px solid $ui-primary-color;
|
border: 1px solid lighten($ui-base-color, 12%);
|
||||||
|
|
||||||
&:active,
|
&:active,
|
||||||
&:focus,
|
&:focus,
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: lighten($ui-primary-color, 4%);
|
background: lighten($ui-base-color, 4%);
|
||||||
|
border-color: lighten($ui-base-color, 16%);
|
||||||
color: lighten($darker-text-color, 4%);
|
color: lighten($darker-text-color, 4%);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1050,7 +1050,9 @@ code {
|
||||||
}
|
}
|
||||||
|
|
||||||
.simple_form .h-captcha {
|
.simple_form .h-captcha {
|
||||||
text-align: center;
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.permissions-list {
|
.permissions-list {
|
||||||
|
|
|
@ -653,11 +653,6 @@ html {
|
||||||
border: 1px solid lighten($ui-base-color, 8%);
|
border: 1px solid lighten($ui-base-color, 8%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dismissable-banner {
|
|
||||||
border-left: 1px solid lighten($ui-base-color, 8%);
|
|
||||||
border-right: 1px solid lighten($ui-base-color, 8%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.status__content,
|
.status__content,
|
||||||
.reply-indicator__content {
|
.reply-indicator__content {
|
||||||
a {
|
a {
|
||||||
|
|
BIN
app/javascript/images/friends-cropped.png
Executable file
BIN
app/javascript/images/friends-cropped.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 189 KiB |
|
@ -129,13 +129,13 @@ export function resetCompose() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const focusCompose = (routerHistory, defaultText) => dispatch => {
|
export const focusCompose = (routerHistory, defaultText) => (dispatch, getState) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: COMPOSE_FOCUS,
|
type: COMPOSE_FOCUS,
|
||||||
defaultText,
|
defaultText,
|
||||||
});
|
});
|
||||||
|
|
||||||
ensureComposeIsVisible(routerHistory);
|
ensureComposeIsVisible(getState, routerHistory);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function mentionCompose(account, routerHistory) {
|
export function mentionCompose(account, routerHistory) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
@ -49,6 +49,7 @@ class Account extends ImmutablePureComponent {
|
||||||
actionTitle: PropTypes.string,
|
actionTitle: PropTypes.string,
|
||||||
defaultAction: PropTypes.string,
|
defaultAction: PropTypes.string,
|
||||||
onActionClick: PropTypes.func,
|
onActionClick: PropTypes.func,
|
||||||
|
withBio: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
|
@ -80,7 +81,7 @@ class Account extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { account, intl, hidden, onActionClick, actionIcon, actionTitle, defaultAction, size, minimal } = this.props;
|
const { account, intl, hidden, withBio, onActionClick, actionIcon, actionTitle, defaultAction, size, minimal } = this.props;
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
return <EmptyAccount size={size} minimal={minimal} />;
|
return <EmptyAccount size={size} minimal={minimal} />;
|
||||||
|
@ -171,6 +172,15 @@ class Account extends ImmutablePureComponent {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{withBio && (account.get('note').length > 0 ? (
|
||||||
|
<div
|
||||||
|
className='account__note translate'
|
||||||
|
dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className='account__note account__note--missing'><FormattedMessage id='account.no_bio' defaultMessage='No description provided.' /></div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { PureComponent } from 'react';
|
|
||||||
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
import ShortNumber from 'mastodon/components/short_number';
|
|
||||||
|
|
||||||
export default class AutosuggestHashtag extends PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
tag: PropTypes.shape({
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
url: PropTypes.string,
|
|
||||||
history: PropTypes.array,
|
|
||||||
}).isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { tag } = this.props;
|
|
||||||
const weeklyUses = tag.history && (
|
|
||||||
<ShortNumber
|
|
||||||
value={tag.history.reduce((total, day) => total + day.uses * 1, 0)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='autosuggest-hashtag'>
|
|
||||||
<div className='autosuggest-hashtag__name'>
|
|
||||||
#<strong>{tag.name}</strong>
|
|
||||||
</div>
|
|
||||||
{tag.history !== undefined && (
|
|
||||||
<div className='autosuggest-hashtag__uses'>
|
|
||||||
<FormattedMessage
|
|
||||||
id='autosuggest_hashtag.per_week'
|
|
||||||
defaultMessage='{count} per week'
|
|
||||||
values={{ count: weeklyUses }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
42
app/javascript/mastodon/components/autosuggest_hashtag.tsx
Normal file
42
app/javascript/mastodon/components/autosuggest_hashtag.tsx
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import ShortNumber from 'mastodon/components/short_number';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
tag: {
|
||||||
|
name: string;
|
||||||
|
url?: string;
|
||||||
|
history?: Array<{
|
||||||
|
uses: number;
|
||||||
|
accounts: string;
|
||||||
|
day: string;
|
||||||
|
}>;
|
||||||
|
following?: boolean;
|
||||||
|
type: 'hashtag';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AutosuggestHashtag: React.FC<Props> = ({ tag }) => {
|
||||||
|
const weeklyUses = tag.history && (
|
||||||
|
<ShortNumber
|
||||||
|
value={tag.history.reduce((total, day) => total + day.uses * 1, 0)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='autosuggest-hashtag'>
|
||||||
|
<div className='autosuggest-hashtag__name'>
|
||||||
|
#<strong>{tag.name}</strong>
|
||||||
|
</div>
|
||||||
|
{tag.history !== undefined && (
|
||||||
|
<div className='autosuggest-hashtag__uses'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='autosuggest_hashtag.per_week'
|
||||||
|
defaultMessage='{count} per week'
|
||||||
|
values={{ count: weeklyUses }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -8,7 +8,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container';
|
import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container';
|
||||||
|
|
||||||
import AutosuggestEmoji from './autosuggest_emoji';
|
import AutosuggestEmoji from './autosuggest_emoji';
|
||||||
import AutosuggestHashtag from './autosuggest_hashtag';
|
import { AutosuggestHashtag } from './autosuggest_hashtag';
|
||||||
|
|
||||||
const textAtCursorMatchesToken = (str, caretPosition, searchTokens) => {
|
const textAtCursorMatchesToken = (str, caretPosition, searchTokens) => {
|
||||||
let word;
|
let word;
|
||||||
|
|
|
@ -10,7 +10,7 @@ import Textarea from 'react-textarea-autosize';
|
||||||
import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container';
|
import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container';
|
||||||
|
|
||||||
import AutosuggestEmoji from './autosuggest_emoji';
|
import AutosuggestEmoji from './autosuggest_emoji';
|
||||||
import AutosuggestHashtag from './autosuggest_hashtag';
|
import { AutosuggestHashtag } from './autosuggest_hashtag';
|
||||||
|
|
||||||
const textAtCursorMatchesToken = (str, caretPosition) => {
|
const textAtCursorMatchesToken = (str, caretPosition) => {
|
||||||
let word;
|
let word;
|
||||||
|
|
27
app/javascript/mastodon/components/circular_progress.tsx
Normal file
27
app/javascript/mastodon/components/circular_progress.tsx
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
interface Props {
|
||||||
|
size: number;
|
||||||
|
strokeWidth: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CircularProgress: React.FC<Props> = ({ size, strokeWidth }) => {
|
||||||
|
const viewBox = `0 0 ${size} ${size}`;
|
||||||
|
const radius = (size - strokeWidth) / 2;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
viewBox={viewBox}
|
||||||
|
className='circular-progress'
|
||||||
|
role='progressbar'
|
||||||
|
>
|
||||||
|
<circle
|
||||||
|
fill='none'
|
||||||
|
cx={size / 2}
|
||||||
|
cy={size / 2}
|
||||||
|
r={radius}
|
||||||
|
strokeWidth={`${strokeWidth}px`}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
|
@ -8,8 +8,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||||
import Overlay from 'react-overlays/Overlay';
|
import Overlay from 'react-overlays/Overlay';
|
||||||
|
|
||||||
import { CircularProgress } from 'mastodon/components/loading_indicator';
|
import { CircularProgress } from "./circular_progress";
|
||||||
|
|
||||||
import { IconButton } from './icon_button';
|
import { IconButton } from './icon_button';
|
||||||
|
|
||||||
const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true;
|
const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true;
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { PureComponent } from 'react';
|
|
||||||
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
export default class LoadPending extends PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
onClick: PropTypes.func,
|
|
||||||
count: PropTypes.number,
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { count } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button className='load-more load-gap' onClick={this.props.onClick}>
|
|
||||||
<FormattedMessage id='load_pending' defaultMessage='{count, plural, one {# new item} other {# new items}}' values={{ count }} />
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
18
app/javascript/mastodon/components/load_pending.tsx
Normal file
18
app/javascript/mastodon/components/load_pending.tsx
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onClick: (event: React.MouseEvent) => void;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LoadPending: React.FC<Props> = ({ onClick, count }) => {
|
||||||
|
return (
|
||||||
|
<button className='load-more load-gap' onClick={onClick}>
|
||||||
|
<FormattedMessage
|
||||||
|
id='load_pending'
|
||||||
|
defaultMessage='{count, plural, one {# new item} other {# new items}}'
|
||||||
|
values={{ count }}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,31 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
export const CircularProgress = ({ size, strokeWidth }) => {
|
|
||||||
const viewBox = `0 0 ${size} ${size}`;
|
|
||||||
const radius = (size - strokeWidth) / 2;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<svg width={size} height={size} viewBox={viewBox} className='circular-progress' role='progressbar'>
|
|
||||||
<circle
|
|
||||||
fill='none'
|
|
||||||
cx={size / 2}
|
|
||||||
cy={size / 2}
|
|
||||||
r={radius}
|
|
||||||
strokeWidth={`${strokeWidth}px`}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
CircularProgress.propTypes = {
|
|
||||||
size: PropTypes.number.isRequired,
|
|
||||||
strokeWidth: PropTypes.number.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
const LoadingIndicator = () => (
|
|
||||||
<div className='loading-indicator'>
|
|
||||||
<CircularProgress size={50} strokeWidth={6} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default LoadingIndicator;
|
|
7
app/javascript/mastodon/components/loading_indicator.tsx
Normal file
7
app/javascript/mastodon/components/loading_indicator.tsx
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { CircularProgress } from './circular_progress';
|
||||||
|
|
||||||
|
export const LoadingIndicator: React.FC = () => (
|
||||||
|
<div className='loading-indicator'>
|
||||||
|
<CircularProgress size={50} strokeWidth={6} />
|
||||||
|
</div>
|
||||||
|
);
|
|
@ -16,8 +16,8 @@ import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from
|
||||||
import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper';
|
import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper';
|
||||||
|
|
||||||
import { LoadMore } from './load_more';
|
import { LoadMore } from './load_more';
|
||||||
import LoadPending from './load_pending';
|
import { LoadPending } from './load_pending';
|
||||||
import LoadingIndicator from './loading_indicator';
|
import { LoadingIndicator } from './loading_indicator';
|
||||||
|
|
||||||
const MOUSE_IDLE_DELAY = 300;
|
const MOUSE_IDLE_DELAY = 300;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
|
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
@ -8,9 +9,9 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
|
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
|
||||||
import EmojiPickerDropdown from '../features/compose/containers/emoji_picker_dropdown_container';
|
|
||||||
|
|
||||||
import DropdownMenuContainer from '../containers/dropdown_menu_container';
|
import DropdownMenuContainer from '../containers/dropdown_menu_container';
|
||||||
|
import EmojiPickerDropdown from '../features/compose/containers/emoji_picker_dropdown_container';
|
||||||
import { me, maxReactions } from '../initial_state';
|
import { me, maxReactions } from '../initial_state';
|
||||||
|
|
||||||
import { IconButton } from './icon_button';
|
import { IconButton } from './icon_button';
|
||||||
|
|
|
@ -1,170 +0,0 @@
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import { autoPlayGif, reduceMotion } from '../initial_state';
|
|
||||||
import spring from 'react-motion/lib/spring';
|
|
||||||
import TransitionMotion from 'react-motion/lib/TransitionMotion';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import React from 'react';
|
|
||||||
import unicodeMapping from '../features/emoji/emoji_unicode_mapping_light';
|
|
||||||
import { AnimatedNumber } from './animated_number';
|
|
||||||
import { assetHost } from '../utils/config';
|
|
||||||
|
|
||||||
export default class StatusReactions extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
statusId: PropTypes.string.isRequired,
|
|
||||||
reactions: ImmutablePropTypes.list.isRequired,
|
|
||||||
numVisible: PropTypes.number,
|
|
||||||
addReaction: PropTypes.func.isRequired,
|
|
||||||
canReact: PropTypes.bool.isRequired,
|
|
||||||
removeReaction: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
willEnter() {
|
|
||||||
return { scale: reduceMotion ? 1 : 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
willLeave() {
|
|
||||||
return { scale: reduceMotion ? 0 : spring(0, { stiffness: 170, damping: 26 }) };
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { reactions, numVisible } = this.props;
|
|
||||||
let visibleReactions = reactions
|
|
||||||
.filter(x => x.get('count') > 0)
|
|
||||||
.sort((a, b) => b.get('count') - a.get('count'));
|
|
||||||
|
|
||||||
if (numVisible >= 0) {
|
|
||||||
visibleReactions = visibleReactions.filter((_, i) => i < numVisible);
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = visibleReactions.map(reaction => ({
|
|
||||||
key: reaction.get('name'),
|
|
||||||
data: reaction,
|
|
||||||
style: { scale: reduceMotion ? 1 : spring(1, { stiffness: 150, damping: 13 }) },
|
|
||||||
})).toArray();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TransitionMotion styles={styles} willEnter={this.willEnter} willLeave={this.willLeave}>
|
|
||||||
{items => (
|
|
||||||
<div className={classNames('reactions-bar', { 'reactions-bar--empty': visibleReactions.isEmpty() })}>
|
|
||||||
{items.map(({ key, data, style }) => (
|
|
||||||
<Reaction
|
|
||||||
key={key}
|
|
||||||
statusId={this.props.statusId}
|
|
||||||
reaction={data}
|
|
||||||
style={{ transform: `scale(${style.scale})`, position: style.scale < 0.5 ? 'absolute' : 'static' }}
|
|
||||||
addReaction={this.props.addReaction}
|
|
||||||
removeReaction={this.props.removeReaction}
|
|
||||||
canReact={this.props.canReact}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</TransitionMotion>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class Reaction extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
statusId: PropTypes.string,
|
|
||||||
reaction: ImmutablePropTypes.map.isRequired,
|
|
||||||
addReaction: PropTypes.func.isRequired,
|
|
||||||
removeReaction: PropTypes.func.isRequired,
|
|
||||||
canReact: PropTypes.bool.isRequired,
|
|
||||||
style: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
hovered: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleClick = () => {
|
|
||||||
const { reaction, statusId, addReaction, removeReaction } = this.props;
|
|
||||||
|
|
||||||
if (reaction.get('me')) {
|
|
||||||
removeReaction(statusId, reaction.get('name'));
|
|
||||||
} else {
|
|
||||||
addReaction(statusId, reaction.get('name'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseEnter = () => this.setState({ hovered: true })
|
|
||||||
|
|
||||||
handleMouseLeave = () => this.setState({ hovered: false })
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { reaction } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
className={classNames('reactions-bar__item', { active: reaction.get('me') })}
|
|
||||||
onClick={this.handleClick}
|
|
||||||
onMouseEnter={this.handleMouseEnter}
|
|
||||||
onMouseLeave={this.handleMouseLeave}
|
|
||||||
disabled={!this.props.canReact}
|
|
||||||
style={this.props.style}
|
|
||||||
>
|
|
||||||
<span className='reactions-bar__item__emoji'>
|
|
||||||
<Emoji
|
|
||||||
hovered={this.state.hovered}
|
|
||||||
emoji={reaction.get('name')}
|
|
||||||
url={reaction.get('url')}
|
|
||||||
staticUrl={reaction.get('static_url')}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<span className='reactions-bar__item__count'>
|
|
||||||
<AnimatedNumber value={reaction.get('count')} />
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class Emoji extends React.PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
emoji: PropTypes.string.isRequired,
|
|
||||||
hovered: PropTypes.bool.isRequired,
|
|
||||||
url: PropTypes.string,
|
|
||||||
staticUrl: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { emoji, hovered, url, staticUrl } = this.props;
|
|
||||||
|
|
||||||
if (unicodeMapping[emoji]) {
|
|
||||||
const { filename, shortCode } = unicodeMapping[this.props.emoji];
|
|
||||||
const title = shortCode ? `:${shortCode}:` : '';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<img
|
|
||||||
draggable='false'
|
|
||||||
className='emojione'
|
|
||||||
alt={emoji}
|
|
||||||
title={title}
|
|
||||||
src={`${assetHost}/emoji/${filename}.svg`}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const filename = (autoPlayGif || hovered) ? url : staticUrl;
|
|
||||||
const shortCode = `:${emoji}:`;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<img
|
|
||||||
draggable='false'
|
|
||||||
className='emojione custom-emoji'
|
|
||||||
alt={shortCode}
|
|
||||||
title={shortCode}
|
|
||||||
src={filename}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,15 +1,20 @@
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import { autoPlayGif, reduceMotion } from '../initial_state';
|
|
||||||
import spring from 'react-motion/lib/spring';
|
|
||||||
import TransitionMotion from 'react-motion/lib/TransitionMotion';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
|
||||||
|
import TransitionMotion from 'react-motion/lib/TransitionMotion';
|
||||||
|
import spring from 'react-motion/lib/spring';
|
||||||
|
|
||||||
import unicodeMapping from '../features/emoji/emoji_unicode_mapping_light';
|
import unicodeMapping from '../features/emoji/emoji_unicode_mapping_light';
|
||||||
import { AnimatedNumber } from './animated_number';
|
import { autoPlayGif, reduceMotion } from '../initial_state';
|
||||||
import { assetHost } from '../utils/config';
|
import { assetHost } from '../utils/config';
|
||||||
|
|
||||||
|
import { AnimatedNumber } from './animated_number';
|
||||||
|
|
||||||
export default class StatusReactions extends ImmutablePureComponent {
|
export default class StatusReactions extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
|
|
@ -1,11 +1,27 @@
|
||||||
import { Icon } from './icon';
|
import { Icon } from './icon';
|
||||||
|
|
||||||
|
const domParser = new DOMParser();
|
||||||
|
|
||||||
|
const stripRelMe = (html: string) => {
|
||||||
|
const document = domParser.parseFromString(html, 'text/html').documentElement;
|
||||||
|
|
||||||
|
document.querySelectorAll<HTMLAnchorElement>('a[rel]').forEach((link) => {
|
||||||
|
link.rel = link.rel
|
||||||
|
.split(' ')
|
||||||
|
.filter((x: string) => x !== 'me')
|
||||||
|
.join(' ');
|
||||||
|
});
|
||||||
|
|
||||||
|
const body = document.querySelector('body');
|
||||||
|
return body ? { __html: body.innerHTML } : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
link: string;
|
link: string;
|
||||||
}
|
}
|
||||||
export const VerifiedBadge: React.FC<Props> = ({ link }) => (
|
export const VerifiedBadge: React.FC<Props> = ({ link }) => (
|
||||||
<span className='verified-badge'>
|
<span className='verified-badge'>
|
||||||
<Icon id='check' className='verified-badge__mark' />
|
<Icon id='check' className='verified-badge__mark' />
|
||||||
<span dangerouslySetInnerHTML={{ __html: link }} />
|
<span dangerouslySetInnerHTML={stripRelMe(link)} />
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
|
@ -374,7 +374,7 @@ class Header extends ImmutablePureComponent {
|
||||||
let badge;
|
let badge;
|
||||||
|
|
||||||
if (account.get('bot')) {
|
if (account.get('bot')) {
|
||||||
badge = (<div className='account-role bot'><FormattedMessage id='account.badges.bot' defaultMessage='Bot' /></div>);
|
badge = (<div className='account-role bot'><FormattedMessage id='account.badges.bot' defaultMessage='Automated' /></div>);
|
||||||
} else if (account.get('group')) {
|
} else if (account.get('group')) {
|
||||||
badge = (<div className='account-role group'><FormattedMessage id='account.badges.group' defaultMessage='Group' /></div>);
|
badge = (<div className='account-role group'><FormattedMessage id='account.badges.group' defaultMessage='Group' /></div>);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { lookupAccount, fetchAccount } from 'mastodon/actions/accounts';
|
||||||
import { openModal } from 'mastodon/actions/modal';
|
import { openModal } from 'mastodon/actions/modal';
|
||||||
import ColumnBackButton from 'mastodon/components/column_back_button';
|
import ColumnBackButton from 'mastodon/components/column_back_button';
|
||||||
import { LoadMore } from 'mastodon/components/load_more';
|
import { LoadMore } from 'mastodon/components/load_more';
|
||||||
import LoadingIndicator from 'mastodon/components/loading_indicator';
|
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||||
import ScrollContainer from 'mastodon/containers/scroll_container';
|
import ScrollContainer from 'mastodon/containers/scroll_container';
|
||||||
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
|
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
|
||||||
import { normalizeForLookup } from 'mastodon/reducers/accounts_map';
|
import { normalizeForLookup } from 'mastodon/reducers/accounts_map';
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { lookupAccount, fetchAccount } from '../../actions/accounts';
|
||||||
import { fetchFeaturedTags } from '../../actions/featured_tags';
|
import { fetchFeaturedTags } from '../../actions/featured_tags';
|
||||||
import { expandAccountFeaturedTimeline, expandAccountTimeline, connectTimeline, disconnectTimeline } from '../../actions/timelines';
|
import { expandAccountFeaturedTimeline, expandAccountTimeline, connectTimeline, disconnectTimeline } from '../../actions/timelines';
|
||||||
import ColumnBackButton from '../../components/column_back_button';
|
import ColumnBackButton from '../../components/column_back_button';
|
||||||
import LoadingIndicator from '../../components/loading_indicator';
|
import { LoadingIndicator } from '../../components/loading_indicator';
|
||||||
import StatusList from '../../components/status_list';
|
import StatusList from '../../components/status_list';
|
||||||
import Column from '../ui/components/column';
|
import Column from '../ui/components/column';
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { debounce } from 'lodash';
|
||||||
|
|
||||||
import { fetchBlocks, expandBlocks } from '../../actions/blocks';
|
import { fetchBlocks, expandBlocks } from '../../actions/blocks';
|
||||||
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
|
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
|
||||||
import LoadingIndicator from '../../components/loading_indicator';
|
import { LoadingIndicator } from '../../components/loading_indicator';
|
||||||
import ScrollableList from '../../components/scrollable_list';
|
import ScrollableList from '../../components/scrollable_list';
|
||||||
import AccountContainer from '../../containers/account_container';
|
import AccountContainer from '../../containers/account_container';
|
||||||
import Column from '../ui/components/column';
|
import Column from '../ui/components/column';
|
||||||
|
|
|
@ -15,13 +15,14 @@ import { addColumn, removeColumn, moveColumn } from 'mastodon/actions/columns';
|
||||||
import ColumnHeader from 'mastodon/components/column_header';
|
import ColumnHeader from 'mastodon/components/column_header';
|
||||||
import StatusList from 'mastodon/components/status_list';
|
import StatusList from 'mastodon/components/status_list';
|
||||||
import Column from 'mastodon/features/ui/components/column';
|
import Column from 'mastodon/features/ui/components/column';
|
||||||
|
import { getStatusList } from 'mastodon/selectors';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
heading: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' },
|
heading: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
statusIds: state.getIn(['status_lists', 'bookmarks', 'items']),
|
statusIds: getStatusList(state, 'bookmarks'),
|
||||||
isLoading: state.getIn(['status_lists', 'bookmarks', 'isLoading'], true),
|
isLoading: state.getIn(['status_lists', 'bookmarks', 'isLoading'], true),
|
||||||
hasMore: !!state.getIn(['status_lists', 'bookmarks', 'next']),
|
hasMore: !!state.getIn(['status_lists', 'bookmarks', 'next']),
|
||||||
});
|
});
|
||||||
|
|
|
@ -140,11 +140,8 @@ class CommunityTimeline extends PureComponent {
|
||||||
<ColumnSettingsContainer columnId={columnId} />
|
<ColumnSettingsContainer columnId={columnId} />
|
||||||
</ColumnHeader>
|
</ColumnHeader>
|
||||||
|
|
||||||
<DismissableBanner id='community_timeline'>
|
|
||||||
<FormattedMessage id='dismissable_banner.community_timeline' defaultMessage='These are the most recent public posts from people whose accounts are hosted by {domain}.' values={{ domain }} />
|
|
||||||
</DismissableBanner>
|
|
||||||
|
|
||||||
<StatusListContainer
|
<StatusListContainer
|
||||||
|
prepend={<DismissableBanner id='community_timeline'><FormattedMessage id='dismissable_banner.community_timeline' defaultMessage='These are the most recent public posts from people whose accounts are hosted by {domain}.' values={{ domain }} /></DismissableBanner>}
|
||||||
trackScroll={!pinned}
|
trackScroll={!pinned}
|
||||||
scrollKey={`community_timeline-${columnId}`}
|
scrollKey={`community_timeline-${columnId}`}
|
||||||
timelineId={`community${onlyMedia ? ':media' : ''}`}
|
timelineId={`community${onlyMedia ? ':media' : ''}`}
|
||||||
|
|
|
@ -390,7 +390,7 @@ class EmojiPickerDropdown extends PureComponent {
|
||||||
{button || <img
|
{button || <img
|
||||||
className={classNames('emojione', { 'pulse-loading': active && loading })}
|
className={classNames('emojione', { 'pulse-loading': active && loading })}
|
||||||
alt='🙂'
|
alt='🙂'
|
||||||
src={`${assetHost}/emoji/1f602.svg`}
|
src={`${assetHost}/emoji/1f642.svg`}
|
||||||
/>}
|
/>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ export default class Upload extends ImmutablePureComponent {
|
||||||
const y = ((focusY / -2) + .5) * 100;
|
const y = ((focusY / -2) + .5) * 100;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='compose-form__upload' tabIndex={0} role='button'>
|
<div className='compose-form__upload'>
|
||||||
<Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
|
<Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
|
||||||
{({ scale }) => (
|
{({ scale }) => (
|
||||||
<div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
|
<div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { fetchDirectory, expandDirectory } from 'mastodon/actions/directory';
|
||||||
import Column from 'mastodon/components/column';
|
import Column from 'mastodon/components/column';
|
||||||
import ColumnHeader from 'mastodon/components/column_header';
|
import ColumnHeader from 'mastodon/components/column_header';
|
||||||
import { LoadMore } from 'mastodon/components/load_more';
|
import { LoadMore } from 'mastodon/components/load_more';
|
||||||
import LoadingIndicator from 'mastodon/components/loading_indicator';
|
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||||
import { RadioButton } from 'mastodon/components/radio_button';
|
import { RadioButton } from 'mastodon/components/radio_button';
|
||||||
import ScrollContainer from 'mastodon/containers/scroll_container';
|
import ScrollContainer from 'mastodon/containers/scroll_container';
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { debounce } from 'lodash';
|
||||||
|
|
||||||
import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks';
|
import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks';
|
||||||
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
|
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
|
||||||
import LoadingIndicator from '../../components/loading_indicator';
|
import { LoadingIndicator } from '../../components/loading_indicator';
|
||||||
import ScrollableList from '../../components/scrollable_list';
|
import ScrollableList from '../../components/scrollable_list';
|
||||||
import DomainContainer from '../../containers/domain_container';
|
import DomainContainer from '../../containers/domain_container';
|
||||||
import Column from '../ui/components/column';
|
import Column from '../ui/components/column';
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue