Merge pull request #2236 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changes up to e387175fc9
			
			
This commit is contained in:
		
						commit
						0222df6047
					
				
					 335 changed files with 3918 additions and 1533 deletions
				
			
		
							
								
								
									
										73
									
								
								.eslintrc.js
									
									
									
									
									
								
							
							
						
						
									
										73
									
								
								.eslintrc.js
									
									
									
									
									
								
							|  | @ -55,10 +55,7 @@ module.exports = { | |||
|       '\\.(css|scss|json)$', | ||||
|     ], | ||||
|     'import/resolver': { | ||||
|       node: { | ||||
|         paths: ['app/javascript'], | ||||
|         extensions: ['.js', '.jsx', '.ts', '.tsx'], | ||||
|       }, | ||||
|       typescript: {}, | ||||
|     }, | ||||
|   }, | ||||
| 
 | ||||
|  | @ -104,7 +101,6 @@ module.exports = { | |||
|     'react/jsx-equals-spacing': 'error', | ||||
|     'react/jsx-no-bind': 'error', | ||||
|     'react/jsx-no-target-blank': 'off', | ||||
|     'react/no-deprecated': 'off', | ||||
|     'react/no-unknown-property': 'off', | ||||
|     'react/self-closing-comp': 'error', | ||||
| 
 | ||||
|  | @ -168,11 +164,14 @@ module.exports = { | |||
|       { | ||||
|         js: 'never', | ||||
|         jsx: 'never', | ||||
|         mjs: 'never', | ||||
|         ts: 'never', | ||||
|         tsx: 'never', | ||||
|       }, | ||||
|     ], | ||||
|     'import/first': 'error', | ||||
|     'import/newline-after-import': 'error', | ||||
|     'import/no-anonymous-default-export': 'error', | ||||
|     'import/no-extraneous-dependencies': [ | ||||
|       'error', | ||||
|       { | ||||
|  | @ -187,6 +186,9 @@ module.exports = { | |||
|     'import/no-amd': 'error', | ||||
|     'import/no-commonjs': 'error', | ||||
|     'import/no-import-module-exports': 'error', | ||||
|     'import/no-relative-packages': 'error', | ||||
|     'import/no-self-import': 'error', | ||||
|     'import/no-useless-path-segments': 'error', | ||||
|     'import/no-webpack-loader-syntax': 'error', | ||||
| 
 | ||||
|     'promise/always-return': 'off', | ||||
|  | @ -258,6 +260,7 @@ module.exports = { | |||
|       extends: [ | ||||
|         'eslint:recommended', | ||||
|         'plugin:@typescript-eslint/recommended', | ||||
|         'plugin:@typescript-eslint/recommended-requiring-type-checking', | ||||
|         'plugin:react/recommended', | ||||
|         'plugin:react-hooks/recommended', | ||||
|         'plugin:jsx-a11y/recommended', | ||||
|  | @ -268,8 +271,66 @@ module.exports = { | |||
|         'plugin:prettier/recommended', | ||||
|       ], | ||||
| 
 | ||||
|       parserOptions: { | ||||
|         project: './tsconfig.json', | ||||
|         tsconfigRootDir: __dirname, | ||||
|       }, | ||||
| 
 | ||||
|       rules: { | ||||
|         '@typescript-eslint/no-explicit-any': 'off', | ||||
|         'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], | ||||
| 
 | ||||
|         'import/order': [ | ||||
|           'error', | ||||
|           { | ||||
|             alphabetize: { order: 'asc' }, | ||||
|             'newlines-between': 'always', | ||||
|             groups: [ | ||||
|               'builtin', | ||||
|               'external', | ||||
|               'internal', | ||||
|               'parent', | ||||
|               ['index', 'sibling'], | ||||
|               'object', | ||||
|             ], | ||||
|             pathGroups: [ | ||||
|               // React core packages
 | ||||
|               { | ||||
|                 pattern: '{react,react-dom,prop-types}', | ||||
|                 group: 'builtin', | ||||
|                 position: 'after', | ||||
|               }, | ||||
|               // I18n
 | ||||
|               { | ||||
|                 pattern: 'react-intl', | ||||
|                 group: 'builtin', | ||||
|                 position: 'after', | ||||
|               }, | ||||
|               // Common React utilities
 | ||||
|               { | ||||
|                 pattern: '{classnames,react-helmet}', | ||||
|                 group: 'external', | ||||
|                 position: 'before', | ||||
|               }, | ||||
|               // Immutable / Redux / data store
 | ||||
|               { | ||||
|                 pattern: '{immutable,react-redux,react-immutable-proptypes,react-immutable-pure-component,reselect}', | ||||
|                 group: 'external', | ||||
|                 position: 'before', | ||||
|               }, | ||||
|               // Internal packages
 | ||||
|               { | ||||
|                 pattern: '{mastodon/**,flavours/glitch-soc/**}', | ||||
|                 group: 'internal', | ||||
|                 position: 'after', | ||||
|               }, | ||||
|             ], | ||||
|             pathGroupsExcludedImportTypes: [], | ||||
|           }, | ||||
|         ], | ||||
| 
 | ||||
|         '@typescript-eslint/consistent-type-definitions': ['warn', 'interface'], | ||||
|         '@typescript-eslint/consistent-type-exports': 'error', | ||||
|         '@typescript-eslint/consistent-type-imports': 'error', | ||||
| 
 | ||||
|         'jsdoc/require-jsdoc': 'off', | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										10
									
								
								.github/workflows/test-migrations-one-step.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/test-migrations-one-step.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -23,9 +23,17 @@ jobs: | |||
|     needs: pre_job | ||||
|     if: needs.pre_job.outputs.should_skip != 'true' | ||||
| 
 | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
| 
 | ||||
|       matrix: | ||||
|         postgres: | ||||
|           - 14-alpine | ||||
|           - 15-alpine | ||||
| 
 | ||||
|     services: | ||||
|       postgres: | ||||
|         image: postgres:14-alpine | ||||
|         image: postgres:${{ matrix.postgres}} | ||||
|         env: | ||||
|           POSTGRES_PASSWORD: postgres | ||||
|           POSTGRES_USER: postgres | ||||
|  |  | |||
							
								
								
									
										10
									
								
								.github/workflows/test-migrations-two-step.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/test-migrations-two-step.yml
									
									
									
									
										vendored
									
									
								
							|  | @ -23,9 +23,17 @@ jobs: | |||
|     needs: pre_job | ||||
|     if: needs.pre_job.outputs.should_skip != 'true' | ||||
| 
 | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
| 
 | ||||
|       matrix: | ||||
|         postgres: | ||||
|           - 14-alpine | ||||
|           - 15-alpine | ||||
| 
 | ||||
|     services: | ||||
|       postgres: | ||||
|         image: postgres:14-alpine | ||||
|         image: postgres:${{ matrix.postgres}} | ||||
|         env: | ||||
|           POSTGRES_PASSWORD: postgres | ||||
|           POSTGRES_USER: postgres | ||||
|  |  | |||
|  | @ -21,12 +21,6 @@ Layout/ArgumentAlignment: | |||
|     - 'config/initializers/cors.rb' | ||||
|     - 'config/initializers/session_store.rb' | ||||
| 
 | ||||
| # This cop supports safe autocorrection (--autocorrect). | ||||
| # Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment. | ||||
| Layout/ExtraSpacing: | ||||
|   Exclude: | ||||
|     - 'config/initializers/omniauth.rb' | ||||
| 
 | ||||
| # This cop supports safe autocorrection (--autocorrect). | ||||
| # Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. | ||||
| # SupportedHashRocketStyles: key, separator, table | ||||
|  | @ -39,12 +33,6 @@ Layout/HashAlignment: | |||
|     - 'config/initializers/rack_attack.rb' | ||||
|     - 'config/routes.rb' | ||||
| 
 | ||||
| # This cop supports safe autocorrection (--autocorrect). | ||||
| # Configuration parameters: Width, AllowedPatterns. | ||||
| Layout/IndentationWidth: | ||||
|   Exclude: | ||||
|     - 'config/initializers/ffmpeg.rb' | ||||
| 
 | ||||
| # This cop supports safe autocorrection (--autocorrect). | ||||
| # Configuration parameters: AllowDoxygenCommentStyle, AllowGemfileRubyComment. | ||||
| Layout/LeadingCommentSpace: | ||||
|  | @ -52,14 +40,6 @@ Layout/LeadingCommentSpace: | |||
|     - 'config/application.rb' | ||||
|     - 'config/initializers/omniauth.rb' | ||||
| 
 | ||||
| # This cop supports safe autocorrection (--autocorrect). | ||||
| # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. | ||||
| # SupportedStyles: space, no_space | ||||
| # SupportedStylesForEmptyBraces: space, no_space | ||||
| Layout/SpaceBeforeBlockBraces: | ||||
|   Exclude: | ||||
|     - 'config/initializers/paperclip.rb' | ||||
| 
 | ||||
| # This cop supports safe autocorrection (--autocorrect). | ||||
| # Configuration parameters: EnforcedStyle. | ||||
| # SupportedStyles: require_no_space, require_space | ||||
|  | @ -68,19 +48,6 @@ Layout/SpaceInLambdaLiteral: | |||
|     - 'config/environments/production.rb' | ||||
|     - 'config/initializers/content_security_policy.rb' | ||||
| 
 | ||||
| # This cop supports safe autocorrection (--autocorrect). | ||||
| # Configuration parameters: EnforcedStyle. | ||||
| # SupportedStyles: space, no_space | ||||
| Layout/SpaceInsideStringInterpolation: | ||||
|   Exclude: | ||||
|     - 'config/initializers/webauthn.rb' | ||||
| 
 | ||||
| # This cop supports safe autocorrection (--autocorrect). | ||||
| # Configuration parameters: AllowInHeredoc. | ||||
| Layout/TrailingWhitespace: | ||||
|   Exclude: | ||||
|     - 'config/initializers/paperclip.rb' | ||||
| 
 | ||||
| # Configuration parameters: AllowedMethods, AllowedPatterns. | ||||
| Lint/AmbiguousBlockAssociation: | ||||
|   Exclude: | ||||
|  | @ -94,11 +61,6 @@ Lint/AmbiguousBlockAssociation: | |||
|     - 'spec/services/unsuspend_account_service_spec.rb' | ||||
|     - 'spec/workers/scheduler/accounts_statuses_cleanup_scheduler_spec.rb' | ||||
| 
 | ||||
| # This cop supports safe autocorrection (--autocorrect). | ||||
| Lint/AmbiguousOperatorPrecedence: | ||||
|   Exclude: | ||||
|     - 'config/initializers/rack_attack.rb' | ||||
| 
 | ||||
| # Configuration parameters: AllowComments, AllowEmptyLambdas. | ||||
| Lint/EmptyBlock: | ||||
|   Exclude: | ||||
|  | @ -277,31 +239,6 @@ Naming/VariableNumber: | |||
|     - 'spec/models/user_spec.rb' | ||||
|     - 'spec/services/activitypub/fetch_featured_collection_service_spec.rb' | ||||
| 
 | ||||
| # This cop supports unsafe autocorrection (--autocorrect-all). | ||||
| Performance/MapCompact: | ||||
|   Exclude: | ||||
|     - 'app/lib/admin/metrics/dimension.rb' | ||||
|     - 'app/lib/admin/metrics/measure.rb' | ||||
|     - 'app/lib/feed_manager.rb' | ||||
|     - 'app/models/account.rb' | ||||
|     - 'app/models/account_statuses_cleanup_policy.rb' | ||||
|     - 'app/models/account_suggestions/setting_source.rb' | ||||
|     - 'app/models/account_suggestions/source.rb' | ||||
|     - 'app/models/follow_recommendation_filter.rb' | ||||
|     - 'app/models/notification.rb' | ||||
|     - 'app/models/user_role.rb' | ||||
|     - 'app/models/webhook.rb' | ||||
|     - 'app/services/process_mentions_service.rb' | ||||
|     - 'app/validators/existing_username_validator.rb' | ||||
|     - 'db/migrate/20200407202420_migrate_unavailable_inboxes.rb' | ||||
|     - 'spec/presenters/status_relationships_presenter_spec.rb' | ||||
| 
 | ||||
| # This cop supports unsafe autocorrection (--autocorrect-all). | ||||
| # Configuration parameters: SafeMultiline. | ||||
| Performance/StartWith: | ||||
|   Exclude: | ||||
|     - 'app/lib/extractor.rb' | ||||
| 
 | ||||
| # This cop supports unsafe autocorrection (--autocorrect-all). | ||||
| Performance/UnfreezeString: | ||||
|   Exclude: | ||||
|  | @ -626,7 +563,6 @@ RSpec/NoExpectationExample: | |||
| 
 | ||||
| RSpec/PendingWithoutReason: | ||||
|   Exclude: | ||||
|     - 'spec/controllers/statuses_controller_spec.rb' | ||||
|     - 'spec/models/account_spec.rb' | ||||
| 
 | ||||
| # This cop supports unsafe autocorrection (--autocorrect-all). | ||||
|  | @ -638,32 +574,6 @@ RSpec/PredicateMatcher: | |||
|     - 'spec/models/user_spec.rb' | ||||
|     - 'spec/services/post_status_service_spec.rb' | ||||
| 
 | ||||
| RSpec/RepeatedExample: | ||||
|   Exclude: | ||||
|     - 'spec/policies/status_policy_spec.rb' | ||||
| 
 | ||||
| RSpec/RepeatedExampleGroupBody: | ||||
|   Exclude: | ||||
|     - 'spec/controllers/statuses_controller_spec.rb' | ||||
| 
 | ||||
| RSpec/RepeatedExampleGroupDescription: | ||||
|   Exclude: | ||||
|     - 'spec/controllers/admin/reports/actions_controller_spec.rb' | ||||
|     - 'spec/policies/report_note_policy_spec.rb' | ||||
| 
 | ||||
| RSpec/ScatteredSetup: | ||||
|   Exclude: | ||||
|     - 'spec/controllers/activitypub/followers_synchronizations_controller_spec.rb' | ||||
|     - 'spec/controllers/activitypub/outboxes_controller_spec.rb' | ||||
|     - 'spec/controllers/admin/disputes/appeals_controller_spec.rb' | ||||
|     - 'spec/controllers/auth/registrations_controller_spec.rb' | ||||
|     - 'spec/services/activitypub/process_account_service_spec.rb' | ||||
| 
 | ||||
| # This cop supports safe autocorrection (--autocorrect). | ||||
| RSpec/SharedContext: | ||||
|   Exclude: | ||||
|     - 'spec/services/unsuspend_account_service_spec.rb' | ||||
| 
 | ||||
| RSpec/StubbedMock: | ||||
|   Exclude: | ||||
|     - 'spec/controllers/api/base_controller_spec.rb' | ||||
|  |  | |||
|  | @ -55,7 +55,7 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"] | |||
| ENV DEBIAN_FRONTEND="noninteractive" \ | ||||
|     PATH="${PATH}:/opt/ruby/bin:/opt/mastodon/bin" | ||||
| 
 | ||||
| # Ignoreing these here since we don't want to pin any versions and the Debian image removes apt-get content after use | ||||
| # Ignoring these here since we don't want to pin any versions and the Debian image removes apt-get content after use | ||||
| # hadolint ignore=DL3008,DL3009 | ||||
| RUN apt-get update && \ | ||||
|     echo "Etc/UTC" > /etc/localtime && \ | ||||
|  |  | |||
							
								
								
									
										92
									
								
								Gemfile
									
									
									
									
									
								
							
							
						
						
									
										92
									
								
								Gemfile
									
									
									
									
									
								
							|  | @ -17,7 +17,7 @@ gem 'makara', '~> 0.5' | |||
| gem 'pghero' | ||||
| gem 'dotenv-rails', '~> 2.8' | ||||
| 
 | ||||
| gem 'aws-sdk-s3', '~> 1.120', require: false | ||||
| gem 'aws-sdk-s3', '~> 1.122', require: false | ||||
| gem 'fog-core', '<= 2.4.0' | ||||
| gem 'fog-openstack', '~> 0.3', require: false | ||||
| gem 'kt-paperclip', '~> 7.1', github: 'kreeti/kt-paperclip', ref: '11abf222dc31bff71160a1d138b445214f434b2b' | ||||
|  | @ -75,7 +75,7 @@ gem 'rails-settings-cached', '~> 0.6', git: 'https://github.com/mastodon/rails-s | |||
| gem 'redcarpet', '~> 3.6' | ||||
| gem 'redis', '~> 4.5', require: ['redis', 'redis/connection/hiredis'] | ||||
| gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock' | ||||
| gem 'rqrcode', '~> 2.1' | ||||
| gem 'rqrcode', '~> 2.2' | ||||
| gem 'ruby-progressbar', '~> 1.13' | ||||
| gem 'sanitize', '~> 6.0' | ||||
| gem 'scenic', '~> 1.7' | ||||
|  | @ -99,54 +99,87 @@ gem 'json-ld' | |||
| gem 'json-ld-preloaded', '~> 3.2' | ||||
| gem 'rdf-normalize', '~> 0.5' | ||||
| 
 | ||||
| group :development, :test do | ||||
|   gem 'fabrication', '~> 2.30' | ||||
|   gem 'fuubar', '~> 2.5' | ||||
|   gem 'i18n-tasks', '~> 1.0', require: false | ||||
|   gem 'rspec-rails', '~> 6.0' | ||||
|   gem 'rspec_chunked', '~> 0.6' | ||||
| 
 | ||||
|   gem 'rubocop-capybara', require: false | ||||
|   gem 'rubocop-performance', require: false | ||||
|   gem 'rubocop-rails', require: false | ||||
|   gem 'rubocop-rspec', require: false | ||||
|   gem 'rubocop', require: false | ||||
| end | ||||
| 
 | ||||
| group :production, :test do | ||||
|   gem 'private_address_check', '~> 0.5' | ||||
| end | ||||
| gem 'private_address_check', '~> 0.5' | ||||
| 
 | ||||
| group :test do | ||||
|   gem 'capybara', '~> 3.39' | ||||
|   gem 'climate_control' | ||||
|   gem 'faker', '~> 3.2' | ||||
|   gem 'json-schema', '~> 4.0' | ||||
|   gem 'rack-test', '~> 2.1' | ||||
|   gem 'rails-controller-testing', '~> 1.0' | ||||
|   gem 'rspec_junit_formatter', '~> 0.6' | ||||
|   # RSpec runner for rails | ||||
|   gem 'rspec-rails', '~> 6.0' | ||||
| 
 | ||||
|   # Used to split testing into chunks in CI | ||||
|   gem 'rspec_chunked', '~> 0.6' | ||||
| 
 | ||||
|   # RSpec progress bar formatter | ||||
|   gem 'fuubar', '~> 2.5' | ||||
| 
 | ||||
|   # Extra RSpec extenion methods and helpers for sidekiq | ||||
|   gem 'rspec-sidekiq', '~> 3.1' | ||||
| 
 | ||||
|   # Browser integration testing | ||||
|   gem 'capybara', '~> 3.39' | ||||
| 
 | ||||
|   # Used to mock environment variables | ||||
|   gem 'climate_control', '~> 0.2' | ||||
| 
 | ||||
|   # Generating fake data for specs | ||||
|   gem 'faker', '~> 3.2' | ||||
| 
 | ||||
|   # Generate test objects for specs | ||||
|   gem 'fabrication', '~> 2.30' | ||||
| 
 | ||||
|   # Add back helpers functions removed in Rails 5.1 | ||||
|   gem 'rails-controller-testing', '~> 1.0' | ||||
| 
 | ||||
|   # Validate schemas in specs | ||||
|   gem 'json-schema', '~> 4.0' | ||||
| 
 | ||||
|   # Test harness fo rack components | ||||
|   gem 'rack-test', '~> 2.1' | ||||
| 
 | ||||
|   # Coverage formatter for RSpec test if DISABLE_SIMPLECOV is false | ||||
|   gem 'simplecov', '~> 0.22', require: false | ||||
| 
 | ||||
|   # Stub web requests for specs | ||||
|   gem 'webmock', '~> 3.18' | ||||
| end | ||||
| 
 | ||||
| group :development do | ||||
|   # Code linting CLI and plugins | ||||
|   gem 'rubocop', require: false | ||||
|   gem 'rubocop-capybara', require: false | ||||
|   gem 'rubocop-performance', require: false | ||||
|   gem 'rubocop-rails', require: false | ||||
|   gem 'rubocop-rspec', require: false | ||||
| 
 | ||||
|   # Annotates modules with schema | ||||
|   gem 'annotate', '~> 3.2' | ||||
| 
 | ||||
|   # Enhanced error message pages for development | ||||
|   gem 'better_errors', '~> 2.9' | ||||
|   gem 'binding_of_caller', '~> 1.0' | ||||
| 
 | ||||
|   # Preview mail in the browser | ||||
|   gem 'letter_opener', '~> 1.8' | ||||
|   gem 'letter_opener_web', '~> 2.0' | ||||
|   gem 'memory_profiler' | ||||
| 
 | ||||
|   # Security analysis CLI tools | ||||
|   gem 'brakeman', '~> 5.4', require: false | ||||
|   gem 'bundler-audit', '~> 0.9', require: false | ||||
| 
 | ||||
|   # Linter CLI for HAML files | ||||
|   gem 'haml_lint', require: false | ||||
| 
 | ||||
|   # Deployment automation | ||||
|   gem 'capistrano', '~> 3.17' | ||||
|   gem 'capistrano-rails', '~> 1.6' | ||||
|   gem 'capistrano-rbenv', '~> 2.2' | ||||
|   gem 'capistrano-yarn', '~> 2.0' | ||||
| 
 | ||||
|   gem 'stackprof' | ||||
|   # Validate missing i18n keys | ||||
|   gem 'i18n-tasks', '~> 1.0', require: false | ||||
| 
 | ||||
|   # Profiling tools | ||||
|   gem 'memory_profiler', require: false | ||||
|   gem 'stackprof', require: false | ||||
| end | ||||
| 
 | ||||
| group :production do | ||||
|  | @ -157,8 +190,9 @@ gem 'concurrent-ruby', require: false | |||
| gem 'connection_pool', require: false | ||||
| gem 'xorcist', '~> 1.1' | ||||
| 
 | ||||
| gem 'hcaptcha', '~> 7.1' | ||||
| gem 'cocoon', '~> 1.2' | ||||
| 
 | ||||
| gem 'net-http', '~> 0.3.2' | ||||
| gem 'rubyzip', '~> 2.3' | ||||
| 
 | ||||
| gem 'hcaptcha', '~> 7.1' | ||||
|  |  | |||
							
								
								
									
										51
									
								
								Gemfile.lock
									
									
									
									
									
								
							
							
						
						
									
										51
									
								
								Gemfile.lock
									
									
									
									
									
								
							|  | @ -109,16 +109,16 @@ GEM | |||
|     attr_required (1.0.1) | ||||
|     awrence (1.2.1) | ||||
|     aws-eventstream (1.2.0) | ||||
|     aws-partitions (1.752.0) | ||||
|     aws-sdk-core (3.171.0) | ||||
|     aws-partitions (1.761.0) | ||||
|     aws-sdk-core (3.172.0) | ||||
|       aws-eventstream (~> 1, >= 1.0.2) | ||||
|       aws-partitions (~> 1, >= 1.651.0) | ||||
|       aws-sigv4 (~> 1.5) | ||||
|       jmespath (~> 1, >= 1.6.1) | ||||
|     aws-sdk-kms (1.63.0) | ||||
|     aws-sdk-kms (1.64.0) | ||||
|       aws-sdk-core (~> 3, >= 3.165.0) | ||||
|       aws-sigv4 (~> 1.1) | ||||
|     aws-sdk-s3 (1.121.0) | ||||
|     aws-sdk-s3 (1.122.0) | ||||
|       aws-sdk-core (~> 3, >= 3.165.0) | ||||
|       aws-sdk-kms (~> 1) | ||||
|       aws-sigv4 (~> 1.4) | ||||
|  | @ -166,7 +166,7 @@ GEM | |||
|       sshkit (~> 1.3) | ||||
|     capistrano-yarn (2.0.2) | ||||
|       capistrano (~> 3.0) | ||||
|     capybara (3.39.0) | ||||
|     capybara (3.39.1) | ||||
|       addressable | ||||
|       matrix | ||||
|       mini_mime (>= 0.1.3) | ||||
|  | @ -189,7 +189,7 @@ GEM | |||
|     coderay (1.1.3) | ||||
|     color_diff (0.1) | ||||
|     concurrent-ruby (1.2.2) | ||||
|     connection_pool (2.4.0) | ||||
|     connection_pool (2.4.1) | ||||
|     cose (1.3.0) | ||||
|       cbor (~> 0.5.9) | ||||
|       openssl-signature_algorithm (~> 1.0) | ||||
|  | @ -331,7 +331,7 @@ GEM | |||
|     httplog (1.6.2) | ||||
|       rack (>= 2.0) | ||||
|       rainbow (>= 2.0.0) | ||||
|     i18n (1.12.0) | ||||
|     i18n (1.13.0) | ||||
|       concurrent-ruby (~> 1.0) | ||||
|     i18n-tasks (1.0.12) | ||||
|       activesupport (>= 4.0.2) | ||||
|  | @ -398,9 +398,9 @@ GEM | |||
|       activesupport (>= 4) | ||||
|       railties (>= 4) | ||||
|       request_store (~> 1.0) | ||||
|     loofah (2.20.0) | ||||
|     loofah (2.21.3) | ||||
|       crass (~> 1.0.2) | ||||
|       nokogiri (>= 1.5.9) | ||||
|       nokogiri (>= 1.12.0) | ||||
|     mail (2.8.1) | ||||
|       mini_mime (>= 0.1.1) | ||||
|       net-imap | ||||
|  | @ -418,7 +418,7 @@ GEM | |||
|       mime-types-data (~> 3.2015) | ||||
|     mime-types-data (3.2023.0218.1) | ||||
|     mini_mime (1.1.2) | ||||
|     mini_portile2 (2.8.1) | ||||
|     mini_portile2 (2.8.2) | ||||
|     minitest (5.18.0) | ||||
|     msgpack (1.7.0) | ||||
|     multi_json (1.15.0) | ||||
|  | @ -576,7 +576,7 @@ GEM | |||
|     rexml (3.2.5) | ||||
|     rotp (6.2.2) | ||||
|     rpam2 (4.0.2) | ||||
|     rqrcode (2.1.2) | ||||
|     rqrcode (2.2.0) | ||||
|       chunky_png (~> 1.0) | ||||
|       rqrcode_core (~> 1.0) | ||||
|     rqrcode_core (1.2.0) | ||||
|  | @ -588,22 +588,20 @@ GEM | |||
|     rspec-mocks (3.12.5) | ||||
|       diff-lcs (>= 1.2.0, < 2.0) | ||||
|       rspec-support (~> 3.12.0) | ||||
|     rspec-rails (6.0.1) | ||||
|     rspec-rails (6.0.2) | ||||
|       actionpack (>= 6.1) | ||||
|       activesupport (>= 6.1) | ||||
|       railties (>= 6.1) | ||||
|       rspec-core (~> 3.11) | ||||
|       rspec-expectations (~> 3.11) | ||||
|       rspec-mocks (~> 3.11) | ||||
|       rspec-support (~> 3.11) | ||||
|       rspec-core (~> 3.12) | ||||
|       rspec-expectations (~> 3.12) | ||||
|       rspec-mocks (~> 3.12) | ||||
|       rspec-support (~> 3.12) | ||||
|     rspec-sidekiq (3.1.0) | ||||
|       rspec-core (~> 3.0, >= 3.0.0) | ||||
|       sidekiq (>= 2.4.0) | ||||
|     rspec-support (3.12.0) | ||||
|     rspec_chunked (0.6) | ||||
|     rspec_junit_formatter (0.6.0) | ||||
|       rspec-core (>= 2, < 4, != 2.12.0) | ||||
|     rubocop (1.50.2) | ||||
|     rubocop (1.51.0) | ||||
|       json (~> 2.3) | ||||
|       parallel (~> 1.10) | ||||
|       parser (>= 3.2.0.0) | ||||
|  | @ -613,11 +611,11 @@ GEM | |||
|       rubocop-ast (>= 1.28.0, < 2.0) | ||||
|       ruby-progressbar (~> 1.7) | ||||
|       unicode-display_width (>= 2.4.0, < 3.0) | ||||
|     rubocop-ast (1.28.0) | ||||
|     rubocop-ast (1.28.1) | ||||
|       parser (>= 3.2.1.0) | ||||
|     rubocop-capybara (2.18.0) | ||||
|       rubocop (~> 1.41) | ||||
|     rubocop-performance (1.17.1) | ||||
|     rubocop-performance (1.18.0) | ||||
|       rubocop (>= 1.7.0, < 2.0) | ||||
|       rubocop-ast (>= 0.4.0) | ||||
|     rubocop-rails (2.19.1) | ||||
|  | @ -698,7 +696,7 @@ GEM | |||
|       unicode-display_width (>= 1.1.1, < 3) | ||||
|     terrapin (0.6.0) | ||||
|       climate_control (>= 0.0.3, < 1.0) | ||||
|     thor (1.2.1) | ||||
|     thor (1.2.2) | ||||
|     tilt (2.1.0) | ||||
|     timeout (0.3.2) | ||||
|     tpm-key_attestation (0.12.0) | ||||
|  | @ -763,7 +761,7 @@ GEM | |||
|     xorcist (1.1.3) | ||||
|     xpath (3.2.0) | ||||
|       nokogiri (~> 1.8) | ||||
|     zeitwerk (2.6.7) | ||||
|     zeitwerk (2.6.8) | ||||
| 
 | ||||
| PLATFORMS | ||||
|   ruby | ||||
|  | @ -772,7 +770,7 @@ DEPENDENCIES | |||
|   active_model_serializers (~> 0.10) | ||||
|   addressable (~> 2.8) | ||||
|   annotate (~> 3.2) | ||||
|   aws-sdk-s3 (~> 1.120) | ||||
|   aws-sdk-s3 (~> 1.122) | ||||
|   better_errors (~> 2.9) | ||||
|   binding_of_caller (~> 1.0) | ||||
|   blurhash (~> 0.1) | ||||
|  | @ -787,7 +785,7 @@ DEPENDENCIES | |||
|   capybara (~> 3.39) | ||||
|   charlock_holmes (~> 0.7.7) | ||||
|   chewy (~> 7.3) | ||||
|   climate_control | ||||
|   climate_control (~> 0.2) | ||||
|   cocoon (~> 1.2) | ||||
|   color_diff (~> 0.1) | ||||
|   concurrent-ruby | ||||
|  | @ -862,11 +860,10 @@ DEPENDENCIES | |||
|   redcarpet (~> 3.6) | ||||
|   redis (~> 4.5) | ||||
|   redis-namespace (~> 1.10) | ||||
|   rqrcode (~> 2.1) | ||||
|   rqrcode (~> 2.2) | ||||
|   rspec-rails (~> 6.0) | ||||
|   rspec-sidekiq (~> 3.1) | ||||
|   rspec_chunked (~> 0.6) | ||||
|   rspec_junit_formatter (~> 0.6) | ||||
|   rubocop | ||||
|   rubocop-capybara | ||||
|   rubocop-performance | ||||
|  |  | |||
|  | @ -58,7 +58,7 @@ class Api::V1::Admin::CanonicalEmailBlocksController < Api::BaseController | |||
|   end | ||||
| 
 | ||||
|   def set_canonical_email_blocks_from_test | ||||
|     @canonical_email_blocks = CanonicalEmailBlock.matching_email(params[:email]) | ||||
|     @canonical_email_blocks = CanonicalEmailBlock.matching_email(params.require(:email)) | ||||
|   end | ||||
| 
 | ||||
|   def set_canonical_email_block | ||||
|  |  | |||
|  | @ -29,7 +29,7 @@ class Api::V1::Admin::DomainAllowsController < Api::BaseController | |||
|   def create | ||||
|     authorize :domain_allow, :create? | ||||
| 
 | ||||
|     @domain_allow = DomainAllow.find_by(resource_params) | ||||
|     @domain_allow = DomainAllow.find_by(domain: resource_params[:domain]) | ||||
| 
 | ||||
|     if @domain_allow.nil? | ||||
|       @domain_allow = DomainAllow.create!(resource_params) | ||||
|  |  | |||
|  | @ -1,9 +1,10 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| class Api::V1::Emails::ConfirmationsController < Api::BaseController | ||||
|   before_action -> { doorkeeper_authorize! :write, :'write:accounts' } | ||||
|   before_action :require_user_owned_by_application! | ||||
|   before_action :require_user_not_confirmed! | ||||
|   before_action -> { authorize_if_got_token! :read, :'read:accounts' }, only: :check | ||||
|   before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, except: :check | ||||
|   before_action :require_user_owned_by_application!, except: :check | ||||
|   before_action :require_user_not_confirmed!, except: :check | ||||
| 
 | ||||
|   def create | ||||
|     current_user.update!(email: params[:email]) if params.key?(:email) | ||||
|  | @ -12,6 +13,10 @@ class Api::V1::Emails::ConfirmationsController < Api::BaseController | |||
|     render_empty | ||||
|   end | ||||
| 
 | ||||
|   def check | ||||
|     render json: current_user.confirmed? | ||||
|   end | ||||
| 
 | ||||
|   private | ||||
| 
 | ||||
|   def require_user_owned_by_application! | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ class Api::V1::FeaturedTagsController < Api::BaseController | |||
|   end | ||||
| 
 | ||||
|   def create | ||||
|     featured_tag = CreateFeaturedTagService.new.call(current_account, featured_tag_params[:name]) | ||||
|     featured_tag = CreateFeaturedTagService.new.call(current_account, params.require(:name)) | ||||
|     render json: featured_tag, serializer: REST::FeaturedTagSerializer | ||||
|   end | ||||
| 
 | ||||
|  | @ -33,6 +33,6 @@ class Api::V1::FeaturedTagsController < Api::BaseController | |||
|   end | ||||
| 
 | ||||
|   def featured_tag_params | ||||
|     params.permit(:name) | ||||
|     params.require(:name) | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -2,6 +2,8 @@ | |||
| 
 | ||||
| class Api::V1::Statuses::ReblogsController < Api::BaseController | ||||
|   include Authorization | ||||
|   include Redisable | ||||
|   include Lockable | ||||
| 
 | ||||
|   before_action -> { doorkeeper_authorize! :write, :'write:statuses' } | ||||
|   before_action :require_user! | ||||
|  | @ -10,7 +12,9 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController | |||
|   override_rate_limit_headers :create, family: :statuses | ||||
| 
 | ||||
|   def create | ||||
|     @status = ReblogService.new.call(current_account, @reblog, reblog_params) | ||||
|     with_redis_lock("reblog:#{current_account.id}:#{@reblog.id}") do | ||||
|       @status = ReblogService.new.call(current_account, @reblog, reblog_params) | ||||
|     end | ||||
| 
 | ||||
|     render json: @status, serializer: REST::StatusSerializer | ||||
|   end | ||||
|  |  | |||
|  | @ -132,7 +132,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController | |||
|   end | ||||
| 
 | ||||
|   def set_sessions | ||||
|     @sessions = current_user.session_activations | ||||
|     @sessions = current_user.session_activations.order(updated_at: :desc) | ||||
|   end | ||||
| 
 | ||||
|   def set_strikes | ||||
|  |  | |||
|  | @ -45,6 +45,6 @@ class Auth::SetupController < ApplicationController | |||
|   end | ||||
| 
 | ||||
|   def set_pack | ||||
|     use_pack 'auth' | ||||
|     use_pack 'sign_up' | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -10,6 +10,8 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio | |||
|   before_action :set_body_classes | ||||
|   before_action :set_cache_headers | ||||
| 
 | ||||
|   before_action :set_last_used_at_by_app, only: :index, unless: -> { request.format == :json } | ||||
| 
 | ||||
|   skip_before_action :require_functional! | ||||
| 
 | ||||
|   include Localized | ||||
|  | @ -40,4 +42,14 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio | |||
|   def set_cache_headers | ||||
|     response.cache_control.replace(private: true, no_store: true) | ||||
|   end | ||||
| 
 | ||||
|   def set_last_used_at_by_app | ||||
|     @last_used_at_by_app = Doorkeeper::AccessToken | ||||
|                            .select('DISTINCT ON (application_id) application_id, last_used_at') | ||||
|                            .where(resource_owner_id: current_resource_owner.id) | ||||
|                            .where.not(last_used_at: nil) | ||||
|                            .order(application_id: :desc, last_used_at: :desc) | ||||
|                            .pluck(:application_id, :last_used_at) | ||||
|                            .to_h | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -16,4 +16,5 @@ pack: | |||
|   modal: public.js | ||||
|   public: public.js | ||||
|   settings: settings.js | ||||
|   sign_up: | ||||
|   share: | ||||
|  |  | |||
|  | @ -1,8 +1,9 @@ | |||
| import { createAction } from '@reduxjs/toolkit'; | ||||
| 
 | ||||
| import type { LayoutType } from '../is_mobile'; | ||||
| 
 | ||||
| type ChangeLayoutPayload = { | ||||
| interface ChangeLayoutPayload { | ||||
|   layout: LayoutType; | ||||
| }; | ||||
| } | ||||
| export const changeLayout = | ||||
|   createAction<ChangeLayoutPayload>('APP_LAYOUT_CHANGE'); | ||||
|  |  | |||
|  | @ -1,12 +1,12 @@ | |||
| import api from '../api'; | ||||
| import { importFetchedStatuses } from './importer'; | ||||
| 
 | ||||
| import { me } from 'flavours/glitch/initial_state'; | ||||
| 
 | ||||
| export const PINNED_STATUSES_FETCH_REQUEST = 'PINNED_STATUSES_FETCH_REQUEST'; | ||||
| export const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS'; | ||||
| export const PINNED_STATUSES_FETCH_FAIL = 'PINNED_STATUSES_FETCH_FAIL'; | ||||
| 
 | ||||
| import { me } from 'flavours/glitch/initial_state'; | ||||
| 
 | ||||
| export function fetchPinnedStatuses() { | ||||
|   return (dispatch, getState) => { | ||||
|     dispatch(fetchPinnedStatusesRequest()); | ||||
|  |  | |||
|  | @ -2,14 +2,14 @@ import React, { Fragment } from 'react'; | |||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { Avatar } from './avatar'; | ||||
| import DisplayName from './display_name'; | ||||
| import { DisplayName } from './display_name'; | ||||
| import Permalink from './permalink'; | ||||
| import { IconButton } from './icon_button'; | ||||
| import { defineMessages, injectIntl } from 'react-intl'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| import { me } from 'flavours/glitch/initial_state'; | ||||
| import { RelativeTimestamp } from './relative_timestamp'; | ||||
| import Skeleton from 'flavours/glitch/components/skeleton'; | ||||
| import { Skeleton } from 'flavours/glitch/components/skeleton'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|   follow: { id: 'account.follow', defaultMessage: 'Follow' }, | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ import api from 'flavours/glitch/api'; | |||
| import { FormattedNumber } from 'react-intl'; | ||||
| import { Sparklines, SparklinesCurve } from 'react-sparklines'; | ||||
| import classNames from 'classnames'; | ||||
| import Skeleton from 'flavours/glitch/components/skeleton'; | ||||
| import { Skeleton } from 'flavours/glitch/components/skeleton'; | ||||
| 
 | ||||
| const percIncrease = (a, b) => { | ||||
|   let percent; | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ import PropTypes from 'prop-types'; | |||
| import api from 'flavours/glitch/api'; | ||||
| import { FormattedNumber } from 'react-intl'; | ||||
| import { roundTo10 } from 'flavours/glitch/utils/numbers'; | ||||
| import Skeleton from 'flavours/glitch/components/skeleton'; | ||||
| import { Skeleton } from 'flavours/glitch/components/skeleton'; | ||||
| 
 | ||||
| export default class Dimension extends React.PureComponent { | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,8 +1,11 @@ | |||
| import React, { useCallback, useState } from 'react'; | ||||
| import ShortNumber from './short_number'; | ||||
| 
 | ||||
| import { TransitionMotion, spring } from 'react-motion'; | ||||
| 
 | ||||
| import { reduceMotion } from '../initial_state'; | ||||
| 
 | ||||
| import ShortNumber from './short_number'; | ||||
| 
 | ||||
| const obfuscatedCount = (count: number) => { | ||||
|   if (count < 0) { | ||||
|     return 0; | ||||
|  | @ -13,10 +16,10 @@ const obfuscatedCount = (count: number) => { | |||
|   } | ||||
| }; | ||||
| 
 | ||||
| type Props = { | ||||
| interface Props { | ||||
|   value: number; | ||||
|   obfuscate?: boolean; | ||||
| }; | ||||
| } | ||||
| export const AnimatedNumber: React.FC<Props> = ({ value, obfuscate }) => { | ||||
|   const [previousValue, setPreviousValue] = useState(value); | ||||
|   const [direction, setDirection] = useState<1 | -1>(1); | ||||
|  | @ -64,7 +67,11 @@ export const AnimatedNumber: React.FC<Props> = ({ value, obfuscate }) => { | |||
|                 transform: `translateY(${style.y * 100}%)`, | ||||
|               }} | ||||
|             > | ||||
|               {obfuscate ? obfuscatedCount(data) : <ShortNumber value={data} />} | ||||
|               {obfuscate ? ( | ||||
|                 obfuscatedCount(data as number) | ||||
|               ) : ( | ||||
|                 <ShortNumber value={data as number} /> | ||||
|               )} | ||||
|             </span> | ||||
|           ))} | ||||
|         </span> | ||||
|  |  | |||
|  | @ -154,7 +154,7 @@ export default class AutosuggestInput extends ImmutablePureComponent { | |||
|     this.input.focus(); | ||||
|   }; | ||||
| 
 | ||||
|   componentWillReceiveProps (nextProps) { | ||||
|   UNSAFE_componentWillReceiveProps (nextProps) { | ||||
|     if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) { | ||||
|       this.setState({ suggestionsHidden: false }); | ||||
|     } | ||||
|  |  | |||
|  | @ -153,7 +153,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { | |||
|     this.textarea.focus(); | ||||
|   }; | ||||
| 
 | ||||
|   componentWillReceiveProps (nextProps) { | ||||
|   UNSAFE_componentWillReceiveProps (nextProps) { | ||||
|     if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) { | ||||
|       this.setState({ suggestionsHidden: false }); | ||||
|     } | ||||
|  |  | |||
|  | @ -1,16 +1,18 @@ | |||
| import * as React from 'react'; | ||||
| 
 | ||||
| import classNames from 'classnames'; | ||||
| import { autoPlayGif } from 'flavours/glitch/initial_state'; | ||||
| 
 | ||||
| import { useHovering } from 'flavours/glitch/hooks/useHovering'; | ||||
| import { autoPlayGif } from 'flavours/glitch/initial_state'; | ||||
| import type { Account } from 'flavours/glitch/types/resources'; | ||||
| 
 | ||||
| type Props = { | ||||
| interface Props { | ||||
|   account: Account | undefined; | ||||
|   className?: string; | ||||
|   size: number; | ||||
|   style?: React.CSSProperties; | ||||
|   inline?: boolean; | ||||
| }; | ||||
| } | ||||
| 
 | ||||
| export const Avatar: React.FC<Props> = ({ | ||||
|   account, | ||||
|  |  | |||
|  | @ -1,14 +1,14 @@ | |||
| import { decode } from 'blurhash'; | ||||
| import React, { useRef, useEffect } from 'react'; | ||||
| 
 | ||||
| type Props = { | ||||
| import { decode } from 'blurhash'; | ||||
| 
 | ||||
| interface Props extends React.HTMLAttributes<HTMLCanvasElement> { | ||||
|   hash: string; | ||||
|   width?: number; | ||||
|   height?: number; | ||||
|   dummy?: boolean; // Whether dummy mode is enabled. If enabled, nothing is rendered and canvas left untouched
 | ||||
|   children?: never; | ||||
|   [key: string]: any; | ||||
| }; | ||||
| } | ||||
| const Blurhash: React.FC<Props> = ({ | ||||
|   hash, | ||||
|   width = 32, | ||||
|  | @ -21,6 +21,7 @@ const Blurhash: React.FC<Props> = ({ | |||
|   useEffect(() => { | ||||
|     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
 | ||||
|     const canvas = canvasRef.current!; | ||||
| 
 | ||||
|     // eslint-disable-next-line no-self-assign
 | ||||
|     canvas.width = canvas.width; // resets canvas
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,6 +3,8 @@ import PropTypes from 'prop-types'; | |||
| import { supportsPassiveEvents } from 'detect-passive-events'; | ||||
| import { scrollTop } from '../scroll'; | ||||
| 
 | ||||
| const listenerOptions = supportsPassiveEvents ? { passive: true } : false; | ||||
| 
 | ||||
| export default class Column extends React.PureComponent { | ||||
| 
 | ||||
|   static propTypes = { | ||||
|  | @ -37,17 +39,17 @@ export default class Column extends React.PureComponent { | |||
| 
 | ||||
|   componentDidMount () { | ||||
|     if (this.props.bindToDocument) { | ||||
|       document.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false); | ||||
|       document.addEventListener('wheel', this.handleWheel, listenerOptions); | ||||
|     } else { | ||||
|       this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false); | ||||
|       this.node.addEventListener('wheel', this.handleWheel, listenerOptions); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   componentWillUnmount () { | ||||
|     if (this.props.bindToDocument) { | ||||
|       document.removeEventListener('wheel', this.handleWheel); | ||||
|       document.removeEventListener('wheel', this.handleWheel, listenerOptions); | ||||
|     } else { | ||||
|       this.node.removeEventListener('wheel', this.handleWheel); | ||||
|       this.node.removeEventListener('wheel', this.handleWheel, listenerOptions); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,83 +0,0 @@ | |||
| import React from 'react'; | ||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import classNames from 'classnames'; | ||||
| import { autoPlayGif } from 'flavours/glitch/initial_state'; | ||||
| import Skeleton from 'flavours/glitch/components/skeleton'; | ||||
| 
 | ||||
| export default class DisplayName extends React.PureComponent { | ||||
| 
 | ||||
|   static propTypes = { | ||||
|     account: ImmutablePropTypes.map, | ||||
|     others: ImmutablePropTypes.list, | ||||
|     localDomain: PropTypes.string, | ||||
|     inline: PropTypes.bool, | ||||
|   }; | ||||
| 
 | ||||
|   handleMouseEnter = ({ currentTarget }) => { | ||||
|     if (autoPlayGif) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     const emojis = currentTarget.querySelectorAll('.custom-emoji'); | ||||
| 
 | ||||
|     for (var i = 0; i < emojis.length; i++) { | ||||
|       let emoji = emojis[i]; | ||||
|       emoji.src = emoji.getAttribute('data-original'); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   handleMouseLeave = ({ currentTarget }) => { | ||||
|     if (autoPlayGif) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     const emojis = currentTarget.querySelectorAll('.custom-emoji'); | ||||
| 
 | ||||
|     for (var i = 0; i < emojis.length; i++) { | ||||
|       let emoji = emojis[i]; | ||||
|       emoji.src = emoji.getAttribute('data-static'); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   render () { | ||||
|     const { others, localDomain, inline } = this.props; | ||||
| 
 | ||||
|     let displayName, suffix, account; | ||||
| 
 | ||||
|     if (others && others.size > 1) { | ||||
|       displayName = others.take(2).map(a => <bdi key={a.get('id')}><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }} /></bdi>).reduce((prev, cur) => [prev, ', ', cur]); | ||||
| 
 | ||||
|       if (others.size - 2 > 0) { | ||||
|         suffix = `+${others.size - 2}`; | ||||
|       } | ||||
|     } else if ((others && others.size > 0) || this.props.account) { | ||||
|       if (others && others.size > 0) { | ||||
|         account = others.first(); | ||||
|       } else { | ||||
|         account = this.props.account; | ||||
|       } | ||||
| 
 | ||||
|       let acct = account.get('acct'); | ||||
| 
 | ||||
|       if (acct.indexOf('@') === -1 && localDomain) { | ||||
|         acct = `${acct}@${localDomain}`; | ||||
|       } | ||||
| 
 | ||||
|       displayName = <bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /></bdi>; | ||||
|       suffix      = <span className='display-name__account'>@{acct}</span>; | ||||
|     } else { | ||||
|       displayName = <bdi><strong className='display-name__html'><Skeleton width='10ch' /></strong></bdi>; | ||||
|       suffix = <span className='display-name__account'><Skeleton width='7ch' /></span>; | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <span className={classNames('display-name', { inline })} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}> | ||||
|         {displayName} | ||||
|         {inline ? ' ' : null} | ||||
|         {suffix} | ||||
|       </span> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										124
									
								
								app/javascript/flavours/glitch/components/display_name.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								app/javascript/flavours/glitch/components/display_name.tsx
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,124 @@ | |||
| import React from 'react'; | ||||
| 
 | ||||
| import classNames from 'classnames'; | ||||
| 
 | ||||
| import type { List } from 'immutable'; | ||||
| 
 | ||||
| import type { Account } from 'flavours/glitch/types/resources'; | ||||
| 
 | ||||
| import { autoPlayGif } from '../initial_state'; | ||||
| 
 | ||||
| import { Skeleton } from './skeleton'; | ||||
| 
 | ||||
| interface Props { | ||||
|   account: Account; | ||||
|   others: List<Account>; | ||||
|   localDomain: string; | ||||
|   inline?: boolean; | ||||
| } | ||||
| export class DisplayName extends React.PureComponent<Props> { | ||||
|   handleMouseEnter: React.ReactEventHandler<HTMLSpanElement> = ({ | ||||
|     currentTarget, | ||||
|   }) => { | ||||
|     if (autoPlayGif) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     const emojis = | ||||
|       currentTarget.querySelectorAll<HTMLImageElement>('img.custom-emoji'); | ||||
| 
 | ||||
|     emojis.forEach((emoji) => { | ||||
|       const originalSrc = emoji.getAttribute('data-original'); | ||||
|       if (originalSrc != null) emoji.src = originalSrc; | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   handleMouseLeave: React.ReactEventHandler<HTMLSpanElement> = ({ | ||||
|     currentTarget, | ||||
|   }) => { | ||||
|     if (autoPlayGif) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     const emojis = | ||||
|       currentTarget.querySelectorAll<HTMLImageElement>('img.custom-emoji'); | ||||
| 
 | ||||
|     emojis.forEach((emoji) => { | ||||
|       const staticSrc = emoji.getAttribute('data-static'); | ||||
|       if (staticSrc != null) emoji.src = staticSrc; | ||||
|     }); | ||||
|   }; | ||||
| 
 | ||||
|   render() { | ||||
|     const { others, localDomain, inline } = this.props; | ||||
| 
 | ||||
|     let displayName: React.ReactNode, suffix: React.ReactNode, account: Account; | ||||
| 
 | ||||
|     if (others && others.size > 1) { | ||||
|       displayName = others | ||||
|         .take(2) | ||||
|         .map((a) => ( | ||||
|           <bdi key={a.get('id')}> | ||||
|             <strong | ||||
|               className='display-name__html' | ||||
|               dangerouslySetInnerHTML={{ __html: a.get('display_name_html') }} | ||||
|             /> | ||||
|           </bdi> | ||||
|         )) | ||||
|         .reduce((prev, cur) => [prev, ', ', cur]); | ||||
| 
 | ||||
|       if (others.size - 2 > 0) { | ||||
|         suffix = `+${others.size - 2}`; | ||||
|       } | ||||
|     } else if ((others && others.size > 0) || this.props.account) { | ||||
|       if (others && others.size > 0) { | ||||
|         account = others.first(); | ||||
|       } else { | ||||
|         account = this.props.account; | ||||
|       } | ||||
| 
 | ||||
|       let acct = account.get('acct'); | ||||
| 
 | ||||
|       if (acct.indexOf('@') === -1 && localDomain) { | ||||
|         acct = `${acct}@${localDomain}`; | ||||
|       } | ||||
| 
 | ||||
|       displayName = ( | ||||
|         <bdi> | ||||
|           <strong | ||||
|             className='display-name__html' | ||||
|             dangerouslySetInnerHTML={{ | ||||
|               __html: account.get('display_name_html'), | ||||
|             }} | ||||
|           /> | ||||
|         </bdi> | ||||
|       ); | ||||
|       suffix = <span className='display-name__account'>@{acct}</span>; | ||||
|     } else { | ||||
|       displayName = ( | ||||
|         <bdi> | ||||
|           <strong className='display-name__html'> | ||||
|             <Skeleton width='10ch' /> | ||||
|           </strong> | ||||
|         </bdi> | ||||
|       ); | ||||
|       suffix = ( | ||||
|         <span className='display-name__account'> | ||||
|           <Skeleton width='7ch' /> | ||||
|         </span> | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     return ( | ||||
|       <span | ||||
|         className={classNames('display-name', { inline })} | ||||
|         onMouseEnter={this.handleMouseEnter} | ||||
|         onMouseLeave={this.handleMouseLeave} | ||||
|       > | ||||
|         {displayName} | ||||
|         {inline ? ' ' : null} | ||||
|         {suffix} | ||||
|       </span> | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | @ -1,6 +1,9 @@ | |||
| import React, { useCallback } from 'react'; | ||||
| 
 | ||||
| import type { InjectedIntl } from 'react-intl'; | ||||
| import { defineMessages, injectIntl } from 'react-intl'; | ||||
| 
 | ||||
| import { IconButton } from './icon_button'; | ||||
| import { InjectedIntl, defineMessages, injectIntl } from 'react-intl'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|   unblockDomain: { | ||||
|  | @ -9,11 +12,11 @@ const messages = defineMessages({ | |||
|   }, | ||||
| }); | ||||
| 
 | ||||
| type Props = { | ||||
| interface Props { | ||||
|   domain: string; | ||||
|   onUnblockDomain: (domain: string) => void; | ||||
|   intl: InjectedIntl; | ||||
| }; | ||||
| } | ||||
| const _Domain: React.FC<Props> = ({ domain, onUnblockDomain, intl }) => { | ||||
|   const handleDomainUnblock = useCallback(() => { | ||||
|     onUnblockDomain(domain); | ||||
|  |  | |||
|  | @ -7,7 +7,7 @@ import { supportsPassiveEvents } from 'detect-passive-events'; | |||
| import classNames from 'classnames'; | ||||
| import { CircularProgress } from 'flavours/glitch/components/loading_indicator'; | ||||
| 
 | ||||
| const listenerOptions = supportsPassiveEvents ? { passive: true } : false; | ||||
| const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true; | ||||
| let id = 0; | ||||
| 
 | ||||
| class DropdownMenu extends React.PureComponent { | ||||
|  | @ -35,12 +35,13 @@ class DropdownMenu extends React.PureComponent { | |||
|   handleDocumentClick = e => { | ||||
|     if (this.node && !this.node.contains(e.target)) { | ||||
|       this.props.onClose(); | ||||
|       e.stopPropagation(); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   componentDidMount () { | ||||
|     document.addEventListener('click', this.handleDocumentClick, false); | ||||
|     document.addEventListener('keydown', this.handleKeyDown, false); | ||||
|     document.addEventListener('click', this.handleDocumentClick, { capture: true }); | ||||
|     document.addEventListener('keydown', this.handleKeyDown, { capture: true }); | ||||
|     document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); | ||||
| 
 | ||||
|     if (this.focusedItem && this.props.openedViaKeyboard) { | ||||
|  | @ -49,8 +50,8 @@ class DropdownMenu extends React.PureComponent { | |||
|   } | ||||
| 
 | ||||
|   componentWillUnmount () { | ||||
|     document.removeEventListener('click', this.handleDocumentClick, false); | ||||
|     document.removeEventListener('keydown', this.handleKeyDown, false); | ||||
|     document.removeEventListener('click', this.handleDocumentClick, { capture: true }); | ||||
|     document.removeEventListener('keydown', this.handleKeyDown, { capture: true }); | ||||
|     document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| import React, { useCallback, useState } from 'react'; | ||||
| 
 | ||||
| type Props = { | ||||
| interface Props { | ||||
|   src: string; | ||||
|   key: string; | ||||
|   alt?: string; | ||||
|  | @ -8,7 +8,7 @@ type Props = { | |||
|   width: number; | ||||
|   height: number; | ||||
|   onClick?: () => void; | ||||
| }; | ||||
| } | ||||
| 
 | ||||
| export const GIFV: React.FC<Props> = ({ | ||||
|   src, | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ import PropTypes from 'prop-types'; | |||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import Permalink from './permalink'; | ||||
| import ShortNumber from 'flavours/glitch/components/short_number'; | ||||
| import Skeleton from 'flavours/glitch/components/skeleton'; | ||||
| import { Skeleton } from 'flavours/glitch/components/skeleton'; | ||||
| import classNames from 'classnames'; | ||||
| 
 | ||||
| class SilentErrorBoundary extends React.Component { | ||||
|  |  | |||
|  | @ -1,13 +1,14 @@ | |||
| import React from 'react'; | ||||
| 
 | ||||
| import classNames from 'classnames'; | ||||
| 
 | ||||
| type Props = { | ||||
| interface Props extends React.HTMLAttributes<HTMLImageElement> { | ||||
|   id: string; | ||||
|   className?: string; | ||||
|   fixedWidth?: boolean; | ||||
|   children?: never; | ||||
|   [key: string]: any; | ||||
| }; | ||||
| } | ||||
| 
 | ||||
| export const Icon: React.FC<Props> = ({ | ||||
|   id, | ||||
|   className, | ||||
|  |  | |||
|  | @ -1,9 +1,11 @@ | |||
| import React from 'react'; | ||||
| import classNames from 'classnames'; | ||||
| import { Icon } from './icon'; | ||||
| import { AnimatedNumber } from './animated_number'; | ||||
| 
 | ||||
| type Props = { | ||||
| import classNames from 'classnames'; | ||||
| 
 | ||||
| import { AnimatedNumber } from './animated_number'; | ||||
| import { Icon } from './icon'; | ||||
| 
 | ||||
| interface Props { | ||||
|   className?: string; | ||||
|   title: string; | ||||
|   icon: string; | ||||
|  | @ -26,11 +28,11 @@ type Props = { | |||
|   obfuscateCount?: boolean; | ||||
|   href?: string; | ||||
|   ariaHidden: boolean; | ||||
| }; | ||||
| type States = { | ||||
| } | ||||
| interface States { | ||||
|   activate: boolean; | ||||
|   deactivate: boolean; | ||||
| }; | ||||
| } | ||||
| export class IconButton extends React.PureComponent<Props, States> { | ||||
|   static defaultProps = { | ||||
|     size: 18, | ||||
|  |  | |||
|  | @ -1,14 +1,15 @@ | |||
| import React from 'react'; | ||||
| 
 | ||||
| import { Icon } from './icon'; | ||||
| 
 | ||||
| const formatNumber = (num: number): number | string => (num > 40 ? '40+' : num); | ||||
| 
 | ||||
| type Props = { | ||||
| interface Props { | ||||
|   id: string; | ||||
|   count: number; | ||||
|   issueBadge: boolean; | ||||
|   className: string; | ||||
| }; | ||||
| } | ||||
| export const IconWithBadge: React.FC<Props> = ({ | ||||
|   id, | ||||
|   count, | ||||
|  |  | |||
|  | @ -254,7 +254,7 @@ class MediaGallery extends React.PureComponent { | |||
|     window.removeEventListener('resize', this.handleResize); | ||||
|   } | ||||
| 
 | ||||
|   componentWillReceiveProps (nextProps) { | ||||
|   UNSAFE_componentWillReceiveProps (nextProps) { | ||||
|     if (!is(nextProps.media, this.props.media) && nextProps.visible === undefined) { | ||||
|       this.setState({ visible: displayMedia !== 'hide_all' && !nextProps.sensitive || displayMedia === 'show_all' }); | ||||
|     } else if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) { | ||||
|  | @ -286,7 +286,7 @@ class MediaGallery extends React.PureComponent { | |||
|   }; | ||||
| 
 | ||||
|   handleClick = (index) => { | ||||
|     this.props.onOpenMedia(this.props.media, index); | ||||
|     this.props.onOpenMedia(this.props.media, index, this.props.lang); | ||||
|   }; | ||||
| 
 | ||||
|   handleRef = (node) => { | ||||
|  |  | |||
|  | @ -62,7 +62,7 @@ export default class ModalRoot extends React.PureComponent { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   componentWillReceiveProps (nextProps) { | ||||
|   UNSAFE_componentWillReceiveProps (nextProps) { | ||||
|     if (!!nextProps.children && !this.props.children) { | ||||
|       this.activeElement = document.activeElement; | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| import React from 'react'; | ||||
| 
 | ||||
| import { FormattedMessage } from 'react-intl'; | ||||
| 
 | ||||
| export const NotSignedInIndicator: React.FC = () => ( | ||||
|  | @ -6,7 +7,7 @@ export const NotSignedInIndicator: React.FC = () => ( | |||
|     <div className='empty-column-indicator'> | ||||
|       <FormattedMessage | ||||
|         id='not_signed_in_indicator.not_signed_in' | ||||
|         defaultMessage='You need to sign in to access this resource.' | ||||
|         defaultMessage='You need to login to access this resource.' | ||||
|       /> | ||||
|     </div> | ||||
|   </div> | ||||
|  |  | |||
|  | @ -1,13 +1,14 @@ | |||
| import React from 'react'; | ||||
| 
 | ||||
| import classNames from 'classnames'; | ||||
| 
 | ||||
| type Props = { | ||||
| interface Props { | ||||
|   value: string; | ||||
|   checked: boolean; | ||||
|   name: string; | ||||
|   onChange: (event: React.ChangeEvent<HTMLInputElement>) => void; | ||||
|   label: React.ReactNode; | ||||
| }; | ||||
| } | ||||
| 
 | ||||
| export const RadioButton: React.FC<Props> = ({ | ||||
|   name, | ||||
|  |  | |||
|  | @ -1,5 +1,7 @@ | |||
| import React from 'react'; | ||||
| import { injectIntl, defineMessages, InjectedIntl } from 'react-intl'; | ||||
| 
 | ||||
| import type { InjectedIntl } from 'react-intl'; | ||||
| import { injectIntl, defineMessages } from 'react-intl'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|   today: { id: 'relative_time.today', defaultMessage: 'today' }, | ||||
|  | @ -187,16 +189,16 @@ const timeRemainingString = ( | |||
|   return relativeTime; | ||||
| }; | ||||
| 
 | ||||
| type Props = { | ||||
| interface Props { | ||||
|   intl: InjectedIntl; | ||||
|   timestamp: string; | ||||
|   year: number; | ||||
|   futureDate?: boolean; | ||||
|   short?: boolean; | ||||
| }; | ||||
| type States = { | ||||
| } | ||||
| interface States { | ||||
|   now: number; | ||||
| }; | ||||
| } | ||||
| class RelativeTimestamp extends React.Component<Props, States> { | ||||
|   state = { | ||||
|     now: this.props.intl.now(), | ||||
|  |  | |||
|  | @ -15,6 +15,8 @@ import { connect } from 'react-redux'; | |||
| 
 | ||||
| const MOUSE_IDLE_DELAY = 300; | ||||
| 
 | ||||
| const listenerOptions = supportsPassiveEvents ? { passive: true } : false; | ||||
| 
 | ||||
| const mapStateToProps = (state, { scrollKey }) => { | ||||
|   return { | ||||
|     preventScroll: scrollKey === state.getIn(['dropdown_menu', 'scroll_key']), | ||||
|  | @ -237,20 +239,20 @@ class ScrollableList extends PureComponent { | |||
|   attachScrollListener () { | ||||
|     if (this.props.bindToDocument) { | ||||
|       document.addEventListener('scroll', this.handleScroll); | ||||
|       document.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : undefined); | ||||
|       document.addEventListener('wheel', this.handleWheel,  listenerOptions); | ||||
|     } else { | ||||
|       this.node.addEventListener('scroll', this.handleScroll); | ||||
|       this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : undefined); | ||||
|       this.node.addEventListener('wheel', this.handleWheel, listenerOptions); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   detachScrollListener () { | ||||
|     if (this.props.bindToDocument) { | ||||
|       document.removeEventListener('scroll', this.handleScroll); | ||||
|       document.removeEventListener('wheel', this.handleWheel); | ||||
|       document.removeEventListener('wheel', this.handleWheel, listenerOptions); | ||||
|     } else { | ||||
|       this.node.removeEventListener('scroll', this.handleScroll); | ||||
|       this.node.removeEventListener('wheel', this.handleWheel); | ||||
|       this.node.removeEventListener('wheel', this.handleWheel, listenerOptions); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,10 +4,10 @@ import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; | |||
| import { connect } from 'react-redux'; | ||||
| import { fetchServer } from 'flavours/glitch/actions/server'; | ||||
| import ShortNumber from 'flavours/glitch/components/short_number'; | ||||
| import Skeleton from 'flavours/glitch/components/skeleton'; | ||||
| import { Skeleton } from 'flavours/glitch/components/skeleton'; | ||||
| import Account from 'flavours/glitch/containers/account_container'; | ||||
| import { domain } from 'flavours/glitch/initial_state'; | ||||
| import { Image } from 'flavours/glitch/components/image'; | ||||
| import { ServerHeroImage } from 'flavours/glitch/components/server_hero_image'; | ||||
| import { Link } from 'react-router-dom'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|  | @ -41,7 +41,7 @@ class ServerBanner extends React.PureComponent { | |||
|           <FormattedMessage id='server_banner.introduction' defaultMessage='{domain} is part of the decentralized social network powered by {mastodon}.' values={{ domain: <strong>{domain}</strong>, mastodon: <a href='https://joinmastodon.org' target='_blank'>Mastodon</a> }} /> | ||||
|         </div> | ||||
| 
 | ||||
|         <Image blurhash={server.getIn(['thumbnail', 'blurhash'])} src={server.getIn(['thumbnail', 'url'])} className='server-banner__hero' /> | ||||
|         <ServerHeroImage blurhash={server.getIn(['thumbnail', 'blurhash'])} src={server.getIn(['thumbnail', 'url'])} className='server-banner__hero' /> | ||||
| 
 | ||||
|         <div className='server-banner__description'> | ||||
|           {isLoading ? ( | ||||
|  |  | |||
|  | @ -1,15 +1,17 @@ | |||
| import React, { useCallback, useState } from 'react'; | ||||
| import { Blurhash } from './blurhash'; | ||||
| 
 | ||||
| import classNames from 'classnames'; | ||||
| 
 | ||||
| type Props = { | ||||
| import { Blurhash } from './blurhash'; | ||||
| 
 | ||||
| interface Props { | ||||
|   src: string; | ||||
|   srcSet?: string; | ||||
|   blurhash?: string; | ||||
|   className?: string; | ||||
| }; | ||||
| } | ||||
| 
 | ||||
| export const Image: React.FC<Props> = ({ | ||||
| export const ServerHeroImage: React.FC<Props> = ({ | ||||
|   src, | ||||
|   srcSet, | ||||
|   blurhash, | ||||
|  | @ -1,11 +0,0 @@ | |||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| 
 | ||||
| const Skeleton = ({ width, height }) => <span className='skeleton' style={{ width, height }}>‌</span>; | ||||
| 
 | ||||
| Skeleton.propTypes = { | ||||
|   width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), | ||||
|   height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), | ||||
| }; | ||||
| 
 | ||||
| export default Skeleton; | ||||
							
								
								
									
										12
									
								
								app/javascript/flavours/glitch/components/skeleton.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								app/javascript/flavours/glitch/components/skeleton.tsx
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,12 @@ | |||
| import React from 'react'; | ||||
| 
 | ||||
| interface Props { | ||||
|   width?: number | string; | ||||
|   height?: number | string; | ||||
| } | ||||
| 
 | ||||
| export const Skeleton: React.FC<Props> = ({ width, height }) => ( | ||||
|   <span className='skeleton' style={{ width, height }}> | ||||
|     ‌ | ||||
|   </span> | ||||
| ); | ||||
|  | @ -388,11 +388,12 @@ class Status extends ImmutablePureComponent { | |||
| 
 | ||||
|   handleOpenVideo = (options) => { | ||||
|     const { status } = this.props; | ||||
|     this.props.onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), options); | ||||
|     this.props.onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), status.get('language'), options); | ||||
|   }; | ||||
| 
 | ||||
|   handleOpenMedia = (media, index) => { | ||||
|     this.props.onOpenMedia(this.props.status.get('id'), media, index); | ||||
|     const { status } = this.props; | ||||
|     this.props.onOpenMedia(status.get('id'), media, index, status.get('language')); | ||||
|   }; | ||||
| 
 | ||||
|   handleHotkeyOpenMedia = e => { | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; | |||
| //  Mastodon imports. | ||||
| import { Avatar } from './avatar'; | ||||
| import AvatarOverlay from './avatar_overlay'; | ||||
| import DisplayName from './display_name'; | ||||
| import { DisplayName } from './display_name'; | ||||
| 
 | ||||
| export default class StatusHeader extends React.PureComponent { | ||||
| 
 | ||||
|  |  | |||
|  | @ -26,6 +26,7 @@ export default class StatusList extends ImmutablePureComponent { | |||
|     alwaysPrepend: PropTypes.bool, | ||||
|     withCounters: PropTypes.bool, | ||||
|     timelineId: PropTypes.string.isRequired, | ||||
|     lastId: PropTypes.string, | ||||
|     regex: PropTypes.string, | ||||
|   }; | ||||
| 
 | ||||
|  | @ -56,7 +57,8 @@ export default class StatusList extends ImmutablePureComponent { | |||
|   }; | ||||
| 
 | ||||
|   handleLoadOlder = debounce(() => { | ||||
|     this.props.onLoadMore(this.props.statusIds.size > 0 ? this.props.statusIds.last() : undefined); | ||||
|     const { statusIds, lastId, onLoadMore } = this.props; | ||||
|     onLoadMore(lastId || (statusIds.size > 0 ? statusIds.last() : undefined)); | ||||
|   }, 300, { leading: true }); | ||||
| 
 | ||||
|   _selectChild (index, align_top) { | ||||
|  |  | |||
|  | @ -1,18 +0,0 @@ | |||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { FormattedMessage } from 'react-intl'; | ||||
| 
 | ||||
| const TimelineHint = ({ resource, url }) => ( | ||||
|   <div className='timeline-hint'> | ||||
|     <strong><FormattedMessage id='timeline_hint.remote_resource_not_displayed' defaultMessage='{resource} from other servers are not displayed.' values={{ resource }} /></strong> | ||||
|     <br /> | ||||
|     <a href={url} target='_blank'><FormattedMessage id='account.browse_more_on_origin_server' defaultMessage='Browse more on the original profile' /></a> | ||||
|   </div> | ||||
| ); | ||||
| 
 | ||||
| TimelineHint.propTypes = { | ||||
|   resource: PropTypes.node.isRequired, | ||||
|   url: PropTypes.string.isRequired, | ||||
| }; | ||||
| 
 | ||||
| export default TimelineHint; | ||||
							
								
								
									
										27
									
								
								app/javascript/flavours/glitch/components/timeline_hint.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								app/javascript/flavours/glitch/components/timeline_hint.tsx
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,27 @@ | |||
| import React from 'react'; | ||||
| 
 | ||||
| import { FormattedMessage } from 'react-intl'; | ||||
| 
 | ||||
| interface Props { | ||||
|   resource: JSX.Element; | ||||
|   url: string; | ||||
| } | ||||
| 
 | ||||
| export const TimelineHint: React.FC<Props> = ({ resource, url }) => ( | ||||
|   <div className='timeline-hint'> | ||||
|     <strong> | ||||
|       <FormattedMessage | ||||
|         id='timeline_hint.remote_resource_not_displayed' | ||||
|         defaultMessage='{resource} from other servers are not displayed.' | ||||
|         values={{ resource }} | ||||
|       /> | ||||
|     </strong> | ||||
|     <br /> | ||||
|     <a href={url} target='_blank' rel='noopener noreferrer'> | ||||
|       <FormattedMessage | ||||
|         id='account.browse_more_on_origin_server' | ||||
|         defaultMessage='Browse more on the original profile' | ||||
|       /> | ||||
|     </a> | ||||
|   </div> | ||||
| ); | ||||
|  | @ -1,5 +1,5 @@ | |||
| import React, { PureComponent, Fragment } from 'react'; | ||||
| import ReactDOM from 'react-dom'; | ||||
| import { createPortal } from 'react-dom'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { IntlProvider, addLocaleData } from 'react-intl'; | ||||
| import { fromJS } from 'immutable'; | ||||
|  | @ -29,19 +29,20 @@ export default class MediaContainer extends PureComponent { | |||
|   state = { | ||||
|     media: null, | ||||
|     index: null, | ||||
|     lang: null, | ||||
|     time: null, | ||||
|     backgroundColor: null, | ||||
|     options: null, | ||||
|   }; | ||||
| 
 | ||||
|   handleOpenMedia = (media, index) => { | ||||
|   handleOpenMedia = (media, index, lang) => { | ||||
|     document.body.classList.add('with-modals--active'); | ||||
|     document.documentElement.style.marginRight = `${getScrollbarWidth()}px`; | ||||
| 
 | ||||
|     this.setState({ media, index }); | ||||
|     this.setState({ media, index, lang }); | ||||
|   }; | ||||
| 
 | ||||
|   handleOpenVideo = (options) => { | ||||
|   handleOpenVideo = (lang, options) => { | ||||
|     const { components } = this.props; | ||||
|     const { media } = JSON.parse(components[options.componentIndex].getAttribute('data-props')); | ||||
|     const mediaList = fromJS(media); | ||||
|  | @ -49,7 +50,7 @@ export default class MediaContainer extends PureComponent { | |||
|     document.body.classList.add('with-modals--active'); | ||||
|     document.documentElement.style.marginRight = `${getScrollbarWidth()}px`; | ||||
| 
 | ||||
|     this.setState({ media: mediaList, options }); | ||||
|     this.setState({ media: mediaList, lang, options }); | ||||
|   }; | ||||
| 
 | ||||
|   handleCloseMedia = () => { | ||||
|  | @ -94,7 +95,7 @@ export default class MediaContainer extends PureComponent { | |||
|               }), | ||||
|             }); | ||||
| 
 | ||||
|             return ReactDOM.createPortal( | ||||
|             return createPortal( | ||||
|               <Component {...props} key={`media-${i}`} />, | ||||
|               component, | ||||
|             ); | ||||
|  | @ -105,6 +106,7 @@ export default class MediaContainer extends PureComponent { | |||
|               <MediaModal | ||||
|                 media={this.state.media} | ||||
|                 index={this.state.index || 0} | ||||
|                 lang={this.state.lang} | ||||
|                 currentTime={this.state.options?.startTime} | ||||
|                 autoPlay={this.state.options?.autoPlay} | ||||
|                 volume={this.state.options?.defaultVolume} | ||||
|  |  | |||
|  | @ -211,12 +211,12 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({ | |||
|     dispatch(mentionCompose(account, router)); | ||||
|   }, | ||||
| 
 | ||||
|   onOpenMedia (statusId, media, index) { | ||||
|     dispatch(openModal('MEDIA', { statusId, media, index })); | ||||
|   onOpenMedia (statusId, media, index, lang) { | ||||
|     dispatch(openModal('MEDIA', { statusId, media, index, lang })); | ||||
|   }, | ||||
| 
 | ||||
|   onOpenVideo (statusId, media, options) { | ||||
|     dispatch(openModal('VIDEO', { statusId, media, options })); | ||||
|   onOpenVideo (statusId, media, lang, options) { | ||||
|     dispatch(openModal('VIDEO', { statusId, media, lang, options })); | ||||
|   }, | ||||
| 
 | ||||
|   onBlock (status) { | ||||
|  |  | |||
|  | @ -8,10 +8,10 @@ import LinkFooter from 'flavours/glitch/features/ui/components/link_footer'; | |||
| import { Helmet } from 'react-helmet'; | ||||
| import { fetchServer, fetchExtendedDescription, fetchDomainBlocks } from 'flavours/glitch/actions/server'; | ||||
| import Account from 'flavours/glitch/containers/account_container'; | ||||
| import Skeleton from 'flavours/glitch/components/skeleton'; | ||||
| import { Skeleton } from 'flavours/glitch/components/skeleton'; | ||||
| import { Icon } from 'flavours/glitch/components/icon'; | ||||
| import classNames from 'classnames'; | ||||
| import { Image } from 'flavours/glitch/components/image'; | ||||
| import { ServerHeroImage } from 'flavours/glitch/components/server_hero_image'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|   title: { id: 'column.about', defaultMessage: 'About' }, | ||||
|  | @ -114,7 +114,7 @@ class About extends React.PureComponent { | |||
|       <Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.title)}> | ||||
|         <div className='scrollable about'> | ||||
|           <div className='about__header'> | ||||
|             <Image blurhash={server.getIn(['thumbnail', 'blurhash'])} src={server.getIn(['thumbnail', 'url'])} srcSet={server.getIn(['thumbnail', 'versions'])?.map((value, key) => `${value} ${key.replace('@', '')}`).join(', ')} className='about__header__hero' /> | ||||
|             <ServerHeroImage blurhash={server.getIn(['thumbnail', 'blurhash'])} src={server.getIn(['thumbnail', 'url'])} srcSet={server.getIn(['thumbnail', 'versions'])?.map((value, key) => `${value} ${key.replace('@', '')}`).join(', ')} className='about__header__hero' /> | ||||
|             <h1>{isLoading ? <Skeleton width='10ch' /> : server.get('domain')}</h1> | ||||
|             <p><FormattedMessage id='about.powered_by' defaultMessage='Decentralized social media powered by {mastodon}' values={{ mastodon: <a href='https://joinmastodon.org' className='about__mail' target='_blank'>Mastodon</a> }} /></p> | ||||
|           </div> | ||||
|  |  | |||
|  | @ -142,16 +142,17 @@ class AccountGallery extends ImmutablePureComponent { | |||
|   handleOpenMedia = attachment => { | ||||
|     const { dispatch } = this.props; | ||||
|     const statusId = attachment.getIn(['status', 'id']); | ||||
|     const lang = attachment.getIn(['status', 'language']); | ||||
| 
 | ||||
|     if (attachment.get('type') === 'video') { | ||||
|       dispatch(openModal('VIDEO', { media: attachment, statusId, options: { autoPlay: true } })); | ||||
|       dispatch(openModal('VIDEO', { media: attachment, statusId, lang, options: { autoPlay: true } })); | ||||
|     } else if (attachment.get('type') === 'audio') { | ||||
|       dispatch(openModal('AUDIO', { media: attachment, statusId, options: { autoPlay: true } })); | ||||
|       dispatch(openModal('AUDIO', { media: attachment, statusId, lang, options: { autoPlay: true } })); | ||||
|     } else { | ||||
|       const media = attachment.getIn(['status', 'media_attachments']); | ||||
|       const index = media.findIndex(x => x.get('id') === attachment.get('id')); | ||||
| 
 | ||||
|       dispatch(openModal('MEDIA', { media, index, statusId })); | ||||
|       dispatch(openModal('MEDIA', { media, index, statusId, lang })); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; | |||
| import { FormattedMessage } from 'react-intl'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| import AvatarOverlay from '../../../components/avatar_overlay'; | ||||
| import DisplayName from '../../../components/display_name'; | ||||
| import { DisplayName } from '../../../components/display_name'; | ||||
| import { Icon } from 'flavours/glitch/components/icon'; | ||||
| 
 | ||||
| export default class MovedNote extends ImmutablePureComponent { | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ import { connect } from 'react-redux'; | |||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { lookupAccount, fetchAccount } from 'flavours/glitch/actions/accounts'; | ||||
| import { expandAccountFeaturedTimeline, expandAccountTimeline } from 'flavours/glitch/actions/timelines'; | ||||
| import { expandAccountFeaturedTimeline, expandAccountTimeline } from '../../actions/timelines'; | ||||
| import StatusList from '../../components/status_list'; | ||||
| import LoadingIndicator from '../../components/loading_indicator'; | ||||
| import Column from '../ui/components/column'; | ||||
|  | @ -12,7 +12,7 @@ import HeaderContainer from './containers/header_container'; | |||
| import { List as ImmutableList } from 'immutable'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| import { FormattedMessage } from 'react-intl'; | ||||
| import TimelineHint from 'flavours/glitch/components/timeline_hint'; | ||||
| import { TimelineHint } from 'flavours/glitch/components/timeline_hint'; | ||||
| import LimitedAccountHint from './components/limited_account_hint'; | ||||
| import { getAccountHidden } from 'flavours/glitch/selectors'; | ||||
| import { fetchFeaturedTags } from '../../actions/featured_tags'; | ||||
|  | @ -122,7 +122,7 @@ class AccountTimeline extends ImmutablePureComponent { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   componentWillReceiveProps (nextProps) { | ||||
|   UNSAFE_componentWillReceiveProps (nextProps) { | ||||
|     const { dispatch } = this.props; | ||||
| 
 | ||||
|     if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) { | ||||
|  |  | |||
|  | @ -142,7 +142,7 @@ class Audio extends React.PureComponent { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   componentWillReceiveProps (nextProps) { | ||||
|   UNSAFE_componentWillReceiveProps (nextProps) { | ||||
|     if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) { | ||||
|       this.setState({ revealed: nextProps.visible }); | ||||
|     } | ||||
|  |  | |||
|  | @ -34,7 +34,7 @@ class Blocks extends ImmutablePureComponent { | |||
|     multiColumn: PropTypes.bool, | ||||
|   }; | ||||
| 
 | ||||
|   componentWillMount () { | ||||
|   UNSAFE_componentWillMount () { | ||||
|     this.props.dispatch(fetchBlocks()); | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -34,7 +34,7 @@ class Bookmarks extends ImmutablePureComponent { | |||
|     isLoading: PropTypes.bool, | ||||
|   }; | ||||
| 
 | ||||
|   componentWillMount () { | ||||
|   UNSAFE_componentWillMount () { | ||||
|     this.props.dispatch(fetchBookmarkedStatuses()); | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| import React from 'react'; | ||||
| import { Avatar } from 'flavours/glitch/components/avatar'; | ||||
| import DisplayName from 'flavours/glitch/components/display_name'; | ||||
| import { DisplayName } from 'flavours/glitch/components/display_name'; | ||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,12 +2,12 @@ | |||
| import PropTypes from 'prop-types'; | ||||
| import React from 'react'; | ||||
| import classNames from 'classnames'; | ||||
| import { supportsPassiveEvents } from 'detect-passive-events'; | ||||
| 
 | ||||
| //  Components. | ||||
| import { Icon } from 'flavours/glitch/components/icon'; | ||||
| 
 | ||||
| //  Utils. | ||||
| import { withPassive } from 'flavours/glitch/utils/dom_helpers'; | ||||
| const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true; | ||||
| 
 | ||||
| //  The component. | ||||
| export default class ComposerOptionsDropdownContent extends React.PureComponent { | ||||
|  | @ -41,6 +41,7 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent | |||
|   handleDocumentClick = (e) => { | ||||
|     if (this.node && !this.node.contains(e.target)) { | ||||
|       this.props.onClose(); | ||||
|       e.stopPropagation(); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|  | @ -51,8 +52,8 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent | |||
| 
 | ||||
|   //  On mounting, we add our listeners. | ||||
|   componentDidMount () { | ||||
|     document.addEventListener('click', this.handleDocumentClick, false); | ||||
|     document.addEventListener('touchend', this.handleDocumentClick, withPassive); | ||||
|     document.addEventListener('click', this.handleDocumentClick, { capture: true }); | ||||
|     document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); | ||||
|     if (this.focusedItem) { | ||||
|       this.focusedItem.focus({ preventScroll: true }); | ||||
|     } else { | ||||
|  | @ -62,8 +63,8 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent | |||
| 
 | ||||
|   //  On unmounting, we remove our listeners. | ||||
|   componentWillUnmount () { | ||||
|     document.removeEventListener('click', this.handleDocumentClick, false); | ||||
|     document.removeEventListener('touchend', this.handleDocumentClick, withPassive); | ||||
|     document.removeEventListener('click', this.handleDocumentClick, { capture: true }); | ||||
|     document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); | ||||
|   } | ||||
| 
 | ||||
|   handleClick = (e) => { | ||||
|  |  | |||
|  | @ -28,7 +28,7 @@ const messages = defineMessages({ | |||
| 
 | ||||
| let EmojiPicker, Emoji; // load asynchronously | ||||
| 
 | ||||
| const listenerOptions = supportsPassiveEvents ? { passive: true } : false; | ||||
| const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true; | ||||
| 
 | ||||
| const backgroundImageFn = () => `${assetHost}/emoji/sheet_13.png`; | ||||
| 
 | ||||
|  | @ -60,7 +60,7 @@ class ModifierPickerMenu extends React.PureComponent { | |||
|     this.props.onSelect(e.currentTarget.getAttribute('data-index') * 1); | ||||
|   }; | ||||
| 
 | ||||
|   componentWillReceiveProps (nextProps) { | ||||
|   UNSAFE_componentWillReceiveProps (nextProps) { | ||||
|     if (nextProps.active) { | ||||
|       this.attachListeners(); | ||||
|     } else { | ||||
|  | @ -79,12 +79,12 @@ class ModifierPickerMenu extends React.PureComponent { | |||
|   }; | ||||
| 
 | ||||
|   attachListeners () { | ||||
|     document.addEventListener('click', this.handleDocumentClick, false); | ||||
|     document.addEventListener('click', this.handleDocumentClick, { capture: true }); | ||||
|     document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); | ||||
|   } | ||||
| 
 | ||||
|   removeListeners () { | ||||
|     document.removeEventListener('click', this.handleDocumentClick, false); | ||||
|     document.removeEventListener('click', this.handleDocumentClick, { capture: true }); | ||||
|     document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); | ||||
|   } | ||||
| 
 | ||||
|  | @ -177,7 +177,7 @@ class EmojiPickerMenuImpl extends React.PureComponent { | |||
|   }; | ||||
| 
 | ||||
|   componentDidMount () { | ||||
|     document.addEventListener('click', this.handleDocumentClick, false); | ||||
|     document.addEventListener('click', this.handleDocumentClick, { capture: true }); | ||||
|     document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); | ||||
| 
 | ||||
|     // Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need | ||||
|  | @ -192,7 +192,7 @@ class EmojiPickerMenuImpl extends React.PureComponent { | |||
|   } | ||||
| 
 | ||||
|   componentWillUnmount () { | ||||
|     document.removeEventListener('click', this.handleDocumentClick, false); | ||||
|     document.removeEventListener('click', this.handleDocumentClick, { capture: true }); | ||||
|     document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -15,7 +15,7 @@ const messages = defineMessages({ | |||
|   clear: { id: 'emoji_button.clear', defaultMessage: 'Clear' }, | ||||
| }); | ||||
| 
 | ||||
| const listenerOptions = supportsPassiveEvents ? { passive: true } : false; | ||||
| const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true; | ||||
| 
 | ||||
| class LanguageDropdownMenu extends React.PureComponent { | ||||
| 
 | ||||
|  | @ -39,11 +39,12 @@ class LanguageDropdownMenu extends React.PureComponent { | |||
|   handleDocumentClick = e => { | ||||
|     if (this.node && !this.node.contains(e.target)) { | ||||
|       this.props.onClose(); | ||||
|       e.stopPropagation(); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   componentDidMount () { | ||||
|     document.addEventListener('click', this.handleDocumentClick, false); | ||||
|     document.addEventListener('click', this.handleDocumentClick, { capture: true }); | ||||
|     document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); | ||||
| 
 | ||||
|     // Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need | ||||
|  | @ -57,7 +58,7 @@ class LanguageDropdownMenu extends React.PureComponent { | |||
|   } | ||||
| 
 | ||||
|   componentWillUnmount () { | ||||
|     document.removeEventListener('click', this.handleDocumentClick, false); | ||||
|     document.removeEventListener('click', this.handleDocumentClick, { capture: true }); | ||||
|     document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ import PropTypes from 'prop-types'; | |||
| import { connect } from 'react-redux'; | ||||
| import { makeGetAccount } from 'flavours/glitch/selectors'; | ||||
| import { Avatar } from 'flavours/glitch/components/avatar'; | ||||
| import DisplayName from 'flavours/glitch/components/display_name'; | ||||
| import { DisplayName } from 'flavours/glitch/components/display_name'; | ||||
| import Permalink from 'flavours/glitch/components/permalink'; | ||||
| import { IconButton } from 'flavours/glitch/components/icon_button'; | ||||
| import Button from 'flavours/glitch/components/button'; | ||||
|  |  | |||
|  | @ -34,7 +34,7 @@ class Blocks extends ImmutablePureComponent { | |||
|     multiColumn: PropTypes.bool, | ||||
|   }; | ||||
| 
 | ||||
|   componentWillMount () { | ||||
|   UNSAFE_componentWillMount () { | ||||
|     this.props.dispatch(fetchDomainBlocks()); | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ import PropTypes from 'prop-types'; | |||
| import { Blurhash } from 'flavours/glitch/components/blurhash'; | ||||
| import { accountsCountRenderer } from 'flavours/glitch/components/hashtag'; | ||||
| import ShortNumber from 'flavours/glitch/components/short_number'; | ||||
| import Skeleton from 'flavours/glitch/components/skeleton'; | ||||
| import { Skeleton } from 'flavours/glitch/components/skeleton'; | ||||
| import classNames from 'classnames'; | ||||
| 
 | ||||
| export default class Story extends React.PureComponent { | ||||
|  |  | |||
|  | @ -34,7 +34,7 @@ class Favourites extends ImmutablePureComponent { | |||
|     isLoading: PropTypes.bool, | ||||
|   }; | ||||
| 
 | ||||
|   componentWillMount () { | ||||
|   UNSAFE_componentWillMount () { | ||||
|     this.props.dispatch(fetchFavouritedStatuses()); | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -32,13 +32,13 @@ class Favourites extends ImmutablePureComponent { | |||
|     intl: PropTypes.object.isRequired, | ||||
|   }; | ||||
| 
 | ||||
|   componentWillMount () { | ||||
|   UNSAFE_componentWillMount () { | ||||
|     if (!this.props.accountIds) { | ||||
|       this.props.dispatch(fetchFavourites(this.props.params.statusId)); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   componentWillReceiveProps (nextProps) { | ||||
|   UNSAFE_componentWillReceiveProps (nextProps) { | ||||
|     if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { | ||||
|       this.props.dispatch(fetchFavourites(nextProps.params.statusId)); | ||||
|     } | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; | |||
| import { connect } from 'react-redux'; | ||||
| import { makeGetAccount } from 'flavours/glitch/selectors'; | ||||
| import { Avatar } from 'flavours/glitch/components/avatar'; | ||||
| import DisplayName from 'flavours/glitch/components/display_name'; | ||||
| import { DisplayName } from 'flavours/glitch/components/display_name'; | ||||
| import Permalink from 'flavours/glitch/components/permalink'; | ||||
| import { IconButton } from 'flavours/glitch/components/icon_button'; | ||||
| import { injectIntl, defineMessages } from 'react-intl'; | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ import PropTypes from 'prop-types'; | |||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import Permalink from 'flavours/glitch/components/permalink'; | ||||
| import { Avatar } from 'flavours/glitch/components/avatar'; | ||||
| import DisplayName from 'flavours/glitch/components/display_name'; | ||||
| import { DisplayName } from 'flavours/glitch/components/display_name'; | ||||
| import { IconButton } from 'flavours/glitch/components/icon_button'; | ||||
| import { defineMessages, injectIntl } from 'react-intl'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
|  |  | |||
|  | @ -39,7 +39,7 @@ class FollowRequests extends ImmutablePureComponent { | |||
|     multiColumn: PropTypes.bool, | ||||
|   }; | ||||
| 
 | ||||
|   componentWillMount () { | ||||
|   UNSAFE_componentWillMount () { | ||||
|     this.props.dispatch(fetchFollowRequests()); | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -17,7 +17,7 @@ import ProfileColumnHeader from 'flavours/glitch/features/account/components/pro | |||
| import HeaderContainer from 'flavours/glitch/features/account_timeline/containers/header_container'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| 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 LimitedAccountHint from '../account_timeline/components/limited_account_hint'; | ||||
| import { getAccountHidden } from 'flavours/glitch/selectors'; | ||||
| import { normalizeForLookup } from 'flavours/glitch/reducers/accounts_map'; | ||||
|  |  | |||
|  | @ -17,7 +17,7 @@ import ProfileColumnHeader from 'flavours/glitch/features/account/components/pro | |||
| import HeaderContainer from 'flavours/glitch/features/account_timeline/containers/header_container'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| 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 LimitedAccountHint from '../account_timeline/components/limited_account_hint'; | ||||
| import { getAccountHidden } from 'flavours/glitch/selectors'; | ||||
| import { normalizeForLookup } from 'flavours/glitch/reducers/accounts_map'; | ||||
|  |  | |||
|  | @ -96,7 +96,7 @@ class GettingStarted extends ImmutablePureComponent { | |||
|     openSettings: PropTypes.func.isRequired, | ||||
|   }; | ||||
| 
 | ||||
|   componentWillMount () { | ||||
|   UNSAFE_componentWillMount () { | ||||
|     this.props.fetchLists(); | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -143,7 +143,7 @@ class InteractionModal extends React.PureComponent { | |||
|         <div className='interaction-modal__choices'> | ||||
|           <div className='interaction-modal__choices__choice'> | ||||
|             <h3><FormattedMessage id='interaction_modal.on_this_server' defaultMessage='On this server' /></h3> | ||||
|             <a href='/auth/sign_in' className='button button--block'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Sign in' /></a> | ||||
|             <a href='/auth/sign_in' className='button button--block'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Login' /></a> | ||||
|             {signupButton} | ||||
|           </div> | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ import { makeGetAccount } from '../../../selectors'; | |||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import { Avatar } from '../../../components/avatar'; | ||||
| import DisplayName from '../../../components/display_name'; | ||||
| import { DisplayName } from '../../../components/display_name'; | ||||
| import { injectIntl } from 'react-intl'; | ||||
| 
 | ||||
| const makeMapStateToProps = () => { | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ import PropTypes from 'prop-types'; | |||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import { Avatar } from 'flavours/glitch/components/avatar'; | ||||
| import DisplayName from 'flavours/glitch/components/display_name'; | ||||
| import { DisplayName } from 'flavours/glitch/components/display_name'; | ||||
| import { IconButton } from 'flavours/glitch/components/icon_button'; | ||||
| import { defineMessages } from 'react-intl'; | ||||
| 
 | ||||
|  |  | |||
|  | @ -76,7 +76,7 @@ class ListTimeline extends React.PureComponent { | |||
|     this.disconnect = dispatch(connectListStream(id)); | ||||
|   } | ||||
| 
 | ||||
|   componentWillReceiveProps (nextProps) { | ||||
|   UNSAFE_componentWillReceiveProps (nextProps) { | ||||
|     const { dispatch } = this.props; | ||||
|     const { id } = nextProps.params; | ||||
| 
 | ||||
|  |  | |||
|  | @ -42,7 +42,7 @@ class Lists extends ImmutablePureComponent { | |||
|     multiColumn: PropTypes.bool, | ||||
|   }; | ||||
| 
 | ||||
|   componentWillMount () { | ||||
|   UNSAFE_componentWillMount () { | ||||
|     this.props.dispatch(fetchLists()); | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -35,7 +35,7 @@ class Mutes extends ImmutablePureComponent { | |||
|     multiColumn: PropTypes.bool, | ||||
|   }; | ||||
| 
 | ||||
|   componentWillMount () { | ||||
|   UNSAFE_componentWillMount () { | ||||
|     this.props.dispatch(fetchMutes()); | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ import React, { Fragment } from 'react'; | |||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { Avatar } from 'flavours/glitch/components/avatar'; | ||||
| import DisplayName from 'flavours/glitch/components/display_name'; | ||||
| import { DisplayName } from 'flavours/glitch/components/display_name'; | ||||
| import Permalink from 'flavours/glitch/components/permalink'; | ||||
| import { IconButton } from 'flavours/glitch/components/icon_button'; | ||||
| import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ import PropTypes from 'prop-types'; | |||
| import { IconButton } from 'flavours/glitch/components/icon_button'; | ||||
| import { Link } from 'react-router-dom'; | ||||
| import { Avatar } from 'flavours/glitch/components/avatar'; | ||||
| import DisplayName from 'flavours/glitch/components/display_name'; | ||||
| import { DisplayName } from 'flavours/glitch/components/display_name'; | ||||
| import { defineMessages, injectIntl } from 'react-intl'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|  |  | |||
|  | @ -29,7 +29,7 @@ class PinnedStatuses extends ImmutablePureComponent { | |||
|     multiColumn: PropTypes.bool, | ||||
|   }; | ||||
| 
 | ||||
|   componentWillMount () { | ||||
|   UNSAFE_componentWillMount () { | ||||
|     this.props.dispatch(fetchPinnedStatuses()); | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ import { Helmet } from 'react-helmet'; | |||
| import { FormattedMessage, FormattedDate, injectIntl, defineMessages } from 'react-intl'; | ||||
| import Column from 'flavours/glitch/components/column'; | ||||
| import api from 'flavours/glitch/api'; | ||||
| import Skeleton from 'flavours/glitch/components/skeleton'; | ||||
| import { Skeleton } from 'flavours/glitch/components/skeleton'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|   title: { id: 'privacy_policy.title', defaultMessage: 'Privacy Policy' }, | ||||
|  |  | |||
|  | @ -32,13 +32,13 @@ class Reblogs extends ImmutablePureComponent { | |||
|     intl: PropTypes.object.isRequired, | ||||
|   }; | ||||
| 
 | ||||
|   componentWillMount () { | ||||
|   UNSAFE_componentWillMount () { | ||||
|     if (!this.props.accountIds) { | ||||
|       this.props.dispatch(fetchReblogs(this.props.params.statusId)); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   componentWillReceiveProps(nextProps) { | ||||
|   UNSAFE_componentWillReceiveProps(nextProps) { | ||||
|     if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { | ||||
|       this.props.dispatch(fetchReblogs(nextProps.params.statusId)); | ||||
|     } | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ import PropTypes from 'prop-types'; | |||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import StatusContent from 'flavours/glitch/components/status_content'; | ||||
| import { Avatar } from 'flavours/glitch/components/avatar'; | ||||
| import DisplayName from 'flavours/glitch/components/display_name'; | ||||
| import { DisplayName } from 'flavours/glitch/components/display_name'; | ||||
| import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp'; | ||||
| import Option from './option'; | ||||
| import MediaAttachments from 'flavours/glitch/components/media_attachments'; | ||||
|  |  | |||
|  | @ -57,7 +57,7 @@ export default class Card extends React.PureComponent { | |||
|     revealed: !this.props.sensitive, | ||||
|   }; | ||||
| 
 | ||||
|   componentWillReceiveProps (nextProps) { | ||||
|   UNSAFE_componentWillReceiveProps (nextProps) { | ||||
|     if (!Immutable.is(this.props.card, nextProps.card)) { | ||||
|       this.setState({ embedded: false, previewLoaded: false }); | ||||
|     } | ||||
|  |  | |||
|  | @ -2,7 +2,7 @@ import React from 'react'; | |||
| import PropTypes from 'prop-types'; | ||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import { Avatar } from 'flavours/glitch/components/avatar'; | ||||
| import DisplayName from 'flavours/glitch/components/display_name'; | ||||
| import { DisplayName } from 'flavours/glitch/components/display_name'; | ||||
| import StatusContent from 'flavours/glitch/components/status_content'; | ||||
| import MediaGallery from 'flavours/glitch/components/media_gallery'; | ||||
| import AttachmentList from 'flavours/glitch/components/attachment_list'; | ||||
|  |  | |||
|  | @ -125,12 +125,12 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ | |||
|     dispatch(mentionCompose(account, router)); | ||||
|   }, | ||||
| 
 | ||||
|   onOpenMedia (media, index) { | ||||
|     dispatch(openModal('MEDIA', { media, index })); | ||||
|   onOpenMedia (media, index, lang) { | ||||
|     dispatch(openModal('MEDIA', { media, index, lang })); | ||||
|   }, | ||||
| 
 | ||||
|   onOpenVideo (media, options) { | ||||
|     dispatch(openModal('VIDEO', { media, options })); | ||||
|   onOpenVideo (media, lang, options) { | ||||
|     dispatch(openModal('VIDEO', { media, lang, options })); | ||||
|   }, | ||||
| 
 | ||||
|   onBlock (status) { | ||||
|  |  | |||
|  | @ -392,12 +392,12 @@ class Status extends ImmutablePureComponent { | |||
|     this.props.dispatch(mentionCompose(account, router)); | ||||
|   }; | ||||
| 
 | ||||
|   handleOpenMedia = (media, index) => { | ||||
|     this.props.dispatch(openModal('MEDIA', { statusId: this.props.status.get('id'), media, index })); | ||||
|   handleOpenMedia = (media, index, lang) => { | ||||
|     this.props.dispatch(openModal('MEDIA', { statusId: this.props.status.get('id'), media, index, lang })); | ||||
|   }; | ||||
| 
 | ||||
|   handleOpenVideo = (media, options) => { | ||||
|     this.props.dispatch(openModal('VIDEO', { statusId: this.props.status.get('id'), media, options })); | ||||
|   handleOpenVideo = (media, lang, options) => { | ||||
|     this.props.dispatch(openModal('VIDEO', { statusId: this.props.status.get('id'), media, lang, options })); | ||||
|   }; | ||||
| 
 | ||||
|   handleHotkeyOpenMedia = e => { | ||||
|  |  | |||
|  | @ -5,7 +5,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; | |||
| import StatusContent from 'flavours/glitch/components/status_content'; | ||||
| import { Avatar } from 'flavours/glitch/components/avatar'; | ||||
| import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp'; | ||||
| import DisplayName from 'flavours/glitch/components/display_name'; | ||||
| import { DisplayName } from 'flavours/glitch/components/display_name'; | ||||
| import classNames from 'classnames'; | ||||
| import { IconButton } from 'flavours/glitch/components/icon_button'; | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,7 +7,7 @@ import Button from 'flavours/glitch/components/button'; | |||
| import StatusContent from 'flavours/glitch/components/status_content'; | ||||
| import { Avatar } from 'flavours/glitch/components/avatar'; | ||||
| import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp'; | ||||
| import DisplayName from 'flavours/glitch/components/display_name'; | ||||
| import { DisplayName } from 'flavours/glitch/components/display_name'; | ||||
| import AttachmentList from 'flavours/glitch/components/attachment_list'; | ||||
| import { Icon } from 'flavours/glitch/components/icon'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
|  |  | |||
|  | @ -33,11 +33,11 @@ class Bundle extends React.Component { | |||
|     forceRender: false, | ||||
|   }; | ||||
| 
 | ||||
|   componentWillMount() { | ||||
|   UNSAFE_componentWillMount() { | ||||
|     this.load(this.props); | ||||
|   } | ||||
| 
 | ||||
|   componentWillReceiveProps(nextProps) { | ||||
|   UNSAFE_componentWillReceiveProps(nextProps) { | ||||
|     if (nextProps.fetchComponent !== this.props.fetchComponent) { | ||||
|       this.load(nextProps); | ||||
|     } | ||||
|  |  | |||
|  | @ -18,7 +18,7 @@ import { | |||
|   BookmarkedStatuses, | ||||
|   ListTimeline, | ||||
|   Directory, | ||||
| } from '../../ui/util/async-components'; | ||||
| } from '../util/async-components'; | ||||
| import ComposePanel from './compose_panel'; | ||||
| import NavigationPanel from './navigation_panel'; | ||||
| 
 | ||||
|  |  | |||
|  | @ -85,7 +85,7 @@ class EmbedModal extends ImmutablePureComponent { | |||
|             className='embed-modal__iframe' | ||||
|             frameBorder='0' | ||||
|             ref={this.setIframeRef} | ||||
|             sandbox='allow-same-origin' | ||||
|             sandbox='allow-scripts allow-same-origin' | ||||
|             title='preview' | ||||
|           /> | ||||
|         </div> | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ import Button from 'flavours/glitch/components/button'; | |||
| import StatusContent from 'flavours/glitch/components/status_content'; | ||||
| import { Avatar } from 'flavours/glitch/components/avatar'; | ||||
| import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp'; | ||||
| import DisplayName from 'flavours/glitch/components/display_name'; | ||||
| import { DisplayName } from 'flavours/glitch/components/display_name'; | ||||
| import AttachmentList from 'flavours/glitch/components/attachment_list'; | ||||
| import { Icon } from 'flavours/glitch/components/icon'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
|  |  | |||
|  | @ -4,7 +4,7 @@ import PropTypes from 'prop-types'; | |||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| import { connect } from 'react-redux'; | ||||
| import classNames from 'classnames'; | ||||
| import { changeUploadCompose, uploadThumbnail, onChangeMediaDescription, onChangeMediaFocus } from 'flavours/glitch/actions/compose'; | ||||
| import { changeUploadCompose, uploadThumbnail, onChangeMediaDescription, onChangeMediaFocus } from '../../../actions/compose'; | ||||
| import Video, { getPointerPosition } from 'flavours/glitch/features/video'; | ||||
| import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; | ||||
| import { IconButton } from 'flavours/glitch/components/icon_button'; | ||||
|  |  | |||
|  | @ -52,13 +52,13 @@ class Header extends React.PureComponent { | |||
| 
 | ||||
|       if (registrationsOpen) { | ||||
|         signupButton = ( | ||||
|           <a href='/auth/sign_up' className='button button-tertiary'> | ||||
|           <a href='/auth/sign_up' className='button'> | ||||
|             <FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' /> | ||||
|           </a> | ||||
|         ); | ||||
|       } else { | ||||
|         signupButton = ( | ||||
|           <button className='button button-tertiary' onClick={openClosedRegistrationsModal}> | ||||
|           <button className='button' onClick={openClosedRegistrationsModal}> | ||||
|             <FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' /> | ||||
|           </button> | ||||
|         ); | ||||
|  | @ -66,8 +66,8 @@ class Header extends React.PureComponent { | |||
| 
 | ||||
|       content = ( | ||||
|         <> | ||||
|           <a href='/auth/sign_in' className='button'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Sign in' /></a> | ||||
|           {signupButton} | ||||
|           <a href='/auth/sign_in' className='button button-tertiary'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Login' /></a> | ||||
|         </> | ||||
|       ); | ||||
|     } | ||||
|  |  | |||
|  | @ -3,7 +3,6 @@ import ReactSwipeableViews from 'react-swipeable-views'; | |||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import Video from 'flavours/glitch/features/video'; | ||||
| import { connect } from 'react-redux'; | ||||
| import classNames from 'classnames'; | ||||
| import { defineMessages, injectIntl } from 'react-intl'; | ||||
| import { IconButton } from 'flavours/glitch/components/icon_button'; | ||||
|  | @ -21,10 +20,6 @@ const messages = defineMessages({ | |||
|   next: { id: 'lightbox.next', defaultMessage: 'Next' }, | ||||
| }); | ||||
| 
 | ||||
| const mapStateToProps = (state, { statusId }) => ({ | ||||
|   language: state.getIn(['statuses', statusId, 'language']), | ||||
| }); | ||||
| 
 | ||||
| class MediaModal extends ImmutablePureComponent { | ||||
| 
 | ||||
|   static contextTypes = { | ||||
|  | @ -34,6 +29,7 @@ class MediaModal extends ImmutablePureComponent { | |||
|   static propTypes = { | ||||
|     media: ImmutablePropTypes.list.isRequired, | ||||
|     statusId: PropTypes.string, | ||||
|     lang: PropTypes.string, | ||||
|     index: PropTypes.number.isRequired, | ||||
|     onClose: PropTypes.func.isRequired, | ||||
|     intl: PropTypes.object.isRequired, | ||||
|  | @ -135,7 +131,7 @@ class MediaModal extends ImmutablePureComponent { | |||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { media, language, statusId, intl, onClose } = this.props; | ||||
|     const { media, statusId, lang, intl, onClose } = this.props; | ||||
|     const { navigationHidden } = this.state; | ||||
| 
 | ||||
|     const index = this.getIndex(); | ||||
|  | @ -155,7 +151,7 @@ class MediaModal extends ImmutablePureComponent { | |||
|             width={width} | ||||
|             height={height} | ||||
|             alt={image.get('description')} | ||||
|             lang={language} | ||||
|             lang={lang} | ||||
|             key={image.get('url')} | ||||
|             onClick={this.toggleNavigation} | ||||
|             zoomButtonHidden={this.state.zoomButtonHidden} | ||||
|  | @ -178,7 +174,7 @@ class MediaModal extends ImmutablePureComponent { | |||
|             onCloseVideo={onClose} | ||||
|             detailed | ||||
|             alt={image.get('description')} | ||||
|             lang={language} | ||||
|             lang={lang} | ||||
|             key={image.get('url')} | ||||
|           /> | ||||
|         ); | ||||
|  | @ -190,7 +186,7 @@ class MediaModal extends ImmutablePureComponent { | |||
|             height={height} | ||||
|             key={image.get('url')} | ||||
|             alt={image.get('description')} | ||||
|             lang={language} | ||||
|             lang={lang} | ||||
|             onClick={this.toggleNavigation} | ||||
|           /> | ||||
|         ); | ||||
|  | @ -258,4 +254,4 @@ class MediaModal extends ImmutablePureComponent { | |||
| 
 | ||||
| } | ||||
| 
 | ||||
| export default connect(mapStateToProps, null, null, { forwardRef: true })(injectIntl(MediaModal)); | ||||
| export default injectIntl(MediaModal); | ||||
|  |  | |||
Some files were not shown because too many files have changed in this diff Show more
		Loading…
	
		Reference in a new issue