Merge remote-tracking branch 'upstream/main'
This commit is contained in:
commit
5fdd18ead1
423 changed files with 6532 additions and 3565 deletions
|
@ -70,7 +70,7 @@ services:
|
|||
hard: -1
|
||||
|
||||
libretranslate:
|
||||
image: libretranslate/libretranslate:v1.4.1
|
||||
image: libretranslate/libretranslate:v1.5.2
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- lt-data:/home/libretranslate/.local
|
||||
|
|
2
.github/actions/setup-javascript/action.yml
vendored
2
.github/actions/setup-javascript/action.yml
vendored
|
@ -9,7 +9,7 @@ runs:
|
|||
using: 'composite'
|
||||
steps:
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
|
|
13
.github/codecov.yml
vendored
Normal file
13
.github/codecov.yml
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
# Github status check is not blocking
|
||||
informational: true
|
||||
patch:
|
||||
default:
|
||||
# Github status check is not blocking
|
||||
informational: true
|
||||
comment:
|
||||
# Only write a comment in PR if there are changes
|
||||
require_changes: true
|
1
.github/renovate.json5
vendored
1
.github/renovate.json5
vendored
|
@ -22,6 +22,7 @@
|
|||
'react-hotkeys', // Requires code changes
|
||||
|
||||
// Requires Webpacker upgrade or replacement
|
||||
'@svgr/webpack',
|
||||
'@types/webpack',
|
||||
'babel-loader',
|
||||
'compression-webpack-plugin',
|
||||
|
|
12
.github/workflows/test-ruby.yml
vendored
12
.github/workflows/test-ruby.yml
vendored
|
@ -94,7 +94,7 @@ jobs:
|
|||
DB_HOST: localhost
|
||||
DB_USER: postgres
|
||||
DB_PASS: postgres
|
||||
DISABLE_SIMPLECOV: true
|
||||
DISABLE_SIMPLECOV: ${{ matrix.ruby-version != '.ruby-version' }}
|
||||
RAILS_ENV: test
|
||||
ALLOW_NOPAM: true
|
||||
PAM_ENABLED: true
|
||||
|
@ -137,6 +137,12 @@ jobs:
|
|||
|
||||
- run: bin/rspec
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
if: matrix.ruby-version == '.ruby-version'
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
files: coverage/lcov/mastodon.lcov
|
||||
|
||||
test-e2e:
|
||||
name: End to End testing
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -221,7 +227,7 @@ jobs:
|
|||
path: tmp/screenshots/
|
||||
|
||||
test-search:
|
||||
name: Testing search
|
||||
name: Elastic Search integration testing
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
needs:
|
||||
|
@ -308,7 +314,7 @@ jobs:
|
|||
- name: Load database schema
|
||||
run: './bin/rails db:create db:schema:load db:seed'
|
||||
|
||||
- run: bundle exec rake spec:search
|
||||
- run: bin/rspec --tag search
|
||||
|
||||
- name: Archive logs
|
||||
uses: actions/upload-artifact@v3
|
||||
|
|
22
.simplecov
Normal file
22
.simplecov
Normal file
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
if ENV['CI']
|
||||
require 'simplecov-lcov'
|
||||
SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true
|
||||
SimpleCov.formatter = SimpleCov::Formatter::LcovFormatter
|
||||
else
|
||||
SimpleCov.formatter = SimpleCov::Formatter::HTMLFormatter
|
||||
end
|
||||
|
||||
SimpleCov.start 'rails' do
|
||||
enable_coverage :branch
|
||||
|
||||
add_filter 'lib/linter'
|
||||
|
||||
add_group 'Libraries', 'lib'
|
||||
add_group 'Policies', 'app/policies'
|
||||
add_group 'Presenters', 'app/presenters'
|
||||
add_group 'Serializers', 'app/serializers'
|
||||
add_group 'Services', 'app/services'
|
||||
add_group 'Validators', 'app/validators'
|
||||
end
|
|
@ -38,6 +38,7 @@ RUN apt-get update && \
|
|||
corepack enable
|
||||
|
||||
COPY Gemfile* package.json yarn.lock .yarnrc.yml /opt/mastodon/
|
||||
COPY streaming/package.json /opt/mastodon/streaming/
|
||||
COPY .yarn /opt/mastodon/.yarn
|
||||
|
||||
RUN bundle install -j"$(nproc)"
|
||||
|
|
10
Gemfile
10
Gemfile
|
@ -9,6 +9,9 @@ gem 'sprockets', '~> 3.7.2'
|
|||
gem 'thor', '~> 1.2'
|
||||
gem 'rack', '~> 2.2.7'
|
||||
|
||||
# For why irb is in the Gemfile, see: https://ruby.social/@st0012/111444685161478182
|
||||
gem 'irb', '~> 1.8'
|
||||
|
||||
gem 'haml-rails', '~>2.0'
|
||||
gem 'pg', '~> 1.5'
|
||||
gem 'pghero'
|
||||
|
@ -109,6 +112,9 @@ group :test do
|
|||
# RSpec progress bar formatter
|
||||
gem 'fuubar', '~> 2.5'
|
||||
|
||||
# RSpec helpers for email specs
|
||||
gem 'email_spec'
|
||||
|
||||
# Extra RSpec extenion methods and helpers for sidekiq
|
||||
gem 'rspec-sidekiq', '~> 4.0'
|
||||
|
||||
|
@ -139,6 +145,7 @@ group :test do
|
|||
|
||||
# Coverage formatter for RSpec test if DISABLE_SIMPLECOV is false
|
||||
gem 'simplecov', '~> 0.22', require: false
|
||||
gem 'simplecov-lcov', '~> 0.8', require: false
|
||||
|
||||
# Stub web requests for specs
|
||||
gem 'webmock', '~> 3.18'
|
||||
|
@ -175,6 +182,9 @@ group :development do
|
|||
end
|
||||
|
||||
group :development, :test do
|
||||
# Interactive Debugging tools
|
||||
gem 'debug', '~> 1.8'
|
||||
|
||||
# Profiling tools
|
||||
gem 'memory_profiler', require: false
|
||||
gem 'ruby-prof', require: false
|
||||
|
|
59
Gemfile.lock
59
Gemfile.lock
|
@ -130,21 +130,21 @@ GEM
|
|||
encryptor (~> 3.0.0)
|
||||
attr_required (1.0.1)
|
||||
awrence (1.2.1)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.828.0)
|
||||
aws-sdk-core (3.183.1)
|
||||
aws-eventstream (1.3.0)
|
||||
aws-partitions (1.857.0)
|
||||
aws-sdk-core (3.188.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.71.0)
|
||||
aws-sdk-core (~> 3, >= 3.177.0)
|
||||
aws-sdk-kms (1.73.0)
|
||||
aws-sdk-core (~> 3, >= 3.188.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.136.0)
|
||||
aws-sdk-core (~> 3, >= 3.181.0)
|
||||
aws-sdk-s3 (1.140.0)
|
||||
aws-sdk-core (~> 3, >= 3.188.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.6)
|
||||
aws-sigv4 (1.6.0)
|
||||
aws-sigv4 (1.7.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
azure-storage-blob (2.0.3)
|
||||
azure-storage-common (~> 2.0)
|
||||
|
@ -154,7 +154,7 @@ GEM
|
|||
faraday_middleware (~> 1.0, >= 1.0.0.rc1)
|
||||
net-http-persistent (~> 4.0)
|
||||
nokogiri (~> 1, >= 1.10.8)
|
||||
base64 (0.1.1)
|
||||
base64 (0.2.0)
|
||||
bcp47_spec (0.2.1)
|
||||
bcrypt (3.1.19)
|
||||
better_errors (2.10.1)
|
||||
|
@ -220,6 +220,9 @@ GEM
|
|||
database_cleaner-core (~> 2.0.0)
|
||||
database_cleaner-core (2.0.1)
|
||||
date (3.3.4)
|
||||
debug (1.8.0)
|
||||
irb (>= 1.5.0)
|
||||
reline (>= 0.3.1)
|
||||
debug_inspector (1.1.0)
|
||||
devise (4.9.3)
|
||||
bcrypt (~> 3.0)
|
||||
|
@ -242,13 +245,13 @@ GEM
|
|||
docile (1.4.0)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
doorkeeper (5.6.6)
|
||||
doorkeeper (5.6.7)
|
||||
railties (>= 5)
|
||||
dotenv (2.8.1)
|
||||
dotenv-rails (2.8.1)
|
||||
dotenv (= 2.8.1)
|
||||
railties (>= 3.2)
|
||||
drb (2.1.1)
|
||||
drb (2.2.0)
|
||||
ruby2_keywords
|
||||
ed25519 (1.3.0)
|
||||
elasticsearch (7.13.3)
|
||||
|
@ -260,12 +263,16 @@ GEM
|
|||
elasticsearch-transport (7.13.3)
|
||||
faraday (~> 1)
|
||||
multi_json
|
||||
email_spec (2.2.2)
|
||||
htmlentities (~> 4.3.3)
|
||||
launchy (~> 2.1)
|
||||
mail (~> 2.7)
|
||||
encryptor (3.0.0)
|
||||
erubi (1.12.0)
|
||||
et-orbi (1.2.7)
|
||||
tzinfo
|
||||
excon (0.104.0)
|
||||
fabrication (2.30.0)
|
||||
fabrication (2.31.0)
|
||||
faker (3.2.2)
|
||||
i18n (>= 1.8.11, < 2)
|
||||
faraday (1.10.3)
|
||||
|
@ -370,7 +377,7 @@ GEM
|
|||
terminal-table (>= 1.5.1)
|
||||
idn-ruby (0.1.5)
|
||||
io-console (0.6.0)
|
||||
irb (1.8.3)
|
||||
irb (1.9.1)
|
||||
rdoc
|
||||
reline (>= 0.3.8)
|
||||
jmespath (1.6.2)
|
||||
|
@ -432,7 +439,7 @@ GEM
|
|||
activesupport (>= 4)
|
||||
railties (>= 4)
|
||||
request_store (~> 1.0)
|
||||
loofah (2.21.4)
|
||||
loofah (2.22.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.12.0)
|
||||
mail (2.8.1)
|
||||
|
@ -458,7 +465,7 @@ GEM
|
|||
msgpack (1.7.2)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.3.0)
|
||||
mutex_m (0.1.2)
|
||||
mutex_m (0.2.0)
|
||||
net-http (0.4.0)
|
||||
uri
|
||||
net-http-persistent (4.0.2)
|
||||
|
@ -474,7 +481,7 @@ GEM
|
|||
net-smtp (0.4.0)
|
||||
net-protocol
|
||||
nio4r (2.5.9)
|
||||
nokogiri (1.15.4)
|
||||
nokogiri (1.15.5)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
oj (3.16.1)
|
||||
|
@ -529,7 +536,7 @@ GEM
|
|||
private_address_check (0.5.0)
|
||||
psych (5.1.1.1)
|
||||
stringio
|
||||
public_suffix (5.0.3)
|
||||
public_suffix (5.0.4)
|
||||
puma (6.4.0)
|
||||
nio4r (~> 2.0)
|
||||
pundit (2.3.1)
|
||||
|
@ -595,13 +602,13 @@ GEM
|
|||
thor (~> 1.0, >= 1.2.2)
|
||||
zeitwerk (~> 2.6)
|
||||
rainbow (3.1.1)
|
||||
rake (13.0.6)
|
||||
rake (13.1.0)
|
||||
rdf (3.3.1)
|
||||
bcp47_spec (~> 0.2)
|
||||
link_header (~> 0.0, >= 0.0.8)
|
||||
rdf-normalize (0.6.1)
|
||||
rdf (~> 3.2)
|
||||
rdoc (6.5.0)
|
||||
rdoc (6.6.0)
|
||||
psych (>= 4.0.0)
|
||||
redcarpet (3.6.0)
|
||||
redis (4.8.1)
|
||||
|
@ -610,7 +617,7 @@ GEM
|
|||
redlock (1.3.2)
|
||||
redis (>= 3.0.0, < 6.0)
|
||||
regexp_parser (2.8.2)
|
||||
reline (0.3.9)
|
||||
reline (0.4.0)
|
||||
io-console (~> 0.5)
|
||||
request_store (1.5.1)
|
||||
rack (>= 1.4)
|
||||
|
@ -635,7 +642,7 @@ GEM
|
|||
rspec-mocks (3.12.6)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.12.0)
|
||||
rspec-rails (6.0.3)
|
||||
rspec-rails (6.1.0)
|
||||
actionpack (>= 6.1)
|
||||
activesupport (>= 6.1)
|
||||
railties (>= 6.1)
|
||||
|
@ -669,10 +676,11 @@ GEM
|
|||
rubocop-performance (1.19.1)
|
||||
rubocop (>= 1.7.0, < 2.0)
|
||||
rubocop-ast (>= 0.4.0)
|
||||
rubocop-rails (2.22.1)
|
||||
rubocop-rails (2.22.2)
|
||||
activesupport (>= 4.2.0)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 1.33.0, < 2.0)
|
||||
rubocop-ast (>= 1.30.0, < 2.0)
|
||||
rubocop-rspec (2.25.0)
|
||||
rubocop (~> 1.40)
|
||||
rubocop-capybara (~> 2.17)
|
||||
|
@ -725,6 +733,7 @@ GEM
|
|||
simplecov-html (~> 0.11)
|
||||
simplecov_json_formatter (~> 0.1)
|
||||
simplecov-html (0.12.3)
|
||||
simplecov-lcov (0.8.0)
|
||||
simplecov_json_formatter (0.1.4)
|
||||
smart_properties (1.17.0)
|
||||
sprockets (3.7.2)
|
||||
|
@ -751,7 +760,7 @@ GEM
|
|||
unicode-display_width (>= 1.1.1, < 3)
|
||||
terrapin (0.6.0)
|
||||
climate_control (>= 0.0.3, < 1.0)
|
||||
test-prof (1.2.3)
|
||||
test-prof (1.3.0)
|
||||
thor (1.3.0)
|
||||
tilt (2.3.0)
|
||||
timeout (0.4.1)
|
||||
|
@ -845,6 +854,7 @@ DEPENDENCIES
|
|||
concurrent-ruby
|
||||
connection_pool
|
||||
database_cleaner-active_record
|
||||
debug (~> 1.8)
|
||||
devise (~> 4.9)
|
||||
devise-two-factor (~> 4.1)
|
||||
devise_pam_authenticatable2 (~> 9.2)
|
||||
|
@ -852,6 +862,7 @@ DEPENDENCIES
|
|||
doorkeeper (~> 5.6)
|
||||
dotenv-rails (~> 2.8)
|
||||
ed25519 (~> 1.3)
|
||||
email_spec
|
||||
fabrication (~> 2.30)
|
||||
faker (~> 3.2)
|
||||
fast_blank (~> 1.0)
|
||||
|
@ -869,6 +880,7 @@ DEPENDENCIES
|
|||
httplog (~> 1.6.2)
|
||||
i18n-tasks (~> 1.0)
|
||||
idn-ruby
|
||||
irb (~> 1.8)
|
||||
json-ld
|
||||
json-ld-preloaded (~> 3.2)
|
||||
json-schema (~> 4.0)
|
||||
|
@ -936,6 +948,7 @@ DEPENDENCIES
|
|||
simple-navigation (~> 4.4)
|
||||
simple_form (~> 5.2)
|
||||
simplecov (~> 0.22)
|
||||
simplecov-lcov (~> 0.8)
|
||||
sprockets (~> 3.7.2)
|
||||
sprockets-rails (~> 3.4)
|
||||
stackprof
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
web: env PORT=3000 RAILS_ENV=development bundle exec puma -C config/puma.rb
|
||||
sidekiq: env PORT=3000 RAILS_ENV=development bundle exec sidekiq
|
||||
stream: env PORT=4000 yarn run start
|
||||
stream: env PORT=4000 yarn workspace @mastodon/streaming start
|
||||
webpack: bin/webpack-dev-server
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AccountsIndex < Chewy::Index
|
||||
include DatetimeClampingConcern
|
||||
|
||||
settings index: index_preset(refresh_interval: '30s'), analysis: {
|
||||
filter: {
|
||||
english_stop: {
|
||||
|
@ -60,7 +62,7 @@ class AccountsIndex < Chewy::Index
|
|||
field(:following_count, type: 'long')
|
||||
field(:followers_count, type: 'long')
|
||||
field(:properties, type: 'keyword', value: ->(account) { account.searchable_properties })
|
||||
field(:last_status_at, type: 'date', value: ->(account) { account.last_status_at || account.created_at })
|
||||
field(:last_status_at, type: 'date', value: ->(account) { clamp_date(account.last_status_at || account.created_at) })
|
||||
field(:display_name, type: 'text', analyzer: 'verbatim') { field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'verbatim' }
|
||||
field(:username, type: 'text', analyzer: 'verbatim', value: ->(account) { [account.username, account.domain].compact.join('@') }) { field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'verbatim' }
|
||||
field(:text, type: 'text', analyzer: 'verbatim', value: ->(account) { account.searchable_text }) { field :stemmed, type: 'text', analyzer: 'natural' }
|
||||
|
|
14
app/chewy/concerns/datetime_clamping_concern.rb
Normal file
14
app/chewy/concerns/datetime_clamping_concern.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DatetimeClampingConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
MIN_ISO8601_DATETIME = '0000-01-01T00:00:00Z'.to_datetime.freeze
|
||||
MAX_ISO8601_DATETIME = '9999-12-31T23:59:59Z'.to_datetime.freeze
|
||||
|
||||
class_methods do
|
||||
def clamp_date(datetime)
|
||||
datetime.clamp(MIN_ISO8601_DATETIME, MAX_ISO8601_DATETIME)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class PublicStatusesIndex < Chewy::Index
|
||||
include DatetimeClampingConcern
|
||||
|
||||
settings index: index_preset(refresh_interval: '30s', number_of_shards: 5), analysis: {
|
||||
filter: {
|
||||
english_stop: {
|
||||
|
@ -62,6 +64,6 @@ class PublicStatusesIndex < Chewy::Index
|
|||
field(:tags, type: 'text', analyzer: 'hashtag', value: ->(status) { status.tags.map(&:display_name) })
|
||||
field(:language, type: 'keyword')
|
||||
field(:properties, type: 'keyword', value: ->(status) { status.searchable_properties })
|
||||
field(:created_at, type: 'date')
|
||||
field(:created_at, type: 'date', value: ->(status) { clamp_date(status.created_at) })
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class StatusesIndex < Chewy::Index
|
||||
include DatetimeClampingConcern
|
||||
|
||||
settings index: index_preset(refresh_interval: '30s', number_of_shards: 5), analysis: {
|
||||
filter: {
|
||||
english_stop: {
|
||||
|
@ -60,6 +62,6 @@ class StatusesIndex < Chewy::Index
|
|||
field(:searchable_by, type: 'long', value: ->(status) { status.searchable_by })
|
||||
field(:language, type: 'keyword')
|
||||
field(:properties, type: 'keyword', value: ->(status) { status.searchable_properties })
|
||||
field(:created_at, type: 'date')
|
||||
field(:created_at, type: 'date', value: ->(status) { clamp_date(status.created_at) })
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class TagsIndex < Chewy::Index
|
||||
include DatetimeClampingConcern
|
||||
|
||||
settings index: index_preset(refresh_interval: '30s'), analysis: {
|
||||
analyzer: {
|
||||
content: {
|
||||
|
@ -42,6 +44,6 @@ class TagsIndex < Chewy::Index
|
|||
field(:name, type: 'text', analyzer: 'content', value: :display_name) { field(:edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content') }
|
||||
field(:reviewed, type: 'boolean', value: ->(tag) { tag.reviewed? })
|
||||
field(:usage, type: 'long', value: ->(tag, crutches) { tag.history.aggregate(crutches.time_period).accounts })
|
||||
field(:last_status_at, type: 'date', value: ->(tag) { tag.last_status_at || tag.created_at })
|
||||
field(:last_status_at, type: 'date', value: ->(tag) { clamp_date(tag.last_status_at || tag.created_at) })
|
||||
end
|
||||
end
|
||||
|
|
|
@ -18,8 +18,6 @@ class AccountsController < ApplicationController
|
|||
respond_to do |format|
|
||||
format.html do
|
||||
expires_in(15.seconds, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.hour) unless user_signed_in?
|
||||
|
||||
@rss_url = rss_url
|
||||
end
|
||||
|
||||
format.rss do
|
||||
|
@ -84,29 +82,21 @@ class AccountsController < ApplicationController
|
|||
short_account_url(@account, format: 'rss')
|
||||
end
|
||||
end
|
||||
helper_method :rss_url
|
||||
|
||||
def media_requested?
|
||||
request.path.split('.').first.end_with?('/media') && !tag_requested?
|
||||
path_without_format.end_with?('/media') && !tag_requested?
|
||||
end
|
||||
|
||||
def replies_requested?
|
||||
request.path.split('.').first.end_with?('/with_replies') && !tag_requested?
|
||||
path_without_format.end_with?('/with_replies') && !tag_requested?
|
||||
end
|
||||
|
||||
def tag_requested?
|
||||
request.path.split('.').first.end_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
|
||||
path_without_format.end_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
|
||||
end
|
||||
|
||||
def cached_filtered_status_page
|
||||
cache_collection_paginated_by_id(
|
||||
filtered_statuses,
|
||||
Status,
|
||||
PAGE_SIZE,
|
||||
params_slice(:max_id, :min_id, :since_id)
|
||||
)
|
||||
end
|
||||
|
||||
def params_slice(*keys)
|
||||
params.slice(*keys).permit(*keys)
|
||||
def path_without_format
|
||||
request.path.split('.').first
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,7 +32,7 @@ module Admin
|
|||
private
|
||||
|
||||
def batched_ordered_status_edits
|
||||
@status.edits.reorder(nil).includes(:account, status: [:account]).find_each(order: :asc)
|
||||
@status.edits.includes(:account, status: [:account]).find_each(order: :asc)
|
||||
end
|
||||
helper_method :batched_ordered_status_edits
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
|
|||
current_user.update(user_params) if user_params
|
||||
ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
|
||||
render json: @account, serializer: REST::CredentialAccountSerializer
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
render json: ValidationErrorFormatter.new(e).as_json, status: 422
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -12,7 +12,7 @@ class Api::V1::Accounts::FamiliarFollowersController < Api::BaseController
|
|||
private
|
||||
|
||||
def set_accounts
|
||||
@accounts = Account.without_suspended.where(id: account_ids).select('id, hide_collections').index_by(&:id).values_at(*account_ids).compact
|
||||
@accounts = Account.without_suspended.where(id: account_ids).select('id, hide_collections')
|
||||
end
|
||||
|
||||
def familiar_followers
|
||||
|
|
|
@ -5,11 +5,8 @@ class Api::V1::Accounts::RelationshipsController < Api::BaseController
|
|||
before_action :require_user!
|
||||
|
||||
def index
|
||||
scope = Account.where(id: account_ids).select('id')
|
||||
scope.merge!(Account.without_suspended) unless truthy_param?(:with_suspended)
|
||||
# .where doesn't guarantee that our results are in the same order
|
||||
# we requested them, so return the "right" order to the requestor.
|
||||
@accounts = scope.index_by(&:id).values_at(*account_ids).compact
|
||||
@accounts = Account.where(id: account_ids).select('id')
|
||||
@accounts.merge!(Account.without_suspended) unless truthy_param?(:with_suspended)
|
||||
render json: @accounts, each_serializer: REST::RelationshipSerializer, relationships: relationships
|
||||
end
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
class Api::V1::Instances::ActivityController < Api::V1::Instances::BaseController
|
||||
before_action :require_enabled_api!
|
||||
|
||||
WEEKS_OF_ACTIVITY = 12
|
||||
|
||||
def show
|
||||
cache_even_if_authenticated!
|
||||
render_with_cache json: :activity, expires_in: 1.day
|
||||
|
@ -11,23 +13,40 @@ class Api::V1::Instances::ActivityController < Api::V1::Instances::BaseControlle
|
|||
private
|
||||
|
||||
def activity
|
||||
statuses_tracker = ActivityTracker.new('activity:statuses:local', :basic)
|
||||
logins_tracker = ActivityTracker.new('activity:logins', :unique)
|
||||
registrations_tracker = ActivityTracker.new('activity:accounts:local', :basic)
|
||||
|
||||
(0...12).map do |i|
|
||||
start_of_week = i.weeks.ago
|
||||
end_of_week = start_of_week + 6.days
|
||||
|
||||
{
|
||||
week: start_of_week.to_i.to_s,
|
||||
statuses: statuses_tracker.sum(start_of_week, end_of_week).to_s,
|
||||
logins: logins_tracker.sum(start_of_week, end_of_week).to_s,
|
||||
registrations: registrations_tracker.sum(start_of_week, end_of_week).to_s,
|
||||
}
|
||||
activity_weeks.map do |weeks_ago|
|
||||
activity_json(*week_edge_days(weeks_ago))
|
||||
end
|
||||
end
|
||||
|
||||
def activity_json(start_of_week, end_of_week)
|
||||
{
|
||||
week: start_of_week.to_i.to_s,
|
||||
statuses: statuses_tracker.sum(start_of_week, end_of_week).to_s,
|
||||
logins: logins_tracker.sum(start_of_week, end_of_week).to_s,
|
||||
registrations: registrations_tracker.sum(start_of_week, end_of_week).to_s,
|
||||
}
|
||||
end
|
||||
|
||||
def activity_weeks
|
||||
0...WEEKS_OF_ACTIVITY
|
||||
end
|
||||
|
||||
def week_edge_days(num)
|
||||
[num.weeks.ago, num.weeks.ago + 6.days]
|
||||
end
|
||||
|
||||
def statuses_tracker
|
||||
ActivityTracker.new('activity:statuses:local', :basic)
|
||||
end
|
||||
|
||||
def logins_tracker
|
||||
ActivityTracker.new('activity:logins', :unique)
|
||||
end
|
||||
|
||||
def registrations_tracker
|
||||
ActivityTracker.new('activity:accounts:local', :basic)
|
||||
end
|
||||
|
||||
def require_enabled_api!
|
||||
head 404 unless Setting.activity_api_enabled && !limited_federation_mode?
|
||||
end
|
||||
|
|
|
@ -19,7 +19,19 @@ class Api::V1::Instances::DomainBlocksController < Api::V1::Instances::BaseContr
|
|||
private
|
||||
|
||||
def require_enabled_api!
|
||||
head 404 unless Setting.show_domain_blocks == 'all' || (Setting.show_domain_blocks == 'users' && user_signed_in?)
|
||||
head 404 unless api_enabled?
|
||||
end
|
||||
|
||||
def api_enabled?
|
||||
show_domain_blocks_for_all? || show_domain_blocks_to_user?
|
||||
end
|
||||
|
||||
def show_domain_blocks_for_all?
|
||||
Setting.show_domain_blocks == 'all'
|
||||
end
|
||||
|
||||
def show_domain_blocks_to_user?
|
||||
Setting.show_domain_blocks == 'users' && user_signed_in?
|
||||
end
|
||||
|
||||
def set_domain_blocks
|
||||
|
|
|
@ -11,6 +11,6 @@ class Api::V1::Statuses::HistoriesController < Api::V1::Statuses::BaseController
|
|||
private
|
||||
|
||||
def status_edits
|
||||
@status.edits.includes(:account, status: [:account]).to_a.presence || [@status.build_snapshot(at_time: @status.edited_at || @status.created_at)]
|
||||
@status.edits.ordered.includes(:account, status: [:account]).to_a.presence || [@status.build_snapshot(at_time: @status.edited_at || @status.created_at)]
|
||||
end
|
||||
end
|
||||
|
|
33
app/controllers/api/v1/timelines/base_controller.rb
Normal file
33
app/controllers/api/v1/timelines/base_controller.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Timelines::BaseController < Api::BaseController
|
||||
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
||||
|
||||
private
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@statuses.last.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@statuses.first.id
|
||||
end
|
||||
|
||||
def next_path_params
|
||||
permitted_params.merge(max_id: pagination_max_id)
|
||||
end
|
||||
|
||||
def prev_path_params
|
||||
permitted_params.merge(min_id: pagination_since_id)
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params
|
||||
.slice(*self.class::PERMITTED_PARAMS)
|
||||
.permit(*self.class::PERMITTED_PARAMS)
|
||||
end
|
||||
end
|
|
@ -1,9 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Timelines::HomeController < Api::BaseController
|
||||
class Api::V1::Timelines::HomeController < Api::V1::Timelines::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: [:show]
|
||||
before_action :require_user!, only: [:show]
|
||||
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
||||
|
||||
PERMITTED_PARAMS = %i(local limit).freeze
|
||||
|
||||
def show
|
||||
with_read_replica do
|
||||
|
@ -40,27 +41,11 @@ class Api::V1::Timelines::HomeController < Api::BaseController
|
|||
HomeFeed.new(current_account)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:local, :limit).permit(:local, :limit).merge(core_params)
|
||||
end
|
||||
|
||||
def next_path
|
||||
api_v1_timelines_home_url pagination_params(max_id: pagination_max_id)
|
||||
api_v1_timelines_home_url next_path_params
|
||||
end
|
||||
|
||||
def prev_path
|
||||
api_v1_timelines_home_url pagination_params(min_id: pagination_since_id)
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@statuses.last.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@statuses.first.id
|
||||
api_v1_timelines_home_url prev_path_params
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Timelines::ListController < Api::BaseController
|
||||
class Api::V1::Timelines::ListController < Api::V1::Timelines::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:lists' }
|
||||
before_action :require_user!
|
||||
before_action :set_list
|
||||
before_action :set_statuses
|
||||
|
||||
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
||||
PERMITTED_PARAMS = %i(limit).freeze
|
||||
|
||||
def show
|
||||
render json: @statuses,
|
||||
|
@ -41,27 +41,11 @@ class Api::V1::Timelines::ListController < Api::BaseController
|
|||
ListFeed.new(@list)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:limit).permit(:limit).merge(core_params)
|
||||
end
|
||||
|
||||
def next_path
|
||||
api_v1_timelines_list_url params[:id], pagination_params(max_id: pagination_max_id)
|
||||
api_v1_timelines_list_url params[:id], next_path_params
|
||||
end
|
||||
|
||||
def prev_path
|
||||
api_v1_timelines_list_url params[:id], pagination_params(min_id: pagination_since_id)
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@statuses.last.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@statuses.first.id
|
||||
api_v1_timelines_list_url params[:id], prev_path_params
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Timelines::PublicController < Api::BaseController
|
||||
class Api::V1::Timelines::PublicController < Api::V1::Timelines::BaseController
|
||||
before_action :require_user!, only: [:show], if: :require_auth?
|
||||
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
||||
|
||||
PERMITTED_PARAMS = %i(local remote limit only_media allow_local_only).freeze
|
||||
|
||||
def show
|
||||
cache_if_unauthenticated!
|
||||
|
@ -45,27 +46,11 @@ class Api::V1::Timelines::PublicController < Api::BaseController
|
|||
)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:local, :remote, :limit, :only_media, :allow_local_only).permit(:local, :remote, :limit, :only_media, :allow_local_only).merge(core_params)
|
||||
end
|
||||
|
||||
def next_path
|
||||
api_v1_timelines_public_url pagination_params(max_id: pagination_max_id)
|
||||
api_v1_timelines_public_url next_path_params
|
||||
end
|
||||
|
||||
def prev_path
|
||||
api_v1_timelines_public_url pagination_params(min_id: pagination_since_id)
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@statuses.last.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@statuses.first.id
|
||||
api_v1_timelines_public_url prev_path_params
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Timelines::TagController < Api::BaseController
|
||||
class Api::V1::Timelines::TagController < Api::V1::Timelines::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: :show, if: :require_auth?
|
||||
before_action :load_tag
|
||||
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
||||
|
||||
PERMITTED_PARAMS = %i(local limit only_media).freeze
|
||||
|
||||
def show
|
||||
cache_if_unauthenticated!
|
||||
|
@ -51,27 +52,11 @@ class Api::V1::Timelines::TagController < Api::BaseController
|
|||
)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.slice(:local, :limit, :only_media).permit(:local, :limit, :only_media).merge(core_params)
|
||||
end
|
||||
|
||||
def next_path
|
||||
api_v1_timelines_tag_url params[:id], pagination_params(max_id: pagination_max_id)
|
||||
api_v1_timelines_tag_url params[:id], next_path_params
|
||||
end
|
||||
|
||||
def prev_path
|
||||
api_v1_timelines_tag_url params[:id], pagination_params(min_id: pagination_since_id)
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@statuses.last.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@statuses.first.id
|
||||
api_v1_timelines_tag_url params[:id], prev_path_params
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,12 +2,22 @@
|
|||
|
||||
class Api::V2::MediaController < Api::V1::MediaController
|
||||
def create
|
||||
@media_attachment = current_account.media_attachments.create!({ delay_processing: true }.merge(media_attachment_params))
|
||||
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: @media_attachment.not_processed? ? 202 : 200
|
||||
@media_attachment = current_account.media_attachments.create!(media_and_delay_params)
|
||||
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_from_media_processing
|
||||
rescue Paperclip::Errors::NotIdentifiedByImageMagickError
|
||||
render json: file_type_error, status: 422
|
||||
rescue Paperclip::Error => e
|
||||
Rails.logger.error "#{e.class}: #{e.message}"
|
||||
render json: processing_error, status: 500
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def media_and_delay_params
|
||||
{ delay_processing: true }.merge(media_attachment_params)
|
||||
end
|
||||
|
||||
def status_from_media_processing
|
||||
@media_attachment.not_processed? ? 202 : 200
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,37 +3,13 @@
|
|||
class Api::Web::PushSubscriptionsController < Api::Web::BaseController
|
||||
before_action :require_user!
|
||||
before_action :set_push_subscription, only: :update
|
||||
before_action :destroy_previous_subscriptions, only: :create, if: :prior_subscriptions?
|
||||
after_action :update_session_with_subscription, only: :create
|
||||
|
||||
def create
|
||||
active_session = current_session
|
||||
@push_subscription = ::Web::PushSubscription.create!(web_push_subscription_params)
|
||||
|
||||
unless active_session.web_push_subscription.nil?
|
||||
active_session.web_push_subscription.destroy!
|
||||
active_session.update!(web_push_subscription: nil)
|
||||
end
|
||||
|
||||
# Mobile devices do not support regular notifications, so we enable push notifications by default
|
||||
alerts_enabled = active_session.detection.device.mobile? || active_session.detection.device.tablet?
|
||||
|
||||
data = {
|
||||
policy: 'all',
|
||||
alerts: Notification::TYPES.index_with { alerts_enabled },
|
||||
}
|
||||
|
||||
data.deep_merge!(data_params) if params[:data]
|
||||
|
||||
push_subscription = ::Web::PushSubscription.create!(
|
||||
endpoint: subscription_params[:endpoint],
|
||||
key_p256dh: subscription_params[:keys][:p256dh],
|
||||
key_auth: subscription_params[:keys][:auth],
|
||||
data: data,
|
||||
user_id: active_session.user_id,
|
||||
access_token_id: active_session.access_token_id
|
||||
)
|
||||
|
||||
active_session.update!(web_push_subscription: push_subscription)
|
||||
|
||||
render json: push_subscription, serializer: REST::WebPushSubscriptionSerializer
|
||||
render json: @push_subscription, serializer: REST::WebPushSubscriptionSerializer
|
||||
end
|
||||
|
||||
def update
|
||||
|
@ -43,6 +19,41 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
|
|||
|
||||
private
|
||||
|
||||
def active_session
|
||||
@active_session ||= current_session
|
||||
end
|
||||
|
||||
def destroy_previous_subscriptions
|
||||
active_session.web_push_subscription.destroy!
|
||||
active_session.update!(web_push_subscription: nil)
|
||||
end
|
||||
|
||||
def prior_subscriptions?
|
||||
active_session.web_push_subscription.present?
|
||||
end
|
||||
|
||||
def subscription_data
|
||||
default_subscription_data.tap do |data|
|
||||
data.deep_merge!(data_params) if params[:data]
|
||||
end
|
||||
end
|
||||
|
||||
def default_subscription_data
|
||||
{
|
||||
policy: 'all',
|
||||
alerts: Notification::TYPES.index_with { alerts_enabled },
|
||||
}
|
||||
end
|
||||
|
||||
def alerts_enabled
|
||||
# Mobile devices do not support regular notifications, so we enable push notifications by default
|
||||
active_session.detection.device.mobile? || active_session.detection.device.tablet?
|
||||
end
|
||||
|
||||
def update_session_with_subscription
|
||||
active_session.update!(web_push_subscription: @push_subscription)
|
||||
end
|
||||
|
||||
def set_push_subscription
|
||||
@push_subscription = ::Web::PushSubscription.find(params[:id])
|
||||
end
|
||||
|
@ -51,6 +62,17 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
|
|||
@subscription_params ||= params.require(:subscription).permit(:endpoint, keys: [:auth, :p256dh])
|
||||
end
|
||||
|
||||
def web_push_subscription_params
|
||||
{
|
||||
access_token_id: active_session.access_token_id,
|
||||
data: subscription_data,
|
||||
endpoint: subscription_params[:endpoint],
|
||||
key_auth: subscription_params[:keys][:auth],
|
||||
key_p256dh: subscription_params[:keys][:p256dh],
|
||||
user_id: active_session.user_id,
|
||||
}
|
||||
end
|
||||
|
||||
def data_params
|
||||
@data_params ||= params.require(:data).permit(:policy, alerts: Notification::TYPES)
|
||||
end
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import type { ApiRelationshipJSON } from 'flavours/glitch/api_types/relationships';
|
||||
import { createAppAsyncThunk } from 'flavours/glitch/store/typed_functions';
|
||||
|
||||
import api from '../api';
|
||||
|
@ -5,8 +6,7 @@ import api from '../api';
|
|||
export const submitAccountNote = createAppAsyncThunk(
|
||||
'account_note/submit',
|
||||
async (args: { id: string; value: string }, { getState }) => {
|
||||
// TODO: replace `unknown` with `ApiRelationshipJSON` when it is merged
|
||||
const response = await api(getState).post<unknown>(
|
||||
const response = await api(getState).post<ApiRelationshipJSON>(
|
||||
`/api/v1/accounts/${args.id}/note`,
|
||||
{
|
||||
comment: args.value,
|
||||
|
|
|
@ -1,5 +1,15 @@
|
|||
import api, { getLinks } from '../api';
|
||||
|
||||
import {
|
||||
followAccountSuccess, unfollowAccountSuccess,
|
||||
authorizeFollowRequestSuccess, rejectFollowRequestSuccess,
|
||||
followAccountRequest, followAccountFail,
|
||||
unfollowAccountRequest, unfollowAccountFail,
|
||||
muteAccountSuccess, unmuteAccountSuccess,
|
||||
blockAccountSuccess, unblockAccountSuccess,
|
||||
pinAccountSuccess, unpinAccountSuccess,
|
||||
fetchRelationshipsSuccess,
|
||||
} from './accounts_typed';
|
||||
import { importFetchedAccount, importFetchedAccounts } from './importer';
|
||||
|
||||
export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST';
|
||||
|
@ -10,36 +20,22 @@ export const ACCOUNT_LOOKUP_REQUEST = 'ACCOUNT_LOOKUP_REQUEST';
|
|||
export const ACCOUNT_LOOKUP_SUCCESS = 'ACCOUNT_LOOKUP_SUCCESS';
|
||||
export const ACCOUNT_LOOKUP_FAIL = 'ACCOUNT_LOOKUP_FAIL';
|
||||
|
||||
export const ACCOUNT_FOLLOW_REQUEST = 'ACCOUNT_FOLLOW_REQUEST';
|
||||
export const ACCOUNT_FOLLOW_SUCCESS = 'ACCOUNT_FOLLOW_SUCCESS';
|
||||
export const ACCOUNT_FOLLOW_FAIL = 'ACCOUNT_FOLLOW_FAIL';
|
||||
|
||||
export const ACCOUNT_UNFOLLOW_REQUEST = 'ACCOUNT_UNFOLLOW_REQUEST';
|
||||
export const ACCOUNT_UNFOLLOW_SUCCESS = 'ACCOUNT_UNFOLLOW_SUCCESS';
|
||||
export const ACCOUNT_UNFOLLOW_FAIL = 'ACCOUNT_UNFOLLOW_FAIL';
|
||||
|
||||
export const ACCOUNT_BLOCK_REQUEST = 'ACCOUNT_BLOCK_REQUEST';
|
||||
export const ACCOUNT_BLOCK_SUCCESS = 'ACCOUNT_BLOCK_SUCCESS';
|
||||
export const ACCOUNT_BLOCK_FAIL = 'ACCOUNT_BLOCK_FAIL';
|
||||
|
||||
export const ACCOUNT_UNBLOCK_REQUEST = 'ACCOUNT_UNBLOCK_REQUEST';
|
||||
export const ACCOUNT_UNBLOCK_SUCCESS = 'ACCOUNT_UNBLOCK_SUCCESS';
|
||||
export const ACCOUNT_UNBLOCK_FAIL = 'ACCOUNT_UNBLOCK_FAIL';
|
||||
|
||||
export const ACCOUNT_MUTE_REQUEST = 'ACCOUNT_MUTE_REQUEST';
|
||||
export const ACCOUNT_MUTE_SUCCESS = 'ACCOUNT_MUTE_SUCCESS';
|
||||
export const ACCOUNT_MUTE_FAIL = 'ACCOUNT_MUTE_FAIL';
|
||||
|
||||
export const ACCOUNT_UNMUTE_REQUEST = 'ACCOUNT_UNMUTE_REQUEST';
|
||||
export const ACCOUNT_UNMUTE_SUCCESS = 'ACCOUNT_UNMUTE_SUCCESS';
|
||||
export const ACCOUNT_UNMUTE_FAIL = 'ACCOUNT_UNMUTE_FAIL';
|
||||
|
||||
export const ACCOUNT_PIN_REQUEST = 'ACCOUNT_PIN_REQUEST';
|
||||
export const ACCOUNT_PIN_SUCCESS = 'ACCOUNT_PIN_SUCCESS';
|
||||
export const ACCOUNT_PIN_FAIL = 'ACCOUNT_PIN_FAIL';
|
||||
|
||||
export const ACCOUNT_UNPIN_REQUEST = 'ACCOUNT_UNPIN_REQUEST';
|
||||
export const ACCOUNT_UNPIN_SUCCESS = 'ACCOUNT_UNPIN_SUCCESS';
|
||||
export const ACCOUNT_UNPIN_FAIL = 'ACCOUNT_UNPIN_FAIL';
|
||||
|
||||
export const FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST';
|
||||
|
@ -59,7 +55,6 @@ export const FOLLOWING_EXPAND_SUCCESS = 'FOLLOWING_EXPAND_SUCCESS';
|
|||
export const FOLLOWING_EXPAND_FAIL = 'FOLLOWING_EXPAND_FAIL';
|
||||
|
||||
export const RELATIONSHIPS_FETCH_REQUEST = 'RELATIONSHIPS_FETCH_REQUEST';
|
||||
export const RELATIONSHIPS_FETCH_SUCCESS = 'RELATIONSHIPS_FETCH_SUCCESS';
|
||||
export const RELATIONSHIPS_FETCH_FAIL = 'RELATIONSHIPS_FETCH_FAIL';
|
||||
|
||||
export const FOLLOW_REQUESTS_FETCH_REQUEST = 'FOLLOW_REQUESTS_FETCH_REQUEST';
|
||||
|
@ -91,9 +86,10 @@ export const PINNED_ACCOUNTS_EDITOR_SUGGESTIONS_CHANGE = 'PINNED_ACCOUNTS_EDITOR
|
|||
|
||||
export const PINNED_ACCOUNTS_EDITOR_RESET = 'PINNED_ACCOUNTS_EDITOR_RESET';
|
||||
|
||||
|
||||
export const ACCOUNT_REVEAL = 'ACCOUNT_REVEAL';
|
||||
|
||||
export * from './accounts_typed';
|
||||
|
||||
export function fetchAccount(id) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(fetchRelationships([id]));
|
||||
|
@ -168,12 +164,12 @@ export function followAccount(id, options = { reblogs: true }) {
|
|||
const alreadyFollowing = getState().getIn(['relationships', id, 'following']);
|
||||
const locked = getState().getIn(['accounts', id, 'locked'], false);
|
||||
|
||||
dispatch(followAccountRequest(id, locked));
|
||||
dispatch(followAccountRequest({ id, locked }));
|
||||
|
||||
api(getState).post(`/api/v1/accounts/${id}/follow`, options).then(response => {
|
||||
dispatch(followAccountSuccess(response.data, alreadyFollowing));
|
||||
dispatch(followAccountSuccess({relationship: response.data, alreadyFollowing}));
|
||||
}).catch(error => {
|
||||
dispatch(followAccountFail(error, locked));
|
||||
dispatch(followAccountFail({ id, error, locked }));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -183,74 +179,22 @@ export function unfollowAccount(id) {
|
|||
dispatch(unfollowAccountRequest(id));
|
||||
|
||||
api(getState).post(`/api/v1/accounts/${id}/unfollow`).then(response => {
|
||||
dispatch(unfollowAccountSuccess(response.data, getState().get('statuses')));
|
||||
dispatch(unfollowAccountSuccess({relationship: response.data, statuses: getState().get('statuses')}));
|
||||
}).catch(error => {
|
||||
dispatch(unfollowAccountFail(error));
|
||||
dispatch(unfollowAccountFail({ id, error }));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function followAccountRequest(id, locked) {
|
||||
return {
|
||||
type: ACCOUNT_FOLLOW_REQUEST,
|
||||
id,
|
||||
locked,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function followAccountSuccess(relationship, alreadyFollowing) {
|
||||
return {
|
||||
type: ACCOUNT_FOLLOW_SUCCESS,
|
||||
relationship,
|
||||
alreadyFollowing,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function followAccountFail(error, locked) {
|
||||
return {
|
||||
type: ACCOUNT_FOLLOW_FAIL,
|
||||
error,
|
||||
locked,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function unfollowAccountRequest(id) {
|
||||
return {
|
||||
type: ACCOUNT_UNFOLLOW_REQUEST,
|
||||
id,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function unfollowAccountSuccess(relationship, statuses) {
|
||||
return {
|
||||
type: ACCOUNT_UNFOLLOW_SUCCESS,
|
||||
relationship,
|
||||
statuses,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function unfollowAccountFail(error) {
|
||||
return {
|
||||
type: ACCOUNT_UNFOLLOW_FAIL,
|
||||
error,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function blockAccount(id) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(blockAccountRequest(id));
|
||||
|
||||
api(getState).post(`/api/v1/accounts/${id}/block`).then(response => {
|
||||
// Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers
|
||||
dispatch(blockAccountSuccess(response.data, getState().get('statuses')));
|
||||
dispatch(blockAccountSuccess({ relationship: response.data, statuses: getState().get('statuses') }));
|
||||
}).catch(error => {
|
||||
dispatch(blockAccountFail(id, error));
|
||||
dispatch(blockAccountFail({ id, error }));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -260,9 +204,9 @@ export function unblockAccount(id) {
|
|||
dispatch(unblockAccountRequest(id));
|
||||
|
||||
api(getState).post(`/api/v1/accounts/${id}/unblock`).then(response => {
|
||||
dispatch(unblockAccountSuccess(response.data));
|
||||
dispatch(unblockAccountSuccess({ relationship: response.data }));
|
||||
}).catch(error => {
|
||||
dispatch(unblockAccountFail(id, error));
|
||||
dispatch(unblockAccountFail({ id, error }));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -273,15 +217,6 @@ export function blockAccountRequest(id) {
|
|||
id,
|
||||
};
|
||||
}
|
||||
|
||||
export function blockAccountSuccess(relationship, statuses) {
|
||||
return {
|
||||
type: ACCOUNT_BLOCK_SUCCESS,
|
||||
relationship,
|
||||
statuses,
|
||||
};
|
||||
}
|
||||
|
||||
export function blockAccountFail(error) {
|
||||
return {
|
||||
type: ACCOUNT_BLOCK_FAIL,
|
||||
|
@ -296,13 +231,6 @@ export function unblockAccountRequest(id) {
|
|||
};
|
||||
}
|
||||
|
||||
export function unblockAccountSuccess(relationship) {
|
||||
return {
|
||||
type: ACCOUNT_UNBLOCK_SUCCESS,
|
||||
relationship,
|
||||
};
|
||||
}
|
||||
|
||||
export function unblockAccountFail(error) {
|
||||
return {
|
||||
type: ACCOUNT_UNBLOCK_FAIL,
|
||||
|
@ -317,9 +245,9 @@ export function muteAccount(id, notifications, duration=0) {
|
|||
|
||||
api(getState).post(`/api/v1/accounts/${id}/mute`, { notifications, duration }).then(response => {
|
||||
// Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers
|
||||
dispatch(muteAccountSuccess(response.data, getState().get('statuses')));
|
||||
dispatch(muteAccountSuccess({ relationship: response.data, statuses: getState().get('statuses') }));
|
||||
}).catch(error => {
|
||||
dispatch(muteAccountFail(id, error));
|
||||
dispatch(muteAccountFail({ id, error }));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -329,9 +257,9 @@ export function unmuteAccount(id) {
|
|||
dispatch(unmuteAccountRequest(id));
|
||||
|
||||
api(getState).post(`/api/v1/accounts/${id}/unmute`).then(response => {
|
||||
dispatch(unmuteAccountSuccess(response.data));
|
||||
dispatch(unmuteAccountSuccess({ relationship: response.data }));
|
||||
}).catch(error => {
|
||||
dispatch(unmuteAccountFail(id, error));
|
||||
dispatch(unmuteAccountFail({ id, error }));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -343,14 +271,6 @@ export function muteAccountRequest(id) {
|
|||
};
|
||||
}
|
||||
|
||||
export function muteAccountSuccess(relationship, statuses) {
|
||||
return {
|
||||
type: ACCOUNT_MUTE_SUCCESS,
|
||||
relationship,
|
||||
statuses,
|
||||
};
|
||||
}
|
||||
|
||||
export function muteAccountFail(error) {
|
||||
return {
|
||||
type: ACCOUNT_MUTE_FAIL,
|
||||
|
@ -365,13 +285,6 @@ export function unmuteAccountRequest(id) {
|
|||
};
|
||||
}
|
||||
|
||||
export function unmuteAccountSuccess(relationship) {
|
||||
return {
|
||||
type: ACCOUNT_UNMUTE_SUCCESS,
|
||||
relationship,
|
||||
};
|
||||
}
|
||||
|
||||
export function unmuteAccountFail(error) {
|
||||
return {
|
||||
type: ACCOUNT_UNMUTE_FAIL,
|
||||
|
@ -568,7 +481,7 @@ export function fetchRelationships(accountIds) {
|
|||
dispatch(fetchRelationshipsRequest(newAccountIds));
|
||||
|
||||
api(getState).get(`/api/v1/accounts/relationships?with_suspended=true&${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => {
|
||||
dispatch(fetchRelationshipsSuccess(response.data));
|
||||
dispatch(fetchRelationshipsSuccess({ relationships: response.data }));
|
||||
}).catch(error => {
|
||||
dispatch(fetchRelationshipsFail(error));
|
||||
});
|
||||
|
@ -583,14 +496,6 @@ export function fetchRelationshipsRequest(ids) {
|
|||
};
|
||||
}
|
||||
|
||||
export function fetchRelationshipsSuccess(relationships) {
|
||||
return {
|
||||
type: RELATIONSHIPS_FETCH_SUCCESS,
|
||||
relationships,
|
||||
skipLoading: true,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchRelationshipsFail(error) {
|
||||
return {
|
||||
type: RELATIONSHIPS_FETCH_FAIL,
|
||||
|
@ -678,7 +583,7 @@ export function authorizeFollowRequest(id) {
|
|||
|
||||
api(getState)
|
||||
.post(`/api/v1/follow_requests/${id}/authorize`)
|
||||
.then(() => dispatch(authorizeFollowRequestSuccess(id)))
|
||||
.then(() => dispatch(authorizeFollowRequestSuccess({ id })))
|
||||
.catch(error => dispatch(authorizeFollowRequestFail(id, error)));
|
||||
};
|
||||
}
|
||||
|
@ -690,13 +595,6 @@ export function authorizeFollowRequestRequest(id) {
|
|||
};
|
||||
}
|
||||
|
||||
export function authorizeFollowRequestSuccess(id) {
|
||||
return {
|
||||
type: FOLLOW_REQUEST_AUTHORIZE_SUCCESS,
|
||||
id,
|
||||
};
|
||||
}
|
||||
|
||||
export function authorizeFollowRequestFail(id, error) {
|
||||
return {
|
||||
type: FOLLOW_REQUEST_AUTHORIZE_FAIL,
|
||||
|
@ -712,7 +610,7 @@ export function rejectFollowRequest(id) {
|
|||
|
||||
api(getState)
|
||||
.post(`/api/v1/follow_requests/${id}/reject`)
|
||||
.then(() => dispatch(rejectFollowRequestSuccess(id)))
|
||||
.then(() => dispatch(rejectFollowRequestSuccess({ id })))
|
||||
.catch(error => dispatch(rejectFollowRequestFail(id, error)));
|
||||
};
|
||||
}
|
||||
|
@ -724,13 +622,6 @@ export function rejectFollowRequestRequest(id) {
|
|||
};
|
||||
}
|
||||
|
||||
export function rejectFollowRequestSuccess(id) {
|
||||
return {
|
||||
type: FOLLOW_REQUEST_REJECT_SUCCESS,
|
||||
id,
|
||||
};
|
||||
}
|
||||
|
||||
export function rejectFollowRequestFail(id, error) {
|
||||
return {
|
||||
type: FOLLOW_REQUEST_REJECT_FAIL,
|
||||
|
@ -744,7 +635,7 @@ export function pinAccount(id) {
|
|||
dispatch(pinAccountRequest(id));
|
||||
|
||||
api(getState).post(`/api/v1/accounts/${id}/pin`).then(response => {
|
||||
dispatch(pinAccountSuccess(response.data));
|
||||
dispatch(pinAccountSuccess({ relationship: response.data }));
|
||||
}).catch(error => {
|
||||
dispatch(pinAccountFail(error));
|
||||
});
|
||||
|
@ -756,7 +647,7 @@ export function unpinAccount(id) {
|
|||
dispatch(unpinAccountRequest(id));
|
||||
|
||||
api(getState).post(`/api/v1/accounts/${id}/unpin`).then(response => {
|
||||
dispatch(unpinAccountSuccess(response.data));
|
||||
dispatch(unpinAccountSuccess({ relationship: response.data }));
|
||||
}).catch(error => {
|
||||
dispatch(unpinAccountFail(error));
|
||||
});
|
||||
|
@ -770,13 +661,6 @@ export function pinAccountRequest(id) {
|
|||
};
|
||||
}
|
||||
|
||||
export function pinAccountSuccess(relationship) {
|
||||
return {
|
||||
type: ACCOUNT_PIN_SUCCESS,
|
||||
relationship,
|
||||
};
|
||||
}
|
||||
|
||||
export function pinAccountFail(error) {
|
||||
return {
|
||||
type: ACCOUNT_PIN_FAIL,
|
||||
|
@ -791,13 +675,6 @@ export function unpinAccountRequest(id) {
|
|||
};
|
||||
}
|
||||
|
||||
export function unpinAccountSuccess(relationship) {
|
||||
return {
|
||||
type: ACCOUNT_UNPIN_SUCCESS,
|
||||
relationship,
|
||||
};
|
||||
}
|
||||
|
||||
export function unpinAccountFail(error) {
|
||||
return {
|
||||
type: ACCOUNT_UNPIN_FAIL,
|
||||
|
@ -805,11 +682,6 @@ export function unpinAccountFail(error) {
|
|||
};
|
||||
}
|
||||
|
||||
export const revealAccount = id => ({
|
||||
type: ACCOUNT_REVEAL,
|
||||
id,
|
||||
});
|
||||
|
||||
export function fetchPinnedAccounts() {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(fetchPinnedAccountsRequest());
|
||||
|
|
97
app/javascript/flavours/glitch/actions/accounts_typed.ts
Normal file
97
app/javascript/flavours/glitch/actions/accounts_typed.ts
Normal file
|
@ -0,0 +1,97 @@
|
|||
import { createAction } from '@reduxjs/toolkit';
|
||||
|
||||
import type { ApiAccountJSON } from 'flavours/glitch/api_types/accounts';
|
||||
import type { ApiRelationshipJSON } from 'flavours/glitch/api_types/relationships';
|
||||
|
||||
export const revealAccount = createAction<{
|
||||
id: string;
|
||||
}>('accounts/revealAccount');
|
||||
|
||||
export const importAccounts = createAction<{ accounts: ApiAccountJSON[] }>(
|
||||
'accounts/importAccounts',
|
||||
);
|
||||
|
||||
function actionWithSkipLoadingTrue<Args extends object>(args: Args) {
|
||||
return {
|
||||
payload: {
|
||||
...args,
|
||||
skipLoading: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const followAccountSuccess = createAction(
|
||||
'accounts/followAccount/SUCCESS',
|
||||
actionWithSkipLoadingTrue<{
|
||||
relationship: ApiRelationshipJSON;
|
||||
alreadyFollowing: boolean;
|
||||
}>,
|
||||
);
|
||||
|
||||
export const unfollowAccountSuccess = createAction(
|
||||
'accounts/unfollowAccount/SUCCESS',
|
||||
actionWithSkipLoadingTrue<{
|
||||
relationship: ApiRelationshipJSON;
|
||||
statuses: unknown;
|
||||
alreadyFollowing?: boolean;
|
||||
}>,
|
||||
);
|
||||
|
||||
export const authorizeFollowRequestSuccess = createAction<{ id: string }>(
|
||||
'accounts/followRequestAuthorize/SUCCESS',
|
||||
);
|
||||
|
||||
export const rejectFollowRequestSuccess = createAction<{ id: string }>(
|
||||
'accounts/followRequestReject/SUCCESS',
|
||||
);
|
||||
|
||||
export const followAccountRequest = createAction(
|
||||
'accounts/follow/REQUEST',
|
||||
actionWithSkipLoadingTrue<{ id: string; locked: boolean }>,
|
||||
);
|
||||
|
||||
export const followAccountFail = createAction(
|
||||
'accounts/follow/FAIL',
|
||||
actionWithSkipLoadingTrue<{ id: string; error: string; locked: boolean }>,
|
||||
);
|
||||
|
||||
export const unfollowAccountRequest = createAction(
|
||||
'accounts/unfollow/REQUEST',
|
||||
actionWithSkipLoadingTrue<{ id: string }>,
|
||||
);
|
||||
|
||||
export const unfollowAccountFail = createAction(
|
||||
'accounts/unfollow/FAIL',
|
||||
actionWithSkipLoadingTrue<{ id: string; error: string }>,
|
||||
);
|
||||
|
||||
export const blockAccountSuccess = createAction<{
|
||||
relationship: ApiRelationshipJSON;
|
||||
statuses: unknown;
|
||||
}>('accounts/block/SUCCESS');
|
||||
|
||||
export const unblockAccountSuccess = createAction<{
|
||||
relationship: ApiRelationshipJSON;
|
||||
}>('accounts/unblock/SUCCESS');
|
||||
|
||||
export const muteAccountSuccess = createAction<{
|
||||
relationship: ApiRelationshipJSON;
|
||||
statuses: unknown;
|
||||
}>('accounts/mute/SUCCESS');
|
||||
|
||||
export const unmuteAccountSuccess = createAction<{
|
||||
relationship: ApiRelationshipJSON;
|
||||
}>('accounts/unmute/SUCCESS');
|
||||
|
||||
export const pinAccountSuccess = createAction<{
|
||||
relationship: ApiRelationshipJSON;
|
||||
}>('accounts/pin/SUCCESS');
|
||||
|
||||
export const unpinAccountSuccess = createAction<{
|
||||
relationship: ApiRelationshipJSON;
|
||||
}>('accounts/unpin/SUCCESS');
|
||||
|
||||
export const fetchRelationshipsSuccess = createAction(
|
||||
'relationships/fetch/SUCCESS',
|
||||
actionWithSkipLoadingTrue<{ relationships: ApiRelationshipJSON[] }>,
|
||||
);
|
|
@ -1,11 +1,13 @@
|
|||
import api, { getLinks } from '../api';
|
||||
|
||||
import { blockDomainSuccess, unblockDomainSuccess } from "./domain_blocks_typed";
|
||||
|
||||
export * from "./domain_blocks_typed";
|
||||
|
||||
export const DOMAIN_BLOCK_REQUEST = 'DOMAIN_BLOCK_REQUEST';
|
||||
export const DOMAIN_BLOCK_SUCCESS = 'DOMAIN_BLOCK_SUCCESS';
|
||||
export const DOMAIN_BLOCK_FAIL = 'DOMAIN_BLOCK_FAIL';
|
||||
|
||||
export const DOMAIN_UNBLOCK_REQUEST = 'DOMAIN_UNBLOCK_REQUEST';
|
||||
export const DOMAIN_UNBLOCK_SUCCESS = 'DOMAIN_UNBLOCK_SUCCESS';
|
||||
export const DOMAIN_UNBLOCK_FAIL = 'DOMAIN_UNBLOCK_FAIL';
|
||||
|
||||
export const DOMAIN_BLOCKS_FETCH_REQUEST = 'DOMAIN_BLOCKS_FETCH_REQUEST';
|
||||
|
@ -24,7 +26,7 @@ export function blockDomain(domain) {
|
|||
const at_domain = '@' + domain;
|
||||
const accounts = getState().get('accounts').filter(item => item.get('acct').endsWith(at_domain)).valueSeq().map(item => item.get('id'));
|
||||
|
||||
dispatch(blockDomainSuccess(domain, accounts));
|
||||
dispatch(blockDomainSuccess({ domain, accounts }));
|
||||
}).catch(err => {
|
||||
dispatch(blockDomainFail(domain, err));
|
||||
});
|
||||
|
@ -38,14 +40,6 @@ export function blockDomainRequest(domain) {
|
|||
};
|
||||
}
|
||||
|
||||
export function blockDomainSuccess(domain, accounts) {
|
||||
return {
|
||||
type: DOMAIN_BLOCK_SUCCESS,
|
||||
domain,
|
||||
accounts,
|
||||
};
|
||||
}
|
||||
|
||||
export function blockDomainFail(domain, error) {
|
||||
return {
|
||||
type: DOMAIN_BLOCK_FAIL,
|
||||
|
@ -61,7 +55,7 @@ export function unblockDomain(domain) {
|
|||
api(getState).delete('/api/v1/domain_blocks', { params: { domain } }).then(() => {
|
||||
const at_domain = '@' + domain;
|
||||
const accounts = getState().get('accounts').filter(item => item.get('acct').endsWith(at_domain)).valueSeq().map(item => item.get('id'));
|
||||
dispatch(unblockDomainSuccess(domain, accounts));
|
||||
dispatch(unblockDomainSuccess({ domain, accounts }));
|
||||
}).catch(err => {
|
||||
dispatch(unblockDomainFail(domain, err));
|
||||
});
|
||||
|
@ -75,14 +69,6 @@ export function unblockDomainRequest(domain) {
|
|||
};
|
||||
}
|
||||
|
||||
export function unblockDomainSuccess(domain, accounts) {
|
||||
return {
|
||||
type: DOMAIN_UNBLOCK_SUCCESS,
|
||||
domain,
|
||||
accounts,
|
||||
};
|
||||
}
|
||||
|
||||
export function unblockDomainFail(domain, error) {
|
||||
return {
|
||||
type: DOMAIN_UNBLOCK_FAIL,
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import { createAction } from '@reduxjs/toolkit';
|
||||
|
||||
import type { Account } from 'flavours/glitch/models/account';
|
||||
|
||||
export const blockDomainSuccess = createAction<{
|
||||
domain: string;
|
||||
accounts: Account[];
|
||||
}>('domain_blocks/block/SUCCESS');
|
||||
|
||||
export const unblockDomainSuccess = createAction<{
|
||||
domain: string;
|
||||
accounts: Account[];
|
||||
}>('domain_blocks/unblock/SUCCESS');
|
|
@ -1,7 +1,7 @@
|
|||
import { normalizeAccount, normalizeStatus, normalizePoll } from './normalizer';
|
||||
import { importAccounts } from '../accounts_typed';
|
||||
|
||||
import { normalizeStatus, normalizePoll } from './normalizer';
|
||||
|
||||
export const ACCOUNT_IMPORT = 'ACCOUNT_IMPORT';
|
||||
export const ACCOUNTS_IMPORT = 'ACCOUNTS_IMPORT';
|
||||
export const STATUS_IMPORT = 'STATUS_IMPORT';
|
||||
export const STATUSES_IMPORT = 'STATUSES_IMPORT';
|
||||
export const POLLS_IMPORT = 'POLLS_IMPORT';
|
||||
|
@ -13,14 +13,6 @@ function pushUnique(array, object) {
|
|||
}
|
||||
}
|
||||
|
||||
export function importAccount(account) {
|
||||
return { type: ACCOUNT_IMPORT, account };
|
||||
}
|
||||
|
||||
export function importAccounts(accounts) {
|
||||
return { type: ACCOUNTS_IMPORT, accounts };
|
||||
}
|
||||
|
||||
export function importStatus(status) {
|
||||
return { type: STATUS_IMPORT, status };
|
||||
}
|
||||
|
@ -45,7 +37,7 @@ export function importFetchedAccounts(accounts) {
|
|||
const normalAccounts = [];
|
||||
|
||||
function processAccount(account) {
|
||||
pushUnique(normalAccounts, normalizeAccount(account));
|
||||
pushUnique(normalAccounts, account);
|
||||
|
||||
if (account.moved) {
|
||||
processAccount(account.moved);
|
||||
|
@ -54,7 +46,7 @@ export function importFetchedAccounts(accounts) {
|
|||
|
||||
accounts.forEach(processAccount);
|
||||
|
||||
return importAccounts(normalAccounts);
|
||||
return importAccounts({ accounts: normalAccounts });
|
||||
}
|
||||
|
||||
export function importFetchedStatus(status) {
|
||||
|
|
|
@ -2,7 +2,6 @@ import escapeTextContentForBrowser from 'escape-html';
|
|||
|
||||
import emojify from '../../features/emoji/emoji';
|
||||
import { autoHideCW } from '../../utils/content_warning';
|
||||
import { unescapeHTML } from '../../utils/html';
|
||||
|
||||
const domParser = new DOMParser();
|
||||
|
||||
|
@ -17,32 +16,6 @@ export function searchTextFromRawStatus (status) {
|
|||
return domParser.parseFromString(searchContent, 'text/html').documentElement.textContent;
|
||||
}
|
||||
|
||||
export function normalizeAccount(account) {
|
||||
account = { ...account };
|
||||
|
||||
const emojiMap = makeEmojiMap(account.emojis);
|
||||
const displayName = account.display_name.trim().length === 0 ? account.username : account.display_name;
|
||||
|
||||
account.display_name_html = emojify(escapeTextContentForBrowser(displayName), emojiMap);
|
||||
account.note_emojified = emojify(account.note, emojiMap);
|
||||
account.note_plain = unescapeHTML(account.note);
|
||||
|
||||
if (account.fields) {
|
||||
account.fields = account.fields.map(pair => ({
|
||||
...pair,
|
||||
name_emojified: emojify(escapeTextContentForBrowser(pair.name), emojiMap),
|
||||
value_emojified: emojify(pair.value, emojiMap),
|
||||
value_plain: unescapeHTML(pair.value),
|
||||
}));
|
||||
}
|
||||
|
||||
if (account.moved) {
|
||||
account.moved = account.moved.id;
|
||||
}
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
export function normalizeFilterResult(result) {
|
||||
const normalResult = { ...result };
|
||||
|
||||
|
|
|
@ -18,10 +18,12 @@ import {
|
|||
importFetchedStatuses,
|
||||
} from './importer';
|
||||
import { submitMarkers } from './markers';
|
||||
import { notificationsUpdate } from "./notifications_typed";
|
||||
import { register as registerPushNotifications } from './push_notifications';
|
||||
import { saveSettings } from './settings';
|
||||
|
||||
export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE';
|
||||
export * from "./notifications_typed";
|
||||
|
||||
export const NOTIFICATIONS_UPDATE_NOOP = 'NOTIFICATIONS_UPDATE_NOOP';
|
||||
|
||||
// tracking the notif cleaning request
|
||||
|
@ -107,12 +109,8 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
|
|||
dispatch(importFetchedAccount(notification.report.target_account));
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: NOTIFICATIONS_UPDATE,
|
||||
notification,
|
||||
usePendingItems: preferPendingItems,
|
||||
meta: (playSound && !filtered) ? { sound: 'boop' } : undefined,
|
||||
});
|
||||
|
||||
dispatch(notificationsUpdate({ notification, preferPendingItems, playSound: playSound && !filtered}));
|
||||
|
||||
fetchRelatedRelationships(dispatch, [notification]);
|
||||
} else if (playSound && !filtered) {
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import { createAction } from '@reduxjs/toolkit';
|
||||
|
||||
import type { ApiAccountJSON } from '../api_types/accounts';
|
||||
// To be replaced once ApiNotificationJSON type exists
|
||||
interface FakeApiNotificationJSON {
|
||||
type: string;
|
||||
account: ApiAccountJSON;
|
||||
}
|
||||
|
||||
export const notificationsUpdate = createAction(
|
||||
'notifications/update',
|
||||
({
|
||||
playSound,
|
||||
...args
|
||||
}: {
|
||||
notification: FakeApiNotificationJSON;
|
||||
usePendingItems: boolean;
|
||||
playSound: boolean;
|
||||
}) => ({
|
||||
payload: args,
|
||||
meta: { sound: playSound ? 'boop' : undefined },
|
||||
}),
|
||||
);
|
|
@ -25,6 +25,7 @@ const applyMigrations = (state) => {
|
|||
});
|
||||
};
|
||||
|
||||
|
||||
export function hydrateStore(rawState) {
|
||||
return dispatch => {
|
||||
const state = applyMigrations(convertState(rawState));
|
||||
|
|
45
app/javascript/flavours/glitch/api_types/accounts.ts
Normal file
45
app/javascript/flavours/glitch/api_types/accounts.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
import type { ApiCustomEmojiJSON } from './custom_emoji';
|
||||
|
||||
export interface ApiAccountFieldJSON {
|
||||
name: string;
|
||||
value: string;
|
||||
verified_at: string | null;
|
||||
}
|
||||
|
||||
export interface ApiAccountRoleJSON {
|
||||
color: string;
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
// See app/serializers/rest/account_serializer.rb
|
||||
export interface ApiAccountJSON {
|
||||
acct: string;
|
||||
avatar: string;
|
||||
avatar_static: string;
|
||||
bot: boolean;
|
||||
created_at: string;
|
||||
discoverable: boolean;
|
||||
display_name: string;
|
||||
emojis: ApiCustomEmojiJSON[];
|
||||
fields: ApiAccountFieldJSON[];
|
||||
followers_count: number;
|
||||
following_count: number;
|
||||
group: boolean;
|
||||
header: string;
|
||||
header_static: string;
|
||||
id: string;
|
||||
last_status_at: string;
|
||||
locked: boolean;
|
||||
noindex?: boolean;
|
||||
note: string;
|
||||
roles?: ApiAccountJSON[];
|
||||
statuses_count: number;
|
||||
uri: string;
|
||||
url: string;
|
||||
username: string;
|
||||
moved?: ApiAccountJSON;
|
||||
suspended?: boolean;
|
||||
limited?: boolean;
|
||||
memorial?: boolean;
|
||||
}
|
8
app/javascript/flavours/glitch/api_types/custom_emoji.ts
Normal file
8
app/javascript/flavours/glitch/api_types/custom_emoji.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
// See app/serializers/rest/account_serializer.rb
|
||||
export interface ApiCustomEmojiJSON {
|
||||
shortcode: string;
|
||||
static_url: string;
|
||||
url: string;
|
||||
category?: string;
|
||||
visible_in_picker: boolean;
|
||||
}
|
18
app/javascript/flavours/glitch/api_types/relationships.ts
Normal file
18
app/javascript/flavours/glitch/api_types/relationships.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
// See app/serializers/rest/relationship_serializer.rb
|
||||
export interface ApiRelationshipJSON {
|
||||
blocked_by: boolean;
|
||||
blocking: boolean;
|
||||
domain_blocking: boolean;
|
||||
endorsed: boolean;
|
||||
followed_by: boolean;
|
||||
following: boolean;
|
||||
id: string;
|
||||
languages: string[] | null;
|
||||
muting_notifications: boolean;
|
||||
muting: boolean;
|
||||
note: string;
|
||||
notifying: boolean;
|
||||
requested_by: boolean;
|
||||
requested: boolean;
|
||||
showing_reblogs: boolean;
|
||||
}
|
|
@ -36,7 +36,7 @@ class Account extends ImmutablePureComponent {
|
|||
|
||||
static propTypes = {
|
||||
size: PropTypes.number,
|
||||
account: ImmutablePropTypes.map,
|
||||
account: ImmutablePropTypes.record,
|
||||
onFollow: PropTypes.func.isRequired,
|
||||
onBlock: PropTypes.func.isRequired,
|
||||
onMute: PropTypes.func.isRequired,
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import classNames from 'classnames';
|
||||
|
||||
import type { Account } from 'flavours/glitch/models/account';
|
||||
|
||||
import { useHovering } from '../hooks/useHovering';
|
||||
import { autoPlayGif } from '../initial_state';
|
||||
import type { Account } from '../types/resources';
|
||||
|
||||
interface Props {
|
||||
account: Account | undefined; // FIXME: remove `undefined` once we know for sure its always there
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import type { Account } from 'flavours/glitch/models/account';
|
||||
|
||||
import { useHovering } from '../hooks/useHovering';
|
||||
import { autoPlayGif } from '../initial_state';
|
||||
import type { Account } from '../types/resources';
|
||||
|
||||
interface Props {
|
||||
account: Account | undefined; // FIXME: remove `undefined` once we know for sure its always there
|
||||
|
|
|
@ -4,8 +4,9 @@ import classNames from 'classnames';
|
|||
|
||||
import type { List } from 'immutable';
|
||||
|
||||
import type { Account } from 'flavours/glitch/models/account';
|
||||
|
||||
import { autoPlayGif } from '../initial_state';
|
||||
import type { Account } from '../types/resources';
|
||||
|
||||
import { Skeleton } from './skeleton';
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ const makeMapStateToProps = () => {
|
|||
class InlineAccount extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
account: ImmutablePropTypes.record.isRequired,
|
||||
};
|
||||
|
||||
render () {
|
||||
|
|
|
@ -80,7 +80,7 @@ class Status extends ImmutablePureComponent {
|
|||
containerId: PropTypes.string,
|
||||
id: PropTypes.string,
|
||||
status: ImmutablePropTypes.map,
|
||||
account: ImmutablePropTypes.map,
|
||||
account: ImmutablePropTypes.record,
|
||||
previousId: PropTypes.string,
|
||||
nextInReplyToId: PropTypes.string,
|
||||
rootId: PropTypes.string,
|
||||
|
|
|
@ -49,7 +49,7 @@ class InlineAlert extends PureComponent {
|
|||
class AccountNote extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
account: ImmutablePropTypes.record.isRequired,
|
||||
value: PropTypes.string,
|
||||
onSave: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
|
|
|
@ -15,7 +15,7 @@ const messages = defineMessages({
|
|||
class FeaturedTags extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map,
|
||||
account: ImmutablePropTypes.record,
|
||||
featuredTags: ImmutablePropTypes.list,
|
||||
tagged: PropTypes.string,
|
||||
intl: PropTypes.object.isRequired,
|
||||
|
|
|
@ -8,7 +8,7 @@ import { Icon } from 'flavours/glitch/components/icon';
|
|||
export default class FollowRequestNote extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
account: ImmutablePropTypes.record.isRequired,
|
||||
};
|
||||
|
||||
render () {
|
||||
|
|
|
@ -83,7 +83,7 @@ const dateFormatOptions = {
|
|||
class Header extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map,
|
||||
account: ImmutablePropTypes.record,
|
||||
identity_props: ImmutablePropTypes.list,
|
||||
onFollow: PropTypes.func.isRequired,
|
||||
onBlock: PropTypes.func.isRequired,
|
||||
|
|
|
@ -18,7 +18,7 @@ import MovedNote from './moved_note';
|
|||
class Header extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map,
|
||||
account: ImmutablePropTypes.record,
|
||||
onFollow: PropTypes.func.isRequired,
|
||||
onBlock: PropTypes.func.isRequired,
|
||||
onMention: PropTypes.func.isRequired,
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { revealAccount } from 'flavours/glitch/actions/accounts';
|
||||
import { Button } from 'flavours/glitch/components/button';
|
||||
import { domain } from 'flavours/glitch/initial_state';
|
||||
|
||||
const mapDispatchToProps = (dispatch, { accountId }) => ({
|
||||
|
||||
reveal () {
|
||||
dispatch(revealAccount(accountId));
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
class LimitedAccountHint extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
accountId: PropTypes.string.isRequired,
|
||||
reveal: PropTypes.func,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { reveal } = this.props;
|
||||
|
||||
return (
|
||||
<div className='limited-account-hint'>
|
||||
<p><FormattedMessage id='limited_account_hint.title' defaultMessage='This profile has been hidden by the moderators of {domain}.' values={{ domain }} /></p>
|
||||
<Button onClick={reveal}><FormattedMessage id='limited_account_hint.action' defaultMessage='Show profile anyway' /></Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(() => {}, mapDispatchToProps)(LimitedAccountHint);
|
|
@ -0,0 +1,35 @@
|
|||
import { useCallback } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { revealAccount } from 'flavours/glitch/actions/accounts_typed';
|
||||
import { Button } from 'flavours/glitch/components/button';
|
||||
import { domain } from 'flavours/glitch/initial_state';
|
||||
import { useAppDispatch } from 'flavours/glitch/store';
|
||||
|
||||
export const LimitedAccountHint: React.FC<{ accountId: string }> = ({
|
||||
accountId,
|
||||
}) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const reveal = useCallback(() => {
|
||||
dispatch(revealAccount({ id: accountId }));
|
||||
}, [dispatch, accountId]);
|
||||
|
||||
return (
|
||||
<div className='limited-account-hint'>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='limited_account_hint.title'
|
||||
defaultMessage='This profile has been hidden by the moderators of {domain}.'
|
||||
values={{ domain }}
|
||||
/>
|
||||
</p>
|
||||
<Button onClick={reveal}>
|
||||
<FormattedMessage
|
||||
id='limited_account_hint.action'
|
||||
defaultMessage='Show profile anyway'
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -20,7 +20,7 @@ import { LoadingIndicator } from '../../components/loading_indicator';
|
|||
import StatusList from '../../components/status_list';
|
||||
import Column from '../ui/components/column';
|
||||
|
||||
import LimitedAccountHint from './components/limited_account_hint';
|
||||
import { LimitedAccountHint } from './components/limited_account_hint';
|
||||
import HeaderContainer from './containers/header_container';
|
||||
|
||||
const emptyList = ImmutableList();
|
||||
|
|
|
@ -28,7 +28,7 @@ const messages = defineMessages({
|
|||
class ActionBar extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
account: ImmutablePropTypes.record.isRequired,
|
||||
onLogout: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
|
|
@ -7,7 +7,7 @@ import { DisplayName } from '../../../components/display_name';
|
|||
export default class AutosuggestAccount extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
account: ImmutablePropTypes.record.isRequired,
|
||||
};
|
||||
|
||||
render () {
|
||||
|
|
|
@ -15,7 +15,7 @@ import ActionBar from './action_bar';
|
|||
export default class NavigationBar extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
account: ImmutablePropTypes.record.isRequired,
|
||||
onLogout: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func,
|
||||
};
|
||||
|
|
|
@ -271,6 +271,7 @@ class Search extends PureComponent {
|
|||
}
|
||||
|
||||
_calculateOptions (value) {
|
||||
const { signedIn } = this.context.identity;
|
||||
const trimmedValue = value.trim();
|
||||
const options = [];
|
||||
|
||||
|
@ -295,7 +296,7 @@ class Search extends PureComponent {
|
|||
|
||||
const couldBeStatusSearch = searchEnabled;
|
||||
|
||||
if (couldBeStatusSearch) {
|
||||
if (couldBeStatusSearch && signedIn) {
|
||||
options.push({ key: 'status-search', label: <FormattedMessage id='search.quick_action.status_search' defaultMessage='Posts matching {x}' values={{ x: <mark>{trimmedValue}</mark> }} />, action: this.handleStatusSearch });
|
||||
}
|
||||
|
||||
|
@ -372,7 +373,7 @@ class Search extends PureComponent {
|
|||
|
||||
<h4><FormattedMessage id='search_popout.options' defaultMessage='Search options' /></h4>
|
||||
|
||||
{searchEnabled ? (
|
||||
{searchEnabled && signedIn ? (
|
||||
<div className='search__popout__menu'>
|
||||
{this.defaultOptions.map(({ key, label, action }, i) => (
|
||||
<button key={key} onMouseDown={action} className={classNames('search__popout__menu__item', { selected: selectedOption === ((options.length || recent.size) + i) })}>
|
||||
|
@ -382,7 +383,11 @@ class Search extends PureComponent {
|
|||
</div>
|
||||
) : (
|
||||
<div className='search__popout__menu__message'>
|
||||
<FormattedMessage id='search_popout.full_text_search_disabled_message' defaultMessage='Not available on {domain}.' values={{ domain }} />
|
||||
{searchEnabled ? (
|
||||
<FormattedMessage id='search_popout.full_text_search_logged_out_message' defaultMessage='Only available when logged in.' />
|
||||
) : (
|
||||
<FormattedMessage id='search_popout.full_text_search_disabled_message' defaultMessage='Not available on {domain}.' values={{ domain }} />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -104,7 +104,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
class AccountCard extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
account: ImmutablePropTypes.record.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
onFollow: PropTypes.func.isRequired,
|
||||
onBlock: PropTypes.func.isRequired,
|
||||
|
|
|
@ -18,7 +18,7 @@ const messages = defineMessages({
|
|||
class AccountAuthorize extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
account: ImmutablePropTypes.record.isRequired,
|
||||
onAuthorize: PropTypes.func.isRequired,
|
||||
onReject: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
|
|
|
@ -23,7 +23,7 @@ import { LoadingIndicator } from '../../components/loading_indicator';
|
|||
import ScrollableList from '../../components/scrollable_list';
|
||||
import AccountContainer from '../../containers/account_container';
|
||||
import ProfileColumnHeader from '../account/components/profile_column_header';
|
||||
import LimitedAccountHint from '../account_timeline/components/limited_account_hint';
|
||||
import { LimitedAccountHint } from '../account_timeline/components/limited_account_hint';
|
||||
import HeaderContainer from '../account_timeline/containers/header_container';
|
||||
import Column from '../ui/components/column';
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ import { LoadingIndicator } from '../../components/loading_indicator';
|
|||
import ScrollableList from '../../components/scrollable_list';
|
||||
import AccountContainer from '../../containers/account_container';
|
||||
import ProfileColumnHeader from '../account/components/profile_column_header';
|
||||
import LimitedAccountHint from '../account_timeline/components/limited_account_hint';
|
||||
import { LimitedAccountHint } from '../account_timeline/components/limited_account_hint';
|
||||
import HeaderContainer from '../account_timeline/containers/header_container';
|
||||
import Column from '../ui/components/column';
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ const makeMapStateToProps = () => {
|
|||
class Account extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
account: ImmutablePropTypes.record.isRequired,
|
||||
};
|
||||
|
||||
render () {
|
||||
|
|
|
@ -36,7 +36,7 @@ const mapDispatchToProps = (dispatch, { accountId }) => ({
|
|||
class Account extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
account: ImmutablePropTypes.record.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
onRemove: PropTypes.func.isRequired,
|
||||
onAdd: PropTypes.func.isRequired,
|
||||
|
|
|
@ -27,7 +27,7 @@ const messages = defineMessages({
|
|||
class FollowRequest extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
account: ImmutablePropTypes.record.isRequired,
|
||||
onAuthorize: PropTypes.func.isRequired,
|
||||
onReject: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
|
|
|
@ -20,7 +20,7 @@ const messages = defineMessages({
|
|||
class Report extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
account: ImmutablePropTypes.record.isRequired,
|
||||
report: ImmutablePropTypes.map.isRequired,
|
||||
hidden: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
|
|
|
@ -42,7 +42,7 @@ const mapStateToProps = () => {
|
|||
class Onboarding extends ImmutablePureComponent {
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
account: ImmutablePropTypes.map,
|
||||
account: ImmutablePropTypes.record,
|
||||
multiColumn: PropTypes.bool,
|
||||
...WithRouterPropTypes,
|
||||
};
|
||||
|
|
|
@ -145,7 +145,7 @@ class Share extends PureComponent {
|
|||
|
||||
static propTypes = {
|
||||
onBack: PropTypes.func,
|
||||
account: ImmutablePropTypes.map,
|
||||
account: ImmutablePropTypes.record,
|
||||
multiColumn: PropTypes.bool,
|
||||
intl: PropTypes.object,
|
||||
};
|
||||
|
|
|
@ -25,7 +25,7 @@ class Header extends ImmutablePureComponent {
|
|||
static propTypes = {
|
||||
accountId: PropTypes.string.isRequired,
|
||||
statusId: PropTypes.string.isRequired,
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
account: ImmutablePropTypes.record.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
|
|
@ -20,7 +20,7 @@ class Thanks extends PureComponent {
|
|||
static propTypes = {
|
||||
submitted: PropTypes.bool,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
account: ImmutablePropTypes.record.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
|
|
@ -109,7 +109,7 @@ class FocalPointModal extends ImmutablePureComponent {
|
|||
|
||||
static propTypes = {
|
||||
media: ImmutablePropTypes.map.isRequired,
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
account: ImmutablePropTypes.record.isRequired,
|
||||
isUploadingThumbnail: PropTypes.bool,
|
||||
onSave: PropTypes.func.isRequired,
|
||||
onChangeDescription: PropTypes.func.isRequired,
|
||||
|
|
|
@ -40,7 +40,7 @@ class ReportModal extends ImmutablePureComponent {
|
|||
statusId: PropTypes.string,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
account: ImmutablePropTypes.record.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
|
|
|
@ -1,43 +1,5 @@
|
|||
// @ts-check
|
||||
|
||||
/**
|
||||
* @typedef Emoji
|
||||
* @property {string} shortcode
|
||||
* @property {string} static_url
|
||||
* @property {string} url
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef AccountField
|
||||
* @property {string} name
|
||||
* @property {string} value
|
||||
* @property {string} verified_at
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef Account
|
||||
* @property {string} acct
|
||||
* @property {string} avatar
|
||||
* @property {string} avatar_static
|
||||
* @property {boolean} bot
|
||||
* @property {string} created_at
|
||||
* @property {boolean=} discoverable
|
||||
* @property {string} display_name
|
||||
* @property {Emoji[]} emojis
|
||||
* @property {AccountField[]} fields
|
||||
* @property {number} followers_count
|
||||
* @property {number} following_count
|
||||
* @property {boolean} group
|
||||
* @property {string} header
|
||||
* @property {string} header_static
|
||||
* @property {string} id
|
||||
* @property {string=} last_status_at
|
||||
* @property {boolean} locked
|
||||
* @property {string} note
|
||||
* @property {number} statuses_count
|
||||
* @property {string} url
|
||||
* @property {string} username
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {[code: string, name: string, localName: string]} InitialStateLanguage
|
||||
|
@ -101,7 +63,7 @@ export const hasMultiColumnPath = initialPath === '/'
|
|||
|
||||
/**
|
||||
* @typedef InitialState
|
||||
* @property {Record<string, Account>} accounts
|
||||
* @property {Record<string, import("./api_types/accounts").ApiAccountJSON>} accounts
|
||||
* @property {InitialStateLanguage[]} languages
|
||||
* @property {boolean=} critical_updates_pending
|
||||
* @property {InitialStateMeta} meta
|
||||
|
|
149
app/javascript/flavours/glitch/models/account.ts
Normal file
149
app/javascript/flavours/glitch/models/account.ts
Normal file
|
@ -0,0 +1,149 @@
|
|||
import type { RecordOf } from 'immutable';
|
||||
import { List, Record as ImmutableRecord } from 'immutable';
|
||||
|
||||
import escapeTextContentForBrowser from 'escape-html';
|
||||
|
||||
import type {
|
||||
ApiAccountFieldJSON,
|
||||
ApiAccountRoleJSON,
|
||||
ApiAccountJSON,
|
||||
} from 'flavours/glitch/api_types/accounts';
|
||||
import type { ApiCustomEmojiJSON } from 'flavours/glitch/api_types/custom_emoji';
|
||||
import emojify from 'flavours/glitch/features/emoji/emoji';
|
||||
import { unescapeHTML } from 'flavours/glitch/utils/html';
|
||||
|
||||
import { CustomEmojiFactory } from './custom_emoji';
|
||||
import type { CustomEmoji } from './custom_emoji';
|
||||
|
||||
// AccountField
|
||||
interface AccountFieldShape extends Required<ApiAccountFieldJSON> {
|
||||
name_emojified: string;
|
||||
value_emojified: string;
|
||||
value_plain: string | null;
|
||||
}
|
||||
|
||||
type AccountField = RecordOf<AccountFieldShape>;
|
||||
|
||||
const AccountFieldFactory = ImmutableRecord<AccountFieldShape>({
|
||||
name: '',
|
||||
value: '',
|
||||
verified_at: null,
|
||||
name_emojified: '',
|
||||
value_emojified: '',
|
||||
value_plain: null,
|
||||
});
|
||||
|
||||
// AccountRole
|
||||
export type AccountRoleShape = ApiAccountRoleJSON;
|
||||
export type AccountRole = RecordOf<AccountRoleShape>;
|
||||
|
||||
const AccountRoleFactory = ImmutableRecord<AccountRoleShape>({
|
||||
color: '',
|
||||
id: '',
|
||||
name: '',
|
||||
});
|
||||
|
||||
// Account
|
||||
export interface AccountShape
|
||||
extends Required<
|
||||
Omit<ApiAccountJSON, 'emojis' | 'fields' | 'roles' | 'moved'>
|
||||
> {
|
||||
emojis: List<CustomEmoji>;
|
||||
fields: List<AccountField>;
|
||||
roles: List<AccountRole>;
|
||||
display_name_html: string;
|
||||
note_emojified: string;
|
||||
note_plain: string | null;
|
||||
hidden: boolean;
|
||||
moved: string | null;
|
||||
}
|
||||
|
||||
export type Account = RecordOf<AccountShape>;
|
||||
|
||||
export const accountDefaultValues: AccountShape = {
|
||||
acct: '',
|
||||
avatar: '',
|
||||
avatar_static: '',
|
||||
bot: false,
|
||||
created_at: '',
|
||||
discoverable: false,
|
||||
display_name: '',
|
||||
display_name_html: '',
|
||||
emojis: List<CustomEmoji>(),
|
||||
fields: List<AccountField>(),
|
||||
group: false,
|
||||
header: '',
|
||||
header_static: '',
|
||||
id: '',
|
||||
last_status_at: '',
|
||||
locked: false,
|
||||
noindex: false,
|
||||
note: '',
|
||||
note_emojified: '',
|
||||
note_plain: 'string',
|
||||
roles: List<AccountRole>(),
|
||||
uri: '',
|
||||
url: '',
|
||||
username: '',
|
||||
followers_count: 0,
|
||||
following_count: 0,
|
||||
statuses_count: 0,
|
||||
hidden: false,
|
||||
suspended: false,
|
||||
memorial: false,
|
||||
limited: false,
|
||||
moved: null,
|
||||
};
|
||||
|
||||
const AccountFactory = ImmutableRecord<AccountShape>(accountDefaultValues);
|
||||
|
||||
type EmojiMap = Record<string, ApiCustomEmojiJSON>;
|
||||
|
||||
function makeEmojiMap(emojis: ApiCustomEmojiJSON[]) {
|
||||
return emojis.reduce<EmojiMap>((obj, emoji) => {
|
||||
obj[`:${emoji.shortcode}:`] = emoji;
|
||||
return obj;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function createAccountField(
|
||||
jsonField: ApiAccountFieldJSON,
|
||||
emojiMap: EmojiMap,
|
||||
) {
|
||||
return AccountFieldFactory({
|
||||
...jsonField,
|
||||
name_emojified: emojify(
|
||||
escapeTextContentForBrowser(jsonField.name),
|
||||
emojiMap,
|
||||
),
|
||||
value_emojified: emojify(jsonField.value, emojiMap),
|
||||
value_plain: unescapeHTML(jsonField.value),
|
||||
});
|
||||
}
|
||||
|
||||
export function createAccountFromServerJSON(serverJSON: ApiAccountJSON) {
|
||||
const { moved, ...accountJSON } = serverJSON;
|
||||
|
||||
const emojiMap = makeEmojiMap(accountJSON.emojis);
|
||||
|
||||
const displayName =
|
||||
accountJSON.display_name.trim().length === 0
|
||||
? accountJSON.username
|
||||
: accountJSON.display_name;
|
||||
|
||||
return AccountFactory({
|
||||
...accountJSON,
|
||||
moved: moved?.id,
|
||||
fields: List(
|
||||
serverJSON.fields.map((field) => createAccountField(field, emojiMap)),
|
||||
),
|
||||
emojis: List(serverJSON.emojis.map((emoji) => CustomEmojiFactory(emoji))),
|
||||
roles: List(serverJSON.roles?.map((role) => AccountRoleFactory(role))),
|
||||
display_name_html: emojify(
|
||||
escapeTextContentForBrowser(displayName),
|
||||
emojiMap,
|
||||
),
|
||||
note_emojified: emojify(accountJSON.note, emojiMap),
|
||||
note_plain: unescapeHTML(accountJSON.note),
|
||||
});
|
||||
}
|
15
app/javascript/flavours/glitch/models/custom_emoji.ts
Normal file
15
app/javascript/flavours/glitch/models/custom_emoji.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import type { RecordOf } from 'immutable';
|
||||
import { Record } from 'immutable';
|
||||
|
||||
import type { ApiCustomEmojiJSON } from 'flavours/glitch/api_types/custom_emoji';
|
||||
|
||||
type CustomEmojiShape = Required<ApiCustomEmojiJSON>; // no changes from server shape
|
||||
export type CustomEmoji = RecordOf<CustomEmojiShape>;
|
||||
|
||||
export const CustomEmojiFactory = Record<CustomEmojiShape>({
|
||||
shortcode: '',
|
||||
static_url: '',
|
||||
url: '',
|
||||
category: '',
|
||||
visible_in_picker: false,
|
||||
});
|
29
app/javascript/flavours/glitch/models/relationship.ts
Normal file
29
app/javascript/flavours/glitch/models/relationship.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import type { RecordOf } from 'immutable';
|
||||
import { Record } from 'immutable';
|
||||
|
||||
import type { ApiRelationshipJSON } from 'flavours/glitch/api_types/relationships';
|
||||
|
||||
type RelationshipShape = Required<ApiRelationshipJSON>; // no changes from server shape
|
||||
export type Relationship = RecordOf<RelationshipShape>;
|
||||
|
||||
const RelationshipFactory = Record<RelationshipShape>({
|
||||
blocked_by: false,
|
||||
blocking: false,
|
||||
domain_blocking: false,
|
||||
endorsed: false,
|
||||
followed_by: false,
|
||||
following: false,
|
||||
id: '',
|
||||
languages: null,
|
||||
muting_notifications: false,
|
||||
muting: false,
|
||||
note: '',
|
||||
notifying: false,
|
||||
requested_by: false,
|
||||
requested: false,
|
||||
showing_reblogs: false,
|
||||
});
|
||||
|
||||
export function createRelationship(attributes: Partial<RelationshipShape>) {
|
||||
return RelationshipFactory(attributes);
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
import { Map as ImmutableMap, fromJS } from 'immutable';
|
||||
|
||||
import { ACCOUNT_REVEAL } from 'flavours/glitch/actions/accounts';
|
||||
import { ACCOUNT_IMPORT, ACCOUNTS_IMPORT } from 'flavours/glitch/actions/importer';
|
||||
|
||||
const initialState = ImmutableMap();
|
||||
|
||||
const normalizeAccount = (state, account) => {
|
||||
account = { ...account };
|
||||
|
||||
delete account.followers_count;
|
||||
delete account.following_count;
|
||||
delete account.statuses_count;
|
||||
|
||||
account.hidden = state.getIn([account.id, 'hidden']) === false ? false : account.limited;
|
||||
|
||||
return state.set(account.id, fromJS(account));
|
||||
};
|
||||
|
||||
const normalizeAccounts = (state, accounts) => {
|
||||
accounts.forEach(account => {
|
||||
state = normalizeAccount(state, account);
|
||||
});
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
export default function accounts(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case ACCOUNT_IMPORT:
|
||||
return normalizeAccount(state, action.account);
|
||||
case ACCOUNTS_IMPORT:
|
||||
return normalizeAccounts(state, action.accounts);
|
||||
case ACCOUNT_REVEAL:
|
||||
return state.setIn([action.id, 'hidden'], false);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
84
app/javascript/flavours/glitch/reducers/accounts.ts
Normal file
84
app/javascript/flavours/glitch/reducers/accounts.ts
Normal file
|
@ -0,0 +1,84 @@
|
|||
import { Map as ImmutableMap } from 'immutable';
|
||||
|
||||
import type { Reducer } from 'redux';
|
||||
|
||||
import {
|
||||
followAccountSuccess,
|
||||
unfollowAccountSuccess,
|
||||
importAccounts,
|
||||
revealAccount,
|
||||
} from 'flavours/glitch/actions/accounts_typed';
|
||||
import type { ApiAccountJSON } from 'flavours/glitch/api_types/accounts';
|
||||
import { me } from 'flavours/glitch/initial_state';
|
||||
import type { Account } from 'flavours/glitch/models/account';
|
||||
import { createAccountFromServerJSON } from 'flavours/glitch/models/account';
|
||||
|
||||
const initialState = ImmutableMap<string, Account>();
|
||||
|
||||
const normalizeAccount = (
|
||||
state: typeof initialState,
|
||||
account: ApiAccountJSON,
|
||||
) => {
|
||||
return state.set(
|
||||
account.id,
|
||||
createAccountFromServerJSON(account).set(
|
||||
'hidden',
|
||||
state.get(account.id)?.hidden === false
|
||||
? false
|
||||
: account.limited || false,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const normalizeAccounts = (
|
||||
state: typeof initialState,
|
||||
accounts: ApiAccountJSON[],
|
||||
) => {
|
||||
accounts.forEach((account) => {
|
||||
state = normalizeAccount(state, account);
|
||||
});
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
function getCurrentUser() {
|
||||
if (!me)
|
||||
throw new Error(
|
||||
'No current user (me) defined when calling `accountsReducer`',
|
||||
);
|
||||
|
||||
return me;
|
||||
}
|
||||
|
||||
export const accountsReducer: Reducer<typeof initialState> = (
|
||||
state = initialState,
|
||||
action,
|
||||
) => {
|
||||
if (revealAccount.match(action))
|
||||
return state.setIn([action.payload.id, 'hidden'], false);
|
||||
else if (importAccounts.match(action))
|
||||
return normalizeAccounts(state, action.payload.accounts);
|
||||
else if (followAccountSuccess.match(action)) {
|
||||
return state
|
||||
.update(
|
||||
action.payload.relationship.id,
|
||||
(account) => account?.update('followers_count', (n) => n + 1),
|
||||
)
|
||||
.update(
|
||||
getCurrentUser(),
|
||||
(account) => account?.update('following_count', (n) => n + 1),
|
||||
);
|
||||
} else if (unfollowAccountSuccess.match(action))
|
||||
return state
|
||||
.update(
|
||||
action.payload.relationship.id,
|
||||
(account) =>
|
||||
account?.update('followers_count', (n) => Math.max(0, n - 1)),
|
||||
)
|
||||
.update(
|
||||
getCurrentUser(),
|
||||
(account) =>
|
||||
account?.update('following_count', (n) => Math.max(0, n - 1)),
|
||||
);
|
||||
else return state;
|
||||
};
|
|
@ -1,49 +0,0 @@
|
|||
import { Map as ImmutableMap, fromJS } from 'immutable';
|
||||
|
||||
import { me } from 'flavours/glitch/initial_state';
|
||||
|
||||
import {
|
||||
ACCOUNT_FOLLOW_SUCCESS,
|
||||
ACCOUNT_UNFOLLOW_SUCCESS,
|
||||
} from '../actions/accounts';
|
||||
import { ACCOUNT_IMPORT, ACCOUNTS_IMPORT } from '../actions/importer';
|
||||
|
||||
const normalizeAccount = (state, account) => state.set(account.id, fromJS({
|
||||
followers_count: account.followers_count,
|
||||
following_count: account.following_count,
|
||||
statuses_count: account.statuses_count,
|
||||
}));
|
||||
|
||||
const normalizeAccounts = (state, accounts) => {
|
||||
accounts.forEach(account => {
|
||||
state = normalizeAccount(state, account);
|
||||
});
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
const incrementFollowers = (state, accountId) =>
|
||||
state.updateIn([accountId, 'followers_count'], num => num + 1)
|
||||
.updateIn([me, 'following_count'], num => num + 1);
|
||||
|
||||
const decrementFollowers = (state, accountId) =>
|
||||
state.updateIn([accountId, 'followers_count'], num => Math.max(0, num - 1))
|
||||
.updateIn([me, 'following_count'], num => Math.max(0, num - 1));
|
||||
|
||||
const initialState = ImmutableMap();
|
||||
|
||||
export default function accountsCounters(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case ACCOUNT_IMPORT:
|
||||
return normalizeAccount(state, action.account);
|
||||
case ACCOUNTS_IMPORT:
|
||||
return normalizeAccounts(state, action.accounts);
|
||||
case ACCOUNT_FOLLOW_SUCCESS:
|
||||
return action.alreadyFollowing ? state :
|
||||
incrementFollowers(state, action.relationship.id);
|
||||
case ACCOUNT_UNFOLLOW_SUCCESS:
|
||||
return decrementFollowers(state, action.relationship.id);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import { Map as ImmutableMap } from 'immutable';
|
||||
|
||||
import { ACCOUNT_LOOKUP_FAIL } from '../actions/accounts';
|
||||
import { ACCOUNT_IMPORT, ACCOUNTS_IMPORT } from '../actions/importer';
|
||||
import { importAccounts } from '../actions/accounts_typed';
|
||||
|
||||
export const normalizeForLookup = str => str.toLowerCase();
|
||||
|
||||
|
@ -11,10 +11,8 @@ export default function accountsMap(state = initialState, action) {
|
|||
switch(action.type) {
|
||||
case ACCOUNT_LOOKUP_FAIL:
|
||||
return action.error?.response?.status === 404 ? state.set(normalizeForLookup(action.acct), null) : state;
|
||||
case ACCOUNT_IMPORT:
|
||||
return state.set(normalizeForLookup(action.account.acct), action.account.id);
|
||||
case ACCOUNTS_IMPORT:
|
||||
return state.withMutations(map => action.accounts.forEach(account => map.set(normalizeForLookup(account.acct), account.id)));
|
||||
case importAccounts.type:
|
||||
return state.withMutations(map => action.payload.accounts.forEach(account => map.set(normalizeForLookup(account.acct), account.id)));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
||||
|
||||
import {
|
||||
ACCOUNT_BLOCK_SUCCESS,
|
||||
ACCOUNT_MUTE_SUCCESS,
|
||||
blockAccountSuccess,
|
||||
muteAccountSuccess,
|
||||
} from '../actions/accounts';
|
||||
import { CONTEXT_FETCH_SUCCESS } from '../actions/statuses';
|
||||
import { TIMELINE_DELETE, TIMELINE_UPDATE } from '../actions/timelines';
|
||||
|
@ -92,9 +92,9 @@ const updateContext = (state, status) => {
|
|||
|
||||
export default function replies(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case ACCOUNT_BLOCK_SUCCESS:
|
||||
case ACCOUNT_MUTE_SUCCESS:
|
||||
return filterContexts(state, action.relationship, action.statuses);
|
||||
case blockAccountSuccess.type:
|
||||
case muteAccountSuccess.type:
|
||||
return filterContexts(state, action.payload.relationship, action.payload.statuses);
|
||||
case CONTEXT_FETCH_SUCCESS:
|
||||
return normalizeContext(state, action.id, action.ancestors, action.descendants);
|
||||
case TIMELINE_DELETE:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
||||
|
||||
import { ACCOUNT_BLOCK_SUCCESS, ACCOUNT_MUTE_SUCCESS } from 'flavours/glitch/actions/accounts';
|
||||
import { DOMAIN_BLOCK_SUCCESS } from 'flavours/glitch/actions/domain_blocks';
|
||||
import { blockAccountSuccess, muteAccountSuccess } from 'flavours/glitch/actions/accounts';
|
||||
import { blockDomainSuccess } from 'flavours/glitch/actions/domain_blocks';
|
||||
|
||||
import {
|
||||
CONVERSATIONS_MOUNT,
|
||||
|
@ -105,11 +105,11 @@ export default function conversations(state = initialState, action) {
|
|||
|
||||
return item;
|
||||
}));
|
||||
case ACCOUNT_BLOCK_SUCCESS:
|
||||
case ACCOUNT_MUTE_SUCCESS:
|
||||
return filterConversations(state, [action.relationship.id]);
|
||||
case DOMAIN_BLOCK_SUCCESS:
|
||||
return filterConversations(state, action.accounts);
|
||||
case blockAccountSuccess.type:
|
||||
case muteAccountSuccess.type:
|
||||
return filterConversations(state, [action.payload.relationship.id]);
|
||||
case blockDomainSuccess.type:
|
||||
return filterConversations(state, action.payload.accounts);
|
||||
case CONVERSATIONS_DELETE_SUCCESS:
|
||||
return state.update('items', list => list.filterNot(item => item.get('id') === action.id));
|
||||
default:
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutabl
|
|||
import {
|
||||
DOMAIN_BLOCKS_FETCH_SUCCESS,
|
||||
DOMAIN_BLOCKS_EXPAND_SUCCESS,
|
||||
DOMAIN_UNBLOCK_SUCCESS,
|
||||
unblockDomainSuccess
|
||||
} from '../actions/domain_blocks';
|
||||
|
||||
const initialState = ImmutableMap({
|
||||
|
@ -18,8 +18,8 @@ export default function domainLists(state = initialState, action) {
|
|||
return state.setIn(['blocks', 'items'], ImmutableOrderedSet(action.domains)).setIn(['blocks', 'next'], action.next);
|
||||
case DOMAIN_BLOCKS_EXPAND_SUCCESS:
|
||||
return state.updateIn(['blocks', 'items'], set => set.union(action.domains)).setIn(['blocks', 'next'], action.next);
|
||||
case DOMAIN_UNBLOCK_SUCCESS:
|
||||
return state.updateIn(['blocks', 'items'], set => set.delete(action.domain));
|
||||
case unblockDomainSuccess.type:
|
||||
return state.updateIn(['blocks', 'items'], set => set.delete(action.payload.domain));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -3,8 +3,7 @@ import { Record as ImmutableRecord } from 'immutable';
|
|||
import { loadingBarReducer } from 'react-redux-loading-bar';
|
||||
import { combineReducers } from 'redux-immutable';
|
||||
|
||||
import accounts from './accounts';
|
||||
import accounts_counters from './accounts_counters';
|
||||
import { accountsReducer } from './accounts';
|
||||
import accounts_map from './accounts_map';
|
||||
import alerts from './alerts';
|
||||
import announcements from './announcements';
|
||||
|
@ -34,7 +33,7 @@ import picture_in_picture from './picture_in_picture';
|
|||
import pinnedAccountsEditor from './pinned_accounts_editor';
|
||||
import polls from './polls';
|
||||
import push_notifications from './push_notifications';
|
||||
import relationships from './relationships';
|
||||
import { relationshipsReducer } from './relationships';
|
||||
import search from './search';
|
||||
import server from './server';
|
||||
import settings from './settings';
|
||||
|
@ -57,11 +56,10 @@ const reducers = {
|
|||
user_lists,
|
||||
domain_lists,
|
||||
status_lists,
|
||||
accounts,
|
||||
accounts_counters,
|
||||
accounts: accountsReducer,
|
||||
accounts_map,
|
||||
statuses,
|
||||
relationships,
|
||||
relationships: relationshipsReducer,
|
||||
settings,
|
||||
local_settings,
|
||||
push_notifications,
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { fromJS, Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
||||
|
||||
import { DOMAIN_BLOCK_SUCCESS } from 'flavours/glitch/actions/domain_blocks';
|
||||
import { blockDomainSuccess } from 'flavours/glitch/actions/domain_blocks';
|
||||
|
||||
import {
|
||||
ACCOUNT_BLOCK_SUCCESS,
|
||||
ACCOUNT_MUTE_SUCCESS,
|
||||
FOLLOW_REQUEST_AUTHORIZE_SUCCESS,
|
||||
FOLLOW_REQUEST_REJECT_SUCCESS,
|
||||
authorizeFollowRequestSuccess,
|
||||
blockAccountSuccess,
|
||||
muteAccountSuccess,
|
||||
rejectFollowRequestSuccess,
|
||||
} from '../actions/accounts';
|
||||
import {
|
||||
MARKERS_FETCH_SUCCESS,
|
||||
|
@ -15,7 +15,7 @@ import {
|
|||
NOTIFICATIONS_MOUNT,
|
||||
NOTIFICATIONS_UNMOUNT,
|
||||
NOTIFICATIONS_SET_VISIBILITY,
|
||||
NOTIFICATIONS_UPDATE,
|
||||
notificationsUpdate,
|
||||
NOTIFICATIONS_EXPAND_SUCCESS,
|
||||
NOTIFICATIONS_EXPAND_REQUEST,
|
||||
NOTIFICATIONS_EXPAND_FAIL,
|
||||
|
@ -316,19 +316,19 @@ export default function notifications(state = initialState, action) {
|
|||
return state.set('items', ImmutableList()).set('pendingItems', ImmutableList()).set('hasMore', true);
|
||||
case NOTIFICATIONS_SCROLL_TOP:
|
||||
return updateTop(state, action.top);
|
||||
case NOTIFICATIONS_UPDATE:
|
||||
return normalizeNotification(state, action.notification, action.usePendingItems);
|
||||
case notificationsUpdate.type:
|
||||
return normalizeNotification(state, action.payload.notification, action.payload.usePendingItems);
|
||||
case NOTIFICATIONS_EXPAND_SUCCESS:
|
||||
return expandNormalizedNotifications(state, action.notifications, action.next, action.isLoadingMore, action.isLoadingRecent, action.usePendingItems);
|
||||
case ACCOUNT_BLOCK_SUCCESS:
|
||||
return filterNotifications(state, [action.relationship.id]);
|
||||
case ACCOUNT_MUTE_SUCCESS:
|
||||
return action.relationship.muting_notifications ? filterNotifications(state, [action.relationship.id]) : state;
|
||||
case DOMAIN_BLOCK_SUCCESS:
|
||||
return filterNotifications(state, action.accounts);
|
||||
case FOLLOW_REQUEST_AUTHORIZE_SUCCESS:
|
||||
case FOLLOW_REQUEST_REJECT_SUCCESS:
|
||||
return filterNotifications(state, [action.id], 'follow_request');
|
||||
case blockAccountSuccess.type:
|
||||
return filterNotifications(state, [action.payload.relationship.id]);
|
||||
case muteAccountSuccess.type:
|
||||
return action.payload.relationship.muting_notifications ? filterNotifications(state, [action.payload.relationship.id]) : state;
|
||||
case blockDomainSuccess.type:
|
||||
return filterNotifications(state, action.payload.accounts);
|
||||
case authorizeFollowRequestSuccess.type:
|
||||
case rejectFollowRequestSuccess.type:
|
||||
return filterNotifications(state, [action.payload.id], 'follow_request');
|
||||
case NOTIFICATIONS_CLEAR:
|
||||
return state.set('items', ImmutableList()).set('pendingItems', ImmutableList()).set('hasMore', false);
|
||||
case TIMELINE_DELETE:
|
||||
|
|
|
@ -8,8 +8,8 @@ import {
|
|||
PINNED_ACCOUNTS_SUGGESTIONS_FETCH_SUCCESS,
|
||||
PINNED_ACCOUNTS_EDITOR_SUGGESTIONS_CLEAR,
|
||||
PINNED_ACCOUNTS_EDITOR_SUGGESTIONS_CHANGE,
|
||||
ACCOUNT_PIN_SUCCESS,
|
||||
ACCOUNT_UNPIN_SUCCESS,
|
||||
pinAccountSuccess,
|
||||
unpinAccountSuccess,
|
||||
} from '../actions/accounts';
|
||||
|
||||
const initialState = ImmutableMap({
|
||||
|
@ -48,10 +48,10 @@ export default function listEditorReducer(state = initialState, action) {
|
|||
map.set('items', ImmutableList());
|
||||
map.set('value', '');
|
||||
}));
|
||||
case ACCOUNT_PIN_SUCCESS:
|
||||
return state.updateIn(['accounts', 'items'], list => list.unshift(action.relationship.id));
|
||||
case ACCOUNT_UNPIN_SUCCESS:
|
||||
return state.updateIn(['accounts', 'items'], list => list.filterNot(item => item === action.relationship.id));
|
||||
case pinAccountSuccess.type:
|
||||
return state.updateIn(['accounts', 'items'], list => list.unshift(action.payload.relationship.id));
|
||||
case unpinAccountSuccess.type:
|
||||
return state.updateIn(['accounts', 'items'], list => list.filterNot(item => item === action.payload.relationship.id));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
import { Map as ImmutableMap, fromJS } from 'immutable';
|
||||
|
||||
import {
|
||||
submitAccountNote,
|
||||
} from '../actions/account_notes';
|
||||
import {
|
||||
ACCOUNT_FOLLOW_SUCCESS,
|
||||
ACCOUNT_FOLLOW_REQUEST,
|
||||
ACCOUNT_FOLLOW_FAIL,
|
||||
ACCOUNT_UNFOLLOW_SUCCESS,
|
||||
ACCOUNT_UNFOLLOW_REQUEST,
|
||||
ACCOUNT_UNFOLLOW_FAIL,
|
||||
ACCOUNT_BLOCK_SUCCESS,
|
||||
ACCOUNT_UNBLOCK_SUCCESS,
|
||||
ACCOUNT_MUTE_SUCCESS,
|
||||
ACCOUNT_UNMUTE_SUCCESS,
|
||||
ACCOUNT_PIN_SUCCESS,
|
||||
ACCOUNT_UNPIN_SUCCESS,
|
||||
RELATIONSHIPS_FETCH_SUCCESS,
|
||||
FOLLOW_REQUEST_AUTHORIZE_SUCCESS,
|
||||
FOLLOW_REQUEST_REJECT_SUCCESS,
|
||||
} from '../actions/accounts';
|
||||
import {
|
||||
DOMAIN_BLOCK_SUCCESS,
|
||||
DOMAIN_UNBLOCK_SUCCESS,
|
||||
} from '../actions/domain_blocks';
|
||||
import {
|
||||
NOTIFICATIONS_UPDATE,
|
||||
} from '../actions/notifications';
|
||||
|
||||
|
||||
const normalizeRelationship = (state, relationship) => state.set(relationship.id, fromJS(relationship));
|
||||
|
||||
const normalizeRelationships = (state, relationships) => {
|
||||
relationships.forEach(relationship => {
|
||||
state = normalizeRelationship(state, relationship);
|
||||
});
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
const setDomainBlocking = (state, accounts, blocking) => {
|
||||
return state.withMutations(map => {
|
||||
accounts.forEach(id => {
|
||||
map.setIn([id, 'domain_blocking'], blocking);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const initialState = ImmutableMap();
|
||||
|
||||
export default function relationships(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case FOLLOW_REQUEST_AUTHORIZE_SUCCESS:
|
||||
return state.setIn([action.id, 'followed_by'], true).setIn([action.id, 'requested_by'], false);
|
||||
case FOLLOW_REQUEST_REJECT_SUCCESS:
|
||||
return state.setIn([action.id, 'followed_by'], false).setIn([action.id, 'requested_by'], false);
|
||||
case NOTIFICATIONS_UPDATE:
|
||||
return action.notification.type === 'follow_request' ? state.setIn([action.notification.account.id, 'requested_by'], true) : state;
|
||||
case ACCOUNT_FOLLOW_REQUEST:
|
||||
return state.getIn([action.id, 'following']) ? state : state.setIn([action.id, action.locked ? 'requested' : 'following'], true);
|
||||
case ACCOUNT_FOLLOW_FAIL:
|
||||
return state.setIn([action.id, action.locked ? 'requested' : 'following'], false);
|
||||
case ACCOUNT_UNFOLLOW_REQUEST:
|
||||
return state.setIn([action.id, 'following'], false);
|
||||
case ACCOUNT_UNFOLLOW_FAIL:
|
||||
return state.setIn([action.id, 'following'], true);
|
||||
case ACCOUNT_FOLLOW_SUCCESS:
|
||||
case ACCOUNT_UNFOLLOW_SUCCESS:
|
||||
case ACCOUNT_BLOCK_SUCCESS:
|
||||
case ACCOUNT_UNBLOCK_SUCCESS:
|
||||
case ACCOUNT_MUTE_SUCCESS:
|
||||
case ACCOUNT_UNMUTE_SUCCESS:
|
||||
case ACCOUNT_PIN_SUCCESS:
|
||||
case ACCOUNT_UNPIN_SUCCESS:
|
||||
return normalizeRelationship(state, action.relationship);
|
||||
case RELATIONSHIPS_FETCH_SUCCESS:
|
||||
return normalizeRelationships(state, action.relationships);
|
||||
case submitAccountNote.fulfilled:
|
||||
return normalizeRelationship(state, action.payload.relationship);
|
||||
case DOMAIN_BLOCK_SUCCESS:
|
||||
return setDomainBlocking(state, action.accounts, true);
|
||||
case DOMAIN_UNBLOCK_SUCCESS:
|
||||
return setDomainBlocking(state, action.accounts, false);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
123
app/javascript/flavours/glitch/reducers/relationships.ts
Normal file
123
app/javascript/flavours/glitch/reducers/relationships.ts
Normal file
|
@ -0,0 +1,123 @@
|
|||
import { Map as ImmutableMap } from 'immutable';
|
||||
|
||||
import { isFulfilled } from '@reduxjs/toolkit';
|
||||
import type { Reducer } from 'redux';
|
||||
|
||||
import type { ApiRelationshipJSON } from 'flavours/glitch/api_types/relationships';
|
||||
import type { Account } from 'flavours/glitch/models/account';
|
||||
import { createRelationship } from 'flavours/glitch/models/relationship';
|
||||
import type { Relationship } from 'flavours/glitch/models/relationship';
|
||||
|
||||
import { submitAccountNote } from '../actions/account_notes';
|
||||
import {
|
||||
followAccountSuccess,
|
||||
unfollowAccountSuccess,
|
||||
authorizeFollowRequestSuccess,
|
||||
rejectFollowRequestSuccess,
|
||||
followAccountRequest,
|
||||
followAccountFail,
|
||||
unfollowAccountRequest,
|
||||
unfollowAccountFail,
|
||||
blockAccountSuccess,
|
||||
unblockAccountSuccess,
|
||||
muteAccountSuccess,
|
||||
unmuteAccountSuccess,
|
||||
pinAccountSuccess,
|
||||
unpinAccountSuccess,
|
||||
fetchRelationshipsSuccess,
|
||||
} from '../actions/accounts_typed';
|
||||
import {
|
||||
blockDomainSuccess,
|
||||
unblockDomainSuccess,
|
||||
} from '../actions/domain_blocks_typed';
|
||||
import { notificationsUpdate } from '../actions/notifications_typed';
|
||||
|
||||
const initialState = ImmutableMap<string, Relationship>();
|
||||
type State = typeof initialState;
|
||||
|
||||
const normalizeRelationship = (
|
||||
state: State,
|
||||
relationship: ApiRelationshipJSON,
|
||||
) => state.set(relationship.id, createRelationship(relationship));
|
||||
|
||||
const normalizeRelationships = (
|
||||
state: State,
|
||||
relationships: ApiRelationshipJSON[],
|
||||
) => {
|
||||
relationships.forEach((relationship) => {
|
||||
state = normalizeRelationship(state, relationship);
|
||||
});
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
const setDomainBlocking = (
|
||||
state: State,
|
||||
accounts: Account[],
|
||||
blocking: boolean,
|
||||
) => {
|
||||
return state.withMutations((map) => {
|
||||
accounts.forEach((id) => {
|
||||
map.setIn([id, 'domain_blocking'], blocking);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const relationshipsReducer: Reducer<State> = (
|
||||
state = initialState,
|
||||
action,
|
||||
) => {
|
||||
if (authorizeFollowRequestSuccess.match(action))
|
||||
return state
|
||||
.setIn([action.payload.id, 'followed_by'], true)
|
||||
.setIn([action.payload.id, 'requested_by'], false);
|
||||
else if (rejectFollowRequestSuccess.match(action))
|
||||
return state
|
||||
.setIn([action.payload.id, 'followed_by'], false)
|
||||
.setIn([action.payload.id, 'requested_by'], false);
|
||||
else if (notificationsUpdate.match(action))
|
||||
return action.payload.notification.type === 'follow_request'
|
||||
? state.setIn(
|
||||
[action.payload.notification.account.id, 'requested_by'],
|
||||
true,
|
||||
)
|
||||
: state;
|
||||
else if (followAccountRequest.match(action))
|
||||
return state.getIn([action.payload.id, 'following'])
|
||||
? state
|
||||
: state.setIn(
|
||||
[
|
||||
action.payload.id,
|
||||
action.payload.locked ? 'requested' : 'following',
|
||||
],
|
||||
true,
|
||||
);
|
||||
else if (followAccountFail.match(action))
|
||||
return state.setIn(
|
||||
[action.payload.id, action.payload.locked ? 'requested' : 'following'],
|
||||
false,
|
||||
);
|
||||
else if (unfollowAccountRequest.match(action))
|
||||
return state.setIn([action.payload.id, 'following'], false);
|
||||
else if (unfollowAccountFail.match(action))
|
||||
return state.setIn([action.payload.id, 'following'], true);
|
||||
else if (
|
||||
followAccountSuccess.match(action) ||
|
||||
unfollowAccountSuccess.match(action) ||
|
||||
blockAccountSuccess.match(action) ||
|
||||
unblockAccountSuccess.match(action) ||
|
||||
muteAccountSuccess.match(action) ||
|
||||
unmuteAccountSuccess.match(action) ||
|
||||
pinAccountSuccess.match(action) ||
|
||||
unpinAccountSuccess.match(action) ||
|
||||
isFulfilled(submitAccountNote)(action)
|
||||
)
|
||||
return normalizeRelationship(state, action.payload.relationship);
|
||||
else if (fetchRelationshipsSuccess.match(action))
|
||||
return normalizeRelationships(state, action.payload.relationships);
|
||||
else if (blockDomainSuccess.match(action))
|
||||
return setDomainBlocking(state, action.payload.accounts, true);
|
||||
else if (unblockDomainSuccess.match(action))
|
||||
return setDomainBlocking(state, action.payload.accounts, false);
|
||||
else return state;
|
||||
};
|
|
@ -1,8 +1,8 @@
|
|||
import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable';
|
||||
|
||||
import {
|
||||
ACCOUNT_BLOCK_SUCCESS,
|
||||
ACCOUNT_MUTE_SUCCESS,
|
||||
blockAccountSuccess,
|
||||
muteAccountSuccess,
|
||||
} from '../actions/accounts';
|
||||
import {
|
||||
BOOKMARKED_STATUSES_FETCH_REQUEST,
|
||||
|
@ -142,9 +142,9 @@ export default function statusLists(state = initialState, action) {
|
|||
return prependOneToList(state, 'pins', action.status);
|
||||
case UNPIN_SUCCESS:
|
||||
return removeOneFromList(state, 'pins', action.status);
|
||||
case ACCOUNT_BLOCK_SUCCESS:
|
||||
case ACCOUNT_MUTE_SUCCESS:
|
||||
return state.updateIn(['trending', 'items'], ImmutableOrderedSet(), list => list.filterNot(statusId => action.statuses.getIn([statusId, 'account']) === action.relationship.id));
|
||||
case blockAccountSuccess.type:
|
||||
case muteAccountSuccess.type:
|
||||
return state.updateIn(['trending', 'items'], ImmutableOrderedSet(), list => list.filterNot(statusId => action.payload.statuses.getIn([statusId, 'account']) === action.payload.relationship.id));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
|
||||
|
||||
import { ACCOUNT_BLOCK_SUCCESS, ACCOUNT_MUTE_SUCCESS } from 'flavours/glitch/actions/accounts';
|
||||
import { DOMAIN_BLOCK_SUCCESS } from 'flavours/glitch/actions/domain_blocks';
|
||||
import { blockAccountSuccess, muteAccountSuccess } from 'flavours/glitch/actions/accounts';
|
||||
import { blockDomainSuccess } from 'flavours/glitch/actions/domain_blocks';
|
||||
|
||||
import {
|
||||
SUGGESTIONS_FETCH_REQUEST,
|
||||
|
@ -29,11 +29,11 @@ export default function suggestionsReducer(state = initialState, action) {
|
|||
return state.set('isLoading', false);
|
||||
case SUGGESTIONS_DISMISS:
|
||||
return state.update('items', list => list.filterNot(x => x.account === action.id));
|
||||
case ACCOUNT_BLOCK_SUCCESS:
|
||||
case ACCOUNT_MUTE_SUCCESS:
|
||||
return state.update('items', list => list.filterNot(x => x.account === action.relationship.id));
|
||||
case DOMAIN_BLOCK_SUCCESS:
|
||||
return state.update('items', list => list.filterNot(x => action.accounts.includes(x.account)));
|
||||
case blockAccountSuccess.type:
|
||||
case muteAccountSuccess.type:
|
||||
return state.update('items', list => list.filterNot(x => x.account === action.payload.relationship.id));
|
||||
case blockDomainSuccess.type:
|
||||
return state.update('items', list => list.filterNot(x => action.payload.accounts.includes(x.account)));
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
|
||||
|
||||
import {
|
||||
ACCOUNT_BLOCK_SUCCESS,
|
||||
ACCOUNT_MUTE_SUCCESS,
|
||||
ACCOUNT_UNFOLLOW_SUCCESS,
|
||||
blockAccountSuccess,
|
||||
muteAccountSuccess,
|
||||
unfollowAccountSuccess
|
||||
} from '../actions/accounts';
|
||||
import {
|
||||
TIMELINE_UPDATE,
|
||||
|
@ -206,11 +206,11 @@ export default function timelines(state = initialState, action) {
|
|||
return deleteStatus(state, action.id, action.references, action.reblogOf);
|
||||
case TIMELINE_CLEAR:
|
||||
return clearTimeline(state, action.timeline);
|
||||
case ACCOUNT_BLOCK_SUCCESS:
|
||||
case ACCOUNT_MUTE_SUCCESS:
|
||||
return filterTimelines(state, action.relationship, action.statuses);
|
||||
case ACCOUNT_UNFOLLOW_SUCCESS:
|
||||
return filterTimeline('home', state, action.relationship, action.statuses);
|
||||
case blockAccountSuccess.type:
|
||||
case muteAccountSuccess.type:
|
||||
return filterTimelines(state, action.payload.relationship, action.payload.statuses);
|
||||
case unfollowAccountSuccess.type:
|
||||
return filterTimeline('home', state, action.payload.relationship, action.payload.statuses);
|
||||
case TIMELINE_SCROLL_TOP:
|
||||
return updateTop(state, action.timeline, action.top);
|
||||
case TIMELINE_CONNECT:
|
||||
|
|
|
@ -33,8 +33,8 @@ import {
|
|||
FOLLOW_REQUESTS_EXPAND_REQUEST,
|
||||
FOLLOW_REQUESTS_EXPAND_SUCCESS,
|
||||
FOLLOW_REQUESTS_EXPAND_FAIL,
|
||||
FOLLOW_REQUEST_AUTHORIZE_SUCCESS,
|
||||
FOLLOW_REQUEST_REJECT_SUCCESS,
|
||||
authorizeFollowRequestSuccess,
|
||||
rejectFollowRequestSuccess,
|
||||
} from '../actions/accounts';
|
||||
import {
|
||||
BLOCKS_FETCH_REQUEST,
|
||||
|
@ -66,11 +66,7 @@ import {
|
|||
MUTES_EXPAND_SUCCESS,
|
||||
MUTES_EXPAND_FAIL,
|
||||
} from '../actions/mutes';
|
||||
import {
|
||||
NOTIFICATIONS_UPDATE,
|
||||
} from '../actions/notifications';
|
||||
|
||||
|
||||
import { notificationsUpdate } from '../actions/notifications';
|
||||
|
||||
const initialListState = ImmutableMap({
|
||||
next: null,
|
||||
|
@ -163,8 +159,8 @@ export default function userLists(state = initialState, action) {
|
|||
case FAVOURITES_FETCH_FAIL:
|
||||
case FAVOURITES_EXPAND_FAIL:
|
||||
return state.setIn(['favourited_by', action.id, 'isLoading'], false);
|
||||
case NOTIFICATIONS_UPDATE:
|
||||
return action.notification.type === 'follow_request' ? normalizeFollowRequest(state, action.notification) : state;
|
||||
case notificationsUpdate.type:
|
||||
return action.payload.notification.type === 'follow_request' ? normalizeFollowRequest(state, action.payload.notification) : state;
|
||||
case FOLLOW_REQUESTS_FETCH_SUCCESS:
|
||||
return normalizeList(state, ['follow_requests'], action.accounts, action.next);
|
||||
case FOLLOW_REQUESTS_EXPAND_SUCCESS:
|
||||
|
@ -175,9 +171,9 @@ export default function userLists(state = initialState, action) {
|
|||
case FOLLOW_REQUESTS_FETCH_FAIL:
|
||||
case FOLLOW_REQUESTS_EXPAND_FAIL:
|
||||
return state.setIn(['follow_requests', 'isLoading'], false);
|
||||
case FOLLOW_REQUEST_AUTHORIZE_SUCCESS:
|
||||
case FOLLOW_REQUEST_REJECT_SUCCESS:
|
||||
return state.updateIn(['follow_requests', 'items'], list => list.filterNot(item => item === action.id));
|
||||
case authorizeFollowRequestSuccess.type:
|
||||
case rejectFollowRequestSuccess.type:
|
||||
return state.updateIn(['follow_requests', 'items'], list => list.filterNot(item => item === action.payload.id));
|
||||
case BLOCKS_FETCH_SUCCESS:
|
||||
return normalizeList(state, ['blocks'], action.accounts, action.next);
|
||||
case BLOCKS_EXPAND_SUCCESS:
|
||||
|
|
47
app/javascript/flavours/glitch/selectors/accounts.ts
Normal file
47
app/javascript/flavours/glitch/selectors/accounts.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
import { Record as ImmutableRecord } from 'immutable';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { accountDefaultValues } from 'flavours/glitch/models/account';
|
||||
import type { Account, AccountShape } from 'flavours/glitch/models/account';
|
||||
import type { Relationship } from 'flavours/glitch/models/relationship';
|
||||
import type { RootState } from 'flavours/glitch/store';
|
||||
|
||||
const getAccountBase = (state: RootState, id: string) =>
|
||||
state.accounts.get(id, null);
|
||||
|
||||
const getAccountRelationship = (state: RootState, id: string) =>
|
||||
state.relationships.get(id, null);
|
||||
|
||||
const getAccountMoved = (state: RootState, id: string) => {
|
||||
const movedToId = state.accounts.get(id)?.moved;
|
||||
|
||||
if (!movedToId) return undefined;
|
||||
|
||||
return state.accounts.get(movedToId);
|
||||
};
|
||||
|
||||
interface FullAccountShape extends Omit<AccountShape, 'moved'> {
|
||||
relationship: Relationship | null;
|
||||
moved: Account | null;
|
||||
}
|
||||
|
||||
const FullAccountFactory = ImmutableRecord<FullAccountShape>({
|
||||
...accountDefaultValues,
|
||||
moved: null,
|
||||
relationship: null,
|
||||
});
|
||||
|
||||
export function makeGetAccount() {
|
||||
return createSelector(
|
||||
[getAccountBase, getAccountRelationship, getAccountMoved],
|
||||
(base, relationship, moved) => {
|
||||
if (base === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return FullAccountFactory(base)
|
||||
.set('relationship', relationship)
|
||||
.set('moved', moved ?? null);
|
||||
},
|
||||
);
|
||||
}
|
|
@ -5,23 +5,7 @@ import { toServerSideType } from 'flavours/glitch/utils/filters';
|
|||
|
||||
import { me } from '../initial_state';
|
||||
|
||||
const getAccountBase = (state, id) => state.getIn(['accounts', id], null);
|
||||
const getAccountCounters = (state, id) => state.getIn(['accounts_counters', id], null);
|
||||
const getAccountRelationship = (state, id) => state.getIn(['relationships', id], null);
|
||||
const getAccountMoved = (state, id) => state.getIn(['accounts', state.getIn(['accounts', id, 'moved'])]);
|
||||
|
||||
export const makeGetAccount = () => {
|
||||
return createSelector([getAccountBase, getAccountCounters, getAccountRelationship, getAccountMoved], (base, counters, relationship, moved) => {
|
||||
if (base === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return base.merge(counters).withMutations(map => {
|
||||
map.set('relationship', relationship);
|
||||
map.set('moved', moved);
|
||||
});
|
||||
});
|
||||
};
|
||||
export { makeGetAccount } from "./accounts";
|
||||
|
||||
const getFilters = (state, { contextType }) => {
|
||||
if (!contextType) return null;
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
import {
|
||||
isAsyncThunkAction,
|
||||
isPending as isThunkActionPending,
|
||||
isFulfilled as isThunkActionFulfilled,
|
||||
isRejected as isThunkActionRejected,
|
||||
} from '@reduxjs/toolkit';
|
||||
import { showLoading, hideLoading } from 'react-redux-loading-bar';
|
||||
import type { AnyAction, Middleware } from 'redux';
|
||||
|
||||
|
@ -21,25 +27,43 @@ export const loadingBarMiddleware = (
|
|||
return ({ dispatch }) =>
|
||||
(next) =>
|
||||
(action: AnyAction) => {
|
||||
if (action.type && !action.skipLoading) {
|
||||
let isPending = false;
|
||||
let isFulfilled = false;
|
||||
let isRejected = false;
|
||||
|
||||
if (
|
||||
isAsyncThunkAction(action)
|
||||
// TODO: once we get the first use-case for it, add a check for skipLoading
|
||||
) {
|
||||
if (isThunkActionPending(action)) isPending = true;
|
||||
else if (isThunkActionFulfilled(action)) isFulfilled = true;
|
||||
else if (isThunkActionRejected(action)) isRejected = true;
|
||||
} else if (
|
||||
action.type &&
|
||||
!action.skipLoading &&
|
||||
typeof action.type === 'string'
|
||||
) {
|
||||
const [PENDING, FULFILLED, REJECTED] = promiseTypeSuffixes;
|
||||
|
||||
const isPending = new RegExp(`${PENDING}$`, 'g');
|
||||
const isFulfilled = new RegExp(`${FULFILLED}$`, 'g');
|
||||
const isRejected = new RegExp(`${REJECTED}$`, 'g');
|
||||
const isPendingRegexp = new RegExp(`${PENDING}$`, 'g');
|
||||
const isFulfilledRegexp = new RegExp(`${FULFILLED}$`, 'g');
|
||||
const isRejectedRegexp = new RegExp(`${REJECTED}$`, 'g');
|
||||
|
||||
if (typeof action.type === 'string') {
|
||||
if (action.type.match(isPending)) {
|
||||
dispatch(showLoading());
|
||||
} else if (
|
||||
action.type.match(isFulfilled) ??
|
||||
action.type.match(isRejected)
|
||||
) {
|
||||
dispatch(hideLoading());
|
||||
}
|
||||
if (action.type.match(isPendingRegexp)) {
|
||||
isPending = true;
|
||||
} else if (action.type.match(isFulfilledRegexp)) {
|
||||
isFulfilled = true;
|
||||
} else if (action.type.match(isRejectedRegexp)) {
|
||||
isRejected = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isPending) {
|
||||
dispatch(showLoading());
|
||||
} else if (isFulfilled || isRejected) {
|
||||
dispatch(hideLoading());
|
||||
}
|
||||
|
||||
return next(action);
|
||||
};
|
||||
};
|
||||
|
|
|
@ -35,6 +35,5 @@ export const store = configureStore({
|
|||
|
||||
// Infer the `RootState` and `AppDispatch` types from the store itself
|
||||
export type RootState = ReturnType<typeof rootReducer>;
|
||||
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
|
||||
export type AppDispatch = typeof store.dispatch;
|
||||
export type GetState = typeof store.getState;
|
||||
|
|
|
@ -400,8 +400,7 @@ $ui-header-height: 55px;
|
|||
|
||||
> .scrollable {
|
||||
background: $ui-base-color;
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-radius: 0 0 4px 4px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue