Merge commit '6a3f6d5876
' into kb_migration_development
This commit is contained in:
commit
64e0e64694
684 changed files with 16574 additions and 8379 deletions
|
@ -1,39 +0,0 @@
|
|||
version: '2'
|
||||
checks:
|
||||
argument-count:
|
||||
enabled: false
|
||||
complex-logic:
|
||||
enabled: false
|
||||
file-lines:
|
||||
enabled: false
|
||||
method-complexity:
|
||||
enabled: false
|
||||
method-count:
|
||||
enabled: false
|
||||
method-lines:
|
||||
enabled: false
|
||||
nested-control-flow:
|
||||
enabled: false
|
||||
return-statements:
|
||||
enabled: false
|
||||
similar-code:
|
||||
enabled: false
|
||||
identical-code:
|
||||
enabled: false
|
||||
plugins:
|
||||
brakeman:
|
||||
enabled: true
|
||||
bundler-audit:
|
||||
enabled: false
|
||||
eslint:
|
||||
enabled: false
|
||||
rubocop:
|
||||
enabled: false
|
||||
sass-lint:
|
||||
enabled: false
|
||||
exclude_patterns:
|
||||
- spec/
|
||||
- vendor/asset/
|
||||
|
||||
- app/javascript/mastodon/locales/**/*.json
|
||||
- config/locales/**/*.yml
|
|
@ -15,6 +15,7 @@
|
|||
"forwardPorts": [3000, 4000],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"onCreateCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}",
|
||||
"postCreateCommand": ".devcontainer/post-create.sh",
|
||||
"waitFor": "postCreateCommand",
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ services:
|
|||
environment:
|
||||
RAILS_ENV: development
|
||||
NODE_ENV: development
|
||||
|
||||
BIND: 0.0.0.0
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: '6379'
|
||||
DB_HOST: db
|
||||
|
@ -23,6 +23,10 @@ services:
|
|||
LIBRE_TRANSLATE_ENDPOINT: http://libretranslate:5000
|
||||
# Overrides default command so things don't shut down after the process ends.
|
||||
command: sleep infinity
|
||||
ports:
|
||||
- '127.0.0.1:3000:3000'
|
||||
- '127.0.0.1:4000:4000'
|
||||
- '127.0.0.1:80:3000'
|
||||
networks:
|
||||
- external_network
|
||||
- internal_network
|
||||
|
@ -66,15 +70,19 @@ services:
|
|||
hard: -1
|
||||
|
||||
libretranslate:
|
||||
image: libretranslate/libretranslate:v1.2.9
|
||||
image: libretranslate/libretranslate:v1.3.10
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- lt-data:/home/libretranslate/.local
|
||||
networks:
|
||||
- external_network
|
||||
- internal_network
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
redis-data:
|
||||
es-data:
|
||||
lt-data:
|
||||
|
||||
networks:
|
||||
external_network:
|
||||
|
|
|
@ -3,17 +3,22 @@
|
|||
set -e # Fail the whole script on first error
|
||||
|
||||
# Fetch Ruby gem dependencies
|
||||
bundle install --path vendor/bundle --with='development test'
|
||||
|
||||
# Fetch Javascript dependencies
|
||||
yarn install
|
||||
bundle config path 'vendor/bundle'
|
||||
bundle config with 'development test'
|
||||
bundle install
|
||||
|
||||
# Make Gemfile.lock pristine again
|
||||
git checkout -- Gemfile.lock
|
||||
|
||||
# Fetch Javascript dependencies
|
||||
yarn --frozen-lockfile
|
||||
|
||||
# [re]create, migrate, and seed the test database
|
||||
RAILS_ENV=test ./bin/rails db:setup
|
||||
|
||||
# [re]create, migrate, and seed the development database
|
||||
RAILS_ENV=development ./bin/rails db:setup
|
||||
|
||||
# Precompile assets for development
|
||||
RAILS_ENV=development ./bin/rails assets:precompile
|
||||
|
||||
|
|
71
.eslintrc.js
71
.eslintrc.js
|
@ -13,38 +13,39 @@ module.exports = {
|
|||
browser: true,
|
||||
node: true,
|
||||
es6: true,
|
||||
jest: true,
|
||||
},
|
||||
|
||||
globals: {
|
||||
ATTACHMENT_HOST: false,
|
||||
},
|
||||
|
||||
parser: '@babel/eslint-parser',
|
||||
parser: '@typescript-eslint/parser',
|
||||
|
||||
plugins: [
|
||||
'react',
|
||||
'jsx-a11y',
|
||||
'import',
|
||||
'promise',
|
||||
'@typescript-eslint',
|
||||
],
|
||||
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
experimentalObjectRestSpread: true,
|
||||
jsx: true,
|
||||
},
|
||||
ecmaVersion: 2021,
|
||||
requireConfigFile: false,
|
||||
babelOptions: {
|
||||
configFile: false,
|
||||
presets: ['@babel/react', '@babel/env'],
|
||||
},
|
||||
},
|
||||
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
'import/extensions': [
|
||||
'.js', '.jsx',
|
||||
],
|
||||
'import/ignore': [
|
||||
'node_modules',
|
||||
'\\.(css|scss|json)$',
|
||||
|
@ -52,7 +53,7 @@ module.exports = {
|
|||
'import/resolver': {
|
||||
node: {
|
||||
paths: ['app/javascript'],
|
||||
extensions: ['.js', '.jsx'],
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -93,7 +94,8 @@ module.exports = {
|
|||
'no-self-assign': 'off',
|
||||
'no-trailing-spaces': 'warn',
|
||||
'no-unused-expressions': 'error',
|
||||
'no-unused-vars': [
|
||||
'no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
vars: 'all',
|
||||
|
@ -112,7 +114,7 @@ module.exports = {
|
|||
semi: 'error',
|
||||
'valid-typeof': 'error',
|
||||
|
||||
'react/jsx-filename-extension': ['error', { 'allow': 'as-needed' }],
|
||||
'react/jsx-filename-extension': ['error', { extensions: ['.jsx', 'tsx'] }],
|
||||
'react/jsx-boolean-value': 'error',
|
||||
'react/jsx-closing-bracket-location': ['error', 'line-aligned'],
|
||||
'react/jsx-curly-spacing': 'error',
|
||||
|
@ -188,6 +190,8 @@ module.exports = {
|
|||
{
|
||||
js: 'never',
|
||||
jsx: 'never',
|
||||
ts: 'never',
|
||||
tsx: 'never',
|
||||
},
|
||||
],
|
||||
'import/newline-after-import': 'error',
|
||||
|
@ -196,6 +200,7 @@ module.exports = {
|
|||
{
|
||||
devDependencies: [
|
||||
'config/webpack/**',
|
||||
'app/javascript/mastodon/performance.js',
|
||||
'app/javascript/mastodon/test_setup.js',
|
||||
'app/javascript/**/__tests__/**',
|
||||
],
|
||||
|
@ -214,4 +219,52 @@ module.exports = {
|
|||
'promise/no-nesting': 'off',
|
||||
'promise/no-promise-in-callback': 'off',
|
||||
},
|
||||
|
||||
overrides: [
|
||||
{
|
||||
files: [
|
||||
'*.config.js',
|
||||
'.*rc.js',
|
||||
'ide-helper.js',
|
||||
],
|
||||
|
||||
env: {
|
||||
commonjs: true,
|
||||
},
|
||||
|
||||
parserOptions: {
|
||||
sourceType: 'script',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'**/*.ts',
|
||||
'**/*.tsx',
|
||||
],
|
||||
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react/recommended',
|
||||
'plugin:jsx-a11y/recommended',
|
||||
'plugin:import/recommended',
|
||||
'plugin:import/typescript',
|
||||
'plugin:promise/recommended',
|
||||
],
|
||||
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'**/__tests__/*.js',
|
||||
'**/__tests__/*.jsx',
|
||||
],
|
||||
|
||||
env: {
|
||||
jest: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
17
.github/workflows/check-i18n.yml
vendored
17
.github/workflows/check-i18n.yml
vendored
|
@ -30,13 +30,28 @@ jobs:
|
|||
ruby-version: .ruby-version
|
||||
bundler-cache: true
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: yarn
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
- name: Install all yarn packages
|
||||
run: yarn --frozen-lockfile
|
||||
|
||||
- name: Check for missing strings in English JSON
|
||||
run: |
|
||||
yarn build:development
|
||||
yarn manage:translations en
|
||||
git diff --exit-code
|
||||
|
||||
- name: Check locale file normalization
|
||||
run: bundle exec i18n-tasks check-normalized
|
||||
|
||||
- name: Check for unused strings
|
||||
run: bundle exec i18n-tasks unused
|
||||
|
||||
- name: Check for missing strings in English
|
||||
- name: Check for missing strings in English YML
|
||||
run: |
|
||||
bundle exec i18n-tasks add-missing -l en
|
||||
git diff --exit-code
|
||||
|
|
17
.github/workflows/haml-lint-problem-matcher.json
vendored
Normal file
17
.github/workflows/haml-lint-problem-matcher.json
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "haml-lint",
|
||||
"severity": "warning",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(.*):(\\d+)\\s\\[W]\\s(.*):\\s(.*)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"code": 3,
|
||||
"message": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
46
.github/workflows/lint-haml.yml
vendored
Normal file
46
.github/workflows/lint-haml.yml
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
name: Haml Linting
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'dependabot/**'
|
||||
paths:
|
||||
- '.github/workflows/haml-lint-problem-matcher.json'
|
||||
- '.github/workflows/lint-haml.yml'
|
||||
- '.haml-lint*.yml'
|
||||
- '.rubocop*.yml'
|
||||
- '.ruby-version'
|
||||
- '**/*.haml'
|
||||
- 'Gemfile*'
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/haml-lint-problem-matcher.json'
|
||||
- '.github/workflows/lint-haml.yml'
|
||||
- '.haml-lint*.yml'
|
||||
- '.rubocop*.yml'
|
||||
- '.ruby-version'
|
||||
- '**/*.haml'
|
||||
- 'Gemfile*'
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install native Ruby dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libicu-dev libidn11-dev
|
||||
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: .ruby-version
|
||||
bundler-cache: true
|
||||
|
||||
- name: Run haml-lint
|
||||
run: |
|
||||
echo "::add-matcher::.github/workflows/haml-lint-problem-matcher.json"
|
||||
bundle exec haml-lint
|
9
.github/workflows/lint-js.yml
vendored
9
.github/workflows/lint-js.yml
vendored
|
@ -6,22 +6,28 @@ on:
|
|||
paths:
|
||||
- 'package.json'
|
||||
- 'yarn.lock'
|
||||
- 'tsconfig.json'
|
||||
- '.nvmrc'
|
||||
- '.prettier*'
|
||||
- '.eslint*'
|
||||
- '**/*.js'
|
||||
- '**/*.jsx'
|
||||
- '**/*.ts'
|
||||
- '**/*.tsx'
|
||||
- '.github/workflows/lint-js.yml'
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
- 'package.json'
|
||||
- 'yarn.lock'
|
||||
- 'tsconfig.json'
|
||||
- '.nvmrc'
|
||||
- '.prettier*'
|
||||
- '.eslint*'
|
||||
- '**/*.js'
|
||||
- '**/*.jsx'
|
||||
- '**/*.ts'
|
||||
- '**/*.tsx'
|
||||
- '.github/workflows/lint-js.yml'
|
||||
|
||||
jobs:
|
||||
|
@ -43,3 +49,6 @@ jobs:
|
|||
|
||||
- name: ESLint
|
||||
run: yarn test:lint:js
|
||||
|
||||
- name: Typecheck
|
||||
run: yarn test:typecheck
|
||||
|
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
- id: skip_check
|
||||
uses: fkirc/skip-duplicate-actions@v5
|
||||
with:
|
||||
paths: '["Gemfile*", ".ruby-version", "**/*.rb", ".github/workflows/test-migrations-one-step.yml"]'
|
||||
paths: '["Gemfile*", ".ruby-version", "**/*.rb", ".github/workflows/test-migrations-one-step.yml", "lib/tasks/tests.rake"]'
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -64,7 +64,9 @@ jobs:
|
|||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install native Ruby dependencies
|
||||
run: sudo apt-get install -y libicu-dev libidn11-dev
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libicu-dev libidn11-dev
|
||||
|
||||
- name: Set up bundler cache
|
||||
uses: ruby/setup-ruby@v1
|
||||
|
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
- id: skip_check
|
||||
uses: fkirc/skip-duplicate-actions@v5
|
||||
with:
|
||||
paths: '["Gemfile*", ".ruby-version", "**/*.rb", ".github/workflows/test-migrations-two-step.yml"]'
|
||||
paths: '["Gemfile*", ".ruby-version", "**/*.rb", ".github/workflows/test-migrations-two-step.yml", "lib/tasks/tests.rake"]'
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -63,7 +63,9 @@ jobs:
|
|||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install native Ruby dependencies
|
||||
run: sudo apt-get install -y libicu-dev libidn11-dev
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libicu-dev libidn11-dev
|
||||
|
||||
- name: Set up bundler cache
|
||||
uses: ruby/setup-ruby@v1
|
||||
|
|
9
.github/workflows/test-ruby.yml
vendored
9
.github/workflows/test-ruby.yml
vendored
|
@ -32,7 +32,9 @@ jobs:
|
|||
node-version-file: '.nvmrc'
|
||||
|
||||
- name: Install native Ruby dependencies
|
||||
run: sudo apt-get install -y libicu-dev libidn11-dev
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libicu-dev libidn11-dev
|
||||
|
||||
- name: Set up bundler cache
|
||||
uses: ruby/setup-ruby@v1
|
||||
|
@ -40,7 +42,7 @@ jobs:
|
|||
ruby-version: .ruby-version
|
||||
bundler-cache: true
|
||||
|
||||
- run: yarn install --frozen-lockfile
|
||||
- run: yarn --frozen-lockfile --production
|
||||
- name: Precompile assets
|
||||
# Previously had set this, but it's not supported
|
||||
# export NODE_OPTIONS=--openssl-legacy-provider
|
||||
|
@ -119,6 +121,9 @@ jobs:
|
|||
path: './public'
|
||||
name: ${{ github.sha }}
|
||||
|
||||
- name: Update package index
|
||||
run: sudo apt-get update
|
||||
|
||||
- name: Install native Ruby dependencies
|
||||
run: sudo apt-get install -y libicu-dev libidn11-dev
|
||||
|
||||
|
|
103
.haml-lint.yml
103
.haml-lint.yml
|
@ -1,108 +1,9 @@
|
|||
# Whether to ignore frontmatter at the beginning of HAML documents for
|
||||
# frameworks such as Jekyll/Middleman
|
||||
skip_frontmatter: false
|
||||
inherits_from: .haml-lint_todo.yml
|
||||
|
||||
exclude:
|
||||
- 'vendor/**/*'
|
||||
- 'spec/**/*'
|
||||
- 'lib/templates/**/*'
|
||||
- 'app/views/kaminari/**/*'
|
||||
- lib/templates/haml/scaffold/_form.html.haml
|
||||
|
||||
linters:
|
||||
AltText:
|
||||
enabled: false
|
||||
|
||||
ClassAttributeWithStaticValue:
|
||||
enabled: true
|
||||
|
||||
ClassesBeforeIds:
|
||||
enabled: true
|
||||
|
||||
ConsecutiveComments:
|
||||
enabled: true
|
||||
|
||||
ConsecutiveSilentScripts:
|
||||
enabled: true
|
||||
max_consecutive: 2
|
||||
|
||||
EmptyObjectReference:
|
||||
enabled: true
|
||||
|
||||
EmptyScript:
|
||||
enabled: true
|
||||
|
||||
FinalNewline:
|
||||
enabled: true
|
||||
present: true
|
||||
|
||||
HtmlAttributes:
|
||||
enabled: true
|
||||
|
||||
ImplicitDiv:
|
||||
enabled: true
|
||||
|
||||
LeadingCommentSpace:
|
||||
enabled: true
|
||||
|
||||
LineLength:
|
||||
enabled: false
|
||||
max: 80
|
||||
|
||||
MultilinePipe:
|
||||
enabled: true
|
||||
|
||||
MultilineScript:
|
||||
enabled: true
|
||||
|
||||
ObjectReferenceAttributes:
|
||||
enabled: true
|
||||
|
||||
RuboCop:
|
||||
enabled: true
|
||||
# These cops are incredibly noisy when it comes to HAML templates, so we
|
||||
# ignore them.
|
||||
ignored_cops:
|
||||
- Lint/BlockAlignment
|
||||
- Lint/EndAlignment
|
||||
- Lint/Void
|
||||
- Metrics/BlockLength
|
||||
- Metrics/LineLength
|
||||
- Style/AlignParameters
|
||||
- Style/BlockNesting
|
||||
- Style/ElseAlignment
|
||||
- Style/EndOfLine
|
||||
- Style/FileName
|
||||
- Style/FinalNewline
|
||||
- Style/FrozenStringLiteralComment
|
||||
- Style/IfUnlessModifier
|
||||
- Style/IndentationWidth
|
||||
- Style/Next
|
||||
- Style/TrailingBlankLines
|
||||
- Style/TrailingWhitespace
|
||||
- Style/WhileUntilModifier
|
||||
|
||||
RubyComments:
|
||||
enabled: true
|
||||
|
||||
SpaceBeforeScript:
|
||||
enabled: true
|
||||
|
||||
SpaceInsideHashAttributes:
|
||||
enabled: true
|
||||
style: space
|
||||
|
||||
Indentation:
|
||||
enabled: true
|
||||
character: space # or tab
|
||||
|
||||
TagName:
|
||||
enabled: true
|
||||
|
||||
TrailingWhitespace:
|
||||
enabled: true
|
||||
|
||||
UnnecessaryInterpolation:
|
||||
enabled: true
|
||||
|
||||
UnnecessaryStringOutput:
|
||||
enabled: true
|
||||
|
|
106
.haml-lint_todo.yml
Normal file
106
.haml-lint_todo.yml
Normal file
|
@ -0,0 +1,106 @@
|
|||
# This configuration was generated by
|
||||
# `haml-lint --auto-gen-config`
|
||||
# on 2023-03-15 00:55:01 -0400 using Haml-Lint version 0.45.0.
|
||||
# The point is for the user to remove these configuration records
|
||||
# one by one as the lints are removed from the code base.
|
||||
# Note that changes in the inspected code, or installation of new
|
||||
# versions of Haml-Lint, may require this file to be generated again.
|
||||
|
||||
linters:
|
||||
# Offense count: 63
|
||||
RuboCop:
|
||||
exclude:
|
||||
- 'app/views/accounts/_og.html.haml'
|
||||
- 'app/views/admin/account_warnings/_account_warning.html.haml'
|
||||
- 'app/views/admin/accounts/index.html.haml'
|
||||
- 'app/views/admin/accounts/show.html.haml'
|
||||
- 'app/views/admin/announcements/edit.html.haml'
|
||||
- 'app/views/admin/announcements/new.html.haml'
|
||||
- 'app/views/admin/disputes/appeals/_appeal.html.haml'
|
||||
- 'app/views/admin/domain_blocks/edit.html.haml'
|
||||
- 'app/views/admin/domain_blocks/new.html.haml'
|
||||
- 'app/views/admin/ip_blocks/new.html.haml'
|
||||
- 'app/views/admin/reports/actions/preview.html.haml'
|
||||
- 'app/views/admin/reports/index.html.haml'
|
||||
- 'app/views/admin/reports/show.html.haml'
|
||||
- 'app/views/admin/roles/_form.html.haml'
|
||||
- 'app/views/admin/settings/about/show.html.haml'
|
||||
- 'app/views/admin/settings/appearance/show.html.haml'
|
||||
- 'app/views/admin/settings/registrations/show.html.haml'
|
||||
- 'app/views/admin/statuses/show.html.haml'
|
||||
- 'app/views/auth/registrations/new.html.haml'
|
||||
- 'app/views/disputes/strikes/show.html.haml'
|
||||
- 'app/views/filters/_filter_fields.html.haml'
|
||||
- 'app/views/invites/_form.html.haml'
|
||||
- 'app/views/layouts/application.html.haml'
|
||||
- 'app/views/layouts/error.html.haml'
|
||||
- 'app/views/notification_mailer/_status.html.haml'
|
||||
- 'app/views/settings/applications/_fields.html.haml'
|
||||
- 'app/views/settings/imports/show.html.haml'
|
||||
- 'app/views/settings/preferences/appearance/show.html.haml'
|
||||
- 'app/views/settings/preferences/other/show.html.haml'
|
||||
- 'app/views/statuses/_detailed_status.html.haml'
|
||||
- 'app/views/statuses/_poll.html.haml'
|
||||
- 'app/views/statuses/show.html.haml'
|
||||
- 'app/views/statuses_cleanup/show.html.haml'
|
||||
- 'app/views/user_mailer/warning.html.haml'
|
||||
|
||||
# Offense count: 913
|
||||
LineLength:
|
||||
enabled: false
|
||||
|
||||
# Offense count: 22
|
||||
UnnecessaryStringOutput:
|
||||
exclude:
|
||||
- 'app/views/accounts/show.html.haml'
|
||||
- 'app/views/admin/custom_emojis/_custom_emoji.html.haml'
|
||||
- 'app/views/admin/relays/_relay.html.haml'
|
||||
- 'app/views/admin/rules/_rule.html.haml'
|
||||
- 'app/views/admin/statuses/index.html.haml'
|
||||
- 'app/views/auth/registrations/_sessions.html.haml'
|
||||
- 'app/views/disputes/strikes/show.html.haml'
|
||||
- 'app/views/notification_mailer/_status.html.haml'
|
||||
- 'app/views/settings/two_factor_authentication_methods/index.html.haml'
|
||||
- 'app/views/statuses/_detailed_status.html.haml'
|
||||
- 'app/views/statuses/_poll.html.haml'
|
||||
- 'app/views/statuses/_simple_status.html.haml'
|
||||
- 'app/views/user_mailer/suspicious_sign_in.html.haml'
|
||||
- 'app/views/user_mailer/webauthn_credential_added.html.haml'
|
||||
- 'app/views/user_mailer/webauthn_credential_deleted.html.haml'
|
||||
- 'app/views/user_mailer/welcome.html.haml'
|
||||
|
||||
# Offense count: 3
|
||||
ViewLength:
|
||||
exclude:
|
||||
- 'app/views/admin/accounts/show.html.haml'
|
||||
- 'app/views/admin/reports/show.html.haml'
|
||||
- 'app/views/disputes/strikes/show.html.haml'
|
||||
|
||||
# Offense count: 41
|
||||
InstanceVariables:
|
||||
exclude:
|
||||
- 'app/views/admin/reports/_actions.html.haml'
|
||||
- 'app/views/admin/roles/_form.html.haml'
|
||||
- 'app/views/admin/webhooks/_form.html.haml'
|
||||
- 'app/views/auth/registrations/_sessions.html.haml'
|
||||
- 'app/views/auth/registrations/_status.html.haml'
|
||||
- 'app/views/auth/sessions/two_factor/_otp_authentication_form.html.haml'
|
||||
- 'app/views/authorize_interactions/_post_follow_actions.html.haml'
|
||||
- 'app/views/invites/_form.html.haml'
|
||||
- 'app/views/relationships/_account.html.haml'
|
||||
- 'app/views/shared/_og.html.haml'
|
||||
- 'app/views/statuses/_status.html.haml'
|
||||
|
||||
# Offense count: 6
|
||||
ConsecutiveSilentScripts:
|
||||
exclude:
|
||||
- 'app/views/admin/settings/shared/_links.html.haml'
|
||||
- 'app/views/settings/login_activities/_login_activity.html.haml'
|
||||
- 'app/views/statuses/_poll.html.haml'
|
||||
|
||||
# Offense count: 3
|
||||
IdNames:
|
||||
exclude:
|
||||
- 'app/views/authorize_interactions/error.html.haml'
|
||||
- 'app/views/oauth/authorizations/error.html.haml'
|
||||
- 'app/views/shared/_error_messages.html.haml'
|
4
.husky/pre-commit
Executable file
4
.husky/pre-commit
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
yarn lint-staged
|
2
.nvmrc
2
.nvmrc
|
@ -1 +1 @@
|
|||
16.19
|
||||
16.20
|
||||
|
|
|
@ -70,6 +70,8 @@ app/javascript/styles/mastodon/reset.scss
|
|||
# Ignore Javascript pending https://github.com/mastodon/mastodon/pull/23631
|
||||
*.js
|
||||
*.jsx
|
||||
*.ts
|
||||
*.tsx
|
||||
|
||||
# Ignore HTML till cleaned and included in CI
|
||||
*.html
|
||||
|
|
151
.rubocop.yml
151
.rubocop.yml
|
@ -1,5 +1,7 @@
|
|||
# Can be removed once all rules are addressed or moved to this file as documented overrides
|
||||
inherit_from: .rubocop_todo.yml
|
||||
|
||||
# Used for merging with exclude lists with .rubocop_todo.yml
|
||||
inherit_mode:
|
||||
merge:
|
||||
- Exclude
|
||||
|
@ -8,31 +10,34 @@ require:
|
|||
- rubocop-rails
|
||||
- rubocop-rspec
|
||||
- rubocop-performance
|
||||
- rubocop-capybara
|
||||
|
||||
AllCops:
|
||||
TargetRubyVersion: 2.7
|
||||
TargetRubyVersion: 2.7 # Set to minimum supported version of CI
|
||||
DisplayCopNames: true
|
||||
DisplayStyleGuide: true
|
||||
ExtraDetails: true
|
||||
UseCache: true
|
||||
CacheRootDirectory: tmp
|
||||
NewCops: enable
|
||||
NewCops: enable # Opt-in to newly added rules
|
||||
Exclude:
|
||||
- db/schema.rb
|
||||
- 'config/**/*'
|
||||
- 'bin/*'
|
||||
- 'Rakefile'
|
||||
- 'node_modules/**/*'
|
||||
- 'Vagrantfile'
|
||||
- 'vendor/**/*'
|
||||
- 'lib/json_ld/*'
|
||||
- 'lib/json_ld/*' # Generated files
|
||||
- 'lib/templates/**/*'
|
||||
|
||||
# Reason: Prefer Hashes without extreme indentation
|
||||
# https://docs.rubocop.org/rubocop/cops_layout.html#layoutfirsthashelementindentation
|
||||
Layout/FirstHashElementIndentation:
|
||||
EnforcedStyle: consistent
|
||||
|
||||
# Reason: Currently disabled in .rubocop_todo.yml
|
||||
# https://docs.rubocop.org/rubocop/cops_layout.html#layoutlinelength
|
||||
Layout/LineLength:
|
||||
Max: 140 # RuboCop default 120
|
||||
AllowedPatterns:
|
||||
# Allow comments to be long lines
|
||||
- !ruby/regexp / \# .*$/
|
||||
|
@ -42,54 +47,124 @@ Layout/LineLength:
|
|||
- db/*migrate/**/*
|
||||
- db/seeds/**/*
|
||||
|
||||
# Reason:
|
||||
# https://docs.rubocop.org/rubocop/cops_lint.html#lintuselessaccessmodifier
|
||||
Lint/UselessAccessModifier:
|
||||
ContextCreatingMethods:
|
||||
- class_methods
|
||||
|
||||
# Reason: Currently disabled in .rubocop_todo.yml
|
||||
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsabcsize
|
||||
Metrics/AbcSize:
|
||||
Max: 34 # RuboCop default 17
|
||||
Exclude:
|
||||
- 'lib/**/*cli*.rb'
|
||||
- db/*migrate/**/*
|
||||
|
||||
# Reason: Some functions cannot be broken up, but others may be refactor candidates
|
||||
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsblocklength
|
||||
Metrics/BlockLength:
|
||||
Max: 55 # Default 25
|
||||
CountAsOne: [array, heredoc]
|
||||
CountAsOne: ['array', 'hash', 'heredoc', 'method_call']
|
||||
Exclude:
|
||||
- 'lib/mastodon/*_cli.rb'
|
||||
- 'lib/tasks/*.rake'
|
||||
- 'app/models/concerns/account_associations.rb'
|
||||
- 'app/models/concerns/account_interactions.rb'
|
||||
- 'app/models/concerns/ldap_authenticable.rb'
|
||||
- 'app/models/concerns/omniauthable.rb'
|
||||
- 'app/models/concerns/pam_authenticable.rb'
|
||||
- 'app/models/concerns/remotable.rb'
|
||||
- 'app/services/suspend_account_service.rb'
|
||||
- 'app/services/unsuspend_account_service.rb'
|
||||
- 'app/views/accounts/show.rss.ruby'
|
||||
- 'app/views/tags/show.rss.ruby'
|
||||
- 'config/environments/development.rb'
|
||||
- 'config/environments/production.rb'
|
||||
- 'config/initializers/devise.rb'
|
||||
- 'config/initializers/doorkeeper.rb'
|
||||
- 'config/initializers/omniauth.rb'
|
||||
- 'config/initializers/simple_form.rb'
|
||||
- 'config/navigation.rb'
|
||||
- 'config/routes.rb'
|
||||
- 'db/post_migrate/20221101190723_backfill_admin_action_logs.rb'
|
||||
- 'db/post_migrate/20221206114142_backfill_admin_action_logs_again.rb'
|
||||
- 'lib/paperclip/gif_transcoder.rb'
|
||||
|
||||
# Reason:
|
||||
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsblocknesting
|
||||
Metrics/BlockNesting:
|
||||
Exclude:
|
||||
- 'lib/mastodon/*_cli.rb'
|
||||
|
||||
# Reason: Some Excluded files would be candidates for refactoring but not currently addressed
|
||||
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsclasslength
|
||||
Metrics/ClassLength:
|
||||
Max: 500 # Default 100
|
||||
CountAsOne: [array, heredoc]
|
||||
CountAsOne: ['array', 'hash', 'heredoc', 'method_call']
|
||||
Exclude:
|
||||
- 'lib/mastodon/*_cli.rb'
|
||||
- 'app/controllers/admin/accounts_controller.rb'
|
||||
- 'app/controllers/api/base_controller.rb'
|
||||
- 'app/controllers/api/v1/admin/accounts_controller.rb'
|
||||
- 'app/controllers/application_controller.rb'
|
||||
- 'app/controllers/auth/registrations_controller.rb'
|
||||
- 'app/controllers/auth/sessions_controller.rb'
|
||||
- 'app/lib/activitypub/activity.rb'
|
||||
- 'app/lib/activitypub/activity/create.rb'
|
||||
- 'app/lib/activitypub/tag_manager.rb'
|
||||
- 'app/lib/feed_manager.rb'
|
||||
- 'app/lib/link_details_extractor.rb'
|
||||
- 'app/lib/request.rb'
|
||||
- 'app/lib/text_formatter.rb'
|
||||
- 'app/lib/user_settings_decorator.rb'
|
||||
- 'app/mailers/user_mailer.rb'
|
||||
- 'app/models/account.rb'
|
||||
- 'app/models/admin/account_action.rb'
|
||||
- 'app/models/form/account_batch.rb'
|
||||
- 'app/models/media_attachment.rb'
|
||||
- 'app/models/status.rb'
|
||||
- 'app/models/tag.rb'
|
||||
- 'app/models/user.rb'
|
||||
- 'app/serializers/activitypub/actor_serializer.rb'
|
||||
- 'app/serializers/activitypub/note_serializer.rb'
|
||||
- 'app/serializers/rest/status_serializer.rb'
|
||||
- 'app/services/account_search_service.rb'
|
||||
- 'app/services/activitypub/process_account_service.rb'
|
||||
- 'app/services/activitypub/process_status_update_service.rb'
|
||||
- 'app/services/backup_service.rb'
|
||||
- 'app/services/delete_account_service.rb'
|
||||
- 'app/services/fan_out_on_write_service.rb'
|
||||
- 'app/services/fetch_link_card_service.rb'
|
||||
- 'app/services/import_service.rb'
|
||||
- 'app/services/notify_service.rb'
|
||||
- 'app/services/post_status_service.rb'
|
||||
- 'app/services/update_status_service.rb'
|
||||
- 'lib/paperclip/color_extractor.rb'
|
||||
|
||||
# Reason: Currently disabled in .rubocop_todo.yml
|
||||
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricscyclomaticcomplexity
|
||||
Metrics/CyclomaticComplexity:
|
||||
Max: 12 # Default 7
|
||||
Exclude:
|
||||
- lib/mastodon/*cli*.rb
|
||||
- db/*migrate/**/*
|
||||
|
||||
# Reason: Currently disabled in .rubocop_todo.yml
|
||||
# https://docs.rubocop.org/rubocop/cops_metrics.html#metricsmethodlength
|
||||
Metrics/MethodLength:
|
||||
Max: 25 # RuboCop default 10
|
||||
CountAsOne: [array, heredoc]
|
||||
Exclude:
|
||||
- 'lib/mastodon/*_cli.rb'
|
||||
|
||||
# Reason:
|
||||
# https://docs.rubocop.org/rubocop/cops_style.html#stylerescuestandarderror
|
||||
Metrics/ModuleLength:
|
||||
Max: 200 # Default 100
|
||||
CountAsOne: [array, heredoc]
|
||||
|
||||
Metrics/PerceivedComplexity:
|
||||
Max: 16 # RuboCop default 8
|
||||
|
||||
# Reason: Prevailing style uses numeric status codes, matches RSpec/Rails/HttpStatus
|
||||
# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railshttpstatus
|
||||
Rails/HttpStatus:
|
||||
EnforcedStyle: numeric
|
||||
|
||||
# Reason: Allowed only in the `tootctl` CLI application code
|
||||
# https://docs.rubocop.org/rubocop-rails/cops_rails.html#railsexit
|
||||
Rails/Exit:
|
||||
Exclude:
|
||||
- 'lib/mastodon/*_cli.rb'
|
||||
|
@ -117,32 +192,70 @@ RSpec/FilePath:
|
|||
- 'spec/controllers/concerns/signature_verification_spec.rb'
|
||||
- 'spec/controllers/concerns/user_tracking_concern_spec.rb'
|
||||
|
||||
# Reason:
|
||||
# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecnamedsubject
|
||||
RSpec/NamedSubject:
|
||||
EnforcedStyle: named_only
|
||||
|
||||
# Reason: Prevailing style choice
|
||||
# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecnottonot
|
||||
RSpec/NotToNot:
|
||||
EnforcedStyle: to_not
|
||||
|
||||
# Reason: Prevailing style uses numeric status codes, matches Rails/HttpStatus
|
||||
# https://docs.rubocop.org/rubocop-rspec/cops_rspec_rails.html#rspecrailshttpstatus
|
||||
RSpec/Rails/HttpStatus:
|
||||
EnforcedStyle: numeric
|
||||
|
||||
# Reason:
|
||||
# https://docs.rubocop.org/rubocop/cops_style.html#styleclassandmodulechildren
|
||||
Style/ClassAndModuleChildren:
|
||||
Enabled: false
|
||||
|
||||
# Reason: Classes mostly self-document with their names
|
||||
# https://docs.rubocop.org/rubocop/cops_style.html#styledocumentation
|
||||
Style/Documentation:
|
||||
Enabled: false
|
||||
|
||||
# Reason: Enforce modern Ruby style
|
||||
# https://docs.rubocop.org/rubocop/cops_style.html#stylehashsyntax
|
||||
Style/HashSyntax:
|
||||
EnforcedStyle: ruby19_no_mixed_keys
|
||||
|
||||
# Reason:
|
||||
# https://docs.rubocop.org/rubocop/cops_style.html#stylenumericliterals
|
||||
Style/NumericLiterals:
|
||||
AllowedPatterns:
|
||||
- \d{4}_\d{2}_\d{2}_\d{6} # For DB migration date version number readability
|
||||
|
||||
# Reason:
|
||||
# https://docs.rubocop.org/rubocop/cops_style.html#stylepercentliteraldelimiters
|
||||
Style/PercentLiteralDelimiters:
|
||||
PreferredDelimiters:
|
||||
'%i': '()'
|
||||
'%w': '()'
|
||||
|
||||
# Reason: Prefer less indentation in conditional assignments
|
||||
# https://docs.rubocop.org/rubocop/cops_style.html#styleredundantbegin
|
||||
Style/RedundantBegin:
|
||||
Enabled: false
|
||||
|
||||
# Reason: Overridden to reduce implicit StandardError rescues
|
||||
# https://docs.rubocop.org/rubocop/cops_style.html#stylerescuestandarderror
|
||||
Style/RescueStandardError:
|
||||
EnforcedStyle: implicit
|
||||
|
||||
# Reason: Originally disabled for CodeClimate, and no config consensus has been found
|
||||
# https://docs.rubocop.org/rubocop/cops_style.html#stylesymbolarray
|
||||
Style/SymbolArray:
|
||||
Enabled: false
|
||||
|
||||
# Reason:
|
||||
# https://docs.rubocop.org/rubocop/cops_style.html#styletrailingcommainarrayliteral
|
||||
Style/TrailingCommaInArrayLiteral:
|
||||
EnforcedStyleForMultiline: 'comma'
|
||||
|
||||
# Reason:
|
||||
# https://docs.rubocop.org/rubocop/cops_style.html#styletrailingcommainhashliteral
|
||||
Style/TrailingCommaInHashLiteral:
|
||||
EnforcedStyleForMultiline: 'comma'
|
||||
|
||||
Style/SymbolArray:
|
||||
Enabled: false
|
||||
|
|
1175
.rubocop_todo.yml
1175
.rubocop_todo.yml
File diff suppressed because it is too large
Load diff
|
@ -1 +1 @@
|
|||
3.2.1
|
||||
3.2.2
|
||||
|
|
|
@ -44,3 +44,6 @@ Gruntfile.js
|
|||
# for specific ignore
|
||||
!.svgo.yml
|
||||
!sass-lint/**/*.yml
|
||||
|
||||
# breaks lint-staged or generally anything using https://github.com/eemeli/yaml/issues/384
|
||||
!**/yaml/dist/**/doc
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# syntax=docker/dockerfile:1.4
|
||||
# This needs to be bullseye-slim because the Ruby image is built on bullseye-slim
|
||||
ARG NODE_VERSION="16.19-bullseye-slim"
|
||||
ARG NODE_VERSION="16.20-bullseye-slim"
|
||||
|
||||
FROM ghcr.io/moritzheiber/ruby-jemalloc:3.2.1-slim as ruby
|
||||
FROM ghcr.io/moritzheiber/ruby-jemalloc:3.2.2-slim as ruby
|
||||
FROM node:${NODE_VERSION} as build
|
||||
|
||||
COPY --link --from=ruby /opt/ruby /opt/ruby
|
||||
|
@ -18,7 +18,6 @@ COPY Gemfile* package.json yarn.lock /opt/mastodon/
|
|||
# hadolint ignore=DL3008
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential \
|
||||
ca-certificates \
|
||||
git \
|
||||
libicu-dev \
|
||||
libidn11-dev \
|
||||
|
@ -37,7 +36,7 @@ RUN apt-get update && \
|
|||
bundle config set --local without 'development test' && \
|
||||
bundle config set silence_root_warning true && \
|
||||
bundle install -j"$(nproc)" && \
|
||||
yarn install --pure-lockfile --network-timeout 600000 && \
|
||||
yarn install --pure-lockfile --production --network-timeout 600000 && \
|
||||
yarn cache clean
|
||||
|
||||
FROM node:${NODE_VERSION}
|
||||
|
|
23
Gemfile
23
Gemfile
|
@ -5,7 +5,7 @@ ruby '>= 2.7.0', '< 3.3.0'
|
|||
|
||||
gem 'pkg-config', '~> 1.5'
|
||||
|
||||
gem 'puma', '~> 6.1'
|
||||
gem 'puma', '~> 6.2'
|
||||
gem 'rails', '~> 6.1.7'
|
||||
gem 'sprockets', '~> 3.7.2'
|
||||
gem 'thor', '~> 1.2'
|
||||
|
@ -17,7 +17,7 @@ gem 'makara', '~> 0.5'
|
|||
gem 'pghero'
|
||||
gem 'dotenv-rails', '~> 2.8'
|
||||
|
||||
gem 'aws-sdk-s3', '~> 1.119', require: false
|
||||
gem 'aws-sdk-s3', '~> 1.120', require: false
|
||||
gem 'fog-core', '<= 2.4.0'
|
||||
gem 'fog-openstack', '~> 0.3', require: false
|
||||
gem 'kt-paperclip', '~> 7.1', github: 'kreeti/kt-paperclip', ref: '11abf222dc31bff71160a1d138b445214f434b2b'
|
||||
|
@ -28,7 +28,7 @@ gem 'addressable', '~> 2.8'
|
|||
gem 'bootsnap', '~> 1.16.0', require: false
|
||||
gem 'browser'
|
||||
gem 'charlock_holmes', '~> 0.7.7'
|
||||
gem 'chewy', '~> 7.2'
|
||||
gem 'chewy', '~> 7.3'
|
||||
gem 'devise', '~> 4.9'
|
||||
gem 'devise-two-factor', '~> 4.0'
|
||||
|
||||
|
@ -36,7 +36,7 @@ group :pam_authentication, optional: true do
|
|||
gem 'devise_pam_authenticatable2', '~> 9.2'
|
||||
end
|
||||
|
||||
gem 'net-ldap', '~> 0.17'
|
||||
gem 'net-ldap', '~> 0.18'
|
||||
gem 'omniauth-cas', '~> 2.0'
|
||||
gem 'omniauth-saml', '~> 1.10'
|
||||
gem 'omniauth_openid_connect', '~> 0.6.1'
|
||||
|
@ -69,7 +69,7 @@ gem 'public_suffix', '~> 5.0'
|
|||
gem 'pundit', '~> 2.3'
|
||||
gem 'premailer-rails'
|
||||
gem 'rack-attack', '~> 6.6'
|
||||
gem 'rack-cors', '~> 1.1', require: 'rack/cors'
|
||||
gem 'rack-cors', '~> 2.0', require: 'rack/cors'
|
||||
gem 'rails-i18n', '~> 6.0'
|
||||
gem 'rails-settings-cached', '~> 0.6', git: 'https://github.com/mastodon/rails-settings-cached.git', branch: 'v0.6.6-aliases-true'
|
||||
gem 'redcarpet', '~> 3.6'
|
||||
|
@ -87,10 +87,10 @@ gem 'simple-navigation', '~> 4.4'
|
|||
gem 'simple_form', '~> 5.2'
|
||||
gem 'sprockets-rails', '~> 3.4', require: 'sprockets/railtie'
|
||||
gem 'stoplight', '~> 3.0.1'
|
||||
gem 'strong_migrations', '~> 0.7'
|
||||
gem 'strong_migrations', '~> 0.8'
|
||||
gem 'tty-prompt', '~> 0.23', require: false
|
||||
gem 'twitter-text', '~> 3.1.0'
|
||||
gem 'tzinfo-data', '~> 1.2022'
|
||||
gem 'tzinfo-data', '~> 1.2023'
|
||||
gem 'webpacker', '~> 5.4'
|
||||
gem 'webpush', github: 'ClearlyClaire/webpush', ref: 'f14a4d52e201128b1b00245d11b6de80d6cfdcd9'
|
||||
gem 'webauthn', '~> 3.0'
|
||||
|
@ -105,6 +105,8 @@ group :development, :test do
|
|||
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
|
||||
|
@ -116,11 +118,11 @@ group :production, :test do
|
|||
end
|
||||
|
||||
group :test do
|
||||
gem 'capybara', '~> 3.38'
|
||||
gem 'capybara', '~> 3.39'
|
||||
gem 'climate_control'
|
||||
gem 'faker', '~> 3.1'
|
||||
gem 'json-schema', '~> 3.0'
|
||||
gem 'rack-test', '~> 2.0'
|
||||
gem 'rack-test', '~> 2.1'
|
||||
gem 'rails-controller-testing', '~> 1.0'
|
||||
gem 'rspec_junit_formatter', '~> 0.6'
|
||||
gem 'rspec-sidekiq', '~> 3.1'
|
||||
|
@ -129,16 +131,15 @@ group :test do
|
|||
end
|
||||
|
||||
group :development do
|
||||
gem 'active_record_query_trace', '~> 1.8'
|
||||
gem 'annotate', '~> 3.2'
|
||||
gem 'better_errors', '~> 2.9'
|
||||
gem 'binding_of_caller', '~> 1.0'
|
||||
gem 'bullet', '~> 7.0'
|
||||
gem 'letter_opener', '~> 1.8'
|
||||
gem 'letter_opener_web', '~> 2.0'
|
||||
gem 'memory_profiler'
|
||||
gem 'brakeman', '~> 5.4', require: false
|
||||
gem 'bundler-audit', '~> 0.9', require: false
|
||||
gem 'haml_lint', require: false
|
||||
|
||||
gem 'capistrano', '~> 3.17'
|
||||
gem 'capistrano-rails', '~> 1.6'
|
||||
|
|
206
Gemfile.lock
206
Gemfile.lock
|
@ -30,40 +30,40 @@ GIT
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (6.1.7.2)
|
||||
actionpack (= 6.1.7.2)
|
||||
activesupport (= 6.1.7.2)
|
||||
actioncable (6.1.7.3)
|
||||
actionpack (= 6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (6.1.7.2)
|
||||
actionpack (= 6.1.7.2)
|
||||
activejob (= 6.1.7.2)
|
||||
activerecord (= 6.1.7.2)
|
||||
activestorage (= 6.1.7.2)
|
||||
activesupport (= 6.1.7.2)
|
||||
actionmailbox (6.1.7.3)
|
||||
actionpack (= 6.1.7.3)
|
||||
activejob (= 6.1.7.3)
|
||||
activerecord (= 6.1.7.3)
|
||||
activestorage (= 6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
mail (>= 2.7.1)
|
||||
actionmailer (6.1.7.2)
|
||||
actionpack (= 6.1.7.2)
|
||||
actionview (= 6.1.7.2)
|
||||
activejob (= 6.1.7.2)
|
||||
activesupport (= 6.1.7.2)
|
||||
actionmailer (6.1.7.3)
|
||||
actionpack (= 6.1.7.3)
|
||||
actionview (= 6.1.7.3)
|
||||
activejob (= 6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (6.1.7.2)
|
||||
actionview (= 6.1.7.2)
|
||||
activesupport (= 6.1.7.2)
|
||||
actionpack (6.1.7.3)
|
||||
actionview (= 6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
rack (~> 2.0, >= 2.0.9)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (6.1.7.2)
|
||||
actionpack (= 6.1.7.2)
|
||||
activerecord (= 6.1.7.2)
|
||||
activestorage (= 6.1.7.2)
|
||||
activesupport (= 6.1.7.2)
|
||||
actiontext (6.1.7.3)
|
||||
actionpack (= 6.1.7.3)
|
||||
activerecord (= 6.1.7.3)
|
||||
activestorage (= 6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (6.1.7.2)
|
||||
activesupport (= 6.1.7.2)
|
||||
actionview (6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
|
@ -73,29 +73,28 @@ GEM
|
|||
activemodel (>= 4.1, < 7.1)
|
||||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||
active_record_query_trace (1.8)
|
||||
activejob (6.1.7.2)
|
||||
activesupport (= 6.1.7.2)
|
||||
activejob (6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (6.1.7.2)
|
||||
activesupport (= 6.1.7.2)
|
||||
activerecord (6.1.7.2)
|
||||
activemodel (= 6.1.7.2)
|
||||
activesupport (= 6.1.7.2)
|
||||
activestorage (6.1.7.2)
|
||||
actionpack (= 6.1.7.2)
|
||||
activejob (= 6.1.7.2)
|
||||
activerecord (= 6.1.7.2)
|
||||
activesupport (= 6.1.7.2)
|
||||
activemodel (6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
activerecord (6.1.7.3)
|
||||
activemodel (= 6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
activestorage (6.1.7.3)
|
||||
actionpack (= 6.1.7.3)
|
||||
activejob (= 6.1.7.3)
|
||||
activerecord (= 6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
marcel (~> 1.0)
|
||||
mini_mime (>= 1.1.0)
|
||||
activesupport (6.1.7.2)
|
||||
activesupport (6.1.7.3)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
tzinfo (~> 2.0)
|
||||
zeitwerk (~> 2.3)
|
||||
addressable (2.8.1)
|
||||
addressable (2.8.4)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
aes_key_wrap (1.1.0)
|
||||
airbrussh (1.4.1)
|
||||
|
@ -110,16 +109,16 @@ GEM
|
|||
attr_required (1.0.1)
|
||||
awrence (1.2.1)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.711.0)
|
||||
aws-sdk-core (3.170.0)
|
||||
aws-partitions (1.743.0)
|
||||
aws-sdk-core (3.171.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.62.0)
|
||||
aws-sdk-kms (1.63.0)
|
||||
aws-sdk-core (~> 3, >= 3.165.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.119.1)
|
||||
aws-sdk-s3 (1.120.1)
|
||||
aws-sdk-core (~> 3, >= 3.165.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.4)
|
||||
|
@ -149,9 +148,6 @@ GEM
|
|||
concurrent-ruby (~> 1.0, >= 1.0.5)
|
||||
redis (>= 1.0, < 6)
|
||||
builder (3.2.4)
|
||||
bullet (7.0.7)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.11)
|
||||
bundler-audit (0.9.1)
|
||||
bundler (>= 1.2.0, < 3)
|
||||
thor (~> 1.0)
|
||||
|
@ -170,7 +166,7 @@ GEM
|
|||
sshkit (~> 1.3)
|
||||
capistrano-yarn (2.0.2)
|
||||
capistrano (~> 3.0)
|
||||
capybara (3.38.0)
|
||||
capybara (3.39.0)
|
||||
addressable
|
||||
matrix
|
||||
mini_mime (>= 0.1.3)
|
||||
|
@ -183,7 +179,7 @@ GEM
|
|||
activesupport
|
||||
cbor (0.5.9.6)
|
||||
charlock_holmes (0.7.7)
|
||||
chewy (7.2.7)
|
||||
chewy (7.3.0)
|
||||
activesupport (>= 5.2)
|
||||
elasticsearch (>= 7.12.0, < 7.14.0)
|
||||
elasticsearch-dsl
|
||||
|
@ -204,7 +200,7 @@ GEM
|
|||
addressable
|
||||
date (3.3.3)
|
||||
debug_inspector (1.0.0)
|
||||
devise (4.9.0)
|
||||
devise (4.9.2)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 4.1.0)
|
||||
|
@ -225,7 +221,7 @@ GEM
|
|||
docile (1.4.0)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
doorkeeper (5.6.5)
|
||||
doorkeeper (5.6.6)
|
||||
railties (>= 5)
|
||||
dotenv (2.8.1)
|
||||
dotenv-rails (2.8.1)
|
||||
|
@ -308,6 +304,12 @@ GEM
|
|||
activesupport (>= 5.1)
|
||||
haml (>= 4.0.6)
|
||||
railties (>= 5.1)
|
||||
haml_lint (0.45.0)
|
||||
haml (>= 4.0, < 6.2)
|
||||
parallel (~> 1.10)
|
||||
rainbow
|
||||
rubocop (>= 0.50.0)
|
||||
sysexits (~> 1.1)
|
||||
hashdiff (1.0.1)
|
||||
hashie (5.0.0)
|
||||
highline (2.0.3)
|
||||
|
@ -394,7 +396,7 @@ GEM
|
|||
activesupport (>= 4)
|
||||
railties (>= 4)
|
||||
request_store (~> 1.0)
|
||||
loofah (2.19.1)
|
||||
loofah (2.20.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.8.1)
|
||||
|
@ -424,7 +426,7 @@ GEM
|
|||
net-imap (0.3.4)
|
||||
date
|
||||
net-protocol
|
||||
net-ldap (0.17.1)
|
||||
net-ldap (0.18.0)
|
||||
net-pop (0.1.2)
|
||||
net-protocol
|
||||
net-protocol (0.2.1)
|
||||
|
@ -434,8 +436,8 @@ GEM
|
|||
net-smtp (0.3.3)
|
||||
net-protocol
|
||||
net-ssh (7.0.1)
|
||||
nio4r (2.5.8)
|
||||
nokogiri (1.14.2)
|
||||
nio4r (2.5.9)
|
||||
nokogiri (1.14.3)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
racc (~> 1.4)
|
||||
nsa (0.2.8)
|
||||
|
@ -443,7 +445,7 @@ GEM
|
|||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
sidekiq (>= 3.5)
|
||||
statsd-ruby (~> 1.4, >= 1.4.0)
|
||||
oj (3.14.2)
|
||||
oj (3.14.3)
|
||||
omniauth (1.9.2)
|
||||
hashie (>= 3.4.6)
|
||||
rack (>= 1.6.2, < 3)
|
||||
|
@ -477,13 +479,13 @@ GEM
|
|||
orm_adapter (0.5.0)
|
||||
ox (2.14.14)
|
||||
parallel (1.22.1)
|
||||
parser (3.2.1.0)
|
||||
parser (3.2.2.0)
|
||||
ast (~> 2.4.1)
|
||||
parslet (2.0.0)
|
||||
pastel (0.8.0)
|
||||
tty-color (~> 0.5)
|
||||
pg (1.4.6)
|
||||
pghero (3.3.0)
|
||||
pghero (3.3.1)
|
||||
activerecord (>= 6)
|
||||
pkg-config (1.5.1)
|
||||
posix-spawn (0.3.15)
|
||||
|
@ -497,16 +499,16 @@ GEM
|
|||
premailer (~> 1.7, >= 1.7.9)
|
||||
private_address_check (0.5.0)
|
||||
public_suffix (5.0.1)
|
||||
puma (6.1.1)
|
||||
puma (6.2.1)
|
||||
nio4r (~> 2.0)
|
||||
pundit (2.3.0)
|
||||
activesupport (>= 3.0.0)
|
||||
raabro (1.4.0)
|
||||
racc (1.6.2)
|
||||
rack (2.2.6.3)
|
||||
rack (2.2.6.4)
|
||||
rack-attack (6.6.1)
|
||||
rack (>= 1.0, < 3)
|
||||
rack-cors (1.1.1)
|
||||
rack-cors (2.0.1)
|
||||
rack (>= 2.0.0)
|
||||
rack-oauth2 (1.21.3)
|
||||
activesupport
|
||||
|
@ -516,22 +518,22 @@ GEM
|
|||
rack (>= 2.1.0)
|
||||
rack-proxy (0.7.6)
|
||||
rack
|
||||
rack-test (2.0.2)
|
||||
rack-test (2.1.0)
|
||||
rack (>= 1.3)
|
||||
rails (6.1.7.2)
|
||||
actioncable (= 6.1.7.2)
|
||||
actionmailbox (= 6.1.7.2)
|
||||
actionmailer (= 6.1.7.2)
|
||||
actionpack (= 6.1.7.2)
|
||||
actiontext (= 6.1.7.2)
|
||||
actionview (= 6.1.7.2)
|
||||
activejob (= 6.1.7.2)
|
||||
activemodel (= 6.1.7.2)
|
||||
activerecord (= 6.1.7.2)
|
||||
activestorage (= 6.1.7.2)
|
||||
activesupport (= 6.1.7.2)
|
||||
rails (6.1.7.3)
|
||||
actioncable (= 6.1.7.3)
|
||||
actionmailbox (= 6.1.7.3)
|
||||
actionmailer (= 6.1.7.3)
|
||||
actionpack (= 6.1.7.3)
|
||||
actiontext (= 6.1.7.3)
|
||||
actionview (= 6.1.7.3)
|
||||
activejob (= 6.1.7.3)
|
||||
activemodel (= 6.1.7.3)
|
||||
activerecord (= 6.1.7.3)
|
||||
activestorage (= 6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 6.1.7.2)
|
||||
railties (= 6.1.7.3)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-controller-testing (1.0.5)
|
||||
actionpack (>= 5.0.1.rc1)
|
||||
|
@ -545,9 +547,9 @@ GEM
|
|||
rails-i18n (6.0.0)
|
||||
i18n (>= 0.7, < 2)
|
||||
railties (>= 6.0.0, < 7)
|
||||
railties (6.1.7.2)
|
||||
actionpack (= 6.1.7.2)
|
||||
activesupport (= 6.1.7.2)
|
||||
railties (6.1.7.3)
|
||||
actionpack (= 6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
method_source
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0)
|
||||
|
@ -599,31 +601,31 @@ GEM
|
|||
rspec_chunked (0.6)
|
||||
rspec_junit_formatter (0.6.0)
|
||||
rspec-core (>= 2, < 4, != 2.12.0)
|
||||
rubocop (1.48.0)
|
||||
rubocop (1.49.0)
|
||||
json (~> 2.3)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.2.0.0)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.8, < 3.0)
|
||||
rexml (>= 3.2.5, < 4.0)
|
||||
rubocop-ast (>= 1.26.0, < 2.0)
|
||||
rubocop-ast (>= 1.28.0, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 3.0)
|
||||
rubocop-ast (1.27.0)
|
||||
rubocop-ast (1.28.0)
|
||||
parser (>= 3.2.1.0)
|
||||
rubocop-capybara (2.17.0)
|
||||
rubocop-capybara (2.17.1)
|
||||
rubocop (~> 1.41)
|
||||
rubocop-performance (1.16.0)
|
||||
rubocop-performance (1.17.1)
|
||||
rubocop (>= 1.7.0, < 2.0)
|
||||
rubocop-ast (>= 0.4.0)
|
||||
rubocop-rails (2.17.4)
|
||||
rubocop-rails (2.18.0)
|
||||
activesupport (>= 4.2.0)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 1.33.0, < 2.0)
|
||||
rubocop-rspec (2.18.1)
|
||||
rubocop-rspec (2.19.0)
|
||||
rubocop (~> 1.33)
|
||||
rubocop-capybara (~> 2.17)
|
||||
ruby-progressbar (1.11.0)
|
||||
ruby-progressbar (1.13.0)
|
||||
ruby-saml (1.13.0)
|
||||
nokogiri (>= 1.10.5)
|
||||
rexml
|
||||
|
@ -677,16 +679,17 @@ GEM
|
|||
sshkit (1.21.4)
|
||||
net-scp (>= 1.1.2)
|
||||
net-ssh (>= 2.8.0)
|
||||
stackprof (0.2.23)
|
||||
stackprof (0.2.25)
|
||||
statsd-ruby (1.5.0)
|
||||
stoplight (3.0.1)
|
||||
redlock (~> 1.0)
|
||||
strong_migrations (0.7.9)
|
||||
activerecord (>= 5)
|
||||
strong_migrations (0.8.0)
|
||||
activerecord (>= 5.2)
|
||||
swd (1.3.0)
|
||||
activesupport (>= 3)
|
||||
attr_required (>= 0.0.5)
|
||||
httpclient (>= 2.4)
|
||||
sysexits (1.2.0)
|
||||
temple (0.10.0)
|
||||
terminal-table (3.0.2)
|
||||
unicode-display_width (>= 1.1.1, < 3)
|
||||
|
@ -714,14 +717,13 @@ GEM
|
|||
unf (~> 0.1.0)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
tzinfo-data (1.2022.7)
|
||||
tzinfo-data (1.2023.3)
|
||||
tzinfo (>= 1.0.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.8.2)
|
||||
unicode-display_width (2.4.2)
|
||||
uniform_notifier (1.16.0)
|
||||
uri (0.12.0)
|
||||
uri (0.12.1)
|
||||
validate_email (0.1.6)
|
||||
activemodel (>= 3.0)
|
||||
mail (>= 2.2.5)
|
||||
|
@ -765,25 +767,23 @@ PLATFORMS
|
|||
|
||||
DEPENDENCIES
|
||||
active_model_serializers (~> 0.10)
|
||||
active_record_query_trace (~> 1.8)
|
||||
addressable (~> 2.8)
|
||||
annotate (~> 3.2)
|
||||
aws-sdk-s3 (~> 1.119)
|
||||
aws-sdk-s3 (~> 1.120)
|
||||
better_errors (~> 2.9)
|
||||
binding_of_caller (~> 1.0)
|
||||
blurhash (~> 0.1)
|
||||
bootsnap (~> 1.16.0)
|
||||
brakeman (~> 5.4)
|
||||
browser
|
||||
bullet (~> 7.0)
|
||||
bundler-audit (~> 0.9)
|
||||
capistrano (~> 3.17)
|
||||
capistrano-rails (~> 1.6)
|
||||
capistrano-rbenv (~> 2.2)
|
||||
capistrano-yarn (~> 2.0)
|
||||
capybara (~> 3.38)
|
||||
capybara (~> 3.39)
|
||||
charlock_holmes (~> 0.7.7)
|
||||
chewy (~> 7.2)
|
||||
chewy (~> 7.3)
|
||||
climate_control
|
||||
cocoon (~> 1.2)
|
||||
color_diff (~> 0.1)
|
||||
|
@ -804,6 +804,7 @@ DEPENDENCIES
|
|||
fog-openstack (~> 0.3)
|
||||
fuubar (~> 2.5)
|
||||
haml-rails (~> 2.0)
|
||||
haml_lint
|
||||
hiredis (~> 0.6)
|
||||
htmlentities (~> 4.3)
|
||||
http (~> 5.1)
|
||||
|
@ -825,7 +826,7 @@ DEPENDENCIES
|
|||
memory_profiler
|
||||
mime-types (~> 3.4.1)
|
||||
net-http (~> 0.3.2)
|
||||
net-ldap (~> 0.17)
|
||||
net-ldap (~> 0.18)
|
||||
nokogiri (~> 1.14)
|
||||
nsa (~> 0.2)
|
||||
oj (~> 3.14)
|
||||
|
@ -843,12 +844,12 @@ DEPENDENCIES
|
|||
premailer-rails
|
||||
private_address_check (~> 0.5)
|
||||
public_suffix (~> 5.0)
|
||||
puma (~> 6.1)
|
||||
puma (~> 6.2)
|
||||
pundit (~> 2.3)
|
||||
rack (~> 2.2.6)
|
||||
rack-attack (~> 6.6)
|
||||
rack-cors (~> 1.1)
|
||||
rack-test (~> 2.0)
|
||||
rack-cors (~> 2.0)
|
||||
rack-test (~> 2.1)
|
||||
rails (~> 6.1.7)
|
||||
rails-controller-testing (~> 1.0)
|
||||
rails-i18n (~> 6.0)
|
||||
|
@ -863,6 +864,7 @@ DEPENDENCIES
|
|||
rspec_chunked (~> 0.6)
|
||||
rspec_junit_formatter (~> 0.6)
|
||||
rubocop
|
||||
rubocop-capybara
|
||||
rubocop-performance
|
||||
rubocop-rails
|
||||
rubocop-rspec
|
||||
|
@ -880,11 +882,11 @@ DEPENDENCIES
|
|||
sprockets-rails (~> 3.4)
|
||||
stackprof
|
||||
stoplight (~> 3.0.1)
|
||||
strong_migrations (~> 0.7)
|
||||
strong_migrations (~> 0.8)
|
||||
thor (~> 1.2)
|
||||
tty-prompt (~> 0.23)
|
||||
twitter-text (~> 3.1.0)
|
||||
tzinfo-data (~> 1.2022)
|
||||
tzinfo-data (~> 1.2023)
|
||||
webauthn (~> 3.0)
|
||||
webmock (~> 3.18)
|
||||
webpacker (~> 5.4)
|
||||
|
|
|
@ -6,11 +6,9 @@
|
|||
|
||||
[][releases]
|
||||
[](https://github.com/mastodon/mastodon/actions/workflows/test-ruby.yml)
|
||||
[][code_climate]
|
||||
[][crowdin]
|
||||
|
||||
[releases]: https://github.com/mastodon/mastodon/releases
|
||||
[code_climate]: https://codeclimate.com/github/mastodon/mastodon
|
||||
[crowdin]: https://crowdin.com/project/mastodon
|
||||
|
||||
Mastodon is a **free, open-source social network server** based on ActivityPub where users can follow friends and discover new ones. On Mastodon, users can publish anything they want: links, pictures, text, video. All Mastodon servers are interoperable as a federated network (users on one server can seamlessly communicate with users from another one, including non-Mastodon software that implements ActivityPub!)
|
||||
|
@ -28,6 +26,7 @@ Click below to **learn more** in a video:
|
|||
- [View sponsors](https://joinmastodon.org/sponsors)
|
||||
- [Blog](https://blog.joinmastodon.org)
|
||||
- [Documentation](https://docs.joinmastodon.org)
|
||||
- [Roadmap](https://joinmastodon.org/roadmap)
|
||||
- [Official Docker image](https://github.com/mastodon/mastodon/pkgs/container/mastodon)
|
||||
- [Browse Mastodon servers](https://joinmastodon.org/communities)
|
||||
- [Browse Mastodon apps](https://joinmastodon.org/apps)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
module Admin
|
||||
class DomainBlocksController < BaseController
|
||||
before_action :set_domain_block, only: [:show, :destroy, :edit, :update]
|
||||
before_action :set_domain_block, only: [:destroy, :edit, :update]
|
||||
|
||||
def batch
|
||||
authorize :domain_block, :create?
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
module Admin
|
||||
class EmailDomainBlocksController < BaseController
|
||||
before_action :set_email_domain_block, only: [:show, :destroy]
|
||||
|
||||
def index
|
||||
authorize :email_domain_block, :index?
|
||||
|
||||
|
@ -59,10 +57,6 @@ module Admin
|
|||
|
||||
private
|
||||
|
||||
def set_email_domain_block
|
||||
@email_domain_block = EmailDomainBlock.find(params[:id])
|
||||
end
|
||||
|
||||
def set_resolved_records
|
||||
Resolv::DNS.open do |dns|
|
||||
dns.timeouts = 5
|
||||
|
|
|
@ -13,7 +13,7 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
|
|||
def update
|
||||
@account = current_account
|
||||
UpdateAccountService.new.call(@account, account_params, raise_error: true)
|
||||
UserSettingsDecorator.new(current_user).update(user_settings_params) if user_settings_params
|
||||
current_user.update(user_params) if user_params
|
||||
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
|
||||
render json: @account, serializer: REST::CredentialAccountSerializer
|
||||
end
|
||||
|
@ -35,15 +35,18 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
|
|||
)
|
||||
end
|
||||
|
||||
def user_settings_params
|
||||
def user_params
|
||||
return nil if params[:source].blank?
|
||||
|
||||
source_params = params.require(:source)
|
||||
|
||||
{
|
||||
'setting_default_privacy' => source_params.fetch(:privacy, @account.user.setting_default_privacy),
|
||||
'setting_default_sensitive' => source_params.fetch(:sensitive, @account.user.setting_default_sensitive),
|
||||
'setting_default_language' => source_params.fetch(:language, @account.user.setting_default_language),
|
||||
settings_attributes: {
|
||||
default_privacy: source_params.fetch(:privacy, @account.user.setting_default_privacy),
|
||||
default_searchability: source_params.fetch(:searchability, @account.user.setting_default_searchability),
|
||||
default_sensitive: source_params.fetch(:sensitive, @account.user.setting_default_sensitive),
|
||||
default_language: source_params.fetch(:language, @account.user.setting_default_language),
|
||||
},
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Instances::TranslationLanguagesController < Api::BaseController
|
||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
||||
|
||||
before_action :set_languages
|
||||
|
||||
def show
|
||||
expires_in 1.day, public: true
|
||||
render json: @languages
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_languages
|
||||
if TranslationService.configured?
|
||||
@languages = Rails.cache.fetch('translation_service/languages', expires_in: 7.days, race_condition_ttl: 1.hour) { TranslationService.configured.languages }
|
||||
@languages['und'] = @languages.delete(nil) if @languages.key?(nil)
|
||||
else
|
||||
@languages = {}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5,7 +5,7 @@ class Api::V1::StreamingController < Api::BaseController
|
|||
if Rails.configuration.x.streaming_api_base_url == request.host
|
||||
not_found
|
||||
else
|
||||
redirect_to streaming_api_url, status: 301
|
||||
redirect_to streaming_api_url, status: 301, allow_other_host: true
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@ class ApplicationController < ActionController::Base
|
|||
helper_method :current_theme
|
||||
helper_method :single_user_mode?
|
||||
helper_method :use_seamless_external_login?
|
||||
helper_method :omniauth_only?
|
||||
helper_method :sso_account_settings
|
||||
helper_method :whitelist_mode?
|
||||
|
||||
rescue_from ActionController::ParameterMissing, Paperclip::AdapterRegistry::NoHandlerError, with: :bad_request
|
||||
|
@ -61,7 +63,11 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
|
||||
def after_sign_out_path_for(_resource_or_scope)
|
||||
new_user_session_path
|
||||
if ENV['OMNIAUTH_ONLY'] == 'true' && ENV['OIDC_ENABLED'] == 'true'
|
||||
'/auth/auth/openid_connect/logout'
|
||||
else
|
||||
new_user_session_path
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
@ -114,6 +120,14 @@ class ApplicationController < ActionController::Base
|
|||
Devise.pam_authentication || Devise.ldap_authentication
|
||||
end
|
||||
|
||||
def omniauth_only?
|
||||
ENV['OMNIAUTH_ONLY'] == 'true'
|
||||
end
|
||||
|
||||
def sso_account_settings
|
||||
ENV.fetch('SSO_ACCOUNT_SETTINGS')
|
||||
end
|
||||
|
||||
def current_account
|
||||
return @current_account if defined?(@current_account)
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ class BackupsController < ApplicationController
|
|||
when :s3
|
||||
redirect_to @backup.dump.expiring_url(10)
|
||||
when :fog
|
||||
if Paperclip::Attachment.default_options.dig(:storage, :fog_credentials, :openstack_temp_url_key).present?
|
||||
if Paperclip::Attachment.default_options.dig(:fog_credentials, :openstack_temp_url_key).present?
|
||||
redirect_to @backup.dump.expiring_url(Time.now.utc + 10)
|
||||
else
|
||||
redirect_to full_asset_url(@backup.dump.url)
|
||||
|
|
|
@ -10,7 +10,8 @@ module AccountControllerConcern
|
|||
|
||||
included do
|
||||
before_action :set_instance_presenter
|
||||
before_action :set_link_headers, if: -> { request.format.nil? || request.format == :html }
|
||||
|
||||
after_action :set_link_headers, if: -> { request.format.nil? || request.format == :html }
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -3,6 +3,158 @@
|
|||
module CacheConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
module ActiveRecordCoder
|
||||
EMPTY_HASH = {}.freeze
|
||||
|
||||
class << self
|
||||
def dump(record)
|
||||
instances = InstanceTracker.new
|
||||
serialized_associations = serialize_associations(record, instances)
|
||||
serialized_records = instances.map { |r| serialize_record(r) }
|
||||
[serialized_associations, *serialized_records]
|
||||
end
|
||||
|
||||
def load(payload)
|
||||
instances = InstanceTracker.new
|
||||
serialized_associations, *serialized_records = payload
|
||||
serialized_records.each { |attrs| instances.push(deserialize_record(*attrs)) }
|
||||
deserialize_associations(serialized_associations, instances)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Records without associations, or which have already been visited before,
|
||||
# are serialized by their id alone.
|
||||
#
|
||||
# Records with associations are serialized as a two-element array including
|
||||
# their id and the record's association cache.
|
||||
#
|
||||
def serialize_associations(record, instances)
|
||||
return unless record
|
||||
|
||||
if (id = instances.lookup(record))
|
||||
payload = id
|
||||
else
|
||||
payload = instances.push(record)
|
||||
|
||||
cached_associations = record.class.reflect_on_all_associations.select do |reflection|
|
||||
record.association_cached?(reflection.name)
|
||||
end
|
||||
|
||||
unless cached_associations.empty?
|
||||
serialized_associations = cached_associations.map do |reflection|
|
||||
association = record.association(reflection.name)
|
||||
|
||||
serialized_target = if reflection.collection?
|
||||
association.target.map { |target_record| serialize_associations(target_record, instances) }
|
||||
else
|
||||
serialize_associations(association.target, instances)
|
||||
end
|
||||
|
||||
[reflection.name, serialized_target]
|
||||
end
|
||||
|
||||
payload = [payload, serialized_associations]
|
||||
end
|
||||
end
|
||||
|
||||
payload
|
||||
end
|
||||
|
||||
def deserialize_associations(payload, instances)
|
||||
return unless payload
|
||||
|
||||
id, associations = payload
|
||||
record = instances.fetch(id)
|
||||
|
||||
associations&.each do |name, serialized_target|
|
||||
begin
|
||||
association = record.association(name)
|
||||
rescue ActiveRecord::AssociationNotFoundError
|
||||
raise AssociationMissingError, "undefined association: #{name}"
|
||||
end
|
||||
|
||||
target = if association.reflection.collection?
|
||||
serialized_target.map! { |serialized_record| deserialize_associations(serialized_record, instances) }
|
||||
else
|
||||
deserialize_associations(serialized_target, instances)
|
||||
end
|
||||
|
||||
association.target = target
|
||||
end
|
||||
|
||||
record
|
||||
end
|
||||
|
||||
def serialize_record(record)
|
||||
arguments = [record.class.name, attributes_for_database(record)]
|
||||
arguments << true if record.new_record?
|
||||
arguments
|
||||
end
|
||||
|
||||
if Rails.gem_version >= Gem::Version.new('7.0')
|
||||
def attributes_for_database(record)
|
||||
attributes = record.attributes_for_database
|
||||
attributes.transform_values! { |attr| attr.is_a?(::ActiveModel::Type::Binary::Data) ? attr.to_s : attr }
|
||||
attributes
|
||||
end
|
||||
else
|
||||
def attributes_for_database(record)
|
||||
attributes = record.instance_variable_get(:@attributes).send(:attributes).transform_values(&:value_for_database)
|
||||
attributes.transform_values! { |attr| attr.is_a?(::ActiveModel::Type::Binary::Data) ? attr.to_s : attr }
|
||||
attributes
|
||||
end
|
||||
end
|
||||
|
||||
def deserialize_record(class_name, attributes_from_database, new_record = false) # rubocop:disable Style/OptionalBooleanParameter
|
||||
begin
|
||||
klass = Object.const_get(class_name)
|
||||
rescue NameError
|
||||
raise ClassMissingError, "undefined class: #{class_name}"
|
||||
end
|
||||
|
||||
# Ideally we'd like to call `klass.instantiate`, however it doesn't allow to pass
|
||||
# wether the record was persisted or not.
|
||||
attributes = klass.attributes_builder.build_from_database(attributes_from_database, EMPTY_HASH)
|
||||
klass.allocate.init_with_attributes(attributes, new_record)
|
||||
end
|
||||
end
|
||||
|
||||
class Error < StandardError
|
||||
end
|
||||
|
||||
class ClassMissingError < Error
|
||||
end
|
||||
|
||||
class AssociationMissingError < Error
|
||||
end
|
||||
|
||||
class InstanceTracker
|
||||
def initialize
|
||||
@instances = []
|
||||
@ids = {}.compare_by_identity
|
||||
end
|
||||
|
||||
def map(&block)
|
||||
@instances.map(&block)
|
||||
end
|
||||
|
||||
def fetch(...)
|
||||
@instances.fetch(...)
|
||||
end
|
||||
|
||||
def push(instance)
|
||||
id = @ids[instance] = @instances.size
|
||||
@instances << instance
|
||||
id
|
||||
end
|
||||
|
||||
def lookup(instance)
|
||||
@ids[instance]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def render_with_cache(**options)
|
||||
raise ArgumentError, 'only JSON render calls are supported' unless options.key?(:json) || block_given?
|
||||
|
||||
|
@ -34,8 +186,13 @@ module CacheConcern
|
|||
raw = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation)
|
||||
return [] if raw.empty?
|
||||
|
||||
cached_keys_with_value = Rails.cache.read_multi(*raw).transform_keys(&:id)
|
||||
uncached_ids = raw.map(&:id) - cached_keys_with_value.keys
|
||||
cached_keys_with_value = begin
|
||||
Rails.cache.read_multi(*raw).transform_keys(&:id).transform_values { |r| ActiveRecordCoder.load(r) }
|
||||
rescue ActiveRecordCoder::Error
|
||||
{} # The serialization format may have changed, let's pretend it's a cache miss.
|
||||
end
|
||||
|
||||
uncached_ids = raw.map(&:id) - cached_keys_with_value.keys
|
||||
|
||||
klass.reload_stale_associations!(cached_keys_with_value.values) if klass.respond_to?(:reload_stale_associations!)
|
||||
|
||||
|
@ -43,7 +200,7 @@ module CacheConcern
|
|||
uncached = klass.where(id: uncached_ids).with_includes.index_by(&:id)
|
||||
|
||||
uncached.each_value do |item|
|
||||
Rails.cache.write(item, item)
|
||||
Rails.cache.write(item, ActiveRecordCoder.dump(item))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -138,7 +138,7 @@ module SignatureVerification
|
|||
end
|
||||
|
||||
def signed_headers
|
||||
signature_params.fetch('headers', signature_algorithm == 'hs2019' ? '(created)' : 'date').downcase.split(' ')
|
||||
signature_params.fetch('headers', signature_algorithm == 'hs2019' ? '(created)' : 'date').downcase.split
|
||||
end
|
||||
|
||||
def verify_signature_strength!
|
||||
|
|
|
@ -23,7 +23,7 @@ class MediaProxyController < ApplicationController
|
|||
redownload! if @media_attachment.needs_redownload? && !reject_media?
|
||||
end
|
||||
|
||||
redirect_to full_asset_url(@media_attachment.file.url(version))
|
||||
redirect_to full_asset_url(@media_attachment.file.url(version)), allow_other_host: true
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -4,8 +4,6 @@ class Settings::PreferencesController < Settings::BaseController
|
|||
def show; end
|
||||
|
||||
def update
|
||||
user_settings.update(user_settings_params.to_h)
|
||||
|
||||
if current_user.update(user_params)
|
||||
I18n.locale = current_user.locale
|
||||
redirect_to after_update_redirect_path, notice: I18n.t('generic.changes_saved_msg')
|
||||
|
@ -20,46 +18,7 @@ class Settings::PreferencesController < Settings::BaseController
|
|||
settings_preferences_path
|
||||
end
|
||||
|
||||
def user_settings
|
||||
UserSettingsDecorator.new(current_user)
|
||||
end
|
||||
|
||||
def user_params
|
||||
params.require(:user).permit(
|
||||
:locale,
|
||||
chosen_languages: []
|
||||
)
|
||||
end
|
||||
|
||||
def user_settings_params
|
||||
params.require(:user).permit(
|
||||
:setting_default_privacy,
|
||||
:setting_default_searchability,
|
||||
:setting_default_sensitive,
|
||||
:setting_public_post_to_unlisted,
|
||||
:setting_default_language,
|
||||
:setting_unfollow_modal,
|
||||
:setting_boost_modal,
|
||||
:setting_delete_modal,
|
||||
:setting_auto_play_gif,
|
||||
:setting_display_media,
|
||||
:setting_display_media_expand,
|
||||
:setting_expand_spoilers,
|
||||
:setting_reduce_motion,
|
||||
:setting_disable_swiping,
|
||||
:setting_system_font_ui,
|
||||
:setting_noindex,
|
||||
:setting_theme,
|
||||
:setting_aggregate_reblogs,
|
||||
:setting_show_application,
|
||||
:setting_advanced_layout,
|
||||
:setting_use_blurhash,
|
||||
:setting_use_pending_items,
|
||||
:setting_trends,
|
||||
:setting_crop_images,
|
||||
:setting_always_send_emails,
|
||||
notification_emails: %i(follow follow_request reblog favourite mention report pending_account trending_tag appeal),
|
||||
interactions: %i(must_be_follower must_be_following must_be_following_dm)
|
||||
)
|
||||
params.require(:user).permit(:locale, chosen_languages: [], settings_attributes: UserSettings.keys)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,18 +22,9 @@ module Settings
|
|||
|
||||
private
|
||||
|
||||
def confirmation_params
|
||||
params.require(:form_two_factor_confirmation).permit(:otp_attempt)
|
||||
end
|
||||
|
||||
def verify_otp_not_enabled
|
||||
redirect_to settings_two_factor_authentication_methods_path if current_user.otp_enabled?
|
||||
end
|
||||
|
||||
def acceptable_code?
|
||||
current_user.validate_and_consume_otp!(confirmation_params[:otp_attempt]) ||
|
||||
current_user.invalidate_otp_backup_code!(confirmation_params[:otp_attempt])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,11 +9,12 @@ class StatusesController < ApplicationController
|
|||
before_action :require_account_signature!, only: [:show, :activity], if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :set_status
|
||||
before_action :set_instance_presenter
|
||||
before_action :set_link_headers
|
||||
before_action :redirect_to_original, only: :show
|
||||
before_action :set_cache_headers
|
||||
before_action :set_body_classes, only: :embed
|
||||
|
||||
after_action :set_link_headers
|
||||
|
||||
skip_around_action :set_locale, if: -> { request.format == :json }
|
||||
skip_before_action :require_functional!, only: [:show, :embed], unless: :whitelist_mode?
|
||||
|
||||
|
@ -70,6 +71,6 @@ class StatusesController < ApplicationController
|
|||
end
|
||||
|
||||
def redirect_to_original
|
||||
redirect_to ActivityPub::TagManager.instance.url_for(@status.reblog) if @status.reblog?
|
||||
redirect_to(ActivityPub::TagManager.instance.url_for(@status.reblog), allow_other_host: true) if @status.reblog?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -112,7 +112,7 @@ module ApplicationHelper
|
|||
def fa_icon(icon, attributes = {})
|
||||
class_names = attributes[:class]&.split(' ') || []
|
||||
class_names << 'fa'
|
||||
class_names += icon.split(' ').map { |cl| "fa-#{cl}" }
|
||||
class_names += icon.split.map { |cl| "fa-#{cl}" }
|
||||
|
||||
content_tag(:i, nil, attributes.merge(class: class_names.join(' ')))
|
||||
end
|
||||
|
@ -166,7 +166,7 @@ module ApplicationHelper
|
|||
end
|
||||
|
||||
def body_classes
|
||||
output = (@body_classes || '').split(' ')
|
||||
output = (@body_classes || '').split
|
||||
output << "theme-#{current_theme.parameterize}"
|
||||
output << 'system-font' if current_account&.user&.setting_system_font_ui
|
||||
output << (current_account&.user&.setting_reduce_motion ? 'reduce-motion' : 'no-reduce-motion')
|
||||
|
|
|
@ -8,7 +8,7 @@ module HomeHelper
|
|||
end
|
||||
|
||||
def account_link_to(account, button = '', path: nil)
|
||||
content_tag(:div, class: 'account') do
|
||||
content_tag(:div, class: 'account account--minimal') do
|
||||
content_tag(:div, class: 'account__wrapper') do
|
||||
section = if account.nil?
|
||||
content_tag(:div, class: 'account__display-name') do
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# rubocop:disable Metrics/ModuleLength, Style/WordArray
|
||||
# rubocop:disable Metrics/ModuleLength
|
||||
|
||||
module LanguagesHelper
|
||||
ISO_639_1 = {
|
||||
|
@ -275,4 +275,4 @@ module LanguagesHelper
|
|||
end
|
||||
end
|
||||
|
||||
# rubocop:enable Metrics/ModuleLength, Style/WordArray
|
||||
# rubocop:enable Metrics/ModuleLength
|
||||
|
|
17
app/javascript/hooks/useHovering.ts
Normal file
17
app/javascript/hooks/useHovering.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { useCallback, useState } from 'react';
|
||||
|
||||
export const useHovering = (animate?: boolean) => {
|
||||
const [hovering, setHovering] = useState<boolean>(animate ?? false);
|
||||
|
||||
const handleMouseEnter = useCallback(() => {
|
||||
if (animate) return;
|
||||
setHovering(true);
|
||||
}, [animate]);
|
||||
|
||||
const handleMouseLeave = useCallback(() => {
|
||||
if (animate) return;
|
||||
setHovering(false);
|
||||
}, [animate]);
|
||||
|
||||
return { hovering, handleMouseEnter, handleMouseLeave };
|
||||
};
|
|
@ -4,7 +4,6 @@ import { defineMessages } from 'react-intl';
|
|||
import api from 'mastodon/api';
|
||||
import { search as emojiSearch } from 'mastodon/features/emoji/emoji_mart_search_light';
|
||||
import { tagHistory } from 'mastodon/settings';
|
||||
import resizeImage from 'mastodon/utils/resize_image';
|
||||
import { showAlert, showAlertForError } from './alerts';
|
||||
import { useEmoji } from './emojis';
|
||||
import { importFetchedAccounts, importFetchedStatus } from './importer';
|
||||
|
@ -279,46 +278,42 @@ export function uploadCompose(files) {
|
|||
|
||||
dispatch(uploadComposeRequest());
|
||||
|
||||
for (const [i, f] of Array.from(files).entries()) {
|
||||
for (const [i, file] of Array.from(files).entries()) {
|
||||
if (media.size + i >= 4) break;
|
||||
|
||||
resizeImage(f).then(file => {
|
||||
const data = new FormData();
|
||||
data.append('file', file);
|
||||
// Account for disparity in size of original image and resized data
|
||||
total += file.size - f.size;
|
||||
const data = new FormData();
|
||||
data.append('file', file);
|
||||
|
||||
return api(getState).post('/api/v2/media', data, {
|
||||
onUploadProgress: function({ loaded }){
|
||||
progress[i] = loaded;
|
||||
dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total));
|
||||
},
|
||||
}).then(({ status, data }) => {
|
||||
// If server-side processing of the media attachment has not completed yet,
|
||||
// poll the server until it is, before showing the media attachment as uploaded
|
||||
api(getState).post('/api/v2/media', data, {
|
||||
onUploadProgress: function({ loaded }){
|
||||
progress[i] = loaded;
|
||||
dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total));
|
||||
},
|
||||
}).then(({ status, data }) => {
|
||||
// If server-side processing of the media attachment has not completed yet,
|
||||
// poll the server until it is, before showing the media attachment as uploaded
|
||||
|
||||
if (status === 200) {
|
||||
dispatch(uploadComposeSuccess(data, f));
|
||||
} else if (status === 202) {
|
||||
dispatch(uploadComposeProcessing());
|
||||
if (status === 200) {
|
||||
dispatch(uploadComposeSuccess(data, file));
|
||||
} else if (status === 202) {
|
||||
dispatch(uploadComposeProcessing());
|
||||
|
||||
let tryCount = 1;
|
||||
let tryCount = 1;
|
||||
|
||||
const poll = () => {
|
||||
api(getState).get(`/api/v1/media/${data.id}`).then(response => {
|
||||
if (response.status === 200) {
|
||||
dispatch(uploadComposeSuccess(response.data, f));
|
||||
} else if (response.status === 206) {
|
||||
const retryAfter = (Math.log2(tryCount) || 1) * 1000;
|
||||
tryCount += 1;
|
||||
setTimeout(() => poll(), retryAfter);
|
||||
}
|
||||
}).catch(error => dispatch(uploadComposeFail(error)));
|
||||
};
|
||||
const poll = () => {
|
||||
api(getState).get(`/api/v1/media/${data.id}`).then(response => {
|
||||
if (response.status === 200) {
|
||||
dispatch(uploadComposeSuccess(response.data, file));
|
||||
} else if (response.status === 206) {
|
||||
const retryAfter = (Math.log2(tryCount) || 1) * 1000;
|
||||
tryCount += 1;
|
||||
setTimeout(() => poll(), retryAfter);
|
||||
}
|
||||
}).catch(error => dispatch(uploadComposeFail(error)));
|
||||
};
|
||||
|
||||
poll();
|
||||
}
|
||||
});
|
||||
poll();
|
||||
}
|
||||
}).catch(error => dispatch(uploadComposeFail(error)));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -55,7 +55,7 @@ export const synchronouslySubmitMarkers = () => (dispatch, getState) => {
|
|||
client.open('POST', '/api/v1/markers', false);
|
||||
client.setRequestHeader('Content-Type', 'application/json');
|
||||
client.setRequestHeader('Authorization', `Bearer ${accessToken}`);
|
||||
client.SUBMIT(JSON.stringify(params));
|
||||
client.send(JSON.stringify(params));
|
||||
} catch (e) {
|
||||
// Do not make the BeforeUnload handler error out
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ export const PICTURE_IN_PICTURE_REMOVE = 'PICTURE_IN_PICTURE_REMOVE';
|
|||
* @return {object}
|
||||
*/
|
||||
export const deployPictureInPicture = (statusId, accountId, playerType, props) => {
|
||||
// @ts-expect-error
|
||||
return (dispatch, getState) => {
|
||||
// Do not open a player for a toot that does not exist
|
||||
if (getState().hasIn(['statuses', statusId])) {
|
||||
|
|
|
@ -14,6 +14,9 @@ export const SEARCH_EXPAND_REQUEST = 'SEARCH_EXPAND_REQUEST';
|
|||
export const SEARCH_EXPAND_SUCCESS = 'SEARCH_EXPAND_SUCCESS';
|
||||
export const SEARCH_EXPAND_FAIL = 'SEARCH_EXPAND_FAIL';
|
||||
|
||||
export const SEARCH_RESULT_CLICK = 'SEARCH_RESULT_CLICK';
|
||||
export const SEARCH_RESULT_FORGET = 'SEARCH_RESULT_FORGET';
|
||||
|
||||
export function changeSearch(value) {
|
||||
return {
|
||||
type: SEARCH_CHANGE,
|
||||
|
@ -27,7 +30,7 @@ export function clearSearch() {
|
|||
};
|
||||
}
|
||||
|
||||
export function submitSearch() {
|
||||
export function submitSearch(type) {
|
||||
return (dispatch, getState) => {
|
||||
const value = getState().getIn(['search', 'value']);
|
||||
const signedIn = !!getState().getIn(['meta', 'me']);
|
||||
|
@ -44,6 +47,7 @@ export function submitSearch() {
|
|||
q: value,
|
||||
resolve: signedIn,
|
||||
limit: 10,
|
||||
type,
|
||||
},
|
||||
}).then(response => {
|
||||
if (response.data.accounts) {
|
||||
|
@ -130,3 +134,42 @@ export const expandSearchFail = error => ({
|
|||
export const showSearch = () => ({
|
||||
type: SEARCH_SHOW,
|
||||
});
|
||||
|
||||
export const openURL = routerHistory => (dispatch, getState) => {
|
||||
const value = getState().getIn(['search', 'value']);
|
||||
const signedIn = !!getState().getIn(['meta', 'me']);
|
||||
|
||||
if (!signedIn) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(fetchSearchRequest());
|
||||
|
||||
api(getState).get('/api/v2/search', { params: { q: value, resolve: true } }).then(response => {
|
||||
if (response.data.accounts?.length > 0) {
|
||||
dispatch(importFetchedAccounts(response.data.accounts));
|
||||
routerHistory.push(`/@${response.data.accounts[0].acct}`);
|
||||
} else if (response.data.statuses?.length > 0) {
|
||||
dispatch(importFetchedStatuses(response.data.statuses));
|
||||
routerHistory.push(`/@${response.data.statuses[0].account.acct}/${response.data.statuses[0].id}`);
|
||||
}
|
||||
|
||||
dispatch(fetchSearchSuccess(response.data, value));
|
||||
}).catch(err => {
|
||||
dispatch(fetchSearchFail(err));
|
||||
});
|
||||
};
|
||||
|
||||
export const clickSearchResult = (q, type) => ({
|
||||
type: SEARCH_RESULT_CLICK,
|
||||
|
||||
result: {
|
||||
type,
|
||||
q,
|
||||
},
|
||||
});
|
||||
|
||||
export const forgetSearchResult = q => ({
|
||||
type: SEARCH_RESULT_FORGET,
|
||||
q,
|
||||
});
|
||||
|
|
|
@ -5,6 +5,10 @@ export const SERVER_FETCH_REQUEST = 'Server_FETCH_REQUEST';
|
|||
export const SERVER_FETCH_SUCCESS = 'Server_FETCH_SUCCESS';
|
||||
export const SERVER_FETCH_FAIL = 'Server_FETCH_FAIL';
|
||||
|
||||
export const SERVER_TRANSLATION_LANGUAGES_FETCH_REQUEST = 'SERVER_TRANSLATION_LANGUAGES_FETCH_REQUEST';
|
||||
export const SERVER_TRANSLATION_LANGUAGES_FETCH_SUCCESS = 'SERVER_TRANSLATION_LANGUAGES_FETCH_SUCCESS';
|
||||
export const SERVER_TRANSLATION_LANGUAGES_FETCH_FAIL = 'SERVER_TRANSLATION_LANGUAGES_FETCH_FAIL';
|
||||
|
||||
export const EXTENDED_DESCRIPTION_REQUEST = 'EXTENDED_DESCRIPTION_REQUEST';
|
||||
export const EXTENDED_DESCRIPTION_SUCCESS = 'EXTENDED_DESCRIPTION_SUCCESS';
|
||||
export const EXTENDED_DESCRIPTION_FAIL = 'EXTENDED_DESCRIPTION_FAIL';
|
||||
|
@ -37,6 +41,29 @@ const fetchServerFail = error => ({
|
|||
error,
|
||||
});
|
||||
|
||||
export const fetchServerTranslationLanguages = () => (dispatch, getState) => {
|
||||
dispatch(fetchServerTranslationLanguagesRequest());
|
||||
|
||||
api(getState)
|
||||
.get('/api/v1/instance/translation_languages').then(({ data }) => {
|
||||
dispatch(fetchServerTranslationLanguagesSuccess(data));
|
||||
}).catch(err => dispatch(fetchServerTranslationLanguagesFail(err)));
|
||||
};
|
||||
|
||||
const fetchServerTranslationLanguagesRequest = () => ({
|
||||
type: SERVER_TRANSLATION_LANGUAGES_FETCH_REQUEST,
|
||||
});
|
||||
|
||||
const fetchServerTranslationLanguagesSuccess = translationLanguages => ({
|
||||
type: SERVER_TRANSLATION_LANGUAGES_FETCH_SUCCESS,
|
||||
translationLanguages,
|
||||
});
|
||||
|
||||
const fetchServerTranslationLanguagesFail = error => ({
|
||||
type: SERVER_TRANSLATION_LANGUAGES_FETCH_FAIL,
|
||||
error,
|
||||
});
|
||||
|
||||
export const fetchExtendedDescription = () => (dispatch, getState) => {
|
||||
dispatch(fetchExtendedDescriptionRequest());
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
|
|||
connectStream(channelName, params, (dispatch, getState) => {
|
||||
const locale = getState().getIn(['meta', 'locale']);
|
||||
|
||||
// @ts-expect-error
|
||||
let pollingId;
|
||||
|
||||
/**
|
||||
|
@ -61,9 +62,10 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
|
|||
onConnect() {
|
||||
dispatch(connectTimeline(timelineId));
|
||||
|
||||
// @ts-expect-error
|
||||
if (pollingId) {
|
||||
clearTimeout(pollingId);
|
||||
pollingId = null;
|
||||
// @ts-ignore
|
||||
clearTimeout(pollingId); pollingId = null;
|
||||
}
|
||||
|
||||
if (options.fillGaps) {
|
||||
|
@ -75,34 +77,41 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
|
|||
dispatch(disconnectTimeline(timelineId));
|
||||
|
||||
if (options.fallback) {
|
||||
// @ts-expect-error
|
||||
pollingId = setTimeout(() => useFallback(options.fallback), randomUpTo(40000));
|
||||
}
|
||||
},
|
||||
|
||||
onReceive (data) {
|
||||
switch(data.event) {
|
||||
onReceive(data) {
|
||||
switch (data.event) {
|
||||
case 'update':
|
||||
// @ts-expect-error
|
||||
dispatch(updateTimeline(timelineId, JSON.parse(data.payload), options.accept));
|
||||
break;
|
||||
case 'status.update':
|
||||
// @ts-expect-error
|
||||
dispatch(updateStatus(JSON.parse(data.payload)));
|
||||
break;
|
||||
case 'delete':
|
||||
dispatch(deleteFromTimelines(data.payload));
|
||||
break;
|
||||
case 'notification':
|
||||
// @ts-expect-error
|
||||
dispatch(updateNotifications(JSON.parse(data.payload), messages, locale));
|
||||
break;
|
||||
case 'emoji_reaction':
|
||||
dispatch(updateEmojiReactions(JSON.parse(data.payload), getState().getIn(['meta', 'me'])));
|
||||
break;
|
||||
case 'conversation':
|
||||
// @ts-expect-error
|
||||
dispatch(updateConversations(JSON.parse(data.payload)));
|
||||
break;
|
||||
case 'announcement':
|
||||
// @ts-expect-error
|
||||
dispatch(updateAnnouncements(JSON.parse(data.payload)));
|
||||
break;
|
||||
case 'announcement.reaction':
|
||||
// @ts-expect-error
|
||||
dispatch(updateAnnouncementsReaction(JSON.parse(data.payload)));
|
||||
break;
|
||||
case 'announcement.delete':
|
||||
|
@ -118,7 +127,9 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
|
|||
* @param {function(): void} done
|
||||
*/
|
||||
const refreshHomeTimelineAndNotification = (dispatch, done) => {
|
||||
// @ts-expect-error
|
||||
dispatch(expandHomeTimeline({}, () =>
|
||||
// @ts-expect-error
|
||||
dispatch(expandNotifications({}, () =>
|
||||
dispatch(fetchAnnouncements(done))))));
|
||||
};
|
||||
|
@ -127,6 +138,7 @@ const refreshHomeTimelineAndNotification = (dispatch, done) => {
|
|||
* @return {function(): void}
|
||||
*/
|
||||
export const connectUserStream = () =>
|
||||
// @ts-expect-error
|
||||
connectTimelineStream('home', 'user', {}, { fallback: refreshHomeTimelineAndNotification, fillGaps: fillHomeTimelineGaps });
|
||||
|
||||
/**
|
||||
|
|
|
@ -36,7 +36,7 @@ const setCSRFHeader = () => {
|
|||
ready(setCSRFHeader);
|
||||
|
||||
/**
|
||||
* @param {() => import('immutable').Map} getState
|
||||
* @param {() => import('immutable').Map<string,any>} getState
|
||||
* @returns {import('axios').RawAxiosRequestHeaders}
|
||||
*/
|
||||
const authorizationHeaderFromState = getState => {
|
||||
|
@ -52,7 +52,7 @@ const authorizationHeaderFromState = getState => {
|
|||
};
|
||||
|
||||
/**
|
||||
* @param {() => import('immutable').Map} getState
|
||||
* @param {() => import('immutable').Map<string,any>} getState
|
||||
* @returns {import('axios').AxiosInstance}
|
||||
*/
|
||||
export default function api(getState) {
|
||||
|
|
|
@ -1,17 +1,11 @@
|
|||
import 'intl';
|
||||
import 'intl/locale-data/jsonp/en';
|
||||
import 'es6-symbol/implement';
|
||||
import includes from 'array-includes';
|
||||
import assign from 'object-assign';
|
||||
import values from 'object.values';
|
||||
import isNaN from 'is-nan';
|
||||
import { decode as decodeBase64 } from './utils/base64';
|
||||
import promiseFinally from 'promise.prototype.finally';
|
||||
|
||||
if (!Array.prototype.includes) {
|
||||
includes.shim();
|
||||
}
|
||||
|
||||
if (!Object.assign) {
|
||||
Object.assign = assign;
|
||||
}
|
||||
|
@ -20,10 +14,6 @@ if (!Object.values) {
|
|||
values.shim();
|
||||
}
|
||||
|
||||
if (!Number.isNaN) {
|
||||
Number.isNaN = isNaN;
|
||||
}
|
||||
|
||||
promiseFinally.shim();
|
||||
|
||||
if (!HTMLCanvasElement.prototype.toBlob) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import Avatar from './avatar';
|
||||
|
@ -10,6 +10,10 @@ import { me } from '../initial_state';
|
|||
import RelativeTimestamp from './relative_timestamp';
|
||||
import Skeleton from 'mastodon/components/skeleton';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { counterRenderer } from 'mastodon/components/common_counter';
|
||||
import ShortNumber from 'mastodon/components/short_number';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const messages = defineMessages({
|
||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
||||
|
@ -23,7 +27,26 @@ const messages = defineMessages({
|
|||
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
class VerifiedBadge extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
link: PropTypes.string.isRequired,
|
||||
verifiedAt: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { link } = this.props;
|
||||
|
||||
return (
|
||||
<span className='verified-badge'>
|
||||
<Icon id='check' className='verified-badge__mark' />
|
||||
<span dangerouslySetInnerHTML={{ __html: link }} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Account extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -35,6 +58,7 @@ class Account extends ImmutablePureComponent {
|
|||
onMuteNotifications: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
hidden: PropTypes.bool,
|
||||
minimal: PropTypes.bool,
|
||||
actionIcon: PropTypes.string,
|
||||
actionTitle: PropTypes.string,
|
||||
defaultAction: PropTypes.string,
|
||||
|
@ -71,15 +95,19 @@ class Account extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
render () {
|
||||
const { account, intl, hidden, onActionClick, actionIcon, actionTitle, defaultAction, size, children } = this.props;
|
||||
const { account, intl, hidden, onActionClick, actionIcon, actionTitle, defaultAction, size, minimal, children } = this.props;
|
||||
|
||||
if (!account) {
|
||||
return (
|
||||
<div className='account'>
|
||||
<div className={classNames('account', { 'account--minimal': minimal })}>
|
||||
<div className='account__wrapper'>
|
||||
<div className='account__display-name'>
|
||||
<div className='account__avatar-wrapper'><Skeleton width={36} height={36} /></div>
|
||||
<DisplayName />
|
||||
<div className='account__avatar-wrapper'><Skeleton width={size} height={size} /></div>
|
||||
|
||||
<div>
|
||||
<DisplayName />
|
||||
<Skeleton width='7ch' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -88,10 +116,10 @@ class Account extends ImmutablePureComponent {
|
|||
|
||||
if (hidden) {
|
||||
return (
|
||||
<Fragment>
|
||||
<>
|
||||
{account.get('display_name')}
|
||||
{account.get('username')}
|
||||
</Fragment>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -119,10 +147,10 @@ class Account extends ImmutablePureComponent {
|
|||
hidingNotificationsButton = <IconButton active icon='bell-slash' title={intl.formatMessage(messages.mute_notifications, { name: account.get('username') })} onClick={this.handleMuteNotifications} />;
|
||||
}
|
||||
buttons = (
|
||||
<Fragment>
|
||||
<>
|
||||
<IconButton active icon='volume-up' title={intl.formatMessage(messages.unmute, { name: account.get('username') })} onClick={this.handleMute} />
|
||||
{hidingNotificationsButton}
|
||||
</Fragment>
|
||||
</>
|
||||
);
|
||||
} else if (defaultAction === 'mute') {
|
||||
buttons = <IconButton icon='volume-off' title={intl.formatMessage(messages.mute, { name: account.get('username') })} onClick={this.handleMute} />;
|
||||
|
@ -133,30 +161,49 @@ class Account extends ImmutablePureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
let mute_expires_at;
|
||||
let muteTimeRemaining;
|
||||
|
||||
if (account.get('mute_expires_at')) {
|
||||
mute_expires_at = <div><RelativeTimestamp timestamp={account.get('mute_expires_at')} futureDate /></div>;
|
||||
muteTimeRemaining = <>· <RelativeTimestamp timestamp={account.get('mute_expires_at')} futureDate /></>;
|
||||
}
|
||||
|
||||
let verification;
|
||||
|
||||
const firstVerifiedField = account.get('fields').find(item => !!item.get('verified_at'));
|
||||
|
||||
if (firstVerifiedField) {
|
||||
verification = <>· <VerifiedBadge link={firstVerifiedField.get('value')} verifiedAt={firstVerifiedField.get('verified_at')} /></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='account'>
|
||||
<div className={classNames('account', { 'account--minimal': minimal })}>
|
||||
<div className='account__wrapper'>
|
||||
<Link key={account.get('id')} className='account__display-name' title={account.get('acct')} to={`/@${account.get('acct')}`}>
|
||||
<div className='account__avatar-wrapper'><Avatar account={account} size={size} /></div>
|
||||
{mute_expires_at}
|
||||
<DisplayName account={account} />
|
||||
<div className='account__avatar-wrapper'>
|
||||
<Avatar account={account} size={size} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<DisplayName account={account} />
|
||||
{!minimal && <><ShortNumber value={account.get('followers_count')} renderer={counterRenderer('followers')} /> {verification} {muteTimeRemaining}</>}
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<div>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
<div className='account__relationship'>
|
||||
{buttons}
|
||||
</div>
|
||||
{!minimal && (
|
||||
<div>
|
||||
<div>
|
||||
{children}
|
||||
</div>
|
||||
<div className='account__relationship'>
|
||||
{buttons}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default injectIntl(Account);
|
||||
|
|
|
@ -33,7 +33,7 @@ class Category extends React.PureComponent {
|
|||
const { id, text, disabled, selected, children } = this.props;
|
||||
|
||||
return (
|
||||
<div tabIndex='0' role='button' className={classNames('report-reason-selector__category', { selected, disabled })} onClick={this.handleClick}>
|
||||
<div tabIndex={0} role='button' className={classNames('report-reason-selector__category', { selected, disabled })} onClick={this.handleClick}>
|
||||
{selected && <input type='hidden' name='report[category]' value={id} />}
|
||||
|
||||
<div className='report-reason-selector__category__label'>
|
||||
|
@ -74,7 +74,7 @@ class Rule extends React.PureComponent {
|
|||
const { id, text, disabled, selected } = this.props;
|
||||
|
||||
return (
|
||||
<div tabIndex='0' role='button' className={classNames('report-reason-selector__rule', { selected, disabled })} onClick={this.handleClick}>
|
||||
<div tabIndex={0} role='button' className={classNames('report-reason-selector__rule', { selected, disabled })} onClick={this.handleClick}>
|
||||
<span className={classNames('poll__input', { checkbox: true, active: selected, disabled })} />
|
||||
{selected && <input type='hidden' name='report[rule_ids][]' value={id} />}
|
||||
{text}
|
||||
|
@ -84,7 +84,6 @@ class Rule extends React.PureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default @injectIntl
|
||||
class ReportReasonSelector extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -157,3 +156,5 @@ class ReportReasonSelector extends React.PureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default injectIntl(ReportReasonSelector);
|
||||
|
|
|
@ -180,7 +180,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<div role='button' tabIndex='0' key={key} data-index={i} className={classNames('autosuggest-textarea__suggestions__item', { selected: i === selectedSuggestion })} onMouseDown={this.onSuggestionClick}>
|
||||
<div role='button' tabIndex={0} key={key} data-index={i} className={classNames('autosuggest-textarea__suggestions__item', { selected: i === selectedSuggestion })} onMouseDown={this.onSuggestionClick}>
|
||||
{inner}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -186,7 +186,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<div role='button' tabIndex='0' key={key} data-index={i} className={classNames('autosuggest-textarea__suggestions__item', { selected: i === selectedSuggestion })} onMouseDown={this.onSuggestionClick}>
|
||||
<div role='button' tabIndex={0} key={key} data-index={i} className={classNames('autosuggest-textarea__suggestions__item', { selected: i === selectedSuggestion })} onMouseDown={this.onSuggestionClick}>
|
||||
{inner}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { autoPlayGif } from '../initial_state';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export default class Avatar extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map,
|
||||
size: PropTypes.number.isRequired,
|
||||
style: PropTypes.object,
|
||||
inline: PropTypes.bool,
|
||||
animate: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
animate: autoPlayGif,
|
||||
size: 20,
|
||||
inline: false,
|
||||
};
|
||||
|
||||
state = {
|
||||
hovering: false,
|
||||
};
|
||||
|
||||
handleMouseEnter = () => {
|
||||
if (this.props.animate) return;
|
||||
this.setState({ hovering: true });
|
||||
};
|
||||
|
||||
handleMouseLeave = () => {
|
||||
if (this.props.animate) return;
|
||||
this.setState({ hovering: false });
|
||||
};
|
||||
|
||||
render () {
|
||||
const { account, size, animate, inline } = this.props;
|
||||
const { hovering } = this.state;
|
||||
|
||||
const style = {
|
||||
...this.props.style,
|
||||
width: `${size}px`,
|
||||
height: `${size}px`,
|
||||
};
|
||||
|
||||
let src;
|
||||
|
||||
if (hovering || animate) {
|
||||
src = account?.get('avatar');
|
||||
} else {
|
||||
src = account?.get('avatar_static');
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classNames('account__avatar', { 'account__avatar-inline': inline })} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} style={style}>
|
||||
{src && <img src={src} alt={account?.get('acct')} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
49
app/javascript/mastodon/components/avatar.tsx
Normal file
49
app/javascript/mastodon/components/avatar.tsx
Normal file
|
@ -0,0 +1,49 @@
|
|||
import * as React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { autoPlayGif } from '../initial_state';
|
||||
import { useHovering } from '../../hooks/useHovering';
|
||||
import type { Account } from '../../types/resources';
|
||||
|
||||
type Props = {
|
||||
account: Account;
|
||||
size: number;
|
||||
style?: React.CSSProperties;
|
||||
inline?: boolean;
|
||||
animate?: boolean;
|
||||
};
|
||||
|
||||
export const Avatar: React.FC<Props> = ({
|
||||
account,
|
||||
animate = autoPlayGif,
|
||||
size = 20,
|
||||
inline = false,
|
||||
style: styleFromParent,
|
||||
}) => {
|
||||
const { hovering, handleMouseEnter, handleMouseLeave } = useHovering(animate);
|
||||
|
||||
const style = {
|
||||
...styleFromParent,
|
||||
width: `${size}px`,
|
||||
height: `${size}px`,
|
||||
};
|
||||
|
||||
const src =
|
||||
hovering || animate
|
||||
? account?.get('avatar')
|
||||
: account?.get('avatar_static');
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames('account__avatar', {
|
||||
'account__avatar-inline': inline,
|
||||
})}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
style={style}
|
||||
>
|
||||
{src && <img src={src} alt={account?.get('acct')} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Avatar;
|
|
@ -44,6 +44,7 @@ function Blurhash({
|
|||
const ctx = canvas.getContext('2d');
|
||||
const imageData = new ImageData(pixels, width, height);
|
||||
|
||||
// @ts-expect-error
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
} catch (err) {
|
||||
console.error('Blurhash decoding failure', { err, hash });
|
||||
|
|
|
@ -8,7 +8,7 @@ export default class ColumnBackButtonSlim extends ColumnBackButton {
|
|||
render () {
|
||||
return (
|
||||
<div className='column-back-button--slim'>
|
||||
<div role='button' tabIndex='0' onClick={this.handleClick} className='column-back-button column-back-button--slim-button'>
|
||||
<div role='button' tabIndex={0} onClick={this.handleClick} className='column-back-button column-back-button--slim-button'>
|
||||
<Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
|
||||
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
|
||||
</div>
|
||||
|
|
|
@ -12,7 +12,6 @@ const messages = defineMessages({
|
|||
moveRight: { id: 'column_header.moveRight_settings', defaultMessage: 'Move column to the right' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
class ColumnHeader extends React.PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
|
@ -209,3 +208,5 @@ class ColumnHeader extends React.PureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default injectIntl(ColumnHeader);
|
||||
|
|
|
@ -8,7 +8,6 @@ const messages = defineMessages({
|
|||
dismiss: { id: 'dismissable_banner.dismiss', defaultMessage: 'Dismiss' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
class DismissableBanner extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -49,3 +48,5 @@ class DismissableBanner extends React.PureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default injectIntl(DismissableBanner);
|
||||
|
|
|
@ -8,7 +8,6 @@ const messages = defineMessages({
|
|||
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
class Account extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -40,3 +39,5 @@ class Account extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default injectIntl(Account);
|
||||
|
|
|
@ -119,7 +119,7 @@ class DropdownMenu extends React.PureComponent {
|
|||
|
||||
return (
|
||||
<li className='dropdown-menu__item' key={`${text}-${i}`}>
|
||||
<a href={href} target={target} data-method={method} rel='noopener noreferrer' role='button' tabIndex='0' ref={i === 0 ? this.setFocusRef : null} onClick={this.handleClick} onKeyPress={this.handleItemKeyPress} data-index={i}>
|
||||
<a href={href} target={target} data-method={method} rel='noopener noreferrer' role='button' tabIndex={0} ref={i === 0 ? this.setFocusRef : null} onClick={this.handleClick} onKeyPress={this.handleItemKeyPress} data-index={i}>
|
||||
{text}
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
@ -16,8 +16,6 @@ const mapDispatchToProps = (dispatch, { statusId }) => ({
|
|||
|
||||
});
|
||||
|
||||
export default @connect(null, mapDispatchToProps)
|
||||
@injectIntl
|
||||
class EditedTimestamp extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -68,3 +66,5 @@ class EditedTimestamp extends React.PureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(injectIntl(EditedTimestamp));
|
||||
|
|
|
@ -46,7 +46,7 @@ export default class GIFV extends React.PureComponent {
|
|||
width={width}
|
||||
height={height}
|
||||
role='button'
|
||||
tabIndex='0'
|
||||
tabIndex={0}
|
||||
aria-label={alt}
|
||||
title={alt}
|
||||
lang={lang}
|
||||
|
@ -57,7 +57,7 @@ export default class GIFV extends React.PureComponent {
|
|||
<video
|
||||
src={src}
|
||||
role='button'
|
||||
tabIndex='0'
|
||||
tabIndex={0}
|
||||
aria-label={alt}
|
||||
title={alt}
|
||||
lang={lang}
|
||||
|
|
|
@ -5,7 +5,9 @@ import { FormattedMessage } from 'react-intl';
|
|||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { Link } from 'react-router-dom';
|
||||
// @ts-expect-error
|
||||
import ShortNumber from 'mastodon/components/short_number';
|
||||
// @ts-expect-error
|
||||
import Skeleton from 'mastodon/components/skeleton';
|
||||
import classNames from 'classnames';
|
||||
|
||||
|
@ -19,11 +21,11 @@ class SilentErrorBoundary extends React.Component {
|
|||
error: false,
|
||||
};
|
||||
|
||||
componentDidCatch () {
|
||||
componentDidCatch() {
|
||||
this.setState({ error: true });
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
if (this.state.error) {
|
||||
return null;
|
||||
}
|
||||
|
@ -50,11 +52,13 @@ export const accountsCountRenderer = (displayNumber, pluralReady) => (
|
|||
/>
|
||||
);
|
||||
|
||||
// @ts-expect-error
|
||||
export const ImmutableHashtag = ({ hashtag }) => (
|
||||
<Hashtag
|
||||
name={hashtag.get('name')}
|
||||
to={`/tags/${hashtag.get('name')}`}
|
||||
people={hashtag.getIn(['history', 0, 'accounts']) * 1 + hashtag.getIn(['history', 1, 'accounts']) * 1}
|
||||
// @ts-expect-error
|
||||
history={hashtag.get('history').reverse().map((day) => day.get('uses')).toArray()}
|
||||
/>
|
||||
);
|
||||
|
@ -63,6 +67,7 @@ ImmutableHashtag.propTypes = {
|
|||
hashtag: ImmutablePropTypes.map.isRequired,
|
||||
};
|
||||
|
||||
// @ts-expect-error
|
||||
const Hashtag = ({ name, to, people, uses, history, className, description, withGraph }) => (
|
||||
<div className={classNames('trends__item', className)}>
|
||||
<div className='trends__item__name'>
|
||||
|
|
|
@ -23,7 +23,7 @@ export default class IconButton extends React.PureComponent {
|
|||
inverted: PropTypes.bool,
|
||||
animate: PropTypes.bool,
|
||||
overlay: PropTypes.bool,
|
||||
tabIndex: PropTypes.string,
|
||||
tabIndex: PropTypes.number,
|
||||
counter: PropTypes.number,
|
||||
obfuscateCount: PropTypes.bool,
|
||||
href: PropTypes.string,
|
||||
|
@ -36,7 +36,7 @@ export default class IconButton extends React.PureComponent {
|
|||
disabled: false,
|
||||
animate: false,
|
||||
overlay: false,
|
||||
tabIndex: '0',
|
||||
tabIndex: 0,
|
||||
ariaHidden: false,
|
||||
};
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ const makeMapStateToProps = () => {
|
|||
return mapStateToProps;
|
||||
};
|
||||
|
||||
export default @connect(makeMapStateToProps)
|
||||
class InlineAccount extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -32,3 +31,5 @@ class InlineAccount extends React.PureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(makeMapStateToProps)(InlineAccount);
|
||||
|
|
|
@ -113,7 +113,7 @@ export default class IntersectionObserverArticle extends React.Component {
|
|||
aria-setsize={listLength}
|
||||
style={{ height: `${this.height || cachedHeight}px`, opacity: 0, overflow: 'hidden' }}
|
||||
data-id={id}
|
||||
tabIndex='0'
|
||||
tabIndex={0}
|
||||
>
|
||||
{children && React.cloneElement(children, { hidden: true })}
|
||||
</article>
|
||||
|
@ -121,7 +121,7 @@ export default class IntersectionObserverArticle extends React.Component {
|
|||
}
|
||||
|
||||
return (
|
||||
<article ref={this.handleRef} aria-posinset={index + 1} aria-setsize={listLength} data-id={id} tabIndex='0'>
|
||||
<article ref={this.handleRef} aria-posinset={index + 1} aria-setsize={listLength} data-id={id} tabIndex={0}>
|
||||
{children && React.cloneElement(children, { hidden: false })}
|
||||
</article>
|
||||
);
|
||||
|
|
|
@ -7,7 +7,6 @@ const messages = defineMessages({
|
|||
load_more: { id: 'status.load_more', defaultMessage: 'Load more' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
class LoadGap extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -32,3 +31,5 @@ class LoadGap extends React.PureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default injectIntl(LoadGap);
|
||||
|
|
|
@ -248,7 +248,6 @@ class Item extends React.PureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default @injectIntl
|
||||
class MediaGallery extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -396,3 +395,5 @@ class MediaGallery extends React.PureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default injectIntl(MediaGallery);
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import illustration from 'mastodon/../images/elephant_ui_disappointed.svg';
|
||||
import classNames from 'classnames';
|
||||
import { Helmet } from 'react-helmet';
|
||||
|
||||
const MissingIndicator = ({ fullPage }) => (
|
||||
<div className={classNames('regeneration-indicator', { 'regeneration-indicator--without-header': fullPage })}>
|
||||
<div className='regeneration-indicator__figure'>
|
||||
<img src={illustration} alt='' />
|
||||
</div>
|
||||
|
||||
<div className='regeneration-indicator__label'>
|
||||
<FormattedMessage id='missing_indicator.label' tagName='strong' defaultMessage='Not found' />
|
||||
<FormattedMessage id='missing_indicator.sublabel' defaultMessage='This resource could not be found' />
|
||||
</div>
|
||||
|
||||
<Helmet>
|
||||
<meta name='robots' content='noindex' />
|
||||
</Helmet>
|
||||
</div>
|
||||
);
|
||||
|
||||
MissingIndicator.propTypes = {
|
||||
fullPage: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default MissingIndicator;
|
|
@ -15,7 +15,6 @@ const DefaultNavigation = () => (
|
|||
</>
|
||||
);
|
||||
|
||||
export default @withRouter
|
||||
class NavigationPortal extends React.PureComponent {
|
||||
|
||||
render () {
|
||||
|
@ -33,3 +32,4 @@ class NavigationPortal extends React.PureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
export default withRouter(NavigationPortal);
|
||||
|
|
|
@ -6,7 +6,6 @@ import { connect } from 'react-redux';
|
|||
import { debounce } from 'lodash';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
export default @connect()
|
||||
class PictureInPicturePlaceholder extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -59,7 +58,7 @@ class PictureInPicturePlaceholder extends React.PureComponent {
|
|||
const { height } = this.state;
|
||||
|
||||
return (
|
||||
<div ref={this.setRef} className='picture-in-picture-placeholder' style={{ height }} role='button' tabIndex='0' onClick={this.handleClick}>
|
||||
<div ref={this.setRef} className='picture-in-picture-placeholder' style={{ height }} role='button' tabIndex={0} onClick={this.handleClick}>
|
||||
<Icon id='window-restore' />
|
||||
<FormattedMessage id='picture_in_picture.restore' defaultMessage='Put it back' />
|
||||
</div>
|
||||
|
@ -67,3 +66,5 @@ class PictureInPicturePlaceholder extends React.PureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect()(PictureInPicturePlaceholder);
|
||||
|
|
|
@ -31,7 +31,6 @@ const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => {
|
|||
return obj;
|
||||
}, {});
|
||||
|
||||
export default @injectIntl
|
||||
class Poll extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
|
@ -155,7 +154,7 @@ class Poll extends ImmutablePureComponent {
|
|||
{!showResults && (
|
||||
<span
|
||||
className={classNames('poll__input', { checkbox: poll.get('multiple'), active })}
|
||||
tabIndex='0'
|
||||
tabIndex={0}
|
||||
role={poll.get('multiple') ? 'checkbox' : 'radio'}
|
||||
onKeyPress={this.handleOptionKeyPress}
|
||||
aria-checked={active}
|
||||
|
@ -234,3 +233,5 @@ class Poll extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default injectIntl(Poll);
|
||||
|
|
|
@ -121,7 +121,6 @@ const timeRemainingString = (intl, date, now, timeGiven = true) => {
|
|||
return relativeTime;
|
||||
};
|
||||
|
||||
export default @injectIntl
|
||||
class RelativeTimestamp extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -197,3 +196,5 @@ class RelativeTimestamp extends React.Component {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default injectIntl(RelativeTimestamp);
|
||||
|
|
|
@ -20,7 +20,6 @@ const mapStateToProps = (state, { scrollKey }) => {
|
|||
};
|
||||
};
|
||||
|
||||
export default @connect(mapStateToProps, null, null, { forwardRef: true })
|
||||
class ScrollableList extends PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
|
@ -365,3 +364,5 @@ class ScrollableList extends PureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null, null, { forwardRef: true })(ScrollableList);
|
||||
|
|
|
@ -18,8 +18,6 @@ const mapStateToProps = state => ({
|
|||
server: state.getIn(['server', 'server']),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class ServerBanner extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -61,7 +59,7 @@ class ServerBanner extends React.PureComponent {
|
|||
<div className='server-banner__meta__column'>
|
||||
<h4><FormattedMessage id='server_banner.administered_by' defaultMessage='Administered by:' /></h4>
|
||||
|
||||
<Account id={server.getIn(['contact', 'account', 'id'])} size={36} />
|
||||
<Account id={server.getIn(['contact', 'account', 'id'])} size={36} minimal />
|
||||
</div>
|
||||
|
||||
<div className='server-banner__meta__column'>
|
||||
|
@ -91,3 +89,5 @@ class ServerBanner extends React.PureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(injectIntl(ServerBanner));
|
||||
|
|
|
@ -61,7 +61,6 @@ const messages = defineMessages({
|
|||
edited: { id: 'status.edited', defaultMessage: 'Edited {date}' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
class Status extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
|
@ -343,7 +342,7 @@ class Status extends ImmutablePureComponent {
|
|||
if (hidden) {
|
||||
return (
|
||||
<HotKeys handlers={handlers}>
|
||||
<div ref={this.handleRef} className={classNames('status__wrapper', { focusable: !this.props.muted })} tabIndex='0'>
|
||||
<div ref={this.handleRef} className={classNames('status__wrapper', { focusable: !this.props.muted })} tabIndex={0}>
|
||||
<span>{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}</span>
|
||||
<span>{status.get('content')}</span>
|
||||
</div>
|
||||
|
@ -360,7 +359,7 @@ class Status extends ImmutablePureComponent {
|
|||
|
||||
return (
|
||||
<HotKeys handlers={minHandlers}>
|
||||
<div className='status__wrapper status__wrapper--filtered focusable' tabIndex='0' ref={this.handleRef}>
|
||||
<div className='status__wrapper status__wrapper--filtered focusable' tabIndex={0} ref={this.handleRef}>
|
||||
<FormattedMessage id='status.filtered' defaultMessage='Filtered' />: {matchedFilters.join(', ')}.
|
||||
{' '}
|
||||
<button className='status__wrapper--filtered__button' onClick={this.handleUnfilterClick}>
|
||||
|
@ -392,6 +391,13 @@ class Status extends ImmutablePureComponent {
|
|||
|
||||
account = status.get('account');
|
||||
status = status.get('reblog');
|
||||
} else if (status.get('visibility') === 'direct') {
|
||||
prepend = (
|
||||
<div className='status__prepend'>
|
||||
<div className='status__prepend-icon-wrapper'><Icon id='at' className='status__prepend-icon' fixedWidth /></div>
|
||||
<FormattedMessage id='status.direct_indicator' defaultMessage='Private mention' />
|
||||
</div>
|
||||
);
|
||||
} else if (showThread && status.get('in_reply_to_id') && status.get('in_reply_to_account_id') === status.getIn(['account', 'id'])) {
|
||||
const display_name_html = { __html: status.getIn(['account', 'display_name_html']) };
|
||||
|
||||
|
@ -549,7 +555,7 @@ class Status extends ImmutablePureComponent {
|
|||
expanded={!status.get('hidden')}
|
||||
onExpandedToggle={this.handleExpandedToggle}
|
||||
onTranslate={this.handleTranslate}
|
||||
collapsable
|
||||
collapsible
|
||||
onCollapsedToggle={this.handleCollapsedToggle}
|
||||
/>
|
||||
|
||||
|
@ -565,3 +571,5 @@ class Status extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default injectIntl(Status);
|
||||
|
|
|
@ -15,7 +15,7 @@ const messages = defineMessages({
|
|||
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
||||
redraft: { id: 'status.redraft', defaultMessage: 'Delete & re-draft' },
|
||||
edit: { id: 'status.edit', defaultMessage: 'Edit' },
|
||||
direct: { id: 'status.direct', defaultMessage: 'Direct message @{name}' },
|
||||
direct: { id: 'status.direct', defaultMessage: 'Privately mention @{name}' },
|
||||
mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' },
|
||||
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
|
||||
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
|
||||
|
@ -55,8 +55,6 @@ const mapStateToProps = (state, { status }) => ({
|
|||
relationship: state.getIn(['relationships', status.getIn(['account', 'id'])]),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class StatusActionBar extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
|
@ -405,3 +403,5 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(injectIntl(StatusActionBar));
|
||||
|
|
|
@ -3,6 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { connect } from 'react-redux';
|
||||
import classnames from 'classnames';
|
||||
import PollContainer from 'mastodon/containers/poll_container';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
|
@ -47,7 +48,10 @@ class TranslateButton extends React.PureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default @injectIntl
|
||||
const mapStateToProps = state => ({
|
||||
languages: state.getIn(['server', 'translationLanguages', 'items']),
|
||||
});
|
||||
|
||||
class StatusContent extends React.PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
|
@ -61,8 +65,9 @@ class StatusContent extends React.PureComponent {
|
|||
onExpandedToggle: PropTypes.func,
|
||||
onTranslate: PropTypes.func,
|
||||
onClick: PropTypes.func,
|
||||
collapsable: PropTypes.bool,
|
||||
collapsible: PropTypes.bool,
|
||||
onCollapsedToggle: PropTypes.func,
|
||||
languages: ImmutablePropTypes.map,
|
||||
intl: PropTypes.object,
|
||||
};
|
||||
|
||||
|
@ -107,10 +112,10 @@ class StatusContent extends React.PureComponent {
|
|||
}
|
||||
|
||||
if (status.get('collapsed', null) === null && onCollapsedToggle) {
|
||||
const { collapsable, onClick } = this.props;
|
||||
const { collapsible, onClick } = this.props;
|
||||
|
||||
const collapsed =
|
||||
collapsable
|
||||
collapsible
|
||||
&& onClick
|
||||
&& node.clientHeight > MAX_HEIGHT
|
||||
&& status.get('spoiler_text').length === 0;
|
||||
|
@ -220,7 +225,9 @@ class StatusContent extends React.PureComponent {
|
|||
|
||||
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
|
||||
const renderReadMore = this.props.onClick && status.get('collapsed');
|
||||
const renderTranslate = this.props.onTranslate && status.get('translatable');
|
||||
const contentLocale = intl.locale.replace(/[_-].*/, '');
|
||||
const targetLanguages = this.props.languages?.get(status.get('language') || 'und');
|
||||
const renderTranslate = this.props.onTranslate && this.context.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('contentHtml').length > 0 && targetLanguages?.includes(contentLocale);
|
||||
|
||||
const content = { __html: status.get('translation') ? status.getIn(['translation', 'content']) : status.get('contentHtml') };
|
||||
const spoilerContent = { __html: status.get('spoilerHtml') };
|
||||
|
@ -261,7 +268,7 @@ class StatusContent extends React.PureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={classNames} ref={this.setRef} tabIndex='0' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
<div className={classNames} ref={this.setRef} tabIndex={0} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
<p style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}>
|
||||
<span dangerouslySetInnerHTML={spoilerContent} className='translate' lang={lang} />
|
||||
{' '}
|
||||
|
@ -279,7 +286,7 @@ class StatusContent extends React.PureComponent {
|
|||
} else if (this.props.onClick) {
|
||||
return (
|
||||
<>
|
||||
<div className={classNames} ref={this.setRef} tabIndex='0' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} key='status-content' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
<div className={classNames} ref={this.setRef} tabIndex={0} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} key='status-content' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
<div className='status__content__text status__content__text--visible translate' lang={lang} dangerouslySetInnerHTML={content} />
|
||||
|
||||
{poll}
|
||||
|
@ -291,7 +298,7 @@ class StatusContent extends React.PureComponent {
|
|||
);
|
||||
} else {
|
||||
return (
|
||||
<div className={classNames} ref={this.setRef} tabIndex='0' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
<div className={classNames} ref={this.setRef} tabIndex={0} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
<div className='status__content__text status__content__text--visible translate' lang={lang} dangerouslySetInnerHTML={content} />
|
||||
|
||||
{poll}
|
||||
|
@ -302,3 +309,5 @@ class StatusContent extends React.PureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(injectIntl(StatusContent));
|
||||
|
|
|
@ -2,7 +2,6 @@ import React from 'react';
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import { injectIntl } from 'react-intl';
|
||||
import emojify from '../features/emoji/emoji';
|
||||
import classNames from 'classnames';
|
||||
import EmojiView from './emoji_view';
|
||||
|
||||
|
@ -50,7 +49,6 @@ class EmojiReactionButton extends React.PureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default @injectIntl
|
||||
class StatusEmojiReactionsBar extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -73,7 +71,7 @@ class StatusEmojiReactionsBar extends React.PureComponent {
|
|||
render () {
|
||||
const { emojiReactions } = this.props;
|
||||
|
||||
const emojiButtons = Array.from(emojiReactions).filter(emoji => emoji.get('count') != 0).map((emoji, index) => (
|
||||
const emojiButtons = Array.from(emojiReactions).filter(emoji => emoji.get('count') !== 0).map((emoji, index) => (
|
||||
<EmojiReactionButton
|
||||
key={index}
|
||||
name={emoji.get('name')}
|
||||
|
@ -94,3 +92,5 @@ class StatusEmojiReactionsBar extends React.PureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default injectIntl(StatusEmojiReactionsBar);
|
||||
|
|
|
@ -54,7 +54,7 @@ export default class MediaContainer extends PureComponent {
|
|||
|
||||
handleCloseMedia = () => {
|
||||
document.body.classList.remove('with-modals--active');
|
||||
document.documentElement.style.marginRight = 0;
|
||||
document.documentElement.style.marginRight = '0';
|
||||
|
||||
this.setState({
|
||||
media: null,
|
||||
|
|
|
@ -67,7 +67,7 @@ class Section extends React.PureComponent {
|
|||
|
||||
return (
|
||||
<div className={classNames('about__section', { active: !collapsed })}>
|
||||
<div className='about__section__title' role='button' tabIndex='0' onClick={this.handleClick}>
|
||||
<div className='about__section__title' role='button' tabIndex={0} onClick={this.handleClick}>
|
||||
<Icon id={collapsed ? 'chevron-right' : 'chevron-down'} fixedWidth /> {title}
|
||||
</div>
|
||||
|
||||
|
@ -80,8 +80,6 @@ class Section extends React.PureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class About extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -125,7 +123,7 @@ class About extends React.PureComponent {
|
|||
<div className='about__meta__column'>
|
||||
<h4><FormattedMessage id='server_banner.administered_by' defaultMessage='Administered by:' /></h4>
|
||||
|
||||
<Account id={server.getIn(['contact', 'account', 'id'])} size={36} />
|
||||
<Account id={server.getIn(['contact', 'account', 'id'])} size={36} minimal />
|
||||
</div>
|
||||
|
||||
<hr className='about__meta__divider' />
|
||||
|
@ -217,3 +215,5 @@ class About extends React.PureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(injectIntl(About));
|
||||
|
|
|
@ -43,7 +43,6 @@ class InlineAlert extends React.PureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default @injectIntl
|
||||
class AccountNote extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -168,3 +167,5 @@ class AccountNote extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default injectIntl(AccountNote);
|
||||
|
|
|
@ -10,7 +10,6 @@ const messages = defineMessages({
|
|||
empty: { id: 'account.featured_tags.last_status_never', defaultMessage: 'No posts' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
class FeaturedTags extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
|
@ -50,3 +49,5 @@ class FeaturedTags extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default injectIntl(FeaturedTags);
|
||||
|
|
|
@ -28,7 +28,7 @@ const messages = defineMessages({
|
|||
linkVerifiedOn: { id: 'account.link_verified_on', defaultMessage: 'Ownership of this link was checked on {date}' },
|
||||
account_locked: { id: 'account.locked_info', defaultMessage: 'This account privacy status is set to locked. The owner manually reviews who can follow them.' },
|
||||
mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' },
|
||||
direct: { id: 'account.direct', defaultMessage: 'Direct message @{name}' },
|
||||
direct: { id: 'account.direct', defaultMessage: 'Privately mention @{name}' },
|
||||
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
|
||||
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
|
||||
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
|
||||
|
@ -76,7 +76,6 @@ const dateFormatOptions = {
|
|||
minute: '2-digit',
|
||||
};
|
||||
|
||||
export default @injectIntl
|
||||
class Header extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
|
@ -419,3 +418,5 @@ class Header extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default injectIntl(Header);
|
||||
|
|
|
@ -19,7 +19,6 @@ const mapStateToProps = (state, { match: { params: { acct } } }) => {
|
|||
};
|
||||
};
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
class AccountNavigation extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -50,3 +49,5 @@ class AccountNavigation extends React.PureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(AccountNavigation);
|
||||
|
|
|
@ -74,7 +74,7 @@ export default class MediaItem extends ImmutablePureComponent {
|
|||
if (['audio', 'video'].includes(attachment.get('type'))) {
|
||||
content = (
|
||||
<img
|
||||
src={attachment.get('preview_url') || attachment.getIn(['account', 'avatar_static'])}
|
||||
src={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
|
||||
alt={attachment.get('description')}
|
||||
lang={status.get('language')}
|
||||
onLoad={this.handleImageLoad}
|
||||
|
|
|
@ -13,10 +13,10 @@ import MediaItem from './components/media_item';
|
|||
import HeaderContainer from '../account_timeline/containers/header_container';
|
||||
import ScrollContainer from 'mastodon/containers/scroll_container';
|
||||
import LoadMore from 'mastodon/components/load_more';
|
||||
import MissingIndicator from 'mastodon/components/missing_indicator';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { normalizeForLookup } from 'mastodon/reducers/accounts_map';
|
||||
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
|
||||
|
||||
const mapStateToProps = (state, { params: { acct, id } }) => {
|
||||
const accountId = id || state.getIn(['accounts_map', normalizeForLookup(acct)]);
|
||||
|
@ -60,7 +60,6 @@ class LoadMoreMedia extends ImmutablePureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
class AccountGallery extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -162,9 +161,7 @@ class AccountGallery extends ImmutablePureComponent {
|
|||
|
||||
if (!isAccount) {
|
||||
return (
|
||||
<Column>
|
||||
<MissingIndicator />
|
||||
</Column>
|
||||
<BundleColumnError multiColumn={multiColumn} errorType='routing' />
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -226,3 +223,5 @@ class AccountGallery extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(AccountGallery);
|
||||
|
|
|
@ -3,6 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
import PropTypes from 'prop-types';
|
||||
import InnerHeader from '../../account/components/header';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import MemorialNote from './memorial_note';
|
||||
import MovedNote from './moved_note';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
|
@ -115,6 +116,7 @@ export default class Header extends ImmutablePureComponent {
|
|||
|
||||
return (
|
||||
<div className='account-timeline__header'>
|
||||
{(!hidden && account.get('memorial')) && <MemorialNote />}
|
||||
{(!hidden && account.get('moved')) && <MovedNote from={account} to={account.get('moved')} />}
|
||||
|
||||
<InnerHeader
|
||||
|
|
|
@ -14,7 +14,6 @@ const mapDispatchToProps = (dispatch, { accountId }) => ({
|
|||
|
||||
});
|
||||
|
||||
export default @connect(() => {}, mapDispatchToProps)
|
||||
class LimitedAccountHint extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -34,3 +33,5 @@ class LimitedAccountHint extends React.PureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(() => {}, mapDispatchToProps)(LimitedAccountHint);
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
const MemorialNote = () => (
|
||||
<div className='account-memorial-banner'>
|
||||
<div className='account-memorial-banner__message'>
|
||||
<FormattedMessage id='account.in_memoriam' defaultMessage='In Memoriam.' />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default MemorialNote;
|
|
@ -12,7 +12,6 @@ import ColumnBackButton from '../../components/column_back_button';
|
|||
import { List as ImmutableList } from 'immutable';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import MissingIndicator from 'mastodon/components/missing_indicator';
|
||||
import TimelineHint from 'mastodon/components/timeline_hint';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import { connectTimeline, disconnectTimeline } from 'mastodon/actions/timelines';
|
||||
|
@ -20,6 +19,7 @@ import LimitedAccountHint from './components/limited_account_hint';
|
|||
import { getAccountHidden } from 'mastodon/selectors';
|
||||
import { fetchFeaturedTags } from '../../actions/featured_tags';
|
||||
import { normalizeForLookup } from 'mastodon/reducers/accounts_map';
|
||||
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
|
||||
|
||||
const emptyList = ImmutableList();
|
||||
|
||||
|
@ -64,7 +64,6 @@ RemoteHint.propTypes = {
|
|||
url: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
class AccountTimeline extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -158,10 +157,7 @@ class AccountTimeline extends ImmutablePureComponent {
|
|||
);
|
||||
} else if (!isLoading && !isAccount) {
|
||||
return (
|
||||
<Column>
|
||||
<ColumnBackButton multiColumn={multiColumn} />
|
||||
<MissingIndicator />
|
||||
</Column>
|
||||
<BundleColumnError multiColumn={multiColumn} errorType='routing' />
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -206,3 +202,5 @@ class AccountTimeline extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(AccountTimeline);
|
||||
|
|
|
@ -22,7 +22,6 @@ const messages = defineMessages({
|
|||
const TICK_SIZE = 10;
|
||||
const PADDING = 180;
|
||||
|
||||
export default @injectIntl
|
||||
class Audio extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -471,7 +470,7 @@ class Audio extends React.PureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={classNames('audio-player', { editable, inactive: !revealed })} ref={this.setPlayerRef} style={{ backgroundColor: this._getBackgroundColor(), color: this._getForegroundColor(), width: '100%', height: this.props.fullscreen ? '100%' : (this.state.height || this.props.height) }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} tabIndex='0' onKeyDown={this.handleKeyDown}>
|
||||
<div className={classNames('audio-player', { editable, inactive: !revealed })} ref={this.setPlayerRef} style={{ backgroundColor: this._getBackgroundColor(), color: this._getForegroundColor(), width: '100%', height: this.props.fullscreen ? '100%' : (this.state.height || this.props.height) }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} tabIndex={0} onKeyDown={this.handleKeyDown}>
|
||||
|
||||
<Blurhash
|
||||
hash={blurhash}
|
||||
|
@ -494,7 +493,7 @@ class Audio extends React.PureComponent {
|
|||
|
||||
<canvas
|
||||
role='button'
|
||||
tabIndex='0'
|
||||
tabIndex={0}
|
||||
className='audio-player__canvas'
|
||||
width={this.state.width}
|
||||
height={this.state.height}
|
||||
|
@ -527,7 +526,7 @@ class Audio extends React.PureComponent {
|
|||
|
||||
<span
|
||||
className={classNames('video-player__seek__handle', { active: dragging })}
|
||||
tabIndex='0'
|
||||
tabIndex={0}
|
||||
style={{ left: `${progress}%`, backgroundColor: this._getAccentColor() }}
|
||||
onKeyDown={this.handleAudioKeyDown}
|
||||
/>
|
||||
|
@ -544,7 +543,7 @@ class Audio extends React.PureComponent {
|
|||
|
||||
<span
|
||||
className='video-player__volume__handle'
|
||||
tabIndex='0'
|
||||
tabIndex={0}
|
||||
style={{ left: `${volume * 100}%`, backgroundColor: this._getAccentColor() }}
|
||||
/>
|
||||
</div>
|
||||
|
@ -569,3 +568,5 @@ class Audio extends React.PureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default injectIntl(Audio);
|
||||
|
|
|
@ -22,8 +22,6 @@ const mapStateToProps = state => ({
|
|||
isLoading: state.getIn(['user_lists', 'blocks', 'isLoading'], true),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class Blocks extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -77,3 +75,5 @@ class Blocks extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(injectIntl(Blocks));
|
||||
|
|
|
@ -22,8 +22,6 @@ const mapStateToProps = state => ({
|
|||
hasMore: !!state.getIn(['status_lists', 'bookmarks', 'next']),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class Bookmarks extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -106,3 +104,5 @@ class Bookmarks extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(injectIntl(Bookmarks));
|
||||
|
|
|
@ -9,7 +9,6 @@ const mapStateToProps = state => ({
|
|||
message: state.getIn(['server', 'server', 'registrations', 'message']),
|
||||
});
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
class ClosedRegistrationsModal extends ImmutablePureComponent {
|
||||
|
||||
componentDidMount () {
|
||||
|
@ -73,3 +72,5 @@ class ClosedRegistrationsModal extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(ClosedRegistrationsModal);
|
||||
|
|
|
@ -4,7 +4,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
import { injectIntl, FormattedMessage } from 'react-intl';
|
||||
import SettingToggle from '../../notifications/components/setting_toggle';
|
||||
|
||||
export default @injectIntl
|
||||
class ColumnSettings extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -27,3 +26,5 @@ class ColumnSettings extends React.PureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default injectIntl(ColumnSettings);
|
||||
|
|
|
@ -30,8 +30,6 @@ const mapStateToProps = (state, { columnId }) => {
|
|||
};
|
||||
};
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class CommunityTimeline extends React.PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
|
@ -158,3 +156,5 @@ class CommunityTimeline extends React.PureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(injectIntl(CommunityTimeline));
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue