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)$', |       '\\.(css|scss|json)$', | ||||||
|     ], |     ], | ||||||
|     'import/resolver': { |     'import/resolver': { | ||||||
|       node: { |       typescript: {}, | ||||||
|         paths: ['app/javascript'], |  | ||||||
|         extensions: ['.js', '.jsx', '.ts', '.tsx'], |  | ||||||
|       }, |  | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|  | @ -104,7 +101,6 @@ module.exports = { | ||||||
|     'react/jsx-equals-spacing': 'error', |     'react/jsx-equals-spacing': 'error', | ||||||
|     'react/jsx-no-bind': 'error', |     'react/jsx-no-bind': 'error', | ||||||
|     'react/jsx-no-target-blank': 'off', |     'react/jsx-no-target-blank': 'off', | ||||||
|     'react/no-deprecated': 'off', |  | ||||||
|     'react/no-unknown-property': 'off', |     'react/no-unknown-property': 'off', | ||||||
|     'react/self-closing-comp': 'error', |     'react/self-closing-comp': 'error', | ||||||
| 
 | 
 | ||||||
|  | @ -168,11 +164,14 @@ module.exports = { | ||||||
|       { |       { | ||||||
|         js: 'never', |         js: 'never', | ||||||
|         jsx: 'never', |         jsx: 'never', | ||||||
|  |         mjs: 'never', | ||||||
|         ts: 'never', |         ts: 'never', | ||||||
|         tsx: 'never', |         tsx: 'never', | ||||||
|       }, |       }, | ||||||
|     ], |     ], | ||||||
|  |     'import/first': 'error', | ||||||
|     'import/newline-after-import': 'error', |     'import/newline-after-import': 'error', | ||||||
|  |     'import/no-anonymous-default-export': 'error', | ||||||
|     'import/no-extraneous-dependencies': [ |     'import/no-extraneous-dependencies': [ | ||||||
|       'error', |       'error', | ||||||
|       { |       { | ||||||
|  | @ -187,6 +186,9 @@ module.exports = { | ||||||
|     'import/no-amd': 'error', |     'import/no-amd': 'error', | ||||||
|     'import/no-commonjs': 'error', |     'import/no-commonjs': 'error', | ||||||
|     'import/no-import-module-exports': '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', |     'import/no-webpack-loader-syntax': 'error', | ||||||
| 
 | 
 | ||||||
|     'promise/always-return': 'off', |     'promise/always-return': 'off', | ||||||
|  | @ -258,6 +260,7 @@ module.exports = { | ||||||
|       extends: [ |       extends: [ | ||||||
|         'eslint:recommended', |         'eslint:recommended', | ||||||
|         'plugin:@typescript-eslint/recommended', |         'plugin:@typescript-eslint/recommended', | ||||||
|  |         'plugin:@typescript-eslint/recommended-requiring-type-checking', | ||||||
|         'plugin:react/recommended', |         'plugin:react/recommended', | ||||||
|         'plugin:react-hooks/recommended', |         'plugin:react-hooks/recommended', | ||||||
|         'plugin:jsx-a11y/recommended', |         'plugin:jsx-a11y/recommended', | ||||||
|  | @ -268,8 +271,66 @@ module.exports = { | ||||||
|         'plugin:prettier/recommended', |         'plugin:prettier/recommended', | ||||||
|       ], |       ], | ||||||
| 
 | 
 | ||||||
|  |       parserOptions: { | ||||||
|  |         project: './tsconfig.json', | ||||||
|  |         tsconfigRootDir: __dirname, | ||||||
|  |       }, | ||||||
|  | 
 | ||||||
|       rules: { |       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', |         '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 |     needs: pre_job | ||||||
|     if: needs.pre_job.outputs.should_skip != 'true' |     if: needs.pre_job.outputs.should_skip != 'true' | ||||||
| 
 | 
 | ||||||
|  |     strategy: | ||||||
|  |       fail-fast: false | ||||||
|  | 
 | ||||||
|  |       matrix: | ||||||
|  |         postgres: | ||||||
|  |           - 14-alpine | ||||||
|  |           - 15-alpine | ||||||
|  | 
 | ||||||
|     services: |     services: | ||||||
|       postgres: |       postgres: | ||||||
|         image: postgres:14-alpine |         image: postgres:${{ matrix.postgres}} | ||||||
|         env: |         env: | ||||||
|           POSTGRES_PASSWORD: postgres |           POSTGRES_PASSWORD: postgres | ||||||
|           POSTGRES_USER: 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 |     needs: pre_job | ||||||
|     if: needs.pre_job.outputs.should_skip != 'true' |     if: needs.pre_job.outputs.should_skip != 'true' | ||||||
| 
 | 
 | ||||||
|  |     strategy: | ||||||
|  |       fail-fast: false | ||||||
|  | 
 | ||||||
|  |       matrix: | ||||||
|  |         postgres: | ||||||
|  |           - 14-alpine | ||||||
|  |           - 15-alpine | ||||||
|  | 
 | ||||||
|     services: |     services: | ||||||
|       postgres: |       postgres: | ||||||
|         image: postgres:14-alpine |         image: postgres:${{ matrix.postgres}} | ||||||
|         env: |         env: | ||||||
|           POSTGRES_PASSWORD: postgres |           POSTGRES_PASSWORD: postgres | ||||||
|           POSTGRES_USER: postgres |           POSTGRES_USER: postgres | ||||||
|  |  | ||||||
|  | @ -21,12 +21,6 @@ Layout/ArgumentAlignment: | ||||||
|     - 'config/initializers/cors.rb' |     - 'config/initializers/cors.rb' | ||||||
|     - 'config/initializers/session_store.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). | # This cop supports safe autocorrection (--autocorrect). | ||||||
| # Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. | # Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. | ||||||
| # SupportedHashRocketStyles: key, separator, table | # SupportedHashRocketStyles: key, separator, table | ||||||
|  | @ -39,12 +33,6 @@ Layout/HashAlignment: | ||||||
|     - 'config/initializers/rack_attack.rb' |     - 'config/initializers/rack_attack.rb' | ||||||
|     - 'config/routes.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). | # This cop supports safe autocorrection (--autocorrect). | ||||||
| # Configuration parameters: AllowDoxygenCommentStyle, AllowGemfileRubyComment. | # Configuration parameters: AllowDoxygenCommentStyle, AllowGemfileRubyComment. | ||||||
| Layout/LeadingCommentSpace: | Layout/LeadingCommentSpace: | ||||||
|  | @ -52,14 +40,6 @@ Layout/LeadingCommentSpace: | ||||||
|     - 'config/application.rb' |     - 'config/application.rb' | ||||||
|     - 'config/initializers/omniauth.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). | # This cop supports safe autocorrection (--autocorrect). | ||||||
| # Configuration parameters: EnforcedStyle. | # Configuration parameters: EnforcedStyle. | ||||||
| # SupportedStyles: require_no_space, require_space | # SupportedStyles: require_no_space, require_space | ||||||
|  | @ -68,19 +48,6 @@ Layout/SpaceInLambdaLiteral: | ||||||
|     - 'config/environments/production.rb' |     - 'config/environments/production.rb' | ||||||
|     - 'config/initializers/content_security_policy.rb' |     - 'config/initializers/content_security_policy.rb' | ||||||
| 
 | 
 | ||||||
| # This cop supports safe autocorrection (--autocorrect). |  | ||||||
| # Configuration parameters: 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. | # Configuration parameters: AllowedMethods, AllowedPatterns. | ||||||
| Lint/AmbiguousBlockAssociation: | Lint/AmbiguousBlockAssociation: | ||||||
|   Exclude: |   Exclude: | ||||||
|  | @ -94,11 +61,6 @@ Lint/AmbiguousBlockAssociation: | ||||||
|     - 'spec/services/unsuspend_account_service_spec.rb' |     - 'spec/services/unsuspend_account_service_spec.rb' | ||||||
|     - 'spec/workers/scheduler/accounts_statuses_cleanup_scheduler_spec.rb' |     - 'spec/workers/scheduler/accounts_statuses_cleanup_scheduler_spec.rb' | ||||||
| 
 | 
 | ||||||
| # This cop supports safe autocorrection (--autocorrect). |  | ||||||
| Lint/AmbiguousOperatorPrecedence: |  | ||||||
|   Exclude: |  | ||||||
|     - 'config/initializers/rack_attack.rb' |  | ||||||
| 
 |  | ||||||
| # Configuration parameters: AllowComments, AllowEmptyLambdas. | # Configuration parameters: AllowComments, AllowEmptyLambdas. | ||||||
| Lint/EmptyBlock: | Lint/EmptyBlock: | ||||||
|   Exclude: |   Exclude: | ||||||
|  | @ -277,31 +239,6 @@ Naming/VariableNumber: | ||||||
|     - 'spec/models/user_spec.rb' |     - 'spec/models/user_spec.rb' | ||||||
|     - 'spec/services/activitypub/fetch_featured_collection_service_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). | # This cop supports unsafe autocorrection (--autocorrect-all). | ||||||
| Performance/UnfreezeString: | Performance/UnfreezeString: | ||||||
|   Exclude: |   Exclude: | ||||||
|  | @ -626,7 +563,6 @@ RSpec/NoExpectationExample: | ||||||
| 
 | 
 | ||||||
| RSpec/PendingWithoutReason: | RSpec/PendingWithoutReason: | ||||||
|   Exclude: |   Exclude: | ||||||
|     - 'spec/controllers/statuses_controller_spec.rb' |  | ||||||
|     - 'spec/models/account_spec.rb' |     - 'spec/models/account_spec.rb' | ||||||
| 
 | 
 | ||||||
| # This cop supports unsafe autocorrection (--autocorrect-all). | # This cop supports unsafe autocorrection (--autocorrect-all). | ||||||
|  | @ -638,32 +574,6 @@ RSpec/PredicateMatcher: | ||||||
|     - 'spec/models/user_spec.rb' |     - 'spec/models/user_spec.rb' | ||||||
|     - 'spec/services/post_status_service_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: | RSpec/StubbedMock: | ||||||
|   Exclude: |   Exclude: | ||||||
|     - 'spec/controllers/api/base_controller_spec.rb' |     - 'spec/controllers/api/base_controller_spec.rb' | ||||||
|  |  | ||||||
|  | @ -55,7 +55,7 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"] | ||||||
| ENV DEBIAN_FRONTEND="noninteractive" \ | ENV DEBIAN_FRONTEND="noninteractive" \ | ||||||
|     PATH="${PATH}:/opt/ruby/bin:/opt/mastodon/bin" |     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 | # hadolint ignore=DL3008,DL3009 | ||||||
| RUN apt-get update && \ | RUN apt-get update && \ | ||||||
|     echo "Etc/UTC" > /etc/localtime && \ |     echo "Etc/UTC" > /etc/localtime && \ | ||||||
|  |  | ||||||
							
								
								
									
										92
									
								
								Gemfile
									
									
									
									
									
								
							
							
						
						
									
										92
									
								
								Gemfile
									
									
									
									
									
								
							|  | @ -17,7 +17,7 @@ gem 'makara', '~> 0.5' | ||||||
| gem 'pghero' | gem 'pghero' | ||||||
| gem 'dotenv-rails', '~> 2.8' | 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-core', '<= 2.4.0' | ||||||
| gem 'fog-openstack', '~> 0.3', require: false | gem 'fog-openstack', '~> 0.3', require: false | ||||||
| gem 'kt-paperclip', '~> 7.1', github: 'kreeti/kt-paperclip', ref: '11abf222dc31bff71160a1d138b445214f434b2b' | 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 'redcarpet', '~> 3.6' | ||||||
| gem 'redis', '~> 4.5', require: ['redis', 'redis/connection/hiredis'] | gem 'redis', '~> 4.5', require: ['redis', 'redis/connection/hiredis'] | ||||||
| gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock' | gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock' | ||||||
| gem 'rqrcode', '~> 2.1' | gem 'rqrcode', '~> 2.2' | ||||||
| gem 'ruby-progressbar', '~> 1.13' | gem 'ruby-progressbar', '~> 1.13' | ||||||
| gem 'sanitize', '~> 6.0' | gem 'sanitize', '~> 6.0' | ||||||
| gem 'scenic', '~> 1.7' | gem 'scenic', '~> 1.7' | ||||||
|  | @ -99,54 +99,87 @@ gem 'json-ld' | ||||||
| gem 'json-ld-preloaded', '~> 3.2' | gem 'json-ld-preloaded', '~> 3.2' | ||||||
| gem 'rdf-normalize', '~> 0.5' | gem 'rdf-normalize', '~> 0.5' | ||||||
| 
 | 
 | ||||||
| group :development, :test do | gem 'private_address_check', '~> 0.5' | ||||||
|   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 |  | ||||||
| 
 | 
 | ||||||
| group :test do | group :test do | ||||||
|   gem 'capybara', '~> 3.39' |   # RSpec runner for rails | ||||||
|   gem 'climate_control' |   gem 'rspec-rails', '~> 6.0' | ||||||
|   gem 'faker', '~> 3.2' | 
 | ||||||
|   gem 'json-schema', '~> 4.0' |   # Used to split testing into chunks in CI | ||||||
|   gem 'rack-test', '~> 2.1' |   gem 'rspec_chunked', '~> 0.6' | ||||||
|   gem 'rails-controller-testing', '~> 1.0' | 
 | ||||||
|   gem 'rspec_junit_formatter', '~> 0.6' |   # RSpec progress bar formatter | ||||||
|  |   gem 'fuubar', '~> 2.5' | ||||||
|  | 
 | ||||||
|  |   # Extra RSpec extenion methods and helpers for sidekiq | ||||||
|   gem 'rspec-sidekiq', '~> 3.1' |   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 |   gem 'simplecov', '~> 0.22', require: false | ||||||
|  | 
 | ||||||
|  |   # Stub web requests for specs | ||||||
|   gem 'webmock', '~> 3.18' |   gem 'webmock', '~> 3.18' | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
| group :development do | 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' |   gem 'annotate', '~> 3.2' | ||||||
|  | 
 | ||||||
|  |   # Enhanced error message pages for development | ||||||
|   gem 'better_errors', '~> 2.9' |   gem 'better_errors', '~> 2.9' | ||||||
|   gem 'binding_of_caller', '~> 1.0' |   gem 'binding_of_caller', '~> 1.0' | ||||||
|  | 
 | ||||||
|  |   # Preview mail in the browser | ||||||
|   gem 'letter_opener', '~> 1.8' |   gem 'letter_opener', '~> 1.8' | ||||||
|   gem 'letter_opener_web', '~> 2.0' |   gem 'letter_opener_web', '~> 2.0' | ||||||
|   gem 'memory_profiler' | 
 | ||||||
|  |   # Security analysis CLI tools | ||||||
|   gem 'brakeman', '~> 5.4', require: false |   gem 'brakeman', '~> 5.4', require: false | ||||||
|   gem 'bundler-audit', '~> 0.9', require: false |   gem 'bundler-audit', '~> 0.9', require: false | ||||||
|  | 
 | ||||||
|  |   # Linter CLI for HAML files | ||||||
|   gem 'haml_lint', require: false |   gem 'haml_lint', require: false | ||||||
| 
 | 
 | ||||||
|  |   # Deployment automation | ||||||
|   gem 'capistrano', '~> 3.17' |   gem 'capistrano', '~> 3.17' | ||||||
|   gem 'capistrano-rails', '~> 1.6' |   gem 'capistrano-rails', '~> 1.6' | ||||||
|   gem 'capistrano-rbenv', '~> 2.2' |   gem 'capistrano-rbenv', '~> 2.2' | ||||||
|   gem 'capistrano-yarn', '~> 2.0' |   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 | end | ||||||
| 
 | 
 | ||||||
| group :production do | group :production do | ||||||
|  | @ -157,8 +190,9 @@ gem 'concurrent-ruby', require: false | ||||||
| gem 'connection_pool', require: false | gem 'connection_pool', require: false | ||||||
| gem 'xorcist', '~> 1.1' | gem 'xorcist', '~> 1.1' | ||||||
| 
 | 
 | ||||||
| gem 'hcaptcha', '~> 7.1' |  | ||||||
| gem 'cocoon', '~> 1.2' | gem 'cocoon', '~> 1.2' | ||||||
| 
 | 
 | ||||||
| gem 'net-http', '~> 0.3.2' | gem 'net-http', '~> 0.3.2' | ||||||
| gem 'rubyzip', '~> 2.3' | gem 'rubyzip', '~> 2.3' | ||||||
|  | 
 | ||||||
|  | gem 'hcaptcha', '~> 7.1' | ||||||
|  |  | ||||||
							
								
								
									
										51
									
								
								Gemfile.lock
									
									
									
									
									
								
							
							
						
						
									
										51
									
								
								Gemfile.lock
									
									
									
									
									
								
							|  | @ -109,16 +109,16 @@ GEM | ||||||
|     attr_required (1.0.1) |     attr_required (1.0.1) | ||||||
|     awrence (1.2.1) |     awrence (1.2.1) | ||||||
|     aws-eventstream (1.2.0) |     aws-eventstream (1.2.0) | ||||||
|     aws-partitions (1.752.0) |     aws-partitions (1.761.0) | ||||||
|     aws-sdk-core (3.171.0) |     aws-sdk-core (3.172.0) | ||||||
|       aws-eventstream (~> 1, >= 1.0.2) |       aws-eventstream (~> 1, >= 1.0.2) | ||||||
|       aws-partitions (~> 1, >= 1.651.0) |       aws-partitions (~> 1, >= 1.651.0) | ||||||
|       aws-sigv4 (~> 1.5) |       aws-sigv4 (~> 1.5) | ||||||
|       jmespath (~> 1, >= 1.6.1) |       jmespath (~> 1, >= 1.6.1) | ||||||
|     aws-sdk-kms (1.63.0) |     aws-sdk-kms (1.64.0) | ||||||
|       aws-sdk-core (~> 3, >= 3.165.0) |       aws-sdk-core (~> 3, >= 3.165.0) | ||||||
|       aws-sigv4 (~> 1.1) |       aws-sigv4 (~> 1.1) | ||||||
|     aws-sdk-s3 (1.121.0) |     aws-sdk-s3 (1.122.0) | ||||||
|       aws-sdk-core (~> 3, >= 3.165.0) |       aws-sdk-core (~> 3, >= 3.165.0) | ||||||
|       aws-sdk-kms (~> 1) |       aws-sdk-kms (~> 1) | ||||||
|       aws-sigv4 (~> 1.4) |       aws-sigv4 (~> 1.4) | ||||||
|  | @ -166,7 +166,7 @@ GEM | ||||||
|       sshkit (~> 1.3) |       sshkit (~> 1.3) | ||||||
|     capistrano-yarn (2.0.2) |     capistrano-yarn (2.0.2) | ||||||
|       capistrano (~> 3.0) |       capistrano (~> 3.0) | ||||||
|     capybara (3.39.0) |     capybara (3.39.1) | ||||||
|       addressable |       addressable | ||||||
|       matrix |       matrix | ||||||
|       mini_mime (>= 0.1.3) |       mini_mime (>= 0.1.3) | ||||||
|  | @ -189,7 +189,7 @@ GEM | ||||||
|     coderay (1.1.3) |     coderay (1.1.3) | ||||||
|     color_diff (0.1) |     color_diff (0.1) | ||||||
|     concurrent-ruby (1.2.2) |     concurrent-ruby (1.2.2) | ||||||
|     connection_pool (2.4.0) |     connection_pool (2.4.1) | ||||||
|     cose (1.3.0) |     cose (1.3.0) | ||||||
|       cbor (~> 0.5.9) |       cbor (~> 0.5.9) | ||||||
|       openssl-signature_algorithm (~> 1.0) |       openssl-signature_algorithm (~> 1.0) | ||||||
|  | @ -331,7 +331,7 @@ GEM | ||||||
|     httplog (1.6.2) |     httplog (1.6.2) | ||||||
|       rack (>= 2.0) |       rack (>= 2.0) | ||||||
|       rainbow (>= 2.0.0) |       rainbow (>= 2.0.0) | ||||||
|     i18n (1.12.0) |     i18n (1.13.0) | ||||||
|       concurrent-ruby (~> 1.0) |       concurrent-ruby (~> 1.0) | ||||||
|     i18n-tasks (1.0.12) |     i18n-tasks (1.0.12) | ||||||
|       activesupport (>= 4.0.2) |       activesupport (>= 4.0.2) | ||||||
|  | @ -398,9 +398,9 @@ GEM | ||||||
|       activesupport (>= 4) |       activesupport (>= 4) | ||||||
|       railties (>= 4) |       railties (>= 4) | ||||||
|       request_store (~> 1.0) |       request_store (~> 1.0) | ||||||
|     loofah (2.20.0) |     loofah (2.21.3) | ||||||
|       crass (~> 1.0.2) |       crass (~> 1.0.2) | ||||||
|       nokogiri (>= 1.5.9) |       nokogiri (>= 1.12.0) | ||||||
|     mail (2.8.1) |     mail (2.8.1) | ||||||
|       mini_mime (>= 0.1.1) |       mini_mime (>= 0.1.1) | ||||||
|       net-imap |       net-imap | ||||||
|  | @ -418,7 +418,7 @@ GEM | ||||||
|       mime-types-data (~> 3.2015) |       mime-types-data (~> 3.2015) | ||||||
|     mime-types-data (3.2023.0218.1) |     mime-types-data (3.2023.0218.1) | ||||||
|     mini_mime (1.1.2) |     mini_mime (1.1.2) | ||||||
|     mini_portile2 (2.8.1) |     mini_portile2 (2.8.2) | ||||||
|     minitest (5.18.0) |     minitest (5.18.0) | ||||||
|     msgpack (1.7.0) |     msgpack (1.7.0) | ||||||
|     multi_json (1.15.0) |     multi_json (1.15.0) | ||||||
|  | @ -576,7 +576,7 @@ GEM | ||||||
|     rexml (3.2.5) |     rexml (3.2.5) | ||||||
|     rotp (6.2.2) |     rotp (6.2.2) | ||||||
|     rpam2 (4.0.2) |     rpam2 (4.0.2) | ||||||
|     rqrcode (2.1.2) |     rqrcode (2.2.0) | ||||||
|       chunky_png (~> 1.0) |       chunky_png (~> 1.0) | ||||||
|       rqrcode_core (~> 1.0) |       rqrcode_core (~> 1.0) | ||||||
|     rqrcode_core (1.2.0) |     rqrcode_core (1.2.0) | ||||||
|  | @ -588,22 +588,20 @@ GEM | ||||||
|     rspec-mocks (3.12.5) |     rspec-mocks (3.12.5) | ||||||
|       diff-lcs (>= 1.2.0, < 2.0) |       diff-lcs (>= 1.2.0, < 2.0) | ||||||
|       rspec-support (~> 3.12.0) |       rspec-support (~> 3.12.0) | ||||||
|     rspec-rails (6.0.1) |     rspec-rails (6.0.2) | ||||||
|       actionpack (>= 6.1) |       actionpack (>= 6.1) | ||||||
|       activesupport (>= 6.1) |       activesupport (>= 6.1) | ||||||
|       railties (>= 6.1) |       railties (>= 6.1) | ||||||
|       rspec-core (~> 3.11) |       rspec-core (~> 3.12) | ||||||
|       rspec-expectations (~> 3.11) |       rspec-expectations (~> 3.12) | ||||||
|       rspec-mocks (~> 3.11) |       rspec-mocks (~> 3.12) | ||||||
|       rspec-support (~> 3.11) |       rspec-support (~> 3.12) | ||||||
|     rspec-sidekiq (3.1.0) |     rspec-sidekiq (3.1.0) | ||||||
|       rspec-core (~> 3.0, >= 3.0.0) |       rspec-core (~> 3.0, >= 3.0.0) | ||||||
|       sidekiq (>= 2.4.0) |       sidekiq (>= 2.4.0) | ||||||
|     rspec-support (3.12.0) |     rspec-support (3.12.0) | ||||||
|     rspec_chunked (0.6) |     rspec_chunked (0.6) | ||||||
|     rspec_junit_formatter (0.6.0) |     rubocop (1.51.0) | ||||||
|       rspec-core (>= 2, < 4, != 2.12.0) |  | ||||||
|     rubocop (1.50.2) |  | ||||||
|       json (~> 2.3) |       json (~> 2.3) | ||||||
|       parallel (~> 1.10) |       parallel (~> 1.10) | ||||||
|       parser (>= 3.2.0.0) |       parser (>= 3.2.0.0) | ||||||
|  | @ -613,11 +611,11 @@ GEM | ||||||
|       rubocop-ast (>= 1.28.0, < 2.0) |       rubocop-ast (>= 1.28.0, < 2.0) | ||||||
|       ruby-progressbar (~> 1.7) |       ruby-progressbar (~> 1.7) | ||||||
|       unicode-display_width (>= 2.4.0, < 3.0) |       unicode-display_width (>= 2.4.0, < 3.0) | ||||||
|     rubocop-ast (1.28.0) |     rubocop-ast (1.28.1) | ||||||
|       parser (>= 3.2.1.0) |       parser (>= 3.2.1.0) | ||||||
|     rubocop-capybara (2.18.0) |     rubocop-capybara (2.18.0) | ||||||
|       rubocop (~> 1.41) |       rubocop (~> 1.41) | ||||||
|     rubocop-performance (1.17.1) |     rubocop-performance (1.18.0) | ||||||
|       rubocop (>= 1.7.0, < 2.0) |       rubocop (>= 1.7.0, < 2.0) | ||||||
|       rubocop-ast (>= 0.4.0) |       rubocop-ast (>= 0.4.0) | ||||||
|     rubocop-rails (2.19.1) |     rubocop-rails (2.19.1) | ||||||
|  | @ -698,7 +696,7 @@ GEM | ||||||
|       unicode-display_width (>= 1.1.1, < 3) |       unicode-display_width (>= 1.1.1, < 3) | ||||||
|     terrapin (0.6.0) |     terrapin (0.6.0) | ||||||
|       climate_control (>= 0.0.3, < 1.0) |       climate_control (>= 0.0.3, < 1.0) | ||||||
|     thor (1.2.1) |     thor (1.2.2) | ||||||
|     tilt (2.1.0) |     tilt (2.1.0) | ||||||
|     timeout (0.3.2) |     timeout (0.3.2) | ||||||
|     tpm-key_attestation (0.12.0) |     tpm-key_attestation (0.12.0) | ||||||
|  | @ -763,7 +761,7 @@ GEM | ||||||
|     xorcist (1.1.3) |     xorcist (1.1.3) | ||||||
|     xpath (3.2.0) |     xpath (3.2.0) | ||||||
|       nokogiri (~> 1.8) |       nokogiri (~> 1.8) | ||||||
|     zeitwerk (2.6.7) |     zeitwerk (2.6.8) | ||||||
| 
 | 
 | ||||||
| PLATFORMS | PLATFORMS | ||||||
|   ruby |   ruby | ||||||
|  | @ -772,7 +770,7 @@ DEPENDENCIES | ||||||
|   active_model_serializers (~> 0.10) |   active_model_serializers (~> 0.10) | ||||||
|   addressable (~> 2.8) |   addressable (~> 2.8) | ||||||
|   annotate (~> 3.2) |   annotate (~> 3.2) | ||||||
|   aws-sdk-s3 (~> 1.120) |   aws-sdk-s3 (~> 1.122) | ||||||
|   better_errors (~> 2.9) |   better_errors (~> 2.9) | ||||||
|   binding_of_caller (~> 1.0) |   binding_of_caller (~> 1.0) | ||||||
|   blurhash (~> 0.1) |   blurhash (~> 0.1) | ||||||
|  | @ -787,7 +785,7 @@ DEPENDENCIES | ||||||
|   capybara (~> 3.39) |   capybara (~> 3.39) | ||||||
|   charlock_holmes (~> 0.7.7) |   charlock_holmes (~> 0.7.7) | ||||||
|   chewy (~> 7.3) |   chewy (~> 7.3) | ||||||
|   climate_control |   climate_control (~> 0.2) | ||||||
|   cocoon (~> 1.2) |   cocoon (~> 1.2) | ||||||
|   color_diff (~> 0.1) |   color_diff (~> 0.1) | ||||||
|   concurrent-ruby |   concurrent-ruby | ||||||
|  | @ -862,11 +860,10 @@ DEPENDENCIES | ||||||
|   redcarpet (~> 3.6) |   redcarpet (~> 3.6) | ||||||
|   redis (~> 4.5) |   redis (~> 4.5) | ||||||
|   redis-namespace (~> 1.10) |   redis-namespace (~> 1.10) | ||||||
|   rqrcode (~> 2.1) |   rqrcode (~> 2.2) | ||||||
|   rspec-rails (~> 6.0) |   rspec-rails (~> 6.0) | ||||||
|   rspec-sidekiq (~> 3.1) |   rspec-sidekiq (~> 3.1) | ||||||
|   rspec_chunked (~> 0.6) |   rspec_chunked (~> 0.6) | ||||||
|   rspec_junit_formatter (~> 0.6) |  | ||||||
|   rubocop |   rubocop | ||||||
|   rubocop-capybara |   rubocop-capybara | ||||||
|   rubocop-performance |   rubocop-performance | ||||||
|  |  | ||||||
|  | @ -58,7 +58,7 @@ class Api::V1::Admin::CanonicalEmailBlocksController < Api::BaseController | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def set_canonical_email_blocks_from_test |   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 |   end | ||||||
| 
 | 
 | ||||||
|   def set_canonical_email_block |   def set_canonical_email_block | ||||||
|  |  | ||||||
|  | @ -29,7 +29,7 @@ class Api::V1::Admin::DomainAllowsController < Api::BaseController | ||||||
|   def create |   def create | ||||||
|     authorize :domain_allow, :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? |     if @domain_allow.nil? | ||||||
|       @domain_allow = DomainAllow.create!(resource_params) |       @domain_allow = DomainAllow.create!(resource_params) | ||||||
|  |  | ||||||
|  | @ -1,9 +1,10 @@ | ||||||
| # frozen_string_literal: true | # frozen_string_literal: true | ||||||
| 
 | 
 | ||||||
| class Api::V1::Emails::ConfirmationsController < Api::BaseController | class Api::V1::Emails::ConfirmationsController < Api::BaseController | ||||||
|   before_action -> { doorkeeper_authorize! :write, :'write:accounts' } |   before_action -> { authorize_if_got_token! :read, :'read:accounts' }, only: :check | ||||||
|   before_action :require_user_owned_by_application! |   before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, except: :check | ||||||
|   before_action :require_user_not_confirmed! |   before_action :require_user_owned_by_application!, except: :check | ||||||
|  |   before_action :require_user_not_confirmed!, except: :check | ||||||
| 
 | 
 | ||||||
|   def create |   def create | ||||||
|     current_user.update!(email: params[:email]) if params.key?(:email) |     current_user.update!(email: params[:email]) if params.key?(:email) | ||||||
|  | @ -12,6 +13,10 @@ class Api::V1::Emails::ConfirmationsController < Api::BaseController | ||||||
|     render_empty |     render_empty | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |   def check | ||||||
|  |     render json: current_user.confirmed? | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   private |   private | ||||||
| 
 | 
 | ||||||
|   def require_user_owned_by_application! |   def require_user_owned_by_application! | ||||||
|  |  | ||||||
|  | @ -13,7 +13,7 @@ class Api::V1::FeaturedTagsController < Api::BaseController | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def create |   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 |     render json: featured_tag, serializer: REST::FeaturedTagSerializer | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  | @ -33,6 +33,6 @@ class Api::V1::FeaturedTagsController < Api::BaseController | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def featured_tag_params |   def featured_tag_params | ||||||
|     params.permit(:name) |     params.require(:name) | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -2,6 +2,8 @@ | ||||||
| 
 | 
 | ||||||
| class Api::V1::Statuses::ReblogsController < Api::BaseController | class Api::V1::Statuses::ReblogsController < Api::BaseController | ||||||
|   include Authorization |   include Authorization | ||||||
|  |   include Redisable | ||||||
|  |   include Lockable | ||||||
| 
 | 
 | ||||||
|   before_action -> { doorkeeper_authorize! :write, :'write:statuses' } |   before_action -> { doorkeeper_authorize! :write, :'write:statuses' } | ||||||
|   before_action :require_user! |   before_action :require_user! | ||||||
|  | @ -10,7 +12,9 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController | ||||||
|   override_rate_limit_headers :create, family: :statuses |   override_rate_limit_headers :create, family: :statuses | ||||||
| 
 | 
 | ||||||
|   def create |   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 |     render json: @status, serializer: REST::StatusSerializer | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  | @ -132,7 +132,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def set_sessions |   def set_sessions | ||||||
|     @sessions = current_user.session_activations |     @sessions = current_user.session_activations.order(updated_at: :desc) | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def set_strikes |   def set_strikes | ||||||
|  |  | ||||||
|  | @ -45,6 +45,6 @@ class Auth::SetupController < ApplicationController | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def set_pack |   def set_pack | ||||||
|     use_pack 'auth' |     use_pack 'sign_up' | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -10,6 +10,8 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio | ||||||
|   before_action :set_body_classes |   before_action :set_body_classes | ||||||
|   before_action :set_cache_headers |   before_action :set_cache_headers | ||||||
| 
 | 
 | ||||||
|  |   before_action :set_last_used_at_by_app, only: :index, unless: -> { request.format == :json } | ||||||
|  | 
 | ||||||
|   skip_before_action :require_functional! |   skip_before_action :require_functional! | ||||||
| 
 | 
 | ||||||
|   include Localized |   include Localized | ||||||
|  | @ -40,4 +42,14 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio | ||||||
|   def set_cache_headers |   def set_cache_headers | ||||||
|     response.cache_control.replace(private: true, no_store: true) |     response.cache_control.replace(private: true, no_store: true) | ||||||
|   end |   end | ||||||
|  | 
 | ||||||
|  |   def 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 | end | ||||||
|  |  | ||||||
|  | @ -16,4 +16,5 @@ pack: | ||||||
|   modal: public.js |   modal: public.js | ||||||
|   public: public.js |   public: public.js | ||||||
|   settings: settings.js |   settings: settings.js | ||||||
|  |   sign_up: | ||||||
|   share: |   share: | ||||||
|  |  | ||||||
|  | @ -1,8 +1,9 @@ | ||||||
| import { createAction } from '@reduxjs/toolkit'; | import { createAction } from '@reduxjs/toolkit'; | ||||||
|  | 
 | ||||||
| import type { LayoutType } from '../is_mobile'; | import type { LayoutType } from '../is_mobile'; | ||||||
| 
 | 
 | ||||||
| type ChangeLayoutPayload = { | interface ChangeLayoutPayload { | ||||||
|   layout: LayoutType; |   layout: LayoutType; | ||||||
| }; | } | ||||||
| export const changeLayout = | export const changeLayout = | ||||||
|   createAction<ChangeLayoutPayload>('APP_LAYOUT_CHANGE'); |   createAction<ChangeLayoutPayload>('APP_LAYOUT_CHANGE'); | ||||||
|  |  | ||||||
|  | @ -1,12 +1,12 @@ | ||||||
| import api from '../api'; | import api from '../api'; | ||||||
| import { importFetchedStatuses } from './importer'; | 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_REQUEST = 'PINNED_STATUSES_FETCH_REQUEST'; | ||||||
| export const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS'; | export const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS'; | ||||||
| export const PINNED_STATUSES_FETCH_FAIL = 'PINNED_STATUSES_FETCH_FAIL'; | export const PINNED_STATUSES_FETCH_FAIL = 'PINNED_STATUSES_FETCH_FAIL'; | ||||||
| 
 | 
 | ||||||
| import { me } from 'flavours/glitch/initial_state'; |  | ||||||
| 
 |  | ||||||
| export function fetchPinnedStatuses() { | export function fetchPinnedStatuses() { | ||||||
|   return (dispatch, getState) => { |   return (dispatch, getState) => { | ||||||
|     dispatch(fetchPinnedStatusesRequest()); |     dispatch(fetchPinnedStatusesRequest()); | ||||||
|  |  | ||||||
|  | @ -2,14 +2,14 @@ import React, { Fragment } from 'react'; | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { Avatar } from './avatar'; | import { Avatar } from './avatar'; | ||||||
| import DisplayName from './display_name'; | import { DisplayName } from './display_name'; | ||||||
| import Permalink from './permalink'; | import Permalink from './permalink'; | ||||||
| import { IconButton } from './icon_button'; | import { IconButton } from './icon_button'; | ||||||
| import { defineMessages, injectIntl } from 'react-intl'; | import { defineMessages, injectIntl } from 'react-intl'; | ||||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||||
| import { me } from 'flavours/glitch/initial_state'; | import { me } from 'flavours/glitch/initial_state'; | ||||||
| import { RelativeTimestamp } from './relative_timestamp'; | import { RelativeTimestamp } from './relative_timestamp'; | ||||||
| import Skeleton from 'flavours/glitch/components/skeleton'; | import { Skeleton } from 'flavours/glitch/components/skeleton'; | ||||||
| 
 | 
 | ||||||
| const messages = defineMessages({ | const messages = defineMessages({ | ||||||
|   follow: { id: 'account.follow', defaultMessage: 'Follow' }, |   follow: { id: 'account.follow', defaultMessage: 'Follow' }, | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ import api from 'flavours/glitch/api'; | ||||||
| import { FormattedNumber } from 'react-intl'; | import { FormattedNumber } from 'react-intl'; | ||||||
| import { Sparklines, SparklinesCurve } from 'react-sparklines'; | import { Sparklines, SparklinesCurve } from 'react-sparklines'; | ||||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||||
| import Skeleton from 'flavours/glitch/components/skeleton'; | import { Skeleton } from 'flavours/glitch/components/skeleton'; | ||||||
| 
 | 
 | ||||||
| const percIncrease = (a, b) => { | const percIncrease = (a, b) => { | ||||||
|   let percent; |   let percent; | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ import PropTypes from 'prop-types'; | ||||||
| import api from 'flavours/glitch/api'; | import api from 'flavours/glitch/api'; | ||||||
| import { FormattedNumber } from 'react-intl'; | import { FormattedNumber } from 'react-intl'; | ||||||
| import { roundTo10 } from 'flavours/glitch/utils/numbers'; | 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 { | export default class Dimension extends React.PureComponent { | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,8 +1,11 @@ | ||||||
| import React, { useCallback, useState } from 'react'; | import React, { useCallback, useState } from 'react'; | ||||||
| import ShortNumber from './short_number'; | 
 | ||||||
| import { TransitionMotion, spring } from 'react-motion'; | import { TransitionMotion, spring } from 'react-motion'; | ||||||
|  | 
 | ||||||
| import { reduceMotion } from '../initial_state'; | import { reduceMotion } from '../initial_state'; | ||||||
| 
 | 
 | ||||||
|  | import ShortNumber from './short_number'; | ||||||
|  | 
 | ||||||
| const obfuscatedCount = (count: number) => { | const obfuscatedCount = (count: number) => { | ||||||
|   if (count < 0) { |   if (count < 0) { | ||||||
|     return 0; |     return 0; | ||||||
|  | @ -13,10 +16,10 @@ const obfuscatedCount = (count: number) => { | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| type Props = { | interface Props { | ||||||
|   value: number; |   value: number; | ||||||
|   obfuscate?: boolean; |   obfuscate?: boolean; | ||||||
| }; | } | ||||||
| export const AnimatedNumber: React.FC<Props> = ({ value, obfuscate }) => { | export const AnimatedNumber: React.FC<Props> = ({ value, obfuscate }) => { | ||||||
|   const [previousValue, setPreviousValue] = useState(value); |   const [previousValue, setPreviousValue] = useState(value); | ||||||
|   const [direction, setDirection] = useState<1 | -1>(1); |   const [direction, setDirection] = useState<1 | -1>(1); | ||||||
|  | @ -64,7 +67,11 @@ export const AnimatedNumber: React.FC<Props> = ({ value, obfuscate }) => { | ||||||
|                 transform: `translateY(${style.y * 100}%)`, |                 transform: `translateY(${style.y * 100}%)`, | ||||||
|               }} |               }} | ||||||
|             > |             > | ||||||
|               {obfuscate ? obfuscatedCount(data) : <ShortNumber value={data} />} |               {obfuscate ? ( | ||||||
|  |                 obfuscatedCount(data as number) | ||||||
|  |               ) : ( | ||||||
|  |                 <ShortNumber value={data as number} /> | ||||||
|  |               )} | ||||||
|             </span> |             </span> | ||||||
|           ))} |           ))} | ||||||
|         </span> |         </span> | ||||||
|  |  | ||||||
|  | @ -154,7 +154,7 @@ export default class AutosuggestInput extends ImmutablePureComponent { | ||||||
|     this.input.focus(); |     this.input.focus(); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   componentWillReceiveProps (nextProps) { |   UNSAFE_componentWillReceiveProps (nextProps) { | ||||||
|     if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) { |     if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) { | ||||||
|       this.setState({ suggestionsHidden: false }); |       this.setState({ suggestionsHidden: false }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -153,7 +153,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent { | ||||||
|     this.textarea.focus(); |     this.textarea.focus(); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   componentWillReceiveProps (nextProps) { |   UNSAFE_componentWillReceiveProps (nextProps) { | ||||||
|     if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) { |     if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden && this.state.focused) { | ||||||
|       this.setState({ suggestionsHidden: false }); |       this.setState({ suggestionsHidden: false }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -1,16 +1,18 @@ | ||||||
| import * as React from 'react'; | import * as React from 'react'; | ||||||
|  | 
 | ||||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||||
| import { autoPlayGif } from 'flavours/glitch/initial_state'; | 
 | ||||||
| import { useHovering } from 'flavours/glitch/hooks/useHovering'; | import { useHovering } from 'flavours/glitch/hooks/useHovering'; | ||||||
|  | import { autoPlayGif } from 'flavours/glitch/initial_state'; | ||||||
| import type { Account } from 'flavours/glitch/types/resources'; | import type { Account } from 'flavours/glitch/types/resources'; | ||||||
| 
 | 
 | ||||||
| type Props = { | interface Props { | ||||||
|   account: Account | undefined; |   account: Account | undefined; | ||||||
|   className?: string; |   className?: string; | ||||||
|   size: number; |   size: number; | ||||||
|   style?: React.CSSProperties; |   style?: React.CSSProperties; | ||||||
|   inline?: boolean; |   inline?: boolean; | ||||||
| }; | } | ||||||
| 
 | 
 | ||||||
| export const Avatar: React.FC<Props> = ({ | export const Avatar: React.FC<Props> = ({ | ||||||
|   account, |   account, | ||||||
|  |  | ||||||
|  | @ -1,14 +1,14 @@ | ||||||
| import { decode } from 'blurhash'; |  | ||||||
| import React, { useRef, useEffect } from 'react'; | import React, { useRef, useEffect } from 'react'; | ||||||
| 
 | 
 | ||||||
| type Props = { | import { decode } from 'blurhash'; | ||||||
|  | 
 | ||||||
|  | interface Props extends React.HTMLAttributes<HTMLCanvasElement> { | ||||||
|   hash: string; |   hash: string; | ||||||
|   width?: number; |   width?: number; | ||||||
|   height?: number; |   height?: number; | ||||||
|   dummy?: boolean; // Whether dummy mode is enabled. If enabled, nothing is rendered and canvas left untouched
 |   dummy?: boolean; // Whether dummy mode is enabled. If enabled, nothing is rendered and canvas left untouched
 | ||||||
|   children?: never; |   children?: never; | ||||||
|   [key: string]: any; | } | ||||||
| }; |  | ||||||
| const Blurhash: React.FC<Props> = ({ | const Blurhash: React.FC<Props> = ({ | ||||||
|   hash, |   hash, | ||||||
|   width = 32, |   width = 32, | ||||||
|  | @ -21,6 +21,7 @@ const Blurhash: React.FC<Props> = ({ | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
 |     // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
 | ||||||
|     const canvas = canvasRef.current!; |     const canvas = canvasRef.current!; | ||||||
|  | 
 | ||||||
|     // eslint-disable-next-line no-self-assign
 |     // eslint-disable-next-line no-self-assign
 | ||||||
|     canvas.width = canvas.width; // resets canvas
 |     canvas.width = canvas.width; // resets canvas
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,6 +3,8 @@ import PropTypes from 'prop-types'; | ||||||
| import { supportsPassiveEvents } from 'detect-passive-events'; | import { supportsPassiveEvents } from 'detect-passive-events'; | ||||||
| import { scrollTop } from '../scroll'; | import { scrollTop } from '../scroll'; | ||||||
| 
 | 
 | ||||||
|  | const listenerOptions = supportsPassiveEvents ? { passive: true } : false; | ||||||
|  | 
 | ||||||
| export default class Column extends React.PureComponent { | export default class Column extends React.PureComponent { | ||||||
| 
 | 
 | ||||||
|   static propTypes = { |   static propTypes = { | ||||||
|  | @ -37,17 +39,17 @@ export default class Column extends React.PureComponent { | ||||||
| 
 | 
 | ||||||
|   componentDidMount () { |   componentDidMount () { | ||||||
|     if (this.props.bindToDocument) { |     if (this.props.bindToDocument) { | ||||||
|       document.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false); |       document.addEventListener('wheel', this.handleWheel, listenerOptions); | ||||||
|     } else { |     } else { | ||||||
|       this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : false); |       this.node.addEventListener('wheel', this.handleWheel, listenerOptions); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   componentWillUnmount () { |   componentWillUnmount () { | ||||||
|     if (this.props.bindToDocument) { |     if (this.props.bindToDocument) { | ||||||
|       document.removeEventListener('wheel', this.handleWheel); |       document.removeEventListener('wheel', this.handleWheel, listenerOptions); | ||||||
|     } else { |     } 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 React, { useCallback } from 'react'; | ||||||
|  | 
 | ||||||
|  | import type { InjectedIntl } from 'react-intl'; | ||||||
|  | import { defineMessages, injectIntl } from 'react-intl'; | ||||||
|  | 
 | ||||||
| import { IconButton } from './icon_button'; | import { IconButton } from './icon_button'; | ||||||
| import { InjectedIntl, defineMessages, injectIntl } from 'react-intl'; |  | ||||||
| 
 | 
 | ||||||
| const messages = defineMessages({ | const messages = defineMessages({ | ||||||
|   unblockDomain: { |   unblockDomain: { | ||||||
|  | @ -9,11 +12,11 @@ const messages = defineMessages({ | ||||||
|   }, |   }, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| type Props = { | interface Props { | ||||||
|   domain: string; |   domain: string; | ||||||
|   onUnblockDomain: (domain: string) => void; |   onUnblockDomain: (domain: string) => void; | ||||||
|   intl: InjectedIntl; |   intl: InjectedIntl; | ||||||
| }; | } | ||||||
| const _Domain: React.FC<Props> = ({ domain, onUnblockDomain, intl }) => { | const _Domain: React.FC<Props> = ({ domain, onUnblockDomain, intl }) => { | ||||||
|   const handleDomainUnblock = useCallback(() => { |   const handleDomainUnblock = useCallback(() => { | ||||||
|     onUnblockDomain(domain); |     onUnblockDomain(domain); | ||||||
|  |  | ||||||
|  | @ -7,7 +7,7 @@ import { supportsPassiveEvents } from 'detect-passive-events'; | ||||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||||
| import { CircularProgress } from 'flavours/glitch/components/loading_indicator'; | 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; | let id = 0; | ||||||
| 
 | 
 | ||||||
| class DropdownMenu extends React.PureComponent { | class DropdownMenu extends React.PureComponent { | ||||||
|  | @ -35,12 +35,13 @@ class DropdownMenu extends React.PureComponent { | ||||||
|   handleDocumentClick = e => { |   handleDocumentClick = e => { | ||||||
|     if (this.node && !this.node.contains(e.target)) { |     if (this.node && !this.node.contains(e.target)) { | ||||||
|       this.props.onClose(); |       this.props.onClose(); | ||||||
|  |       e.stopPropagation(); | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   componentDidMount () { |   componentDidMount () { | ||||||
|     document.addEventListener('click', this.handleDocumentClick, false); |     document.addEventListener('click', this.handleDocumentClick, { capture: true }); | ||||||
|     document.addEventListener('keydown', this.handleKeyDown, false); |     document.addEventListener('keydown', this.handleKeyDown, { capture: true }); | ||||||
|     document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); |     document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); | ||||||
| 
 | 
 | ||||||
|     if (this.focusedItem && this.props.openedViaKeyboard) { |     if (this.focusedItem && this.props.openedViaKeyboard) { | ||||||
|  | @ -49,8 +50,8 @@ class DropdownMenu extends React.PureComponent { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   componentWillUnmount () { |   componentWillUnmount () { | ||||||
|     document.removeEventListener('click', this.handleDocumentClick, false); |     document.removeEventListener('click', this.handleDocumentClick, { capture: true }); | ||||||
|     document.removeEventListener('keydown', this.handleKeyDown, false); |     document.removeEventListener('keydown', this.handleKeyDown, { capture: true }); | ||||||
|     document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); |     document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import React, { useCallback, useState } from 'react'; | import React, { useCallback, useState } from 'react'; | ||||||
| 
 | 
 | ||||||
| type Props = { | interface Props { | ||||||
|   src: string; |   src: string; | ||||||
|   key: string; |   key: string; | ||||||
|   alt?: string; |   alt?: string; | ||||||
|  | @ -8,7 +8,7 @@ type Props = { | ||||||
|   width: number; |   width: number; | ||||||
|   height: number; |   height: number; | ||||||
|   onClick?: () => void; |   onClick?: () => void; | ||||||
| }; | } | ||||||
| 
 | 
 | ||||||
| export const GIFV: React.FC<Props> = ({ | export const GIFV: React.FC<Props> = ({ | ||||||
|   src, |   src, | ||||||
|  |  | ||||||
|  | @ -6,7 +6,7 @@ import PropTypes from 'prop-types'; | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import Permalink from './permalink'; | import Permalink from './permalink'; | ||||||
| import ShortNumber from 'flavours/glitch/components/short_number'; | 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'; | import classNames from 'classnames'; | ||||||
| 
 | 
 | ||||||
| class SilentErrorBoundary extends React.Component { | class SilentErrorBoundary extends React.Component { | ||||||
|  |  | ||||||
|  | @ -1,13 +1,14 @@ | ||||||
| import React from 'react'; | import React from 'react'; | ||||||
|  | 
 | ||||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||||
| 
 | 
 | ||||||
| type Props = { | interface Props extends React.HTMLAttributes<HTMLImageElement> { | ||||||
|   id: string; |   id: string; | ||||||
|   className?: string; |   className?: string; | ||||||
|   fixedWidth?: boolean; |   fixedWidth?: boolean; | ||||||
|   children?: never; |   children?: never; | ||||||
|   [key: string]: any; | } | ||||||
| }; | 
 | ||||||
| export const Icon: React.FC<Props> = ({ | export const Icon: React.FC<Props> = ({ | ||||||
|   id, |   id, | ||||||
|   className, |   className, | ||||||
|  |  | ||||||
|  | @ -1,9 +1,11 @@ | ||||||
| import React from 'react'; | 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; |   className?: string; | ||||||
|   title: string; |   title: string; | ||||||
|   icon: string; |   icon: string; | ||||||
|  | @ -26,11 +28,11 @@ type Props = { | ||||||
|   obfuscateCount?: boolean; |   obfuscateCount?: boolean; | ||||||
|   href?: string; |   href?: string; | ||||||
|   ariaHidden: boolean; |   ariaHidden: boolean; | ||||||
| }; | } | ||||||
| type States = { | interface States { | ||||||
|   activate: boolean; |   activate: boolean; | ||||||
|   deactivate: boolean; |   deactivate: boolean; | ||||||
| }; | } | ||||||
| export class IconButton extends React.PureComponent<Props, States> { | export class IconButton extends React.PureComponent<Props, States> { | ||||||
|   static defaultProps = { |   static defaultProps = { | ||||||
|     size: 18, |     size: 18, | ||||||
|  |  | ||||||
|  | @ -1,14 +1,15 @@ | ||||||
| import React from 'react'; | import React from 'react'; | ||||||
|  | 
 | ||||||
| import { Icon } from './icon'; | import { Icon } from './icon'; | ||||||
| 
 | 
 | ||||||
| const formatNumber = (num: number): number | string => (num > 40 ? '40+' : num); | const formatNumber = (num: number): number | string => (num > 40 ? '40+' : num); | ||||||
| 
 | 
 | ||||||
| type Props = { | interface Props { | ||||||
|   id: string; |   id: string; | ||||||
|   count: number; |   count: number; | ||||||
|   issueBadge: boolean; |   issueBadge: boolean; | ||||||
|   className: string; |   className: string; | ||||||
| }; | } | ||||||
| export const IconWithBadge: React.FC<Props> = ({ | export const IconWithBadge: React.FC<Props> = ({ | ||||||
|   id, |   id, | ||||||
|   count, |   count, | ||||||
|  |  | ||||||
|  | @ -254,7 +254,7 @@ class MediaGallery extends React.PureComponent { | ||||||
|     window.removeEventListener('resize', this.handleResize); |     window.removeEventListener('resize', this.handleResize); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   componentWillReceiveProps (nextProps) { |   UNSAFE_componentWillReceiveProps (nextProps) { | ||||||
|     if (!is(nextProps.media, this.props.media) && nextProps.visible === undefined) { |     if (!is(nextProps.media, this.props.media) && nextProps.visible === undefined) { | ||||||
|       this.setState({ visible: displayMedia !== 'hide_all' && !nextProps.sensitive || displayMedia === 'show_all' }); |       this.setState({ visible: displayMedia !== 'hide_all' && !nextProps.sensitive || displayMedia === 'show_all' }); | ||||||
|     } else if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) { |     } else if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) { | ||||||
|  | @ -286,7 +286,7 @@ class MediaGallery extends React.PureComponent { | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   handleClick = (index) => { |   handleClick = (index) => { | ||||||
|     this.props.onOpenMedia(this.props.media, index); |     this.props.onOpenMedia(this.props.media, index, this.props.lang); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   handleRef = (node) => { |   handleRef = (node) => { | ||||||
|  |  | ||||||
|  | @ -62,7 +62,7 @@ export default class ModalRoot extends React.PureComponent { | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   componentWillReceiveProps (nextProps) { |   UNSAFE_componentWillReceiveProps (nextProps) { | ||||||
|     if (!!nextProps.children && !this.props.children) { |     if (!!nextProps.children && !this.props.children) { | ||||||
|       this.activeElement = document.activeElement; |       this.activeElement = document.activeElement; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,4 +1,5 @@ | ||||||
| import React from 'react'; | import React from 'react'; | ||||||
|  | 
 | ||||||
| import { FormattedMessage } from 'react-intl'; | import { FormattedMessage } from 'react-intl'; | ||||||
| 
 | 
 | ||||||
| export const NotSignedInIndicator: React.FC = () => ( | export const NotSignedInIndicator: React.FC = () => ( | ||||||
|  | @ -6,7 +7,7 @@ export const NotSignedInIndicator: React.FC = () => ( | ||||||
|     <div className='empty-column-indicator'> |     <div className='empty-column-indicator'> | ||||||
|       <FormattedMessage |       <FormattedMessage | ||||||
|         id='not_signed_in_indicator.not_signed_in' |         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> | ||||||
|   </div> |   </div> | ||||||
|  |  | ||||||
|  | @ -1,13 +1,14 @@ | ||||||
| import React from 'react'; | import React from 'react'; | ||||||
|  | 
 | ||||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||||
| 
 | 
 | ||||||
| type Props = { | interface Props { | ||||||
|   value: string; |   value: string; | ||||||
|   checked: boolean; |   checked: boolean; | ||||||
|   name: string; |   name: string; | ||||||
|   onChange: (event: React.ChangeEvent<HTMLInputElement>) => void; |   onChange: (event: React.ChangeEvent<HTMLInputElement>) => void; | ||||||
|   label: React.ReactNode; |   label: React.ReactNode; | ||||||
| }; | } | ||||||
| 
 | 
 | ||||||
| export const RadioButton: React.FC<Props> = ({ | export const RadioButton: React.FC<Props> = ({ | ||||||
|   name, |   name, | ||||||
|  |  | ||||||
|  | @ -1,5 +1,7 @@ | ||||||
| import React from 'react'; | 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({ | const messages = defineMessages({ | ||||||
|   today: { id: 'relative_time.today', defaultMessage: 'today' }, |   today: { id: 'relative_time.today', defaultMessage: 'today' }, | ||||||
|  | @ -187,16 +189,16 @@ const timeRemainingString = ( | ||||||
|   return relativeTime; |   return relativeTime; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| type Props = { | interface Props { | ||||||
|   intl: InjectedIntl; |   intl: InjectedIntl; | ||||||
|   timestamp: string; |   timestamp: string; | ||||||
|   year: number; |   year: number; | ||||||
|   futureDate?: boolean; |   futureDate?: boolean; | ||||||
|   short?: boolean; |   short?: boolean; | ||||||
| }; | } | ||||||
| type States = { | interface States { | ||||||
|   now: number; |   now: number; | ||||||
| }; | } | ||||||
| class RelativeTimestamp extends React.Component<Props, States> { | class RelativeTimestamp extends React.Component<Props, States> { | ||||||
|   state = { |   state = { | ||||||
|     now: this.props.intl.now(), |     now: this.props.intl.now(), | ||||||
|  |  | ||||||
|  | @ -15,6 +15,8 @@ import { connect } from 'react-redux'; | ||||||
| 
 | 
 | ||||||
| const MOUSE_IDLE_DELAY = 300; | const MOUSE_IDLE_DELAY = 300; | ||||||
| 
 | 
 | ||||||
|  | const listenerOptions = supportsPassiveEvents ? { passive: true } : false; | ||||||
|  | 
 | ||||||
| const mapStateToProps = (state, { scrollKey }) => { | const mapStateToProps = (state, { scrollKey }) => { | ||||||
|   return { |   return { | ||||||
|     preventScroll: scrollKey === state.getIn(['dropdown_menu', 'scroll_key']), |     preventScroll: scrollKey === state.getIn(['dropdown_menu', 'scroll_key']), | ||||||
|  | @ -237,20 +239,20 @@ class ScrollableList extends PureComponent { | ||||||
|   attachScrollListener () { |   attachScrollListener () { | ||||||
|     if (this.props.bindToDocument) { |     if (this.props.bindToDocument) { | ||||||
|       document.addEventListener('scroll', this.handleScroll); |       document.addEventListener('scroll', this.handleScroll); | ||||||
|       document.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : undefined); |       document.addEventListener('wheel', this.handleWheel,  listenerOptions); | ||||||
|     } else { |     } else { | ||||||
|       this.node.addEventListener('scroll', this.handleScroll); |       this.node.addEventListener('scroll', this.handleScroll); | ||||||
|       this.node.addEventListener('wheel', this.handleWheel, supportsPassiveEvents ? { passive: true } : undefined); |       this.node.addEventListener('wheel', this.handleWheel, listenerOptions); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   detachScrollListener () { |   detachScrollListener () { | ||||||
|     if (this.props.bindToDocument) { |     if (this.props.bindToDocument) { | ||||||
|       document.removeEventListener('scroll', this.handleScroll); |       document.removeEventListener('scroll', this.handleScroll); | ||||||
|       document.removeEventListener('wheel', this.handleWheel); |       document.removeEventListener('wheel', this.handleWheel, listenerOptions); | ||||||
|     } else { |     } else { | ||||||
|       this.node.removeEventListener('scroll', this.handleScroll); |       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 { connect } from 'react-redux'; | ||||||
| import { fetchServer } from 'flavours/glitch/actions/server'; | import { fetchServer } from 'flavours/glitch/actions/server'; | ||||||
| import ShortNumber from 'flavours/glitch/components/short_number'; | 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 Account from 'flavours/glitch/containers/account_container'; | ||||||
| import { domain } from 'flavours/glitch/initial_state'; | 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'; | import { Link } from 'react-router-dom'; | ||||||
| 
 | 
 | ||||||
| const messages = defineMessages({ | 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> }} /> |           <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> |         </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'> |         <div className='server-banner__description'> | ||||||
|           {isLoading ? ( |           {isLoading ? ( | ||||||
|  |  | ||||||
|  | @ -1,15 +1,17 @@ | ||||||
| import React, { useCallback, useState } from 'react'; | import React, { useCallback, useState } from 'react'; | ||||||
| import { Blurhash } from './blurhash'; | 
 | ||||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||||
| 
 | 
 | ||||||
| type Props = { | import { Blurhash } from './blurhash'; | ||||||
|  | 
 | ||||||
|  | interface Props { | ||||||
|   src: string; |   src: string; | ||||||
|   srcSet?: string; |   srcSet?: string; | ||||||
|   blurhash?: string; |   blurhash?: string; | ||||||
|   className?: string; |   className?: string; | ||||||
| }; | } | ||||||
| 
 | 
 | ||||||
| export const Image: React.FC<Props> = ({ | export const ServerHeroImage: React.FC<Props> = ({ | ||||||
|   src, |   src, | ||||||
|   srcSet, |   srcSet, | ||||||
|   blurhash, |   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) => { |   handleOpenVideo = (options) => { | ||||||
|     const { status } = this.props; |     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) => { |   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 => { |   handleHotkeyOpenMedia = e => { | ||||||
|  |  | ||||||
|  | @ -6,7 +6,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| //  Mastodon imports. | //  Mastodon imports. | ||||||
| import { Avatar } from './avatar'; | import { Avatar } from './avatar'; | ||||||
| import AvatarOverlay from './avatar_overlay'; | import AvatarOverlay from './avatar_overlay'; | ||||||
| import DisplayName from './display_name'; | import { DisplayName } from './display_name'; | ||||||
| 
 | 
 | ||||||
| export default class StatusHeader extends React.PureComponent { | export default class StatusHeader extends React.PureComponent { | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -26,6 +26,7 @@ export default class StatusList extends ImmutablePureComponent { | ||||||
|     alwaysPrepend: PropTypes.bool, |     alwaysPrepend: PropTypes.bool, | ||||||
|     withCounters: PropTypes.bool, |     withCounters: PropTypes.bool, | ||||||
|     timelineId: PropTypes.string.isRequired, |     timelineId: PropTypes.string.isRequired, | ||||||
|  |     lastId: PropTypes.string, | ||||||
|     regex: PropTypes.string, |     regex: PropTypes.string, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|  | @ -56,7 +57,8 @@ export default class StatusList extends ImmutablePureComponent { | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   handleLoadOlder = debounce(() => { |   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 }); |   }, 300, { leading: true }); | ||||||
| 
 | 
 | ||||||
|   _selectChild (index, align_top) { |   _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 React, { PureComponent, Fragment } from 'react'; | ||||||
| import ReactDOM from 'react-dom'; | import { createPortal } from 'react-dom'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { IntlProvider, addLocaleData } from 'react-intl'; | import { IntlProvider, addLocaleData } from 'react-intl'; | ||||||
| import { fromJS } from 'immutable'; | import { fromJS } from 'immutable'; | ||||||
|  | @ -29,19 +29,20 @@ export default class MediaContainer extends PureComponent { | ||||||
|   state = { |   state = { | ||||||
|     media: null, |     media: null, | ||||||
|     index: null, |     index: null, | ||||||
|  |     lang: null, | ||||||
|     time: null, |     time: null, | ||||||
|     backgroundColor: null, |     backgroundColor: null, | ||||||
|     options: null, |     options: null, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   handleOpenMedia = (media, index) => { |   handleOpenMedia = (media, index, lang) => { | ||||||
|     document.body.classList.add('with-modals--active'); |     document.body.classList.add('with-modals--active'); | ||||||
|     document.documentElement.style.marginRight = `${getScrollbarWidth()}px`; |     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 { components } = this.props; | ||||||
|     const { media } = JSON.parse(components[options.componentIndex].getAttribute('data-props')); |     const { media } = JSON.parse(components[options.componentIndex].getAttribute('data-props')); | ||||||
|     const mediaList = fromJS(media); |     const mediaList = fromJS(media); | ||||||
|  | @ -49,7 +50,7 @@ export default class MediaContainer extends PureComponent { | ||||||
|     document.body.classList.add('with-modals--active'); |     document.body.classList.add('with-modals--active'); | ||||||
|     document.documentElement.style.marginRight = `${getScrollbarWidth()}px`; |     document.documentElement.style.marginRight = `${getScrollbarWidth()}px`; | ||||||
| 
 | 
 | ||||||
|     this.setState({ media: mediaList, options }); |     this.setState({ media: mediaList, lang, options }); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   handleCloseMedia = () => { |   handleCloseMedia = () => { | ||||||
|  | @ -94,7 +95,7 @@ export default class MediaContainer extends PureComponent { | ||||||
|               }), |               }), | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|             return ReactDOM.createPortal( |             return createPortal( | ||||||
|               <Component {...props} key={`media-${i}`} />, |               <Component {...props} key={`media-${i}`} />, | ||||||
|               component, |               component, | ||||||
|             ); |             ); | ||||||
|  | @ -105,6 +106,7 @@ export default class MediaContainer extends PureComponent { | ||||||
|               <MediaModal |               <MediaModal | ||||||
|                 media={this.state.media} |                 media={this.state.media} | ||||||
|                 index={this.state.index || 0} |                 index={this.state.index || 0} | ||||||
|  |                 lang={this.state.lang} | ||||||
|                 currentTime={this.state.options?.startTime} |                 currentTime={this.state.options?.startTime} | ||||||
|                 autoPlay={this.state.options?.autoPlay} |                 autoPlay={this.state.options?.autoPlay} | ||||||
|                 volume={this.state.options?.defaultVolume} |                 volume={this.state.options?.defaultVolume} | ||||||
|  |  | ||||||
|  | @ -211,12 +211,12 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({ | ||||||
|     dispatch(mentionCompose(account, router)); |     dispatch(mentionCompose(account, router)); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   onOpenMedia (statusId, media, index) { |   onOpenMedia (statusId, media, index, lang) { | ||||||
|     dispatch(openModal('MEDIA', { statusId, media, index })); |     dispatch(openModal('MEDIA', { statusId, media, index, lang })); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   onOpenVideo (statusId, media, options) { |   onOpenVideo (statusId, media, lang, options) { | ||||||
|     dispatch(openModal('VIDEO', { statusId, media, options })); |     dispatch(openModal('VIDEO', { statusId, media, lang, options })); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   onBlock (status) { |   onBlock (status) { | ||||||
|  |  | ||||||
|  | @ -8,10 +8,10 @@ import LinkFooter from 'flavours/glitch/features/ui/components/link_footer'; | ||||||
| import { Helmet } from 'react-helmet'; | import { Helmet } from 'react-helmet'; | ||||||
| import { fetchServer, fetchExtendedDescription, fetchDomainBlocks } from 'flavours/glitch/actions/server'; | import { fetchServer, fetchExtendedDescription, fetchDomainBlocks } from 'flavours/glitch/actions/server'; | ||||||
| import Account from 'flavours/glitch/containers/account_container'; | 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 { Icon } from 'flavours/glitch/components/icon'; | ||||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||||
| import { Image } from 'flavours/glitch/components/image'; | import { ServerHeroImage } from 'flavours/glitch/components/server_hero_image'; | ||||||
| 
 | 
 | ||||||
| const messages = defineMessages({ | const messages = defineMessages({ | ||||||
|   title: { id: 'column.about', defaultMessage: 'About' }, |   title: { id: 'column.about', defaultMessage: 'About' }, | ||||||
|  | @ -114,7 +114,7 @@ class About extends React.PureComponent { | ||||||
|       <Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.title)}> |       <Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.title)}> | ||||||
|         <div className='scrollable about'> |         <div className='scrollable about'> | ||||||
|           <div className='about__header'> |           <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> |             <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> |             <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> |           </div> | ||||||
|  |  | ||||||
|  | @ -142,16 +142,17 @@ class AccountGallery extends ImmutablePureComponent { | ||||||
|   handleOpenMedia = attachment => { |   handleOpenMedia = attachment => { | ||||||
|     const { dispatch } = this.props; |     const { dispatch } = this.props; | ||||||
|     const statusId = attachment.getIn(['status', 'id']); |     const statusId = attachment.getIn(['status', 'id']); | ||||||
|  |     const lang = attachment.getIn(['status', 'language']); | ||||||
| 
 | 
 | ||||||
|     if (attachment.get('type') === 'video') { |     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') { |     } 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 { |     } else { | ||||||
|       const media = attachment.getIn(['status', 'media_attachments']); |       const media = attachment.getIn(['status', 'media_attachments']); | ||||||
|       const index = media.findIndex(x => x.get('id') === attachment.get('id')); |       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 { FormattedMessage } from 'react-intl'; | ||||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||||
| import AvatarOverlay from '../../../components/avatar_overlay'; | 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'; | import { Icon } from 'flavours/glitch/components/icon'; | ||||||
| 
 | 
 | ||||||
| export default class MovedNote extends ImmutablePureComponent { | export default class MovedNote extends ImmutablePureComponent { | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ import { connect } from 'react-redux'; | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { lookupAccount, fetchAccount } from 'flavours/glitch/actions/accounts'; | 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 StatusList from '../../components/status_list'; | ||||||
| import LoadingIndicator from '../../components/loading_indicator'; | import LoadingIndicator from '../../components/loading_indicator'; | ||||||
| import Column from '../ui/components/column'; | import Column from '../ui/components/column'; | ||||||
|  | @ -12,7 +12,7 @@ import HeaderContainer from './containers/header_container'; | ||||||
| import { List as ImmutableList } from 'immutable'; | import { List as ImmutableList } from 'immutable'; | ||||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||||
| import { FormattedMessage } from 'react-intl'; | 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 LimitedAccountHint from './components/limited_account_hint'; | ||||||
| import { getAccountHidden } from 'flavours/glitch/selectors'; | import { getAccountHidden } from 'flavours/glitch/selectors'; | ||||||
| import { fetchFeaturedTags } from '../../actions/featured_tags'; | import { fetchFeaturedTags } from '../../actions/featured_tags'; | ||||||
|  | @ -122,7 +122,7 @@ class AccountTimeline extends ImmutablePureComponent { | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   componentWillReceiveProps (nextProps) { |   UNSAFE_componentWillReceiveProps (nextProps) { | ||||||
|     const { dispatch } = this.props; |     const { dispatch } = this.props; | ||||||
| 
 | 
 | ||||||
|     if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) { |     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) { |     if (!is(nextProps.visible, this.props.visible) && nextProps.visible !== undefined) { | ||||||
|       this.setState({ revealed: nextProps.visible }); |       this.setState({ revealed: nextProps.visible }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -34,7 +34,7 @@ class Blocks extends ImmutablePureComponent { | ||||||
|     multiColumn: PropTypes.bool, |     multiColumn: PropTypes.bool, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   componentWillMount () { |   UNSAFE_componentWillMount () { | ||||||
|     this.props.dispatch(fetchBlocks()); |     this.props.dispatch(fetchBlocks()); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -34,7 +34,7 @@ class Bookmarks extends ImmutablePureComponent { | ||||||
|     isLoading: PropTypes.bool, |     isLoading: PropTypes.bool, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   componentWillMount () { |   UNSAFE_componentWillMount () { | ||||||
|     this.props.dispatch(fetchBookmarkedStatuses()); |     this.props.dispatch(fetchBookmarkedStatuses()); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import React from 'react'; | import React from 'react'; | ||||||
| import { Avatar } from 'flavours/glitch/components/avatar'; | 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 ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,12 +2,12 @@ | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import React from 'react'; | import React from 'react'; | ||||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||||
|  | import { supportsPassiveEvents } from 'detect-passive-events'; | ||||||
| 
 | 
 | ||||||
| //  Components. | //  Components. | ||||||
| import { Icon } from 'flavours/glitch/components/icon'; | import { Icon } from 'flavours/glitch/components/icon'; | ||||||
| 
 | 
 | ||||||
| //  Utils. | const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true; | ||||||
| import { withPassive } from 'flavours/glitch/utils/dom_helpers'; |  | ||||||
| 
 | 
 | ||||||
| //  The component. | //  The component. | ||||||
| export default class ComposerOptionsDropdownContent extends React.PureComponent { | export default class ComposerOptionsDropdownContent extends React.PureComponent { | ||||||
|  | @ -41,6 +41,7 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent | ||||||
|   handleDocumentClick = (e) => { |   handleDocumentClick = (e) => { | ||||||
|     if (this.node && !this.node.contains(e.target)) { |     if (this.node && !this.node.contains(e.target)) { | ||||||
|       this.props.onClose(); |       this.props.onClose(); | ||||||
|  |       e.stopPropagation(); | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|  | @ -51,8 +52,8 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent | ||||||
| 
 | 
 | ||||||
|   //  On mounting, we add our listeners. |   //  On mounting, we add our listeners. | ||||||
|   componentDidMount () { |   componentDidMount () { | ||||||
|     document.addEventListener('click', this.handleDocumentClick, false); |     document.addEventListener('click', this.handleDocumentClick, { capture: true }); | ||||||
|     document.addEventListener('touchend', this.handleDocumentClick, withPassive); |     document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); | ||||||
|     if (this.focusedItem) { |     if (this.focusedItem) { | ||||||
|       this.focusedItem.focus({ preventScroll: true }); |       this.focusedItem.focus({ preventScroll: true }); | ||||||
|     } else { |     } else { | ||||||
|  | @ -62,8 +63,8 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent | ||||||
| 
 | 
 | ||||||
|   //  On unmounting, we remove our listeners. |   //  On unmounting, we remove our listeners. | ||||||
|   componentWillUnmount () { |   componentWillUnmount () { | ||||||
|     document.removeEventListener('click', this.handleDocumentClick, false); |     document.removeEventListener('click', this.handleDocumentClick, { capture: true }); | ||||||
|     document.removeEventListener('touchend', this.handleDocumentClick, withPassive); |     document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   handleClick = (e) => { |   handleClick = (e) => { | ||||||
|  |  | ||||||
|  | @ -28,7 +28,7 @@ const messages = defineMessages({ | ||||||
| 
 | 
 | ||||||
| let EmojiPicker, Emoji; // load asynchronously | 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`; | 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); |     this.props.onSelect(e.currentTarget.getAttribute('data-index') * 1); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   componentWillReceiveProps (nextProps) { |   UNSAFE_componentWillReceiveProps (nextProps) { | ||||||
|     if (nextProps.active) { |     if (nextProps.active) { | ||||||
|       this.attachListeners(); |       this.attachListeners(); | ||||||
|     } else { |     } else { | ||||||
|  | @ -79,12 +79,12 @@ class ModifierPickerMenu extends React.PureComponent { | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   attachListeners () { |   attachListeners () { | ||||||
|     document.addEventListener('click', this.handleDocumentClick, false); |     document.addEventListener('click', this.handleDocumentClick, { capture: true }); | ||||||
|     document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); |     document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   removeListeners () { |   removeListeners () { | ||||||
|     document.removeEventListener('click', this.handleDocumentClick, false); |     document.removeEventListener('click', this.handleDocumentClick, { capture: true }); | ||||||
|     document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); |     document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -177,7 +177,7 @@ class EmojiPickerMenuImpl extends React.PureComponent { | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   componentDidMount () { |   componentDidMount () { | ||||||
|     document.addEventListener('click', this.handleDocumentClick, false); |     document.addEventListener('click', this.handleDocumentClick, { capture: true }); | ||||||
|     document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); |     document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); | ||||||
| 
 | 
 | ||||||
|     // Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need |     // Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need | ||||||
|  | @ -192,7 +192,7 @@ class EmojiPickerMenuImpl extends React.PureComponent { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   componentWillUnmount () { |   componentWillUnmount () { | ||||||
|     document.removeEventListener('click', this.handleDocumentClick, false); |     document.removeEventListener('click', this.handleDocumentClick, { capture: true }); | ||||||
|     document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); |     document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -15,7 +15,7 @@ const messages = defineMessages({ | ||||||
|   clear: { id: 'emoji_button.clear', defaultMessage: 'Clear' }, |   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 { | class LanguageDropdownMenu extends React.PureComponent { | ||||||
| 
 | 
 | ||||||
|  | @ -39,11 +39,12 @@ class LanguageDropdownMenu extends React.PureComponent { | ||||||
|   handleDocumentClick = e => { |   handleDocumentClick = e => { | ||||||
|     if (this.node && !this.node.contains(e.target)) { |     if (this.node && !this.node.contains(e.target)) { | ||||||
|       this.props.onClose(); |       this.props.onClose(); | ||||||
|  |       e.stopPropagation(); | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   componentDidMount () { |   componentDidMount () { | ||||||
|     document.addEventListener('click', this.handleDocumentClick, false); |     document.addEventListener('click', this.handleDocumentClick, { capture: true }); | ||||||
|     document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); |     document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); | ||||||
| 
 | 
 | ||||||
|     // Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need |     // Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need | ||||||
|  | @ -57,7 +58,7 @@ class LanguageDropdownMenu extends React.PureComponent { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   componentWillUnmount () { |   componentWillUnmount () { | ||||||
|     document.removeEventListener('click', this.handleDocumentClick, false); |     document.removeEventListener('click', this.handleDocumentClick, { capture: true }); | ||||||
|     document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); |     document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ import PropTypes from 'prop-types'; | ||||||
| import { connect } from 'react-redux'; | import { connect } from 'react-redux'; | ||||||
| import { makeGetAccount } from 'flavours/glitch/selectors'; | import { makeGetAccount } from 'flavours/glitch/selectors'; | ||||||
| import { Avatar } from 'flavours/glitch/components/avatar'; | 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 Permalink from 'flavours/glitch/components/permalink'; | ||||||
| import { IconButton } from 'flavours/glitch/components/icon_button'; | import { IconButton } from 'flavours/glitch/components/icon_button'; | ||||||
| import Button from 'flavours/glitch/components/button'; | import Button from 'flavours/glitch/components/button'; | ||||||
|  |  | ||||||
|  | @ -34,7 +34,7 @@ class Blocks extends ImmutablePureComponent { | ||||||
|     multiColumn: PropTypes.bool, |     multiColumn: PropTypes.bool, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   componentWillMount () { |   UNSAFE_componentWillMount () { | ||||||
|     this.props.dispatch(fetchDomainBlocks()); |     this.props.dispatch(fetchDomainBlocks()); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ import PropTypes from 'prop-types'; | ||||||
| import { Blurhash } from 'flavours/glitch/components/blurhash'; | import { Blurhash } from 'flavours/glitch/components/blurhash'; | ||||||
| import { accountsCountRenderer } from 'flavours/glitch/components/hashtag'; | import { accountsCountRenderer } from 'flavours/glitch/components/hashtag'; | ||||||
| import ShortNumber from 'flavours/glitch/components/short_number'; | 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'; | import classNames from 'classnames'; | ||||||
| 
 | 
 | ||||||
| export default class Story extends React.PureComponent { | export default class Story extends React.PureComponent { | ||||||
|  |  | ||||||
|  | @ -34,7 +34,7 @@ class Favourites extends ImmutablePureComponent { | ||||||
|     isLoading: PropTypes.bool, |     isLoading: PropTypes.bool, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   componentWillMount () { |   UNSAFE_componentWillMount () { | ||||||
|     this.props.dispatch(fetchFavouritedStatuses()); |     this.props.dispatch(fetchFavouritedStatuses()); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -32,13 +32,13 @@ class Favourites extends ImmutablePureComponent { | ||||||
|     intl: PropTypes.object.isRequired, |     intl: PropTypes.object.isRequired, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   componentWillMount () { |   UNSAFE_componentWillMount () { | ||||||
|     if (!this.props.accountIds) { |     if (!this.props.accountIds) { | ||||||
|       this.props.dispatch(fetchFavourites(this.props.params.statusId)); |       this.props.dispatch(fetchFavourites(this.props.params.statusId)); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   componentWillReceiveProps (nextProps) { |   UNSAFE_componentWillReceiveProps (nextProps) { | ||||||
|     if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { |     if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { | ||||||
|       this.props.dispatch(fetchFavourites(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 { connect } from 'react-redux'; | ||||||
| import { makeGetAccount } from 'flavours/glitch/selectors'; | import { makeGetAccount } from 'flavours/glitch/selectors'; | ||||||
| import { Avatar } from 'flavours/glitch/components/avatar'; | 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 Permalink from 'flavours/glitch/components/permalink'; | ||||||
| import { IconButton } from 'flavours/glitch/components/icon_button'; | import { IconButton } from 'flavours/glitch/components/icon_button'; | ||||||
| import { injectIntl, defineMessages } from 'react-intl'; | import { injectIntl, defineMessages } from 'react-intl'; | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ import PropTypes from 'prop-types'; | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import Permalink from 'flavours/glitch/components/permalink'; | import Permalink from 'flavours/glitch/components/permalink'; | ||||||
| import { Avatar } from 'flavours/glitch/components/avatar'; | 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 { IconButton } from 'flavours/glitch/components/icon_button'; | ||||||
| import { defineMessages, injectIntl } from 'react-intl'; | import { defineMessages, injectIntl } from 'react-intl'; | ||||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||||
|  |  | ||||||
|  | @ -39,7 +39,7 @@ class FollowRequests extends ImmutablePureComponent { | ||||||
|     multiColumn: PropTypes.bool, |     multiColumn: PropTypes.bool, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   componentWillMount () { |   UNSAFE_componentWillMount () { | ||||||
|     this.props.dispatch(fetchFollowRequests()); |     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 HeaderContainer from 'flavours/glitch/features/account_timeline/containers/header_container'; | ||||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||||
| import ScrollableList from 'flavours/glitch/components/scrollable_list'; | import ScrollableList from 'flavours/glitch/components/scrollable_list'; | ||||||
| import TimelineHint from 'flavours/glitch/components/timeline_hint'; | import { TimelineHint } from 'flavours/glitch/components/timeline_hint'; | ||||||
| import LimitedAccountHint from '../account_timeline/components/limited_account_hint'; | import LimitedAccountHint from '../account_timeline/components/limited_account_hint'; | ||||||
| import { getAccountHidden } from 'flavours/glitch/selectors'; | import { getAccountHidden } from 'flavours/glitch/selectors'; | ||||||
| import { normalizeForLookup } from 'flavours/glitch/reducers/accounts_map'; | 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 HeaderContainer from 'flavours/glitch/features/account_timeline/containers/header_container'; | ||||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||||
| import ScrollableList from 'flavours/glitch/components/scrollable_list'; | import ScrollableList from 'flavours/glitch/components/scrollable_list'; | ||||||
| import TimelineHint from 'flavours/glitch/components/timeline_hint'; | import { TimelineHint } from 'flavours/glitch/components/timeline_hint'; | ||||||
| import LimitedAccountHint from '../account_timeline/components/limited_account_hint'; | import LimitedAccountHint from '../account_timeline/components/limited_account_hint'; | ||||||
| import { getAccountHidden } from 'flavours/glitch/selectors'; | import { getAccountHidden } from 'flavours/glitch/selectors'; | ||||||
| import { normalizeForLookup } from 'flavours/glitch/reducers/accounts_map'; | import { normalizeForLookup } from 'flavours/glitch/reducers/accounts_map'; | ||||||
|  |  | ||||||
|  | @ -96,7 +96,7 @@ class GettingStarted extends ImmutablePureComponent { | ||||||
|     openSettings: PropTypes.func.isRequired, |     openSettings: PropTypes.func.isRequired, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   componentWillMount () { |   UNSAFE_componentWillMount () { | ||||||
|     this.props.fetchLists(); |     this.props.fetchLists(); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -143,7 +143,7 @@ class InteractionModal extends React.PureComponent { | ||||||
|         <div className='interaction-modal__choices'> |         <div className='interaction-modal__choices'> | ||||||
|           <div className='interaction-modal__choices__choice'> |           <div className='interaction-modal__choices__choice'> | ||||||
|             <h3><FormattedMessage id='interaction_modal.on_this_server' defaultMessage='On this server' /></h3> |             <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} |             {signupButton} | ||||||
|           </div> |           </div> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ import { makeGetAccount } from '../../../selectors'; | ||||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import { Avatar } from '../../../components/avatar'; | import { Avatar } from '../../../components/avatar'; | ||||||
| import DisplayName from '../../../components/display_name'; | import { DisplayName } from '../../../components/display_name'; | ||||||
| import { injectIntl } from 'react-intl'; | import { injectIntl } from 'react-intl'; | ||||||
| 
 | 
 | ||||||
| const makeMapStateToProps = () => { | const makeMapStateToProps = () => { | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ import PropTypes from 'prop-types'; | ||||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import { Avatar } from 'flavours/glitch/components/avatar'; | 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 { IconButton } from 'flavours/glitch/components/icon_button'; | ||||||
| import { defineMessages } from 'react-intl'; | import { defineMessages } from 'react-intl'; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -76,7 +76,7 @@ class ListTimeline extends React.PureComponent { | ||||||
|     this.disconnect = dispatch(connectListStream(id)); |     this.disconnect = dispatch(connectListStream(id)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   componentWillReceiveProps (nextProps) { |   UNSAFE_componentWillReceiveProps (nextProps) { | ||||||
|     const { dispatch } = this.props; |     const { dispatch } = this.props; | ||||||
|     const { id } = nextProps.params; |     const { id } = nextProps.params; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -42,7 +42,7 @@ class Lists extends ImmutablePureComponent { | ||||||
|     multiColumn: PropTypes.bool, |     multiColumn: PropTypes.bool, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   componentWillMount () { |   UNSAFE_componentWillMount () { | ||||||
|     this.props.dispatch(fetchLists()); |     this.props.dispatch(fetchLists()); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -35,7 +35,7 @@ class Mutes extends ImmutablePureComponent { | ||||||
|     multiColumn: PropTypes.bool, |     multiColumn: PropTypes.bool, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   componentWillMount () { |   UNSAFE_componentWillMount () { | ||||||
|     this.props.dispatch(fetchMutes()); |     this.props.dispatch(fetchMutes()); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ import React, { Fragment } from 'react'; | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import { Avatar } from 'flavours/glitch/components/avatar'; | 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 Permalink from 'flavours/glitch/components/permalink'; | ||||||
| import { IconButton } from 'flavours/glitch/components/icon_button'; | import { IconButton } from 'flavours/glitch/components/icon_button'; | ||||||
| import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | 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 { IconButton } from 'flavours/glitch/components/icon_button'; | ||||||
| import { Link } from 'react-router-dom'; | import { Link } from 'react-router-dom'; | ||||||
| import { Avatar } from 'flavours/glitch/components/avatar'; | 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'; | import { defineMessages, injectIntl } from 'react-intl'; | ||||||
| 
 | 
 | ||||||
| const messages = defineMessages({ | const messages = defineMessages({ | ||||||
|  |  | ||||||
|  | @ -29,7 +29,7 @@ class PinnedStatuses extends ImmutablePureComponent { | ||||||
|     multiColumn: PropTypes.bool, |     multiColumn: PropTypes.bool, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   componentWillMount () { |   UNSAFE_componentWillMount () { | ||||||
|     this.props.dispatch(fetchPinnedStatuses()); |     this.props.dispatch(fetchPinnedStatuses()); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ import { Helmet } from 'react-helmet'; | ||||||
| import { FormattedMessage, FormattedDate, injectIntl, defineMessages } from 'react-intl'; | import { FormattedMessage, FormattedDate, injectIntl, defineMessages } from 'react-intl'; | ||||||
| import Column from 'flavours/glitch/components/column'; | import Column from 'flavours/glitch/components/column'; | ||||||
| import api from 'flavours/glitch/api'; | import api from 'flavours/glitch/api'; | ||||||
| import Skeleton from 'flavours/glitch/components/skeleton'; | import { Skeleton } from 'flavours/glitch/components/skeleton'; | ||||||
| 
 | 
 | ||||||
| const messages = defineMessages({ | const messages = defineMessages({ | ||||||
|   title: { id: 'privacy_policy.title', defaultMessage: 'Privacy Policy' }, |   title: { id: 'privacy_policy.title', defaultMessage: 'Privacy Policy' }, | ||||||
|  |  | ||||||
|  | @ -32,13 +32,13 @@ class Reblogs extends ImmutablePureComponent { | ||||||
|     intl: PropTypes.object.isRequired, |     intl: PropTypes.object.isRequired, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   componentWillMount () { |   UNSAFE_componentWillMount () { | ||||||
|     if (!this.props.accountIds) { |     if (!this.props.accountIds) { | ||||||
|       this.props.dispatch(fetchReblogs(this.props.params.statusId)); |       this.props.dispatch(fetchReblogs(this.props.params.statusId)); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   componentWillReceiveProps(nextProps) { |   UNSAFE_componentWillReceiveProps(nextProps) { | ||||||
|     if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { |     if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { | ||||||
|       this.props.dispatch(fetchReblogs(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 ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import StatusContent from 'flavours/glitch/components/status_content'; | import StatusContent from 'flavours/glitch/components/status_content'; | ||||||
| import { Avatar } from 'flavours/glitch/components/avatar'; | 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 { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp'; | ||||||
| import Option from './option'; | import Option from './option'; | ||||||
| import MediaAttachments from 'flavours/glitch/components/media_attachments'; | import MediaAttachments from 'flavours/glitch/components/media_attachments'; | ||||||
|  |  | ||||||
|  | @ -57,7 +57,7 @@ export default class Card extends React.PureComponent { | ||||||
|     revealed: !this.props.sensitive, |     revealed: !this.props.sensitive, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   componentWillReceiveProps (nextProps) { |   UNSAFE_componentWillReceiveProps (nextProps) { | ||||||
|     if (!Immutable.is(this.props.card, nextProps.card)) { |     if (!Immutable.is(this.props.card, nextProps.card)) { | ||||||
|       this.setState({ embedded: false, previewLoaded: false }); |       this.setState({ embedded: false, previewLoaded: false }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ import React from 'react'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import { Avatar } from 'flavours/glitch/components/avatar'; | 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 StatusContent from 'flavours/glitch/components/status_content'; | ||||||
| import MediaGallery from 'flavours/glitch/components/media_gallery'; | import MediaGallery from 'flavours/glitch/components/media_gallery'; | ||||||
| import AttachmentList from 'flavours/glitch/components/attachment_list'; | import AttachmentList from 'flavours/glitch/components/attachment_list'; | ||||||
|  |  | ||||||
|  | @ -125,12 +125,12 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ | ||||||
|     dispatch(mentionCompose(account, router)); |     dispatch(mentionCompose(account, router)); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   onOpenMedia (media, index) { |   onOpenMedia (media, index, lang) { | ||||||
|     dispatch(openModal('MEDIA', { media, index })); |     dispatch(openModal('MEDIA', { media, index, lang })); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   onOpenVideo (media, options) { |   onOpenVideo (media, lang, options) { | ||||||
|     dispatch(openModal('VIDEO', { media, options })); |     dispatch(openModal('VIDEO', { media, lang, options })); | ||||||
|   }, |   }, | ||||||
| 
 | 
 | ||||||
|   onBlock (status) { |   onBlock (status) { | ||||||
|  |  | ||||||
|  | @ -392,12 +392,12 @@ class Status extends ImmutablePureComponent { | ||||||
|     this.props.dispatch(mentionCompose(account, router)); |     this.props.dispatch(mentionCompose(account, router)); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   handleOpenMedia = (media, index) => { |   handleOpenMedia = (media, index, lang) => { | ||||||
|     this.props.dispatch(openModal('MEDIA', { statusId: this.props.status.get('id'), media, index })); |     this.props.dispatch(openModal('MEDIA', { statusId: this.props.status.get('id'), media, index, lang })); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   handleOpenVideo = (media, options) => { |   handleOpenVideo = (media, lang, options) => { | ||||||
|     this.props.dispatch(openModal('VIDEO', { statusId: this.props.status.get('id'), media, options })); |     this.props.dispatch(openModal('VIDEO', { statusId: this.props.status.get('id'), media, lang, options })); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   handleHotkeyOpenMedia = e => { |   handleHotkeyOpenMedia = e => { | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||||
| import StatusContent from 'flavours/glitch/components/status_content'; | import StatusContent from 'flavours/glitch/components/status_content'; | ||||||
| import { Avatar } from 'flavours/glitch/components/avatar'; | import { Avatar } from 'flavours/glitch/components/avatar'; | ||||||
| import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp'; | 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 classNames from 'classnames'; | ||||||
| import { IconButton } from 'flavours/glitch/components/icon_button'; | 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 StatusContent from 'flavours/glitch/components/status_content'; | ||||||
| import { Avatar } from 'flavours/glitch/components/avatar'; | import { Avatar } from 'flavours/glitch/components/avatar'; | ||||||
| import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp'; | 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 AttachmentList from 'flavours/glitch/components/attachment_list'; | ||||||
| import { Icon } from 'flavours/glitch/components/icon'; | import { Icon } from 'flavours/glitch/components/icon'; | ||||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||||
|  |  | ||||||
|  | @ -33,11 +33,11 @@ class Bundle extends React.Component { | ||||||
|     forceRender: false, |     forceRender: false, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   componentWillMount() { |   UNSAFE_componentWillMount() { | ||||||
|     this.load(this.props); |     this.load(this.props); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   componentWillReceiveProps(nextProps) { |   UNSAFE_componentWillReceiveProps(nextProps) { | ||||||
|     if (nextProps.fetchComponent !== this.props.fetchComponent) { |     if (nextProps.fetchComponent !== this.props.fetchComponent) { | ||||||
|       this.load(nextProps); |       this.load(nextProps); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -18,7 +18,7 @@ import { | ||||||
|   BookmarkedStatuses, |   BookmarkedStatuses, | ||||||
|   ListTimeline, |   ListTimeline, | ||||||
|   Directory, |   Directory, | ||||||
| } from '../../ui/util/async-components'; | } from '../util/async-components'; | ||||||
| import ComposePanel from './compose_panel'; | import ComposePanel from './compose_panel'; | ||||||
| import NavigationPanel from './navigation_panel'; | import NavigationPanel from './navigation_panel'; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -85,7 +85,7 @@ class EmbedModal extends ImmutablePureComponent { | ||||||
|             className='embed-modal__iframe' |             className='embed-modal__iframe' | ||||||
|             frameBorder='0' |             frameBorder='0' | ||||||
|             ref={this.setIframeRef} |             ref={this.setIframeRef} | ||||||
|             sandbox='allow-same-origin' |             sandbox='allow-scripts allow-same-origin' | ||||||
|             title='preview' |             title='preview' | ||||||
|           /> |           /> | ||||||
|         </div> |         </div> | ||||||
|  |  | ||||||
|  | @ -6,7 +6,7 @@ import Button from 'flavours/glitch/components/button'; | ||||||
| import StatusContent from 'flavours/glitch/components/status_content'; | import StatusContent from 'flavours/glitch/components/status_content'; | ||||||
| import { Avatar } from 'flavours/glitch/components/avatar'; | import { Avatar } from 'flavours/glitch/components/avatar'; | ||||||
| import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp'; | 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 AttachmentList from 'flavours/glitch/components/attachment_list'; | ||||||
| import { Icon } from 'flavours/glitch/components/icon'; | import { Icon } from 'flavours/glitch/components/icon'; | ||||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ import PropTypes from 'prop-types'; | ||||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||||
| import { connect } from 'react-redux'; | import { connect } from 'react-redux'; | ||||||
| import classNames from 'classnames'; | 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 Video, { getPointerPosition } from 'flavours/glitch/features/video'; | ||||||
| import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; | import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; | ||||||
| import { IconButton } from 'flavours/glitch/components/icon_button'; | import { IconButton } from 'flavours/glitch/components/icon_button'; | ||||||
|  |  | ||||||
|  | @ -52,13 +52,13 @@ class Header extends React.PureComponent { | ||||||
| 
 | 
 | ||||||
|       if (registrationsOpen) { |       if (registrationsOpen) { | ||||||
|         signupButton = ( |         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' /> |             <FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' /> | ||||||
|           </a> |           </a> | ||||||
|         ); |         ); | ||||||
|       } else { |       } else { | ||||||
|         signupButton = ( |         signupButton = ( | ||||||
|           <button className='button button-tertiary' onClick={openClosedRegistrationsModal}> |           <button className='button' onClick={openClosedRegistrationsModal}> | ||||||
|             <FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' /> |             <FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' /> | ||||||
|           </button> |           </button> | ||||||
|         ); |         ); | ||||||
|  | @ -66,8 +66,8 @@ class Header extends React.PureComponent { | ||||||
| 
 | 
 | ||||||
|       content = ( |       content = ( | ||||||
|         <> |         <> | ||||||
|           <a href='/auth/sign_in' className='button'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Sign in' /></a> |  | ||||||
|           {signupButton} |           {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 ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import Video from 'flavours/glitch/features/video'; | import Video from 'flavours/glitch/features/video'; | ||||||
| import { connect } from 'react-redux'; |  | ||||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||||
| import { defineMessages, injectIntl } from 'react-intl'; | import { defineMessages, injectIntl } from 'react-intl'; | ||||||
| import { IconButton } from 'flavours/glitch/components/icon_button'; | import { IconButton } from 'flavours/glitch/components/icon_button'; | ||||||
|  | @ -21,10 +20,6 @@ const messages = defineMessages({ | ||||||
|   next: { id: 'lightbox.next', defaultMessage: 'Next' }, |   next: { id: 'lightbox.next', defaultMessage: 'Next' }, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| const mapStateToProps = (state, { statusId }) => ({ |  | ||||||
|   language: state.getIn(['statuses', statusId, 'language']), |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| class MediaModal extends ImmutablePureComponent { | class MediaModal extends ImmutablePureComponent { | ||||||
| 
 | 
 | ||||||
|   static contextTypes = { |   static contextTypes = { | ||||||
|  | @ -34,6 +29,7 @@ class MediaModal extends ImmutablePureComponent { | ||||||
|   static propTypes = { |   static propTypes = { | ||||||
|     media: ImmutablePropTypes.list.isRequired, |     media: ImmutablePropTypes.list.isRequired, | ||||||
|     statusId: PropTypes.string, |     statusId: PropTypes.string, | ||||||
|  |     lang: PropTypes.string, | ||||||
|     index: PropTypes.number.isRequired, |     index: PropTypes.number.isRequired, | ||||||
|     onClose: PropTypes.func.isRequired, |     onClose: PropTypes.func.isRequired, | ||||||
|     intl: PropTypes.object.isRequired, |     intl: PropTypes.object.isRequired, | ||||||
|  | @ -135,7 +131,7 @@ class MediaModal extends ImmutablePureComponent { | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   render () { |   render () { | ||||||
|     const { media, language, statusId, intl, onClose } = this.props; |     const { media, statusId, lang, intl, onClose } = this.props; | ||||||
|     const { navigationHidden } = this.state; |     const { navigationHidden } = this.state; | ||||||
| 
 | 
 | ||||||
|     const index = this.getIndex(); |     const index = this.getIndex(); | ||||||
|  | @ -155,7 +151,7 @@ class MediaModal extends ImmutablePureComponent { | ||||||
|             width={width} |             width={width} | ||||||
|             height={height} |             height={height} | ||||||
|             alt={image.get('description')} |             alt={image.get('description')} | ||||||
|             lang={language} |             lang={lang} | ||||||
|             key={image.get('url')} |             key={image.get('url')} | ||||||
|             onClick={this.toggleNavigation} |             onClick={this.toggleNavigation} | ||||||
|             zoomButtonHidden={this.state.zoomButtonHidden} |             zoomButtonHidden={this.state.zoomButtonHidden} | ||||||
|  | @ -178,7 +174,7 @@ class MediaModal extends ImmutablePureComponent { | ||||||
|             onCloseVideo={onClose} |             onCloseVideo={onClose} | ||||||
|             detailed |             detailed | ||||||
|             alt={image.get('description')} |             alt={image.get('description')} | ||||||
|             lang={language} |             lang={lang} | ||||||
|             key={image.get('url')} |             key={image.get('url')} | ||||||
|           /> |           /> | ||||||
|         ); |         ); | ||||||
|  | @ -190,7 +186,7 @@ class MediaModal extends ImmutablePureComponent { | ||||||
|             height={height} |             height={height} | ||||||
|             key={image.get('url')} |             key={image.get('url')} | ||||||
|             alt={image.get('description')} |             alt={image.get('description')} | ||||||
|             lang={language} |             lang={lang} | ||||||
|             onClick={this.toggleNavigation} |             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