Merge pull request #3074 from ClearlyClaire/glitch-soc/merge-upstream
[Breaking changes] Merge upstram changes up to a13756148d353c7479f68e65a210f6d88d26c785
This commit is contained in:
commit
c9c2fbb27d
@ -9,6 +9,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
RAILS_ENV: development
|
RAILS_ENV: development
|
||||||
NODE_ENV: development
|
NODE_ENV: development
|
||||||
|
VITE_RUBY_HOST: 0.0.0.0
|
||||||
BIND: 0.0.0.0
|
BIND: 0.0.0.0
|
||||||
BOOTSNAP_CACHE_DIR: /tmp
|
BOOTSNAP_CACHE_DIR: /tmp
|
||||||
REDIS_HOST: redis
|
REDIS_HOST: redis
|
||||||
@ -22,11 +23,12 @@ services:
|
|||||||
ES_PORT: '9200'
|
ES_PORT: '9200'
|
||||||
LIBRE_TRANSLATE_ENDPOINT: http://libretranslate:5000
|
LIBRE_TRANSLATE_ENDPOINT: http://libretranslate:5000
|
||||||
LOCAL_DOMAIN: ${LOCAL_DOMAIN:-localhost:3000}
|
LOCAL_DOMAIN: ${LOCAL_DOMAIN:-localhost:3000}
|
||||||
|
VITE_DEV_SERVER_PUBLIC: ${VITE_DEV_SERVER_PUBLIC:-localhost:3036}
|
||||||
# Overrides default command so things don't shut down after the process ends.
|
# Overrides default command so things don't shut down after the process ends.
|
||||||
command: sleep infinity
|
command: sleep infinity
|
||||||
ports:
|
ports:
|
||||||
- '3000:3000'
|
- '3000:3000'
|
||||||
- '3035:3035'
|
- '3036:3036'
|
||||||
- '4000:4000'
|
- '4000:4000'
|
||||||
networks:
|
networks:
|
||||||
- external_network
|
- external_network
|
||||||
|
|||||||
@ -71,7 +71,6 @@ DB_PORT=5432
|
|||||||
# Generate each with the `RAILS_ENV=production bundle exec rails secret` task (`docker-compose run --rm web bundle exec rails secret` if you use docker compose)
|
# Generate each with the `RAILS_ENV=production bundle exec rails secret` task (`docker-compose run --rm web bundle exec rails secret` if you use docker compose)
|
||||||
# -------
|
# -------
|
||||||
SECRET_KEY_BASE=
|
SECRET_KEY_BASE=
|
||||||
OTP_SECRET=
|
|
||||||
|
|
||||||
# Encryption secrets
|
# Encryption secrets
|
||||||
# ------------------
|
# ------------------
|
||||||
|
|||||||
20
.github/renovate.json5
vendored
20
.github/renovate.json5
vendored
@ -25,26 +25,12 @@
|
|||||||
'tesseract.js', // Requires code changes
|
'tesseract.js', // Requires code changes
|
||||||
'react-hotkeys', // Requires code changes
|
'react-hotkeys', // Requires code changes
|
||||||
|
|
||||||
// Requires Webpacker upgrade or replacement
|
|
||||||
'@svgr/webpack',
|
|
||||||
'@types/webpack',
|
|
||||||
'babel-loader',
|
|
||||||
'compression-webpack-plugin',
|
|
||||||
'css-loader',
|
|
||||||
'imports-loader',
|
|
||||||
'mini-css-extract-plugin',
|
|
||||||
'postcss-loader',
|
|
||||||
'sass-loader',
|
|
||||||
'terser-webpack-plugin',
|
|
||||||
'webpack',
|
|
||||||
'webpack-assets-manifest',
|
|
||||||
'webpack-bundle-analyzer',
|
|
||||||
'webpack-dev-server',
|
|
||||||
'webpack-cli',
|
|
||||||
|
|
||||||
// react-router: Requires manual upgrade
|
// react-router: Requires manual upgrade
|
||||||
'history',
|
'history',
|
||||||
'react-router-dom',
|
'react-router-dom',
|
||||||
|
|
||||||
|
// react-spring: Requires manual upgrade when upgrading react
|
||||||
|
'@react-spring/web',
|
||||||
],
|
],
|
||||||
matchUpdateTypes: ['major'],
|
matchUpdateTypes: ['major'],
|
||||||
dependencyDashboardApproval: true,
|
dependencyDashboardApproval: true,
|
||||||
|
|||||||
18
.github/workflows/test-ruby.yml
vendored
18
.github/workflows/test-ruby.yml
vendored
@ -49,7 +49,7 @@ jobs:
|
|||||||
public/assets
|
public/assets
|
||||||
public/packs
|
public/packs
|
||||||
public/packs-test
|
public/packs-test
|
||||||
tmp/cache/webpacker
|
tmp/cache/vite
|
||||||
key: ${{ matrix.mode }}-assets-${{ github.head_ref || github.ref_name }}-${{ github.sha }}
|
key: ${{ matrix.mode }}-assets-${{ github.head_ref || github.ref_name }}-${{ github.sha }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ matrix.mode }}-assets-${{ github.head_ref || github.ref_name }}-${{ github.sha }}
|
${{ matrix.mode }}-assets-${{ github.head_ref || github.ref_name }}-${{ github.sha }}
|
||||||
@ -63,7 +63,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Archive asset artifacts
|
- name: Archive asset artifacts
|
||||||
run: |
|
run: |
|
||||||
tar --exclude={"*.br","*.gz"} -zcf artifacts.tar.gz public/assets public/packs*
|
tar --exclude={"*.br","*.gz"} -zcf artifacts.tar.gz public/assets public/packs* tmp/cache/vite/last-build*.json
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
if: matrix.mode == 'test'
|
if: matrix.mode == 'test'
|
||||||
@ -143,7 +143,7 @@ jobs:
|
|||||||
uses: ./.github/actions/setup-ruby
|
uses: ./.github/actions/setup-ruby
|
||||||
with:
|
with:
|
||||||
ruby-version: ${{ matrix.ruby-version}}
|
ruby-version: ${{ matrix.ruby-version}}
|
||||||
additional-system-dependencies: ffmpeg imagemagick libpam-dev
|
additional-system-dependencies: ffmpeg libpam-dev
|
||||||
|
|
||||||
- name: Load database schema
|
- name: Load database schema
|
||||||
run: |
|
run: |
|
||||||
@ -173,8 +173,8 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
||||||
test-libvips:
|
test-imagemagick:
|
||||||
name: Libvips tests
|
name: ImageMagick tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
needs:
|
needs:
|
||||||
@ -220,7 +220,7 @@ jobs:
|
|||||||
CAS_ENABLED: true
|
CAS_ENABLED: true
|
||||||
BUNDLE_WITH: 'pam_authentication test'
|
BUNDLE_WITH: 'pam_authentication test'
|
||||||
GITHUB_RSPEC: ${{ matrix.ruby-version == '.ruby-version' && github.event.pull_request && 'true' }}
|
GITHUB_RSPEC: ${{ matrix.ruby-version == '.ruby-version' && github.event.pull_request && 'true' }}
|
||||||
MASTODON_USE_LIBVIPS: true
|
MASTODON_USE_LIBVIPS: false
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@ -245,7 +245,7 @@ jobs:
|
|||||||
uses: ./.github/actions/setup-ruby
|
uses: ./.github/actions/setup-ruby
|
||||||
with:
|
with:
|
||||||
ruby-version: ${{ matrix.ruby-version}}
|
ruby-version: ${{ matrix.ruby-version}}
|
||||||
additional-system-dependencies: ffmpeg libpam-dev
|
additional-system-dependencies: ffmpeg imagemagick libpam-dev
|
||||||
|
|
||||||
- name: Load database schema
|
- name: Load database schema
|
||||||
run: './bin/rails db:create db:schema:load db:seed'
|
run: './bin/rails db:create db:schema:load db:seed'
|
||||||
@ -324,7 +324,7 @@ jobs:
|
|||||||
uses: ./.github/actions/setup-ruby
|
uses: ./.github/actions/setup-ruby
|
||||||
with:
|
with:
|
||||||
ruby-version: ${{ matrix.ruby-version}}
|
ruby-version: ${{ matrix.ruby-version}}
|
||||||
additional-system-dependencies: ffmpeg imagemagick
|
additional-system-dependencies: ffmpeg
|
||||||
|
|
||||||
- name: Set up Javascript environment
|
- name: Set up Javascript environment
|
||||||
uses: ./.github/actions/setup-javascript
|
uses: ./.github/actions/setup-javascript
|
||||||
@ -443,7 +443,7 @@ jobs:
|
|||||||
uses: ./.github/actions/setup-ruby
|
uses: ./.github/actions/setup-ruby
|
||||||
with:
|
with:
|
||||||
ruby-version: ${{ matrix.ruby-version}}
|
ruby-version: ${{ matrix.ruby-version}}
|
||||||
additional-system-dependencies: ffmpeg imagemagick
|
additional-system-dependencies: ffmpeg
|
||||||
|
|
||||||
- name: Set up Javascript environment
|
- name: Set up Javascript environment
|
||||||
uses: ./.github/actions/setup-javascript
|
uses: ./.github/actions/setup-javascript
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -21,10 +21,11 @@
|
|||||||
/public/system
|
/public/system
|
||||||
/public/assets
|
/public/assets
|
||||||
/public/packs
|
/public/packs
|
||||||
|
/public/packs-dev
|
||||||
/public/packs-test
|
/public/packs-test
|
||||||
.env
|
.env
|
||||||
.env.production
|
.env.production
|
||||||
/node_modules/
|
node_modules/
|
||||||
/build/
|
/build/
|
||||||
|
|
||||||
# Ignore Vagrant files
|
# Ignore Vagrant files
|
||||||
|
|||||||
@ -18,10 +18,6 @@
|
|||||||
!/log/.keep
|
!/log/.keep
|
||||||
/tmp
|
/tmp
|
||||||
/coverage
|
/coverage
|
||||||
/public/system
|
|
||||||
/public/assets
|
|
||||||
/public/packs
|
|
||||||
/public/packs-test
|
|
||||||
.env
|
.env
|
||||||
.env.production
|
.env.production
|
||||||
.env.development
|
.env.development
|
||||||
@ -60,6 +56,7 @@ docker-compose.override.yml
|
|||||||
/public/packs
|
/public/packs
|
||||||
/public/packs-test
|
/public/packs-test
|
||||||
/public/system
|
/public/system
|
||||||
|
/public/vite*
|
||||||
|
|
||||||
# Ignore emoji map file
|
# Ignore emoji map file
|
||||||
/app/javascript/mastodon/features/emoji/emoji_map.json
|
/app/javascript/mastodon/features/emoji/emoji_map.json
|
||||||
|
|||||||
@ -70,13 +70,6 @@ Style/OptionalBooleanParameter:
|
|||||||
- 'app/workers/domain_block_worker.rb'
|
- 'app/workers/domain_block_worker.rb'
|
||||||
- 'app/workers/unfollow_follow_worker.rb'
|
- 'app/workers/unfollow_follow_worker.rb'
|
||||||
|
|
||||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
|
||||||
# Configuration parameters: EnforcedStyle.
|
|
||||||
# SupportedStyles: short, verbose
|
|
||||||
Style/PreferredHashMethods:
|
|
||||||
Exclude:
|
|
||||||
- 'config/initializers/paperclip.rb'
|
|
||||||
|
|
||||||
# This cop supports safe autocorrection (--autocorrect).
|
# This cop supports safe autocorrection (--autocorrect).
|
||||||
Style/RedundantConstantBase:
|
Style/RedundantConstantBase:
|
||||||
Exclude:
|
Exclude:
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
3.4.3
|
3.4.4
|
||||||
|
|||||||
@ -62,6 +62,11 @@ Bug reports and feature suggestions must use descriptive and concise titles and
|
|||||||
be submitted to [GitHub Issues]. Please use the search function to make sure
|
be submitted to [GitHub Issues]. Please use the search function to make sure
|
||||||
there are not duplicate bug reports or feature requests.
|
there are not duplicate bug reports or feature requests.
|
||||||
|
|
||||||
|
## Security Issues
|
||||||
|
|
||||||
|
If you believe you have identified a security issue in Mastodon or our own apps,
|
||||||
|
check [SECURITY].
|
||||||
|
|
||||||
## Translations
|
## Translations
|
||||||
|
|
||||||
Translations are community contributed via [Crowdin]. They are periodically
|
Translations are community contributed via [Crowdin]. They are periodically
|
||||||
@ -124,3 +129,4 @@ and API docs. Improvements are made via PRs to the [documentation repository].
|
|||||||
[GitHub Issues]: https://github.com/mastodon/mastodon/issues
|
[GitHub Issues]: https://github.com/mastodon/mastodon/issues
|
||||||
[keepachangelog]: https://keepachangelog.com/en/1.0.0/
|
[keepachangelog]: https://keepachangelog.com/en/1.0.0/
|
||||||
[Mastodon documentation]: https://docs.joinmastodon.org
|
[Mastodon documentation]: https://docs.joinmastodon.org
|
||||||
|
[SECURITY]: SECURITY.md
|
||||||
|
|||||||
@ -13,7 +13,7 @@ ARG BASE_REGISTRY="docker.io"
|
|||||||
|
|
||||||
# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.4.x"]
|
# Ruby image to use for base image, change with [--build-arg RUBY_VERSION="3.4.x"]
|
||||||
# renovate: datasource=docker depName=docker.io/ruby
|
# renovate: datasource=docker depName=docker.io/ruby
|
||||||
ARG RUBY_VERSION="3.4.3"
|
ARG RUBY_VERSION="3.4.4"
|
||||||
# # Node.js version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"]
|
# # Node.js version to use in base image, change with [--build-arg NODE_MAJOR_VERSION="20"]
|
||||||
# renovate: datasource=node-version depName=node
|
# renovate: datasource=node-version depName=node
|
||||||
ARG NODE_MAJOR_VERSION="22"
|
ARG NODE_MAJOR_VERSION="22"
|
||||||
|
|||||||
6
Gemfile
6
Gemfile
@ -62,7 +62,7 @@ gem 'inline_svg'
|
|||||||
gem 'irb', '~> 1.8'
|
gem 'irb', '~> 1.8'
|
||||||
gem 'kaminari', '~> 1.2'
|
gem 'kaminari', '~> 1.2'
|
||||||
gem 'link_header', '~> 0.0'
|
gem 'link_header', '~> 0.0'
|
||||||
gem 'linzer', '~> 0.6.1'
|
gem 'linzer', '~> 0.7.2'
|
||||||
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
|
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
|
||||||
gem 'mime-types', '~> 3.7.0', require: 'mime/types/columnar'
|
gem 'mime-types', '~> 3.7.0', require: 'mime/types/columnar'
|
||||||
gem 'mutex_m'
|
gem 'mutex_m'
|
||||||
@ -78,7 +78,6 @@ gem 'rack-cors', '~> 2.0', require: 'rack/cors'
|
|||||||
gem 'rails-i18n', '~> 8.0'
|
gem 'rails-i18n', '~> 8.0'
|
||||||
gem 'redcarpet', '~> 3.6'
|
gem 'redcarpet', '~> 3.6'
|
||||||
gem 'redis', '~> 4.5', require: ['redis', 'redis/connection/hiredis']
|
gem 'redis', '~> 4.5', require: ['redis', 'redis/connection/hiredis']
|
||||||
gem 'redis-namespace', '~> 1.10'
|
|
||||||
gem 'rqrcode', '~> 3.0'
|
gem 'rqrcode', '~> 3.0'
|
||||||
gem 'ruby-progressbar', '~> 1.13'
|
gem 'ruby-progressbar', '~> 1.13'
|
||||||
gem 'sanitize', '~> 7.0'
|
gem 'sanitize', '~> 7.0'
|
||||||
@ -95,7 +94,6 @@ gem 'tty-prompt', '~> 0.23', require: false
|
|||||||
gem 'twitter-text', '~> 3.1.0'
|
gem 'twitter-text', '~> 3.1.0'
|
||||||
gem 'tzinfo-data', '~> 1.2023'
|
gem 'tzinfo-data', '~> 1.2023'
|
||||||
gem 'webauthn', '~> 3.0'
|
gem 'webauthn', '~> 3.0'
|
||||||
gem 'webpacker', '~> 5.4'
|
|
||||||
gem 'webpush', github: 'mastodon/webpush', ref: '9631ac63045cfabddacc69fc06e919b4c13eb913'
|
gem 'webpush', github: 'mastodon/webpush', ref: '9631ac63045cfabddacc69fc06e919b4c13eb913'
|
||||||
|
|
||||||
gem 'json-ld'
|
gem 'json-ld'
|
||||||
@ -230,3 +228,5 @@ gem 'rubyzip', '~> 2.3'
|
|||||||
gem 'hcaptcha', '~> 7.1'
|
gem 'hcaptcha', '~> 7.1'
|
||||||
|
|
||||||
gem 'mail', '~> 2.8'
|
gem 'mail', '~> 2.8'
|
||||||
|
|
||||||
|
gem 'vite_rails', '~> 3.0.19'
|
||||||
|
|||||||
76
Gemfile.lock
76
Gemfile.lock
@ -94,7 +94,7 @@ GEM
|
|||||||
ast (2.4.3)
|
ast (2.4.3)
|
||||||
attr_required (1.0.2)
|
attr_required (1.0.2)
|
||||||
aws-eventstream (1.3.2)
|
aws-eventstream (1.3.2)
|
||||||
aws-partitions (1.1087.0)
|
aws-partitions (1.1103.0)
|
||||||
aws-sdk-core (3.215.1)
|
aws-sdk-core (3.215.1)
|
||||||
aws-eventstream (~> 1, >= 1.3.0)
|
aws-eventstream (~> 1, >= 1.3.0)
|
||||||
aws-partitions (~> 1, >= 1.992.0)
|
aws-partitions (~> 1, >= 1.992.0)
|
||||||
@ -109,7 +109,7 @@ GEM
|
|||||||
aws-sigv4 (~> 1.5)
|
aws-sigv4 (~> 1.5)
|
||||||
aws-sigv4 (1.11.0)
|
aws-sigv4 (1.11.0)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
azure-blob (0.5.7)
|
azure-blob (0.5.8)
|
||||||
rexml
|
rexml
|
||||||
base64 (0.2.0)
|
base64 (0.2.0)
|
||||||
bcp47_spec (0.2.1)
|
bcp47_spec (0.2.1)
|
||||||
@ -124,7 +124,7 @@ GEM
|
|||||||
binding_of_caller (1.0.1)
|
binding_of_caller (1.0.1)
|
||||||
debug_inspector (>= 1.2.0)
|
debug_inspector (>= 1.2.0)
|
||||||
blurhash (0.1.8)
|
blurhash (0.1.8)
|
||||||
bootsnap (1.18.5)
|
bootsnap (1.18.6)
|
||||||
msgpack (~> 1.2)
|
msgpack (~> 1.2)
|
||||||
brakeman (7.0.2)
|
brakeman (7.0.2)
|
||||||
racc
|
racc
|
||||||
@ -148,6 +148,7 @@ GEM
|
|||||||
case_transform (0.2)
|
case_transform (0.2)
|
||||||
activesupport
|
activesupport
|
||||||
cbor (0.5.9.8)
|
cbor (0.5.9.8)
|
||||||
|
cgi (0.4.2)
|
||||||
charlock_holmes (0.7.9)
|
charlock_holmes (0.7.9)
|
||||||
chewy (7.6.0)
|
chewy (7.6.0)
|
||||||
activesupport (>= 5.2)
|
activesupport (>= 5.2)
|
||||||
@ -194,7 +195,7 @@ GEM
|
|||||||
devise_pam_authenticatable2 (9.2.0)
|
devise_pam_authenticatable2 (9.2.0)
|
||||||
devise (>= 4.0.0)
|
devise (>= 4.0.0)
|
||||||
rpam2 (~> 4.0)
|
rpam2 (~> 4.0)
|
||||||
diff-lcs (1.6.1)
|
diff-lcs (1.6.2)
|
||||||
discard (1.4.0)
|
discard (1.4.0)
|
||||||
activerecord (>= 4.2, < 9.0)
|
activerecord (>= 4.2, < 9.0)
|
||||||
docile (1.4.1)
|
docile (1.4.1)
|
||||||
@ -203,6 +204,7 @@ GEM
|
|||||||
railties (>= 5)
|
railties (>= 5)
|
||||||
dotenv (3.1.8)
|
dotenv (3.1.8)
|
||||||
drb (2.2.1)
|
drb (2.2.1)
|
||||||
|
dry-cli (1.2.0)
|
||||||
elasticsearch (7.17.11)
|
elasticsearch (7.17.11)
|
||||||
elasticsearch-api (= 7.17.11)
|
elasticsearch-api (= 7.17.11)
|
||||||
elasticsearch-transport (= 7.17.11)
|
elasticsearch-transport (= 7.17.11)
|
||||||
@ -227,7 +229,7 @@ GEM
|
|||||||
fabrication (2.31.0)
|
fabrication (2.31.0)
|
||||||
faker (3.5.1)
|
faker (3.5.1)
|
||||||
i18n (>= 1.8.11, < 2)
|
i18n (>= 1.8.11, < 2)
|
||||||
faraday (2.13.0)
|
faraday (2.13.1)
|
||||||
faraday-net_http (>= 2.0, < 3.5)
|
faraday-net_http (>= 2.0, < 3.5)
|
||||||
json
|
json
|
||||||
logger
|
logger
|
||||||
@ -261,15 +263,16 @@ GEM
|
|||||||
fog-core (~> 2.1)
|
fog-core (~> 2.1)
|
||||||
fog-json (>= 1.0)
|
fog-json (>= 1.0)
|
||||||
formatador (1.1.0)
|
formatador (1.1.0)
|
||||||
|
forwardable (1.3.3)
|
||||||
fugit (1.11.1)
|
fugit (1.11.1)
|
||||||
et-orbi (~> 1, >= 1.2.11)
|
et-orbi (~> 1, >= 1.2.11)
|
||||||
raabro (~> 1.4)
|
raabro (~> 1.4)
|
||||||
globalid (1.2.1)
|
globalid (1.2.1)
|
||||||
activesupport (>= 6.1)
|
activesupport (>= 6.1)
|
||||||
google-protobuf (4.30.2)
|
google-protobuf (4.31.0)
|
||||||
bigdecimal
|
bigdecimal
|
||||||
rake (>= 13)
|
rake (>= 13)
|
||||||
googleapis-common-protos-types (1.19.0)
|
googleapis-common-protos-types (1.20.0)
|
||||||
google-protobuf (>= 3.18, < 5.a)
|
google-protobuf (>= 3.18, < 5.a)
|
||||||
haml (6.3.0)
|
haml (6.3.0)
|
||||||
temple (>= 0.8.2)
|
temple (>= 0.8.2)
|
||||||
@ -337,7 +340,7 @@ GEM
|
|||||||
azure-blob (~> 0.5.2)
|
azure-blob (~> 0.5.2)
|
||||||
hashie (~> 5.0)
|
hashie (~> 5.0)
|
||||||
jmespath (1.6.2)
|
jmespath (1.6.2)
|
||||||
json (2.10.2)
|
json (2.12.0)
|
||||||
json-canonicalization (1.0.0)
|
json-canonicalization (1.0.0)
|
||||||
json-jwt (1.16.7)
|
json-jwt (1.16.7)
|
||||||
activesupport (>= 4.2)
|
activesupport (>= 4.2)
|
||||||
@ -381,7 +384,7 @@ GEM
|
|||||||
marcel (~> 1.0.1)
|
marcel (~> 1.0.1)
|
||||||
mime-types
|
mime-types
|
||||||
terrapin (>= 0.6.0, < 2.0)
|
terrapin (>= 0.6.0, < 2.0)
|
||||||
language_server-protocol (3.17.0.4)
|
language_server-protocol (3.17.0.5)
|
||||||
launchy (3.1.1)
|
launchy (3.1.1)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
childprocess (~> 5.0)
|
childprocess (~> 5.0)
|
||||||
@ -395,7 +398,11 @@ GEM
|
|||||||
rexml
|
rexml
|
||||||
link_header (0.0.8)
|
link_header (0.0.8)
|
||||||
lint_roller (1.1.0)
|
lint_roller (1.1.0)
|
||||||
linzer (0.6.5)
|
linzer (0.7.2)
|
||||||
|
cgi (~> 0.4.2)
|
||||||
|
forwardable (~> 1.3, >= 1.3.3)
|
||||||
|
logger (~> 1.7, >= 1.7.0)
|
||||||
|
net-http (~> 0.6.0)
|
||||||
openssl (~> 3.0, >= 3.0.0)
|
openssl (~> 3.0, >= 3.0.0)
|
||||||
rack (>= 2.2, < 4.0)
|
rack (>= 2.2, < 4.0)
|
||||||
starry (~> 0.2)
|
starry (~> 0.2)
|
||||||
@ -410,7 +417,7 @@ GEM
|
|||||||
activesupport (>= 4)
|
activesupport (>= 4)
|
||||||
railties (>= 4)
|
railties (>= 4)
|
||||||
request_store (~> 1.0)
|
request_store (~> 1.0)
|
||||||
loofah (2.24.0)
|
loofah (2.24.1)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.12.0)
|
nokogiri (>= 1.12.0)
|
||||||
mail (2.8.1)
|
mail (2.8.1)
|
||||||
@ -426,9 +433,9 @@ GEM
|
|||||||
mime-types (3.7.0)
|
mime-types (3.7.0)
|
||||||
logger
|
logger
|
||||||
mime-types-data (~> 3.2025, >= 3.2025.0507)
|
mime-types-data (~> 3.2025, >= 3.2025.0507)
|
||||||
mime-types-data (3.2025.0507)
|
mime-types-data (3.2025.0514)
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
mini_portile2 (2.8.8)
|
mini_portile2 (2.8.9)
|
||||||
minitest (5.25.5)
|
minitest (5.25.5)
|
||||||
msgpack (1.8.0)
|
msgpack (1.8.0)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
@ -503,7 +510,7 @@ GEM
|
|||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-active_support (~> 0.7)
|
opentelemetry-instrumentation-active_support (~> 0.7)
|
||||||
opentelemetry-instrumentation-base (~> 0.23.0)
|
opentelemetry-instrumentation-base (~> 0.23.0)
|
||||||
opentelemetry-instrumentation-action_pack (0.12.0)
|
opentelemetry-instrumentation-action_pack (0.12.1)
|
||||||
opentelemetry-api (~> 1.0)
|
opentelemetry-api (~> 1.0)
|
||||||
opentelemetry-instrumentation-base (~> 0.23.0)
|
opentelemetry-instrumentation-base (~> 0.23.0)
|
||||||
opentelemetry-instrumentation-rack (~> 0.21)
|
opentelemetry-instrumentation-rack (~> 0.21)
|
||||||
@ -617,7 +624,7 @@ GEM
|
|||||||
activesupport (>= 7.0.0)
|
activesupport (>= 7.0.0)
|
||||||
rack
|
rack
|
||||||
railties (>= 7.0.0)
|
railties (>= 7.0.0)
|
||||||
psych (5.2.3)
|
psych (5.2.6)
|
||||||
date
|
date
|
||||||
stringio
|
stringio
|
||||||
public_suffix (6.0.2)
|
public_suffix (6.0.2)
|
||||||
@ -627,7 +634,7 @@ GEM
|
|||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
raabro (1.4.0)
|
raabro (1.4.0)
|
||||||
racc (1.8.1)
|
racc (1.8.1)
|
||||||
rack (2.2.14)
|
rack (2.2.16)
|
||||||
rack-attack (6.7.0)
|
rack-attack (6.7.0)
|
||||||
rack (>= 1.0, < 4)
|
rack (>= 1.0, < 4)
|
||||||
rack-cors (2.0.2)
|
rack-cors (2.0.2)
|
||||||
@ -695,8 +702,6 @@ GEM
|
|||||||
psych (>= 4.0.0)
|
psych (>= 4.0.0)
|
||||||
redcarpet (3.6.1)
|
redcarpet (3.6.1)
|
||||||
redis (4.8.1)
|
redis (4.8.1)
|
||||||
redis-namespace (1.11.0)
|
|
||||||
redis (>= 4)
|
|
||||||
redlock (1.3.2)
|
redlock (1.3.2)
|
||||||
redis (>= 3.0.0, < 6.0)
|
redis (>= 3.0.0, < 6.0)
|
||||||
regexp_parser (2.10.0)
|
regexp_parser (2.10.0)
|
||||||
@ -709,7 +714,7 @@ GEM
|
|||||||
railties (>= 5.2)
|
railties (>= 5.2)
|
||||||
rexml (3.4.1)
|
rexml (3.4.1)
|
||||||
rotp (6.3.0)
|
rotp (6.3.0)
|
||||||
rouge (4.5.1)
|
rouge (4.5.2)
|
||||||
rpam2 (4.0.2)
|
rpam2 (4.0.2)
|
||||||
rqrcode (3.1.0)
|
rqrcode (3.1.0)
|
||||||
chunky_png (~> 1.0)
|
chunky_png (~> 1.0)
|
||||||
@ -726,7 +731,7 @@ GEM
|
|||||||
rspec-support (~> 3.13.0)
|
rspec-support (~> 3.13.0)
|
||||||
rspec-github (3.0.0)
|
rspec-github (3.0.0)
|
||||||
rspec-core (~> 3.0)
|
rspec-core (~> 3.0)
|
||||||
rspec-mocks (3.13.3)
|
rspec-mocks (3.13.4)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.13.0)
|
rspec-support (~> 3.13.0)
|
||||||
rspec-rails (8.0.0)
|
rspec-rails (8.0.0)
|
||||||
@ -743,7 +748,7 @@ GEM
|
|||||||
rspec-mocks (~> 3.0)
|
rspec-mocks (~> 3.0)
|
||||||
sidekiq (>= 5, < 9)
|
sidekiq (>= 5, < 9)
|
||||||
rspec-support (3.13.3)
|
rspec-support (3.13.3)
|
||||||
rubocop (1.75.5)
|
rubocop (1.75.7)
|
||||||
json (~> 2.3)
|
json (~> 2.3)
|
||||||
language_server-protocol (~> 3.17.0.2)
|
language_server-protocol (~> 3.17.0.2)
|
||||||
lint_roller (~> 1.1.0)
|
lint_roller (~> 1.1.0)
|
||||||
@ -767,12 +772,12 @@ GEM
|
|||||||
lint_roller (~> 1.1)
|
lint_roller (~> 1.1)
|
||||||
rubocop (>= 1.75.0, < 2.0)
|
rubocop (>= 1.75.0, < 2.0)
|
||||||
rubocop-ast (>= 1.38.0, < 2.0)
|
rubocop-ast (>= 1.38.0, < 2.0)
|
||||||
rubocop-rails (2.31.0)
|
rubocop-rails (2.32.0)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
lint_roller (~> 1.1)
|
lint_roller (~> 1.1)
|
||||||
rack (>= 1.1)
|
rack (>= 1.1)
|
||||||
rubocop (>= 1.75.0, < 2.0)
|
rubocop (>= 1.75.0, < 2.0)
|
||||||
rubocop-ast (>= 1.38.0, < 2.0)
|
rubocop-ast (>= 1.44.0, < 2.0)
|
||||||
rubocop-rspec (3.6.0)
|
rubocop-rspec (3.6.0)
|
||||||
lint_roller (~> 1.1)
|
lint_roller (~> 1.1)
|
||||||
rubocop (~> 1.72, >= 1.72.1)
|
rubocop (~> 1.72, >= 1.72.1)
|
||||||
@ -780,7 +785,8 @@ GEM
|
|||||||
lint_roller (~> 1.1)
|
lint_roller (~> 1.1)
|
||||||
rubocop (~> 1.72, >= 1.72.1)
|
rubocop (~> 1.72, >= 1.72.1)
|
||||||
rubocop-rspec (~> 3.5)
|
rubocop-rspec (~> 3.5)
|
||||||
ruby-prof (1.7.1)
|
ruby-prof (1.7.2)
|
||||||
|
base64
|
||||||
ruby-progressbar (1.13.0)
|
ruby-progressbar (1.13.0)
|
||||||
ruby-saml (1.18.0)
|
ruby-saml (1.18.0)
|
||||||
nokogiri (>= 1.13.10)
|
nokogiri (>= 1.13.10)
|
||||||
@ -806,7 +812,6 @@ GEM
|
|||||||
rexml (~> 3.2, >= 3.2.5)
|
rexml (~> 3.2, >= 3.2.5)
|
||||||
rubyzip (>= 1.2.2, < 3.0)
|
rubyzip (>= 1.2.2, < 3.0)
|
||||||
websocket (~> 1.0)
|
websocket (~> 1.0)
|
||||||
semantic_range (3.1.0)
|
|
||||||
shoulda-matchers (6.5.0)
|
shoulda-matchers (6.5.0)
|
||||||
activesupport (>= 5.2.0)
|
activesupport (>= 5.2.0)
|
||||||
sidekiq (6.5.12)
|
sidekiq (6.5.12)
|
||||||
@ -892,6 +897,15 @@ GEM
|
|||||||
validate_url (1.0.15)
|
validate_url (1.0.15)
|
||||||
activemodel (>= 3.0.0)
|
activemodel (>= 3.0.0)
|
||||||
public_suffix
|
public_suffix
|
||||||
|
vite_rails (3.0.19)
|
||||||
|
railties (>= 5.1, < 9)
|
||||||
|
vite_ruby (~> 3.0, >= 3.2.2)
|
||||||
|
vite_ruby (3.9.2)
|
||||||
|
dry-cli (>= 0.7, < 2)
|
||||||
|
logger (~> 1.6)
|
||||||
|
mutex_m
|
||||||
|
rack-proxy (~> 0.6, >= 0.6.1)
|
||||||
|
zeitwerk (~> 2.2)
|
||||||
warden (1.2.9)
|
warden (1.2.9)
|
||||||
rack (>= 2.0.9)
|
rack (>= 2.0.9)
|
||||||
webauthn (3.4.0)
|
webauthn (3.4.0)
|
||||||
@ -910,11 +924,6 @@ GEM
|
|||||||
addressable (>= 2.8.0)
|
addressable (>= 2.8.0)
|
||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
hashdiff (>= 0.4.0, < 2.0.0)
|
hashdiff (>= 0.4.0, < 2.0.0)
|
||||||
webpacker (5.4.4)
|
|
||||||
activesupport (>= 5.2)
|
|
||||||
rack-proxy (>= 0.6.1)
|
|
||||||
railties (>= 5.2)
|
|
||||||
semantic_range (>= 2.3.0)
|
|
||||||
webrick (1.9.1)
|
webrick (1.9.1)
|
||||||
websocket (1.2.11)
|
websocket (1.2.11)
|
||||||
websocket-driver (0.7.7)
|
websocket-driver (0.7.7)
|
||||||
@ -991,7 +1000,7 @@ DEPENDENCIES
|
|||||||
letter_opener (~> 1.8)
|
letter_opener (~> 1.8)
|
||||||
letter_opener_web (~> 3.0)
|
letter_opener_web (~> 3.0)
|
||||||
link_header (~> 0.0)
|
link_header (~> 0.0)
|
||||||
linzer (~> 0.6.1)
|
linzer (~> 0.7.2)
|
||||||
lograge (~> 0.12)
|
lograge (~> 0.12)
|
||||||
mail (~> 2.8)
|
mail (~> 2.8)
|
||||||
mario-redis-lock (~> 1.2)
|
mario-redis-lock (~> 1.2)
|
||||||
@ -1042,7 +1051,6 @@ DEPENDENCIES
|
|||||||
rdf-normalize (~> 0.5)
|
rdf-normalize (~> 0.5)
|
||||||
redcarpet (~> 3.6)
|
redcarpet (~> 3.6)
|
||||||
redis (~> 4.5)
|
redis (~> 4.5)
|
||||||
redis-namespace (~> 1.10)
|
|
||||||
rqrcode (~> 3.0)
|
rqrcode (~> 3.0)
|
||||||
rspec-github (~> 3.0)
|
rspec-github (~> 3.0)
|
||||||
rspec-rails (~> 8.0)
|
rspec-rails (~> 8.0)
|
||||||
@ -1078,9 +1086,9 @@ DEPENDENCIES
|
|||||||
tty-prompt (~> 0.23)
|
tty-prompt (~> 0.23)
|
||||||
twitter-text (~> 3.1.0)
|
twitter-text (~> 3.1.0)
|
||||||
tzinfo-data (~> 1.2023)
|
tzinfo-data (~> 1.2023)
|
||||||
|
vite_rails (~> 3.0.19)
|
||||||
webauthn (~> 3.0)
|
webauthn (~> 3.0)
|
||||||
webmock (~> 3.18)
|
webmock (~> 3.18)
|
||||||
webpacker (~> 5.4)
|
|
||||||
webpush!
|
webpush!
|
||||||
xorcist (~> 1.1)
|
xorcist (~> 1.1)
|
||||||
|
|
||||||
@ -1088,4 +1096,4 @@ RUBY VERSION
|
|||||||
ruby 3.4.1p0
|
ruby 3.4.1p0
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.6.8
|
2.6.9
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
web: env PORT=3000 RAILS_ENV=development bundle exec puma -C config/puma.rb
|
web: env PORT=3000 RAILS_ENV=development bundle exec puma -C config/puma.rb
|
||||||
sidekiq: env PORT=3000 RAILS_ENV=development bundle exec sidekiq
|
sidekiq: env PORT=3000 RAILS_ENV=development bundle exec sidekiq
|
||||||
stream: env PORT=4000 yarn workspace @mastodon/streaming start
|
stream: env PORT=4000 yarn workspace @mastodon/streaming start
|
||||||
webpack: bin/webpack-dev-server
|
vite: yarn dev
|
||||||
|
|||||||
10
README.md
10
README.md
@ -73,10 +73,10 @@ Mastodon is a **free, open-source social network server** based on ActivityPub w
|
|||||||
|
|
||||||
### Requirements
|
### Requirements
|
||||||
|
|
||||||
- **PostgreSQL** 12+
|
- **PostgreSQL** 13+
|
||||||
- **Redis** 4+
|
- **Redis** 6.2+
|
||||||
- **Ruby** 3.2+
|
- **Ruby** 3.2+
|
||||||
- **Node.js** 18+
|
- **Node.js** 20+
|
||||||
|
|
||||||
The repository includes deployment configurations for **Docker and docker-compose** as well as specific platforms like **Heroku**, and **Scalingo**. For Helm charts, reference the [mastodon/chart repository](https://github.com/mastodon/chart). The [**standalone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the documentation.
|
The repository includes deployment configurations for **Docker and docker-compose** as well as specific platforms like **Heroku**, and **Scalingo**. For Helm charts, reference the [mastodon/chart repository](https://github.com/mastodon/chart). The [**standalone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the documentation.
|
||||||
|
|
||||||
@ -93,12 +93,12 @@ accepted into Mastodon, you can request to be paid through our [OpenCollective].
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright (c) 2016-2024 Eugen Rochko (+ [`mastodon authors`](AUTHORS.md))
|
Copyright (c) 2016-2025 Eugen Rochko (+ [`mastodon authors`](AUTHORS.md))
|
||||||
|
|
||||||
Licensed under GNU Affero General Public License as stated in the [LICENSE](LICENSE):
|
Licensed under GNU Affero General Public License as stated in the [LICENSE](LICENSE):
|
||||||
|
|
||||||
```
|
```
|
||||||
Copyright (c) 2016-2024 Eugen Rochko & other Mastodon contributors
|
Copyright (c) 2016-2025 Eugen Rochko & other Mastodon contributors
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify it under
|
This program is free software: you can redistribute it and/or modify it under
|
||||||
the terms of the GNU Affero General Public License as published by the Free
|
the terms of the GNU Affero General Public License as published by the Free
|
||||||
|
|||||||
4
app.json
4
app.json
@ -17,10 +17,6 @@
|
|||||||
"description": "The secret key base",
|
"description": "The secret key base",
|
||||||
"generator": "secret"
|
"generator": "secret"
|
||||||
},
|
},
|
||||||
"OTP_SECRET": {
|
|
||||||
"description": "One-time password secret",
|
|
||||||
"generator": "secret"
|
|
||||||
},
|
|
||||||
"SINGLE_USER_MODE": {
|
"SINGLE_USER_MODE": {
|
||||||
"description": "Should the instance run in single user mode? (Disable registrations, redirect to front page)",
|
"description": "Should the instance run in single user mode? (Disable registrations, redirect to front page)",
|
||||||
"value": "false",
|
"value": "false",
|
||||||
|
|||||||
@ -7,7 +7,7 @@ module Admin
|
|||||||
def index
|
def index
|
||||||
authorize :rule, :index?
|
authorize :rule, :index?
|
||||||
|
|
||||||
@rules = Rule.ordered
|
@rules = Rule.ordered.includes(:translations)
|
||||||
end
|
end
|
||||||
|
|
||||||
def new
|
def new
|
||||||
@ -27,7 +27,6 @@ module Admin
|
|||||||
if @rule.save
|
if @rule.save
|
||||||
redirect_to admin_rules_path
|
redirect_to admin_rules_path
|
||||||
else
|
else
|
||||||
@rules = Rule.ordered
|
|
||||||
render :new
|
render :new
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -50,6 +49,22 @@ module Admin
|
|||||||
redirect_to admin_rules_path
|
redirect_to admin_rules_path
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def move_up
|
||||||
|
authorize @rule, :update?
|
||||||
|
|
||||||
|
@rule.move!(-1)
|
||||||
|
|
||||||
|
redirect_to admin_rules_path
|
||||||
|
end
|
||||||
|
|
||||||
|
def move_down
|
||||||
|
authorize @rule, :update?
|
||||||
|
|
||||||
|
@rule.move!(+1)
|
||||||
|
|
||||||
|
redirect_to admin_rules_path
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_rule
|
def set_rule
|
||||||
@ -58,7 +73,7 @@ module Admin
|
|||||||
|
|
||||||
def resource_params
|
def resource_params
|
||||||
params
|
params
|
||||||
.expect(rule: [:text, :hint, :priority])
|
.expect(rule: [:text, :hint, :priority, translations_attributes: [[:id, :language, :text, :hint, :_destroy]]])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -42,37 +42,22 @@ class Api::Fasp::BaseController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def validate_signature!
|
def validate_signature!
|
||||||
signature_input = request.headers['signature-input']&.encode('UTF-8')
|
raise Error, 'signature-input is missing' if request.headers['signature-input'].blank?
|
||||||
raise Error, 'signature-input is missing' if signature_input.blank?
|
|
||||||
|
|
||||||
keyid = signature_input.match(KEYID_PATTERN)[1]
|
provider = nil
|
||||||
|
|
||||||
|
Linzer.verify!(request.rack_request, no_older_than: 5.minutes) do |keyid|
|
||||||
provider = Fasp::Provider.find(keyid)
|
provider = Fasp::Provider.find(keyid)
|
||||||
linzer_request = Linzer.new_request(
|
Linzer.new_ed25519_public_key(provider.provider_public_key_pem, keyid)
|
||||||
request.method,
|
end
|
||||||
request.original_url,
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
'content-digest' => request.headers['content-digest'],
|
|
||||||
'signature-input' => signature_input,
|
|
||||||
'signature' => request.headers['signature'],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
message = Linzer::Message.new(linzer_request)
|
|
||||||
key = Linzer.new_ed25519_public_key(provider.provider_public_key_pem, keyid)
|
|
||||||
signature = Linzer::Signature.build(message.headers)
|
|
||||||
Linzer.verify(key, message, signature)
|
|
||||||
@current_provider = provider
|
@current_provider = provider
|
||||||
end
|
end
|
||||||
|
|
||||||
def sign_response
|
def sign_response
|
||||||
response.headers['content-digest'] = "sha-256=:#{OpenSSL::Digest.base64digest('sha256', response.body || '')}:"
|
response.headers['content-digest'] = "sha-256=:#{OpenSSL::Digest.base64digest('sha256', response.body || '')}:"
|
||||||
|
|
||||||
linzer_response = Linzer.new_response(response.body, response.status, { 'content-digest' => response.headers['content-digest'] })
|
|
||||||
message = Linzer::Message.new(linzer_response)
|
|
||||||
key = Linzer.new_ed25519_key(current_provider.server_private_key_pem)
|
key = Linzer.new_ed25519_key(current_provider.server_private_key_pem)
|
||||||
signature = Linzer.sign(key, message, %w(@status content-digest))
|
Linzer.sign!(response, key:, components: %w(@status content-digest))
|
||||||
|
|
||||||
response.headers.merge!(signature.to_h)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_fasp_enabled
|
def check_fasp_enabled
|
||||||
|
|||||||
@ -18,6 +18,6 @@ class Api::V1::Instances::RulesController < Api::V1::Instances::BaseController
|
|||||||
private
|
private
|
||||||
|
|
||||||
def set_rules
|
def set_rules
|
||||||
@rules = Rule.ordered
|
@rules = Rule.ordered.includes(:translations)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -126,7 +126,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def set_rules
|
def set_rules
|
||||||
@rules = Rule.ordered
|
@rules = Rule.ordered.includes(:translations)
|
||||||
end
|
end
|
||||||
|
|
||||||
def require_rules_acceptance!
|
def require_rules_acceptance!
|
||||||
|
|||||||
@ -177,9 +177,7 @@ class Auth::SessionsController < Devise::SessionsController
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Only send a notification email every hour at most
|
# Only send a notification email every hour at most
|
||||||
return if redis.get("2fa_failure_notification:#{user.id}").present?
|
return if redis.set("2fa_failure_notification:#{user.id}", '1', ex: 1.hour, get: true).present?
|
||||||
|
|
||||||
redis.set("2fa_failure_notification:#{user.id}", '1', ex: 1.hour)
|
|
||||||
|
|
||||||
UserMailer.failed_2fa(user, request.remote_ip, request.user_agent, Time.now.utc).deliver_later!
|
UserMailer.failed_2fa(user, request.remote_ip, request.user_agent, Time.now.utc).deliver_later!
|
||||||
end
|
end
|
||||||
|
|||||||
@ -12,7 +12,7 @@ module ThemingConcern
|
|||||||
def current_skin
|
def current_skin
|
||||||
@current_skin ||= begin
|
@current_skin ||= begin
|
||||||
skins = Themes.instance.skins_for(current_flavour)
|
skins = Themes.instance.skins_for(current_flavour)
|
||||||
[current_user&.setting_skin, Setting.skin, 'system', 'default'].find { |skin| skins.include?(skin) }
|
[current_user&.setting_skin, Setting.skin, 'system', 'application'].find { |skin| skins.include?(skin) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@ module WebAppControllerConcern
|
|||||||
|
|
||||||
before_action :redirect_unauthenticated_to_permalinks!
|
before_action :redirect_unauthenticated_to_permalinks!
|
||||||
before_action :set_referer_header
|
before_action :set_referer_header
|
||||||
|
before_action :redirect_to_tos_interstitial!
|
||||||
|
|
||||||
content_security_policy do |p|
|
content_security_policy do |p|
|
||||||
policy = ContentSecurityPolicy.new
|
policy = ContentSecurityPolicy.new
|
||||||
@ -45,6 +46,13 @@ module WebAppControllerConcern
|
|||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
|
def redirect_to_tos_interstitial!
|
||||||
|
return unless current_user&.require_tos_interstitial?
|
||||||
|
|
||||||
|
@terms_of_service = TermsOfService.published.first
|
||||||
|
render 'terms_of_service_interstitial/show', layout: 'auth'
|
||||||
|
end
|
||||||
|
|
||||||
def set_referer_header
|
def set_referer_header
|
||||||
response.set_header('Referrer-Policy', Setting.allow_referrer_origin ? 'strict-origin-when-cross-origin' : 'same-origin')
|
response.set_header('Referrer-Policy', Setting.allow_referrer_origin ? 'strict-origin-when-cross-origin' : 'same-origin')
|
||||||
end
|
end
|
||||||
|
|||||||
@ -4,8 +4,19 @@ class TermsOfServiceController < ApplicationController
|
|||||||
include WebAppControllerConcern
|
include WebAppControllerConcern
|
||||||
|
|
||||||
skip_before_action :require_functional!
|
skip_before_action :require_functional!
|
||||||
|
skip_before_action :redirect_to_tos_interstitial!
|
||||||
|
|
||||||
|
before_action :clear_redirect_interstitial!
|
||||||
|
|
||||||
def show
|
def show
|
||||||
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless user_signed_in?
|
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless user_signed_in?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def clear_redirect_interstitial!
|
||||||
|
return unless user_signed_in?
|
||||||
|
|
||||||
|
current_user.update(require_tos_interstitial: false)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -248,22 +248,8 @@ module ApplicationHelper
|
|||||||
end
|
end
|
||||||
|
|
||||||
# glitch-soc addition to handle the multiple flavors
|
# glitch-soc addition to handle the multiple flavors
|
||||||
def preload_locale_pack
|
def flavoured_vite_typescript_tag(pack_name, **)
|
||||||
supported_locales = Themes.instance.flavour(current_flavour)['locales']
|
vite_typescript_tag("#{Themes.instance.flavour(current_flavour)['pack_directory'].delete_prefix('app/javascript/')}/#{pack_name}", **)
|
||||||
preload_pack_asset "locales/#{current_flavour}/#{I18n.locale}-json.js" if supported_locales.include?(I18n.locale.to_s)
|
|
||||||
end
|
|
||||||
|
|
||||||
def flavoured_javascript_pack_tag(pack_name, **)
|
|
||||||
javascript_pack_tag("flavours/#{current_flavour}/#{pack_name}", **)
|
|
||||||
end
|
|
||||||
|
|
||||||
def flavoured_stylesheet_pack_tag(pack_name, **)
|
|
||||||
stylesheet_pack_tag("flavours/#{current_flavour}/#{pack_name}", **)
|
|
||||||
end
|
|
||||||
|
|
||||||
def preload_signed_in_js_packs
|
|
||||||
preload_files = Themes.instance.flavour(current_flavour)&.fetch('signed_in_preload', nil) || []
|
|
||||||
safe_join(preload_files.map { |entry| preload_pack_asset entry })
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@ -4,7 +4,7 @@ module RoutingHelper
|
|||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
include ActionView::Helpers::AssetTagHelper
|
include ActionView::Helpers::AssetTagHelper
|
||||||
include Webpacker::Helper
|
include ViteRails::TagHelpers
|
||||||
|
|
||||||
included do
|
included do
|
||||||
include Rails.application.routes.url_helpers
|
include Rails.application.routes.url_helpers
|
||||||
@ -25,7 +25,7 @@ module RoutingHelper
|
|||||||
end
|
end
|
||||||
|
|
||||||
def frontend_asset_path(source, **)
|
def frontend_asset_path(source, **)
|
||||||
asset_pack_path("media/#{source}", **)
|
vite_asset_path(source, **)
|
||||||
end
|
end
|
||||||
|
|
||||||
def frontend_asset_url(source, **)
|
def frontend_asset_url(source, **)
|
||||||
|
|||||||
@ -6,11 +6,13 @@ module ThemeHelper
|
|||||||
|
|
||||||
if theme == 'system'
|
if theme == 'system'
|
||||||
''.html_safe.tap do |tags|
|
''.html_safe.tap do |tags|
|
||||||
tags << stylesheet_pack_tag("skins/#{flavour}/mastodon-light", media: 'not all and (prefers-color-scheme: dark)', crossorigin: 'anonymous')
|
tags << vite_stylesheet_tag("skins/#{flavour}/mastodon-light.scss", media: 'not all and (prefers-color-scheme: dark)', crossorigin: 'anonymous')
|
||||||
tags << stylesheet_pack_tag("skins/#{flavour}/default", media: '(prefers-color-scheme: dark)', crossorigin: 'anonymous')
|
tags << vite_stylesheet_tag("skins/#{flavour}/application.scss", media: '(prefers-color-scheme: dark)', crossorigin: 'anonymous')
|
||||||
end
|
end
|
||||||
|
elsif theme == 'default'
|
||||||
|
vite_stylesheet_tag "skins/#{flavour}/application.scss", media: 'all', crossorigin: 'anonymous'
|
||||||
else
|
else
|
||||||
stylesheet_pack_tag "skins/#{flavour}/#{theme}", media: 'all', crossorigin: 'anonymous'
|
vite_stylesheet_tag "skins/#{flavour}/#{theme}.scss", media: 'all', crossorigin: 'anonymous'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import './public-path';
|
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
import Rails from '@rails/ujs';
|
import Rails from '@rails/ujs';
|
||||||
@ -273,7 +272,7 @@ async function mountReactComponent(element: Element) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { default: Component } = (await import(
|
const { default: Component } = (await import(
|
||||||
`@/mastodon/components/admin/${componentName}`
|
`@/mastodon/components/admin/${componentName}.jsx`
|
||||||
)) as { default: React.ComponentType };
|
)) as { default: React.ComponentType };
|
||||||
|
|
||||||
const root = createRoot(element);
|
const root = createRoot(element);
|
||||||
|
|||||||
@ -1,11 +1,6 @@
|
|||||||
import './public-path';
|
import { loadLocale } from 'mastodon/locales';
|
||||||
import main from 'mastodon/main';
|
import main from 'mastodon/main';
|
||||||
|
import { loadPolyfills } from 'mastodon/polyfills';
|
||||||
import { start } from '../mastodon/common';
|
|
||||||
import { loadLocale } from '../mastodon/locales';
|
|
||||||
import { loadPolyfills } from '../mastodon/polyfills';
|
|
||||||
|
|
||||||
start();
|
|
||||||
|
|
||||||
loadPolyfills()
|
loadPolyfills()
|
||||||
.then(loadLocale)
|
.then(loadLocale)
|
||||||
|
|||||||
@ -1,4 +0,0 @@
|
|||||||
/* This file is a hack to have something more reliable than the upstream `common` tag
|
|
||||||
that is implicitly generated as the common chunk through webpack's `splitChunks` config */
|
|
||||||
|
|
||||||
import './public-path';
|
|
||||||
3
app/javascript/entrypoints/common.ts
Normal file
3
app/javascript/entrypoints/common.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { start } from 'mastodon/common';
|
||||||
|
|
||||||
|
start();
|
||||||
@ -1,15 +1,11 @@
|
|||||||
import './public-path';
|
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
import { afterInitialRender } from 'mastodon/hooks/useRenderSignal';
|
import { afterInitialRender } from 'mastodon/hooks/useRenderSignal';
|
||||||
|
|
||||||
import { start } from '../mastodon/common';
|
|
||||||
import { Status } from '../mastodon/features/standalone/status';
|
import { Status } from '../mastodon/features/standalone/status';
|
||||||
import { loadPolyfills } from '../mastodon/polyfills';
|
import { loadPolyfills } from '../mastodon/polyfills';
|
||||||
import ready from '../mastodon/ready';
|
import ready from '../mastodon/ready';
|
||||||
|
|
||||||
start();
|
|
||||||
|
|
||||||
function loaded() {
|
function loaded() {
|
||||||
const mountNode = document.getElementById('mastodon-status');
|
const mountNode = document.getElementById('mastodon-status');
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import './public-path';
|
|
||||||
import ready from '../mastodon/ready';
|
import ready from '../mastodon/ready';
|
||||||
|
|
||||||
ready(() => {
|
ready(() => {
|
||||||
|
|||||||
@ -1,4 +0,0 @@
|
|||||||
/* Placeholder file to have `inert.scss` compiled by Webpack
|
|
||||||
This is used by the `wicg-inert` polyfill */
|
|
||||||
|
|
||||||
import '../styles/inert.scss';
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
import '../styles/mailer.scss';
|
|
||||||
|
|
||||||
require.context('../icons');
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
// Dynamically set webpack's loading path depending on a meta header, in order
|
|
||||||
// to share the same assets regardless of instance configuration.
|
|
||||||
// See https://webpack.js.org/guides/public-path/#on-the-fly
|
|
||||||
|
|
||||||
function removeOuterSlashes(string: string) {
|
|
||||||
return string.replace(/^\/*/, '').replace(/\/*$/, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatPublicPath(host = '', path = '') {
|
|
||||||
let formattedHost = removeOuterSlashes(host);
|
|
||||||
if (formattedHost && !/^http/i.test(formattedHost)) {
|
|
||||||
formattedHost = `//${formattedHost}`;
|
|
||||||
}
|
|
||||||
const formattedPath = removeOuterSlashes(path);
|
|
||||||
return `${formattedHost}/${formattedPath}/`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cdnHost = document.querySelector<HTMLMetaElement>('meta[name=cdn-host]');
|
|
||||||
|
|
||||||
__webpack_public_path__ = formatPublicPath(
|
|
||||||
cdnHost ? cdnHost.content : '',
|
|
||||||
process.env.PUBLIC_OUTPUT_PATH,
|
|
||||||
);
|
|
||||||
@ -1,7 +1,5 @@
|
|||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
import './public-path';
|
|
||||||
|
|
||||||
import { IntlMessageFormat } from 'intl-messageformat';
|
import { IntlMessageFormat } from 'intl-messageformat';
|
||||||
import type { MessageDescriptor, PrimitiveType } from 'react-intl';
|
import type { MessageDescriptor, PrimitiveType } from 'react-intl';
|
||||||
import { defineMessages } from 'react-intl';
|
import { defineMessages } from 'react-intl';
|
||||||
@ -10,7 +8,6 @@ import Rails from '@rails/ujs';
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { throttle } from 'lodash';
|
import { throttle } from 'lodash';
|
||||||
|
|
||||||
import { start } from '../mastodon/common';
|
|
||||||
import { timeAgoString } from '../mastodon/components/relative_timestamp';
|
import { timeAgoString } from '../mastodon/components/relative_timestamp';
|
||||||
import emojify from '../mastodon/features/emoji/emoji';
|
import emojify from '../mastodon/features/emoji/emoji';
|
||||||
import loadKeyboardExtensions from '../mastodon/load_keyboard_extensions';
|
import loadKeyboardExtensions from '../mastodon/load_keyboard_extensions';
|
||||||
@ -20,8 +17,6 @@ import ready from '../mastodon/ready';
|
|||||||
|
|
||||||
import 'cocoon-js-vanilla';
|
import 'cocoon-js-vanilla';
|
||||||
|
|
||||||
start();
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
usernameTaken: {
|
usernameTaken: {
|
||||||
id: 'username.taken',
|
id: 'username.taken',
|
||||||
@ -153,9 +148,7 @@ function loaded() {
|
|||||||
const reactComponents = document.querySelectorAll('[data-component]');
|
const reactComponents = document.querySelectorAll('[data-component]');
|
||||||
|
|
||||||
if (reactComponents.length > 0) {
|
if (reactComponents.length > 0) {
|
||||||
import(
|
import('../mastodon/containers/media_container')
|
||||||
/* webpackChunkName: "containers/media_container" */ '../mastodon/containers/media_container'
|
|
||||||
)
|
|
||||||
.then(({ default: MediaContainer }) => {
|
.then(({ default: MediaContainer }) => {
|
||||||
reactComponents.forEach((component) => {
|
reactComponents.forEach((component) => {
|
||||||
Array.from(component.children).forEach((child) => {
|
Array.from(component.children).forEach((child) => {
|
||||||
|
|||||||
@ -8,8 +8,6 @@ and performs no other task.
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import './public-path';
|
|
||||||
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
interface JRDLink {
|
interface JRDLink {
|
||||||
|
|||||||
@ -1,13 +1,9 @@
|
|||||||
import './public-path';
|
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
import { start } from '../mastodon/common';
|
|
||||||
import ComposeContainer from '../mastodon/containers/compose_container';
|
import ComposeContainer from '../mastodon/containers/compose_container';
|
||||||
import { loadPolyfills } from '../mastodon/polyfills';
|
import { loadPolyfills } from '../mastodon/polyfills';
|
||||||
import ready from '../mastodon/ready';
|
import ready from '../mastodon/ready';
|
||||||
|
|
||||||
start();
|
|
||||||
|
|
||||||
function loaded() {
|
function loaded() {
|
||||||
const mountNode = document.getElementById('mastodon-compose');
|
const mountNode = document.getElementById('mastodon-compose');
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import './public-path';
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
import ready from '../mastodon/ready';
|
import ready from '../mastodon/ready';
|
||||||
|
|||||||
@ -69,6 +69,10 @@ export function importFetchedStatuses(statuses) {
|
|||||||
processStatus(status.reblog);
|
processStatus(status.reblog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (status.quote?.quoted_status) {
|
||||||
|
processStatus(status.quote.quoted_status);
|
||||||
|
}
|
||||||
|
|
||||||
if (status.poll?.id) {
|
if (status.poll?.id) {
|
||||||
pushUnique(polls, createPollFromServerJSON(status.poll, getState().polls[status.poll.id]));
|
pushUnique(polls, createPollFromServerJSON(status.poll, getState().polls[status.poll.id]));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,12 +23,20 @@ export function normalizeFilterResult(result) {
|
|||||||
|
|
||||||
export function normalizeStatus(status, normalOldStatus, settings) {
|
export function normalizeStatus(status, normalOldStatus, settings) {
|
||||||
const normalStatus = { ...status };
|
const normalStatus = { ...status };
|
||||||
|
|
||||||
normalStatus.account = status.account.id;
|
normalStatus.account = status.account.id;
|
||||||
|
|
||||||
if (status.reblog && status.reblog.id) {
|
if (status.reblog && status.reblog.id) {
|
||||||
normalStatus.reblog = status.reblog.id;
|
normalStatus.reblog = status.reblog.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (status.quote?.quoted_status ?? status.quote?.quoted_status_id) {
|
||||||
|
normalStatus.quote = {
|
||||||
|
...status.quote,
|
||||||
|
quoted_status: status.quote.quoted_status?.id ?? status.quote?.quoted_status_id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (status.poll && status.poll.id) {
|
if (status.poll && status.poll.id) {
|
||||||
normalStatus.poll = status.poll.id;
|
normalStatus.poll = status.poll.id;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
import Rails from '@rails/ujs';
|
import Rails from '@rails/ujs';
|
||||||
|
|
||||||
export function start() {
|
export function start() {
|
||||||
require.context('@/images/', true, /\.(jpg|png|svg)$/);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Rails.start();
|
Rails.start();
|
||||||
} catch {
|
} catch {
|
||||||
@ -10,9 +10,12 @@ import {
|
|||||||
unblockAccount,
|
unblockAccount,
|
||||||
muteAccount,
|
muteAccount,
|
||||||
unmuteAccount,
|
unmuteAccount,
|
||||||
|
followAccountSuccess,
|
||||||
} from 'flavours/glitch/actions/accounts';
|
} from 'flavours/glitch/actions/accounts';
|
||||||
|
import { showAlertForError } from 'flavours/glitch/actions/alerts';
|
||||||
import { openModal } from 'flavours/glitch/actions/modal';
|
import { openModal } from 'flavours/glitch/actions/modal';
|
||||||
import { initMuteModal } from 'flavours/glitch/actions/mutes';
|
import { initMuteModal } from 'flavours/glitch/actions/mutes';
|
||||||
|
import { apiFollowAccount } from 'flavours/glitch/api/accounts';
|
||||||
import { Avatar } from 'flavours/glitch/components/avatar';
|
import { Avatar } from 'flavours/glitch/components/avatar';
|
||||||
import { Button } from 'flavours/glitch/components/button';
|
import { Button } from 'flavours/glitch/components/button';
|
||||||
import { FollowersCounter } from 'flavours/glitch/components/counters';
|
import { FollowersCounter } from 'flavours/glitch/components/counters';
|
||||||
@ -23,6 +26,8 @@ import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp
|
|||||||
import { ShortNumber } from 'flavours/glitch/components/short_number';
|
import { ShortNumber } from 'flavours/glitch/components/short_number';
|
||||||
import { Skeleton } from 'flavours/glitch/components/skeleton';
|
import { Skeleton } from 'flavours/glitch/components/skeleton';
|
||||||
import { VerifiedBadge } from 'flavours/glitch/components/verified_badge';
|
import { VerifiedBadge } from 'flavours/glitch/components/verified_badge';
|
||||||
|
import { useIdentity } from 'flavours/glitch/identity_context';
|
||||||
|
import { me } from 'flavours/glitch/initial_state';
|
||||||
import type { MenuItem } from 'flavours/glitch/models/dropdown_menu';
|
import type { MenuItem } from 'flavours/glitch/models/dropdown_menu';
|
||||||
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
|
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
|
||||||
|
|
||||||
@ -67,10 +72,12 @@ export const Account: React.FC<{
|
|||||||
withBio?: boolean;
|
withBio?: boolean;
|
||||||
}> = ({ id, size = 46, hidden, minimal, defaultAction, withBio }) => {
|
}> = ({ id, size = 46, hidden, minimal, defaultAction, withBio }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
const { signedIn } = useIdentity();
|
||||||
const account = useAppSelector((state) => state.accounts.get(id));
|
const account = useAppSelector((state) => state.accounts.get(id));
|
||||||
const relationship = useAppSelector((state) => state.relationships.get(id));
|
const relationship = useAppSelector((state) => state.relationships.get(id));
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const accountUrl = account?.url;
|
const accountUrl = account?.url;
|
||||||
|
const isRemote = account?.acct !== account?.username;
|
||||||
|
|
||||||
const handleBlock = useCallback(() => {
|
const handleBlock = useCallback(() => {
|
||||||
if (relationship?.blocking) {
|
if (relationship?.blocking) {
|
||||||
@ -113,7 +120,18 @@ export const Account: React.FC<{
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
} else if (defaultAction !== 'block') {
|
} else if (defaultAction !== 'block') {
|
||||||
|
arr = [];
|
||||||
|
|
||||||
|
if (isRemote && accountUrl) {
|
||||||
|
arr.push({
|
||||||
|
text: intl.formatMessage(messages.openOriginalPage),
|
||||||
|
href: accountUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signedIn) {
|
||||||
const handleAddToLists = () => {
|
const handleAddToLists = () => {
|
||||||
|
const openAddToListModal = () => {
|
||||||
dispatch(
|
dispatch(
|
||||||
openModal({
|
openModal({
|
||||||
modalType: 'LIST_ADDER',
|
modalType: 'LIST_ADDER',
|
||||||
@ -123,27 +141,53 @@ export const Account: React.FC<{
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
if (relationship?.following || relationship?.requested || id === me) {
|
||||||
|
openAddToListModal();
|
||||||
|
} else {
|
||||||
|
dispatch(
|
||||||
|
openModal({
|
||||||
|
modalType: 'CONFIRM_FOLLOW_TO_LIST',
|
||||||
|
modalProps: {
|
||||||
|
accountId: id,
|
||||||
|
onConfirm: () => {
|
||||||
|
apiFollowAccount(id)
|
||||||
|
.then((relationship) => {
|
||||||
|
dispatch(
|
||||||
|
followAccountSuccess({
|
||||||
|
relationship,
|
||||||
|
alreadyFollowing: false,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
openAddToListModal();
|
||||||
|
})
|
||||||
|
.catch((err: unknown) => {
|
||||||
|
dispatch(showAlertForError(err));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
arr = [
|
arr.push({
|
||||||
{
|
|
||||||
text: intl.formatMessage(messages.addToLists),
|
text: intl.formatMessage(messages.addToLists),
|
||||||
action: handleAddToLists,
|
action: handleAddToLists,
|
||||||
},
|
});
|
||||||
];
|
|
||||||
|
|
||||||
if (accountUrl) {
|
|
||||||
arr.unshift(
|
|
||||||
{
|
|
||||||
text: intl.formatMessage(messages.openOriginalPage),
|
|
||||||
href: accountUrl,
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return arr;
|
return arr;
|
||||||
}, [dispatch, intl, id, accountUrl, relationship, defaultAction]);
|
}, [
|
||||||
|
dispatch,
|
||||||
|
intl,
|
||||||
|
id,
|
||||||
|
accountUrl,
|
||||||
|
relationship,
|
||||||
|
defaultAction,
|
||||||
|
isRemote,
|
||||||
|
signedIn,
|
||||||
|
]);
|
||||||
|
|
||||||
if (hidden) {
|
if (hidden) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -7,10 +7,16 @@ import classNames from 'classnames';
|
|||||||
|
|
||||||
export const AvatarGroup: React.FC<{
|
export const AvatarGroup: React.FC<{
|
||||||
compact?: boolean;
|
compact?: boolean;
|
||||||
|
avatarHeight?: number;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}> = ({ children, compact = false }) => (
|
}> = ({ children, compact = false, avatarHeight }) => (
|
||||||
<div
|
<div
|
||||||
className={classNames('avatar-group', { 'avatar-group--compact': compact })}
|
className={classNames('avatar-group', { 'avatar-group--compact': compact })}
|
||||||
|
style={
|
||||||
|
avatarHeight
|
||||||
|
? ({ '--avatar-height': `${avatarHeight}px` } as React.CSSProperties)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -43,3 +43,17 @@ export const FollowersCounter = (
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const FollowersYouKnowCounter = (
|
||||||
|
displayNumber: React.ReactNode,
|
||||||
|
pluralReady: number,
|
||||||
|
) => (
|
||||||
|
<FormattedMessage
|
||||||
|
id='account.followers_you_know_counter'
|
||||||
|
defaultMessage='{counter} you know'
|
||||||
|
values={{
|
||||||
|
count: pluralReady,
|
||||||
|
counter: <strong>{displayNumber}</strong>,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|||||||
@ -8,12 +8,17 @@ import { fetchAccount } from 'flavours/glitch/actions/accounts';
|
|||||||
import { AccountBio } from 'flavours/glitch/components/account_bio';
|
import { AccountBio } from 'flavours/glitch/components/account_bio';
|
||||||
import { AccountFields } from 'flavours/glitch/components/account_fields';
|
import { AccountFields } from 'flavours/glitch/components/account_fields';
|
||||||
import { Avatar } from 'flavours/glitch/components/avatar';
|
import { Avatar } from 'flavours/glitch/components/avatar';
|
||||||
import { FollowersCounter } from 'flavours/glitch/components/counters';
|
import { AvatarGroup } from 'flavours/glitch/components/avatar_group';
|
||||||
|
import {
|
||||||
|
FollowersCounter,
|
||||||
|
FollowersYouKnowCounter,
|
||||||
|
} from 'flavours/glitch/components/counters';
|
||||||
import { DisplayName } from 'flavours/glitch/components/display_name';
|
import { DisplayName } from 'flavours/glitch/components/display_name';
|
||||||
import { FollowButton } from 'flavours/glitch/components/follow_button';
|
import { FollowButton } from 'flavours/glitch/components/follow_button';
|
||||||
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
||||||
import { Permalink } from 'flavours/glitch/components/permalink';
|
import { Permalink } from 'flavours/glitch/components/permalink';
|
||||||
import { ShortNumber } from 'flavours/glitch/components/short_number';
|
import { ShortNumber } from 'flavours/glitch/components/short_number';
|
||||||
|
import { useFetchFamiliarFollowers } from 'flavours/glitch/features/account_timeline/hooks/familiar_followers';
|
||||||
import { domain } from 'flavours/glitch/initial_state';
|
import { domain } from 'flavours/glitch/initial_state';
|
||||||
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
|
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
|
||||||
|
|
||||||
@ -38,6 +43,8 @@ export const HoverCardAccount = forwardRef<
|
|||||||
}
|
}
|
||||||
}, [dispatch, accountId, account]);
|
}, [dispatch, accountId, account]);
|
||||||
|
|
||||||
|
const { familiarFollowers } = useFetchFamiliarFollowers({ accountId });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@ -77,11 +84,27 @@ export const HoverCardAccount = forwardRef<
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='hover-card__number'>
|
<div className='hover-card__numbers'>
|
||||||
<ShortNumber
|
<ShortNumber
|
||||||
value={account.followers_count}
|
value={account.followers_count}
|
||||||
renderer={FollowersCounter}
|
renderer={FollowersCounter}
|
||||||
/>
|
/>
|
||||||
|
{familiarFollowers.length > 0 && (
|
||||||
|
<>
|
||||||
|
·
|
||||||
|
<div className='hover-card__familiar-followers'>
|
||||||
|
<ShortNumber
|
||||||
|
value={familiarFollowers.length}
|
||||||
|
renderer={FollowersYouKnowCounter}
|
||||||
|
/>
|
||||||
|
<AvatarGroup compact>
|
||||||
|
{familiarFollowers.slice(0, 3).map((account) => (
|
||||||
|
<Avatar key={account.id} account={account} size={22} />
|
||||||
|
))}
|
||||||
|
</AvatarGroup>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FollowButton accountId={accountId} />
|
<FollowButton accountId={accountId} />
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
|
|
||||||
import { IconLogo } from 'flavours/glitch/components/logo';
|
|
||||||
import { AuthorLink } from 'flavours/glitch/features/explore/components/author_link';
|
|
||||||
|
|
||||||
export const MoreFromAuthor = ({ accountId }) => (
|
|
||||||
<div className='more-from-author'>
|
|
||||||
<IconLogo />
|
|
||||||
<FormattedMessage id='link_preview.more_from_author' defaultMessage='More from {name}' values={{ name: <AuthorLink accountId={accountId} /> }} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
MoreFromAuthor.propTypes = {
|
|
||||||
accountId: PropTypes.string.isRequired,
|
|
||||||
};
|
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import { IconLogo } from 'flavours/glitch/components/logo';
|
||||||
|
import { AuthorLink } from 'flavours/glitch/features/explore/components/author_link';
|
||||||
|
|
||||||
|
export const MoreFromAuthor: React.FC<{ accountId: string }> = ({
|
||||||
|
accountId,
|
||||||
|
}) => (
|
||||||
|
<FormattedMessage
|
||||||
|
id='link_preview.more_from_author'
|
||||||
|
defaultMessage='More from {name}'
|
||||||
|
values={{ name: <AuthorLink accountId={accountId} /> }}
|
||||||
|
>
|
||||||
|
{(chunks) => (
|
||||||
|
<div className='more-from-author'>
|
||||||
|
<IconLogo />
|
||||||
|
{chunks}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</FormattedMessage>
|
||||||
|
);
|
||||||
@ -82,6 +82,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
status: ImmutablePropTypes.map,
|
status: ImmutablePropTypes.map,
|
||||||
account: ImmutablePropTypes.record,
|
account: ImmutablePropTypes.record,
|
||||||
|
children: PropTypes.node,
|
||||||
previousId: PropTypes.string,
|
previousId: PropTypes.string,
|
||||||
nextInReplyToId: PropTypes.string,
|
nextInReplyToId: PropTypes.string,
|
||||||
rootId: PropTypes.string,
|
rootId: PropTypes.string,
|
||||||
@ -111,6 +112,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
withDismiss: PropTypes.bool,
|
withDismiss: PropTypes.bool,
|
||||||
onMoveUp: PropTypes.func,
|
onMoveUp: PropTypes.func,
|
||||||
onMoveDown: PropTypes.func,
|
onMoveDown: PropTypes.func,
|
||||||
|
isQuotedPost: PropTypes.bool,
|
||||||
getScrollPosition: PropTypes.func,
|
getScrollPosition: PropTypes.func,
|
||||||
updateScrollBottom: PropTypes.func,
|
updateScrollBottom: PropTypes.func,
|
||||||
expanded: PropTypes.bool,
|
expanded: PropTypes.bool,
|
||||||
@ -440,7 +442,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, hidden, featured, unfocusable, unread, pictureInPicture, previousId, nextInReplyToId, rootId, skipPrepend, avatarSize = 46 } = this.props;
|
const { intl, hidden, featured, unfocusable, unread, pictureInPicture, previousId, nextInReplyToId, rootId, skipPrepend, avatarSize = 46, children } = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
status,
|
status,
|
||||||
@ -452,6 +454,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
onOpenMedia,
|
onOpenMedia,
|
||||||
notification,
|
notification,
|
||||||
history,
|
history,
|
||||||
|
isQuotedPost,
|
||||||
...other
|
...other
|
||||||
} = this.props;
|
} = this.props;
|
||||||
let attachments = null;
|
let attachments = null;
|
||||||
@ -618,7 +621,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
);
|
);
|
||||||
mediaIcons.push('video-camera');
|
mediaIcons.push('video-camera');
|
||||||
}
|
}
|
||||||
} else if (status.get('card') && settings.get('inline_preview_cards') && !this.props.muted) {
|
} else if (status.get('card') && settings.get('inline_preview_cards') && !this.props.muted && !status.get('quote')) {
|
||||||
media.push(
|
media.push(
|
||||||
<Card
|
<Card
|
||||||
onOpenMedia={this.handleOpenMedia}
|
onOpenMedia={this.handleOpenMedia}
|
||||||
@ -684,7 +687,17 @@ class Status extends ImmutablePureComponent {
|
|||||||
{!skipPrepend && prepend}
|
{!skipPrepend && prepend}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), 'status--in-thread': !!rootId, 'status--first-in-thread': previousId && (!connectUp || connectToRoot), muted: this.props.muted })}
|
className={
|
||||||
|
classNames('status', `status-${status.get('visibility')}`,
|
||||||
|
{
|
||||||
|
'status-reply': !!status.get('in_reply_to_id'),
|
||||||
|
'status--in-thread': !!rootId,
|
||||||
|
'status--first-in-thread': previousId && (!connectUp || connectToRoot),
|
||||||
|
muted: this.props.muted,
|
||||||
|
'status--is-quote': isQuotedPost,
|
||||||
|
'status--has-quote': !!status.get('quote'),
|
||||||
|
})
|
||||||
|
}
|
||||||
data-id={status.get('id')}
|
data-id={status.get('id')}
|
||||||
>
|
>
|
||||||
{(connectReply || connectUp || connectToRoot) && <div className={classNames('status__line', { 'status__line--full': connectReply, 'status__line--first': !status.get('in_reply_to_id') && !connectToRoot })} />}
|
{(connectReply || connectUp || connectToRoot) && <div className={classNames('status__line', { 'status__line--full': connectReply, 'status__line--first': !status.get('in_reply_to_id') && !connectToRoot })} />}
|
||||||
@ -722,6 +735,8 @@ class Status extends ImmutablePureComponent {
|
|||||||
{...statusContentProps}
|
{...statusContentProps}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{children}
|
||||||
|
|
||||||
{media}
|
{media}
|
||||||
{hashtagBar}
|
{hashtagBar}
|
||||||
</>
|
</>
|
||||||
@ -730,6 +745,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
{/* This is a glitch-soc addition to have a placeholder */}
|
{/* This is a glitch-soc addition to have a placeholder */}
|
||||||
{!expanded && <MentionsPlaceholder status={status} />}
|
{!expanded && <MentionsPlaceholder status={status} />}
|
||||||
|
|
||||||
|
{!isQuotedPost &&
|
||||||
<StatusActionBar
|
<StatusActionBar
|
||||||
status={status}
|
status={status}
|
||||||
account={status.get('account')}
|
account={status.get('account')}
|
||||||
@ -737,6 +753,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
onFilter={matchedFilters ? this.handleFilterClick : null}
|
onFilter={matchedFilters ? this.handleFilterClick : null}
|
||||||
{...other}
|
{...other}
|
||||||
/>
|
/>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</HotKeys>
|
</HotKeys>
|
||||||
|
|||||||
@ -18,8 +18,8 @@ import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
|
|||||||
import StarBorderIcon from '@/material-icons/400-24px/star.svg?react';
|
import StarBorderIcon from '@/material-icons/400-24px/star.svg?react';
|
||||||
import VisibilityIcon from '@/material-icons/400-24px/visibility.svg?react';
|
import VisibilityIcon from '@/material-icons/400-24px/visibility.svg?react';
|
||||||
import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react';
|
import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react';
|
||||||
import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg';
|
import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react';
|
||||||
import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg';
|
import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react';
|
||||||
import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react';
|
import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react';
|
||||||
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
|
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
|
||||||
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions';
|
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions';
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { TIMELINE_GAP, TIMELINE_SUGGESTIONS } from 'flavours/glitch/actions/time
|
|||||||
import { RegenerationIndicator } from 'flavours/glitch/components/regeneration_indicator';
|
import { RegenerationIndicator } from 'flavours/glitch/components/regeneration_indicator';
|
||||||
import { InlineFollowSuggestions } from 'flavours/glitch/features/home_timeline/components/inline_follow_suggestions';
|
import { InlineFollowSuggestions } from 'flavours/glitch/features/home_timeline/components/inline_follow_suggestions';
|
||||||
|
|
||||||
import StatusContainer from '../containers/status_container';
|
import { StatusQuoteManager } from '../components/status_quoted';
|
||||||
|
|
||||||
import { LoadGap } from './load_gap';
|
import { LoadGap } from './load_gap';
|
||||||
import ScrollableList from './scrollable_list';
|
import ScrollableList from './scrollable_list';
|
||||||
@ -114,7 +114,7 @@ export default class StatusList extends ImmutablePureComponent {
|
|||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<StatusContainer
|
<StatusQuoteManager
|
||||||
key={statusId}
|
key={statusId}
|
||||||
id={statusId}
|
id={statusId}
|
||||||
onMoveUp={this.handleMoveUp}
|
onMoveUp={this.handleMoveUp}
|
||||||
@ -130,7 +130,7 @@ export default class StatusList extends ImmutablePureComponent {
|
|||||||
|
|
||||||
if (scrollableContent && featuredStatusIds) {
|
if (scrollableContent && featuredStatusIds) {
|
||||||
scrollableContent = featuredStatusIds.map(statusId => (
|
scrollableContent = featuredStatusIds.map(statusId => (
|
||||||
<StatusContainer
|
<StatusQuoteManager
|
||||||
key={`f-${statusId}`}
|
key={`f-${statusId}`}
|
||||||
id={statusId}
|
id={statusId}
|
||||||
featured
|
featured
|
||||||
|
|||||||
185
app/javascript/flavours/glitch/components/status_quoted.tsx
Normal file
185
app/javascript/flavours/glitch/components/status_quoted.tsx
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
import type { Map as ImmutableMap } from 'immutable';
|
||||||
|
|
||||||
|
import QuoteIcon from '@/images/quote.svg?react';
|
||||||
|
import ArticleIcon from '@/material-icons/400-24px/article.svg?react';
|
||||||
|
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
|
||||||
|
import { Icon } from 'flavours/glitch/components/icon';
|
||||||
|
import StatusContainer from 'flavours/glitch/containers/status_container';
|
||||||
|
import type { Status } from 'flavours/glitch/models/status';
|
||||||
|
import { useAppSelector } from 'flavours/glitch/store';
|
||||||
|
|
||||||
|
const MAX_QUOTE_POSTS_NESTING_LEVEL = 1;
|
||||||
|
|
||||||
|
const QuoteWrapper: React.FC<{
|
||||||
|
isError?: boolean;
|
||||||
|
children: React.ReactElement;
|
||||||
|
}> = ({ isError, children }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames('status__quote', {
|
||||||
|
'status__quote--error': isError,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Icon id='quote' icon={QuoteIcon} className='status__quote-icon' />
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const QuoteLink: React.FC<{
|
||||||
|
status: Status;
|
||||||
|
}> = ({ status }) => {
|
||||||
|
const accountId = status.get('account') as string;
|
||||||
|
const account = useAppSelector((state) =>
|
||||||
|
accountId ? state.accounts.get(accountId) : undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
const quoteAuthorName = account?.display_name_html;
|
||||||
|
|
||||||
|
if (!quoteAuthorName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const quoteAuthorElement = (
|
||||||
|
<span dangerouslySetInnerHTML={{ __html: quoteAuthorName }} />
|
||||||
|
);
|
||||||
|
const quoteUrl = `/@${account.get('acct')}/${status.get('id') as string}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link to={quoteUrl} className='status__quote-author-button'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='status.quote_post_author'
|
||||||
|
defaultMessage='Post by {name}'
|
||||||
|
values={{ name: quoteAuthorElement }}
|
||||||
|
/>
|
||||||
|
<Icon id='chevron_right' icon={ChevronRightIcon} />
|
||||||
|
<Icon id='article' icon={ArticleIcon} />
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type QuoteMap = ImmutableMap<'state' | 'quoted_status', string | null>;
|
||||||
|
|
||||||
|
export const QuotedStatus: React.FC<{
|
||||||
|
quote: QuoteMap;
|
||||||
|
contextType?: string;
|
||||||
|
variant?: 'full' | 'link';
|
||||||
|
nestingLevel?: number;
|
||||||
|
}> = ({ quote, contextType, nestingLevel = 1, variant = 'full' }) => {
|
||||||
|
const quotedStatusId = quote.get('quoted_status');
|
||||||
|
const state = quote.get('state');
|
||||||
|
const status = useAppSelector((state) =>
|
||||||
|
quotedStatusId ? state.statuses.get(quotedStatusId) : undefined,
|
||||||
|
);
|
||||||
|
let quoteError: React.ReactNode = null;
|
||||||
|
|
||||||
|
if (state === 'deleted') {
|
||||||
|
quoteError = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='status.quote_error.removed'
|
||||||
|
defaultMessage='This post was removed by its author.'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (state === 'unauthorized') {
|
||||||
|
quoteError = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='status.quote_error.unauthorized'
|
||||||
|
defaultMessage='This post cannot be displayed as you are not authorized to view it.'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (state === 'pending') {
|
||||||
|
quoteError = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='status.quote_error.pending_approval'
|
||||||
|
defaultMessage='This post is pending approval from the original author.'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (state === 'rejected' || state === 'revoked') {
|
||||||
|
quoteError = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='status.quote_error.rejected'
|
||||||
|
defaultMessage='This post cannot be displayed as the original author does not allow it to be quoted.'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (!status || !quotedStatusId) {
|
||||||
|
quoteError = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='status.quote_error.not_found'
|
||||||
|
defaultMessage='This post cannot be displayed.'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quoteError) {
|
||||||
|
return <QuoteWrapper isError>{quoteError}</QuoteWrapper>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (variant === 'link' && status) {
|
||||||
|
return <QuoteLink status={status} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const childQuote = status?.get('quote') as QuoteMap | undefined;
|
||||||
|
const canRenderChildQuote =
|
||||||
|
childQuote && nestingLevel <= MAX_QUOTE_POSTS_NESTING_LEVEL;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<QuoteWrapper>
|
||||||
|
{/* @ts-expect-error Status is not yet typed */}
|
||||||
|
<StatusContainer
|
||||||
|
isQuotedPost
|
||||||
|
id={quotedStatusId}
|
||||||
|
contextType={contextType}
|
||||||
|
avatarSize={40}
|
||||||
|
>
|
||||||
|
{canRenderChildQuote && (
|
||||||
|
<QuotedStatus
|
||||||
|
quote={childQuote}
|
||||||
|
contextType={contextType}
|
||||||
|
variant={
|
||||||
|
nestingLevel === MAX_QUOTE_POSTS_NESTING_LEVEL ? 'link' : 'full'
|
||||||
|
}
|
||||||
|
nestingLevel={nestingLevel + 1}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</StatusContainer>
|
||||||
|
</QuoteWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface StatusQuoteManagerProps {
|
||||||
|
id: string;
|
||||||
|
contextType?: string;
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This wrapper component takes a status ID and, if the associated status
|
||||||
|
* is a quote post, it renders the quote into `StatusContainer` as a child.
|
||||||
|
* It passes all other props through to `StatusContainer`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const StatusQuoteManager = (props: StatusQuoteManagerProps) => {
|
||||||
|
const status = useAppSelector((state) => {
|
||||||
|
const status = state.statuses.get(props.id);
|
||||||
|
const reblogId = status?.get('reblog') as string | undefined;
|
||||||
|
return reblogId ? state.statuses.get(reblogId) : status;
|
||||||
|
});
|
||||||
|
const quote = status?.get('quote') as QuoteMap | undefined;
|
||||||
|
|
||||||
|
if (quote) {
|
||||||
|
return (
|
||||||
|
/* @ts-expect-error Status is not yet typed */
|
||||||
|
<StatusContainer {...props}>
|
||||||
|
<QuotedStatus quote={quote} contextType={props.contextType} />
|
||||||
|
</StatusContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* @ts-expect-error Status is not yet typed */
|
||||||
|
return <StatusContainer {...props} />;
|
||||||
|
};
|
||||||
@ -1,4 +1,3 @@
|
|||||||
import '@/entrypoints/public-path';
|
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
import Rails from '@rails/ujs';
|
import Rails from '@rails/ujs';
|
||||||
@ -273,7 +272,7 @@ async function mountReactComponent(element: Element) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const { default: Component } = (await import(
|
const { default: Component } = (await import(
|
||||||
`@/flavours/glitch/components/admin/${componentName}`
|
`@/flavours/glitch/components/admin/${componentName}.jsx`
|
||||||
)) as { default: React.ComponentType };
|
)) as { default: React.ComponentType };
|
||||||
|
|
||||||
const root = createRoot(element);
|
const root = createRoot(element);
|
||||||
|
|||||||
@ -1,12 +1,7 @@
|
|||||||
import '@/entrypoints/public-path';
|
|
||||||
|
|
||||||
import { start } from 'flavours/glitch/common';
|
|
||||||
import { loadLocale } from 'flavours/glitch/locales';
|
import { loadLocale } from 'flavours/glitch/locales';
|
||||||
import main from 'flavours/glitch/main';
|
import main from 'flavours/glitch/main';
|
||||||
import { loadPolyfills } from 'flavours/glitch/polyfills';
|
import { loadPolyfills } from 'flavours/glitch/polyfills';
|
||||||
|
|
||||||
start();
|
|
||||||
|
|
||||||
loadPolyfills()
|
loadPolyfills()
|
||||||
.then(loadLocale)
|
.then(loadLocale)
|
||||||
.then(main)
|
.then(main)
|
||||||
|
|||||||
@ -1,7 +0,0 @@
|
|||||||
/* This file is a hack to have something more reliable than the upstream `common` tag
|
|
||||||
that is implicitly generated as the common chunk through webpack's `splitChunks` config */
|
|
||||||
|
|
||||||
import '@/entrypoints/public-path';
|
|
||||||
|
|
||||||
// This is a hack to ensures that webpack compiles our images.
|
|
||||||
require.context('../images', true);
|
|
||||||
3
app/javascript/flavours/glitch/entrypoints/common.ts
Normal file
3
app/javascript/flavours/glitch/entrypoints/common.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { start } from 'flavours/glitch/common';
|
||||||
|
|
||||||
|
start();
|
||||||
@ -1,15 +1,10 @@
|
|||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
import '@/entrypoints/public-path';
|
|
||||||
|
|
||||||
import { start } from 'flavours/glitch/common';
|
|
||||||
import { Status } from 'flavours/glitch/features/standalone/status';
|
import { Status } from 'flavours/glitch/features/standalone/status';
|
||||||
import { afterInitialRender } from 'flavours/glitch/hooks/useRenderSignal';
|
import { afterInitialRender } from 'flavours/glitch/hooks/useRenderSignal';
|
||||||
import { loadPolyfills } from 'flavours/glitch/polyfills';
|
import { loadPolyfills } from 'flavours/glitch/polyfills';
|
||||||
import ready from 'flavours/glitch/ready';
|
import ready from 'flavours/glitch/ready';
|
||||||
|
|
||||||
start();
|
|
||||||
|
|
||||||
function loaded() {
|
function loaded() {
|
||||||
const mountNode = document.getElementById('mastodon-status');
|
const mountNode = document.getElementById('mastodon-status');
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import '@/entrypoints/public-path';
|
|
||||||
import ready from 'flavours/glitch/ready';
|
import ready from 'flavours/glitch/ready';
|
||||||
|
|
||||||
ready(() => {
|
ready(() => {
|
||||||
|
|||||||
@ -1,4 +0,0 @@
|
|||||||
/* Placeholder file to have `inert.scss` compiled by Webpack
|
|
||||||
This is used by the `wicg-inert` polyfill */
|
|
||||||
|
|
||||||
import '@/styles/inert.scss';
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
import '@/styles/mailer.scss';
|
|
||||||
|
|
||||||
require.context('@/icons');
|
|
||||||
@ -1,7 +1,5 @@
|
|||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
import '@/entrypoints/public-path';
|
|
||||||
|
|
||||||
import { IntlMessageFormat } from 'intl-messageformat';
|
import { IntlMessageFormat } from 'intl-messageformat';
|
||||||
import type { MessageDescriptor, PrimitiveType } from 'react-intl';
|
import type { MessageDescriptor, PrimitiveType } from 'react-intl';
|
||||||
import { defineMessages } from 'react-intl';
|
import { defineMessages } from 'react-intl';
|
||||||
@ -10,7 +8,6 @@ import Rails from '@rails/ujs';
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { throttle } from 'lodash';
|
import { throttle } from 'lodash';
|
||||||
|
|
||||||
import { start } from 'flavours/glitch/common';
|
|
||||||
import { timeAgoString } from 'flavours/glitch/components/relative_timestamp';
|
import { timeAgoString } from 'flavours/glitch/components/relative_timestamp';
|
||||||
import emojify from 'flavours/glitch/features/emoji/emoji';
|
import emojify from 'flavours/glitch/features/emoji/emoji';
|
||||||
import loadKeyboardExtensions from 'flavours/glitch/load_keyboard_extensions';
|
import loadKeyboardExtensions from 'flavours/glitch/load_keyboard_extensions';
|
||||||
@ -20,8 +17,6 @@ import ready from 'flavours/glitch/ready';
|
|||||||
|
|
||||||
import 'cocoon-js-vanilla';
|
import 'cocoon-js-vanilla';
|
||||||
|
|
||||||
start();
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
usernameTaken: {
|
usernameTaken: {
|
||||||
id: 'username.taken',
|
id: 'username.taken',
|
||||||
@ -153,9 +148,7 @@ function loaded() {
|
|||||||
const reactComponents = document.querySelectorAll('[data-component]');
|
const reactComponents = document.querySelectorAll('[data-component]');
|
||||||
|
|
||||||
if (reactComponents.length > 0) {
|
if (reactComponents.length > 0) {
|
||||||
import(
|
import('flavours/glitch/containers/media_container')
|
||||||
/* webpackChunkName: "containers/media_container" */ 'flavours/glitch/containers/media_container'
|
|
||||||
)
|
|
||||||
.then(({ default: MediaContainer }) => {
|
.then(({ default: MediaContainer }) => {
|
||||||
reactComponents.forEach((component) => {
|
reactComponents.forEach((component) => {
|
||||||
Array.from(component.children).forEach((child) => {
|
Array.from(component.children).forEach((child) => {
|
||||||
|
|||||||
@ -8,8 +8,6 @@ and performs no other task.
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import '@/entrypoints/public-path';
|
|
||||||
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
interface JRDLink {
|
interface JRDLink {
|
||||||
|
|||||||
@ -1,13 +1,9 @@
|
|||||||
import '@/entrypoints/public-path';
|
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
import { start } from 'flavours/glitch/common';
|
|
||||||
import ComposeContainer from 'flavours/glitch/containers/compose_container';
|
import ComposeContainer from 'flavours/glitch/containers/compose_container';
|
||||||
import { loadPolyfills } from 'flavours/glitch/polyfills';
|
import { loadPolyfills } from 'flavours/glitch/polyfills';
|
||||||
import ready from 'flavours/glitch/ready';
|
import ready from 'flavours/glitch/ready';
|
||||||
|
|
||||||
start();
|
|
||||||
|
|
||||||
function loaded() {
|
function loaded() {
|
||||||
const mountNode = document.getElementById('mastodon-compose');
|
const mountNode = document.getElementById('mastodon-compose');
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import '@/entrypoints/public-path';
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
import ready from 'flavours/glitch/ready';
|
import ready from 'flavours/glitch/ready';
|
||||||
|
|||||||
@ -44,6 +44,7 @@ const severityMessages = {
|
|||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
server: state.getIn(['server', 'server']),
|
server: state.getIn(['server', 'server']),
|
||||||
|
locale: state.getIn(['meta', 'locale']),
|
||||||
extendedDescription: state.getIn(['server', 'extendedDescription']),
|
extendedDescription: state.getIn(['server', 'extendedDescription']),
|
||||||
domainBlocks: state.getIn(['server', 'domainBlocks']),
|
domainBlocks: state.getIn(['server', 'domainBlocks']),
|
||||||
});
|
});
|
||||||
@ -91,6 +92,7 @@ class About extends PureComponent {
|
|||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
server: ImmutablePropTypes.map,
|
server: ImmutablePropTypes.map,
|
||||||
|
locale: ImmutablePropTypes.string,
|
||||||
extendedDescription: ImmutablePropTypes.map,
|
extendedDescription: ImmutablePropTypes.map,
|
||||||
domainBlocks: ImmutablePropTypes.contains({
|
domainBlocks: ImmutablePropTypes.contains({
|
||||||
isLoading: PropTypes.bool,
|
isLoading: PropTypes.bool,
|
||||||
@ -114,7 +116,7 @@ class About extends PureComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { multiColumn, intl, server, extendedDescription, domainBlocks } = this.props;
|
const { multiColumn, intl, server, extendedDescription, domainBlocks, locale } = this.props;
|
||||||
const isLoading = server.get('isLoading');
|
const isLoading = server.get('isLoading');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -168,12 +170,15 @@ class About extends PureComponent {
|
|||||||
<p><FormattedMessage id='about.not_available' defaultMessage='This information has not been made available on this server.' /></p>
|
<p><FormattedMessage id='about.not_available' defaultMessage='This information has not been made available on this server.' /></p>
|
||||||
) : (
|
) : (
|
||||||
<ol className='rules-list'>
|
<ol className='rules-list'>
|
||||||
{server.get('rules').map(rule => (
|
{server.get('rules').map(rule => {
|
||||||
|
const text = rule.getIn(['translations', locale, 'text']) || rule.getIn(['translations', locale.split('-')[0], 'text']) || rule.get('text');
|
||||||
|
const hint = rule.getIn(['translations', locale, 'hint']) || rule.getIn(['translations', locale.split('-')[0], 'hint']) || rule.get('hint');
|
||||||
|
return (
|
||||||
<li key={rule.get('id')}>
|
<li key={rule.get('id')}>
|
||||||
<div className='rules-list__text'>{rule.get('text')}</div>
|
<div className='rules-list__text'>{text}</div>
|
||||||
{rule.get('hint').length > 0 && (<div className='rules-list__hint'>{rule.get('hint')}</div>)}
|
{hint.length > 0 && (<div className='rules-list__hint'>{hint}</div>)}
|
||||||
</li>
|
</li>
|
||||||
))}
|
)})}
|
||||||
</ol>
|
</ol>
|
||||||
))}
|
))}
|
||||||
</Section>
|
</Section>
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import { Account } from 'flavours/glitch/components/account';
|
|||||||
import { ColumnBackButton } from 'flavours/glitch/components/column_back_button';
|
import { ColumnBackButton } from 'flavours/glitch/components/column_back_button';
|
||||||
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
|
||||||
import { RemoteHint } from 'flavours/glitch/components/remote_hint';
|
import { RemoteHint } from 'flavours/glitch/components/remote_hint';
|
||||||
import StatusContainer from 'flavours/glitch/containers/status_container';
|
import { StatusQuoteManager } from 'flavours/glitch/components/status_quoted';
|
||||||
import { AccountHeader } from 'flavours/glitch/features/account_timeline/components/account_header';
|
import { AccountHeader } from 'flavours/glitch/features/account_timeline/components/account_header';
|
||||||
import BundleColumnError from 'flavours/glitch/features/ui/components/bundle_column_error';
|
import BundleColumnError from 'flavours/glitch/features/ui/components/bundle_column_error';
|
||||||
import Column from 'flavours/glitch/features/ui/components/column';
|
import Column from 'flavours/glitch/features/ui/components/column';
|
||||||
@ -142,9 +142,8 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
|||||||
/>
|
/>
|
||||||
</h4>
|
</h4>
|
||||||
{featuredStatusIds.map((statusId) => (
|
{featuredStatusIds.map((statusId) => (
|
||||||
<StatusContainer
|
<StatusQuoteManager
|
||||||
key={`f-${statusId}`}
|
key={`f-${statusId}`}
|
||||||
// @ts-expect-error inferred props are wrong
|
|
||||||
id={statusId}
|
id={statusId}
|
||||||
contextType='account'
|
contextType='account'
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,15 +1,12 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { fetchAccountsFamiliarFollowers } from '@/flavours/glitch/actions/accounts_familiar_followers';
|
|
||||||
import { Avatar } from '@/flavours/glitch/components/avatar';
|
import { Avatar } from '@/flavours/glitch/components/avatar';
|
||||||
import { AvatarGroup } from '@/flavours/glitch/components/avatar_group';
|
import { AvatarGroup } from '@/flavours/glitch/components/avatar_group';
|
||||||
import type { Account } from '@/flavours/glitch/models/account';
|
import type { Account } from '@/flavours/glitch/models/account';
|
||||||
import { getAccountFamiliarFollowers } from '@/flavours/glitch/selectors/accounts';
|
|
||||||
import { useAppDispatch, useAppSelector } from '@/flavours/glitch/store';
|
import { useFetchFamiliarFollowers } from '../hooks/familiar_followers';
|
||||||
|
|
||||||
const AccountLink: React.FC<{ account?: Account }> = ({ account }) => {
|
const AccountLink: React.FC<{ account?: Account }> = ({ account }) => {
|
||||||
if (!account) {
|
if (!account) {
|
||||||
@ -64,20 +61,11 @@ const FamiliarFollowersReadout: React.FC<{ familiarFollowers: Account[] }> = ({
|
|||||||
export const FamiliarFollowers: React.FC<{ accountId: string }> = ({
|
export const FamiliarFollowers: React.FC<{ accountId: string }> = ({
|
||||||
accountId,
|
accountId,
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useAppDispatch();
|
const { familiarFollowers, isLoading } = useFetchFamiliarFollowers({
|
||||||
const familiarFollowers = useAppSelector((state) =>
|
accountId,
|
||||||
getAccountFamiliarFollowers(state, accountId),
|
});
|
||||||
);
|
|
||||||
|
|
||||||
const hasNoData = familiarFollowers === null;
|
if (isLoading || familiarFollowers.length === 0) {
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (hasNoData) {
|
|
||||||
void dispatch(fetchAccountsFamiliarFollowers({ id: accountId }));
|
|
||||||
}
|
|
||||||
}, [dispatch, accountId, hasNoData]);
|
|
||||||
|
|
||||||
if (hasNoData || familiarFollowers.length === 0) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,30 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
import { fetchAccountsFamiliarFollowers } from '@/flavours/glitch/actions/accounts_familiar_followers';
|
||||||
|
import { getAccountFamiliarFollowers } from '@/flavours/glitch/selectors/accounts';
|
||||||
|
import { useAppDispatch, useAppSelector } from '@/flavours/glitch/store';
|
||||||
|
import { me } from 'flavours/glitch/initial_state';
|
||||||
|
|
||||||
|
export const useFetchFamiliarFollowers = ({
|
||||||
|
accountId,
|
||||||
|
}: {
|
||||||
|
accountId?: string;
|
||||||
|
}) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const familiarFollowers = useAppSelector((state) =>
|
||||||
|
accountId ? getAccountFamiliarFollowers(state, accountId) : null,
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasNoData = familiarFollowers === null;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (hasNoData && accountId && accountId !== me) {
|
||||||
|
void dispatch(fetchAccountsFamiliarFollowers({ id: accountId }));
|
||||||
|
}
|
||||||
|
}, [dispatch, accountId, hasNoData]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
familiarFollowers: hasNoData ? [] : familiarFollowers,
|
||||||
|
isLoading: hasNoData,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -15,10 +15,6 @@ import type { List as ImmutableList, Map as ImmutableMap } from 'immutable';
|
|||||||
import { useSpring, animated } from '@react-spring/web';
|
import { useSpring, animated } from '@react-spring/web';
|
||||||
import Textarea from 'react-textarea-autosize';
|
import Textarea from 'react-textarea-autosize';
|
||||||
import { length } from 'stringz';
|
import { length } from 'stringz';
|
||||||
// eslint-disable-next-line import/extensions
|
|
||||||
import tesseractWorkerPath from 'tesseract.js/dist/worker.min.js';
|
|
||||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
||||||
import tesseractCorePath from 'tesseract.js-core/tesseract-core.wasm.js';
|
|
||||||
|
|
||||||
import { showAlertForError } from 'flavours/glitch/actions/alerts';
|
import { showAlertForError } from 'flavours/glitch/actions/alerts';
|
||||||
import { uploadThumbnail } from 'flavours/glitch/actions/compose';
|
import { uploadThumbnail } from 'flavours/glitch/actions/compose';
|
||||||
@ -350,9 +346,15 @@ export const AltTextModal = forwardRef<ModalRef, Props & Partial<RestoreProps>>(
|
|||||||
|
|
||||||
fetchTesseract()
|
fetchTesseract()
|
||||||
.then(async ({ createWorker }) => {
|
.then(async ({ createWorker }) => {
|
||||||
|
const [tesseractWorkerPath, tesseractCorePath] = await Promise.all([
|
||||||
|
// eslint-disable-next-line import/extensions
|
||||||
|
import('tesseract.js/dist/worker.min.js?url'),
|
||||||
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
|
import('tesseract.js-core/tesseract-core.wasm.js?url'),
|
||||||
|
]);
|
||||||
const worker = await createWorker('eng', 1, {
|
const worker = await createWorker('eng', 1, {
|
||||||
workerPath: tesseractWorkerPath as string,
|
workerPath: tesseractWorkerPath.default,
|
||||||
corePath: tesseractCorePath as string,
|
corePath: tesseractCorePath.default,
|
||||||
langPath: `${assetHost}/ocr/lang-data`,
|
langPath: `${assetHost}/ocr/lang-data`,
|
||||||
cacheMethod: 'write',
|
cacheMethod: 'write',
|
||||||
});
|
});
|
||||||
@ -501,5 +503,4 @@ export const AltTextModal = forwardRef<ModalRef, Props & Partial<RestoreProps>>(
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
AltTextModal.displayName = 'AltTextModal';
|
AltTextModal.displayName = 'AltTextModal';
|
||||||
|
|||||||
@ -9,14 +9,13 @@
|
|||||||
// to ensure that the prevaled file is regenerated by Babel
|
// to ensure that the prevaled file is regenerated by Babel
|
||||||
// version: 4
|
// version: 4
|
||||||
|
|
||||||
const { NimbleEmojiIndex } = require('emoji-mart');
|
import { NimbleEmojiIndex } from 'emoji-mart';
|
||||||
const { uncompress: emojiMartUncompress } = require('emoji-mart/dist/utils/data');
|
import { uncompress as emojiMartUncompress } from 'emoji-mart/dist/utils/data';
|
||||||
|
|
||||||
|
import data from './emoji_data.json';
|
||||||
let data = require('./emoji_data.json');
|
import emojiMap from './emoji_map.json';
|
||||||
const emojiMap = require('./emoji_map.json');
|
import { unicodeToFilename } from './unicode_to_filename';
|
||||||
const { unicodeToFilename } = require('./unicode_to_filename');
|
import { unicodeToUnifiedName } from './unicode_to_unified_name';
|
||||||
const { unicodeToUnifiedName } = require('./unicode_to_unified_name');
|
|
||||||
|
|
||||||
emojiMartUncompress(data);
|
emojiMartUncompress(data);
|
||||||
|
|
||||||
@ -30,7 +29,7 @@ const shortcodeMap = {};
|
|||||||
const shortCodesToEmojiData = {};
|
const shortCodesToEmojiData = {};
|
||||||
const emojisWithoutShortCodes = [];
|
const emojisWithoutShortCodes = [];
|
||||||
|
|
||||||
Object.keys(emojiIndex.emojis).forEach(key => {
|
Object.keys(emojiIndex.emojis).forEach((key) => {
|
||||||
let emoji = emojiIndex.emojis[key];
|
let emoji = emojiIndex.emojis[key];
|
||||||
|
|
||||||
// Emojis with skin tone modifiers are stored like this
|
// Emojis with skin tone modifiers are stored like this
|
||||||
@ -41,15 +40,15 @@ Object.keys(emojiIndex.emojis).forEach(key => {
|
|||||||
shortcodeMap[emoji.native] = emoji.id;
|
shortcodeMap[emoji.native] = emoji.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
const stripModifiers = unicode => {
|
const stripModifiers = (unicode) => {
|
||||||
skinTones.forEach(tone => {
|
skinTones.forEach((tone) => {
|
||||||
unicode = unicode.replace(tone, '');
|
unicode = unicode.replace(tone, '');
|
||||||
});
|
});
|
||||||
|
|
||||||
return unicode;
|
return unicode;
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.keys(emojiMap).forEach(key => {
|
Object.keys(emojiMap).forEach((key) => {
|
||||||
if (excluded.includes(key)) {
|
if (excluded.includes(key)) {
|
||||||
delete emojiMap[key];
|
delete emojiMap[key];
|
||||||
return;
|
return;
|
||||||
@ -82,7 +81,7 @@ Object.keys(emojiMap).forEach(key => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.keys(emojiIndex.emojis).forEach(key => {
|
Object.keys(emojiIndex.emojis).forEach((key) => {
|
||||||
let emoji = emojiIndex.emojis[key];
|
let emoji = emojiIndex.emojis[key];
|
||||||
|
|
||||||
// Emojis with skin tone modifiers are stored like this
|
// Emojis with skin tone modifiers are stored like this
|
||||||
@ -94,9 +93,11 @@ Object.keys(emojiIndex.emojis).forEach(key => {
|
|||||||
let { short_names, search, unified } = emojiMartData.emojis[key];
|
let { short_names, search, unified } = emojiMartData.emojis[key];
|
||||||
|
|
||||||
if (short_names[0] !== key) {
|
if (short_names[0] !== key) {
|
||||||
throw new Error('The compressor expects the first short_code to be the ' +
|
throw new Error(
|
||||||
|
'The compressor expects the first short_code to be the ' +
|
||||||
'key. It may need to be rewritten if the emoji change such that this ' +
|
'key. It may need to be rewritten if the emoji change such that this ' +
|
||||||
'is no longer the case.');
|
'is no longer the case.',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
short_names = short_names.slice(1); // first short name can be inferred from the key
|
short_names = short_names.slice(1); // first short name can be inferred from the key
|
||||||
@ -117,7 +118,8 @@ Object.keys(emojiIndex.emojis).forEach(key => {
|
|||||||
|
|
||||||
// JSON.parse/stringify is to emulate what @preval is doing and avoid any
|
// JSON.parse/stringify is to emulate what @preval is doing and avoid any
|
||||||
// inconsistent behavior in dev mode
|
// inconsistent behavior in dev mode
|
||||||
module.exports = JSON.parse(JSON.stringify([
|
export default JSON.parse(
|
||||||
|
JSON.stringify([
|
||||||
shortCodesToEmojiData,
|
shortCodesToEmojiData,
|
||||||
/*
|
/*
|
||||||
* The property `skins` is not found in the current context.
|
* The property `skins` is not found in the current context.
|
||||||
@ -126,11 +128,12 @@ module.exports = JSON.parse(JSON.stringify([
|
|||||||
* Currently, no definitions or references to `skins` property can be found in:
|
* Currently, no definitions or references to `skins` property can be found in:
|
||||||
* - {@link node_modules/emoji-mart/dist/utils/data.js}
|
* - {@link node_modules/emoji-mart/dist/utils/data.js}
|
||||||
* - {@link node_modules/emoji-mart/data/all.json}
|
* - {@link node_modules/emoji-mart/data/all.json}
|
||||||
* - {@link app/javascript/mastodon/features/emoji/emoji_compressed.d.ts#Skins}
|
* - {@link app/javascript/flavours/glitch/features/emoji/emoji_compressed.d.ts#Skins}
|
||||||
* Future refactorings or updates should consider adding definitions or handling for `skins` property.
|
* Future refactorings or updates should consider adding definitions or handling for `skins` property.
|
||||||
*/
|
*/
|
||||||
emojiMartData.skins,
|
emojiMartData.skins,
|
||||||
emojiMartData.categories,
|
emojiMartData.categories,
|
||||||
emojiMartData.aliases,
|
emojiMartData.aliases,
|
||||||
emojisWithoutShortCodes
|
emojisWithoutShortCodes,
|
||||||
]));
|
]),
|
||||||
|
);
|
||||||
@ -3,9 +3,13 @@
|
|||||||
// emojiIndex.search functionality.
|
// emojiIndex.search functionality.
|
||||||
import type { BaseEmoji } from 'emoji-mart';
|
import type { BaseEmoji } from 'emoji-mart';
|
||||||
import type { Emoji } from 'emoji-mart/dist-es/utils/data';
|
import type { Emoji } from 'emoji-mart/dist-es/utils/data';
|
||||||
|
// eslint-disable-next-line import/no-unresolved
|
||||||
|
import emojiCompressed from 'virtual:mastodon-emoji-compressed';
|
||||||
|
import type {
|
||||||
|
Search,
|
||||||
|
ShortCodesToEmojiData,
|
||||||
|
} from 'virtual:mastodon-emoji-compressed';
|
||||||
|
|
||||||
import type { Search, ShortCodesToEmojiData } from './emoji_compressed';
|
|
||||||
import emojiCompressed from './emoji_compressed';
|
|
||||||
import { unicodeToUnifiedName } from './unicode_to_unified_name';
|
import { unicodeToUnifiedName } from './unicode_to_unified_name';
|
||||||
|
|
||||||
type Emojis = Record<
|
type Emojis = Record<
|
||||||
|
|||||||
@ -2,11 +2,13 @@
|
|||||||
// (i.e. the svg filename) and a shortCode intended to be shown
|
// (i.e. the svg filename) and a shortCode intended to be shown
|
||||||
// as a "title" attribute in an HTML element (aka tooltip).
|
// as a "title" attribute in an HTML element (aka tooltip).
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-unresolved
|
||||||
|
import emojiCompressed from 'virtual:mastodon-emoji-compressed';
|
||||||
import type {
|
import type {
|
||||||
FilenameData,
|
FilenameData,
|
||||||
ShortCodesToEmojiDataKey,
|
ShortCodesToEmojiDataKey,
|
||||||
} from './emoji_compressed';
|
} from 'virtual:mastodon-emoji-compressed';
|
||||||
import emojiCompressed from './emoji_compressed';
|
|
||||||
import { unicodeToFilename } from './unicode_to_filename';
|
import { unicodeToFilename } from './unicode_to_filename';
|
||||||
|
|
||||||
type UnicodeMapping = Record<
|
type UnicodeMapping = Record<
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// taken from:
|
// taken from:
|
||||||
// https://github.com/twitter/twemoji/blob/47732c7/twemoji-generator.js#L848-L866
|
// https://github.com/twitter/twemoji/blob/47732c7/twemoji-generator.js#L848-L866
|
||||||
exports.unicodeToFilename = (str) => {
|
export const unicodeToFilename = (str) => {
|
||||||
let result = '';
|
let result = '';
|
||||||
let charCode = 0;
|
let charCode = 0;
|
||||||
let p = 0;
|
let p = 0;
|
||||||
|
|||||||
@ -6,7 +6,7 @@ function padLeft(str, num) {
|
|||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.unicodeToUnifiedName = (str) => {
|
export const unicodeToUnifiedName = (str) => {
|
||||||
let output = '';
|
let output = '';
|
||||||
|
|
||||||
for (let i = 0; i < str.length; i += 2) {
|
for (let i = 0; i < str.length; i += 2) {
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import PersonAddIcon from '@/material-icons/400-24px/person_add-fill.svg?react';
|
|||||||
import { Account } from 'flavours/glitch/components/account';
|
import { Account } from 'flavours/glitch/components/account';
|
||||||
import { Icon } from 'flavours/glitch/components/icon';
|
import { Icon } from 'flavours/glitch/components/icon';
|
||||||
import { Permalink } from 'flavours/glitch/components/permalink';
|
import { Permalink } from 'flavours/glitch/components/permalink';
|
||||||
import StatusContainer from 'flavours/glitch/containers/status_container';
|
import { StatusQuoteManager } from 'flavours/glitch/components/status_quoted';
|
||||||
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
|
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
|
||||||
|
|
||||||
import FollowRequestContainer from '../containers/follow_request_container';
|
import FollowRequestContainer from '../containers/follow_request_container';
|
||||||
@ -159,7 +159,7 @@ class Notification extends ImmutablePureComponent {
|
|||||||
|
|
||||||
renderMention (notification) {
|
renderMention (notification) {
|
||||||
return (
|
return (
|
||||||
<StatusContainer
|
<StatusQuoteManager
|
||||||
id={notification.get('status')}
|
id={notification.get('status')}
|
||||||
containerId={notification.get('id')}
|
containerId={notification.get('id')}
|
||||||
withDismiss
|
withDismiss
|
||||||
@ -181,7 +181,7 @@ class Notification extends ImmutablePureComponent {
|
|||||||
|
|
||||||
renderFavourite (notification) {
|
renderFavourite (notification) {
|
||||||
return (
|
return (
|
||||||
<StatusContainer
|
<StatusQuoteManager
|
||||||
containerId={notification.get('id')}
|
containerId={notification.get('id')}
|
||||||
hidden={!!this.props.hidden}
|
hidden={!!this.props.hidden}
|
||||||
id={notification.get('status')}
|
id={notification.get('status')}
|
||||||
@ -206,7 +206,7 @@ class Notification extends ImmutablePureComponent {
|
|||||||
|
|
||||||
renderReblog (notification) {
|
renderReblog (notification) {
|
||||||
return (
|
return (
|
||||||
<StatusContainer
|
<StatusQuoteManager
|
||||||
containerId={notification.get('id')}
|
containerId={notification.get('id')}
|
||||||
hidden={!!this.props.hidden}
|
hidden={!!this.props.hidden}
|
||||||
id={notification.get('status')}
|
id={notification.get('status')}
|
||||||
@ -231,7 +231,7 @@ class Notification extends ImmutablePureComponent {
|
|||||||
|
|
||||||
renderStatus (notification) {
|
renderStatus (notification) {
|
||||||
return (
|
return (
|
||||||
<StatusContainer
|
<StatusQuoteManager
|
||||||
containerId={notification.get('id')}
|
containerId={notification.get('id')}
|
||||||
hidden={!!this.props.hidden}
|
hidden={!!this.props.hidden}
|
||||||
id={notification.get('status')}
|
id={notification.get('status')}
|
||||||
@ -256,7 +256,7 @@ class Notification extends ImmutablePureComponent {
|
|||||||
|
|
||||||
renderUpdate (notification) {
|
renderUpdate (notification) {
|
||||||
return (
|
return (
|
||||||
<StatusContainer
|
<StatusQuoteManager
|
||||||
containerId={notification.get('id')}
|
containerId={notification.get('id')}
|
||||||
hidden={!!this.props.hidden}
|
hidden={!!this.props.hidden}
|
||||||
id={notification.get('status')}
|
id={notification.get('status')}
|
||||||
@ -281,7 +281,7 @@ class Notification extends ImmutablePureComponent {
|
|||||||
|
|
||||||
renderPoll (notification) {
|
renderPoll (notification) {
|
||||||
return (
|
return (
|
||||||
<StatusContainer
|
<StatusQuoteManager
|
||||||
containerId={notification.get('id')}
|
containerId={notification.get('id')}
|
||||||
hidden={!!this.props.hidden}
|
hidden={!!this.props.hidden}
|
||||||
id={notification.get('status')}
|
id={notification.get('status')}
|
||||||
|
|||||||
@ -18,12 +18,14 @@ import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
|
|||||||
import { DisplayedName } from './displayed_name';
|
import { DisplayedName } from './displayed_name';
|
||||||
import { EmbeddedStatus } from './embedded_status';
|
import { EmbeddedStatus } from './embedded_status';
|
||||||
|
|
||||||
|
const AVATAR_SIZE = 28;
|
||||||
|
|
||||||
export const AvatarById: React.FC<{ accountId: string }> = ({ accountId }) => {
|
export const AvatarById: React.FC<{ accountId: string }> = ({ accountId }) => {
|
||||||
const account = useAppSelector((state) => state.accounts.get(accountId));
|
const account = useAppSelector((state) => state.accounts.get(accountId));
|
||||||
|
|
||||||
if (!account) return null;
|
if (!account) return null;
|
||||||
|
|
||||||
return <Avatar withLink account={account} size={28} />;
|
return <Avatar withLink account={account} size={AVATAR_SIZE} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LabelRenderer = (
|
export type LabelRenderer = (
|
||||||
@ -108,7 +110,7 @@ export const NotificationGroupWithStatus: React.FC<{
|
|||||||
<div className='notification-group__main'>
|
<div className='notification-group__main'>
|
||||||
<div className='notification-group__main__header'>
|
<div className='notification-group__main__header'>
|
||||||
<div className='notification-group__main__header__wrapper'>
|
<div className='notification-group__main__header__wrapper'>
|
||||||
<AvatarGroup>
|
<AvatarGroup avatarHeight={AVATAR_SIZE}>
|
||||||
{accountIds
|
{accountIds
|
||||||
.slice(0, NOTIFICATIONS_GROUP_MAX_AVATARS)
|
.slice(0, NOTIFICATIONS_GROUP_MAX_AVATARS)
|
||||||
.map((id) => (
|
.map((id) => (
|
||||||
@ -123,7 +125,14 @@ export const NotificationGroupWithStatus: React.FC<{
|
|||||||
|
|
||||||
<div className='notification-group__main__header__label'>
|
<div className='notification-group__main__header__label'>
|
||||||
{label}
|
{label}
|
||||||
{timestamp && <RelativeTimestamp timestamp={timestamp} />}
|
{timestamp && (
|
||||||
|
<>
|
||||||
|
<span className='notification-group__main__header__label-separator'>
|
||||||
|
·
|
||||||
|
</span>
|
||||||
|
<RelativeTimestamp timestamp={timestamp} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import {
|
|||||||
} from 'flavours/glitch/actions/statuses';
|
} from 'flavours/glitch/actions/statuses';
|
||||||
import type { IconProp } from 'flavours/glitch/components/icon';
|
import type { IconProp } from 'flavours/glitch/components/icon';
|
||||||
import { Icon } from 'flavours/glitch/components/icon';
|
import { Icon } from 'flavours/glitch/components/icon';
|
||||||
import Status from 'flavours/glitch/containers/status_container';
|
import { StatusQuoteManager } from 'flavours/glitch/components/status_quoted';
|
||||||
import { getStatusHidden } from 'flavours/glitch/selectors/filters';
|
import { getStatusHidden } from 'flavours/glitch/selectors/filters';
|
||||||
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
|
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
|
||||||
|
|
||||||
@ -106,8 +106,7 @@ export const NotificationWithStatus: React.FC<{
|
|||||||
{label}
|
{label}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Status
|
<StatusQuoteManager
|
||||||
// @ts-expect-error -- <Status> is not yet typed
|
|
||||||
id={statusId}
|
id={statusId}
|
||||||
contextType='notifications'
|
contextType='notifications'
|
||||||
withDismiss
|
withDismiss
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import Option from './components/option';
|
|||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
rules: state.getIn(['server', 'server', 'rules']),
|
rules: state.getIn(['server', 'server', 'rules']),
|
||||||
|
locale: state.getIn(['meta', 'locale']),
|
||||||
});
|
});
|
||||||
|
|
||||||
class Rules extends PureComponent {
|
class Rules extends PureComponent {
|
||||||
@ -19,6 +20,7 @@ class Rules extends PureComponent {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
onNextStep: PropTypes.func.isRequired,
|
onNextStep: PropTypes.func.isRequired,
|
||||||
rules: ImmutablePropTypes.list,
|
rules: ImmutablePropTypes.list,
|
||||||
|
locale: PropTypes.string,
|
||||||
selectedRuleIds: ImmutablePropTypes.set.isRequired,
|
selectedRuleIds: ImmutablePropTypes.set.isRequired,
|
||||||
onToggle: PropTypes.func.isRequired,
|
onToggle: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
@ -34,7 +36,7 @@ class Rules extends PureComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { rules, selectedRuleIds } = this.props;
|
const { rules, locale, selectedRuleIds } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -49,7 +51,7 @@ class Rules extends PureComponent {
|
|||||||
value={item.get('id')}
|
value={item.get('id')}
|
||||||
checked={selectedRuleIds.includes(item.get('id'))}
|
checked={selectedRuleIds.includes(item.get('id'))}
|
||||||
onToggle={this.handleRulesToggle}
|
onToggle={this.handleRulesToggle}
|
||||||
label={item.get('text')}
|
label={item.getIn(['translations', locale, 'text']) || item.getIn(['translations', locale.split('-')[0], 'text']) || item.get('text')}
|
||||||
multiple
|
multiple
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import { ColumnHeader } from 'flavours/glitch/components/column_header';
|
|||||||
import { CompatibilityHashtag as Hashtag } from 'flavours/glitch/components/hashtag';
|
import { CompatibilityHashtag as Hashtag } from 'flavours/glitch/components/hashtag';
|
||||||
import { Icon } from 'flavours/glitch/components/icon';
|
import { Icon } from 'flavours/glitch/components/icon';
|
||||||
import ScrollableList from 'flavours/glitch/components/scrollable_list';
|
import ScrollableList from 'flavours/glitch/components/scrollable_list';
|
||||||
import Status from 'flavours/glitch/containers/status_container';
|
import { StatusQuoteManager } from 'flavours/glitch/components/status_quoted';
|
||||||
import { Search } from 'flavours/glitch/features/compose/components/search';
|
import { Search } from 'flavours/glitch/features/compose/components/search';
|
||||||
import { useSearchParam } from 'flavours/glitch/hooks/useSearchParam';
|
import { useSearchParam } from 'flavours/glitch/hooks/useSearchParam';
|
||||||
import type { Hashtag as HashtagType } from 'flavours/glitch/models/tags';
|
import type { Hashtag as HashtagType } from 'flavours/glitch/models/tags';
|
||||||
@ -53,8 +53,7 @@ const renderHashtags = (hashtags: HashtagType[]) =>
|
|||||||
|
|
||||||
const renderStatuses = (statusIds: string[]) =>
|
const renderStatuses = (statusIds: string[]) =>
|
||||||
hidePeek<string>(statusIds).map((id) => (
|
hidePeek<string>(statusIds).map((id) => (
|
||||||
// @ts-expect-error inferred props are wrong
|
<StatusQuoteManager key={id} id={id} />
|
||||||
<Status key={id} id={id} />
|
|
||||||
));
|
));
|
||||||
|
|
||||||
type SearchType = 'all' | ApiSearchType;
|
type SearchType = 'all' | ApiSearchType;
|
||||||
@ -190,8 +189,7 @@ export const SearchResults: React.FC<{ multiColumn: boolean }> = ({
|
|||||||
onClickMore={handleSelectStatuses}
|
onClickMore={handleSelectStatuses}
|
||||||
>
|
>
|
||||||
{results.statuses.slice(0, INITIAL_DISPLAY).map((id) => (
|
{results.statuses.slice(0, INITIAL_DISPLAY).map((id) => (
|
||||||
// @ts-expect-error inferred props are wrong
|
<StatusQuoteManager key={id} id={id} />
|
||||||
<Status key={id} id={id} />
|
|
||||||
))}
|
))}
|
||||||
</SearchSection>
|
</SearchSection>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -27,6 +27,7 @@ import { MentionsPlaceholder } from 'flavours/glitch/components/mentions_placeho
|
|||||||
import { Permalink } from 'flavours/glitch/components/permalink';
|
import { Permalink } from 'flavours/glitch/components/permalink';
|
||||||
import { PictureInPicturePlaceholder } from 'flavours/glitch/components/picture_in_picture_placeholder';
|
import { PictureInPicturePlaceholder } from 'flavours/glitch/components/picture_in_picture_placeholder';
|
||||||
import StatusContent from 'flavours/glitch/components/status_content';
|
import StatusContent from 'flavours/glitch/components/status_content';
|
||||||
|
import { QuotedStatus } from 'flavours/glitch/components/status_quoted';
|
||||||
import { VisibilityIcon } from 'flavours/glitch/components/visibility_icon';
|
import { VisibilityIcon } from 'flavours/glitch/components/visibility_icon';
|
||||||
import { Audio } from 'flavours/glitch/features/audio';
|
import { Audio } from 'flavours/glitch/features/audio';
|
||||||
import scheduleIdleTask from 'flavours/glitch/features/ui/util/schedule_idle_task';
|
import scheduleIdleTask from 'flavours/glitch/features/ui/util/schedule_idle_task';
|
||||||
@ -265,7 +266,7 @@ export const DetailedStatus: React.FC<{
|
|||||||
);
|
);
|
||||||
mediaIcons.push('video-camera');
|
mediaIcons.push('video-camera');
|
||||||
}
|
}
|
||||||
} else if (status.get('card')) {
|
} else if (status.get('card') && !status.get('quote')) {
|
||||||
media = (
|
media = (
|
||||||
<Card
|
<Card
|
||||||
sensitive={status.get('sensitive')}
|
sensitive={status.get('sensitive')}
|
||||||
@ -355,6 +356,9 @@ export const DetailedStatus: React.FC<{
|
|||||||
className={classNames(
|
className={classNames(
|
||||||
'detailed-status',
|
'detailed-status',
|
||||||
`detailed-status-${status.get('visibility')}`,
|
`detailed-status-${status.get('visibility')}`,
|
||||||
|
{
|
||||||
|
'status--has-quote': !!status.get('quote'),
|
||||||
|
},
|
||||||
)}
|
)}
|
||||||
data-status-by={status.getIn(['account', 'acct'])}
|
data-status-by={status.getIn(['account', 'acct'])}
|
||||||
>
|
>
|
||||||
@ -410,6 +414,10 @@ export const DetailedStatus: React.FC<{
|
|||||||
{...(statusContentProps as any)}
|
{...(statusContentProps as any)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{status.get('quote') && (
|
||||||
|
<QuotedStatus quote={status.get('quote')} />
|
||||||
|
)}
|
||||||
|
|
||||||
{media}
|
{media}
|
||||||
{hashtagBar}
|
{hashtagBar}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -6,8 +6,6 @@ import classNames from 'classnames';
|
|||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
|
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
|
||||||
import { List as ImmutableList } from 'immutable';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
@ -56,7 +54,7 @@ import {
|
|||||||
} from '../../actions/statuses';
|
} from '../../actions/statuses';
|
||||||
import ColumnHeader from '../../components/column_header';
|
import ColumnHeader from '../../components/column_header';
|
||||||
import { textForScreenReader, defaultMediaVisibility } from '../../components/status';
|
import { textForScreenReader, defaultMediaVisibility } from '../../components/status';
|
||||||
import StatusContainer from '../../containers/status_container';
|
import { StatusQuoteManager } from '../../components/status_quoted';
|
||||||
import { deleteModal } from '../../initial_state';
|
import { deleteModal } from '../../initial_state';
|
||||||
import { makeGetStatus, makeGetPictureInPicture } from '../../selectors';
|
import { makeGetStatus, makeGetPictureInPicture } from '../../selectors';
|
||||||
import { getAncestorsIds, getDescendantsIds } from 'flavours/glitch/selectors/contexts';
|
import { getAncestorsIds, getDescendantsIds } from 'flavours/glitch/selectors/contexts';
|
||||||
@ -492,7 +490,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
const { params: { statusId } } = this.props;
|
const { params: { statusId } } = this.props;
|
||||||
|
|
||||||
return list.map((id, i) => (
|
return list.map((id, i) => (
|
||||||
<StatusContainer
|
<StatusQuoteManager
|
||||||
key={id}
|
key={id}
|
||||||
id={id}
|
id={id}
|
||||||
expanded={this.state.threadExpanded}
|
expanded={this.state.threadExpanded}
|
||||||
|
|||||||
@ -1,243 +1,243 @@
|
|||||||
export function EmojiPicker () {
|
export function EmojiPicker () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/emoji_picker" */'../../emoji/emoji_picker');
|
return import('../../emoji/emoji_picker');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Compose () {
|
export function Compose () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/compose" */'../../compose');
|
return import('../../compose');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Notifications () {
|
export function Notifications () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/notifications" */'../../notifications_v2');
|
return import('../../notifications_v2');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HomeTimeline () {
|
export function HomeTimeline () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/home_timeline" */'../../home_timeline');
|
return import('../../home_timeline');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PublicTimeline () {
|
export function PublicTimeline () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/public_timeline" */'../../public_timeline');
|
return import('../../public_timeline');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CommunityTimeline () {
|
export function CommunityTimeline () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/community_timeline" */'../../community_timeline');
|
return import('../../community_timeline');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Firehose () {
|
export function Firehose () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/firehose" */'../../firehose');
|
return import('../../firehose');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function HashtagTimeline () {
|
export function HashtagTimeline () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/hashtag_timeline" */'../../hashtag_timeline');
|
return import('../../hashtag_timeline');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ListTimeline () {
|
export function ListTimeline () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/list_timeline" */'../../list_timeline');
|
return import('../../list_timeline');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Lists () {
|
export function Lists () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/lists" */'../../lists');
|
return import('../../lists');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DirectTimeline() {
|
export function DirectTimeline() {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/direct_timeline" */'../../direct_timeline');
|
return import('../../direct_timeline');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Status () {
|
export function Status () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/status" */'../../status');
|
return import('../../status');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GettingStarted () {
|
export function GettingStarted () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/getting_started" */'../../getting_started');
|
return import('../../getting_started');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function GettingStartedMisc () {
|
export function GettingStartedMisc () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/getting_started_misc" */'../../getting_started_misc');
|
return import('../../getting_started_misc');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function KeyboardShortcuts () {
|
export function KeyboardShortcuts () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/keyboard_shortcuts" */'../../keyboard_shortcuts');
|
return import('../../keyboard_shortcuts');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PinnedStatuses () {
|
export function PinnedStatuses () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/pinned_statuses" */'../../pinned_statuses');
|
return import('../../pinned_statuses');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AccountTimeline () {
|
export function AccountTimeline () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/account_timeline" */'../../account_timeline');
|
return import('../../account_timeline');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AccountGallery () {
|
export function AccountGallery () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/account_gallery" */'../../account_gallery');
|
return import('../../account_gallery');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AccountFeatured() {
|
export function AccountFeatured() {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/account_featured" */'../../account_featured');
|
return import('../../account_featured');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Followers () {
|
export function Followers () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/followers" */'../../followers');
|
return import('../../followers');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Following () {
|
export function Following () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/following" */'../../following');
|
return import('../../following');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Reblogs () {
|
export function Reblogs () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/reblogs" */'../../reblogs');
|
return import('../../reblogs');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Favourites () {
|
export function Favourites () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/favourites" */'../../favourites');
|
return import('../../favourites');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FollowRequests () {
|
export function FollowRequests () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/follow_requests" */'../../follow_requests');
|
return import('../../follow_requests');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FavouritedStatuses () {
|
export function FavouritedStatuses () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/favourited_statuses" */'../../favourited_statuses');
|
return import('../../favourited_statuses');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FollowedTags () {
|
export function FollowedTags () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/followed_tags" */'../../followed_tags');
|
return import('../../followed_tags');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BookmarkedStatuses () {
|
export function BookmarkedStatuses () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/bookmarked_statuses" */'../../bookmarked_statuses');
|
return import('../../bookmarked_statuses');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Blocks () {
|
export function Blocks () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/blocks" */'../../blocks');
|
return import('../../blocks');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DomainBlocks () {
|
export function DomainBlocks () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/domain_blocks" */'../../domain_blocks');
|
return import('../../domain_blocks');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Mutes () {
|
export function Mutes () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/mutes" */'../../mutes');
|
return import('../../mutes');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MuteModal () {
|
export function MuteModal () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/mute_modal" */'../components/mute_modal');
|
return import('../components/mute_modal');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function BlockModal () {
|
export function BlockModal () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/block_modal" */'../components/block_modal');
|
return import('../components/block_modal');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DomainBlockModal () {
|
export function DomainBlockModal () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/modals/domain_block_modal" */'../components/domain_block_modal');
|
return import('../components/domain_block_modal');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ReportModal () {
|
export function ReportModal () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/report_modal" */'../components/report_modal');
|
return import('../components/report_modal');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SettingsModal () {
|
export function SettingsModal () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/settings_modal" */'../../local_settings');
|
return import('../../local_settings');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function IgnoreNotificationsModal () {
|
export function IgnoreNotificationsModal () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/ignore_notifications_modal" */'../components/ignore_notifications_modal');
|
return import('../components/ignore_notifications_modal');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MediaGallery () {
|
export function MediaGallery () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/media_gallery" */'../../../components/media_gallery');
|
return import('../../../components/media_gallery');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Video () {
|
export function Video () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/video" */'../../video');
|
return import('../../video');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EmbedModal () {
|
export function EmbedModal () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/embed_modal" */'../components/embed_modal');
|
return import('../components/embed_modal');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ListAdder () {
|
export function ListAdder () {
|
||||||
return import(/* webpackChunkName: "features/glitch/async/list_adder" */'../../list_adder');
|
return import('../../list_adder');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Tesseract () {
|
export function Tesseract () {
|
||||||
return import(/*webpackChunkName: "tesseract" */'tesseract.js');
|
return import('tesseract.js');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Audio () {
|
export function Audio () {
|
||||||
return import(/* webpackChunkName: "features/glitch/async/audio" */'../../audio');
|
return import('../../audio');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Directory () {
|
export function Directory () {
|
||||||
return import(/* webpackChunkName: "features/glitch/async/directory" */'../../directory');
|
return import('../../directory');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function OnboardingProfile () {
|
export function OnboardingProfile () {
|
||||||
return import(/* webpackChunkName: "features/glitch/async/onboarding" */'../../onboarding/profile');
|
return import('../../onboarding/profile');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function OnboardingFollows () {
|
export function OnboardingFollows () {
|
||||||
return import(/* webpackChunkName: "features/glitch/async/onboarding" */'../../onboarding/follows');
|
return import('../../onboarding/follows');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CompareHistoryModal () {
|
export function CompareHistoryModal () {
|
||||||
return import(/*webpackChunkName: "flavours/glitch/async/compare_history_modal" */'../components/compare_history_modal');
|
return import('../components/compare_history_modal');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Explore () {
|
export function Explore () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/explore" */'../../explore');
|
return import('../../explore');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Search () {
|
export function Search () {
|
||||||
return import(/* webpackChunkName: "features/glitch/async/explore" */'../../search');
|
return import('../../search');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FilterModal () {
|
export function FilterModal () {
|
||||||
return import(/*webpackChunkName: "flavours/glitch/async/filter_modal" */'../components/filter_modal');
|
return import('../components/filter_modal');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function InteractionModal () {
|
export function InteractionModal () {
|
||||||
return import(/*webpackChunkName: "flavours/glitch/async/modals/interaction_modal" */'../../interaction_modal');
|
return import('../../interaction_modal');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SubscribedLanguagesModal () {
|
export function SubscribedLanguagesModal () {
|
||||||
return import(/*webpackChunkName: "flavours/glitch/async/modals/subscribed_languages_modal" */'../../subscribed_languages_modal');
|
return import('../../subscribed_languages_modal');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ClosedRegistrationsModal () {
|
export function ClosedRegistrationsModal () {
|
||||||
return import(/*webpackChunkName: "flavours/glitch/async/modals/closed_registrations_modal" */'../../closed_registrations_modal');
|
return import('../../closed_registrations_modal');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function About () {
|
export function About () {
|
||||||
return import(/*webpackChunkName: "features/glitch/async/about" */'../../about');
|
return import('../../about');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PrivacyPolicy () {
|
export function PrivacyPolicy () {
|
||||||
return import(/*webpackChunkName: "features/glitch/async/privacy_policy" */'../../privacy_policy');
|
return import('../../privacy_policy');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TermsOfService () {
|
export function TermsOfService () {
|
||||||
return import(/*webpackChunkName: "features/glitch/async/terms_of_service" */'../../terms_of_service');
|
return import('../../terms_of_service');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NotificationRequests () {
|
export function NotificationRequests () {
|
||||||
return import(/*webpackChunkName: "features/glitch/notifications/requests" */'../../notifications/requests');
|
return import('../../notifications/requests');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NotificationRequest () {
|
export function NotificationRequest () {
|
||||||
return import(/*webpackChunkName: "features/glitch/notifications/request" */'../../notifications/request');
|
return import('../../notifications/request');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function LinkTimeline () {
|
export function LinkTimeline () {
|
||||||
return import(/*webpackChunkName: "features/glitch/link_timeline" */'../../link_timeline');
|
return import('../../link_timeline');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AnnualReportModal () {
|
export function AnnualReportModal () {
|
||||||
return import(/*webpackChunkName: "flavours/glitch/async/modals/annual_report_modal" */'../components/annual_report_modal');
|
return import('../components/annual_report_modal');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ListEdit () {
|
export function ListEdit () {
|
||||||
return import(/*webpackChunkName: "flavours/glitch/async/lists" */'../../lists/new');
|
return import('../../lists/new');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ListMembers () {
|
export function ListMembers () {
|
||||||
return import(/* webpackChunkName: "flavours/glitch/async/lists" */'../../lists/members');
|
return import('../../lists/members');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -350,8 +350,10 @@ export const Video: React.FC<{
|
|||||||
const updateProgress = () => {
|
const updateProgress = () => {
|
||||||
nextFrame = requestAnimationFrame(() => {
|
nextFrame = requestAnimationFrame(() => {
|
||||||
if (videoRef.current) {
|
if (videoRef.current) {
|
||||||
|
const progress =
|
||||||
|
videoRef.current.currentTime / videoRef.current.duration;
|
||||||
void api.start({
|
void api.start({
|
||||||
progress: `${(videoRef.current.currentTime / videoRef.current.duration) * 100}%`,
|
progress: isNaN(progress) ? '0%' : `${progress * 100}%`,
|
||||||
immediate: reduceMotion,
|
immediate: reduceMotion,
|
||||||
config: config.stiff,
|
config: config.stiff,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
// can at least log in using KaiOS devices).
|
// can at least log in using KaiOS devices).
|
||||||
|
|
||||||
function importArrowKeyNavigation() {
|
function importArrowKeyNavigation() {
|
||||||
return import(/* webpackChunkName: "arrow-key-navigation" */ 'arrow-key-navigation');
|
return import('arrow-key-navigation');
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function loadKeyboardExtensions() {
|
export default function loadKeyboardExtensions() {
|
||||||
|
|||||||
@ -5,6 +5,13 @@ import { isLocaleLoaded, setLocale } from './global_locale';
|
|||||||
|
|
||||||
const localeLoadingSemaphore = new Semaphore(1);
|
const localeLoadingSemaphore = new Semaphore(1);
|
||||||
|
|
||||||
|
const upstreamLocaleFiles = import.meta.glob<{ default: LocaleData['messages'] }>([
|
||||||
|
'@/mastodon/locales/*.json',
|
||||||
|
]);
|
||||||
|
const localeFiles = import.meta.glob<{ default: LocaleData['messages'] }>([
|
||||||
|
'./*.json',
|
||||||
|
]);
|
||||||
|
|
||||||
export async function loadLocale() {
|
export async function loadLocale() {
|
||||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- we want to match empty strings
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- we want to match empty strings
|
||||||
const locale = document.querySelector<HTMLElement>('html')?.lang || 'en';
|
const locale = document.querySelector<HTMLElement>('html')?.lang || 'en';
|
||||||
@ -17,21 +24,23 @@ export async function loadLocale() {
|
|||||||
// if the locale is already set, then do nothing
|
// if the locale is already set, then do nothing
|
||||||
if (isLocaleLoaded()) return;
|
if (isLocaleLoaded()) return;
|
||||||
|
|
||||||
const upstreamLocaleData = await import(
|
// If there is no locale file, then fallback to english
|
||||||
/* webpackMode: "lazy" */
|
const upstreamLocaleFile = Object.hasOwn(upstreamLocaleFiles, `@/mastodon/locales/${locale}.json`)
|
||||||
/* webpackChunkName: "locales/vanilla/[request]" */
|
? upstreamLocaleFiles[`/mastodon/locales/${locale}.json`]
|
||||||
/* webpackInclude: /\.json$/ */
|
: upstreamLocaleFiles['/mastodon/locales/en.json'];
|
||||||
/* webpackPreload: true */
|
|
||||||
`mastodon/locales/${locale}.json`
|
|
||||||
) as LocaleData['messages'];
|
|
||||||
|
|
||||||
const localeData = await import(
|
if (!upstreamLocaleFile) throw new Error('Could not load the upstream locale JSON file');
|
||||||
/* webpackMode: "lazy" */
|
|
||||||
/* webpackChunkName: "locales/glitch/[request]" */
|
const { default: upstreamLocaleData } = await upstreamLocaleFile();
|
||||||
/* webpackInclude: /\.json$/ */
|
|
||||||
/* webpackPreload: true */
|
// If there is no locale file, then fallback to english
|
||||||
`flavours/glitch/locales/${locale}.json`
|
const localeFile = Object.hasOwn(localeFiles, `./${locale}.json`)
|
||||||
) as LocaleData['messages'];
|
? localeFiles[`./${locale}.json`]
|
||||||
|
: localeFiles['./en.json'];
|
||||||
|
|
||||||
|
if (!localeFile) throw new Error('Could not load the locale JSON file');
|
||||||
|
|
||||||
|
const { default: localeData } = await localeFile();
|
||||||
|
|
||||||
setLocale({ messages: { ...upstreamLocaleData, ...localeData }, locale });
|
setLocale({ messages: { ...upstreamLocaleData, ...localeData }, locale });
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,17 +7,19 @@ import * as perf from 'flavours/glitch/performance';
|
|||||||
import ready from 'flavours/glitch/ready';
|
import ready from 'flavours/glitch/ready';
|
||||||
import { store } from 'flavours/glitch/store';
|
import { store } from 'flavours/glitch/store';
|
||||||
|
|
||||||
import { isProduction } from './utils/environment';
|
import { isProduction, isDevelopment } from './utils/environment';
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
function main() {
|
function main() {
|
||||||
perf.start('main()');
|
perf.start('main()');
|
||||||
|
|
||||||
return ready(async () => {
|
return ready(async () => {
|
||||||
const mountNode = document.getElementById('mastodon');
|
const mountNode = document.getElementById('mastodon');
|
||||||
const props = JSON.parse(mountNode.getAttribute('data-props'));
|
if (!mountNode) {
|
||||||
|
throw new Error('Mount node not found');
|
||||||
|
}
|
||||||
|
const props = JSON.parse(
|
||||||
|
mountNode.getAttribute('data-props') ?? '{}',
|
||||||
|
) as Record<string, unknown>;
|
||||||
|
|
||||||
const root = createRoot(mountNode);
|
const root = createRoot(mountNode);
|
||||||
root.render(<Mastodon {...props} />);
|
root.render(<Mastodon {...props} />);
|
||||||
@ -25,8 +27,10 @@ function main() {
|
|||||||
|
|
||||||
if (isProduction() && me && 'serviceWorker' in navigator) {
|
if (isProduction() && me && 'serviceWorker' in navigator) {
|
||||||
const { Workbox } = await import('workbox-window');
|
const { Workbox } = await import('workbox-window');
|
||||||
const wb = new Workbox('/sw.js');
|
const wb = new Workbox(
|
||||||
/** @type {ServiceWorkerRegistration} */
|
isDevelopment() ? '/packs-dev/dev-sw.js?dev-sw' : '/sw.js',
|
||||||
|
{ type: 'module', scope: '/' },
|
||||||
|
);
|
||||||
let registration;
|
let registration;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -35,8 +39,14 @@ function main() {
|
|||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (registration && 'Notification' in window && Notification.permission === 'granted') {
|
if (
|
||||||
const registerPushNotifications = await import('flavours/glitch/actions/push_notifications');
|
registration &&
|
||||||
|
'Notification' in window &&
|
||||||
|
Notification.permission === 'granted'
|
||||||
|
) {
|
||||||
|
const registerPushNotifications = await import(
|
||||||
|
'flavours/glitch/actions/push_notifications'
|
||||||
|
);
|
||||||
|
|
||||||
store.dispatch(registerPushNotifications.register());
|
store.dispatch(registerPushNotifications.register());
|
||||||
}
|
}
|
||||||
@ -46,4 +56,5 @@ function main() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-default-export
|
||||||
export default main;
|
export default main;
|
||||||
@ -1,24 +1,11 @@
|
|||||||
//
|
//
|
||||||
// Tools for performance debugging, only enabled in development mode.
|
// Tools for performance debugging, only enabled in development mode.
|
||||||
// Open up Chrome Dev Tools, then Timeline, then User Timing to see output.
|
// Open up Chrome Dev Tools, then Timeline, then User Timing to see output.
|
||||||
// Also see config/webpack/loaders/mark.js for the webpack loader marks.
|
|
||||||
|
|
||||||
import * as marky from 'marky';
|
import * as marky from 'marky';
|
||||||
|
|
||||||
import { isDevelopment } from './utils/environment';
|
import { isDevelopment } from './utils/environment';
|
||||||
|
|
||||||
if (isDevelopment()) {
|
|
||||||
if (typeof performance !== 'undefined' && performance.setResourceTimingBufferSize) {
|
|
||||||
// Increase Firefox's performance entry limit; otherwise it's capped to 150.
|
|
||||||
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1331135
|
|
||||||
performance.setResourceTimingBufferSize(Infinity);
|
|
||||||
}
|
|
||||||
|
|
||||||
// allows us to easily do e.g. ReactPerf.printWasted() while debugging
|
|
||||||
//window.ReactPerf = require('react-addons-perf');
|
|
||||||
//window.ReactPerf.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function start(name) {
|
export function start(name) {
|
||||||
if (isDevelopment()) {
|
if (isDevelopment()) {
|
||||||
marky.mark(name);
|
marky.mark(name);
|
||||||
|
|||||||
@ -2,10 +2,13 @@
|
|||||||
// If there are no polyfills, then this is just Promise.resolve() which means
|
// If there are no polyfills, then this is just Promise.resolve() which means
|
||||||
// it will execute in the same tick of the event loop (i.e. near-instant).
|
// it will execute in the same tick of the event loop (i.e. near-instant).
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/extensions -- This file is virtual so it thinks it has an extension
|
||||||
|
import 'vite/modulepreload-polyfill';
|
||||||
|
|
||||||
import { loadIntlPolyfills } from './intl';
|
import { loadIntlPolyfills } from './intl';
|
||||||
|
|
||||||
function importExtraPolyfills() {
|
function importExtraPolyfills() {
|
||||||
return import(/* webpackChunkName: "extra_polyfills" */ './extra_polyfills');
|
return import('./extra_polyfills');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadPolyfills() {
|
export function loadPolyfills() {
|
||||||
|
|||||||
@ -54,11 +54,9 @@ async function loadIntlPluralRulesPolyfills(locale: string) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Load the polyfill 1st BEFORE loading data
|
// Load the polyfill 1st BEFORE loading data
|
||||||
|
await import('@formatjs/intl-pluralrules/polyfill-force');
|
||||||
await import(
|
await import(
|
||||||
/* webpackChunkName: "i18n-pluralrules-polyfill" */ '@formatjs/intl-pluralrules/polyfill-force'
|
`../../../../../node_modules/@formatjs/intl-pluralrules/locale-data/${unsupportedLocale}.js`
|
||||||
);
|
|
||||||
await import(
|
|
||||||
/* webpackChunkName: "i18n-pluralrules-polyfill-[request]" */ `@formatjs/intl-pluralrules/locale-data/${unsupportedLocale}`
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,11 +68,9 @@ async function loadIntlPluralRulesPolyfills(locale: string) {
|
|||||||
// }
|
// }
|
||||||
// // Load the polyfill 1st BEFORE loading data
|
// // Load the polyfill 1st BEFORE loading data
|
||||||
// await import(
|
// await import(
|
||||||
// /* webpackChunkName: "i18n-relativetimeformat-polyfill" */
|
|
||||||
// '@formatjs/intl-relativetimeformat/polyfill-force'
|
// '@formatjs/intl-relativetimeformat/polyfill-force'
|
||||||
// );
|
// );
|
||||||
// await import(
|
// await import(
|
||||||
// /* webpackChunkName: "i18n-relativetimeformat-polyfill-[request]" */
|
|
||||||
// `@formatjs/intl-relativetimeformat/locale-data/${unsupportedLocale}`
|
// `@formatjs/intl-relativetimeformat/locale-data/${unsupportedLocale}`
|
||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
@use 'functions' as *;
|
@use 'functions' as *;
|
||||||
|
|
||||||
// Commonly used web colors
|
// Commonly used web colors
|
||||||
$black: #000000; // Black
|
$black: #000; // Black
|
||||||
$white: #ffffff; // White
|
$white: #fff; // White
|
||||||
$red-600: #b7253d !default; // Deep Carmine
|
$red-600: #b7253d !default; // Deep Carmine
|
||||||
$red-500: #df405a !default; // Cerise
|
$red-500: #df405a !default; // Cerise
|
||||||
$blurple-600: #563acc; // Iris
|
$blurple-600: #563acc; // Iris
|
||||||
|
|||||||
@ -114,15 +114,14 @@ $content-width: 840px;
|
|||||||
|
|
||||||
a {
|
a {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
display: block;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
color: $darker-text-color;
|
color: $darker-text-color;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: all 200ms linear;
|
transition: all 200ms linear;
|
||||||
transition-property: color, background-color;
|
transition-property: color, background-color;
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $primary-text-color;
|
color: $primary-text-color;
|
||||||
@ -1126,7 +1125,7 @@ a.name-tag,
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
word-break: break-word;
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__permissions {
|
&__permissions {
|
||||||
@ -1149,6 +1148,15 @@ a.name-tag,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rule-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
a.table-action-link {
|
||||||
|
padding-inline-start: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.dashboard__counters.admin-account-counters {
|
.dashboard__counters.admin-account-counters {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
@ -1451,8 +1459,8 @@ a.sparkline {
|
|||||||
inset-inline-start: 10px;
|
inset-inline-start: 10px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background:
|
background:
|
||||||
url('~images/warning-stripes.svg') repeat-y left,
|
url('@/images/warning-stripes.svg') repeat-y left,
|
||||||
url('~images/warning-stripes.svg') repeat-y right,
|
url('@/images/warning-stripes.svg') repeat-y right,
|
||||||
var(--background-color);
|
var(--background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
@use 'mixins';
|
@use 'mixins';
|
||||||
@use 'variables';
|
@use 'variables';
|
||||||
@use 'css_variables';
|
@use 'css_variables';
|
||||||
@use 'styles/fonts/roboto';
|
@use '@/styles/fonts/roboto';
|
||||||
@use 'styles/fonts/roboto-mono';
|
@use '@/styles/fonts/roboto-mono';
|
||||||
|
|
||||||
@use 'reset';
|
@use 'reset';
|
||||||
@use 'basics';
|
@use 'basics';
|
||||||
|
|||||||
@ -641,7 +641,7 @@ body > [data-popper-placement] {
|
|||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
|
||||||
&__border {
|
&__border {
|
||||||
background: url('~images/warning-stripes.svg') repeat-y;
|
background: url('@/images/warning-stripes.svg') repeat-y;
|
||||||
width: 5px;
|
width: 5px;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|
||||||
@ -1480,6 +1480,10 @@ body > [data-popper-placement] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status--has-quote .quote-inline {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
padding: 10px 14px; // glitch: reduced padding
|
padding: 10px 14px; // glitch: reduced padding
|
||||||
min-height: 54px;
|
min-height: 54px;
|
||||||
@ -1550,8 +1554,12 @@ body > [data-popper-placement] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--is-quote {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
&--in-thread {
|
&--in-thread {
|
||||||
$thread-margin: 46px + 10px;
|
--thread-margin: calc(46px + 8px);
|
||||||
|
|
||||||
border-bottom: 0;
|
border-bottom: 0;
|
||||||
|
|
||||||
@ -1567,16 +1575,16 @@ body > [data-popper-placement] {
|
|||||||
.hashtag-bar,
|
.hashtag-bar,
|
||||||
.content-warning,
|
.content-warning,
|
||||||
.filter-warning {
|
.filter-warning {
|
||||||
margin-inline-start: $thread-margin;
|
margin-inline-start: var(--thread-margin);
|
||||||
width: calc(100% - $thread-margin);
|
width: calc(100% - var(--thread-margin));
|
||||||
}
|
}
|
||||||
|
|
||||||
.more-from-author {
|
.more-from-author {
|
||||||
width: calc(100% - $thread-margin + 2px);
|
width: calc(100% - var(--thread-margin) + 2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.status__content__read-more-button {
|
.status__content__read-more-button {
|
||||||
margin-inline-start: $thread-margin;
|
margin-inline-start: var(--thread-margin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1939,6 +1947,81 @@ body > [data-popper-placement] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status__quote {
|
||||||
|
position: relative;
|
||||||
|
margin-block-start: 16px;
|
||||||
|
margin-inline-start: 36px;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--nested-card-text);
|
||||||
|
background: var(--nested-card-background);
|
||||||
|
border: var(--nested-card-border);
|
||||||
|
|
||||||
|
@media screen and (min-width: $mobile-breakpoint) {
|
||||||
|
margin-inline-start: 56px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.status__quote--error {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status__quote-author-button {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
display: inline-flex;
|
||||||
|
width: auto;
|
||||||
|
margin-block-start: 10px;
|
||||||
|
padding: 5px 12px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: normal;
|
||||||
|
letter-spacing: 0;
|
||||||
|
text-decoration: none;
|
||||||
|
color: $highlight-text-color;
|
||||||
|
background: var(--nested-card-background);
|
||||||
|
border: var(--nested-card-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
&:active,
|
||||||
|
&:focus,
|
||||||
|
&:hover {
|
||||||
|
border-color: lighten($highlight-text-color, 4%);
|
||||||
|
color: lighten($highlight-text-color, 4%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-visible {
|
||||||
|
outline: $ui-button-icon-focus-outline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.status__quote-icon {
|
||||||
|
position: absolute;
|
||||||
|
inset-block-start: 18px;
|
||||||
|
inset-inline-start: -40px;
|
||||||
|
display: block;
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
padding: 5px;
|
||||||
|
color: #6a49ba;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
.status__quote--error & {
|
||||||
|
inset-block-start: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: $mobile-breakpoint) {
|
||||||
|
inset-inline-start: -50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.detailed-status__link {
|
.detailed-status__link {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -2235,14 +2318,18 @@ a .account__avatar {
|
|||||||
|
|
||||||
.avatar-group {
|
.avatar-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
--avatar-height: 28px;
|
||||||
|
|
||||||
|
&:not(.avatar-group--compact) {
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
height: var(--avatar-height);
|
||||||
|
overflow-y: clip;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-group--compact {
|
.avatar-group--compact {
|
||||||
gap: 0;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
|
|
||||||
& > :not(:first-child) {
|
& > :not(:first-child) {
|
||||||
margin-inline-start: -12px;
|
margin-inline-start: -12px;
|
||||||
}
|
}
|
||||||
@ -2371,11 +2458,6 @@ a.account__display-name {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.status__avatar {
|
|
||||||
width: 46px;
|
|
||||||
height: 46px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.muted {
|
.muted {
|
||||||
.status__content,
|
.status__content,
|
||||||
.status__content p,
|
.status__content p,
|
||||||
@ -10643,7 +10725,8 @@ noscript {
|
|||||||
padding: 15px;
|
padding: 15px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
flex-wrap: wrap;
|
||||||
|
gap: 4px 8px;
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
@ -10737,12 +10820,6 @@ noscript {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
container-type: inline-size;
|
container-type: inline-size;
|
||||||
|
|
||||||
@container (width < 350px) {
|
|
||||||
&__header time {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__header {
|
&__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -10755,7 +10832,8 @@ noscript {
|
|||||||
|
|
||||||
&__label {
|
&__label {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
flex-wrap: wrap;
|
||||||
|
gap: 2px 8px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
color: $darker-text-color;
|
color: $darker-text-color;
|
||||||
@ -10773,6 +10851,13 @@ noscript {
|
|||||||
time {
|
time {
|
||||||
color: $dark-text-color;
|
color: $dark-text-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@container (width < 350px) {
|
||||||
|
time,
|
||||||
|
&-separator {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -10830,6 +10915,7 @@ noscript {
|
|||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
color: $darker-text-color;
|
color: $darker-text-color;
|
||||||
-webkit-line-clamp: 4;
|
-webkit-line-clamp: 4;
|
||||||
|
line-clamp: 4;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
max-height: none;
|
max-height: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -10984,7 +11070,15 @@ noscript {
|
|||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__number {
|
&__numbers,
|
||||||
|
&__familiar-followers {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__numbers {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
color: $secondary-text-color;
|
color: $secondary-text-color;
|
||||||
@ -11132,9 +11226,9 @@ noscript {
|
|||||||
.content-warning {
|
.content-warning {
|
||||||
display: block;
|
display: block;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background: rgba($ui-highlight-color, 0.05);
|
background: var(--nested-card-background);
|
||||||
color: $secondary-text-color;
|
color: var(--nested-card-text);
|
||||||
border: 1px solid rgba($ui-highlight-color, 0.15);
|
border: var(--nested-card-border);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 8px (5px + 8px);
|
padding: 8px (5px + 8px);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
@use '../functions' as *;
|
@use '../functions' as *;
|
||||||
|
|
||||||
// Dependent colors
|
// Dependent colors
|
||||||
$black: #000000;
|
$black: #000;
|
||||||
|
|
||||||
$classic-base-color: hsl(240deg, 16%, 19%);
|
$classic-base-color: hsl(240deg, 16%, 19%);
|
||||||
$classic-primary-color: hsl(240deg, 29%, 70%);
|
$classic-primary-color: hsl(240deg, 29%, 70%);
|
||||||
|
|||||||
@ -27,6 +27,10 @@
|
|||||||
--rich-text-container-color: rgba(87, 24, 60, 100%);
|
--rich-text-container-color: rgba(87, 24, 60, 100%);
|
||||||
--rich-text-text-color: rgba(255, 175, 212, 100%);
|
--rich-text-text-color: rgba(255, 175, 212, 100%);
|
||||||
--rich-text-decorations-color: rgba(128, 58, 95, 100%);
|
--rich-text-decorations-color: rgba(128, 58, 95, 100%);
|
||||||
|
--nested-card-background: color(from #{$ui-highlight-color} srgb r g b / 5%);
|
||||||
|
--nested-card-text: #{$secondary-text-color};
|
||||||
|
--nested-card-border: 1px solid
|
||||||
|
color(from #{$ui-highlight-color} srgb r g b / 15%);
|
||||||
--input-placeholder-color: #{$dark-text-color};
|
--input-placeholder-color: #{$dark-text-color};
|
||||||
--input-background-color: var(--surface-variant-background-color);
|
--input-background-color: var(--surface-variant-background-color);
|
||||||
--on-input-color: #{$secondary-text-color};
|
--on-input-color: #{$secondary-text-color};
|
||||||
|
|||||||
@ -394,7 +394,7 @@ code {
|
|||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
border-radius: var(--avatar-border-radius);
|
border-radius: var(--avatar-border-radius);
|
||||||
background: url('images/void.png');
|
background: url('@/images/void.png');
|
||||||
|
|
||||||
&[src$='missing.png'] {
|
&[src$='missing.png'] {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
@ -1156,7 +1156,7 @@ code {
|
|||||||
|
|
||||||
&__type {
|
&__type {
|
||||||
color: $darker-text-color;
|
color: $darker-text-color;
|
||||||
word-break: break-word;
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,8 +5,8 @@
|
|||||||
$lighten-multiplier: -1
|
$lighten-multiplier: -1
|
||||||
);
|
);
|
||||||
|
|
||||||
$black: #000000; // Black
|
$black: #000; // Black
|
||||||
$white: #ffffff; // White
|
$white: #fff; // White
|
||||||
$blurple-500: #6364ff; // Brand purple
|
$blurple-500: #6364ff; // Brand purple
|
||||||
$grey-600: hsl(240deg, 8%, 33%); // Trout
|
$grey-600: hsl(240deg, 8%, 33%); // Trout
|
||||||
$grey-100: hsl(240deg, 51%, 90%); // Topaz
|
$grey-100: hsl(240deg, 51%, 90%); // Topaz
|
||||||
|
|||||||
@ -44,7 +44,7 @@
|
|||||||
content: '';
|
content: '';
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
mask-image: url('~images/quote.svg');
|
mask-image: url('@/images/quote.svg');
|
||||||
background-color: var(--rich-text-decorations-color);
|
background-color: var(--rich-text-decorations-color);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset-inline-start: 0;
|
inset-inline-start: 0;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user