Compare commits
330 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
bb911043de | ||
|
da0333f1cb | ||
|
d8a0ee1956 | ||
|
91c71471ab | ||
|
98eacb2238 | ||
|
80c13bf0ef | ||
|
e17c2e5da5 | ||
|
4a618908e8 | ||
|
a208e7d655 | ||
|
c1b9ae7fc2 | ||
|
dc8a6244fc | ||
|
0f52e42c2d | ||
|
85af2405cf | ||
|
47ace633dc | ||
|
85d5518b6b | ||
|
5104bd7988 | ||
|
3e425b51fd | ||
|
37dbfa4cd7 | ||
|
0d23c81662 | ||
|
b436b31d5a | ||
|
72133fbed6 | ||
|
abbdacedc5 | ||
|
ddd3251912 | ||
|
605e2a417c | ||
|
f8fe394e7a | ||
|
2a545e0fb1 | ||
|
ce812466c7 | ||
|
47bf7a8047 | ||
|
85d405c810 | ||
|
f596a413ef | ||
|
9e53fe5c29 | ||
|
3690f04e4a | ||
|
f3e8bc9f8f | ||
|
dcf0530218 | ||
|
47338bc13d | ||
|
6fb9726b99 | ||
|
8015fd7600 | ||
|
4919b89ab8 | ||
|
2925372ff4 | ||
|
778430b54a | ||
|
5282ba862a | ||
|
0464602978 | ||
|
9b03cf0ddd | ||
|
cdff1da901 | ||
|
1a065fb146 | ||
|
022008a2a6 | ||
|
a3715598cc | ||
|
1be48dd805 | ||
|
6384041d17 | ||
|
140e73bc82 | ||
|
e3fae6f52c | ||
|
65d8c73bae | ||
|
177dd8bb53 | ||
|
380b20eed6 | ||
|
c207b4bb33 | ||
|
b87eb8ea14 | ||
|
8902e265b4 | ||
|
b8ea28d6d0 | ||
|
f741673638 | ||
|
0a0b9a271a | ||
|
7d2b4186c3 | ||
|
90689190a3 | ||
|
8acadeea76 | ||
|
75c6513c67 | ||
|
73540ffe6b | ||
|
92bb166246 | ||
|
8cf8ce4ac0 | ||
|
0f1b1d78b1 | ||
|
d3bbef27e7 | ||
|
f0634ba876 | ||
|
1d68fe1a60 | ||
|
6bd6dcf6df | ||
|
28d2920472 | ||
|
34bfea8bbf | ||
|
2d91944285 | ||
|
0026ba2751 | ||
|
b623dd12c1 | ||
|
722d152082 | ||
|
7623766241 | ||
|
e34c5a3503 | ||
|
004672aa6c | ||
|
ad4a28f4f6 | ||
|
d8ae3efec3 | ||
|
cd81a1c52a | ||
|
dcf73ddeff | ||
|
d81b706f12 | ||
|
30fa5fe1a4 | ||
|
7a7bfa5170 | ||
|
e969c78645 | ||
|
7adac1bc51 | ||
|
e859d6f259 | ||
|
a0880edc6e | ||
|
61fcdbbf7e | ||
|
43af695ba1 | ||
|
facd90e7a6 | ||
|
6201f96b8a | ||
|
c26cea262b | ||
|
1f1d6bf2a0 | ||
|
4c06d1cb24 | ||
|
2985d08951 | ||
|
66ca7157db | ||
|
4addf051d4 | ||
|
ab914ce6d5 | ||
|
6a4b224397 | ||
|
6adbd114c1 | ||
|
037f96c5ae | ||
|
f54dca06a9 | ||
|
370fa70924 | ||
|
5be1214c26 | ||
|
f7a30e2fae | ||
|
3f815b2052 | ||
|
defe4f9bc3 | ||
|
943775fd90 | ||
|
42844df966 | ||
|
b0fe58dc69 | ||
|
e07b57852e | ||
|
7c7c18fdea | ||
|
a84664026e | ||
|
02a0fd5b64 | ||
|
6505a42be0 | ||
|
e674608d10 | ||
|
c7af8cbc90 | ||
|
9475fbae78 | ||
|
00e61d6807 | ||
|
19084d3c6c | ||
|
e014bf8ed0 | ||
|
f6e2309e70 | ||
|
9d2154c4ab | ||
|
1dfd27a028 | ||
|
b97ebaf620 | ||
|
8ee2eb5d2e | ||
|
20b647020b | ||
|
3eedad2737 | ||
|
ce7c0def88 | ||
|
dab8fc4584 | ||
|
8a597f0138 | ||
|
3363f2f4d6 | ||
|
c7f2d6af55 | ||
|
e878ddb7c0 | ||
|
336f0b0823 | ||
|
3ea3f24a02 | ||
|
d567a382e3 | ||
|
18fe77084f | ||
|
dc253ea234 | ||
|
9304114b57 | ||
|
1fd5251376 | ||
|
edddc7c791 | ||
|
10768aa204 | ||
|
e98559c3ff | ||
|
2212dc4aaa | ||
|
e1fdac3e9a | ||
|
1162f61ca3 | ||
|
39ea5c0e2e | ||
|
509b0cfafc | ||
|
fda5c699c2 | ||
|
cb7ee4698f | ||
|
d010e270e6 | ||
|
d1e08bd38c | ||
|
dbccdcc1b1 | ||
|
5c63523972 | ||
|
de4681b2be | ||
|
a132332b86 | ||
|
b25e42a77f | ||
|
5236a62861 | ||
|
0f155829b7 | ||
|
84dda45df9 | ||
|
9c7505489f | ||
|
75cad1d9d6 | ||
|
2cc3111a77 | ||
|
bf811e4d4a | ||
|
d6774d2ca3 | ||
|
bd669e3907 | ||
|
1a4860a57a | ||
|
41fa53253c | ||
|
c00ead8a72 | ||
|
e49dc6a06e | ||
|
0e12a8dab9 | ||
|
3652a39de0 | ||
|
79335e46fd | ||
|
7c6e02aaf3 | ||
|
7f55430652 | ||
|
8235623362 | ||
|
83435c49ea | ||
|
93de41b39b | ||
|
b1d4b74a44 | ||
|
bfdf47bc98 | ||
|
33f669a5f8 | ||
|
3576fa0d59 | ||
|
1dcfb90202 | ||
|
22cf18e16f | ||
|
0ebe7d6d23 | ||
|
23081bb299 | ||
|
4c7fe48c40 | ||
|
499cc7b803 | ||
|
7db98aa70e | ||
|
e031fd60ad | ||
|
bc4fad9e22 | ||
|
5ac4d677e9 | ||
|
b42bdd80e8 | ||
|
76fa9d2488 | ||
|
dfc43a6d3d | ||
|
67bc58dd60 | ||
|
2d39560dc1 | ||
|
c49ff7395e | ||
|
e0ada97770 | ||
|
3a2003ba86 | ||
|
9a81be0d37 | ||
|
5e2c5e95b6 | ||
|
34a93ccf57 | ||
|
922fb74197 | ||
|
7bf2d6cb06 | ||
|
11e5c965c3 | ||
|
34157d118c | ||
|
7b92950f1c | ||
|
97d7028c31 | ||
|
a7f2961621 | ||
|
00dda99789 | ||
|
2e27ce3b61 | ||
|
2c10c5a069 | ||
|
bd4dd4c4a0 | ||
|
7d33b60f3f | ||
|
aecce5694b | ||
|
0e4ca51951 | ||
|
dde043f6cd | ||
|
c778a60e4f | ||
|
c347327d54 | ||
|
fd328cf6e8 | ||
|
7b473d7514 | ||
|
dff576b75d | ||
|
52ae83d008 | ||
|
5aacd9d4c7 | ||
|
d24d3fa283 | ||
|
c8a226f61c | ||
|
7a281c477a | ||
|
91c789ec63 | ||
|
9ead3d1cdb | ||
|
402c19a924 | ||
|
b5e8994844 | ||
|
4bd327a0c5 | ||
|
184325077e | ||
|
8236f942ff | ||
|
8963f8c3c2 | ||
|
5e41c26203 | ||
|
45837c533e | ||
|
3fa8512474 | ||
|
0e20de9f89 | ||
|
24d645b7d0 | ||
|
7b23f79d41 | ||
|
3b4095cf1b | ||
|
28cbfb9f10 | ||
|
189a06d2a2 | ||
|
450441fc11 | ||
|
b619362a36 | ||
|
425d02287a | ||
|
2e429c0c25 | ||
|
e0e12b0fee | ||
|
62ca37884a | ||
|
f9180823bc | ||
|
4b0c667c09 | ||
|
1b732cad61 | ||
|
ecef03bb15 | ||
|
9642601126 | ||
|
3836d293a1 | ||
|
0734e1fe33 | ||
|
44cb08297c | ||
|
bd21afb5ed | ||
|
ef80ad17b3 | ||
|
9ea4f37e78 | ||
|
c48772fd3f | ||
|
860e257a68 | ||
|
902d9e34b4 | ||
|
2d97c898f2 | ||
|
f6a93fc150 | ||
|
019f3377bb | ||
|
4b11675bdc | ||
|
2531c5953b | ||
|
c6db416ff7 | ||
|
b00cb2aed3 | ||
|
7c67cb5997 | ||
|
a098d08d12 | ||
|
6267759607 | ||
|
bc39ad37c4 | ||
|
a6ba004bf5 | ||
|
b89ab7e69d | ||
|
33d7338779 | ||
|
cf4fe6cab8 | ||
|
2241a15ee9 | ||
|
bca334cd28 | ||
|
3e3ec9b2c8 | ||
|
a8736aab7a | ||
|
71b266377c | ||
|
08dce5e607 | ||
|
2469fd1cdc | ||
|
531c1bb245 | ||
|
58f5040ee8 | ||
|
838f51770b | ||
|
c52090dbfe | ||
|
807c192fcf | ||
|
3b59f9c6c2 | ||
|
135bdd149e | ||
|
3572138b16 | ||
|
9f69aa3cb1 | ||
|
f5c3d20e9c | ||
|
1ec7c87001 | ||
|
8e4d1cba00 | ||
|
676ba50601 | ||
|
bbc3db8b20 | ||
|
f937cad68f | ||
|
be83d450eb | ||
|
1fd18a61bd | ||
|
5d9f479538 | ||
|
3ce9ca4c99 | ||
|
2ca1f0737a | ||
|
19ecde8fe7 | ||
|
7ee5fc5d68 | ||
|
4289ed1d13 | ||
|
256e3adc1d | ||
|
152b4d54e8 | ||
|
ea2ef16ea4 | ||
|
1d3e0a5060 | ||
|
bf575a1f5e | ||
|
860ffc0560 | ||
|
7eb4abe20a | ||
|
1baa75f79f | ||
|
1d436a4322 | ||
|
8fd174298d | ||
|
9afd7dadbf | ||
|
8e84177305 | ||
|
a28ce13b3e | ||
|
e1b42e9aa0 |
2
.babelrc
2
.babelrc
@@ -14,7 +14,7 @@
|
||||
],
|
||||
"plugins": [
|
||||
"syntax-dynamic-import",
|
||||
"transform-object-rest-spread",
|
||||
["transform-object-rest-spread", { "useBuiltIns": true }],
|
||||
"transform-class-properties",
|
||||
[
|
||||
"react-intl",
|
||||
|
@@ -2,6 +2,7 @@
|
||||
.env.*
|
||||
public/system
|
||||
public/assets
|
||||
public/packs
|
||||
node_modules
|
||||
storybook
|
||||
neo4j
|
||||
|
111
.env.nanobox
Normal file
111
.env.nanobox
Normal file
@@ -0,0 +1,111 @@
|
||||
# Service dependencies
|
||||
# You may set REDIS_URL instead for more advanced options
|
||||
REDIS_HOST=$DATA_REDIS_HOST
|
||||
REDIS_PORT=6379
|
||||
# REDIS_DB=0
|
||||
|
||||
# You may set DATABASE_URL instead for more advanced options
|
||||
DB_HOST=$DATA_DB_HOST
|
||||
DB_USER=$DATA_DB_USER
|
||||
DB_NAME=gonano
|
||||
DB_PASS=$DATA_DB_PASS
|
||||
DB_PORT=5432
|
||||
|
||||
DATABASE_URL=postgresql://$DATA_DB_USER:$DATA_DB_PASS@$DATA_DB_HOST/gonano
|
||||
|
||||
# Federation
|
||||
# Note: Changing LOCAL_DOMAIN or LOCAL_HTTPS at a later time will cause unwanted side effects.
|
||||
# LOCAL_DOMAIN should *NOT* contain the protocol part of the domain e.g https://example.com.
|
||||
LOCAL_DOMAIN=${APP_NAME}.nanoapp.io
|
||||
LOCAL_HTTPS=false
|
||||
|
||||
# Use this only if you need to run mastodon on a different domain than the one used for federation.
|
||||
# You can read more about this option on https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Serving_a_different_domain.md
|
||||
# DO *NOT* USE THIS UNLESS YOU KNOW *EXACTLY* WHAT YOU ARE DOING.
|
||||
# WEB_DOMAIN=mastodon.example.com
|
||||
|
||||
# Use this if you want to have several aliases handler@example1.com
|
||||
# handler@example2.com etc. for the same user. LOCAL_DOMAIN should not
|
||||
# be added. Comma separated values
|
||||
# ALTERNATE_DOMAINS=example1.com,example2.com
|
||||
|
||||
# Application secrets
|
||||
# Generate each with the `rake secret` task (`nanobox run bundle exec rake secret`)
|
||||
PAPERCLIP_SECRET=$PAPERCLIP_SECRET
|
||||
SECRET_KEY_BASE=$SECRET_KEY_BASE
|
||||
OTP_SECRET=$OTP_SECRET
|
||||
|
||||
# Registrations
|
||||
# Single user mode will disable registrations and redirect frontpage to the first profile
|
||||
# SINGLE_USER_MODE=true
|
||||
# Prevent registrations with following e-mail domains
|
||||
# EMAIL_DOMAIN_BLACKLIST=example1.com|example2.de|etc
|
||||
# Only allow registrations with the following e-mail domains
|
||||
# EMAIL_DOMAIN_WHITELIST=example1.com|example2.de|etc
|
||||
|
||||
# Optionally change default language
|
||||
# DEFAULT_LOCALE=de
|
||||
|
||||
# E-mail configuration
|
||||
# Note: Mailgun and SparkPost (https://sparkpo.st/smtp) each have good free tiers
|
||||
# If you want to use an SMTP server without authentication (e.g local Postfix relay)
|
||||
# then set SMTP_AUTH_METHOD and SMTP_OPENSSL_VERIFY_MODE to 'none' and
|
||||
# *comment* SMTP_LOGIN and SMTP_PASSWORD (leaving them blank is not enough).
|
||||
SMTP_SERVER=$SMTP_SERVER
|
||||
SMTP_PORT=587
|
||||
SMTP_LOGIN=$SMTP_LOGIN
|
||||
SMTP_PASSWORD=$SMTP_PASSWORD
|
||||
SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
|
||||
#SMTP_DOMAIN= # defaults to LOCAL_DOMAIN
|
||||
#SMTP_DELIVERY_METHOD=smtp # delivery method can also be sendmail
|
||||
#SMTP_AUTH_METHOD=plain
|
||||
#SMTP_CA_FILE=/etc/ssl/certs/ca-certificates.crt
|
||||
#SMTP_OPENSSL_VERIFY_MODE=peer
|
||||
#SMTP_ENABLE_STARTTLS_AUTO=true
|
||||
|
||||
|
||||
# Optional user upload path and URL (images, avatars). Default is :rails_root/public/system. If you set this variable, you are responsible for making your HTTP server (eg. nginx) serve these files.
|
||||
# PAPERCLIP_ROOT_PATH=/var/lib/mastodon/public-system
|
||||
# PAPERCLIP_ROOT_URL=/system
|
||||
|
||||
# Optional asset host for multi-server setups
|
||||
# CDN_HOST=assets.example.com
|
||||
|
||||
# S3 (optional)
|
||||
# S3_ENABLED=true
|
||||
# S3_BUCKET=
|
||||
# AWS_ACCESS_KEY_ID=
|
||||
# AWS_SECRET_ACCESS_KEY=
|
||||
# S3_REGION=
|
||||
# S3_PROTOCOL=http
|
||||
# S3_HOSTNAME=192.168.1.123:9000
|
||||
|
||||
# S3 (Minio Config (optional) Please check Minio instance for details)
|
||||
# S3_ENABLED=true
|
||||
# S3_BUCKET=
|
||||
# AWS_ACCESS_KEY_ID=
|
||||
# AWS_SECRET_ACCESS_KEY=
|
||||
# S3_REGION=
|
||||
# S3_PROTOCOL=https
|
||||
# S3_HOSTNAME=
|
||||
# S3_ENDPOINT=
|
||||
# S3_SIGNATURE_VERSION=
|
||||
|
||||
# Optional alias for S3 if you want to use Cloudfront or Cloudflare in front
|
||||
# S3_CLOUDFRONT_HOST=
|
||||
|
||||
# Streaming API integration
|
||||
# STREAMING_API_BASE_URL=
|
||||
|
||||
# Advanced settings
|
||||
# If you need to use pgBouncer, you need to disable prepared statements:
|
||||
# PREPARED_STATEMENTS=false
|
||||
|
||||
# Cluster number setting for streaming API server.
|
||||
# If you comment out following line, cluster number will be `numOfCpuCores - 1`.
|
||||
STREAMING_CLUSTER_NUM=1
|
||||
|
||||
# Docker mastodon user
|
||||
# If you use Docker, you may want to assign UID/GID manually.
|
||||
# UID=1000
|
||||
# GID=1000
|
@@ -65,7 +65,7 @@ SMTP_FROM_ADDRESS=notifications@example.com
|
||||
# PAPERCLIP_ROOT_URL=/system
|
||||
|
||||
# Optional asset host for multi-server setups
|
||||
# CDN_HOST=assets.example.com
|
||||
# CDN_HOST=https://assets.example.com
|
||||
|
||||
# S3 (optional)
|
||||
# S3_ENABLED=true
|
||||
|
@@ -21,22 +21,10 @@ parserOptions:
|
||||
|
||||
rules:
|
||||
|
||||
no-cond-assign: error
|
||||
no-console: warn
|
||||
no-irregular-whitespace: error
|
||||
no-unreachable: error
|
||||
valid-typeof: error
|
||||
consistent-return: error
|
||||
dot-notation: error
|
||||
eqeqeq: error
|
||||
no-fallthrough: error
|
||||
no-unused-expressions: error
|
||||
strict: off
|
||||
no-catch-shadow: error
|
||||
indent:
|
||||
- warn
|
||||
- 2
|
||||
brace-style: warn
|
||||
comma-dangle:
|
||||
- error
|
||||
- always-multiline
|
||||
comma-spacing:
|
||||
- warn
|
||||
- before: false
|
||||
@@ -44,22 +32,61 @@ rules:
|
||||
comma-style:
|
||||
- warn
|
||||
- last
|
||||
consistent-return: error
|
||||
dot-notation: error
|
||||
eqeqeq: error
|
||||
indent:
|
||||
- warn
|
||||
- 2
|
||||
jsx-quotes:
|
||||
- error
|
||||
- prefer-single
|
||||
no-catch-shadow: error
|
||||
no-cond-assign: error
|
||||
no-console:
|
||||
- warn
|
||||
- allow:
|
||||
- error
|
||||
no-fallthrough: error
|
||||
no-irregular-whitespace: error
|
||||
no-mixed-spaces-and-tabs: warn
|
||||
no-nested-ternary: warn
|
||||
no-trailing-spaces: warn
|
||||
semi: error
|
||||
no-unreachable: error
|
||||
no-unused-expressions: error
|
||||
object-curly-spacing:
|
||||
- error
|
||||
- always
|
||||
padded-blocks:
|
||||
- error
|
||||
- classes: always
|
||||
comma-dangle:
|
||||
quotes:
|
||||
- error
|
||||
- always-multiline
|
||||
- single
|
||||
semi: error
|
||||
strict: off
|
||||
valid-typeof: error
|
||||
|
||||
react/jsx-wrap-multilines: error
|
||||
react/jsx-boolean-value: error
|
||||
react/jsx-closing-bracket-location:
|
||||
- error
|
||||
- line-aligned
|
||||
react/jsx-curly-spacing: error
|
||||
react/jsx-equals-spacing: error
|
||||
react/jsx-first-prop-new-line:
|
||||
- error
|
||||
- multiline-multiprop
|
||||
react/jsx-indent:
|
||||
- error
|
||||
- 2
|
||||
react/jsx-no-bind: error
|
||||
react/self-closing-comp: error
|
||||
react/prop-types: error
|
||||
react/jsx-no-duplicate-props: error
|
||||
react/jsx-tag-spacing: error
|
||||
react/jsx-wrap-multilines: error
|
||||
react/no-multi-comp: off
|
||||
react/no-string-refs: error
|
||||
react/prop-types: error
|
||||
react/self-closing-comp: error
|
||||
|
||||
jsx-a11y/accessible-emoji: warn
|
||||
jsx-a11y/anchor-has-content: warn
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -33,6 +33,7 @@ config/deploy/*
|
||||
|
||||
# Ignore IDE files
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# Ignore postgres + redis volume optionally created by docker-compose
|
||||
postgres
|
||||
|
20
.nanoignore
Normal file
20
.nanoignore
Normal file
@@ -0,0 +1,20 @@
|
||||
.DS_Store
|
||||
.git/
|
||||
.gitignore
|
||||
|
||||
.bundle/
|
||||
.cache/
|
||||
config/deploy/*
|
||||
coverage
|
||||
docs/
|
||||
.env
|
||||
log/*.log
|
||||
neo4j/
|
||||
node_modules/
|
||||
public/assets/
|
||||
public/system/
|
||||
spec/
|
||||
storybook/
|
||||
tmp/
|
||||
.vagrant/
|
||||
vendor/bundle/
|
1
.profile
Normal file
1
.profile
Normal file
@@ -0,0 +1 @@
|
||||
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/app/.apt/lib/x86_64-linux-gnu:/app/.apt/usr/lib/x86_64-linux-gnu/mesa:/app/.apt/usr/lib/x86_64-linux-gnu/pulseaudio
|
93
.rubocop.yml
93
.rubocop.yml
@@ -1,26 +1,35 @@
|
||||
Rails:
|
||||
Enabled: true
|
||||
AllCops:
|
||||
TargetRubyVersion: 2.3
|
||||
Exclude:
|
||||
- 'spec/**/*'
|
||||
- 'db/**/*'
|
||||
- 'app/views/**/*'
|
||||
- 'config/**/*'
|
||||
- 'bin/*'
|
||||
- 'Rakefile'
|
||||
- 'node_modules/**/*'
|
||||
- 'Vagrantfile'
|
||||
- 'vendor/**/*'
|
||||
|
||||
Style/PerlBackrefs:
|
||||
AutoCorrect: false
|
||||
|
||||
Style/ClassAndModuleChildren:
|
||||
Bundler/OrderedGems:
|
||||
Enabled: false
|
||||
|
||||
Metrics/BlockNesting:
|
||||
Max: 2
|
||||
Layout/AccessModifierIndentation:
|
||||
EnforcedStyle: indent
|
||||
|
||||
Metrics/LineLength:
|
||||
AllowURI: true
|
||||
Layout/EmptyLineAfterMagicComment:
|
||||
Enabled: false
|
||||
|
||||
Metrics/MethodLength:
|
||||
CountComments: false
|
||||
Max: 10
|
||||
Layout/SpaceInsideHashLiteralBraces:
|
||||
EnforcedStyle: space
|
||||
|
||||
Metrics/AbcSize:
|
||||
Max: 100
|
||||
|
||||
Metrics/BlockLength:
|
||||
Exclude:
|
||||
- 'lib/tasks/**/*'
|
||||
|
||||
Metrics/BlockNesting:
|
||||
Max: 3
|
||||
|
||||
@@ -31,22 +40,36 @@ Metrics/ClassLength:
|
||||
Metrics/CyclomaticComplexity:
|
||||
Max: 15
|
||||
|
||||
Metrics/LineLength:
|
||||
AllowURI: true
|
||||
Enabled: false
|
||||
|
||||
Metrics/MethodLength:
|
||||
CountComments: false
|
||||
Max: 55
|
||||
|
||||
Metrics/ModuleLength:
|
||||
CountComments: false
|
||||
Max: 200
|
||||
|
||||
Metrics/PerceivedComplexity:
|
||||
Max: 10
|
||||
|
||||
Metrics/ParameterLists:
|
||||
Max: 4
|
||||
CountKeywordArgs: true
|
||||
|
||||
Style/AccessModifierIndentation:
|
||||
EnforcedStyle: indent
|
||||
Metrics/PerceivedComplexity:
|
||||
Max: 10
|
||||
|
||||
Rails:
|
||||
Enabled: true
|
||||
|
||||
Rails/HasAndBelongsToMany:
|
||||
Enabled: false
|
||||
|
||||
Rails/SkipsModelValidations:
|
||||
Enabled: false
|
||||
|
||||
Style/ClassAndModuleChildren:
|
||||
Enabled: false
|
||||
|
||||
Style/CollectionMethods:
|
||||
Enabled: true
|
||||
@@ -62,33 +85,25 @@ Style/DoubleNegation:
|
||||
Style/FrozenStringLiteralComment:
|
||||
Enabled: true
|
||||
|
||||
Style/SpaceInsideHashLiteralBraces:
|
||||
EnforcedStyle: space
|
||||
|
||||
Style/TrailingCommaInLiteral:
|
||||
EnforcedStyleForMultiline: 'comma'
|
||||
|
||||
Style/RegexpLiteral:
|
||||
Style/GuardClause:
|
||||
Enabled: false
|
||||
|
||||
Style/Lambda:
|
||||
Enabled: false
|
||||
|
||||
Rails/HasAndBelongsToMany:
|
||||
Style/PercentLiteralDelimiters:
|
||||
PreferredDelimiters:
|
||||
'%i': '()'
|
||||
'%w': '()'
|
||||
|
||||
Style/PerlBackrefs:
|
||||
AutoCorrect: false
|
||||
|
||||
Style/RegexpLiteral:
|
||||
Enabled: false
|
||||
|
||||
Bundler/OrderedGems:
|
||||
Style/SymbolArray:
|
||||
Enabled: false
|
||||
|
||||
AllCops:
|
||||
TargetRubyVersion: 2.3
|
||||
Exclude:
|
||||
- 'spec/**/*'
|
||||
- 'db/**/*'
|
||||
- 'app/views/**/*'
|
||||
- 'config/**/*'
|
||||
- 'bin/*'
|
||||
- 'Rakefile'
|
||||
- 'node_modules/**/*'
|
||||
- 'Vagrantfile'
|
||||
- 'vendor/**/*'
|
||||
Style/TrailingCommaInLiteral:
|
||||
EnforcedStyleForMultiline: 'comma'
|
||||
|
@@ -4,6 +4,8 @@ cache:
|
||||
yarn: true
|
||||
directories:
|
||||
- node_modules
|
||||
- public/assets
|
||||
- public/packs
|
||||
dist: trusty
|
||||
sudo: false
|
||||
|
||||
@@ -50,6 +52,6 @@ before_script:
|
||||
- ln -s /usr/bin/x86_64-linux-gnu-g++-6 "$HOME/g++"
|
||||
|
||||
script:
|
||||
- bundle exec parallel_test spec/ --group-by filesize --type rspec
|
||||
- travis_retry bundle exec parallel_test spec/ --group-by filesize --type rspec
|
||||
- npm test
|
||||
- bundle exec i18n-tasks unused
|
||||
|
7
Gemfile
7
Gemfile
@@ -6,7 +6,7 @@ ruby '>= 2.3.0', '< 2.5.0'
|
||||
gem 'pkg-config', '~> 1.2'
|
||||
|
||||
gem 'puma', '~> 3.8'
|
||||
gem 'rails', '~> 5.0'
|
||||
gem 'rails', '~> 5.1.0'
|
||||
gem 'uglifier', '~> 3.2'
|
||||
|
||||
gem 'hamlit-rails', '~> 0.2'
|
||||
@@ -38,6 +38,7 @@ gem 'nokogiri', '~> 1.7'
|
||||
gem 'oj', '~> 3.0'
|
||||
gem 'ostatus2', '~> 2.0'
|
||||
gem 'ox', '~> 2.5'
|
||||
gem 'pundit', '~> 1.1'
|
||||
gem 'rabl', '~> 0.13'
|
||||
gem 'rack-attack', '~> 5.0'
|
||||
gem 'rack-cors', '~> 0.4', require: 'rack/cors'
|
||||
@@ -51,6 +52,7 @@ gem 'sanitize', '~> 4.4'
|
||||
gem 'sidekiq', '~> 5.0'
|
||||
gem 'sidekiq-scheduler', '~> 2.1'
|
||||
gem 'sidekiq-unique-jobs', '~> 5.0'
|
||||
gem 'sidekiq-bulk', '~>0.1.1'
|
||||
gem 'simple-navigation', '~> 4.0'
|
||||
gem 'simple_form', '~> 3.4'
|
||||
gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
|
||||
@@ -69,6 +71,7 @@ end
|
||||
|
||||
group :test do
|
||||
gem 'capybara', '~> 2.14'
|
||||
gem 'climate_control', '~> 0.2'
|
||||
gem 'faker', '~> 1.7'
|
||||
gem 'microformats2', '~> 3.0'
|
||||
gem 'rails-controller-testing', '~> 1.0'
|
||||
@@ -86,7 +89,7 @@ group :development do
|
||||
gem 'bullet', '~> 5.5'
|
||||
gem 'letter_opener', '~> 1.4'
|
||||
gem 'letter_opener_web', '~> 1.3'
|
||||
gem 'rubocop', '~> 0.48', require: false
|
||||
gem 'rubocop', require: false
|
||||
gem 'brakeman', '~> 3.6', require: false
|
||||
gem 'bundler-audit', '~> 0.5', require: false
|
||||
gem 'scss_lint', '~> 0.53', require: false
|
||||
|
148
Gemfile.lock
148
Gemfile.lock
@@ -1,40 +1,40 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (5.0.3)
|
||||
actionpack (= 5.0.3)
|
||||
nio4r (>= 1.2, < 3.0)
|
||||
actioncable (5.1.1)
|
||||
actionpack (= 5.1.1)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (~> 0.6.1)
|
||||
actionmailer (5.0.3)
|
||||
actionpack (= 5.0.3)
|
||||
actionview (= 5.0.3)
|
||||
activejob (= 5.0.3)
|
||||
actionmailer (5.1.1)
|
||||
actionpack (= 5.1.1)
|
||||
actionview (= 5.1.1)
|
||||
activejob (= 5.1.1)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (5.0.3)
|
||||
actionview (= 5.0.3)
|
||||
activesupport (= 5.0.3)
|
||||
actionpack (5.1.1)
|
||||
actionview (= 5.1.1)
|
||||
activesupport (= 5.1.1)
|
||||
rack (~> 2.0)
|
||||
rack-test (~> 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
actionview (5.0.3)
|
||||
activesupport (= 5.0.3)
|
||||
actionview (5.1.1)
|
||||
activesupport (= 5.1.1)
|
||||
builder (~> 3.1)
|
||||
erubis (~> 2.7.0)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
||||
active_record_query_trace (1.5.4)
|
||||
activejob (5.0.3)
|
||||
activesupport (= 5.0.3)
|
||||
activejob (5.1.1)
|
||||
activesupport (= 5.1.1)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (5.0.3)
|
||||
activesupport (= 5.0.3)
|
||||
activerecord (5.0.3)
|
||||
activemodel (= 5.0.3)
|
||||
activesupport (= 5.0.3)
|
||||
arel (~> 7.0)
|
||||
activesupport (5.0.3)
|
||||
activemodel (5.1.1)
|
||||
activesupport (= 5.1.1)
|
||||
activerecord (5.1.1)
|
||||
activemodel (= 5.1.1)
|
||||
activesupport (= 5.1.1)
|
||||
arel (~> 8.0)
|
||||
activesupport (5.1.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (~> 0.7)
|
||||
minitest (~> 5.1)
|
||||
@@ -43,22 +43,22 @@ GEM
|
||||
public_suffix (~> 2.0, >= 2.0.2)
|
||||
airbrussh (1.2.0)
|
||||
sshkit (>= 1.6.1, != 1.7.0)
|
||||
annotate (2.7.1)
|
||||
annotate (2.7.2)
|
||||
activerecord (>= 3.2, < 6.0)
|
||||
rake (>= 10.4, < 12.0)
|
||||
arel (7.1.4)
|
||||
rake (>= 10.4, < 13.0)
|
||||
arel (8.0.0)
|
||||
ast (2.3.0)
|
||||
attr_encrypted (3.0.3)
|
||||
encryptor (~> 3.0.0)
|
||||
av (0.9.0)
|
||||
cocaine (~> 0.5.3)
|
||||
aws-sdk (2.9.21)
|
||||
aws-sdk-resources (= 2.9.21)
|
||||
aws-sdk-core (2.9.21)
|
||||
aws-sdk (2.9.37)
|
||||
aws-sdk-resources (= 2.9.37)
|
||||
aws-sdk-core (2.9.37)
|
||||
aws-sigv4 (~> 1.0)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-resources (2.9.21)
|
||||
aws-sdk-core (= 2.9.21)
|
||||
aws-sdk-resources (2.9.37)
|
||||
aws-sdk-core (= 2.9.37)
|
||||
aws-sigv4 (1.0.0)
|
||||
bcrypt (3.1.11)
|
||||
better_errors (2.1.1)
|
||||
@@ -67,9 +67,9 @@ GEM
|
||||
rack (>= 0.9.0)
|
||||
binding_of_caller (0.7.2)
|
||||
debug_inspector (>= 0.0.1)
|
||||
bootsnap (0.2.14)
|
||||
bootsnap (1.0.0)
|
||||
msgpack (~> 1.0)
|
||||
brakeman (3.6.1)
|
||||
brakeman (3.6.2)
|
||||
builder (3.2.3)
|
||||
bullet (5.5.1)
|
||||
activesupport (>= 3.0.0)
|
||||
@@ -85,7 +85,7 @@ GEM
|
||||
capistrano-bundler (1.2.0)
|
||||
capistrano (~> 3.1)
|
||||
sshkit (~> 1.2)
|
||||
capistrano-rails (1.2.3)
|
||||
capistrano-rails (1.3.0)
|
||||
capistrano (~> 3.1)
|
||||
capistrano-bundler (~> 1.1)
|
||||
capistrano-rbenv (2.1.1)
|
||||
@@ -93,7 +93,7 @@ GEM
|
||||
sshkit (~> 1.3)
|
||||
capistrano-yarn (2.0.2)
|
||||
capistrano (~> 3.0)
|
||||
capybara (2.14.0)
|
||||
capybara (2.14.2)
|
||||
addressable
|
||||
mime-types (>= 1.16)
|
||||
nokogiri (>= 1.3.3)
|
||||
@@ -130,7 +130,7 @@ GEM
|
||||
docile (1.1.5)
|
||||
domain_name (0.5.20170404)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
doorkeeper (4.2.5)
|
||||
doorkeeper (4.2.6)
|
||||
railties (>= 4.2)
|
||||
dotenv (2.2.1)
|
||||
dotenv-rails (2.2.1)
|
||||
@@ -141,6 +141,7 @@ GEM
|
||||
thread
|
||||
thread_safe
|
||||
encryptor (3.0.0)
|
||||
erubi (1.6.0)
|
||||
erubis (2.7.0)
|
||||
et-orbi (1.0.4)
|
||||
tzinfo
|
||||
@@ -185,7 +186,7 @@ GEM
|
||||
httplog (0.99.3)
|
||||
colorize
|
||||
rack
|
||||
i18n (0.8.1)
|
||||
i18n (0.8.4)
|
||||
i18n-tasks (0.9.15)
|
||||
activesupport (>= 4.0.2)
|
||||
ast (>= 2.1.0)
|
||||
@@ -225,7 +226,7 @@ GEM
|
||||
railties (>= 4, < 5.2)
|
||||
loofah (2.0.3)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.6.5)
|
||||
mail (2.6.6)
|
||||
mime-types (>= 1.16, < 4)
|
||||
method_source (0.8.2)
|
||||
microformats2 (3.1.0)
|
||||
@@ -235,22 +236,22 @@ GEM
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2016.0521)
|
||||
mimemagic (0.3.2)
|
||||
mini_portile2 (2.1.0)
|
||||
mini_portile2 (2.2.0)
|
||||
minitest (5.10.2)
|
||||
msgpack (1.1.0)
|
||||
multi_json (1.12.1)
|
||||
net-scp (1.2.1)
|
||||
net-ssh (>= 2.6.5)
|
||||
net-ssh (4.1.0)
|
||||
nio4r (2.0.0)
|
||||
nokogiri (1.7.2)
|
||||
mini_portile2 (~> 2.1.0)
|
||||
nokogumbo (1.4.11)
|
||||
nio4r (2.1.0)
|
||||
nokogiri (1.8.0)
|
||||
mini_portile2 (~> 2.2.0)
|
||||
nokogumbo (1.4.13)
|
||||
nokogiri
|
||||
oj (3.0.9)
|
||||
oj (3.1.0)
|
||||
openssl (2.0.3)
|
||||
orm_adapter (0.5.0)
|
||||
ostatus2 (2.0.0)
|
||||
ostatus2 (2.0.1)
|
||||
addressable (~> 2.4)
|
||||
http (~> 2.0)
|
||||
nokogiri (~> 1.6)
|
||||
@@ -273,7 +274,7 @@ GEM
|
||||
pg (0.20.0)
|
||||
pghero (1.7.0)
|
||||
activerecord
|
||||
pkg-config (1.2.0)
|
||||
pkg-config (1.2.3)
|
||||
powerpack (0.1.1)
|
||||
pry (0.10.4)
|
||||
coderay (~> 1.1.0)
|
||||
@@ -282,7 +283,9 @@ GEM
|
||||
pry-rails (0.3.6)
|
||||
pry (>= 0.10.4)
|
||||
public_suffix (2.0.5)
|
||||
puma (3.8.2)
|
||||
puma (3.9.1)
|
||||
pundit (1.1.0)
|
||||
activesupport (>= 3.0.0)
|
||||
rabl (0.13.1)
|
||||
activesupport (>= 2.3.14)
|
||||
rack (2.0.3)
|
||||
@@ -294,17 +297,17 @@ GEM
|
||||
rack-test (0.6.3)
|
||||
rack (>= 1.0)
|
||||
rack-timeout (0.4.2)
|
||||
rails (5.0.3)
|
||||
actioncable (= 5.0.3)
|
||||
actionmailer (= 5.0.3)
|
||||
actionpack (= 5.0.3)
|
||||
actionview (= 5.0.3)
|
||||
activejob (= 5.0.3)
|
||||
activemodel (= 5.0.3)
|
||||
activerecord (= 5.0.3)
|
||||
activesupport (= 5.0.3)
|
||||
rails (5.1.1)
|
||||
actioncable (= 5.1.1)
|
||||
actionmailer (= 5.1.1)
|
||||
actionpack (= 5.1.1)
|
||||
actionview (= 5.1.1)
|
||||
activejob (= 5.1.1)
|
||||
activemodel (= 5.1.1)
|
||||
activerecord (= 5.1.1)
|
||||
activesupport (= 5.1.1)
|
||||
bundler (>= 1.3.0, < 2.0)
|
||||
railties (= 5.0.3)
|
||||
railties (= 5.1.1)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-controller-testing (1.0.2)
|
||||
actionpack (~> 5.x, >= 5.0.1)
|
||||
@@ -320,15 +323,15 @@ GEM
|
||||
railties (~> 5.0)
|
||||
rails-settings-cached (0.6.5)
|
||||
rails (>= 4.2.0)
|
||||
railties (5.0.3)
|
||||
actionpack (= 5.0.3)
|
||||
activesupport (= 5.0.3)
|
||||
railties (5.1.1)
|
||||
actionpack (= 5.1.1)
|
||||
activesupport (= 5.1.1)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
rainbow (2.2.2)
|
||||
rake
|
||||
rake (11.3.0)
|
||||
rake (12.0.0)
|
||||
redis (3.3.3)
|
||||
redis-actionpack (5.0.1)
|
||||
actionpack (>= 4.0, < 6)
|
||||
@@ -374,7 +377,8 @@ GEM
|
||||
rspec-core (~> 3.0, >= 3.0.0)
|
||||
sidekiq (>= 2.4.0)
|
||||
rspec-support (3.6.0)
|
||||
rubocop (0.48.1)
|
||||
rubocop (0.49.1)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.3.3.1, < 3.0)
|
||||
powerpack (~> 0.1)
|
||||
rainbow (>= 1.99.1, < 3.0)
|
||||
@@ -382,10 +386,10 @@ GEM
|
||||
unicode-display_width (~> 1.0, >= 1.0.1)
|
||||
ruby-oembed (0.12.0)
|
||||
ruby-progressbar (1.8.1)
|
||||
rufus-scheduler (3.4.0)
|
||||
rufus-scheduler (3.4.2)
|
||||
et-orbi (~> 1.0)
|
||||
safe_yaml (1.0.4)
|
||||
sanitize (4.4.0)
|
||||
sanitize (4.5.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.4.4)
|
||||
nokogumbo (~> 1.4.1)
|
||||
@@ -393,12 +397,15 @@ GEM
|
||||
scss_lint (0.53.0)
|
||||
rake (>= 0.9, < 13)
|
||||
sass (~> 3.4.20)
|
||||
sidekiq (5.0.0)
|
||||
sidekiq (5.0.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
connection_pool (~> 2.2, >= 2.2.0)
|
||||
rack-protection (>= 1.5.0)
|
||||
redis (~> 3.3, >= 3.3.3)
|
||||
sidekiq-scheduler (2.1.4)
|
||||
sidekiq-bulk (0.1.1)
|
||||
activesupport
|
||||
sidekiq
|
||||
sidekiq-scheduler (2.1.5)
|
||||
redis (~> 3)
|
||||
rufus-scheduler (~> 3.2)
|
||||
sidekiq (>= 3)
|
||||
@@ -461,7 +468,7 @@ GEM
|
||||
websocket-driver (0.6.5)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.2)
|
||||
xpath (2.0.0)
|
||||
xpath (2.1.0)
|
||||
nokogiri (~> 1.3)
|
||||
|
||||
PLATFORMS
|
||||
@@ -484,6 +491,7 @@ DEPENDENCIES
|
||||
capistrano-yarn (~> 2.0)
|
||||
capybara (~> 2.14)
|
||||
cld3 (~> 3.1)
|
||||
climate_control (~> 0.2)
|
||||
devise (~> 4.2)
|
||||
devise-two-factor (~> 3.0)
|
||||
doorkeeper (~> 4.2)
|
||||
@@ -518,11 +526,12 @@ DEPENDENCIES
|
||||
pkg-config (~> 1.2)
|
||||
pry-rails (~> 0.3)
|
||||
puma (~> 3.8)
|
||||
pundit (~> 1.1)
|
||||
rabl (~> 0.13)
|
||||
rack-attack (~> 5.0)
|
||||
rack-cors (~> 0.4)
|
||||
rack-timeout (~> 0.4)
|
||||
rails (~> 5.0)
|
||||
rails (~> 5.1.0)
|
||||
rails-controller-testing (~> 1.0)
|
||||
rails-i18n (~> 5.0)
|
||||
rails-settings-cached (~> 0.6)
|
||||
@@ -532,11 +541,12 @@ DEPENDENCIES
|
||||
rqrcode (~> 0.10)
|
||||
rspec-rails (~> 3.6)
|
||||
rspec-sidekiq (~> 3.0)
|
||||
rubocop (~> 0.48)
|
||||
rubocop
|
||||
ruby-oembed (~> 0.12)
|
||||
sanitize (~> 4.4)
|
||||
scss_lint (~> 0.53)
|
||||
sidekiq (~> 5.0)
|
||||
sidekiq-bulk (~> 0.1.1)
|
||||
sidekiq-scheduler (~> 2.1)
|
||||
sidekiq-unique-jobs (~> 5.0)
|
||||
simple-navigation (~> 4.0)
|
||||
@@ -554,4 +564,4 @@ RUBY VERSION
|
||||
ruby 2.4.1p111
|
||||
|
||||
BUNDLED WITH
|
||||
1.14.6
|
||||
1.15.1
|
||||
|
@@ -13,7 +13,7 @@ An alternative implementation of the GNU social project. Based on [ActivityStrea
|
||||
|
||||
Click on the screenshot to watch a demo of the UI:
|
||||
|
||||
[][youtube_demo]
|
||||
[][youtube_demo]
|
||||
|
||||
[youtube_demo]: https://www.youtube.com/watch?v=YO1jQ8_rAMU
|
||||
|
||||
|
@@ -6,12 +6,12 @@ class AccountsController < ApplicationController
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
@statuses = @account.statuses.permitted_for(@account, current_account).order(id: :desc).paginate_by_max_id(20, params[:max_id], params[:since_id])
|
||||
@statuses = @account.statuses.permitted_for(@account, current_account).paginate_by_max_id(20, params[:max_id], params[:since_id])
|
||||
@statuses = cache_collection(@statuses, Status)
|
||||
end
|
||||
|
||||
format.atom do
|
||||
@entries = @account.stream_entries.order(id: :desc).where(hidden: false).with_includes.paginate_by_max_id(20, params[:max_id], params[:since_id])
|
||||
@entries = @account.stream_entries.where(hidden: false).with_includes.paginate_by_max_id(20, params[:max_id], params[:since_id])
|
||||
render xml: AtomSerializer.render(AtomSerializer.new.feed(@account, @entries.to_a))
|
||||
end
|
||||
|
||||
|
@@ -2,16 +2,43 @@
|
||||
|
||||
module Admin
|
||||
class AccountsController < BaseController
|
||||
before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload]
|
||||
before_action :require_remote_account!, only: [:subscribe, :unsubscribe, :redownload]
|
||||
|
||||
def index
|
||||
@accounts = filtered_accounts.page(params[:page])
|
||||
end
|
||||
|
||||
def show
|
||||
@account = Account.find(params[:id])
|
||||
def show; end
|
||||
|
||||
def subscribe
|
||||
Pubsubhubbub::SubscribeWorker.perform_async(@account.id)
|
||||
redirect_to admin_account_path(@account.id)
|
||||
end
|
||||
|
||||
def unsubscribe
|
||||
UnsubscribeService.new.call(@account)
|
||||
redirect_to admin_account_path(@account.id)
|
||||
end
|
||||
|
||||
def redownload
|
||||
@account.avatar = @account.avatar_remote_url
|
||||
@account.header = @account.header_remote_url
|
||||
@account.save!
|
||||
|
||||
redirect_to admin_account_path(@account.id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:id])
|
||||
end
|
||||
|
||||
def require_remote_account!
|
||||
redirect_to admin_account_path(@account.id) if @account.local?
|
||||
end
|
||||
|
||||
def filtered_accounts
|
||||
AccountFilter.new(filter_params).results
|
||||
end
|
||||
|
@@ -3,13 +3,18 @@
|
||||
module Admin
|
||||
class InstancesController < BaseController
|
||||
def index
|
||||
@instances = ordered_instances.page(params[:page])
|
||||
@instances = ordered_instances
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def paginated_instances
|
||||
Account.remote.by_domain_accounts.page(params[:page])
|
||||
end
|
||||
helper_method :paginated_instances
|
||||
|
||||
def ordered_instances
|
||||
Account.remote.by_domain_accounts
|
||||
paginated_instances.map { |account| Instance.new(account) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@@ -1,9 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class PubsubhubbubController < BaseController
|
||||
def index
|
||||
@subscriptions = Subscription.order(id: :desc).includes(:account).page(params[:page])
|
||||
end
|
||||
end
|
||||
end
|
@@ -2,17 +2,34 @@
|
||||
|
||||
module Admin
|
||||
class ReportedStatusesController < BaseController
|
||||
def destroy
|
||||
status = Status.find params[:id]
|
||||
include Authorization
|
||||
|
||||
RemovalWorker.perform_async(status.id)
|
||||
redirect_to admin_report_path(report)
|
||||
before_action :set_report
|
||||
before_action :set_status
|
||||
|
||||
def update
|
||||
@status.update(status_params)
|
||||
redirect_to admin_report_path(@report)
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @status, :destroy?
|
||||
RemovalWorker.perform_async(@status.id)
|
||||
redirect_to admin_report_path(@report)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def report
|
||||
Report.find(params[:report_id])
|
||||
def status_params
|
||||
params.require(:status).permit(:sensitive)
|
||||
end
|
||||
|
||||
def set_report
|
||||
@report = Report.find(params[:report_id])
|
||||
end
|
||||
|
||||
def set_status
|
||||
@status = @report.statuses.find(params[:id])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
19
app/controllers/admin/subscriptions_controller.rb
Normal file
19
app/controllers/admin/subscriptions_controller.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class SubscriptionsController < BaseController
|
||||
def index
|
||||
@subscriptions = ordered_subscriptions.page(requested_page)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ordered_subscriptions
|
||||
Subscription.order(id: :desc).includes(:account)
|
||||
end
|
||||
|
||||
def requested_page
|
||||
params[:page].to_i
|
||||
end
|
||||
end
|
||||
end
|
@@ -1,6 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::Activitypub::ActivitiesController < ApiController
|
||||
class Api::Activitypub::ActivitiesController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
# before_action :set_follow, only: [:show_follow]
|
||||
before_action :set_status, only: [:show_status]
|
||||
|
||||
@@ -8,7 +10,7 @@ class Api::Activitypub::ActivitiesController < ApiController
|
||||
|
||||
# Show a status in AS2 format, as either an Announce (reblog) or a Create (post) activity.
|
||||
def show_status
|
||||
return forbidden unless @status.permitted?
|
||||
authorize @status, :show?
|
||||
|
||||
if @status.reblog?
|
||||
render :show_status_announce
|
||||
|
@@ -1,12 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::Activitypub::NotesController < ApiController
|
||||
class Api::Activitypub::NotesController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
before_action :set_status
|
||||
|
||||
respond_to :activitystreams2
|
||||
|
||||
def show
|
||||
forbidden unless @status.permitted?
|
||||
authorize @status, :show?
|
||||
end
|
||||
|
||||
private
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::Activitypub::OutboxController < ApiController
|
||||
class Api::Activitypub::OutboxController < Api::BaseController
|
||||
before_action :set_account
|
||||
|
||||
respond_to :activitystreams2
|
||||
|
@@ -1,16 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ApiController < ApplicationController
|
||||
class Api::BaseController < ApplicationController
|
||||
DEFAULT_STATUSES_LIMIT = 20
|
||||
DEFAULT_ACCOUNTS_LIMIT = 40
|
||||
|
||||
protect_from_forgery with: :null_session
|
||||
include RateLimitHeaders
|
||||
|
||||
skip_before_action :verify_authenticity_token
|
||||
skip_before_action :store_current_location
|
||||
|
||||
before_action :set_rate_limit_headers
|
||||
|
||||
rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
|
||||
render json: { error: e.to_s }, status: 422
|
||||
end
|
||||
@@ -45,17 +43,6 @@ class ApiController < ApplicationController
|
||||
|
||||
protected
|
||||
|
||||
def set_rate_limit_headers
|
||||
return if request.env['rack.attack.throttle_data'].nil?
|
||||
|
||||
now = Time.now.utc
|
||||
match_data = request.env['rack.attack.throttle_data']['api']
|
||||
|
||||
response.headers['X-RateLimit-Limit'] = match_data[:limit].to_s
|
||||
response.headers['X-RateLimit-Remaining'] = (match_data[:limit] - match_data[:count]).to_s
|
||||
response.headers['X-RateLimit-Reset'] = (now + (match_data[:period] - now.to_i % match_data[:period])).iso8601(6)
|
||||
end
|
||||
|
||||
def set_pagination_headers(next_path = nil, prev_path = nil)
|
||||
links = []
|
||||
links << [next_path, [%w(rel next)]] if next_path
|
@@ -1,33 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::OEmbedController < ApiController
|
||||
class Api::OEmbedController < Api::BaseController
|
||||
respond_to :json
|
||||
|
||||
def show
|
||||
@stream_entry = stream_entry_from_url(params[:url])
|
||||
@width = params[:maxwidth].present? ? params[:maxwidth].to_i : 400
|
||||
@height = params[:maxheight].present? ? params[:maxheight].to_i : nil
|
||||
@stream_entry = find_stream_entry.stream_entry
|
||||
@width = maxwidth_or_default
|
||||
@height = maxheight_or_default
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def stream_entry_from_url(url)
|
||||
params = Rails.application.routes.recognize_path(url)
|
||||
|
||||
raise ActiveRecord::RecordNotFound unless recognized_stream_entry_url?(params)
|
||||
|
||||
stream_entry(params)
|
||||
def find_stream_entry
|
||||
StreamEntryFinder.new(params[:url])
|
||||
end
|
||||
|
||||
def recognized_stream_entry_url?(params)
|
||||
%w(stream_entries statuses).include?(params[:controller]) && params[:action] == 'show'
|
||||
def maxwidth_or_default
|
||||
(params[:maxwidth].presence || 400).to_i
|
||||
end
|
||||
|
||||
def stream_entry(params)
|
||||
if params[:controller] == 'stream_entries'
|
||||
StreamEntry.find(params[:id])
|
||||
else
|
||||
Status.find(params[:id]).stream_entry
|
||||
end
|
||||
def maxheight_or_default
|
||||
params[:maxheight].present? ? params[:maxheight].to_i : nil
|
||||
end
|
||||
end
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::PushController < ApiController
|
||||
class Api::PushController < Api::BaseController
|
||||
def update
|
||||
response, status = process_push_request
|
||||
render plain: response, status: status
|
||||
|
@@ -1,14 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::SalmonController < ApiController
|
||||
class Api::SalmonController < Api::BaseController
|
||||
before_action :set_account
|
||||
respond_to :txt
|
||||
|
||||
def update
|
||||
payload = request.body.read
|
||||
|
||||
if !payload.nil? && verify?(payload)
|
||||
SalmonWorker.perform_async(@account.id, payload.force_encoding('UTF-8'))
|
||||
if verify_payload?
|
||||
process_salmon
|
||||
head 201
|
||||
else
|
||||
head 202
|
||||
@@ -21,7 +19,15 @@ class Api::SalmonController < ApiController
|
||||
@account = Account.find(params[:id])
|
||||
end
|
||||
|
||||
def verify?(payload)
|
||||
VerifySalmonService.new.call(payload)
|
||||
def payload
|
||||
@_payload ||= request.body.read
|
||||
end
|
||||
|
||||
def verify_payload?
|
||||
payload.present? && VerifySalmonService.new.call(payload)
|
||||
end
|
||||
|
||||
def process_salmon
|
||||
SalmonWorker.perform_async(@account.id, payload.force_encoding('UTF-8'))
|
||||
end
|
||||
end
|
||||
|
@@ -1,22 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::SubscriptionsController < ApiController
|
||||
class Api::SubscriptionsController < Api::BaseController
|
||||
before_action :set_account
|
||||
respond_to :txt
|
||||
|
||||
def show
|
||||
if @account.subscription(api_subscription_url(@account.id)).valid?(params['hub.topic'])
|
||||
@account.update(subscription_expires_at: Time.now.utc + (params['hub.lease_seconds'] || 86_400).to_i.seconds)
|
||||
render plain: HTMLEntities.new.encode(params['hub.challenge']), status: 200
|
||||
if subscription.valid?(params['hub.topic'])
|
||||
@account.update(subscription_expires_at: future_expires)
|
||||
render plain: encoded_challenge, status: 200
|
||||
else
|
||||
head 404
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
body = request.body.read
|
||||
subscription = @account.subscription(api_subscription_url(@account.id))
|
||||
|
||||
if subscription.verify(body, request.headers['HTTP_X_HUB_SIGNATURE'])
|
||||
ProcessingWorker.perform_async(@account.id, body.force_encoding('UTF-8'))
|
||||
end
|
||||
@@ -26,6 +23,28 @@ class Api::SubscriptionsController < ApiController
|
||||
|
||||
private
|
||||
|
||||
def subscription
|
||||
@_subscription ||= @account.subscription(
|
||||
api_subscription_url(@account.id)
|
||||
)
|
||||
end
|
||||
|
||||
def body
|
||||
@_body ||= request.body.read
|
||||
end
|
||||
|
||||
def encoded_challenge
|
||||
HTMLEntities.new.encode(params['hub.challenge'])
|
||||
end
|
||||
|
||||
def future_expires
|
||||
Time.now.utc + lease_seconds_or_default
|
||||
end
|
||||
|
||||
def lease_seconds_or_default
|
||||
(params['hub.lease_seconds'] || 86_400).to_i.seconds
|
||||
end
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:id])
|
||||
end
|
||||
|
23
app/controllers/api/v1/accounts/credentials_controller.rb
Normal file
23
app/controllers/api/v1/accounts/credentials_controller.rb
Normal file
@@ -0,0 +1,23 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Accounts::CredentialsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :write }, only: [:update]
|
||||
before_action :require_user!
|
||||
|
||||
def show
|
||||
@account = current_account
|
||||
render 'api/v1/accounts/show'
|
||||
end
|
||||
|
||||
def update
|
||||
current_account.update!(account_params)
|
||||
@account = current_account
|
||||
render 'api/v1/accounts/show'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def account_params
|
||||
params.permit(:display_name, :note, :avatar, :header)
|
||||
end
|
||||
end
|
@@ -0,0 +1,68 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read }
|
||||
before_action :set_account
|
||||
after_action :insert_pagination_headers
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
@accounts = load_accounts
|
||||
render 'api/v1/accounts/index'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:account_id])
|
||||
end
|
||||
|
||||
def load_accounts
|
||||
default_accounts.merge(paginated_follows).to_a
|
||||
end
|
||||
|
||||
def default_accounts
|
||||
Account.includes(:active_relationships).references(:active_relationships)
|
||||
end
|
||||
|
||||
def paginated_follows
|
||||
Follow.where(target_account: @account).paginate_by_max_id(
|
||||
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||
params[:max_id],
|
||||
params[:since_id]
|
||||
)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def next_path
|
||||
if records_continue?
|
||||
api_v1_account_followers_url pagination_params(max_id: pagination_max_id)
|
||||
end
|
||||
end
|
||||
|
||||
def prev_path
|
||||
unless @accounts.empty?
|
||||
api_v1_account_followers_url pagination_params(since_id: pagination_since_id)
|
||||
end
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@accounts.last.active_relationships.first.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@accounts.first.active_relationships.first.id
|
||||
end
|
||||
|
||||
def records_continue?
|
||||
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.permit(:limit).merge(core_params)
|
||||
end
|
||||
end
|
@@ -0,0 +1,68 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read }
|
||||
before_action :set_account
|
||||
after_action :insert_pagination_headers
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
@accounts = load_accounts
|
||||
render 'api/v1/accounts/index'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:account_id])
|
||||
end
|
||||
|
||||
def load_accounts
|
||||
default_accounts.merge(paginated_follows).to_a
|
||||
end
|
||||
|
||||
def default_accounts
|
||||
Account.includes(:passive_relationships).references(:passive_relationships)
|
||||
end
|
||||
|
||||
def paginated_follows
|
||||
Follow.where(account: @account).paginate_by_max_id(
|
||||
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||
params[:max_id],
|
||||
params[:since_id]
|
||||
)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def next_path
|
||||
if records_continue?
|
||||
api_v1_account_following_index_url pagination_params(max_id: pagination_max_id)
|
||||
end
|
||||
end
|
||||
|
||||
def prev_path
|
||||
unless @accounts.empty?
|
||||
api_v1_account_following_index_url pagination_params(since_id: pagination_since_id)
|
||||
end
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@accounts.last.passive_relationships.first.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@accounts.first.passive_relationships.first.id
|
||||
end
|
||||
|
||||
def records_continue?
|
||||
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.permit(:limit).merge(core_params)
|
||||
end
|
||||
end
|
24
app/controllers/api/v1/accounts/relationships_controller.rb
Normal file
24
app/controllers/api/v1/accounts/relationships_controller.rb
Normal file
@@ -0,0 +1,24 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Accounts::RelationshipsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read }
|
||||
before_action :require_user!
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
@accounts = Account.where(id: account_ids).select('id')
|
||||
@following = Account.following_map(account_ids, current_user.account_id)
|
||||
@followed_by = Account.followed_by_map(account_ids, current_user.account_id)
|
||||
@blocking = Account.blocking_map(account_ids, current_user.account_id)
|
||||
@muting = Account.muting_map(account_ids, current_user.account_id)
|
||||
@requested = Account.requested_map(account_ids, current_user.account_id)
|
||||
@domain_blocking = Account.domain_blocking_map(account_ids, current_user.account_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def account_ids
|
||||
@_account_ids ||= Array(params[:id]).map(&:to_i)
|
||||
end
|
||||
end
|
29
app/controllers/api/v1/accounts/search_controller.rb
Normal file
29
app/controllers/api/v1/accounts/search_controller.rb
Normal file
@@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Accounts::SearchController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read }
|
||||
before_action :require_user!
|
||||
|
||||
respond_to :json
|
||||
|
||||
def show
|
||||
@accounts = account_search
|
||||
|
||||
render 'api/v1/accounts/index'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def account_search
|
||||
AccountSearchService.new.call(
|
||||
params[:q],
|
||||
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||
resolving_search?,
|
||||
current_account
|
||||
)
|
||||
end
|
||||
|
||||
def resolving_search?
|
||||
params[:resolve] == 'true'
|
||||
end
|
||||
end
|
92
app/controllers/api/v1/accounts/statuses_controller.rb
Normal file
92
app/controllers/api/v1/accounts/statuses_controller.rb
Normal file
@@ -0,0 +1,92 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Accounts::StatusesController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read }
|
||||
before_action :set_account
|
||||
after_action :insert_pagination_headers
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
@statuses = load_statuses
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:account_id])
|
||||
end
|
||||
|
||||
def load_statuses
|
||||
cached_account_statuses.tap do |statuses|
|
||||
set_maps(statuses)
|
||||
end
|
||||
end
|
||||
|
||||
def cached_account_statuses
|
||||
cache_collection account_statuses, Status
|
||||
end
|
||||
|
||||
def account_statuses
|
||||
default_statuses.tap do |statuses|
|
||||
statuses.merge!(only_media_scope) if params[:only_media]
|
||||
statuses.merge!(no_replies_scope) if params[:exclude_replies]
|
||||
end
|
||||
end
|
||||
|
||||
def default_statuses
|
||||
permitted_account_statuses.paginate_by_max_id(
|
||||
limit_param(DEFAULT_STATUSES_LIMIT),
|
||||
params[:max_id],
|
||||
params[:since_id]
|
||||
)
|
||||
end
|
||||
|
||||
def permitted_account_statuses
|
||||
@account.statuses.permitted_for(@account, current_account)
|
||||
end
|
||||
|
||||
def only_media_scope
|
||||
Status.where(id: account_media_status_ids)
|
||||
end
|
||||
|
||||
def account_media_status_ids
|
||||
@account.media_attachments.attached.reorder(nil).select(:status_id).distinct
|
||||
end
|
||||
|
||||
def no_replies_scope
|
||||
Status.without_replies
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.permit(:limit, :only_media, :exclude_replies).merge(core_params)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def next_path
|
||||
if records_continue?
|
||||
api_v1_account_statuses_url pagination_params(max_id: pagination_max_id)
|
||||
end
|
||||
end
|
||||
|
||||
def prev_path
|
||||
unless @statuses.empty?
|
||||
api_v1_account_statuses_url pagination_params(since_id: pagination_since_id)
|
||||
end
|
||||
end
|
||||
|
||||
def records_continue?
|
||||
@statuses.size == limit_param(DEFAULT_STATUSES_LIMIT)
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@statuses.last.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@statuses.first.id
|
||||
end
|
||||
end
|
@@ -1,73 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::AccountsController < ApiController
|
||||
before_action -> { doorkeeper_authorize! :read }, except: [:follow, :unfollow, :block, :unblock, :mute, :unmute, :update_credentials]
|
||||
class Api::V1::AccountsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read }, except: [:follow, :unfollow, :block, :unblock, :mute, :unmute]
|
||||
before_action -> { doorkeeper_authorize! :follow }, only: [:follow, :unfollow, :block, :unblock, :mute, :unmute]
|
||||
before_action -> { doorkeeper_authorize! :write }, only: [:update_credentials]
|
||||
before_action :require_user!, except: [:show, :following, :followers, :statuses]
|
||||
before_action :set_account, except: [:verify_credentials, :update_credentials, :suggestions, :search]
|
||||
before_action :require_user!, except: [:show]
|
||||
before_action :set_account
|
||||
|
||||
respond_to :json
|
||||
|
||||
def show; end
|
||||
|
||||
def verify_credentials
|
||||
@account = current_user.account
|
||||
render :show
|
||||
end
|
||||
|
||||
def update_credentials
|
||||
current_account.update!(account_params)
|
||||
@account = current_account
|
||||
render :show
|
||||
end
|
||||
|
||||
def following
|
||||
@accounts = Account.includes(:followers)
|
||||
.references(:followers)
|
||||
.merge(Follow.where(account: @account)
|
||||
.paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id]))
|
||||
.to_a
|
||||
|
||||
next_path = following_api_v1_account_url(pagination_params(max_id: @accounts.last.followers.last.id)) if @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||
prev_path = following_api_v1_account_url(pagination_params(since_id: @accounts.first.followers.first.id)) unless @accounts.empty?
|
||||
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
|
||||
render :index
|
||||
end
|
||||
|
||||
def followers
|
||||
@accounts = Account.includes(:following)
|
||||
.references(:following)
|
||||
.merge(Follow.where(target_account: @account)
|
||||
.paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||
params[:max_id],
|
||||
params[:since_id]))
|
||||
.to_a
|
||||
|
||||
next_path = followers_api_v1_account_url(pagination_params(max_id: @accounts.last.following.last.id)) if @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||
prev_path = followers_api_v1_account_url(pagination_params(since_id: @accounts.first.following.first.id)) unless @accounts.empty?
|
||||
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
|
||||
render :index
|
||||
end
|
||||
|
||||
def statuses
|
||||
@statuses = @account.statuses.permitted_for(@account, current_account).paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id])
|
||||
@statuses = @statuses.where(id: MediaAttachment.where(account: @account).where.not(status_id: nil).reorder('').select('distinct status_id')) if params[:only_media]
|
||||
@statuses = @statuses.without_replies if params[:exclude_replies]
|
||||
@statuses = cache_collection(@statuses, Status)
|
||||
|
||||
set_maps(@statuses)
|
||||
|
||||
next_path = statuses_api_v1_account_url(statuses_pagination_params(max_id: @statuses.last.id)) unless @statuses.empty?
|
||||
prev_path = statuses_api_v1_account_url(statuses_pagination_params(since_id: @statuses.first.id)) unless @statuses.empty?
|
||||
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def follow
|
||||
FollowService.new.call(current_user.account, @account.acct)
|
||||
set_relationship
|
||||
@@ -111,24 +53,6 @@ class Api::V1::AccountsController < ApiController
|
||||
render :relationship
|
||||
end
|
||||
|
||||
def relationships
|
||||
ids = params[:id].is_a?(Enumerable) ? params[:id].map(&:to_i) : [params[:id].to_i]
|
||||
|
||||
@accounts = Account.where(id: ids).select('id')
|
||||
@following = Account.following_map(ids, current_user.account_id)
|
||||
@followed_by = Account.followed_by_map(ids, current_user.account_id)
|
||||
@blocking = Account.blocking_map(ids, current_user.account_id)
|
||||
@muting = Account.muting_map(ids, current_user.account_id)
|
||||
@requested = Account.requested_map(ids, current_user.account_id)
|
||||
@domain_blocking = Account.domain_blocking_map(ids, current_user.account_id)
|
||||
end
|
||||
|
||||
def search
|
||||
@accounts = AccountSearchService.new.call(params[:q], limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:resolve] == 'true', current_account)
|
||||
|
||||
render :index
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@@ -143,16 +67,4 @@ class Api::V1::AccountsController < ApiController
|
||||
@requested = Account.requested_map([@account.id], current_user.account_id)
|
||||
@domain_blocking = Account.domain_blocking_map([@account.id], current_user.account_id)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.permit(:limit).merge(core_params)
|
||||
end
|
||||
|
||||
def statuses_pagination_params(core_params)
|
||||
params.permit(:limit, :only_media, :exclude_replies).merge(core_params)
|
||||
end
|
||||
|
||||
def account_params
|
||||
params.permit(:display_name, :note, :avatar, :header)
|
||||
end
|
||||
end
|
||||
|
@@ -1,14 +1,27 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::AppsController < ApiController
|
||||
class Api::V1::AppsController < Api::BaseController
|
||||
respond_to :json
|
||||
|
||||
def create
|
||||
@app = Doorkeeper::Application.create!(name: app_params[:client_name], redirect_uri: app_params[:redirect_uris], scopes: (app_params[:scopes] || Doorkeeper.configuration.default_scopes), website: app_params[:website])
|
||||
@app = Doorkeeper::Application.create!(application_options)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def application_options
|
||||
{
|
||||
name: app_params[:client_name],
|
||||
redirect_uri: app_params[:redirect_uris],
|
||||
scopes: app_scopes_or_default,
|
||||
website: app_params[:website],
|
||||
}
|
||||
end
|
||||
|
||||
def app_scopes_or_default
|
||||
app_params[:scopes] || Doorkeeper.configuration.default_scopes
|
||||
end
|
||||
|
||||
def app_params
|
||||
params.permit(:client_name, :redirect_uris, :scopes, :website)
|
||||
end
|
||||
|
@@ -1,26 +1,62 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::BlocksController < ApiController
|
||||
class Api::V1::BlocksController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :follow }
|
||||
before_action :require_user!
|
||||
after_action :insert_pagination_headers
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
@accounts = Account.includes(:blocked_by)
|
||||
.references(:blocked_by)
|
||||
.merge(Block.where(account: current_account)
|
||||
.paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id]))
|
||||
.to_a
|
||||
|
||||
next_path = api_v1_blocks_url(pagination_params(max_id: @accounts.last.blocked_by_ids.last)) if @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||
prev_path = api_v1_blocks_url(pagination_params(since_id: @accounts.first.blocked_by_ids.first)) unless @accounts.empty?
|
||||
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
@accounts = load_accounts
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_accounts
|
||||
default_accounts.merge(paginated_blocks).to_a
|
||||
end
|
||||
|
||||
def default_accounts
|
||||
Account.includes(:blocked_by).references(:blocked_by)
|
||||
end
|
||||
|
||||
def paginated_blocks
|
||||
Block.where(account: current_account).paginate_by_max_id(
|
||||
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||
params[:max_id],
|
||||
params[:since_id]
|
||||
)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def next_path
|
||||
if records_continue?
|
||||
api_v1_blocks_url pagination_params(max_id: pagination_max_id)
|
||||
end
|
||||
end
|
||||
|
||||
def prev_path
|
||||
unless @accounts.empty?
|
||||
api_v1_blocks_url pagination_params(since_id: pagination_since_id)
|
||||
end
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@accounts.last.blocked_by_ids.last
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@accounts.first.blocked_by_ids.first
|
||||
end
|
||||
|
||||
def records_continue?
|
||||
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.permit(:limit).merge(core_params)
|
||||
end
|
||||
|
@@ -1,18 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::DomainBlocksController < ApiController
|
||||
class Api::V1::DomainBlocksController < Api::BaseController
|
||||
BLOCK_LIMIT = 100
|
||||
|
||||
before_action -> { doorkeeper_authorize! :follow }
|
||||
before_action :require_user!
|
||||
after_action :insert_pagination_headers, only: :show
|
||||
|
||||
respond_to :json
|
||||
|
||||
def show
|
||||
@blocks = AccountDomainBlock.where(account: current_account).paginate_by_max_id(limit_param(100), params[:max_id], params[:since_id])
|
||||
|
||||
next_path = api_v1_domain_blocks_url(pagination_params(max_id: @blocks.last.id)) if @blocks.size == limit_param(100)
|
||||
prev_path = api_v1_domain_blocks_url(pagination_params(since_id: @blocks.first.id)) unless @blocks.empty?
|
||||
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
@blocks = load_domain_blocks
|
||||
render json: @blocks.map(&:domain)
|
||||
end
|
||||
|
||||
@@ -28,6 +26,46 @@ class Api::V1::DomainBlocksController < ApiController
|
||||
|
||||
private
|
||||
|
||||
def load_domain_blocks
|
||||
account_domain_blocks.paginate_by_max_id(
|
||||
limit_param(BLOCK_LIMIT),
|
||||
params[:max_id],
|
||||
params[:since_id]
|
||||
)
|
||||
end
|
||||
|
||||
def account_domain_blocks
|
||||
current_account.domain_blocks
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def next_path
|
||||
if records_continue?
|
||||
api_v1_domain_blocks_url pagination_params(max_id: pagination_max_id)
|
||||
end
|
||||
end
|
||||
|
||||
def prev_path
|
||||
unless @blocks.empty?
|
||||
api_v1_domain_blocks_url pagination_params(since_id: pagination_since_id)
|
||||
end
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@blocks.last.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@blocks.first.id
|
||||
end
|
||||
|
||||
def records_continue?
|
||||
@blocks.size == limit_param(BLOCK_LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.permit(:limit).merge(core_params)
|
||||
end
|
||||
|
@@ -1,25 +1,73 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::FavouritesController < ApiController
|
||||
class Api::V1::FavouritesController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read }
|
||||
before_action :require_user!
|
||||
after_action :insert_pagination_headers
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
results = Favourite.where(account: current_account).paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id])
|
||||
@statuses = cache_collection(Status.where(id: results.map(&:status_id)), Status)
|
||||
|
||||
set_maps(@statuses)
|
||||
|
||||
next_path = api_v1_favourites_url(pagination_params(max_id: results.last.id)) if results.size == limit_param(DEFAULT_STATUSES_LIMIT)
|
||||
prev_path = api_v1_favourites_url(pagination_params(since_id: results.first.id)) unless results.empty?
|
||||
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
@statuses = load_statuses
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_statuses
|
||||
cached_favourites.tap do |statuses|
|
||||
set_maps(statuses)
|
||||
end
|
||||
end
|
||||
|
||||
def cached_favourites
|
||||
cache_collection(
|
||||
Status.where(
|
||||
id: results.map(&:status_id)
|
||||
),
|
||||
Status
|
||||
)
|
||||
end
|
||||
|
||||
def results
|
||||
@_results ||= account_favourites.paginate_by_max_id(
|
||||
limit_param(DEFAULT_STATUSES_LIMIT),
|
||||
params[:max_id],
|
||||
params[:since_id]
|
||||
)
|
||||
end
|
||||
|
||||
def account_favourites
|
||||
current_account.favourites
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def next_path
|
||||
if records_continue?
|
||||
api_v1_favourites_url pagination_params(max_id: pagination_max_id)
|
||||
end
|
||||
end
|
||||
|
||||
def prev_path
|
||||
unless results.empty?
|
||||
api_v1_favourites_url pagination_params(since_id: pagination_since_id)
|
||||
end
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
results.last.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
results.first.id
|
||||
end
|
||||
|
||||
def records_continue?
|
||||
results.size == limit_param(DEFAULT_STATUSES_LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.permit(:limit).merge(core_params)
|
||||
end
|
||||
|
@@ -1,34 +1,74 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::FollowRequestsController < ApiController
|
||||
class Api::V1::FollowRequestsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :follow }
|
||||
before_action :require_user!
|
||||
after_action :insert_pagination_headers, only: :index
|
||||
|
||||
def index
|
||||
@accounts = Account.includes(:follow_requests)
|
||||
.references(:follow_requests)
|
||||
.merge(FollowRequest.where(target_account: current_account)
|
||||
.paginate_by_max_id(DEFAULT_ACCOUNTS_LIMIT, params[:max_id], params[:since_id]))
|
||||
.to_a
|
||||
|
||||
next_path = api_v1_follow_requests_url(pagination_params(max_id: @accounts.last.follow_requests.last.id)) if @accounts.size == DEFAULT_ACCOUNTS_LIMIT
|
||||
prev_path = api_v1_follow_requests_url(pagination_params(since_id: @accounts.first.follow_requests.first.id)) unless @accounts.empty?
|
||||
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
@accounts = load_accounts
|
||||
end
|
||||
|
||||
def authorize
|
||||
AuthorizeFollowService.new.call(Account.find(params[:id]), current_account)
|
||||
AuthorizeFollowService.new.call(account, current_account)
|
||||
render_empty
|
||||
end
|
||||
|
||||
def reject
|
||||
RejectFollowService.new.call(Account.find(params[:id]), current_account)
|
||||
RejectFollowService.new.call(account, current_account)
|
||||
render_empty
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def account
|
||||
Account.find(params[:id])
|
||||
end
|
||||
|
||||
def load_accounts
|
||||
default_accounts.merge(paginated_follow_requests).to_a
|
||||
end
|
||||
|
||||
def default_accounts
|
||||
Account.includes(:follow_requests).references(:follow_requests)
|
||||
end
|
||||
|
||||
def paginated_follow_requests
|
||||
FollowRequest.where(target_account: current_account).paginate_by_max_id(
|
||||
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||
params[:max_id],
|
||||
params[:since_id]
|
||||
)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def next_path
|
||||
if records_continue?
|
||||
api_v1_follow_requests_url pagination_params(max_id: pagination_max_id)
|
||||
end
|
||||
end
|
||||
|
||||
def prev_path
|
||||
unless @accounts.empty?
|
||||
api_v1_follow_requests_url pagination_params(since_id: pagination_since_id)
|
||||
end
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@accounts.last.follow_requests.last.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@accounts.first.follow_requests.first.id
|
||||
end
|
||||
|
||||
def records_continue?
|
||||
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.permit(:limit).merge(core_params)
|
||||
end
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::FollowsController < ApiController
|
||||
class Api::V1::FollowsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :follow }
|
||||
before_action :require_user!
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::InstancesController < ApiController
|
||||
class Api::V1::InstancesController < Api::BaseController
|
||||
respond_to :json
|
||||
|
||||
def show; end
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::MediaController < ApiController
|
||||
class Api::V1::MediaController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :write }
|
||||
before_action :require_user!
|
||||
|
||||
@@ -10,11 +10,11 @@ class Api::V1::MediaController < ApiController
|
||||
respond_to :json
|
||||
|
||||
def create
|
||||
@media = MediaAttachment.create!(account: current_user.account, file: media_params[:file])
|
||||
@media = current_account.media_attachments.create!(file: media_params[:file])
|
||||
rescue Paperclip::Errors::NotIdentifiedByImageMagickError
|
||||
render json: { error: 'File type of uploaded media could not be verified' }, status: 422
|
||||
render json: file_type_error, status: 422
|
||||
rescue Paperclip::Error
|
||||
render json: { error: 'Error processing thumbnail for uploaded media' }, status: 500
|
||||
render json: processing_error, status: 500
|
||||
end
|
||||
|
||||
private
|
||||
@@ -22,4 +22,12 @@ class Api::V1::MediaController < ApiController
|
||||
def media_params
|
||||
params.permit(:file)
|
||||
end
|
||||
|
||||
def file_type_error
|
||||
{ error: 'File type of uploaded media could not be verified' }
|
||||
end
|
||||
|
||||
def processing_error
|
||||
{ error: 'Error processing thumbnail for uploaded media' }
|
||||
end
|
||||
end
|
||||
|
@@ -1,26 +1,62 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::MutesController < ApiController
|
||||
class Api::V1::MutesController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :follow }
|
||||
before_action :require_user!
|
||||
after_action :insert_pagination_headers
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
@accounts = Account.includes(:muted_by)
|
||||
.references(:muted_by)
|
||||
.merge(Mute.where(account: current_account)
|
||||
.paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id]))
|
||||
.to_a
|
||||
|
||||
next_path = api_v1_mutes_url(pagination_params(max_id: @accounts.last.muted_by_ids.last)) if @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||
prev_path = api_v1_mutes_url(pagination_params(since_id: @accounts.first.muted_by_ids.first)) unless @accounts.empty?
|
||||
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
@accounts = load_accounts
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_accounts
|
||||
default_accounts.merge(paginated_mutes).to_a
|
||||
end
|
||||
|
||||
def default_accounts
|
||||
Account.includes(:muted_by).references(:muted_by)
|
||||
end
|
||||
|
||||
def paginated_mutes
|
||||
Mute.where(account: current_account).paginate_by_max_id(
|
||||
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||
params[:max_id],
|
||||
params[:since_id]
|
||||
)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def next_path
|
||||
if records_continue?
|
||||
api_v1_mutes_url pagination_params(max_id: pagination_max_id)
|
||||
end
|
||||
end
|
||||
|
||||
def prev_path
|
||||
unless @accounts.empty?
|
||||
api_v1_mutes_url pagination_params(since_id: pagination_since_id)
|
||||
end
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@accounts.last.muted_by_ids.last
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@accounts.first.muted_by_ids.first
|
||||
end
|
||||
|
||||
def records_continue?
|
||||
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.permit(:limit).merge(core_params)
|
||||
end
|
||||
|
@@ -1,42 +1,83 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::NotificationsController < ApiController
|
||||
class Api::V1::NotificationsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read }
|
||||
before_action :require_user!
|
||||
after_action :insert_pagination_headers, only: :index
|
||||
|
||||
respond_to :json
|
||||
|
||||
DEFAULT_NOTIFICATIONS_LIMIT = 15
|
||||
|
||||
def index
|
||||
@notifications = Notification.where(account: current_account).browserable(exclude_types).paginate_by_max_id(limit_param(DEFAULT_NOTIFICATIONS_LIMIT), params[:max_id], params[:since_id])
|
||||
@notifications = cache_collection(@notifications, Notification)
|
||||
statuses = @notifications.select { |n| !n.target_status.nil? }.map(&:target_status)
|
||||
|
||||
set_maps(statuses)
|
||||
|
||||
next_path = api_v1_notifications_url(pagination_params(max_id: @notifications.last.id)) unless @notifications.empty?
|
||||
prev_path = api_v1_notifications_url(pagination_params(since_id: @notifications.first.id)) unless @notifications.empty?
|
||||
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
@notifications = load_notifications
|
||||
set_maps_for_notification_target_statuses
|
||||
end
|
||||
|
||||
def show
|
||||
@notification = Notification.where(account: current_account).find(params[:id])
|
||||
@notification = current_account.notifications.find(params[:id])
|
||||
end
|
||||
|
||||
def clear
|
||||
Notification.where(account: current_account).delete_all
|
||||
current_account.notifications.delete_all
|
||||
render_empty
|
||||
end
|
||||
|
||||
def dismiss
|
||||
Notification.find_by!(account: current_account, id: params[:id]).destroy!
|
||||
current_account.notifications.find_by!(id: params[:id]).destroy!
|
||||
render_empty
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_notifications
|
||||
cache_collection paginated_notifications, Notification
|
||||
end
|
||||
|
||||
def paginated_notifications
|
||||
browserable_account_notifications.paginate_by_max_id(
|
||||
limit_param(DEFAULT_NOTIFICATIONS_LIMIT),
|
||||
params[:max_id],
|
||||
params[:since_id]
|
||||
)
|
||||
end
|
||||
|
||||
def browserable_account_notifications
|
||||
current_account.notifications.browserable(exclude_types)
|
||||
end
|
||||
|
||||
def set_maps_for_notification_target_statuses
|
||||
set_maps target_statuses_from_notifications
|
||||
end
|
||||
|
||||
def target_statuses_from_notifications
|
||||
@notifications.reject { |notification| notification.target_status.nil? }.map(&:target_status)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def next_path
|
||||
unless @notifications.empty?
|
||||
api_v1_notifications_url pagination_params(max_id: pagination_max_id)
|
||||
end
|
||||
end
|
||||
|
||||
def prev_path
|
||||
unless @notifications.empty?
|
||||
api_v1_notifications_url pagination_params(since_id: pagination_since_id)
|
||||
end
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@notifications.last.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@notifications.first.id
|
||||
end
|
||||
|
||||
def exclude_types
|
||||
val = params.permit(exclude_types: [])[:exclude_types] || []
|
||||
val = [val] unless val.is_a?(Enumerable)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::ReportsController < ApiController
|
||||
class Api::V1::ReportsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read }, except: [:create]
|
||||
before_action -> { doorkeeper_authorize! :write }, only: [:create]
|
||||
before_action :require_user!
|
||||
@@ -8,22 +8,32 @@ class Api::V1::ReportsController < ApiController
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
@reports = Report.where(account: current_account)
|
||||
@reports = current_account.reports
|
||||
end
|
||||
|
||||
def create
|
||||
status_ids = report_params[:status_ids].is_a?(Enumerable) ? report_params[:status_ids] : [report_params[:status_ids]]
|
||||
|
||||
@report = Report.create!(account: current_account,
|
||||
target_account: Account.find(report_params[:account_id]),
|
||||
status_ids: Status.find(status_ids).pluck(:id),
|
||||
comment: report_params[:comment])
|
||||
|
||||
@report = current_account.reports.create!(
|
||||
target_account: reported_account,
|
||||
status_ids: reported_status_ids,
|
||||
comment: report_params[:comment]
|
||||
)
|
||||
render :show
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def reported_status_ids
|
||||
Status.find(status_ids).pluck(:id)
|
||||
end
|
||||
|
||||
def status_ids
|
||||
Array(report_params[:status_ids])
|
||||
end
|
||||
|
||||
def reported_account
|
||||
Account.find(report_params[:account_id])
|
||||
end
|
||||
|
||||
def report_params
|
||||
params.permit(:account_id, :comment, status_ids: [])
|
||||
end
|
||||
|
@@ -1,9 +1,26 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::SearchController < ApiController
|
||||
class Api::V1::SearchController < Api::BaseController
|
||||
RESULTS_LIMIT = 5
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
@search = OpenStruct.new(SearchService.new.call(params[:q], 5, params[:resolve] == 'true', current_account))
|
||||
@search = OpenStruct.new(search_results)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def search_results
|
||||
SearchService.new.call(
|
||||
params[:q],
|
||||
RESULTS_LIMIT,
|
||||
resolving_search?,
|
||||
current_account
|
||||
)
|
||||
end
|
||||
|
||||
def resolving_search?
|
||||
params[:resolve] == 'true'
|
||||
end
|
||||
end
|
||||
|
@@ -0,0 +1,82 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
before_action :authorize_if_got_token
|
||||
before_action :set_status
|
||||
after_action :insert_pagination_headers
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
@accounts = load_accounts
|
||||
render 'api/v1/statuses/accounts'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_accounts
|
||||
default_accounts.merge(paginated_favourites).to_a
|
||||
end
|
||||
|
||||
def default_accounts
|
||||
Account
|
||||
.includes(:favourites)
|
||||
.references(:favourites)
|
||||
.where(favourites: { status_id: @status.id })
|
||||
end
|
||||
|
||||
def paginated_favourites
|
||||
Favourite.paginate_by_max_id(
|
||||
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||
params[:max_id],
|
||||
params[:since_id]
|
||||
)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def next_path
|
||||
if records_continue?
|
||||
api_v1_status_favourited_by_index_url pagination_params(max_id: pagination_max_id)
|
||||
end
|
||||
end
|
||||
|
||||
def prev_path
|
||||
unless @accounts.empty?
|
||||
api_v1_status_favourited_by_index_url pagination_params(since_id: pagination_since_id)
|
||||
end
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@accounts.last.favourites.last.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@accounts.first.favourites.first.id
|
||||
end
|
||||
|
||||
def records_continue?
|
||||
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||
end
|
||||
|
||||
def set_status
|
||||
@status = Status.find(params[:status_id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
# Reraise in order to get a 404 instead of a 403 error code
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
|
||||
def authorize_if_got_token
|
||||
request_token = Doorkeeper::OAuth::Token.from_request(request, *Doorkeeper.configuration.access_token_methods)
|
||||
doorkeeper_authorize! :read if request_token
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.permit(:limit).merge(core_params)
|
||||
end
|
||||
end
|
38
app/controllers/api/v1/statuses/favourites_controller.rb
Normal file
38
app/controllers/api/v1/statuses/favourites_controller.rb
Normal file
@@ -0,0 +1,38 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Statuses::FavouritesController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
before_action -> { doorkeeper_authorize! :write }
|
||||
before_action :require_user!
|
||||
|
||||
respond_to :json
|
||||
|
||||
def create
|
||||
@status = favourited_status
|
||||
render 'api/v1/statuses/show'
|
||||
end
|
||||
|
||||
def destroy
|
||||
@status = requested_status
|
||||
@favourites_map = { @status.id => false }
|
||||
|
||||
UnfavouriteWorker.perform_async(current_user.account_id, @status.id)
|
||||
|
||||
render 'api/v1/statuses/show'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def favourited_status
|
||||
service_result.status.reload
|
||||
end
|
||||
|
||||
def service_result
|
||||
FavouriteService.new.call(current_user.account, requested_status)
|
||||
end
|
||||
|
||||
def requested_status
|
||||
Status.find(params[:status_id])
|
||||
end
|
||||
end
|
41
app/controllers/api/v1/statuses/mutes_controller.rb
Normal file
41
app/controllers/api/v1/statuses/mutes_controller.rb
Normal file
@@ -0,0 +1,41 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Statuses::MutesController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
before_action -> { doorkeeper_authorize! :write }
|
||||
before_action :require_user!
|
||||
before_action :set_status
|
||||
before_action :set_conversation
|
||||
|
||||
respond_to :json
|
||||
|
||||
def create
|
||||
current_account.mute_conversation!(@conversation)
|
||||
@mutes_map = { @conversation.id => true }
|
||||
|
||||
render 'api/v1/statuses/show'
|
||||
end
|
||||
|
||||
def destroy
|
||||
current_account.unmute_conversation!(@conversation)
|
||||
@mutes_map = { @conversation.id => false }
|
||||
|
||||
render 'api/v1/statuses/show'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_status
|
||||
@status = Status.find(params[:status_id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
# Reraise in order to get a 404 instead of a 403 error code
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
|
||||
def set_conversation
|
||||
@conversation = @status.conversation
|
||||
raise Mastodon::ValidationError if @conversation.nil?
|
||||
end
|
||||
end
|
@@ -0,0 +1,79 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
before_action :authorize_if_got_token
|
||||
before_action :set_status
|
||||
after_action :insert_pagination_headers
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
@accounts = load_accounts
|
||||
render 'api/v1/statuses/accounts'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_accounts
|
||||
default_accounts.merge(paginated_statuses).to_a
|
||||
end
|
||||
|
||||
def default_accounts
|
||||
Account.includes(:statuses).references(:statuses)
|
||||
end
|
||||
|
||||
def paginated_statuses
|
||||
Status.where(reblog_of_id: @status.id).paginate_by_max_id(
|
||||
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||
params[:max_id],
|
||||
params[:since_id]
|
||||
)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def next_path
|
||||
if records_continue?
|
||||
api_v1_status_reblogged_by_index_url pagination_params(max_id: pagination_max_id)
|
||||
end
|
||||
end
|
||||
|
||||
def prev_path
|
||||
unless @accounts.empty?
|
||||
api_v1_status_reblogged_by_index_url pagination_params(since_id: pagination_since_id)
|
||||
end
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@accounts.last.statuses.last.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@accounts.first.statuses.first.id
|
||||
end
|
||||
|
||||
def records_continue?
|
||||
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||
end
|
||||
|
||||
def set_status
|
||||
@status = Status.find(params[:status_id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
# Reraise in order to get a 404 instead of a 403 error code
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
|
||||
def authorize_if_got_token
|
||||
request_token = Doorkeeper::OAuth::Token.from_request(request, *Doorkeeper.configuration.access_token_methods)
|
||||
doorkeeper_authorize! :read if request_token
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.permit(:limit).merge(core_params)
|
||||
end
|
||||
end
|
35
app/controllers/api/v1/statuses/reblogs_controller.rb
Normal file
35
app/controllers/api/v1/statuses/reblogs_controller.rb
Normal file
@@ -0,0 +1,35 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Statuses::ReblogsController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
before_action -> { doorkeeper_authorize! :write }
|
||||
before_action :require_user!
|
||||
|
||||
respond_to :json
|
||||
|
||||
def create
|
||||
@status = ReblogService.new.call(current_user.account, status_for_reblog)
|
||||
render 'api/v1/statuses/show'
|
||||
end
|
||||
|
||||
def destroy
|
||||
@status = status_for_destroy.reblog
|
||||
@reblogs_map = { @status.id => false }
|
||||
|
||||
authorize status_for_destroy, :unreblog?
|
||||
RemovalWorker.perform_async(status_for_destroy.id)
|
||||
|
||||
render 'api/v1/statuses/show'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def status_for_reblog
|
||||
Status.find params[:status_id]
|
||||
end
|
||||
|
||||
def status_for_destroy
|
||||
current_user.account.statuses.where(reblog_of_id: params[:status_id]).first!
|
||||
end
|
||||
end
|
@@ -1,11 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::StatusesController < ApiController
|
||||
before_action :authorize_if_got_token, except: [:create, :destroy, :reblog, :unreblog, :favourite, :unfavourite, :mute, :unmute]
|
||||
before_action -> { doorkeeper_authorize! :write }, only: [:create, :destroy, :reblog, :unreblog, :favourite, :unfavourite, :mute, :unmute]
|
||||
before_action :require_user!, except: [:show, :context, :card, :reblogged_by, :favourited_by]
|
||||
before_action :set_status, only: [:show, :context, :card, :reblogged_by, :favourited_by, :mute, :unmute]
|
||||
before_action :set_conversation, only: [:mute, :unmute]
|
||||
class Api::V1::StatusesController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
before_action :authorize_if_got_token, except: [:create, :destroy]
|
||||
before_action -> { doorkeeper_authorize! :write }, only: [:create, :destroy]
|
||||
before_action :require_user!, except: [:show, :context, :card]
|
||||
before_action :set_status, only: [:show, :context, :card]
|
||||
|
||||
respond_to :json
|
||||
|
||||
@@ -31,36 +32,6 @@ class Api::V1::StatusesController < ApiController
|
||||
render_empty if @card.nil?
|
||||
end
|
||||
|
||||
def reblogged_by
|
||||
@accounts = Account.includes(:statuses)
|
||||
.references(:statuses)
|
||||
.merge(Status.where(reblog_of_id: @status.id)
|
||||
.paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id]))
|
||||
.to_a
|
||||
|
||||
next_path = reblogged_by_api_v1_status_url(pagination_params(max_id: @accounts.last.statuses.last.id)) if @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||
prev_path = reblogged_by_api_v1_status_url(pagination_params(since_id: @accounts.first.statuses.first.id)) unless @accounts.empty?
|
||||
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
|
||||
render :accounts
|
||||
end
|
||||
|
||||
def favourited_by
|
||||
@accounts = Account.includes(:favourites)
|
||||
.references(:favourites)
|
||||
.where(favourites: { status_id: @status.id })
|
||||
.merge(Favourite.paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id]))
|
||||
.to_a
|
||||
|
||||
next_path = favourited_by_api_v1_status_url(pagination_params(max_id: @accounts.last.favourites.last.id)) if @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||
prev_path = favourited_by_api_v1_status_url(pagination_params(since_id: @accounts.first.favourites.first.id)) unless @accounts.empty?
|
||||
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
|
||||
render :accounts
|
||||
end
|
||||
|
||||
def create
|
||||
@status = PostStatusService.new.call(current_user.account,
|
||||
status_params[:status],
|
||||
@@ -77,65 +48,21 @@ class Api::V1::StatusesController < ApiController
|
||||
|
||||
def destroy
|
||||
@status = Status.where(account_id: current_user.account).find(params[:id])
|
||||
authorize @status, :destroy?
|
||||
|
||||
RemovalWorker.perform_async(@status.id)
|
||||
|
||||
render_empty
|
||||
end
|
||||
|
||||
def reblog
|
||||
@status = ReblogService.new.call(current_user.account, Status.find(params[:id]))
|
||||
render :show
|
||||
end
|
||||
|
||||
def unreblog
|
||||
reblog = Status.where(account_id: current_user.account, reblog_of_id: params[:id]).first!
|
||||
@status = reblog.reblog
|
||||
@reblogs_map = { @status.id => false }
|
||||
|
||||
RemovalWorker.perform_async(reblog.id)
|
||||
|
||||
render :show
|
||||
end
|
||||
|
||||
def favourite
|
||||
@status = FavouriteService.new.call(current_user.account, Status.find(params[:id])).status.reload
|
||||
render :show
|
||||
end
|
||||
|
||||
def unfavourite
|
||||
@status = Status.find(params[:id])
|
||||
@favourites_map = { @status.id => false }
|
||||
|
||||
UnfavouriteWorker.perform_async(current_user.account_id, @status.id)
|
||||
|
||||
render :show
|
||||
end
|
||||
|
||||
def mute
|
||||
current_account.mute_conversation!(@conversation)
|
||||
|
||||
@mutes_map = { @conversation.id => true }
|
||||
|
||||
render :show
|
||||
end
|
||||
|
||||
def unmute
|
||||
current_account.unmute_conversation!(@conversation)
|
||||
|
||||
@mutes_map = { @conversation.id => false }
|
||||
|
||||
render :show
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_status
|
||||
@status = Status.find(params[:id])
|
||||
raise ActiveRecord::RecordNotFound unless @status.permitted?(current_account)
|
||||
end
|
||||
|
||||
def set_conversation
|
||||
@conversation = @status.conversation
|
||||
raise Mastodon::ValidationError if @conversation.nil?
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
# Reraise in order to get a 404 instead of a 403 error code
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
|
||||
def status_params
|
||||
|
15
app/controllers/api/v1/streaming_controller.rb
Normal file
15
app/controllers/api/v1/streaming_controller.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::StreamingController < Api::BaseController
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
if Rails.configuration.x.streaming_api_base_url != request.host
|
||||
uri = URI.parse(request.url)
|
||||
uri.host = URI.parse(Rails.configuration.x.streaming_api_base_url).host
|
||||
redirect_to uri.to_s, status: 301
|
||||
else
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
end
|
||||
end
|
62
app/controllers/api/v1/timelines/home_controller.rb
Normal file
62
app/controllers/api/v1/timelines/home_controller.rb
Normal file
@@ -0,0 +1,62 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Timelines::HomeController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read }, only: [:show]
|
||||
before_action :require_user!, only: [:show]
|
||||
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
||||
|
||||
respond_to :json
|
||||
|
||||
def show
|
||||
@statuses = load_statuses
|
||||
render 'api/v1/timelines/show'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_statuses
|
||||
cached_home_statuses.tap do |statuses|
|
||||
set_maps(statuses)
|
||||
end
|
||||
end
|
||||
|
||||
def cached_home_statuses
|
||||
cache_collection home_statuses, Status
|
||||
end
|
||||
|
||||
def home_statuses
|
||||
account_home_feed.get(
|
||||
limit_param(DEFAULT_STATUSES_LIMIT),
|
||||
params[:max_id],
|
||||
params[:since_id]
|
||||
)
|
||||
end
|
||||
|
||||
def account_home_feed
|
||||
Feed.new(:home, current_account)
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.permit(:local, :limit).merge(core_params)
|
||||
end
|
||||
|
||||
def next_path
|
||||
api_v1_timelines_home_url pagination_params(max_id: pagination_max_id)
|
||||
end
|
||||
|
||||
def prev_path
|
||||
api_v1_timelines_home_url pagination_params(since_id: pagination_since_id)
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@statuses.last.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@statuses.first.id
|
||||
end
|
||||
end
|
60
app/controllers/api/v1/timelines/public_controller.rb
Normal file
60
app/controllers/api/v1/timelines/public_controller.rb
Normal file
@@ -0,0 +1,60 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Timelines::PublicController < Api::BaseController
|
||||
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
||||
|
||||
respond_to :json
|
||||
|
||||
def show
|
||||
@statuses = load_statuses
|
||||
render 'api/v1/timelines/show'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_statuses
|
||||
cached_public_statuses.tap do |statuses|
|
||||
set_maps(statuses)
|
||||
end
|
||||
end
|
||||
|
||||
def cached_public_statuses
|
||||
cache_collection public_statuses, Status
|
||||
end
|
||||
|
||||
def public_statuses
|
||||
public_timeline_statuses.paginate_by_max_id(
|
||||
limit_param(DEFAULT_STATUSES_LIMIT),
|
||||
params[:max_id],
|
||||
params[:since_id]
|
||||
)
|
||||
end
|
||||
|
||||
def public_timeline_statuses
|
||||
Status.as_public_timeline(current_account, params[:local])
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.permit(:local, :limit).merge(core_params)
|
||||
end
|
||||
|
||||
def next_path
|
||||
api_v1_timelines_public_url pagination_params(max_id: pagination_max_id)
|
||||
end
|
||||
|
||||
def prev_path
|
||||
api_v1_timelines_public_url pagination_params(since_id: pagination_since_id)
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@statuses.last.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@statuses.first.id
|
||||
end
|
||||
end
|
69
app/controllers/api/v1/timelines/tag_controller.rb
Normal file
69
app/controllers/api/v1/timelines/tag_controller.rb
Normal file
@@ -0,0 +1,69 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Timelines::TagController < Api::BaseController
|
||||
before_action :load_tag
|
||||
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
||||
|
||||
respond_to :json
|
||||
|
||||
def show
|
||||
@statuses = load_statuses
|
||||
render 'api/v1/timelines/show'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_tag
|
||||
@tag = Tag.find_by(name: params[:id].downcase)
|
||||
end
|
||||
|
||||
def load_statuses
|
||||
cached_tagged_statuses.tap do |statuses|
|
||||
set_maps(statuses)
|
||||
end
|
||||
end
|
||||
|
||||
def cached_tagged_statuses
|
||||
cache_collection tagged_statuses, Status
|
||||
end
|
||||
|
||||
def tagged_statuses
|
||||
if @tag.nil?
|
||||
[]
|
||||
else
|
||||
tag_timeline_statuses.paginate_by_max_id(
|
||||
limit_param(DEFAULT_STATUSES_LIMIT),
|
||||
params[:max_id],
|
||||
params[:since_id]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def tag_timeline_statuses
|
||||
Status.as_tag_timeline(@tag, current_account, params[:local])
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.permit(:local, :limit).merge(core_params)
|
||||
end
|
||||
|
||||
def next_path
|
||||
api_v1_timelines_tag_url params[:id], pagination_params(max_id: pagination_max_id)
|
||||
end
|
||||
|
||||
def prev_path
|
||||
api_v1_timelines_tag_url params[:id], pagination_params(since_id: pagination_since_id)
|
||||
end
|
||||
|
||||
def pagination_max_id
|
||||
@statuses.last.id
|
||||
end
|
||||
|
||||
def pagination_since_id
|
||||
@statuses.first.id
|
||||
end
|
||||
end
|
@@ -1,61 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::TimelinesController < ApiController
|
||||
before_action -> { doorkeeper_authorize! :read }, only: [:home]
|
||||
before_action :require_user!, only: [:home]
|
||||
|
||||
respond_to :json
|
||||
|
||||
def home
|
||||
@statuses = Feed.new(:home, current_account).get(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id])
|
||||
@statuses = cache_collection(@statuses)
|
||||
|
||||
set_maps(@statuses)
|
||||
|
||||
next_path = api_v1_home_timeline_url(pagination_params(max_id: @statuses.last.id)) unless @statuses.empty?
|
||||
prev_path = api_v1_home_timeline_url(pagination_params(since_id: @statuses.first.id)) unless @statuses.empty?
|
||||
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
|
||||
render :index
|
||||
end
|
||||
|
||||
def public
|
||||
@statuses = Status.as_public_timeline(current_account, params[:local]).paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id])
|
||||
@statuses = cache_collection(@statuses)
|
||||
|
||||
set_maps(@statuses)
|
||||
|
||||
next_path = api_v1_public_timeline_url(pagination_params(max_id: @statuses.last.id)) unless @statuses.empty?
|
||||
prev_path = api_v1_public_timeline_url(pagination_params(since_id: @statuses.first.id)) unless @statuses.empty?
|
||||
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
|
||||
render :index
|
||||
end
|
||||
|
||||
def tag
|
||||
@tag = Tag.find_by(name: params[:id].downcase)
|
||||
@statuses = @tag.nil? ? [] : Status.as_tag_timeline(@tag, current_account, params[:local]).paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id])
|
||||
@statuses = cache_collection(@statuses)
|
||||
|
||||
set_maps(@statuses)
|
||||
|
||||
next_path = api_v1_hashtag_timeline_url(params[:id], pagination_params(max_id: @statuses.last.id)) unless @statuses.empty?
|
||||
prev_path = api_v1_hashtag_timeline_url(params[:id], pagination_params(since_id: @statuses.first.id)) unless @statuses.empty?
|
||||
|
||||
set_pagination_headers(next_path, prev_path)
|
||||
|
||||
render :index
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def cache_collection(raw)
|
||||
super(raw, Status)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
params.permit(:local, :limit).merge(core_params)
|
||||
end
|
||||
end
|
@@ -1,15 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::Web::SettingsController < ApiController
|
||||
class Api::Web::SettingsController < Api::BaseController
|
||||
respond_to :json
|
||||
|
||||
before_action :require_user!
|
||||
|
||||
def update
|
||||
setting = ::Web::Setting.where(user: current_user).first_or_initialize(user: current_user)
|
||||
setting.data = params[:data]
|
||||
setting.save!
|
||||
|
||||
render_empty
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def setting
|
||||
@_setting ||= ::Web::Setting.where(user: current_user).first_or_initialize(user: current_user)
|
||||
end
|
||||
end
|
||||
|
@@ -39,7 +39,7 @@ class ApplicationController < ActionController::Base
|
||||
end
|
||||
|
||||
def check_suspension
|
||||
head 403 if current_user.account.suspended?
|
||||
forbidden if current_user.account.suspended?
|
||||
end
|
||||
|
||||
protected
|
||||
|
@@ -6,6 +6,10 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
||||
before_action :check_enabled_registrations, only: [:new, :create]
|
||||
before_action :configure_sign_up_params, only: [:create]
|
||||
|
||||
def destroy
|
||||
not_found
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def build_resource(hash = nil)
|
||||
|
@@ -12,13 +12,13 @@ class Auth::SessionsController < Devise::SessionsController
|
||||
def create
|
||||
super do |resource|
|
||||
remember_me(resource)
|
||||
flash[:notice] = nil
|
||||
flash.delete(:notice)
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
super
|
||||
flash[:notice] = nil
|
||||
flash.delete(:notice)
|
||||
end
|
||||
|
||||
protected
|
||||
@@ -27,7 +27,7 @@ class Auth::SessionsController < Devise::SessionsController
|
||||
if session[:otp_user_id]
|
||||
User.find(session[:otp_user_id])
|
||||
elsif user_params[:email]
|
||||
User.find_by(email: user_params[:email])
|
||||
User.find_for_authentication(email: user_params[:email])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -35,10 +35,10 @@ class Auth::SessionsController < Devise::SessionsController
|
||||
params.require(:user).permit(:email, :password, :otp_attempt)
|
||||
end
|
||||
|
||||
def after_sign_in_path_for(_resource)
|
||||
def after_sign_in_path_for(resource)
|
||||
last_url = stored_location_for(:user)
|
||||
|
||||
if [about_path].include?(last_url)
|
||||
if home_paths(resource).include?(last_url)
|
||||
root_path
|
||||
else
|
||||
last_url || root_path
|
||||
@@ -81,4 +81,14 @@ class Auth::SessionsController < Devise::SessionsController
|
||||
session[:otp_user_id] = user.id
|
||||
render :two_factor
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def home_paths(resource)
|
||||
paths = [about_path]
|
||||
if single_user_mode? && resource.is_a?(User)
|
||||
paths << short_account_path(username: resource.account)
|
||||
end
|
||||
paths
|
||||
end
|
||||
end
|
||||
|
22
app/controllers/concerns/authorization.rb
Normal file
22
app/controllers/concerns/authorization.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Authorization
|
||||
extend ActiveSupport::Concern
|
||||
include Pundit
|
||||
|
||||
def pundit_user
|
||||
current_account
|
||||
end
|
||||
|
||||
def authorize(*)
|
||||
super
|
||||
rescue Pundit::NotAuthorizedError
|
||||
raise Mastodon::NotPermittedError
|
||||
end
|
||||
|
||||
def authorize_with(user, record, query)
|
||||
Pundit.authorize(user, record, query)
|
||||
rescue Pundit::NotAuthorizedError
|
||||
raise Mastodon::NotPermittedError
|
||||
end
|
||||
end
|
30
app/controllers/concerns/export_controller_concern.rb
Normal file
30
app/controllers/concerns/export_controller_concern.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ExportControllerConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :authenticate_user!
|
||||
before_action :load_export
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_export
|
||||
@export = Export.new(current_account)
|
||||
end
|
||||
|
||||
def send_export_file
|
||||
respond_to do |format|
|
||||
format.csv { send_data export_data, filename: export_filename }
|
||||
end
|
||||
end
|
||||
|
||||
def export_data
|
||||
raise 'Override in controller'
|
||||
end
|
||||
|
||||
def export_filename
|
||||
"#{controller_name}.csv"
|
||||
end
|
||||
end
|
@@ -17,12 +17,24 @@ module Localized
|
||||
end
|
||||
|
||||
def default_locale
|
||||
ENV.fetch('DEFAULT_LOCALE') do
|
||||
user_supplied_locale || I18n.default_locale
|
||||
end
|
||||
request_locale || env_locale || I18n.default_locale
|
||||
end
|
||||
|
||||
def user_supplied_locale
|
||||
http_accept_language.language_region_compatible_from(I18n.available_locales)
|
||||
def env_locale
|
||||
ENV['DEFAULT_LOCALE']
|
||||
end
|
||||
|
||||
def request_locale
|
||||
preferred_locale || compatible_locale
|
||||
end
|
||||
|
||||
def preferred_locale
|
||||
http_accept_language.preferred_language_from([env_locale]) ||
|
||||
http_accept_language.preferred_language_from(I18n.available_locales)
|
||||
end
|
||||
|
||||
def compatible_locale
|
||||
http_accept_language.compatible_language_from([env_locale]) ||
|
||||
http_accept_language.compatible_language_from(I18n.available_locales)
|
||||
end
|
||||
end
|
||||
|
@@ -4,19 +4,13 @@ module ObfuscateFilename
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
class_methods do
|
||||
def obfuscate_filename(*args)
|
||||
before_action { obfuscate_filename(*args) }
|
||||
def obfuscate_filename(path)
|
||||
before_action do
|
||||
file = params.dig(*path)
|
||||
next if file.nil?
|
||||
|
||||
file.original_filename = SecureRandom.hex(8) + File.extname(file.original_filename)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def obfuscate_filename(path)
|
||||
file = params.dig(*path)
|
||||
return if file.nil?
|
||||
|
||||
file.original_filename = secure_token + File.extname(file.original_filename)
|
||||
end
|
||||
|
||||
def secure_token(length = 16)
|
||||
SecureRandom.hex(length / 2)
|
||||
end
|
||||
end
|
||||
|
57
app/controllers/concerns/rate_limit_headers.rb
Normal file
57
app/controllers/concerns/rate_limit_headers.rb
Normal file
@@ -0,0 +1,57 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module RateLimitHeaders
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :set_rate_limit_headers, if: :rate_limited_request?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_rate_limit_headers
|
||||
apply_header_limit
|
||||
apply_header_remaining
|
||||
apply_header_reset
|
||||
end
|
||||
|
||||
def rate_limited_request?
|
||||
!request.env['rack.attack.throttle_data'].nil?
|
||||
end
|
||||
|
||||
def apply_header_limit
|
||||
response.headers['X-RateLimit-Limit'] = rate_limit_limit
|
||||
end
|
||||
|
||||
def rate_limit_limit
|
||||
api_throttle_data[:limit].to_s
|
||||
end
|
||||
|
||||
def apply_header_remaining
|
||||
response.headers['X-RateLimit-Remaining'] = rate_limit_remaining
|
||||
end
|
||||
|
||||
def rate_limit_remaining
|
||||
(api_throttle_data[:limit] - api_throttle_data[:count]).to_s
|
||||
end
|
||||
|
||||
def apply_header_reset
|
||||
response.headers['X-RateLimit-Reset'] = rate_limit_reset
|
||||
end
|
||||
|
||||
def rate_limit_reset
|
||||
(request_time + reset_period_offset).iso8601(6)
|
||||
end
|
||||
|
||||
def api_throttle_data
|
||||
request.env['rack.attack.throttle_data']['api']
|
||||
end
|
||||
|
||||
def request_time
|
||||
@_request_time ||= Time.now.utc
|
||||
end
|
||||
|
||||
def reset_period_offset
|
||||
api_throttle_data[:period] - request_time.to_i % api_throttle_data[:period]
|
||||
end
|
||||
end
|
@@ -17,7 +17,7 @@ module UserTrackingConcern
|
||||
current_user.update_tracked_fields!(request)
|
||||
|
||||
# Regenerate feed if needed
|
||||
RegenerationWorker.perform_async(current_user.account_id) if user_needs_feed_update?
|
||||
regenerate_feed! if user_needs_feed_update?
|
||||
end
|
||||
|
||||
def user_needs_sign_in_update?
|
||||
@@ -27,4 +27,9 @@ module UserTrackingConcern
|
||||
def user_needs_feed_update?
|
||||
current_user.last_sign_in_at < REGENERATE_FEED_DAYS.days.ago
|
||||
end
|
||||
|
||||
def regenerate_feed!
|
||||
Redis.current.setnx("account:#{current_user.account_id}:regeneration", true) == 1 && Redis.current.expire("account:#{current_user.account_id}:regeneration", 3_600 * 24)
|
||||
RegenerationWorker.perform_async(current_user.account_id)
|
||||
end
|
||||
end
|
||||
|
@@ -4,6 +4,6 @@ class FollowerAccountsController < ApplicationController
|
||||
include AccountControllerConcern
|
||||
|
||||
def index
|
||||
@follows = Follow.where(target_account: @account).order(id: :desc).page(params[:page]).per(FOLLOW_PER_PAGE).preload(:account)
|
||||
@follows = Follow.where(target_account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:account)
|
||||
end
|
||||
end
|
||||
|
@@ -4,6 +4,6 @@ class FollowingAccountsController < ApplicationController
|
||||
include AccountControllerConcern
|
||||
|
||||
def index
|
||||
@follows = Follow.where(account: @account).order(id: :desc).page(params[:page]).per(FOLLOW_PER_PAGE).preload(:target_account)
|
||||
@follows = Follow.where(account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:target_account)
|
||||
end
|
||||
end
|
||||
|
11
app/controllers/manifests_controller.rb
Normal file
11
app/controllers/manifests_controller.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ManifestsController < ApplicationController
|
||||
before_action :set_instance_presenter
|
||||
|
||||
def show; end
|
||||
|
||||
def set_instance_presenter
|
||||
@instance_presenter = InstancePresenter.new
|
||||
end
|
||||
end
|
@@ -1,6 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class MediaController < ApplicationController
|
||||
include Authorization
|
||||
|
||||
before_action :verify_permitted_status
|
||||
|
||||
def show
|
||||
@@ -14,6 +16,9 @@ class MediaController < ApplicationController
|
||||
end
|
||||
|
||||
def verify_permitted_status
|
||||
raise ActiveRecord::RecordNotFound unless media_attachment.status.permitted?(current_account)
|
||||
authorize media_attachment.status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
# Reraise in order to get a 404 instead of a 403 error code
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
end
|
||||
|
27
app/controllers/settings/deletes_controller.rb
Normal file
27
app/controllers/settings/deletes_controller.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Settings::DeletesController < ApplicationController
|
||||
layout 'admin'
|
||||
|
||||
before_action :authenticate_user!
|
||||
|
||||
def show
|
||||
@confirmation = Form::DeleteConfirmation.new
|
||||
end
|
||||
|
||||
def destroy
|
||||
if current_user.valid_password?(delete_params[:password])
|
||||
Admin::SuspensionWorker.perform_async(current_user.account_id, true)
|
||||
sign_out
|
||||
redirect_to new_user_session_path, notice: I18n.t('deletes.success_msg')
|
||||
else
|
||||
redirect_to settings_delete_path, alert: I18n.t('deletes.bad_password_msg')
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def delete_params
|
||||
params.require(:form_delete_confirmation).permit(:password)
|
||||
end
|
||||
end
|
@@ -1,23 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Settings
|
||||
module Exports
|
||||
class BaseController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
|
||||
def index
|
||||
@export = Export.new(current_account)
|
||||
|
||||
respond_to do |format|
|
||||
format.csv { send_data export_data, filename: export_filename }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def export_filename
|
||||
"#{controller_name}.csv"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@@ -2,7 +2,13 @@
|
||||
|
||||
module Settings
|
||||
module Exports
|
||||
class BlockedAccountsController < BaseController
|
||||
class BlockedAccountsController < ApplicationController
|
||||
include ExportControllerConcern
|
||||
|
||||
def index
|
||||
send_export_file
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def export_data
|
||||
|
@@ -2,7 +2,13 @@
|
||||
|
||||
module Settings
|
||||
module Exports
|
||||
class FollowingAccountsController < BaseController
|
||||
class FollowingAccountsController < ApplicationController
|
||||
include ExportControllerConcern
|
||||
|
||||
def index
|
||||
send_export_file
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def export_data
|
||||
|
@@ -2,7 +2,13 @@
|
||||
|
||||
module Settings
|
||||
module Exports
|
||||
class MutedAccountsController < BaseController
|
||||
class MutedAccountsController < ApplicationController
|
||||
include ExportControllerConcern
|
||||
|
||||
def index
|
||||
send_export_file
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def export_data
|
||||
|
@@ -1,5 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'sidekiq-bulk'
|
||||
|
||||
class Settings::FollowerDomainsController < ApplicationController
|
||||
layout 'admin'
|
||||
|
||||
@@ -13,8 +15,8 @@ class Settings::FollowerDomainsController < ApplicationController
|
||||
def update
|
||||
domains = bulk_params[:select] || []
|
||||
|
||||
domains.each do |domain|
|
||||
SoftBlockDomainFollowersWorker.perform_async(current_account.id, domain)
|
||||
SoftBlockDomainFollowersWorker.push_bulk(domains) do |domain|
|
||||
[current_account.id, domain]
|
||||
end
|
||||
|
||||
redirect_to settings_follower_domains_path, notice: I18n.t('followers.success', count: domains.size)
|
||||
|
@@ -35,6 +35,7 @@ class Settings::PreferencesController < ApplicationController
|
||||
params.require(:user).permit(
|
||||
:setting_default_privacy,
|
||||
:setting_boost_modal,
|
||||
:setting_delete_modal,
|
||||
:setting_auto_play_gif,
|
||||
notification_emails: %i(follow follow_request reblog favourite mention digest),
|
||||
interactions: %i(must_be_follower must_be_following)
|
||||
|
@@ -1,6 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class StatusesController < ApplicationController
|
||||
include Authorization
|
||||
|
||||
layout 'public'
|
||||
|
||||
before_action :set_account
|
||||
@@ -30,7 +32,10 @@ class StatusesController < ApplicationController
|
||||
@stream_entry = @status.stream_entry
|
||||
@type = @stream_entry.activity_type.downcase
|
||||
|
||||
raise ActiveRecord::RecordNotFound unless @status.permitted?(current_account)
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
# Reraise in order to get a 404
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
|
||||
def check_account_suspension
|
||||
|
@@ -1,6 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class StreamEntriesController < ApplicationController
|
||||
include Authorization
|
||||
|
||||
layout 'public'
|
||||
|
||||
before_action :set_account
|
||||
@@ -11,12 +13,8 @@ class StreamEntriesController < ApplicationController
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
return gone if @stream_entry.activity.nil?
|
||||
|
||||
if @stream_entry.activity_type == 'Status'
|
||||
@ancestors = @stream_entry.activity.reply? ? cache_collection(@stream_entry.activity.ancestors(current_account), Status) : []
|
||||
@descendants = cache_collection(@stream_entry.activity.descendants(current_account), Status)
|
||||
end
|
||||
@ancestors = @stream_entry.activity.reply? ? cache_collection(@stream_entry.activity.ancestors(current_account), Status) : []
|
||||
@descendants = cache_collection(@stream_entry.activity.descendants(current_account), Status)
|
||||
end
|
||||
|
||||
format.atom do
|
||||
@@ -46,7 +44,11 @@ class StreamEntriesController < ApplicationController
|
||||
@stream_entry = @account.stream_entries.where(activity_type: 'Status').find(params[:id])
|
||||
@type = @stream_entry.activity_type.downcase
|
||||
|
||||
raise ActiveRecord::RecordNotFound if @stream_entry.activity.nil? || (@stream_entry.hidden? && (@stream_entry.activity_type != 'Status' || (@stream_entry.activity_type == 'Status' && !@stream_entry.activity.permitted?(current_account))))
|
||||
raise ActiveRecord::RecordNotFound if @stream_entry.activity.nil?
|
||||
authorize @stream_entry.activity, :show? if @stream_entry.hidden?
|
||||
rescue Mastodon::NotPermittedError
|
||||
# Reraise in order to get a 404
|
||||
raise ActiveRecord::RecordNotFound
|
||||
end
|
||||
|
||||
def check_account_suspension
|
||||
|
@@ -2,6 +2,8 @@
|
||||
|
||||
module WellKnown
|
||||
class HostMetaController < ApplicationController
|
||||
include RoutingHelper
|
||||
|
||||
def show
|
||||
@webfinger_template = "#{webfinger_url}?resource={uri}"
|
||||
|
||||
|
@@ -2,6 +2,8 @@
|
||||
|
||||
module WellKnown
|
||||
class WebfingerController < ApplicationController
|
||||
include RoutingHelper
|
||||
|
||||
def show
|
||||
@account = Account.find_local!(username_from_resource)
|
||||
@canonical_account_uri = @account.to_webfinger_s
|
||||
|
@@ -1,8 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module StreamEntriesHelper
|
||||
EMBEDDED_CONTROLLER = 'stream_entries'.freeze
|
||||
EMBEDDED_ACTION = 'embed'.freeze
|
||||
EMBEDDED_CONTROLLER = 'stream_entries'
|
||||
EMBEDDED_ACTION = 'embed'
|
||||
|
||||
def display_name(account)
|
||||
account.display_name.presence || account.username
|
||||
@@ -47,11 +47,16 @@ module StreamEntriesHelper
|
||||
end
|
||||
end
|
||||
|
||||
def rtl_status?(status)
|
||||
status.local? ? rtl?(status.text) : rtl?(strip_tags(status.text))
|
||||
end
|
||||
|
||||
def rtl?(text)
|
||||
text = simplified_text(text)
|
||||
rtl_characters = /[\p{Hebrew}|\p{Arabic}|\p{Syriac}|\p{Thaana}|\p{Nko}]+/m.match(text)
|
||||
|
||||
if rtl_characters.present?
|
||||
total_size = text.strip.size.to_f
|
||||
total_size = text.size.to_f
|
||||
rtl_size(rtl_characters.to_a) / total_size > 0.3
|
||||
else
|
||||
false
|
||||
@@ -60,6 +65,18 @@ module StreamEntriesHelper
|
||||
|
||||
private
|
||||
|
||||
def simplified_text(text)
|
||||
text.dup.tap do |new_text|
|
||||
URI.extract(new_text).each do |url|
|
||||
new_text.gsub!(url, '')
|
||||
end
|
||||
|
||||
new_text.gsub!(Account::MENTION_RE, '')
|
||||
new_text.gsub!(Tag::HASHTAG_RE, '')
|
||||
new_text.gsub!(/\s+/, '')
|
||||
end
|
||||
end
|
||||
|
||||
def rtl_size(characters)
|
||||
characters.reduce(0) { |acc, elem| acc + elem.size }.to_f
|
||||
end
|
||||
|
BIN
app/javascript/images/elephant-friend-1.png
Normal file
BIN
app/javascript/images/elephant-friend-1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 142 KiB |
@@ -29,22 +29,6 @@ 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_TIMELINE_FETCH_REQUEST = 'ACCOUNT_TIMELINE_FETCH_REQUEST';
|
||||
export const ACCOUNT_TIMELINE_FETCH_SUCCESS = 'ACCOUNT_TIMELINE_FETCH_SUCCESS';
|
||||
export const ACCOUNT_TIMELINE_FETCH_FAIL = 'ACCOUNT_TIMELINE_FETCH_FAIL';
|
||||
|
||||
export const ACCOUNT_TIMELINE_EXPAND_REQUEST = 'ACCOUNT_TIMELINE_EXPAND_REQUEST';
|
||||
export const ACCOUNT_TIMELINE_EXPAND_SUCCESS = 'ACCOUNT_TIMELINE_EXPAND_SUCCESS';
|
||||
export const ACCOUNT_TIMELINE_EXPAND_FAIL = 'ACCOUNT_TIMELINE_EXPAND_FAIL';
|
||||
|
||||
export const ACCOUNT_MEDIA_TIMELINE_FETCH_REQUEST = 'ACCOUNT_MEDIA_TIMELINE_FETCH_REQUEST';
|
||||
export const ACCOUNT_MEDIA_TIMELINE_FETCH_SUCCESS = 'ACCOUNT_MEDIA_TIMELINE_FETCH_SUCCESS';
|
||||
export const ACCOUNT_MEDIA_TIMELINE_FETCH_FAIL = 'ACCOUNT_MEDIA_TIMELINE_FETCH_FAIL';
|
||||
|
||||
export const ACCOUNT_MEDIA_TIMELINE_EXPAND_REQUEST = 'ACCOUNT_MEDIA_TIMELINE_EXPAND_REQUEST';
|
||||
export const ACCOUNT_MEDIA_TIMELINE_EXPAND_SUCCESS = 'ACCOUNT_MEDIA_TIMELINE_EXPAND_SUCCESS';
|
||||
export const ACCOUNT_MEDIA_TIMELINE_EXPAND_FAIL = 'ACCOUNT_MEDIA_TIMELINE_EXPAND_FAIL';
|
||||
|
||||
export const FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST';
|
||||
export const FOLLOWERS_FETCH_SUCCESS = 'FOLLOWERS_FETCH_SUCCESS';
|
||||
export const FOLLOWERS_FETCH_FAIL = 'FOLLOWERS_FETCH_FAIL';
|
||||
@@ -99,93 +83,6 @@ export function fetchAccount(id) {
|
||||
};
|
||||
};
|
||||
|
||||
export function fetchAccountTimeline(id, replace = false) {
|
||||
return (dispatch, getState) => {
|
||||
const ids = getState().getIn(['timelines', 'accounts_timelines', id, 'items'], Immutable.List());
|
||||
const newestId = ids.size > 0 ? ids.first() : null;
|
||||
|
||||
let params = {};
|
||||
let skipLoading = false;
|
||||
|
||||
if (newestId !== null && !replace) {
|
||||
params.since_id = newestId;
|
||||
skipLoading = true;
|
||||
}
|
||||
|
||||
dispatch(fetchAccountTimelineRequest(id, skipLoading));
|
||||
|
||||
api(getState).get(`/api/v1/accounts/${id}/statuses`, { params }).then(response => {
|
||||
dispatch(fetchAccountTimelineSuccess(id, response.data, replace, skipLoading));
|
||||
}).catch(error => {
|
||||
dispatch(fetchAccountTimelineFail(id, error, skipLoading));
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export function fetchAccountMediaTimeline(id, replace = false) {
|
||||
return (dispatch, getState) => {
|
||||
const ids = getState().getIn(['timelines', 'accounts_media_timelines', id, 'items'], Immutable.List());
|
||||
const newestId = ids.size > 0 ? ids.first() : null;
|
||||
|
||||
let params = { only_media: 'true', limit: 12 };
|
||||
let skipLoading = false;
|
||||
|
||||
if (newestId !== null && !replace) {
|
||||
params.since_id = newestId;
|
||||
skipLoading = true;
|
||||
}
|
||||
|
||||
dispatch(fetchAccountMediaTimelineRequest(id, skipLoading));
|
||||
|
||||
api(getState).get(`/api/v1/accounts/${id}/statuses`, { params }).then(response => {
|
||||
dispatch(fetchAccountMediaTimelineSuccess(id, response.data, replace, skipLoading));
|
||||
}).catch(error => {
|
||||
dispatch(fetchAccountMediaTimelineFail(id, error, skipLoading));
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export function expandAccountTimeline(id) {
|
||||
return (dispatch, getState) => {
|
||||
const lastId = getState().getIn(['timelines', 'accounts_timelines', id, 'items'], Immutable.List()).last();
|
||||
|
||||
dispatch(expandAccountTimelineRequest(id));
|
||||
|
||||
api(getState).get(`/api/v1/accounts/${id}/statuses`, {
|
||||
params: {
|
||||
limit: 10,
|
||||
max_id: lastId,
|
||||
},
|
||||
}).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(expandAccountTimelineSuccess(id, response.data, next));
|
||||
}).catch(error => {
|
||||
dispatch(expandAccountTimelineFail(id, error));
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export function expandAccountMediaTimeline(id) {
|
||||
return (dispatch, getState) => {
|
||||
const lastId = getState().getIn(['timelines', 'accounts_media_timelines', id, 'items'], Immutable.List()).last();
|
||||
|
||||
dispatch(expandAccountMediaTimelineRequest(id));
|
||||
|
||||
api(getState).get(`/api/v1/accounts/${id}/statuses`, {
|
||||
params: {
|
||||
limit: 12,
|
||||
only_media: 'true',
|
||||
max_id: lastId,
|
||||
},
|
||||
}).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(expandAccountMediaTimelineSuccess(id, response.data, next));
|
||||
}).catch(error => {
|
||||
dispatch(expandAccountMediaTimelineFail(id, error));
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export function fetchAccountRequest(id) {
|
||||
return {
|
||||
type: ACCOUNT_FETCH_REQUEST,
|
||||
@@ -275,110 +172,6 @@ export function unfollowAccountFail(error) {
|
||||
};
|
||||
};
|
||||
|
||||
export function fetchAccountTimelineRequest(id, skipLoading) {
|
||||
return {
|
||||
type: ACCOUNT_TIMELINE_FETCH_REQUEST,
|
||||
id,
|
||||
skipLoading,
|
||||
};
|
||||
};
|
||||
|
||||
export function fetchAccountTimelineSuccess(id, statuses, replace, skipLoading) {
|
||||
return {
|
||||
type: ACCOUNT_TIMELINE_FETCH_SUCCESS,
|
||||
id,
|
||||
statuses,
|
||||
replace,
|
||||
skipLoading,
|
||||
};
|
||||
};
|
||||
|
||||
export function fetchAccountTimelineFail(id, error, skipLoading) {
|
||||
return {
|
||||
type: ACCOUNT_TIMELINE_FETCH_FAIL,
|
||||
id,
|
||||
error,
|
||||
skipLoading,
|
||||
skipAlert: error.response.status === 404,
|
||||
};
|
||||
};
|
||||
|
||||
export function fetchAccountMediaTimelineRequest(id, skipLoading) {
|
||||
return {
|
||||
type: ACCOUNT_MEDIA_TIMELINE_FETCH_REQUEST,
|
||||
id,
|
||||
skipLoading,
|
||||
};
|
||||
};
|
||||
|
||||
export function fetchAccountMediaTimelineSuccess(id, statuses, replace, skipLoading) {
|
||||
return {
|
||||
type: ACCOUNT_MEDIA_TIMELINE_FETCH_SUCCESS,
|
||||
id,
|
||||
statuses,
|
||||
replace,
|
||||
skipLoading,
|
||||
};
|
||||
};
|
||||
|
||||
export function fetchAccountMediaTimelineFail(id, error, skipLoading) {
|
||||
return {
|
||||
type: ACCOUNT_MEDIA_TIMELINE_FETCH_FAIL,
|
||||
id,
|
||||
error,
|
||||
skipLoading,
|
||||
skipAlert: error.response.status === 404,
|
||||
};
|
||||
};
|
||||
|
||||
export function expandAccountTimelineRequest(id) {
|
||||
return {
|
||||
type: ACCOUNT_TIMELINE_EXPAND_REQUEST,
|
||||
id,
|
||||
};
|
||||
};
|
||||
|
||||
export function expandAccountTimelineSuccess(id, statuses, next) {
|
||||
return {
|
||||
type: ACCOUNT_TIMELINE_EXPAND_SUCCESS,
|
||||
id,
|
||||
statuses,
|
||||
next,
|
||||
};
|
||||
};
|
||||
|
||||
export function expandAccountTimelineFail(id, error) {
|
||||
return {
|
||||
type: ACCOUNT_TIMELINE_EXPAND_FAIL,
|
||||
id,
|
||||
error,
|
||||
};
|
||||
};
|
||||
|
||||
export function expandAccountMediaTimelineRequest(id) {
|
||||
return {
|
||||
type: ACCOUNT_MEDIA_TIMELINE_EXPAND_REQUEST,
|
||||
id,
|
||||
};
|
||||
};
|
||||
|
||||
export function expandAccountMediaTimelineSuccess(id, statuses, next) {
|
||||
return {
|
||||
type: ACCOUNT_MEDIA_TIMELINE_EXPAND_SUCCESS,
|
||||
id,
|
||||
statuses,
|
||||
next,
|
||||
};
|
||||
};
|
||||
|
||||
export function expandAccountMediaTimelineFail(id, error) {
|
||||
return {
|
||||
type: ACCOUNT_MEDIA_TIMELINE_EXPAND_FAIL,
|
||||
id,
|
||||
error,
|
||||
};
|
||||
};
|
||||
|
||||
export function blockAccount(id) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(blockAccountRequest(id));
|
||||
|
40
app/javascript/mastodon/actions/columns.js
Normal file
40
app/javascript/mastodon/actions/columns.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import { saveSettings } from './settings';
|
||||
|
||||
export const COLUMN_ADD = 'COLUMN_ADD';
|
||||
export const COLUMN_REMOVE = 'COLUMN_REMOVE';
|
||||
export const COLUMN_MOVE = 'COLUMN_MOVE';
|
||||
|
||||
export function addColumn(id, params) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: COLUMN_ADD,
|
||||
id,
|
||||
params,
|
||||
});
|
||||
|
||||
dispatch(saveSettings());
|
||||
};
|
||||
};
|
||||
|
||||
export function removeColumn(uuid) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: COLUMN_REMOVE,
|
||||
uuid,
|
||||
});
|
||||
|
||||
dispatch(saveSettings());
|
||||
};
|
||||
};
|
||||
|
||||
export function moveColumn(uuid, direction) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: COLUMN_MOVE,
|
||||
uuid,
|
||||
direction,
|
||||
});
|
||||
|
||||
dispatch(saveSettings());
|
||||
};
|
||||
};
|
@@ -124,25 +124,22 @@ export function refreshNotificationsFail(error, skipLoading) {
|
||||
|
||||
export function expandNotifications() {
|
||||
return (dispatch, getState) => {
|
||||
const url = getState().getIn(['notifications', 'next'], null);
|
||||
const lastId = getState().getIn(['notifications', 'items']).last();
|
||||
const items = getState().getIn(['notifications', 'items'], Immutable.List());
|
||||
|
||||
if (url === null || getState().getIn(['notifications', 'isLoading'])) {
|
||||
if (getState().getIn(['notifications', 'isLoading']) || items.size === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(expandNotificationsRequest());
|
||||
|
||||
const params = {
|
||||
max_id: lastId,
|
||||
max_id: items.last().get('id'),
|
||||
limit: 20,
|
||||
exclude_types: excludeTypesFromSettings(getState()),
|
||||
};
|
||||
|
||||
params.exclude_types = excludeTypesFromSettings(getState());
|
||||
dispatch(expandNotificationsRequest());
|
||||
|
||||
api(getState).get(url, params).then(response => {
|
||||
api(getState).get('/api/v1/notifications', { params }).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
|
||||
dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null));
|
||||
fetchRelatedRelationships(dispatch, response.data);
|
||||
}).catch(error => {
|
||||
|
@@ -3,10 +3,14 @@ import axios from 'axios';
|
||||
export const SETTING_CHANGE = 'SETTING_CHANGE';
|
||||
|
||||
export function changeSetting(key, value) {
|
||||
return {
|
||||
type: SETTING_CHANGE,
|
||||
key,
|
||||
value,
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: SETTING_CHANGE,
|
||||
key,
|
||||
value,
|
||||
});
|
||||
|
||||
dispatch(saveSettings());
|
||||
};
|
||||
};
|
||||
|
||||
|
@@ -56,91 +56,89 @@ export function deleteFromTimelines(id) {
|
||||
};
|
||||
};
|
||||
|
||||
export function refreshTimelineRequest(timeline, id, skipLoading) {
|
||||
export function refreshTimelineRequest(timeline, skipLoading) {
|
||||
return {
|
||||
type: TIMELINE_REFRESH_REQUEST,
|
||||
timeline,
|
||||
id,
|
||||
skipLoading,
|
||||
};
|
||||
};
|
||||
|
||||
export function refreshTimeline(timeline, id = null) {
|
||||
export function refreshTimeline(timelineId, path, params = {}) {
|
||||
return function (dispatch, getState) {
|
||||
if (getState().getIn(['timelines', timeline, 'isLoading'])) {
|
||||
const timeline = getState().getIn(['timelines', timelineId], Immutable.Map());
|
||||
|
||||
if (timeline.get('isLoading') || timeline.get('online')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ids = getState().getIn(['timelines', timeline, 'items'], Immutable.List());
|
||||
const ids = timeline.get('items', Immutable.List());
|
||||
const newestId = ids.size > 0 ? ids.first() : null;
|
||||
let params = getState().getIn(['timelines', timeline, 'params'], {});
|
||||
const path = getState().getIn(['timelines', timeline, 'path'])(id);
|
||||
|
||||
let skipLoading = false;
|
||||
let skipLoading = timeline.get('loaded');
|
||||
|
||||
if (newestId !== null && getState().getIn(['timelines', timeline, 'loaded']) && (id === null || getState().getIn(['timelines', timeline, 'id']) === id)) {
|
||||
if (id === null && getState().getIn(['timelines', timeline, 'online'])) {
|
||||
// Skip refreshing when timeline is live anyway
|
||||
return;
|
||||
}
|
||||
|
||||
params = { ...params, since_id: newestId };
|
||||
skipLoading = true;
|
||||
} else if (getState().getIn(['timelines', timeline, 'loaded'])) {
|
||||
skipLoading = true;
|
||||
if (newestId !== null) {
|
||||
params.since_id = newestId;
|
||||
}
|
||||
|
||||
dispatch(refreshTimelineRequest(timeline, id, skipLoading));
|
||||
dispatch(refreshTimelineRequest(timelineId, skipLoading));
|
||||
|
||||
api(getState).get(path, { params }).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(refreshTimelineSuccess(timeline, response.data, skipLoading, next ? next.uri : null));
|
||||
dispatch(refreshTimelineSuccess(timelineId, response.data, skipLoading, next ? next.uri : null));
|
||||
}).catch(error => {
|
||||
dispatch(refreshTimelineFail(timeline, error, skipLoading));
|
||||
dispatch(refreshTimelineFail(timelineId, error, skipLoading));
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export const refreshHomeTimeline = () => refreshTimeline('home', '/api/v1/timelines/home');
|
||||
export const refreshPublicTimeline = () => refreshTimeline('public', '/api/v1/timelines/public');
|
||||
export const refreshCommunityTimeline = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true });
|
||||
export const refreshAccountTimeline = accountId => refreshTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`);
|
||||
export const refreshAccountMediaTimeline = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true });
|
||||
export const refreshHashtagTimeline = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
|
||||
|
||||
export function refreshTimelineFail(timeline, error, skipLoading) {
|
||||
return {
|
||||
type: TIMELINE_REFRESH_FAIL,
|
||||
timeline,
|
||||
error,
|
||||
skipLoading,
|
||||
skipAlert: error.response.status === 404,
|
||||
};
|
||||
};
|
||||
|
||||
export function expandTimeline(timeline) {
|
||||
export function expandTimeline(timelineId, path, params = {}) {
|
||||
return (dispatch, getState) => {
|
||||
if (getState().getIn(['timelines', timeline, 'isLoading'])) {
|
||||
const timeline = getState().getIn(['timelines', timelineId], Immutable.Map());
|
||||
const ids = timeline.get('items', Immutable.List());
|
||||
|
||||
if (timeline.get('isLoading') || ids.size === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (getState().getIn(['timelines', timeline, 'items']).size === 0) {
|
||||
return;
|
||||
}
|
||||
params.max_id = ids.last();
|
||||
params.limit = 10;
|
||||
|
||||
const path = getState().getIn(['timelines', timeline, 'path'])(getState().getIn(['timelines', timeline, 'id']));
|
||||
const params = getState().getIn(['timelines', timeline, 'params'], {});
|
||||
const lastId = getState().getIn(['timelines', timeline, 'items']).last();
|
||||
dispatch(expandTimelineRequest(timelineId));
|
||||
|
||||
dispatch(expandTimelineRequest(timeline));
|
||||
|
||||
api(getState).get(path, {
|
||||
params: {
|
||||
...params,
|
||||
max_id: lastId,
|
||||
limit: 10,
|
||||
},
|
||||
}).then(response => {
|
||||
api(getState).get(path, { params }).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(expandTimelineSuccess(timeline, response.data, next ? next.uri : null));
|
||||
dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null));
|
||||
}).catch(error => {
|
||||
dispatch(expandTimelineFail(timeline, error));
|
||||
dispatch(expandTimelineFail(timelineId, error));
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export const expandHomeTimeline = () => expandTimeline('home', '/api/v1/timelines/home');
|
||||
export const expandPublicTimeline = () => expandTimeline('public', '/api/v1/timelines/public');
|
||||
export const expandCommunityTimeline = () => expandTimeline('community', '/api/v1/timelines/public', { local: true });
|
||||
export const expandAccountTimeline = accountId => expandTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`);
|
||||
export const expandAccountMediaTimeline = accountId => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true });
|
||||
export const expandHashtagTimeline = hashtag => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
|
||||
|
||||
export function expandTimelineRequest(timeline) {
|
||||
return {
|
||||
type: TIMELINE_EXPAND_REQUEST,
|
||||
|
@@ -27,22 +27,15 @@ class Account extends ImmutablePureComponent {
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
constructor (props, context) {
|
||||
super(props, context);
|
||||
this.handleFollow = this.handleFollow.bind(this);
|
||||
this.handleBlock = this.handleBlock.bind(this);
|
||||
this.handleMute = this.handleMute.bind(this);
|
||||
}
|
||||
|
||||
handleFollow () {
|
||||
handleFollow = () => {
|
||||
this.props.onFollow(this.props.account);
|
||||
}
|
||||
|
||||
handleBlock () {
|
||||
handleBlock = () => {
|
||||
this.props.onBlock(this.props.account);
|
||||
}
|
||||
|
||||
handleMute () {
|
||||
handleMute = () => {
|
||||
this.props.onMute(this.props.account);
|
||||
}
|
||||
|
||||
@@ -62,11 +55,11 @@ class Account extends ImmutablePureComponent {
|
||||
const muting = account.getIn(['relationship', 'muting']);
|
||||
|
||||
if (requested) {
|
||||
buttons = <IconButton disabled={true} icon='hourglass' title={intl.formatMessage(messages.requested)} />;
|
||||
buttons = <IconButton disabled icon='hourglass' title={intl.formatMessage(messages.requested)} />;
|
||||
} else if (blocking) {
|
||||
buttons = <IconButton active={true} icon='unlock-alt' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.handleBlock} />;
|
||||
buttons = <IconButton active icon='unlock-alt' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.handleBlock} />;
|
||||
} else if (muting) {
|
||||
buttons = <IconButton active={true} icon='volume-up' title={intl.formatMessage(messages.unmute, { name: account.get('username') })} onClick={this.handleMute} />;
|
||||
buttons = <IconButton active icon='volume-up' title={intl.formatMessage(messages.unmute, { name: account.get('username') })} onClick={this.handleMute} />;
|
||||
} else {
|
||||
buttons = <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following} />;
|
||||
}
|
||||
|
@@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
const filename = url => url.split('/').pop().split('#')[0].split('?')[0];
|
||||
|
||||
class AttachmentList extends React.PureComponent {
|
||||
class AttachmentList extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
media: ImmutablePropTypes.list.isRequired,
|
||||
|
@@ -4,6 +4,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import { isRtl } from '../rtl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import Textarea from 'react-textarea-autosize';
|
||||
|
||||
const textAtCursorMatchesToken = (str, caretPosition) => {
|
||||
let word;
|
||||
@@ -51,23 +52,14 @@ class AutosuggestTextarea extends ImmutablePureComponent {
|
||||
autoFocus: true,
|
||||
};
|
||||
|
||||
constructor (props, context) {
|
||||
super(props, context);
|
||||
this.state = {
|
||||
suggestionsHidden: false,
|
||||
selectedSuggestion: 0,
|
||||
lastToken: null,
|
||||
tokenStart: 0,
|
||||
};
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
this.onBlur = this.onBlur.bind(this);
|
||||
this.onSuggestionClick = this.onSuggestionClick.bind(this);
|
||||
this.setTextarea = this.setTextarea.bind(this);
|
||||
this.onPaste = this.onPaste.bind(this);
|
||||
}
|
||||
state = {
|
||||
suggestionsHidden: false,
|
||||
selectedSuggestion: 0,
|
||||
lastToken: null,
|
||||
tokenStart: 0,
|
||||
};
|
||||
|
||||
onChange (e) {
|
||||
onChange = (e) => {
|
||||
const [ tokenStart, token ] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart);
|
||||
|
||||
if (token !== null && this.state.lastToken !== token) {
|
||||
@@ -78,14 +70,10 @@ class AutosuggestTextarea extends ImmutablePureComponent {
|
||||
this.props.onSuggestionsClearRequested();
|
||||
}
|
||||
|
||||
// auto-resize textarea
|
||||
e.target.style.height = 'auto';
|
||||
e.target.style.height = `${e.target.scrollHeight}px`;
|
||||
|
||||
this.props.onChange(e);
|
||||
}
|
||||
|
||||
onKeyDown (e) {
|
||||
onKeyDown = (e) => {
|
||||
const { suggestions, disabled } = this.props;
|
||||
const { selectedSuggestion, suggestionsHidden } = this.state;
|
||||
|
||||
@@ -135,17 +123,11 @@ class AutosuggestTextarea extends ImmutablePureComponent {
|
||||
this.props.onKeyDown(e);
|
||||
}
|
||||
|
||||
onBlur () {
|
||||
// If we hide the suggestions immediately, then this will prevent the
|
||||
// onClick for the suggestions themselves from firing.
|
||||
// Setting a short window for that to take place before hiding the
|
||||
// suggestions ensures that can't happen.
|
||||
setTimeout(() => {
|
||||
this.setState({ suggestionsHidden: true });
|
||||
}, 100);
|
||||
onBlur = () => {
|
||||
this.setState({ suggestionsHidden: true });
|
||||
}
|
||||
|
||||
onSuggestionClick (e) {
|
||||
onSuggestionClick = (e) => {
|
||||
const suggestion = Number(e.currentTarget.getAttribute('data-index'));
|
||||
e.preventDefault();
|
||||
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion);
|
||||
@@ -158,21 +140,17 @@ class AutosuggestTextarea extends ImmutablePureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
setTextarea (c) {
|
||||
setTextarea = (c) => {
|
||||
this.textarea = c;
|
||||
}
|
||||
|
||||
onPaste (e) {
|
||||
onPaste = (e) => {
|
||||
if (e.clipboardData && e.clipboardData.files.length === 1) {
|
||||
this.props.onPaste(e.clipboardData.files);
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
reset () {
|
||||
this.textarea.style.height = 'auto';
|
||||
}
|
||||
|
||||
render () {
|
||||
const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus } = this.props;
|
||||
const { suggestionsHidden, selectedSuggestion } = this.state;
|
||||
@@ -184,8 +162,8 @@ class AutosuggestTextarea extends ImmutablePureComponent {
|
||||
|
||||
return (
|
||||
<div className='autosuggest-textarea'>
|
||||
<textarea
|
||||
ref={this.setTextarea}
|
||||
<Textarea
|
||||
inputRef={this.setTextarea}
|
||||
className='autosuggest-textarea__textarea'
|
||||
disabled={disabled}
|
||||
placeholder={placeholder}
|
||||
@@ -207,7 +185,8 @@ class AutosuggestTextarea extends ImmutablePureComponent {
|
||||
key={suggestion}
|
||||
data-index={suggestion}
|
||||
className={`autosuggest-textarea__suggestions__item ${i === selectedSuggestion ? 'selected' : ''}`}
|
||||
onClick={this.onSuggestionClick}>
|
||||
onMouseDown={this.onSuggestionClick}
|
||||
>
|
||||
<AutosuggestAccountContainer id={suggestion} />
|
||||
</div>
|
||||
))}
|
||||
|
@@ -19,7 +19,7 @@ class Avatar extends React.PureComponent {
|
||||
};
|
||||
|
||||
state = {
|
||||
hovering: true,
|
||||
hovering: false,
|
||||
};
|
||||
|
||||
handleMouseEnter = () => {
|
||||
|
@@ -9,7 +9,7 @@ class AvatarOverlay extends React.PureComponent {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {staticSrc, overlaySrc} = this.props;
|
||||
const { staticSrc, overlaySrc } = this.props;
|
||||
|
||||
const baseStyle = {
|
||||
backgroundImage: `url(${staticSrc})`,
|
||||
@@ -21,8 +21,8 @@ class AvatarOverlay extends React.PureComponent {
|
||||
|
||||
return (
|
||||
<div className='account__avatar-overlay'>
|
||||
<div className="account__avatar-overlay-base" style={baseStyle} />
|
||||
<div className="account__avatar-overlay-overlay" style={overlayStyle} />
|
||||
<div className='account__avatar-overlay-base' style={baseStyle} />
|
||||
<div className='account__avatar-overlay-overlay' style={overlayStyle} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
|
||||
class Button extends React.PureComponent {
|
||||
|
||||
@@ -10,6 +11,7 @@ class Button extends React.PureComponent {
|
||||
block: PropTypes.bool,
|
||||
secondary: PropTypes.bool,
|
||||
size: PropTypes.number,
|
||||
className: PropTypes.string,
|
||||
style: PropTypes.object,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
@@ -20,10 +22,18 @@ class Button extends React.PureComponent {
|
||||
|
||||
handleClick = (e) => {
|
||||
if (!this.props.disabled) {
|
||||
this.props.onClick();
|
||||
this.props.onClick(e);
|
||||
}
|
||||
}
|
||||
|
||||
setRef = (c) => {
|
||||
this.node = c;
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.node.focus();
|
||||
}
|
||||
|
||||
render () {
|
||||
const style = {
|
||||
padding: `0 ${this.props.size / 2.25}px`,
|
||||
@@ -32,11 +42,17 @@ class Button extends React.PureComponent {
|
||||
...this.props.style,
|
||||
};
|
||||
|
||||
const className = classNames('button', this.props.className, {
|
||||
'button-secondary': this.props.secondary,
|
||||
'button--block': this.props.block,
|
||||
});
|
||||
|
||||
return (
|
||||
<button
|
||||
className={`button ${this.props.secondary ? 'button-secondary' : ''} ${this.props.block ? 'button--block' : ''}`}
|
||||
className={className}
|
||||
disabled={this.props.disabled}
|
||||
onClick={this.handleClick}
|
||||
ref={this.setRef}
|
||||
style={style}
|
||||
>
|
||||
{this.props.text || this.props.children}
|
||||
|
45
app/javascript/mastodon/components/column.js
Normal file
45
app/javascript/mastodon/components/column.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import scrollTop from '../scroll';
|
||||
|
||||
class Column extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
scrollTop () {
|
||||
const scrollable = this.node.querySelector('.scrollable');
|
||||
|
||||
if (!scrollable) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._interruptScrollAnimation = scrollTop(scrollable);
|
||||
}
|
||||
|
||||
handleWheel = () => {
|
||||
if (typeof this._interruptScrollAnimation !== 'function') {
|
||||
return;
|
||||
}
|
||||
|
||||
this._interruptScrollAnimation();
|
||||
}
|
||||
|
||||
setRef = c => {
|
||||
this.node = c;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { children } = this.props;
|
||||
|
||||
return (
|
||||
<div role='region' className='column' ref={this.setRef} onWheel={this.handleWheel}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Column;
|
@@ -9,14 +9,14 @@ class ColumnBackButton extends React.PureComponent {
|
||||
};
|
||||
|
||||
handleClick = () => {
|
||||
if (window.history && window.history.length === 1) this.context.router.push("/");
|
||||
if (window.history && window.history.length === 1) this.context.router.push('/');
|
||||
else this.context.router.goBack();
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div role='button' tabIndex='0' onClick={this.handleClick} className='column-back-button'>
|
||||
<i className='fa fa-fw fa-chevron-left column-back-button__icon'/>
|
||||
<i className='fa fa-fw fa-chevron-left column-back-button__icon' />
|
||||
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
|
||||
</div>
|
||||
);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user