Merge pull request #2236 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changes up to e387175fc9
This commit is contained in:
commit
0222df6047
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
|
||||||
|
with_redis_lock("reblog:#{current_account.id}:#{@reblog.id}") do
|
||||||
@status = ReblogService.new.call(current_account, @reblog, reblog_params)
|
@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