mirror of
https://github.com/glitch-soc/mastodon.git
synced 2025-12-15 00:38:27 +00:00
Compare commits
236 Commits
fix-null-s
...
no-spin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4dd19054d6 | ||
|
|
b28b405b97 | ||
|
|
dc6e031364 | ||
|
|
9dd5e329ab | ||
|
|
3e90987c8b | ||
|
|
2151fd3150 | ||
|
|
ad207456d6 | ||
|
|
9e3d24a150 | ||
|
|
ee560abdbe | ||
|
|
35fbdc36f9 | ||
|
|
556c07df1f | ||
|
|
88627fd7aa | ||
|
|
7e17e764a5 | ||
|
|
3023725936 | ||
|
|
04508868b0 | ||
|
|
3e4b01b47d | ||
|
|
19e8b861a2 | ||
|
|
7d7df877ef | ||
|
|
c73a1fb537 | ||
|
|
f6bc6399e2 | ||
|
|
031a5a8f92 | ||
|
|
6d7e05ec1f | ||
|
|
58bca7b1e4 | ||
|
|
1c25853842 | ||
|
|
546257bc7f | ||
|
|
fbef909c2a | ||
|
|
c3ec1e87b8 | ||
|
|
48e27c47a7 | ||
|
|
1f1838420f | ||
|
|
20150659e6 | ||
|
|
8087aa83d4 | ||
|
|
249b0fe107 | ||
|
|
a6682a3000 | ||
|
|
4112a0631f | ||
|
|
0e6c4cb796 | ||
|
|
bfd9230d61 | ||
|
|
656d54e945 | ||
|
|
92aaa55f06 | ||
|
|
5df8e30415 | ||
|
|
60f247c2e7 | ||
|
|
cf7e840990 | ||
|
|
252d0fe020 | ||
|
|
2fb722397d | ||
|
|
9a42f7cbed | ||
|
|
07f7192bc3 | ||
|
|
48c705bbad | ||
|
|
fcb9533549 | ||
|
|
5128c4261e | ||
|
|
7bb8b0b2fc | ||
|
|
2b1190065c | ||
|
|
b95c48748c | ||
|
|
56720ba590 | ||
|
|
e5aa4128f6 | ||
|
|
f9e7336296 | ||
|
|
4944515020 | ||
|
|
07cca6e364 | ||
|
|
54b42901df | ||
|
|
d200e041fe | ||
|
|
49a285ce15 | ||
|
|
cfd7b7a0b7 | ||
|
|
36376b5e23 | ||
|
|
eb97bd8af6 | ||
|
|
4c0a85ef9b | ||
|
|
64cc129225 | ||
|
|
97fc2da2e0 | ||
|
|
889ada5ee2 | ||
|
|
3f16caaa50 | ||
|
|
5d5c0f4f43 | ||
|
|
1032f3994f | ||
|
|
cbbeec05be | ||
|
|
e618edf85a | ||
|
|
b6e2e999bd | ||
|
|
782224c991 | ||
|
|
84cfee2488 | ||
|
|
7bea1530f4 | ||
|
|
47b0c61853 | ||
|
|
864c4d869f | ||
|
|
d8cd9000d9 | ||
|
|
e1b7785788 | ||
|
|
bc8532359b | ||
|
|
d307ee79e9 | ||
|
|
cf01326cc1 | ||
|
|
a617060dfc | ||
|
|
e0298d66f8 | ||
|
|
d48779cf7b | ||
|
|
8a588145d5 | ||
|
|
8abe9e9058 | ||
|
|
15c0f6ae56 | ||
|
|
73bf0ea7d1 | ||
|
|
276432790a | ||
|
|
254b74c71f | ||
|
|
da3adc0a73 | ||
|
|
0338c16f9f | ||
|
|
38d072446b | ||
|
|
8ae9bd0eea | ||
|
|
5521e94e24 | ||
|
|
763a2f8511 | ||
|
|
60f962eedc | ||
|
|
47d56438da | ||
|
|
0692991b54 | ||
|
|
6705463ed0 | ||
|
|
a2a4bf4e78 | ||
|
|
b254e6ca5f | ||
|
|
29609fbb6a | ||
|
|
d37a56c07c | ||
|
|
2cea4592a3 | ||
|
|
512feab222 | ||
|
|
5e111ce16d | ||
|
|
4080569c2d | ||
|
|
2cbb8e8cd1 | ||
|
|
3e9236b343 | ||
|
|
89c77fe225 | ||
|
|
e843f62f47 | ||
|
|
ec487166db | ||
|
|
37b267e2ab | ||
|
|
3de22a82bf | ||
|
|
e4080772b5 | ||
|
|
870d71b78b | ||
|
|
781105293c | ||
|
|
0cb329f63a | ||
|
|
0129f5eada | ||
|
|
656f5b6f87 | ||
|
|
22da775a85 | ||
|
|
dd28b94cf0 | ||
|
|
d556be2968 | ||
|
|
4f337c020a | ||
|
|
02f7f3619a | ||
|
|
a2612d0d38 | ||
|
|
31814ddda0 | ||
|
|
42f2045c21 | ||
|
|
5f0268ab84 | ||
|
|
20fee786b1 | ||
|
|
74777599cf | ||
|
|
1ba3725473 | ||
|
|
e40fe4092d | ||
|
|
d9485e6497 | ||
|
|
d5c8ebe205 | ||
|
|
d03b48cea0 | ||
|
|
9226257a1b | ||
|
|
641f90e73a | ||
|
|
f5a3283976 | ||
|
|
516eeeb43d | ||
|
|
664c9aa708 | ||
|
|
119d477c8b | ||
|
|
8410d33b49 | ||
|
|
4f01e6e8d5 | ||
|
|
a76b024228 | ||
|
|
3db80f75a6 | ||
|
|
af8f06413e | ||
|
|
1a60445a5f | ||
|
|
4c84513e04 | ||
|
|
4b68e82a19 | ||
|
|
19826774f0 | ||
|
|
fdb0848e08 | ||
|
|
ad86c86fa8 | ||
|
|
670e6a33f8 | ||
|
|
cd04e3df58 | ||
|
|
4a64181461 | ||
|
|
2e03a10059 | ||
|
|
4fa2f7e82d | ||
|
|
b4b657eb1d | ||
|
|
693c66dfde | ||
|
|
a4851100fd | ||
|
|
9f609bc94e | ||
|
|
603cf02b70 | ||
|
|
4745d6eeca | ||
|
|
9093e2de7a | ||
|
|
d589dd7cd0 | ||
|
|
a7be86e875 | ||
|
|
b15dd05514 | ||
|
|
21bafc6555 | ||
|
|
f5e2469485 | ||
|
|
8392ddbf87 | ||
|
|
9423553e5c | ||
|
|
049381b284 | ||
|
|
90770f6d59 | ||
|
|
c756651278 | ||
|
|
eb907a5bab | ||
|
|
39c9cdf7fe | ||
|
|
86cf4468af | ||
|
|
42e8c8eb0e | ||
|
|
09d81defcd | ||
|
|
3810d98cd8 | ||
|
|
26b2a6a71e | ||
|
|
edf9a5e4fc | ||
|
|
c710069c12 | ||
|
|
990d6dd565 | ||
|
|
402da46ff6 | ||
|
|
e7099d8d9e | ||
|
|
637ea3bb5b | ||
|
|
363d0d3a44 | ||
|
|
6e54719474 | ||
|
|
f3003417c5 | ||
|
|
33ea042dec | ||
|
|
8b22a63ab0 | ||
|
|
05686cc99d | ||
|
|
484208ce12 | ||
|
|
3bc8924940 | ||
|
|
a02de9e012 | ||
|
|
2d395324e1 | ||
|
|
e6c9756fa9 | ||
|
|
5050719fac | ||
|
|
989553c69a | ||
|
|
0e0c6b1b4b | ||
|
|
554c2fd8af | ||
|
|
a2b600428c | ||
|
|
df1a9c5ab5 | ||
|
|
4421f6598f | ||
|
|
7364b26e4b | ||
|
|
313ba202ef | ||
|
|
7c44ad6355 | ||
|
|
37ff061d9b | ||
|
|
3d7de06db4 | ||
|
|
26f08f0791 | ||
|
|
8b9ee5f16b | ||
|
|
4b397adb5b | ||
|
|
8980aa804f | ||
|
|
34118169ac | ||
|
|
4fd7aebd5e | ||
|
|
65154869df | ||
|
|
bc89995f65 | ||
|
|
7e9d93472c | ||
|
|
dbb1fce94d | ||
|
|
7cc71748ce | ||
|
|
aec70b44fc | ||
|
|
6f490b4bfe | ||
|
|
03975dbde4 | ||
|
|
f72936b4e6 | ||
|
|
3c530d95f6 | ||
|
|
1e7b3bf625 | ||
|
|
bf0ee1a25c | ||
|
|
fa0be3f834 | ||
|
|
981e20b03a | ||
|
|
d5b767c374 | ||
|
|
93b54b8d4b | ||
|
|
e7ab9bf8b4 |
30
.env.nanobox
30
.env.nanobox
@@ -35,6 +35,17 @@ PAPERCLIP_SECRET=$PAPERCLIP_SECRET
|
|||||||
SECRET_KEY_BASE=$SECRET_KEY_BASE
|
SECRET_KEY_BASE=$SECRET_KEY_BASE
|
||||||
OTP_SECRET=$OTP_SECRET
|
OTP_SECRET=$OTP_SECRET
|
||||||
|
|
||||||
|
# VAPID keys (used for push notifications)
|
||||||
|
# You can generate the keys using the following command (first is the private key, second is the public one)
|
||||||
|
# You should only generate this once per instance. If you later decide to change it, all push subscription will
|
||||||
|
# be invalidated, requiring the users to access the website again to resubscribe.
|
||||||
|
#
|
||||||
|
# Generate with `rake mastodon:webpush:generate_vapid_key` task (`nanobox run bundle exec rake mastodon:webpush:generate_vapid_key`)
|
||||||
|
#
|
||||||
|
# For more information visit https://rossta.net/blog/using-the-web-push-api-with-vapid.html
|
||||||
|
VAPID_PRIVATE_KEY=$VAPID_PRIVATE_KEY
|
||||||
|
VAPID_PUBLIC_KEY=$VAPID_PUBLIC_KEY
|
||||||
|
|
||||||
# Registrations
|
# Registrations
|
||||||
# Single user mode will disable registrations and redirect frontpage to the first profile
|
# Single user mode will disable registrations and redirect frontpage to the first profile
|
||||||
# SINGLE_USER_MODE=true
|
# SINGLE_USER_MODE=true
|
||||||
@@ -62,7 +73,7 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
|
|||||||
#SMTP_CA_FILE=/etc/ssl/certs/ca-certificates.crt
|
#SMTP_CA_FILE=/etc/ssl/certs/ca-certificates.crt
|
||||||
#SMTP_OPENSSL_VERIFY_MODE=peer
|
#SMTP_OPENSSL_VERIFY_MODE=peer
|
||||||
#SMTP_ENABLE_STARTTLS_AUTO=true
|
#SMTP_ENABLE_STARTTLS_AUTO=true
|
||||||
|
#SMTP_TLS=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.
|
# 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_PATH=/var/lib/mastodon/public-system
|
||||||
@@ -91,6 +102,23 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
|
|||||||
# S3_ENDPOINT=
|
# S3_ENDPOINT=
|
||||||
# S3_SIGNATURE_VERSION=
|
# S3_SIGNATURE_VERSION=
|
||||||
|
|
||||||
|
# Swift (optional)
|
||||||
|
# SWIFT_ENABLED=true
|
||||||
|
# SWIFT_USERNAME=
|
||||||
|
# For Keystone V3, the value for SWIFT_TENANT should be the project name
|
||||||
|
# SWIFT_TENANT=
|
||||||
|
# SWIFT_PASSWORD=
|
||||||
|
# Keystone V2 and V3 URLs are supported. Use a V3 URL if possible to avoid
|
||||||
|
# issues with token rate-limiting during high load.
|
||||||
|
# SWIFT_AUTH_URL=
|
||||||
|
# SWIFT_CONTAINER=
|
||||||
|
# SWIFT_OBJECT_URL=
|
||||||
|
# SWIFT_REGION=
|
||||||
|
# Defaults to 'default'
|
||||||
|
# SWIFT_DOMAIN_NAME=
|
||||||
|
# Defaults to 60 seconds. Set to 0 to disable
|
||||||
|
# SWIFT_CACHE_TTL=
|
||||||
|
|
||||||
# Optional alias for S3 if you want to use Cloudfront or Cloudflare in front
|
# Optional alias for S3 if you want to use Cloudfront or Cloudflare in front
|
||||||
# S3_CLOUDFRONT_HOST=
|
# S3_CLOUDFRONT_HOST=
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ env:
|
|||||||
browser: true
|
browser: true
|
||||||
node: true
|
node: true
|
||||||
es6: true
|
es6: true
|
||||||
|
jest: true
|
||||||
|
|
||||||
parser: babel-eslint
|
parser: babel-eslint
|
||||||
|
|
||||||
plugins:
|
plugins:
|
||||||
- react
|
- react
|
||||||
- jsx-a11y
|
- jsx-a11y
|
||||||
|
- import
|
||||||
|
|
||||||
parserOptions:
|
parserOptions:
|
||||||
sourceType: module
|
sourceType: module
|
||||||
@@ -21,8 +23,19 @@ parserOptions:
|
|||||||
modules: true
|
modules: true
|
||||||
spread: true
|
spread: true
|
||||||
|
|
||||||
rules:
|
settings:
|
||||||
|
import/extensions:
|
||||||
|
- .js
|
||||||
|
import/ignore:
|
||||||
|
- node_modules
|
||||||
|
- \\.(css|scss|json)$
|
||||||
|
import/resolver:
|
||||||
|
node:
|
||||||
|
moduleDirectory:
|
||||||
|
- node_modules
|
||||||
|
- app/javascript
|
||||||
|
|
||||||
|
rules:
|
||||||
brace-style: warn
|
brace-style: warn
|
||||||
comma-dangle:
|
comma-dangle:
|
||||||
- error
|
- error
|
||||||
@@ -125,3 +138,17 @@ rules:
|
|||||||
jsx-a11y/role-supports-aria-props: off
|
jsx-a11y/role-supports-aria-props: off
|
||||||
jsx-a11y/scope: warn
|
jsx-a11y/scope: warn
|
||||||
jsx-a11y/tabindex-no-positive: warn
|
jsx-a11y/tabindex-no-positive: warn
|
||||||
|
|
||||||
|
import/extensions:
|
||||||
|
- error
|
||||||
|
- always
|
||||||
|
- js: never
|
||||||
|
import/newline-after-import: error
|
||||||
|
import/no-extraneous-dependencies:
|
||||||
|
- error
|
||||||
|
- devDependencies:
|
||||||
|
- "config/webpack/**"
|
||||||
|
- "app/javascript/mastodon/test_setup.js"
|
||||||
|
- "app/javascript/**/__tests__/**"
|
||||||
|
import/no-unresolved: error
|
||||||
|
import/no-webpack-loader-syntax: error
|
||||||
|
|||||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "app/javascript/themes/mastodon-go"]
|
||||||
|
path = app/javascript/themes/mastodon-go
|
||||||
|
url = https://github.com/marrus-sh/mastodon-go
|
||||||
@@ -53,5 +53,5 @@ before_script:
|
|||||||
|
|
||||||
script:
|
script:
|
||||||
- travis_retry bundle exec parallel_test spec/ --group-by filesize --type rspec
|
- travis_retry bundle exec parallel_test spec/ --group-by filesize --type rspec
|
||||||
- npm test
|
- yarn test
|
||||||
- bundle exec i18n-tasks unused
|
- bundle exec i18n-tasks check-normalized && bundle exec i18n-tasks unused
|
||||||
|
|||||||
46
CODE_OF_CONDUCT.md
Normal file
46
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment include:
|
||||||
|
|
||||||
|
* Using welcoming and inclusive language
|
||||||
|
* Being respectful of differing viewpoints and experiences
|
||||||
|
* Gracefully accepting constructive criticism
|
||||||
|
* Focusing on what is best for the community
|
||||||
|
* Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||||
|
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at eugen@zeonfederated.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
|
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||||
|
|
||||||
|
[homepage]: http://contributor-covenant.org
|
||||||
|
[version]: http://contributor-covenant.org/version/1/4/
|
||||||
32
Gemfile
32
Gemfile
@@ -14,8 +14,10 @@ gem 'pg', '~> 0.20'
|
|||||||
gem 'pghero', '~> 1.7'
|
gem 'pghero', '~> 1.7'
|
||||||
gem 'dotenv-rails', '~> 2.2'
|
gem 'dotenv-rails', '~> 2.2'
|
||||||
|
|
||||||
gem 'aws-sdk', '~> 2.9'
|
gem 'fog-aws', '~> 1.4', require: false
|
||||||
gem 'fog-openstack', '~> 0.1'
|
gem 'fog-core', '~> 1.45'
|
||||||
|
gem 'fog-local', '~> 0.4', require: false
|
||||||
|
gem 'fog-openstack', '~> 0.1', require: false
|
||||||
gem 'paperclip', '~> 5.1'
|
gem 'paperclip', '~> 5.1'
|
||||||
gem 'paperclip-av-transcoder', '~> 0.6'
|
gem 'paperclip-av-transcoder', '~> 0.6'
|
||||||
|
|
||||||
@@ -38,14 +40,14 @@ gem 'http', '~> 2.2'
|
|||||||
gem 'http_accept_language', '~> 2.1'
|
gem 'http_accept_language', '~> 2.1'
|
||||||
gem 'httplog', '~> 0.99'
|
gem 'httplog', '~> 0.99'
|
||||||
gem 'idn-ruby', require: 'idn'
|
gem 'idn-ruby', require: 'idn'
|
||||||
gem 'kaminari', '~> 1.0'
|
gem 'kaminari', '~> 1.1'
|
||||||
gem 'link_header', '~> 0.0'
|
gem 'link_header', '~> 0.0'
|
||||||
gem 'mime-types', '~> 3.1'
|
gem 'mime-types', '~> 3.1'
|
||||||
gem 'nokogiri', '~> 1.7'
|
gem 'nokogiri', '~> 1.8'
|
||||||
gem 'nsa', '~> 0.2'
|
gem 'nsa', '~> 0.2'
|
||||||
gem 'oj', '~> 3.0'
|
gem 'oj', '~> 3.3'
|
||||||
gem 'ostatus2', '~> 2.0'
|
gem 'ostatus2', '~> 2.0'
|
||||||
gem 'ox', '~> 2.5'
|
gem 'ox', '~> 2.8'
|
||||||
gem 'pundit', '~> 1.1'
|
gem 'pundit', '~> 1.1'
|
||||||
gem 'rabl', '~> 0.13'
|
gem 'rabl', '~> 0.13'
|
||||||
gem 'rack-attack', '~> 5.0'
|
gem 'rack-attack', '~> 5.0'
|
||||||
@@ -75,15 +77,15 @@ gem 'json-ld-preloaded', '~> 2.2.1'
|
|||||||
gem 'rdf-normalize', '~> 0.3.1'
|
gem 'rdf-normalize', '~> 0.3.1'
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
gem 'fabrication', '~> 2.16'
|
gem 'fabrication', '~> 2.18'
|
||||||
gem 'fuubar', '~> 2.2'
|
gem 'fuubar', '~> 2.2'
|
||||||
gem 'i18n-tasks', '~> 0.9', require: false
|
gem 'i18n-tasks', '~> 0.9', require: false
|
||||||
gem 'pry-rails', '~> 0.3'
|
gem 'pry-rails', '~> 0.3'
|
||||||
gem 'rspec-rails', '~> 3.6'
|
gem 'rspec-rails', '~> 3.7'
|
||||||
end
|
end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
gem 'capybara', '~> 2.14'
|
gem 'capybara', '~> 2.15'
|
||||||
gem 'climate_control', '~> 0.2'
|
gem 'climate_control', '~> 0.2'
|
||||||
gem 'faker', '~> 1.7'
|
gem 'faker', '~> 1.7'
|
||||||
gem 'microformats', '~> 4.0'
|
gem 'microformats', '~> 4.0'
|
||||||
@@ -91,13 +93,13 @@ group :test do
|
|||||||
gem 'rspec-sidekiq', '~> 3.0'
|
gem 'rspec-sidekiq', '~> 3.0'
|
||||||
gem 'simplecov', '~> 0.14', require: false
|
gem 'simplecov', '~> 0.14', require: false
|
||||||
gem 'webmock', '~> 3.0'
|
gem 'webmock', '~> 3.0'
|
||||||
gem 'parallel_tests', '~> 2.14'
|
gem 'parallel_tests', '~> 2.17'
|
||||||
end
|
end
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
gem 'active_record_query_trace', '~> 1.5'
|
gem 'active_record_query_trace', '~> 1.5'
|
||||||
gem 'annotate', '~> 2.7'
|
gem 'annotate', '~> 2.7'
|
||||||
gem 'better_errors', '~> 2.1'
|
gem 'better_errors', '~> 2.4'
|
||||||
gem 'binding_of_caller', '~> 0.7'
|
gem 'binding_of_caller', '~> 0.7'
|
||||||
gem 'bullet', '~> 5.5'
|
gem 'bullet', '~> 5.5'
|
||||||
gem 'letter_opener', '~> 1.4'
|
gem 'letter_opener', '~> 1.4'
|
||||||
@@ -105,15 +107,15 @@ group :development do
|
|||||||
gem 'rubocop', require: false
|
gem 'rubocop', require: false
|
||||||
gem 'brakeman', '~> 4.0', require: false
|
gem 'brakeman', '~> 4.0', require: false
|
||||||
gem 'bundler-audit', '~> 0.6', require: false
|
gem 'bundler-audit', '~> 0.6', require: false
|
||||||
gem 'scss_lint', '~> 0.53', require: false
|
gem 'scss_lint', '~> 0.55', require: false
|
||||||
|
|
||||||
gem 'capistrano', '~> 3.8'
|
gem 'capistrano', '~> 3.10'
|
||||||
gem 'capistrano-rails', '~> 1.2'
|
gem 'capistrano-rails', '~> 1.3'
|
||||||
gem 'capistrano-rbenv', '~> 2.1'
|
gem 'capistrano-rbenv', '~> 2.1'
|
||||||
gem 'capistrano-yarn', '~> 2.0'
|
gem 'capistrano-yarn', '~> 2.0'
|
||||||
end
|
end
|
||||||
|
|
||||||
group :production do
|
group :production do
|
||||||
gem 'lograge', '~> 0.5'
|
gem 'lograge', '~> 0.7'
|
||||||
gem 'redis-rails', '~> 5.0'
|
gem 'redis-rails', '~> 5.0'
|
||||||
end
|
end
|
||||||
|
|||||||
197
Gemfile.lock
197
Gemfile.lock
@@ -57,25 +57,17 @@ GEM
|
|||||||
encryptor (~> 3.0.0)
|
encryptor (~> 3.0.0)
|
||||||
av (0.9.0)
|
av (0.9.0)
|
||||||
cocaine (~> 0.5.3)
|
cocaine (~> 0.5.3)
|
||||||
aws-sdk (2.10.46)
|
|
||||||
aws-sdk-resources (= 2.10.46)
|
|
||||||
aws-sdk-core (2.10.46)
|
|
||||||
aws-sigv4 (~> 1.0)
|
|
||||||
jmespath (~> 1.0)
|
|
||||||
aws-sdk-resources (2.10.46)
|
|
||||||
aws-sdk-core (= 2.10.46)
|
|
||||||
aws-sigv4 (1.0.2)
|
|
||||||
bcrypt (3.1.11)
|
bcrypt (3.1.11)
|
||||||
better_errors (2.3.0)
|
better_errors (2.4.0)
|
||||||
coderay (>= 1.0.0)
|
coderay (>= 1.0.0)
|
||||||
erubi (>= 1.0.0)
|
erubi (>= 1.0.0)
|
||||||
rack (>= 0.9.0)
|
rack (>= 0.9.0)
|
||||||
binding_of_caller (0.7.2)
|
binding_of_caller (0.7.3)
|
||||||
debug_inspector (>= 0.0.1)
|
debug_inspector (>= 0.0.1)
|
||||||
bootsnap (1.1.3)
|
bootsnap (1.1.5)
|
||||||
msgpack (~> 1.0)
|
msgpack (~> 1.0)
|
||||||
brakeman (4.0.1)
|
brakeman (4.0.1)
|
||||||
browser (2.5.1)
|
browser (2.5.2)
|
||||||
builder (3.2.3)
|
builder (3.2.3)
|
||||||
bullet (5.6.1)
|
bullet (5.6.1)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
@@ -83,23 +75,23 @@ GEM
|
|||||||
bundler-audit (0.6.0)
|
bundler-audit (0.6.0)
|
||||||
bundler (~> 1.2)
|
bundler (~> 1.2)
|
||||||
thor (~> 0.18)
|
thor (~> 0.18)
|
||||||
capistrano (3.9.1)
|
capistrano (3.10.0)
|
||||||
airbrussh (>= 1.0.0)
|
airbrussh (>= 1.0.0)
|
||||||
i18n
|
i18n
|
||||||
rake (>= 10.0.0)
|
rake (>= 10.0.0)
|
||||||
sshkit (>= 1.9.0)
|
sshkit (>= 1.9.0)
|
||||||
capistrano-bundler (1.2.0)
|
capistrano-bundler (1.3.0)
|
||||||
capistrano (~> 3.1)
|
capistrano (~> 3.1)
|
||||||
sshkit (~> 1.2)
|
sshkit (~> 1.2)
|
||||||
capistrano-rails (1.3.0)
|
capistrano-rails (1.3.0)
|
||||||
capistrano (~> 3.1)
|
capistrano (~> 3.1)
|
||||||
capistrano-bundler (~> 1.1)
|
capistrano-bundler (~> 1.1)
|
||||||
capistrano-rbenv (2.1.1)
|
capistrano-rbenv (2.1.2)
|
||||||
capistrano (~> 3.1)
|
capistrano (~> 3.1)
|
||||||
sshkit (~> 1.3)
|
sshkit (~> 1.3)
|
||||||
capistrano-yarn (2.0.2)
|
capistrano-yarn (2.0.2)
|
||||||
capistrano (~> 3.0)
|
capistrano (~> 3.0)
|
||||||
capybara (2.15.1)
|
capybara (2.15.4)
|
||||||
addressable
|
addressable
|
||||||
mini_mime (>= 0.1.3)
|
mini_mime (>= 0.1.3)
|
||||||
nokogiri (>= 1.3.3)
|
nokogiri (>= 1.3.3)
|
||||||
@@ -110,7 +102,7 @@ GEM
|
|||||||
activesupport
|
activesupport
|
||||||
charlock_holmes (0.7.5)
|
charlock_holmes (0.7.5)
|
||||||
chunky_png (1.3.8)
|
chunky_png (1.3.8)
|
||||||
cld3 (3.2.0)
|
cld3 (3.2.1)
|
||||||
ffi (>= 1.1.0, < 1.10.0)
|
ffi (>= 1.1.0, < 1.10.0)
|
||||||
climate_control (0.2.0)
|
climate_control (0.2.0)
|
||||||
cocaine (0.5.8)
|
cocaine (0.5.8)
|
||||||
@@ -150,16 +142,21 @@ GEM
|
|||||||
thread
|
thread
|
||||||
thread_safe
|
thread_safe
|
||||||
encryptor (3.0.0)
|
encryptor (3.0.0)
|
||||||
erubi (1.6.1)
|
erubi (1.7.0)
|
||||||
et-orbi (1.0.5)
|
et-orbi (1.0.8)
|
||||||
tzinfo
|
tzinfo
|
||||||
excon (0.59.0)
|
excon (0.59.0)
|
||||||
execjs (2.7.0)
|
execjs (2.7.0)
|
||||||
fabrication (2.16.3)
|
fabrication (2.18.0)
|
||||||
faker (1.8.4)
|
faker (1.8.4)
|
||||||
i18n (~> 0.5)
|
i18n (~> 0.5)
|
||||||
fast_blank (1.0.0)
|
fast_blank (1.0.0)
|
||||||
ffi (1.9.18)
|
ffi (1.9.18)
|
||||||
|
fog-aws (1.4.1)
|
||||||
|
fog-core (~> 1.38)
|
||||||
|
fog-json (~> 1.0)
|
||||||
|
fog-xml (~> 0.1)
|
||||||
|
ipaddress (~> 0.8)
|
||||||
fog-core (1.45.0)
|
fog-core (1.45.0)
|
||||||
builder
|
builder
|
||||||
excon (~> 0.58)
|
excon (~> 0.58)
|
||||||
@@ -167,15 +164,20 @@ GEM
|
|||||||
fog-json (1.0.2)
|
fog-json (1.0.2)
|
||||||
fog-core (~> 1.0)
|
fog-core (~> 1.0)
|
||||||
multi_json (~> 1.10)
|
multi_json (~> 1.10)
|
||||||
fog-openstack (0.1.21)
|
fog-local (0.4.0)
|
||||||
|
fog-core (~> 1.27)
|
||||||
|
fog-openstack (0.1.22)
|
||||||
fog-core (>= 1.40)
|
fog-core (>= 1.40)
|
||||||
fog-json (>= 1.0)
|
fog-json (>= 1.0)
|
||||||
ipaddress (>= 0.8)
|
ipaddress (>= 0.8)
|
||||||
|
fog-xml (0.1.3)
|
||||||
|
fog-core
|
||||||
|
nokogiri (>= 1.5.11, < 2.0.0)
|
||||||
formatador (0.2.5)
|
formatador (0.2.5)
|
||||||
fuubar (2.2.0)
|
fuubar (2.2.0)
|
||||||
rspec-core (~> 3.0)
|
rspec-core (~> 3.0)
|
||||||
ruby-progressbar (~> 1.4)
|
ruby-progressbar (~> 1.4)
|
||||||
globalid (0.4.0)
|
globalid (0.4.1)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
goldfinger (2.0.1)
|
goldfinger (2.0.1)
|
||||||
addressable (~> 2.5)
|
addressable (~> 2.5)
|
||||||
@@ -211,7 +213,8 @@ GEM
|
|||||||
httplog (0.99.7)
|
httplog (0.99.7)
|
||||||
colorize
|
colorize
|
||||||
rack
|
rack
|
||||||
i18n (0.8.6)
|
i18n (0.9.0)
|
||||||
|
concurrent-ruby (~> 1.0)
|
||||||
i18n-tasks (0.9.18)
|
i18n-tasks (0.9.18)
|
||||||
activesupport (>= 4.0.2)
|
activesupport (>= 4.0.2)
|
||||||
ast (>= 2.1.0)
|
ast (>= 2.1.0)
|
||||||
@@ -225,29 +228,28 @@ GEM
|
|||||||
idn-ruby (0.1.0)
|
idn-ruby (0.1.0)
|
||||||
ipaddress (0.8.3)
|
ipaddress (0.8.3)
|
||||||
iso-639 (0.2.8)
|
iso-639 (0.2.8)
|
||||||
jmespath (1.3.1)
|
|
||||||
json (2.1.0)
|
json (2.1.0)
|
||||||
json-ld (2.1.5)
|
json-ld (2.1.7)
|
||||||
multi_json (~> 1.12)
|
multi_json (~> 1.12)
|
||||||
rdf (~> 2.2)
|
rdf (~> 2.2, >= 2.2.8)
|
||||||
json-ld-preloaded (2.2.2)
|
json-ld-preloaded (2.2.2)
|
||||||
json-ld (~> 2.1, >= 2.1.5)
|
json-ld (~> 2.1, >= 2.1.5)
|
||||||
multi_json (~> 1.11)
|
multi_json (~> 1.11)
|
||||||
rdf (~> 2.2)
|
rdf (~> 2.2)
|
||||||
jsonapi-renderer (0.1.3)
|
jsonapi-renderer (0.1.3)
|
||||||
jwt (1.5.6)
|
jwt (1.5.6)
|
||||||
kaminari (1.0.1)
|
kaminari (1.1.1)
|
||||||
activesupport (>= 4.1.0)
|
activesupport (>= 4.1.0)
|
||||||
kaminari-actionview (= 1.0.1)
|
kaminari-actionview (= 1.1.1)
|
||||||
kaminari-activerecord (= 1.0.1)
|
kaminari-activerecord (= 1.1.1)
|
||||||
kaminari-core (= 1.0.1)
|
kaminari-core (= 1.1.1)
|
||||||
kaminari-actionview (1.0.1)
|
kaminari-actionview (1.1.1)
|
||||||
actionview
|
actionview
|
||||||
kaminari-core (= 1.0.1)
|
kaminari-core (= 1.1.1)
|
||||||
kaminari-activerecord (1.0.1)
|
kaminari-activerecord (1.1.1)
|
||||||
activerecord
|
activerecord
|
||||||
kaminari-core (= 1.0.1)
|
kaminari-core (= 1.1.1)
|
||||||
kaminari-core (1.0.1)
|
kaminari-core (1.1.1)
|
||||||
launchy (2.4.3)
|
launchy (2.4.3)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.3)
|
||||||
letter_opener (1.4.1)
|
letter_opener (1.4.1)
|
||||||
@@ -257,18 +259,19 @@ GEM
|
|||||||
letter_opener (~> 1.0)
|
letter_opener (~> 1.0)
|
||||||
railties (>= 3.2)
|
railties (>= 3.2)
|
||||||
link_header (0.0.8)
|
link_header (0.0.8)
|
||||||
lograge (0.6.0)
|
lograge (0.7.1)
|
||||||
actionpack (>= 4, < 5.2)
|
actionpack (>= 4, < 5.2)
|
||||||
activesupport (>= 4, < 5.2)
|
activesupport (>= 4, < 5.2)
|
||||||
railties (>= 4, < 5.2)
|
railties (>= 4, < 5.2)
|
||||||
request_store (~> 1.0)
|
request_store (~> 1.0)
|
||||||
loofah (2.0.3)
|
loofah (2.1.1)
|
||||||
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.5.9)
|
nokogiri (>= 1.5.9)
|
||||||
mail (2.6.6)
|
mail (2.6.6)
|
||||||
mime-types (>= 1.16, < 4)
|
mime-types (>= 1.16, < 4)
|
||||||
mario-redis-lock (1.2.0)
|
mario-redis-lock (1.2.0)
|
||||||
redis (~> 3, >= 3.0.5)
|
redis (~> 3, >= 3.0.5)
|
||||||
method_source (0.8.2)
|
method_source (0.9.0)
|
||||||
microformats (4.0.7)
|
microformats (4.0.7)
|
||||||
json
|
json
|
||||||
nokogiri
|
nokogiri
|
||||||
@@ -277,7 +280,7 @@ GEM
|
|||||||
mime-types-data (3.2016.0521)
|
mime-types-data (3.2016.0521)
|
||||||
mimemagic (0.3.2)
|
mimemagic (0.3.2)
|
||||||
mini_mime (0.1.4)
|
mini_mime (0.1.4)
|
||||||
mini_portile2 (2.2.0)
|
mini_portile2 (2.3.0)
|
||||||
minitest (5.10.3)
|
minitest (5.10.3)
|
||||||
msgpack (1.1.0)
|
msgpack (1.1.0)
|
||||||
multi_json (1.12.2)
|
multi_json (1.12.2)
|
||||||
@@ -285,8 +288,8 @@ GEM
|
|||||||
net-ssh (>= 2.6.5)
|
net-ssh (>= 2.6.5)
|
||||||
net-ssh (4.2.0)
|
net-ssh (4.2.0)
|
||||||
nio4r (2.1.0)
|
nio4r (2.1.0)
|
||||||
nokogiri (1.8.0)
|
nokogiri (1.8.1)
|
||||||
mini_portile2 (~> 2.2.0)
|
mini_portile2 (~> 2.3.0)
|
||||||
nokogumbo (1.4.13)
|
nokogumbo (1.4.13)
|
||||||
nokogiri
|
nokogiri
|
||||||
nsa (0.2.4)
|
nsa (0.2.4)
|
||||||
@@ -294,15 +297,15 @@ GEM
|
|||||||
concurrent-ruby (~> 1.0.0)
|
concurrent-ruby (~> 1.0.0)
|
||||||
sidekiq (>= 3.5.0)
|
sidekiq (>= 3.5.0)
|
||||||
statsd-ruby (~> 1.2.0)
|
statsd-ruby (~> 1.2.0)
|
||||||
oj (3.3.5)
|
oj (3.3.9)
|
||||||
openssl (2.0.5)
|
openssl (2.0.6)
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
ostatus2 (2.0.1)
|
ostatus2 (2.0.1)
|
||||||
addressable (~> 2.4)
|
addressable (~> 2.4)
|
||||||
http (~> 2.0)
|
http (~> 2.0)
|
||||||
nokogiri (~> 1.6)
|
nokogiri (~> 1.6)
|
||||||
openssl (~> 2.0)
|
openssl (~> 2.0)
|
||||||
ox (2.6.0)
|
ox (2.8.1)
|
||||||
paperclip (5.1.0)
|
paperclip (5.1.0)
|
||||||
activemodel (>= 4.2.0)
|
activemodel (>= 4.2.0)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
@@ -313,19 +316,18 @@ GEM
|
|||||||
av (~> 0.9.0)
|
av (~> 0.9.0)
|
||||||
paperclip (>= 2.5.2)
|
paperclip (>= 2.5.2)
|
||||||
parallel (1.12.0)
|
parallel (1.12.0)
|
||||||
parallel_tests (2.15.0)
|
parallel_tests (2.17.0)
|
||||||
parallel
|
parallel
|
||||||
parser (2.4.0.0)
|
parser (2.4.0.0)
|
||||||
ast (~> 2.2)
|
ast (~> 2.2)
|
||||||
pg (0.21.0)
|
pg (0.21.0)
|
||||||
pghero (1.7.0)
|
pghero (1.7.0)
|
||||||
activerecord
|
activerecord
|
||||||
pkg-config (1.2.7)
|
pkg-config (1.2.8)
|
||||||
powerpack (0.1.1)
|
powerpack (0.1.1)
|
||||||
pry (0.10.4)
|
pry (0.11.2)
|
||||||
coderay (~> 1.1.0)
|
coderay (~> 1.1.0)
|
||||||
method_source (~> 0.8.1)
|
method_source (~> 0.9.0)
|
||||||
slop (~> 3.4)
|
|
||||||
pry-rails (0.3.6)
|
pry-rails (0.3.6)
|
||||||
pry (>= 0.10.4)
|
pry (>= 0.10.4)
|
||||||
public_suffix (3.0.0)
|
public_suffix (3.0.0)
|
||||||
@@ -379,31 +381,31 @@ GEM
|
|||||||
thor (>= 0.18.1, < 2.0)
|
thor (>= 0.18.1, < 2.0)
|
||||||
rainbow (2.2.2)
|
rainbow (2.2.2)
|
||||||
rake
|
rake
|
||||||
rake (12.1.0)
|
rake (12.2.1)
|
||||||
rdf (2.2.9)
|
rdf (2.2.11)
|
||||||
hamster (~> 3.0)
|
hamster (~> 3.0)
|
||||||
link_header (~> 0.0, >= 0.0.8)
|
link_header (~> 0.0, >= 0.0.8)
|
||||||
rdf-normalize (0.3.2)
|
rdf-normalize (0.3.2)
|
||||||
rdf (~> 2.0)
|
rdf (~> 2.0)
|
||||||
redis (3.3.3)
|
redis (3.3.5)
|
||||||
redis-actionpack (5.0.1)
|
redis-actionpack (5.0.2)
|
||||||
actionpack (>= 4.0, < 6)
|
actionpack (>= 4.0, < 6)
|
||||||
redis-rack (>= 1, < 3)
|
redis-rack (>= 1, < 3)
|
||||||
redis-store (>= 1.1.0, < 1.4.0)
|
redis-store (>= 1.1.0, < 2)
|
||||||
redis-activesupport (5.0.3)
|
redis-activesupport (5.0.4)
|
||||||
activesupport (>= 3, < 6)
|
activesupport (>= 3, < 6)
|
||||||
redis-store (~> 1.3.0)
|
redis-store (>= 1.3, < 2)
|
||||||
redis-namespace (1.5.3)
|
redis-namespace (1.5.3)
|
||||||
redis (~> 3.0, >= 3.0.4)
|
redis (~> 3.0, >= 3.0.4)
|
||||||
redis-rack (2.0.2)
|
redis-rack (2.0.3)
|
||||||
rack (>= 1.5, < 3)
|
rack (>= 1.5, < 3)
|
||||||
redis-store (>= 1.2, < 1.4)
|
redis-store (>= 1.2, < 2)
|
||||||
redis-rails (5.0.2)
|
redis-rails (5.0.2)
|
||||||
redis-actionpack (>= 5.0, < 6)
|
redis-actionpack (>= 5.0, < 6)
|
||||||
redis-activesupport (>= 5.0, < 6)
|
redis-activesupport (>= 5.0, < 6)
|
||||||
redis-store (>= 1.2, < 2)
|
redis-store (>= 1.2, < 2)
|
||||||
redis-store (1.3.0)
|
redis-store (1.4.1)
|
||||||
redis (>= 2.2)
|
redis (>= 2.2, < 5)
|
||||||
request_store (1.3.2)
|
request_store (1.3.2)
|
||||||
responders (2.4.0)
|
responders (2.4.0)
|
||||||
actionpack (>= 4.2.0, < 5.3)
|
actionpack (>= 4.2.0, < 5.3)
|
||||||
@@ -411,27 +413,27 @@ GEM
|
|||||||
rotp (2.1.2)
|
rotp (2.1.2)
|
||||||
rqrcode (0.10.1)
|
rqrcode (0.10.1)
|
||||||
chunky_png (~> 1.0)
|
chunky_png (~> 1.0)
|
||||||
rspec-core (3.6.0)
|
rspec-core (3.7.0)
|
||||||
rspec-support (~> 3.6.0)
|
rspec-support (~> 3.7.0)
|
||||||
rspec-expectations (3.6.0)
|
rspec-expectations (3.7.0)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.6.0)
|
rspec-support (~> 3.7.0)
|
||||||
rspec-mocks (3.6.0)
|
rspec-mocks (3.7.0)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.6.0)
|
rspec-support (~> 3.7.0)
|
||||||
rspec-rails (3.6.1)
|
rspec-rails (3.7.1)
|
||||||
actionpack (>= 3.0)
|
actionpack (>= 3.0)
|
||||||
activesupport (>= 3.0)
|
activesupport (>= 3.0)
|
||||||
railties (>= 3.0)
|
railties (>= 3.0)
|
||||||
rspec-core (~> 3.6.0)
|
rspec-core (~> 3.7.0)
|
||||||
rspec-expectations (~> 3.6.0)
|
rspec-expectations (~> 3.7.0)
|
||||||
rspec-mocks (~> 3.6.0)
|
rspec-mocks (~> 3.7.0)
|
||||||
rspec-support (~> 3.6.0)
|
rspec-support (~> 3.7.0)
|
||||||
rspec-sidekiq (3.0.3)
|
rspec-sidekiq (3.0.3)
|
||||||
rspec-core (~> 3.0, >= 3.0.0)
|
rspec-core (~> 3.0, >= 3.0.0)
|
||||||
sidekiq (>= 2.4.0)
|
sidekiq (>= 2.4.0)
|
||||||
rspec-support (3.6.0)
|
rspec-support (3.7.0)
|
||||||
rubocop (0.50.0)
|
rubocop (0.51.0)
|
||||||
parallel (~> 1.10)
|
parallel (~> 1.10)
|
||||||
parser (>= 2.3.3.1, < 3.0)
|
parser (>= 2.3.3.1, < 3.0)
|
||||||
powerpack (~> 0.1)
|
powerpack (~> 0.1)
|
||||||
@@ -439,7 +441,7 @@ GEM
|
|||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (~> 1.0, >= 1.0.1)
|
unicode-display_width (~> 1.0, >= 1.0.1)
|
||||||
ruby-oembed (0.12.0)
|
ruby-oembed (0.12.0)
|
||||||
ruby-progressbar (1.8.3)
|
ruby-progressbar (1.9.0)
|
||||||
rufus-scheduler (3.4.2)
|
rufus-scheduler (3.4.2)
|
||||||
et-orbi (~> 1.0)
|
et-orbi (~> 1.0)
|
||||||
safe_yaml (1.0.4)
|
safe_yaml (1.0.4)
|
||||||
@@ -448,19 +450,19 @@ GEM
|
|||||||
nokogiri (>= 1.4.4)
|
nokogiri (>= 1.4.4)
|
||||||
nokogumbo (~> 1.4.1)
|
nokogumbo (~> 1.4.1)
|
||||||
sass (3.4.25)
|
sass (3.4.25)
|
||||||
scss_lint (0.54.0)
|
scss_lint (0.55.0)
|
||||||
rake (>= 0.9, < 13)
|
rake (>= 0.9, < 13)
|
||||||
sass (~> 3.4.20)
|
sass (~> 3.4.20)
|
||||||
sidekiq (5.0.4)
|
sidekiq (5.0.5)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
connection_pool (~> 2.2, >= 2.2.0)
|
connection_pool (~> 2.2, >= 2.2.0)
|
||||||
rack-protection (>= 1.5.0)
|
rack-protection (>= 1.5.0)
|
||||||
redis (~> 3.3, >= 3.3.3)
|
redis (>= 3.3.4, < 5)
|
||||||
sidekiq-bulk (0.1.1)
|
sidekiq-bulk (0.1.1)
|
||||||
activesupport
|
activesupport
|
||||||
sidekiq
|
sidekiq
|
||||||
sidekiq-scheduler (2.1.9)
|
sidekiq-scheduler (2.1.10)
|
||||||
redis (~> 3)
|
redis (>= 3, < 5)
|
||||||
rufus-scheduler (~> 3.2)
|
rufus-scheduler (~> 3.2)
|
||||||
sidekiq (>= 3)
|
sidekiq (>= 3)
|
||||||
tilt (>= 1.4.0)
|
tilt (>= 1.4.0)
|
||||||
@@ -477,7 +479,6 @@ GEM
|
|||||||
json (>= 1.8, < 3)
|
json (>= 1.8, < 3)
|
||||||
simplecov-html (~> 0.10.0)
|
simplecov-html (~> 0.10.0)
|
||||||
simplecov-html (0.10.2)
|
simplecov-html (0.10.2)
|
||||||
slop (3.6.0)
|
|
||||||
sprockets (3.7.1)
|
sprockets (3.7.1)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
rack (> 1, < 3)
|
rack (> 1, < 3)
|
||||||
@@ -500,9 +501,9 @@ GEM
|
|||||||
tilt (2.0.8)
|
tilt (2.0.8)
|
||||||
twitter-text (1.14.7)
|
twitter-text (1.14.7)
|
||||||
unf (~> 0.1.0)
|
unf (~> 0.1.0)
|
||||||
tzinfo (1.2.3)
|
tzinfo (1.2.4)
|
||||||
thread_safe (~> 0.1)
|
thread_safe (~> 0.1)
|
||||||
tzinfo-data (1.2017.2)
|
tzinfo-data (1.2017.3)
|
||||||
tzinfo (>= 1.0.0)
|
tzinfo (>= 1.0.0)
|
||||||
uglifier (3.2.0)
|
uglifier (3.2.0)
|
||||||
execjs (>= 0.3.0, < 3)
|
execjs (>= 0.3.0, < 3)
|
||||||
@@ -517,7 +518,7 @@ GEM
|
|||||||
addressable (>= 2.3.6)
|
addressable (>= 2.3.6)
|
||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
hashdiff
|
hashdiff
|
||||||
webpacker (3.0.1)
|
webpacker (3.0.2)
|
||||||
activesupport (>= 4.2)
|
activesupport (>= 4.2)
|
||||||
rack-proxy (>= 0.6.1)
|
rack-proxy (>= 0.6.1)
|
||||||
railties (>= 4.2)
|
railties (>= 4.2)
|
||||||
@@ -538,19 +539,18 @@ DEPENDENCIES
|
|||||||
active_record_query_trace (~> 1.5)
|
active_record_query_trace (~> 1.5)
|
||||||
addressable (~> 2.5)
|
addressable (~> 2.5)
|
||||||
annotate (~> 2.7)
|
annotate (~> 2.7)
|
||||||
aws-sdk (~> 2.9)
|
better_errors (~> 2.4)
|
||||||
better_errors (~> 2.1)
|
|
||||||
binding_of_caller (~> 0.7)
|
binding_of_caller (~> 0.7)
|
||||||
bootsnap
|
bootsnap
|
||||||
brakeman (~> 4.0)
|
brakeman (~> 4.0)
|
||||||
browser
|
browser
|
||||||
bullet (~> 5.5)
|
bullet (~> 5.5)
|
||||||
bundler-audit (~> 0.6)
|
bundler-audit (~> 0.6)
|
||||||
capistrano (~> 3.8)
|
capistrano (~> 3.10)
|
||||||
capistrano-rails (~> 1.2)
|
capistrano-rails (~> 1.3)
|
||||||
capistrano-rbenv (~> 2.1)
|
capistrano-rbenv (~> 2.1)
|
||||||
capistrano-yarn (~> 2.0)
|
capistrano-yarn (~> 2.0)
|
||||||
capybara (~> 2.14)
|
capybara (~> 2.15)
|
||||||
charlock_holmes (~> 0.7.5)
|
charlock_holmes (~> 0.7.5)
|
||||||
cld3 (~> 3.2.0)
|
cld3 (~> 3.2.0)
|
||||||
climate_control (~> 0.2)
|
climate_control (~> 0.2)
|
||||||
@@ -558,9 +558,12 @@ DEPENDENCIES
|
|||||||
devise-two-factor (~> 3.0)
|
devise-two-factor (~> 3.0)
|
||||||
doorkeeper (~> 4.2)
|
doorkeeper (~> 4.2)
|
||||||
dotenv-rails (~> 2.2)
|
dotenv-rails (~> 2.2)
|
||||||
fabrication (~> 2.16)
|
fabrication (~> 2.18)
|
||||||
faker (~> 1.7)
|
faker (~> 1.7)
|
||||||
fast_blank (~> 1.0)
|
fast_blank (~> 1.0)
|
||||||
|
fog-aws (~> 1.4)
|
||||||
|
fog-core (~> 1.45)
|
||||||
|
fog-local (~> 0.4)
|
||||||
fog-openstack (~> 0.1)
|
fog-openstack (~> 0.1)
|
||||||
fuubar (~> 2.2)
|
fuubar (~> 2.2)
|
||||||
goldfinger (~> 2.0)
|
goldfinger (~> 2.0)
|
||||||
@@ -574,22 +577,22 @@ DEPENDENCIES
|
|||||||
idn-ruby
|
idn-ruby
|
||||||
iso-639
|
iso-639
|
||||||
json-ld-preloaded (~> 2.2.1)
|
json-ld-preloaded (~> 2.2.1)
|
||||||
kaminari (~> 1.0)
|
kaminari (~> 1.1)
|
||||||
letter_opener (~> 1.4)
|
letter_opener (~> 1.4)
|
||||||
letter_opener_web (~> 1.3)
|
letter_opener_web (~> 1.3)
|
||||||
link_header (~> 0.0)
|
link_header (~> 0.0)
|
||||||
lograge (~> 0.5)
|
lograge (~> 0.7)
|
||||||
mario-redis-lock (~> 1.2)
|
mario-redis-lock (~> 1.2)
|
||||||
microformats (~> 4.0)
|
microformats (~> 4.0)
|
||||||
mime-types (~> 3.1)
|
mime-types (~> 3.1)
|
||||||
nokogiri (~> 1.7)
|
nokogiri (~> 1.8)
|
||||||
nsa (~> 0.2)
|
nsa (~> 0.2)
|
||||||
oj (~> 3.0)
|
oj (~> 3.3)
|
||||||
ostatus2 (~> 2.0)
|
ostatus2 (~> 2.0)
|
||||||
ox (~> 2.5)
|
ox (~> 2.8)
|
||||||
paperclip (~> 5.1)
|
paperclip (~> 5.1)
|
||||||
paperclip-av-transcoder (~> 0.6)
|
paperclip-av-transcoder (~> 0.6)
|
||||||
parallel_tests (~> 2.14)
|
parallel_tests (~> 2.17)
|
||||||
pg (~> 0.20)
|
pg (~> 0.20)
|
||||||
pghero (~> 1.7)
|
pghero (~> 1.7)
|
||||||
pkg-config (~> 1.2)
|
pkg-config (~> 1.2)
|
||||||
@@ -609,12 +612,12 @@ DEPENDENCIES
|
|||||||
redis-namespace (~> 1.5)
|
redis-namespace (~> 1.5)
|
||||||
redis-rails (~> 5.0)
|
redis-rails (~> 5.0)
|
||||||
rqrcode (~> 0.10)
|
rqrcode (~> 0.10)
|
||||||
rspec-rails (~> 3.6)
|
rspec-rails (~> 3.7)
|
||||||
rspec-sidekiq (~> 3.0)
|
rspec-sidekiq (~> 3.0)
|
||||||
rubocop
|
rubocop
|
||||||
ruby-oembed (~> 0.12)
|
ruby-oembed (~> 0.12)
|
||||||
sanitize (~> 4.4)
|
sanitize (~> 4.4)
|
||||||
scss_lint (~> 0.53)
|
scss_lint (~> 0.55)
|
||||||
sidekiq (~> 5.0)
|
sidekiq (~> 5.0)
|
||||||
sidekiq-bulk (~> 0.1.1)
|
sidekiq-bulk (~> 0.1.1)
|
||||||
sidekiq-scheduler (~> 2.1)
|
sidekiq-scheduler (~> 2.1)
|
||||||
|
|||||||
2
Vagrantfile
vendored
2
Vagrantfile
vendored
@@ -83,7 +83,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
|||||||
|
|
||||||
config.vm.provider :virtualbox do |vb|
|
config.vm.provider :virtualbox do |vb|
|
||||||
vb.name = "mastodon"
|
vb.name = "mastodon"
|
||||||
vb.customize ["modifyvm", :id, "--memory", "2048"]
|
vb.customize ["modifyvm", :id, "--memory", "4096"]
|
||||||
|
|
||||||
# Disable VirtualBox DNS proxy to skip long-delay IPv6 resolutions.
|
# Disable VirtualBox DNS proxy to skip long-delay IPv6 resolutions.
|
||||||
# https://github.com/mitchellh/vagrant/issues/1172
|
# https://github.com/mitchellh/vagrant/issues/1172
|
||||||
|
|||||||
@@ -1,23 +1,28 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Admin::AccountModerationNotesController < Admin::BaseController
|
module Admin
|
||||||
|
class AccountModerationNotesController < BaseController
|
||||||
|
before_action :set_account_moderation_note, only: [:destroy]
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
authorize AccountModerationNote, :create?
|
||||||
|
|
||||||
@account_moderation_note = current_account.account_moderation_notes.new(resource_params)
|
@account_moderation_note = current_account.account_moderation_notes.new(resource_params)
|
||||||
|
|
||||||
if @account_moderation_note.save
|
if @account_moderation_note.save
|
||||||
@target_account = @account_moderation_note.target_account
|
redirect_to admin_account_path(@account_moderation_note.target_account_id), notice: I18n.t('admin.account_moderation_notes.created_msg')
|
||||||
redirect_to admin_account_path(@target_account.id), notice: I18n.t('admin.account_moderation_notes.created_msg')
|
|
||||||
else
|
else
|
||||||
@account = @account_moderation_note.target_account
|
@account = @account_moderation_note.target_account
|
||||||
@moderation_notes = @account.targeted_moderation_notes.latest
|
@moderation_notes = @account.targeted_moderation_notes.latest
|
||||||
|
|
||||||
render template: 'admin/accounts/show'
|
render template: 'admin/accounts/show'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
@account_moderation_note = AccountModerationNote.find(params[:id])
|
authorize @account_moderation_note, :destroy?
|
||||||
@target_account = @account_moderation_note.target_account
|
|
||||||
@account_moderation_note.destroy
|
@account_moderation_note.destroy
|
||||||
redirect_to admin_account_path(@target_account.id), notice: I18n.t('admin.account_moderation_notes.destroyed_msg')
|
redirect_to admin_account_path(@account_moderation_note.target_account_id), notice: I18n.t('admin.account_moderation_notes.destroyed_msg')
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -28,4 +33,9 @@ class Admin::AccountModerationNotesController < Admin::BaseController
|
|||||||
:target_account_id
|
:target_account_id
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_account_moderation_note
|
||||||
|
@account_moderation_note = AccountModerationNote.find(params[:id])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,29 +2,54 @@
|
|||||||
|
|
||||||
module Admin
|
module Admin
|
||||||
class AccountsController < BaseController
|
class AccountsController < BaseController
|
||||||
before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload]
|
before_action :set_account, only: [:show, :subscribe, :unsubscribe, :redownload, :enable, :disable, :memorialize]
|
||||||
before_action :require_remote_account!, only: [:subscribe, :unsubscribe, :redownload]
|
before_action :require_remote_account!, only: [:subscribe, :unsubscribe, :redownload]
|
||||||
|
before_action :require_local_account!, only: [:enable, :disable, :memorialize]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
authorize :account, :index?
|
||||||
@accounts = filtered_accounts.page(params[:page])
|
@accounts = filtered_accounts.page(params[:page])
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
authorize @account, :show?
|
||||||
@account_moderation_note = current_account.account_moderation_notes.new(target_account: @account)
|
@account_moderation_note = current_account.account_moderation_notes.new(target_account: @account)
|
||||||
@moderation_notes = @account.targeted_moderation_notes.latest
|
@moderation_notes = @account.targeted_moderation_notes.latest
|
||||||
end
|
end
|
||||||
|
|
||||||
def subscribe
|
def subscribe
|
||||||
|
authorize @account, :subscribe?
|
||||||
Pubsubhubbub::SubscribeWorker.perform_async(@account.id)
|
Pubsubhubbub::SubscribeWorker.perform_async(@account.id)
|
||||||
redirect_to admin_account_path(@account.id)
|
redirect_to admin_account_path(@account.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def unsubscribe
|
def unsubscribe
|
||||||
|
authorize @account, :unsubscribe?
|
||||||
Pubsubhubbub::UnsubscribeWorker.perform_async(@account.id)
|
Pubsubhubbub::UnsubscribeWorker.perform_async(@account.id)
|
||||||
redirect_to admin_account_path(@account.id)
|
redirect_to admin_account_path(@account.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def memorialize
|
||||||
|
authorize @account, :memorialize?
|
||||||
|
@account.memorialize!
|
||||||
|
redirect_to admin_account_path(@account.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def enable
|
||||||
|
authorize @account.user, :enable?
|
||||||
|
@account.user.enable!
|
||||||
|
redirect_to admin_account_path(@account.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def disable
|
||||||
|
authorize @account.user, :disable?
|
||||||
|
@account.user.disable!
|
||||||
|
redirect_to admin_account_path(@account.id)
|
||||||
|
end
|
||||||
|
|
||||||
def redownload
|
def redownload
|
||||||
|
authorize @account, :redownload?
|
||||||
|
|
||||||
@account.reset_avatar!
|
@account.reset_avatar!
|
||||||
@account.reset_header!
|
@account.reset_header!
|
||||||
@account.save!
|
@account.save!
|
||||||
@@ -42,6 +67,10 @@ module Admin
|
|||||||
redirect_to admin_account_path(@account.id) if @account.local?
|
redirect_to admin_account_path(@account.id) if @account.local?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def require_local_account!
|
||||||
|
redirect_to admin_account_path(@account.id) unless @account.local? && @account.user.present?
|
||||||
|
end
|
||||||
|
|
||||||
def filtered_accounts
|
def filtered_accounts
|
||||||
AccountFilter.new(filter_params).results
|
AccountFilter.new(filter_params).results
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
module Admin
|
module Admin
|
||||||
class BaseController < ApplicationController
|
class BaseController < ApplicationController
|
||||||
before_action :require_admin!
|
include Authorization
|
||||||
|
|
||||||
|
before_action :require_staff!
|
||||||
|
|
||||||
layout 'admin'
|
layout 'admin'
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,15 +2,18 @@
|
|||||||
|
|
||||||
module Admin
|
module Admin
|
||||||
class ConfirmationsController < BaseController
|
class ConfirmationsController < BaseController
|
||||||
|
before_action :set_user
|
||||||
|
|
||||||
def create
|
def create
|
||||||
account_user.confirm
|
authorize @user, :confirm?
|
||||||
|
@user.confirm!
|
||||||
redirect_to admin_accounts_path
|
redirect_to admin_accounts_path
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def account_user
|
def set_user
|
||||||
Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
|
@user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,14 +5,18 @@ module Admin
|
|||||||
before_action :set_custom_emoji, except: [:index, :new, :create]
|
before_action :set_custom_emoji, except: [:index, :new, :create]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@custom_emojis = filtered_custom_emojis.page(params[:page])
|
authorize :custom_emoji, :index?
|
||||||
|
@custom_emojis = filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page])
|
||||||
end
|
end
|
||||||
|
|
||||||
def new
|
def new
|
||||||
|
authorize :custom_emoji, :create?
|
||||||
@custom_emoji = CustomEmoji.new
|
@custom_emoji = CustomEmoji.new
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
authorize :custom_emoji, :create?
|
||||||
|
|
||||||
@custom_emoji = CustomEmoji.new(resource_params)
|
@custom_emoji = CustomEmoji.new(resource_params)
|
||||||
|
|
||||||
if @custom_emoji.save
|
if @custom_emoji.save
|
||||||
@@ -22,29 +26,44 @@ module Admin
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
authorize @custom_emoji, :update?
|
||||||
|
|
||||||
|
if @custom_emoji.update(resource_params)
|
||||||
|
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.updated_msg')
|
||||||
|
else
|
||||||
|
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.update_failed_msg')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
|
authorize @custom_emoji, :destroy?
|
||||||
@custom_emoji.destroy
|
@custom_emoji.destroy
|
||||||
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.destroyed_msg')
|
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.destroyed_msg')
|
||||||
end
|
end
|
||||||
|
|
||||||
def copy
|
def copy
|
||||||
emoji = CustomEmoji.new(domain: nil, shortcode: @custom_emoji.shortcode, image: @custom_emoji.image)
|
authorize @custom_emoji, :copy?
|
||||||
|
|
||||||
if emoji.save
|
emoji = CustomEmoji.find_or_create_by(domain: nil, shortcode: @custom_emoji.shortcode)
|
||||||
|
|
||||||
|
if emoji.update(image: @custom_emoji.image)
|
||||||
flash[:notice] = I18n.t('admin.custom_emojis.copied_msg')
|
flash[:notice] = I18n.t('admin.custom_emojis.copied_msg')
|
||||||
else
|
else
|
||||||
flash[:alert] = I18n.t('admin.custom_emojis.copy_failed_msg')
|
flash[:alert] = I18n.t('admin.custom_emojis.copy_failed_msg')
|
||||||
end
|
end
|
||||||
|
|
||||||
redirect_to admin_custom_emojis_path(params[:page])
|
redirect_to admin_custom_emojis_path(page: params[:page])
|
||||||
end
|
end
|
||||||
|
|
||||||
def enable
|
def enable
|
||||||
|
authorize @custom_emoji, :enable?
|
||||||
@custom_emoji.update!(disabled: false)
|
@custom_emoji.update!(disabled: false)
|
||||||
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.enabled_msg')
|
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.enabled_msg')
|
||||||
end
|
end
|
||||||
|
|
||||||
def disable
|
def disable
|
||||||
|
authorize @custom_emoji, :disable?
|
||||||
@custom_emoji.update!(disabled: true)
|
@custom_emoji.update!(disabled: true)
|
||||||
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.disabled_msg')
|
redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.disabled_msg')
|
||||||
end
|
end
|
||||||
@@ -56,7 +75,7 @@ module Admin
|
|||||||
end
|
end
|
||||||
|
|
||||||
def resource_params
|
def resource_params
|
||||||
params.require(:custom_emoji).permit(:shortcode, :image)
|
params.require(:custom_emoji).permit(:shortcode, :image, :visible_in_picker)
|
||||||
end
|
end
|
||||||
|
|
||||||
def filtered_custom_emojis
|
def filtered_custom_emojis
|
||||||
|
|||||||
@@ -5,14 +5,18 @@ module Admin
|
|||||||
before_action :set_domain_block, only: [:show, :destroy]
|
before_action :set_domain_block, only: [:show, :destroy]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
authorize :domain_block, :index?
|
||||||
@domain_blocks = DomainBlock.page(params[:page])
|
@domain_blocks = DomainBlock.page(params[:page])
|
||||||
end
|
end
|
||||||
|
|
||||||
def new
|
def new
|
||||||
|
authorize :domain_block, :create?
|
||||||
@domain_block = DomainBlock.new
|
@domain_block = DomainBlock.new
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
authorize :domain_block, :create?
|
||||||
|
|
||||||
@domain_block = DomainBlock.new(resource_params)
|
@domain_block = DomainBlock.new(resource_params)
|
||||||
|
|
||||||
if @domain_block.save
|
if @domain_block.save
|
||||||
@@ -23,9 +27,12 @@ module Admin
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def show; end
|
def show
|
||||||
|
authorize @domain_block, :show?
|
||||||
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
|
authorize @domain_block, :destroy?
|
||||||
UnblockDomainService.new.call(@domain_block, retroactive_unblock?)
|
UnblockDomainService.new.call(@domain_block, retroactive_unblock?)
|
||||||
redirect_to admin_domain_blocks_path, notice: I18n.t('admin.domain_blocks.destroyed_msg')
|
redirect_to admin_domain_blocks_path, notice: I18n.t('admin.domain_blocks.destroyed_msg')
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,14 +5,18 @@ module Admin
|
|||||||
before_action :set_email_domain_block, only: [:show, :destroy]
|
before_action :set_email_domain_block, only: [:show, :destroy]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
authorize :email_domain_block, :index?
|
||||||
@email_domain_blocks = EmailDomainBlock.page(params[:page])
|
@email_domain_blocks = EmailDomainBlock.page(params[:page])
|
||||||
end
|
end
|
||||||
|
|
||||||
def new
|
def new
|
||||||
|
authorize :email_domain_block, :create?
|
||||||
@email_domain_block = EmailDomainBlock.new
|
@email_domain_block = EmailDomainBlock.new
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
authorize :email_domain_block, :create?
|
||||||
|
|
||||||
@email_domain_block = EmailDomainBlock.new(resource_params)
|
@email_domain_block = EmailDomainBlock.new(resource_params)
|
||||||
|
|
||||||
if @email_domain_block.save
|
if @email_domain_block.save
|
||||||
@@ -23,6 +27,7 @@ module Admin
|
|||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
|
authorize @email_domain_block, :destroy?
|
||||||
@email_domain_block.destroy
|
@email_domain_block.destroy
|
||||||
redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.destroyed_msg')
|
redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.destroyed_msg')
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,10 +3,12 @@
|
|||||||
module Admin
|
module Admin
|
||||||
class InstancesController < BaseController
|
class InstancesController < BaseController
|
||||||
def index
|
def index
|
||||||
|
authorize :instance, :index?
|
||||||
@instances = ordered_instances
|
@instances = ordered_instances
|
||||||
end
|
end
|
||||||
|
|
||||||
def resubscribe
|
def resubscribe
|
||||||
|
authorize :instance, :resubscribe?
|
||||||
params.require(:by_domain)
|
params.require(:by_domain)
|
||||||
Pubsubhubbub::SubscribeWorker.push_bulk(subscribeable_accounts.pluck(:id))
|
Pubsubhubbub::SubscribeWorker.push_bulk(subscribeable_accounts.pluck(:id))
|
||||||
redirect_to admin_instances_path
|
redirect_to admin_instances_path
|
||||||
|
|||||||
@@ -2,19 +2,20 @@
|
|||||||
|
|
||||||
module Admin
|
module Admin
|
||||||
class ReportedStatusesController < BaseController
|
class ReportedStatusesController < BaseController
|
||||||
include Authorization
|
|
||||||
|
|
||||||
before_action :set_report
|
before_action :set_report
|
||||||
before_action :set_status, only: [:update, :destroy]
|
before_action :set_status, only: [:update, :destroy]
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
authorize :status, :update?
|
||||||
|
|
||||||
@form = Form::StatusBatch.new(form_status_batch_params)
|
@form = Form::StatusBatch.new(form_status_batch_params)
|
||||||
flash[:alert] = t('admin.statuses.failed_to_execute') unless @form.save
|
flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save
|
||||||
|
|
||||||
redirect_to admin_report_path(@report)
|
redirect_to admin_report_path(@report)
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
authorize @status, :update?
|
||||||
@status.update(status_params)
|
@status.update(status_params)
|
||||||
redirect_to admin_report_path(@report)
|
redirect_to admin_report_path(@report)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,14 +5,17 @@ module Admin
|
|||||||
before_action :set_report, except: [:index]
|
before_action :set_report, except: [:index]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
authorize :report, :index?
|
||||||
@reports = filtered_reports.page(params[:page])
|
@reports = filtered_reports.page(params[:page])
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
authorize @report, :show?
|
||||||
@form = Form::StatusBatch.new
|
@form = Form::StatusBatch.new
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
authorize @report, :update?
|
||||||
process_report
|
process_report
|
||||||
redirect_to admin_report_path(@report)
|
redirect_to admin_report_path(@report)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,17 +2,18 @@
|
|||||||
|
|
||||||
module Admin
|
module Admin
|
||||||
class ResetsController < BaseController
|
class ResetsController < BaseController
|
||||||
before_action :set_account
|
before_action :set_user
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@account.user.send_reset_password_instructions
|
authorize @user, :reset_password?
|
||||||
|
@user.send_reset_password_instructions
|
||||||
redirect_to admin_accounts_path
|
redirect_to admin_accounts_path
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_account
|
def set_user
|
||||||
@account = Account.find(params[:account_id])
|
@user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
25
app/controllers/admin/roles_controller.rb
Normal file
25
app/controllers/admin/roles_controller.rb
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Admin
|
||||||
|
class RolesController < BaseController
|
||||||
|
before_action :set_user
|
||||||
|
|
||||||
|
def promote
|
||||||
|
authorize @user, :promote?
|
||||||
|
@user.promote!
|
||||||
|
redirect_to admin_account_path(@user.account_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def demote
|
||||||
|
authorize @user, :demote?
|
||||||
|
@user.demote!
|
||||||
|
redirect_to admin_account_path(@user.account_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_user
|
||||||
|
@user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -28,10 +28,13 @@ module Admin
|
|||||||
).freeze
|
).freeze
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
|
authorize :settings, :show?
|
||||||
@admin_settings = Form::AdminSettings.new
|
@admin_settings = Form::AdminSettings.new
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
authorize :settings, :update?
|
||||||
|
|
||||||
settings_params.each do |key, value|
|
settings_params.each do |key, value|
|
||||||
if UPLOAD_SETTINGS.include?(key)
|
if UPLOAD_SETTINGS.include?(key)
|
||||||
upload = SiteUpload.where(var: key).first_or_initialize(var: key)
|
upload = SiteUpload.where(var: key).first_or_initialize(var: key)
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ module Admin
|
|||||||
before_action :set_account
|
before_action :set_account
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
authorize @account, :silence?
|
||||||
@account.update(silenced: true)
|
@account.update(silenced: true)
|
||||||
redirect_to admin_accounts_path
|
redirect_to admin_accounts_path
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
|
authorize @account, :unsilence?
|
||||||
@account.update(silenced: false)
|
@account.update(silenced: false)
|
||||||
redirect_to admin_accounts_path
|
redirect_to admin_accounts_path
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
module Admin
|
module Admin
|
||||||
class StatusesController < BaseController
|
class StatusesController < BaseController
|
||||||
include Authorization
|
|
||||||
|
|
||||||
helper_method :current_params
|
helper_method :current_params
|
||||||
|
|
||||||
before_action :set_account
|
before_action :set_account
|
||||||
@@ -12,24 +10,30 @@ module Admin
|
|||||||
PER_PAGE = 20
|
PER_PAGE = 20
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
authorize :status, :index?
|
||||||
|
|
||||||
@statuses = @account.statuses
|
@statuses = @account.statuses
|
||||||
|
|
||||||
if params[:media]
|
if params[:media]
|
||||||
account_media_status_ids = @account.media_attachments.attached.reorder(nil).select(:status_id).distinct
|
account_media_status_ids = @account.media_attachments.attached.reorder(nil).select(:status_id).distinct
|
||||||
@statuses.merge!(Status.where(id: account_media_status_ids))
|
@statuses.merge!(Status.where(id: account_media_status_ids))
|
||||||
end
|
end
|
||||||
@statuses = @statuses.preload(:media_attachments, :mentions).page(params[:page]).per(PER_PAGE)
|
|
||||||
|
|
||||||
|
@statuses = @statuses.preload(:media_attachments, :mentions).page(params[:page]).per(PER_PAGE)
|
||||||
@form = Form::StatusBatch.new
|
@form = Form::StatusBatch.new
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
authorize :status, :update?
|
||||||
|
|
||||||
@form = Form::StatusBatch.new(form_status_batch_params)
|
@form = Form::StatusBatch.new(form_status_batch_params)
|
||||||
flash[:alert] = t('admin.statuses.failed_to_execute') unless @form.save
|
flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save
|
||||||
|
|
||||||
redirect_to admin_account_statuses_path(@account.id, current_params)
|
redirect_to admin_account_statuses_path(@account.id, current_params)
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
authorize @status, :update?
|
||||||
@status.update(status_params)
|
@status.update(status_params)
|
||||||
redirect_to admin_account_statuses_path(@account.id, current_params)
|
redirect_to admin_account_statuses_path(@account.id, current_params)
|
||||||
end
|
end
|
||||||
@@ -60,6 +64,7 @@ module Admin
|
|||||||
|
|
||||||
def current_params
|
def current_params
|
||||||
page = (params[:page] || 1).to_i
|
page = (params[:page] || 1).to_i
|
||||||
|
|
||||||
{
|
{
|
||||||
media: params[:media],
|
media: params[:media],
|
||||||
page: page > 1 && page,
|
page: page > 1 && page,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
module Admin
|
module Admin
|
||||||
class SubscriptionsController < BaseController
|
class SubscriptionsController < BaseController
|
||||||
def index
|
def index
|
||||||
|
authorize :subscription, :index?
|
||||||
@subscriptions = ordered_subscriptions.page(requested_page)
|
@subscriptions = ordered_subscriptions.page(requested_page)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ module Admin
|
|||||||
before_action :set_account
|
before_action :set_account
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
authorize @account, :suspend?
|
||||||
Admin::SuspensionWorker.perform_async(@account.id)
|
Admin::SuspensionWorker.perform_async(@account.id)
|
||||||
redirect_to admin_accounts_path
|
redirect_to admin_accounts_path
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
@account.update(suspended: false)
|
authorize @account, :unsuspend?
|
||||||
|
@account.unsuspend!
|
||||||
redirect_to admin_accounts_path
|
redirect_to admin_accounts_path
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ module Admin
|
|||||||
before_action :set_user
|
before_action :set_user
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
|
authorize @user, :disable_2fa?
|
||||||
@user.disable_two_factor!
|
@user.disable_two_factor!
|
||||||
redirect_to admin_accounts_path
|
redirect_to admin_accounts_path
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -13,9 +13,11 @@ class Api::V1::AccountsController < Api::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def follow
|
def follow
|
||||||
FollowService.new.call(current_user.account, @account.acct)
|
reblogs_arg = { reblogs: params[:reblogs] }
|
||||||
|
|
||||||
options = @account.locked? ? {} : { following_map: { @account.id => true }, requested_map: { @account.id => false } }
|
FollowService.new.call(current_user.account, @account.acct, reblogs_arg)
|
||||||
|
|
||||||
|
options = @account.locked? ? {} : { following_map: { @account.id => reblogs_arg }, requested_map: { @account.id => false } }
|
||||||
|
|
||||||
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(options)
|
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships(options)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class Api::V1::ReportsController < Api::BaseController
|
|||||||
comment: report_params[:comment]
|
comment: report_params[:comment]
|
||||||
)
|
)
|
||||||
|
|
||||||
User.admins.includes(:account).each { |u| AdminMailer.new_report(u.account, @report).deliver_later }
|
User.staff.includes(:account).each { |u| AdminMailer.new_report(u.account, @report).deliver_later }
|
||||||
|
|
||||||
render json: @report, serializer: REST::ReportSerializer
|
render json: @report, serializer: REST::ReportSerializer
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::SearchController < Api::BaseController
|
class Api::V1::SearchController < Api::BaseController
|
||||||
|
include Authorization
|
||||||
|
|
||||||
RESULTS_LIMIT = 10
|
RESULTS_LIMIT = 10
|
||||||
|
|
||||||
before_action -> { doorkeeper_authorize! :read }
|
before_action -> { doorkeeper_authorize! :read }
|
||||||
@@ -9,12 +11,24 @@ class Api::V1::SearchController < Api::BaseController
|
|||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@search = Search.new(search_results)
|
@search = Search.new(search)
|
||||||
render json: @search, serializer: REST::SearchSerializer
|
render json: @search, serializer: REST::SearchSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def search
|
||||||
|
search_results.tap do |search|
|
||||||
|
search[:statuses].keep_if do |status|
|
||||||
|
begin
|
||||||
|
authorize status, :show?
|
||||||
|
rescue Mastodon::NotPermittedError
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def search_results
|
def search_results
|
||||||
SearchService.new.call(
|
SearchService.new.call(
|
||||||
params[:q],
|
params[:q],
|
||||||
|
|||||||
60
app/controllers/api/v1/timelines/direct_controller.rb
Normal file
60
app/controllers/api/v1/timelines/direct_controller.rb
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Timelines::DirectController < 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 json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def load_statuses
|
||||||
|
cached_direct_statuses
|
||||||
|
end
|
||||||
|
|
||||||
|
def cached_direct_statuses
|
||||||
|
cache_collection direct_statuses, Status
|
||||||
|
end
|
||||||
|
|
||||||
|
def direct_statuses
|
||||||
|
direct_timeline_statuses.paginate_by_max_id(
|
||||||
|
limit_param(DEFAULT_STATUSES_LIMIT),
|
||||||
|
params[:max_id],
|
||||||
|
params[:since_id]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def direct_timeline_statuses
|
||||||
|
Status.as_direct_timeline(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_direct_url pagination_params(max_id: pagination_max_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def prev_path
|
||||||
|
api_v1_timelines_direct_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
|
||||||
@@ -18,6 +18,7 @@ class ApplicationController < ActionController::Base
|
|||||||
rescue_from ActionController::RoutingError, with: :not_found
|
rescue_from ActionController::RoutingError, with: :not_found
|
||||||
rescue_from ActiveRecord::RecordNotFound, with: :not_found
|
rescue_from ActiveRecord::RecordNotFound, with: :not_found
|
||||||
rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity
|
rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity
|
||||||
|
rescue_from Mastodon::NotPermittedError, with: :forbidden
|
||||||
|
|
||||||
before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?
|
before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?
|
||||||
before_action :check_suspension, if: :user_signed_in?
|
before_action :check_suspension, if: :user_signed_in?
|
||||||
@@ -40,6 +41,10 @@ class ApplicationController < ActionController::Base
|
|||||||
redirect_to root_path unless current_user&.admin?
|
redirect_to root_path unless current_user&.admin?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def require_staff!
|
||||||
|
redirect_to root_path unless current_user&.staff?
|
||||||
|
end
|
||||||
|
|
||||||
def check_suspension
|
def check_suspension
|
||||||
forbidden if current_user.account.suspended?
|
forbidden if current_user.account.suspended?
|
||||||
end
|
end
|
||||||
@@ -99,7 +104,7 @@ class ApplicationController < ActionController::Base
|
|||||||
unless uncached_ids.empty?
|
unless uncached_ids.empty?
|
||||||
uncached = klass.where(id: uncached_ids).with_includes.map { |item| [item.id, item] }.to_h
|
uncached = klass.where(id: uncached_ids).with_includes.map { |item| [item.id, item] }.to_h
|
||||||
|
|
||||||
uncached.values.each do |item|
|
uncached.each_value do |item|
|
||||||
Rails.cache.write(item.cache_key, item)
|
Rails.cache.write(item.cache_key, item)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class Auth::SessionsController < Devise::SessionsController
|
|||||||
|
|
||||||
if user_params[:otp_attempt].present? && session[:otp_user_id]
|
if user_params[:otp_attempt].present? && session[:otp_user_id]
|
||||||
authenticate_with_two_factor_via_otp(user)
|
authenticate_with_two_factor_via_otp(user)
|
||||||
elsif user && user.valid_password?(user_params[:password])
|
elsif user&.valid_password?(user_params[:password])
|
||||||
prompt_for_two_factor(user)
|
prompt_for_two_factor(user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
module Authorization
|
module Authorization
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
include Pundit
|
include Pundit
|
||||||
|
|
||||||
def pundit_user
|
def pundit_user
|
||||||
|
|||||||
64
app/controllers/settings/keyword_mutes_controller.rb
Normal file
64
app/controllers/settings/keyword_mutes_controller.rb
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Settings::KeywordMutesController < ApplicationController
|
||||||
|
layout 'admin'
|
||||||
|
|
||||||
|
before_action :authenticate_user!
|
||||||
|
before_action :load_keyword_mute, only: [:edit, :update, :destroy]
|
||||||
|
|
||||||
|
def index
|
||||||
|
@keyword_mutes = paginated_keyword_mutes_for_account
|
||||||
|
end
|
||||||
|
|
||||||
|
def new
|
||||||
|
@keyword_mute = keyword_mutes_for_account.build
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
@keyword_mute = keyword_mutes_for_account.create(keyword_mute_params)
|
||||||
|
|
||||||
|
if @keyword_mute.persisted?
|
||||||
|
redirect_to settings_keyword_mutes_path, notice: I18n.t('generic.changes_saved_msg')
|
||||||
|
else
|
||||||
|
render :new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
if @keyword_mute.update(keyword_mute_params)
|
||||||
|
redirect_to settings_keyword_mutes_path, notice: I18n.t('generic.changes_saved_msg')
|
||||||
|
else
|
||||||
|
render :edit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@keyword_mute.destroy!
|
||||||
|
|
||||||
|
redirect_to settings_keyword_mutes_path, notice: I18n.t('generic.changes_saved_msg')
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy_all
|
||||||
|
keyword_mutes_for_account.delete_all
|
||||||
|
|
||||||
|
redirect_to settings_keyword_mutes_path, notice: I18n.t('generic.changes_saved_msg')
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def keyword_mutes_for_account
|
||||||
|
Glitch::KeywordMute.where(account: current_account)
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_keyword_mute
|
||||||
|
@keyword_mute = keyword_mutes_for_account.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def keyword_mute_params
|
||||||
|
params.require(:keyword_mute).permit(:keyword, :whole_word)
|
||||||
|
end
|
||||||
|
|
||||||
|
def paginated_keyword_mutes_for_account
|
||||||
|
keyword_mutes_for_account.order(:keyword).page params[:page]
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -26,7 +26,7 @@ class Settings::NotificationsController < ApplicationController
|
|||||||
def user_settings_params
|
def user_settings_params
|
||||||
params.require(:user).permit(
|
params.require(:user).permit(
|
||||||
notification_emails: %i(follow follow_request reblog favourite mention digest),
|
notification_emails: %i(follow follow_request reblog favourite mention digest),
|
||||||
interactions: %i(must_be_follower must_be_following)
|
interactions: %i(must_be_follower must_be_following must_be_following_dm)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ class Settings::PreferencesController < ApplicationController
|
|||||||
:setting_boost_modal,
|
:setting_boost_modal,
|
||||||
:setting_delete_modal,
|
:setting_delete_modal,
|
||||||
:setting_auto_play_gif,
|
:setting_auto_play_gif,
|
||||||
|
:setting_reduce_motion,
|
||||||
:setting_system_font_ui,
|
:setting_system_font_ui,
|
||||||
:setting_noindex,
|
:setting_noindex,
|
||||||
:setting_theme,
|
:setting_theme,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ module Admin::FilterHelper
|
|||||||
|
|
||||||
def selected?(more_params)
|
def selected?(more_params)
|
||||||
new_url = filtered_url_for(more_params)
|
new_url = filtered_url_for(more_params)
|
||||||
filter_link_class(new_url) == 'selected' ? true : false
|
filter_link_class(new_url) == 'selected'
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -35,6 +35,11 @@ module ApplicationHelper
|
|||||||
Rails.env.production? ? site_title : "#{site_title} (Dev)"
|
Rails.env.production? ? site_title : "#{site_title} (Dev)"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def can?(action, record)
|
||||||
|
return false if record.nil?
|
||||||
|
policy(record).public_send("#{action}?")
|
||||||
|
end
|
||||||
|
|
||||||
def fa_icon(icon, attributes = {})
|
def fa_icon(icon, attributes = {})
|
||||||
class_names = attributes[:class]&.split(' ') || []
|
class_names = attributes[:class]&.split(' ') || []
|
||||||
class_names << 'fa'
|
class_names << 'fa'
|
||||||
@@ -43,6 +48,10 @@ module ApplicationHelper
|
|||||||
content_tag(:i, nil, attributes.merge(class: class_names.join(' ')))
|
content_tag(:i, nil, attributes.merge(class: class_names.join(' ')))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def custom_emoji_tag(custom_emoji)
|
||||||
|
image_tag(custom_emoji.image.url, class: 'emojione', alt: ":#{custom_emoji.shortcode}:")
|
||||||
|
end
|
||||||
|
|
||||||
def opengraph(property, content)
|
def opengraph(property, content)
|
||||||
tag(:meta, content: content, property: property)
|
tag(:meta, content: content, property: property)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ module JsonLdHelper
|
|||||||
value.is_a?(Array) ? value.first : value
|
value.is_a?(Array) ? value.first : value
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def as_array(value)
|
||||||
|
value.is_a?(Array) ? value : [value]
|
||||||
|
end
|
||||||
|
|
||||||
def value_or_id(value)
|
def value_or_id(value)
|
||||||
value.is_a?(String) || value.nil? ? value : value['id']
|
value.is_a?(String) || value.nil? ? value : value['id']
|
||||||
end
|
end
|
||||||
|
|||||||
2
app/helpers/settings/keyword_mutes_helper.rb
Normal file
2
app/helpers/settings/keyword_mutes_helper.rb
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
module Settings::KeywordMutesHelper
|
||||||
|
end
|
||||||
@@ -27,6 +27,7 @@ module SettingsHelper
|
|||||||
pt: 'Português',
|
pt: 'Português',
|
||||||
'pt-BR': 'Português do Brasil',
|
'pt-BR': 'Português do Brasil',
|
||||||
ru: 'Русский',
|
ru: 'Русский',
|
||||||
|
sv: 'Svenska',
|
||||||
th: 'ภาษาไทย',
|
th: 'ภาษาไทย',
|
||||||
tr: 'Türkçe',
|
tr: 'Türkçe',
|
||||||
uk: 'Українська',
|
uk: 'Українська',
|
||||||
|
|||||||
@@ -48,9 +48,10 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
|||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
|
||||||
// Mastodon imports //
|
// Mastodon imports //
|
||||||
import emojify from 'mastodon/features/emoji/emoji';
|
import emojify from '../../../mastodon/features/emoji/emoji';
|
||||||
import IconButton from '../../../mastodon/components/icon_button';
|
import IconButton from '../../../mastodon/components/icon_button';
|
||||||
import Avatar from '../../../mastodon/components/avatar';
|
import Avatar from '../../../mastodon/components/avatar';
|
||||||
|
import { me } from '../../../mastodon/initial_state';
|
||||||
|
|
||||||
// Our imports //
|
// Our imports //
|
||||||
import { processBio } from '../../util/bio_metadata';
|
import { processBio } from '../../util/bio_metadata';
|
||||||
@@ -88,7 +89,6 @@ export default class AccountHeader extends ImmutablePureComponent {
|
|||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
account : ImmutablePropTypes.map,
|
account : ImmutablePropTypes.map,
|
||||||
me : PropTypes.string.isRequired,
|
|
||||||
onFollow : PropTypes.func.isRequired,
|
onFollow : PropTypes.func.isRequired,
|
||||||
intl : PropTypes.object.isRequired,
|
intl : PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
@@ -102,7 +102,7 @@ The `render()` function is used to render our component.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { account, me, intl } = this.props;
|
const { account, intl } = this.props;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
@@ -152,7 +152,7 @@ appropriate icon.
|
|||||||
<IconButton
|
<IconButton
|
||||||
size={26}
|
size={26}
|
||||||
icon={following ? 'user-times' : 'user-plus'}
|
icon={following ? 'user-times' : 'user-plus'}
|
||||||
active={following}
|
active={following ? true : false}
|
||||||
title={intl.formatMessage(following ? messages.unfollow : messages.follow)}
|
title={intl.formatMessage(following ? messages.unfollow : messages.follow)}
|
||||||
onClick={this.props.onFollow}
|
onClick={this.props.onFollow}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -47,11 +47,9 @@ import PropTypes from 'prop-types';
|
|||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { injectIntl, defineMessages } from 'react-intl';
|
import { injectIntl, defineMessages } from 'react-intl';
|
||||||
|
|
||||||
// Mastodon imports //
|
|
||||||
import IconButton from '../../../../mastodon/components/icon_button';
|
|
||||||
|
|
||||||
// Our imports //
|
// Our imports //
|
||||||
import ComposeAdvancedOptionsToggle from './toggle';
|
import ComposeAdvancedOptionsToggle from './toggle';
|
||||||
|
import ComposeDropdown from '../dropdown/index';
|
||||||
|
|
||||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
|
||||||
@@ -77,11 +75,6 @@ const messages = defineMessages({
|
|||||||
{ id: 'advanced_options.icon_title', defaultMessage: 'Advanced options' },
|
{ id: 'advanced_options.icon_title', defaultMessage: 'Advanced options' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const iconStyle = {
|
|
||||||
height : null,
|
|
||||||
lineHeight : '27px',
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
Implementation:
|
Implementation:
|
||||||
@@ -100,67 +93,6 @@ export default class ComposeAdvancedOptions extends React.PureComponent {
|
|||||||
intl : PropTypes.object.isRequired,
|
intl : PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
|
||||||
open: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
### `onToggleDropdown()`
|
|
||||||
|
|
||||||
This function toggles the opening and closing of the advanced options
|
|
||||||
dropdown.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
onToggleDropdown = () => {
|
|
||||||
this.setState({ open: !this.state.open });
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
### `onGlobalClick(e)`
|
|
||||||
|
|
||||||
This function closes the advanced options dropdown if you click
|
|
||||||
anywhere else on the screen.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
onGlobalClick = (e) => {
|
|
||||||
if (e.target !== this.node && !this.node.contains(e.target) && this.state.open) {
|
|
||||||
this.setState({ open: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
### `componentDidMount()`, `componentWillUnmount()`
|
|
||||||
|
|
||||||
This function closes the advanced options dropdown if you click
|
|
||||||
anywhere else on the screen.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
window.addEventListener('click', this.onGlobalClick);
|
|
||||||
window.addEventListener('touchstart', this.onGlobalClick);
|
|
||||||
}
|
|
||||||
componentWillUnmount () {
|
|
||||||
window.removeEventListener('click', this.onGlobalClick);
|
|
||||||
window.removeEventListener('touchstart', this.onGlobalClick);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
### `setRef(c)`
|
|
||||||
|
|
||||||
`setRef()` stores a reference to the dropdown's `<div> in `this.node`.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
setRef = (c) => {
|
|
||||||
this.node = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
@@ -171,7 +103,6 @@ anywhere else on the screen.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { open } = this.state;
|
|
||||||
const { intl, values } = this.props;
|
const { intl, values } = this.props;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -218,23 +149,14 @@ toggle as its `key` so that React can keep track of it.
|
|||||||
Finally, we can render our component.
|
Finally, we can render our component.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={this.setRef} className={`advanced-options-dropdown ${open ? 'open' : ''} ${anyEnabled ? 'active' : ''} `}>
|
<ComposeDropdown
|
||||||
<div className='advanced-options-dropdown__value'>
|
|
||||||
<IconButton
|
|
||||||
className='advanced-options-dropdown__value'
|
|
||||||
title={intl.formatMessage(messages.advanced_options_icon_title)}
|
title={intl.formatMessage(messages.advanced_options_icon_title)}
|
||||||
icon='ellipsis-h' active={open || anyEnabled}
|
icon='home'
|
||||||
size={18}
|
highlight={anyEnabled}
|
||||||
style={iconStyle}
|
>
|
||||||
onClick={this.onToggleDropdown}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='advanced-options-dropdown__dropdown'>
|
|
||||||
{optionElems}
|
{optionElems}
|
||||||
</div>
|
</ComposeDropdown>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
133
app/javascript/glitch/components/compose/attach_options/index.js
Normal file
133
app/javascript/glitch/components/compose/attach_options/index.js
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
// Package imports //
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { injectIntl, defineMessages } from 'react-intl';
|
||||||
|
|
||||||
|
// Our imports //
|
||||||
|
import ComposeDropdown from '../dropdown/index';
|
||||||
|
import { uploadCompose } from '../../../../mastodon/actions/compose';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import { openModal } from '../../../../mastodon/actions/modal';
|
||||||
|
|
||||||
|
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
upload :
|
||||||
|
{ id: 'compose.attach.upload', defaultMessage: 'Upload a file' },
|
||||||
|
doodle :
|
||||||
|
{ id: 'compose.attach.doodle', defaultMessage: 'Draw something' },
|
||||||
|
attach :
|
||||||
|
{ id: 'compose.attach', defaultMessage: 'Attach...' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
// This horrible expression is copied from vanilla upload_button_container
|
||||||
|
disabled: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size > 3 || state.getIn(['compose', 'media_attachments']).some(m => m.get('type') === 'video')),
|
||||||
|
resetFileKey: state.getIn(['compose', 'resetFileKey']),
|
||||||
|
acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']),
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
onSelectFile (files) {
|
||||||
|
dispatch(uploadCompose(files));
|
||||||
|
},
|
||||||
|
onOpenDoodle () {
|
||||||
|
dispatch(openModal('DOODLE', { noEsc: true }));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
@injectIntl
|
||||||
|
@connect(mapStateToProps, mapDispatchToProps)
|
||||||
|
export default class ComposeAttachOptions extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
intl : PropTypes.object.isRequired,
|
||||||
|
resetFileKey: PropTypes.number,
|
||||||
|
acceptContentTypes: ImmutablePropTypes.listOf(PropTypes.string).isRequired,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
onSelectFile: PropTypes.func.isRequired,
|
||||||
|
onOpenDoodle: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
handleItemClick = bt => {
|
||||||
|
if (bt === 'upload') {
|
||||||
|
this.fileElement.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bt === 'doodle') {
|
||||||
|
this.props.onOpenDoodle();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dropdown.setState({ open: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleFileChange = (e) => {
|
||||||
|
if (e.target.files.length > 0) {
|
||||||
|
this.props.onSelectFile(e.target.files);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setFileRef = (c) => {
|
||||||
|
this.fileElement = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDropdownRef = (c) => {
|
||||||
|
this.dropdown = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { intl, resetFileKey, disabled, acceptContentTypes } = this.props;
|
||||||
|
|
||||||
|
const options = [
|
||||||
|
{ icon: 'cloud-upload', text: messages.upload, name: 'upload' },
|
||||||
|
{ icon: 'paint-brush', text: messages.doodle, name: 'doodle' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const optionElems = options.map((item) => {
|
||||||
|
const hdl = () => this.handleItemClick(item.name);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role='button'
|
||||||
|
tabIndex='0'
|
||||||
|
key={item.name}
|
||||||
|
onClick={hdl}
|
||||||
|
className='privacy-dropdown__option'
|
||||||
|
>
|
||||||
|
<div className='privacy-dropdown__option__icon'>
|
||||||
|
<i className={`fa fa-fw fa-${item.icon}`} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='privacy-dropdown__option__content'>
|
||||||
|
<strong>{intl.formatMessage(item.text)}</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ComposeDropdown
|
||||||
|
title={intl.formatMessage(messages.attach)}
|
||||||
|
icon='paperclip'
|
||||||
|
disabled={disabled}
|
||||||
|
ref={this.setDropdownRef}
|
||||||
|
>
|
||||||
|
{optionElems}
|
||||||
|
</ComposeDropdown>
|
||||||
|
<input
|
||||||
|
key={resetFileKey}
|
||||||
|
ref={this.setFileRef}
|
||||||
|
type='file'
|
||||||
|
multiple={false}
|
||||||
|
accept={acceptContentTypes.toArray().join(',')}
|
||||||
|
onChange={this.handleFileChange}
|
||||||
|
disabled={disabled}
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
77
app/javascript/glitch/components/compose/dropdown/index.js
Normal file
77
app/javascript/glitch/components/compose/dropdown/index.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
// Package imports //
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
// Mastodon imports //
|
||||||
|
import IconButton from '../../../../mastodon/components/icon_button';
|
||||||
|
|
||||||
|
const iconStyle = {
|
||||||
|
height : null,
|
||||||
|
lineHeight : '27px',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class ComposeDropdown extends React.PureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
icon: PropTypes.string,
|
||||||
|
highlight: PropTypes.bool,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
children: PropTypes.arrayOf(PropTypes.node).isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
open: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
onGlobalClick = (e) => {
|
||||||
|
if (e.target !== this.node && !this.node.contains(e.target) && this.state.open) {
|
||||||
|
this.setState({ open: false });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
window.addEventListener('click', this.onGlobalClick);
|
||||||
|
window.addEventListener('touchstart', this.onGlobalClick);
|
||||||
|
}
|
||||||
|
componentWillUnmount () {
|
||||||
|
window.removeEventListener('click', this.onGlobalClick);
|
||||||
|
window.removeEventListener('touchstart', this.onGlobalClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
onToggleDropdown = () => {
|
||||||
|
if (this.props.disabled) return;
|
||||||
|
this.setState({ open: !this.state.open });
|
||||||
|
};
|
||||||
|
|
||||||
|
setRef = (c) => {
|
||||||
|
this.node = c;
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { open } = this.state;
|
||||||
|
let { highlight, title, icon, disabled } = this.props;
|
||||||
|
|
||||||
|
if (!icon) icon = 'ellipsis-h';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={this.setRef} className={`advanced-options-dropdown ${open ? 'open' : ''} ${highlight ? 'active' : ''} `}>
|
||||||
|
<div className='advanced-options-dropdown__value'>
|
||||||
|
<IconButton
|
||||||
|
className={'inverted'}
|
||||||
|
title={title}
|
||||||
|
icon={icon} active={open || highlight}
|
||||||
|
size={18}
|
||||||
|
style={iconStyle}
|
||||||
|
disabled={disabled}
|
||||||
|
onClick={this.onToggleDropdown}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='advanced-options-dropdown__dropdown'>
|
||||||
|
{this.props.children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,10 +2,10 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
// Mastodon imports //
|
// Mastodon imports //
|
||||||
import { closeModal } from 'mastodon/actions/modal';
|
import { closeModal } from '../../../mastodon/actions/modal';
|
||||||
|
|
||||||
// Our imports //
|
// Our imports //
|
||||||
import { changeLocalSetting } from 'glitch/actions/local_settings';
|
import { changeLocalSetting } from '../../../glitch/actions/local_settings';
|
||||||
import LocalSettings from '.';
|
import LocalSettings from '.';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import LocalSettingsPage from './page';
|
|||||||
import LocalSettingsNavigation from './navigation';
|
import LocalSettingsNavigation from './navigation';
|
||||||
|
|
||||||
// Stylesheet imports
|
// Stylesheet imports
|
||||||
import './style';
|
import './style.scss';
|
||||||
|
|
||||||
export default class LocalSettings extends React.PureComponent {
|
export default class LocalSettings extends React.PureComponent {
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { injectIntl, defineMessages } from 'react-intl';
|
|||||||
import LocalSettingsNavigationItem from './item';
|
import LocalSettingsNavigationItem from './item';
|
||||||
|
|
||||||
// Stylesheet imports
|
// Stylesheet imports
|
||||||
import './style';
|
import './style.scss';
|
||||||
|
|
||||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
// Stylesheet imports
|
// Stylesheet imports
|
||||||
import './style';
|
import './style.scss';
|
||||||
|
|
||||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import 'styles/variables';
|
@import 'styles/mastodon/variables';
|
||||||
|
|
||||||
.glitch.local-settings__navigation__item {
|
.glitch.local-settings__navigation__item {
|
||||||
display: block;
|
display: block;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import 'styles/variables';
|
@import 'styles/mastodon/variables';
|
||||||
|
|
||||||
.glitch.local-settings__navigation {
|
.glitch.local-settings__navigation {
|
||||||
background: $primary-text-color;
|
background: $primary-text-color;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
|||||||
import LocalSettingsPageItem from './item';
|
import LocalSettingsPageItem from './item';
|
||||||
|
|
||||||
// Stylesheet imports
|
// Stylesheet imports
|
||||||
import './style';
|
import './style.scss';
|
||||||
|
|
||||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
|
||||||
@@ -124,6 +124,16 @@ export default class LocalSettingsPage extends React.PureComponent {
|
|||||||
>
|
>
|
||||||
<FormattedMessage id='settings.auto_collapse_lengthy' defaultMessage='Lengthy toots' />
|
<FormattedMessage id='settings.auto_collapse_lengthy' defaultMessage='Lengthy toots' />
|
||||||
</LocalSettingsPageItem>
|
</LocalSettingsPageItem>
|
||||||
|
<LocalSettingsPageItem
|
||||||
|
settings={settings}
|
||||||
|
item={['collapsed', 'auto', 'reblogs']}
|
||||||
|
id='mastodon-settings--collapsed-auto-reblogs'
|
||||||
|
onChange={onChange}
|
||||||
|
dependsOn={[['collapsed', 'enabled']]}
|
||||||
|
dependsOnNot={[['collapsed', 'auto', 'all']]}
|
||||||
|
>
|
||||||
|
<FormattedMessage id='settings.auto_collapse_reblogs' defaultMessage='Boosts' />
|
||||||
|
</LocalSettingsPageItem>
|
||||||
<LocalSettingsPageItem
|
<LocalSettingsPageItem
|
||||||
settings={settings}
|
settings={settings}
|
||||||
item={['collapsed', 'auto', 'replies']}
|
item={['collapsed', 'auto', 'replies']}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
|
|||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
|
||||||
// Stylesheet imports
|
// Stylesheet imports
|
||||||
import './style';
|
import './style.scss';
|
||||||
|
|
||||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import 'styles/variables';
|
@import 'styles/mastodon/variables';
|
||||||
|
|
||||||
.glitch.local-settings__page__item {
|
.glitch.local-settings__page__item {
|
||||||
select {
|
select {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import 'styles/variables';
|
@import 'styles/mastodon/variables';
|
||||||
|
|
||||||
.glitch.local-settings__page {
|
.glitch.local-settings__page {
|
||||||
display: block;
|
display: block;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import 'styles/variables';
|
@import 'styles/mastodon/variables';
|
||||||
|
|
||||||
.glitch.local-settings {
|
.glitch.local-settings {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
|||||||
import RelativeTimestamp from '../../../mastodon/components/relative_timestamp';
|
import RelativeTimestamp from '../../../mastodon/components/relative_timestamp';
|
||||||
import IconButton from '../../../mastodon/components/icon_button';
|
import IconButton from '../../../mastodon/components/icon_button';
|
||||||
import DropdownMenuContainer from '../../../mastodon/containers/dropdown_menu_container';
|
import DropdownMenuContainer from '../../../mastodon/containers/dropdown_menu_container';
|
||||||
|
import { me } from '../../../mastodon/initial_state';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
||||||
@@ -50,7 +51,6 @@ export default class StatusActionBar extends ImmutablePureComponent {
|
|||||||
onEmbed: PropTypes.func,
|
onEmbed: PropTypes.func,
|
||||||
onMuteConversation: PropTypes.func,
|
onMuteConversation: PropTypes.func,
|
||||||
onPin: PropTypes.func,
|
onPin: PropTypes.func,
|
||||||
me: PropTypes.string,
|
|
||||||
withDismiss: PropTypes.bool,
|
withDismiss: PropTypes.bool,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
@@ -59,7 +59,6 @@ export default class StatusActionBar extends ImmutablePureComponent {
|
|||||||
// evaluate to false. See react-immutable-pure-component for usage.
|
// evaluate to false. See react-immutable-pure-component for usage.
|
||||||
updateOnProps = [
|
updateOnProps = [
|
||||||
'status',
|
'status',
|
||||||
'me',
|
|
||||||
'withDismiss',
|
'withDismiss',
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -119,7 +118,7 @@ export default class StatusActionBar extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { status, me, intl, withDismiss } = this.props;
|
const { status, intl, withDismiss } = this.props;
|
||||||
|
|
||||||
const mutingConversation = status.get('muted');
|
const mutingConversation = status.get('muted');
|
||||||
const anonymousAccess = !me;
|
const anonymousAccess = !me;
|
||||||
|
|||||||
@@ -140,12 +140,10 @@ Here are the props we pass to `<Status>`.
|
|||||||
return {
|
return {
|
||||||
status : status,
|
status : status,
|
||||||
account : account || ownProps.account,
|
account : account || ownProps.account,
|
||||||
me : state.getIn(['meta', 'me']),
|
|
||||||
settings : state.get('local_settings'),
|
settings : state.get('local_settings'),
|
||||||
prepend : prepend || ownProps.prepend,
|
prepend : prepend || ownProps.prepend,
|
||||||
reblogModal : state.getIn(['meta', 'boost_modal']),
|
reblogModal : state.getIn(['meta', 'boost_modal']),
|
||||||
deleteModal : state.getIn(['meta', 'delete_modal']),
|
deleteModal : state.getIn(['meta', 'delete_modal']),
|
||||||
autoPlayGif : state.getIn(['meta', 'auto_play_gif']),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
|||||||
|
|
||||||
// Mastodon imports //
|
// Mastodon imports //
|
||||||
import scheduleIdleTask from '../../../mastodon/features/ui/util/schedule_idle_task';
|
import scheduleIdleTask from '../../../mastodon/features/ui/util/schedule_idle_task';
|
||||||
|
import { autoPlayGif } from '../../../mastodon/initial_state';
|
||||||
|
|
||||||
// Our imports //
|
// Our imports //
|
||||||
import StatusPrepend from './prepend';
|
import StatusPrepend from './prepend';
|
||||||
@@ -89,9 +90,6 @@ few parts:
|
|||||||
These are our local settings, fetched from our store. We need this
|
These are our local settings, fetched from our store. We need this
|
||||||
to determine how best to collapse our statuses, among other things.
|
to determine how best to collapse our statuses, among other things.
|
||||||
|
|
||||||
- __`me` (`PropTypes.number`) :__
|
|
||||||
This is the id of the currently-signed-in user.
|
|
||||||
|
|
||||||
- __`onFavourite`, `onReblog`, `onModalReblog`, `onDelete`,
|
- __`onFavourite`, `onReblog`, `onModalReblog`, `onDelete`,
|
||||||
`onMention`, `onMute`, `onMuteConversation`, onBlock`, `onReport`,
|
`onMention`, `onMute`, `onMuteConversation`, onBlock`, `onReport`,
|
||||||
`onOpenMedia`, `onOpenVideo` (`PropTypes.func`) :__
|
`onOpenMedia`, `onOpenVideo` (`PropTypes.func`) :__
|
||||||
@@ -103,9 +101,6 @@ few parts:
|
|||||||
reblogging and deleting statuses. They are used by the `onReblog`
|
reblogging and deleting statuses. They are used by the `onReblog`
|
||||||
and `onDelete` functions, but we don't deal with them here.
|
and `onDelete` functions, but we don't deal with them here.
|
||||||
|
|
||||||
- __`autoPlayGif` (`PropTypes.bool`) :__
|
|
||||||
This tells the frontend whether or not to autoplay gifs!
|
|
||||||
|
|
||||||
- __`muted` (`PropTypes.bool`) :__
|
- __`muted` (`PropTypes.bool`) :__
|
||||||
This has nothing to do with a user or conversation mute! "Muted" is
|
This has nothing to do with a user or conversation mute! "Muted" is
|
||||||
what Mastodon internally calls the subdued look of statuses in the
|
what Mastodon internally calls the subdued look of statuses in the
|
||||||
@@ -160,7 +155,6 @@ export default class Status extends ImmutablePureComponent {
|
|||||||
account : ImmutablePropTypes.map,
|
account : ImmutablePropTypes.map,
|
||||||
settings : ImmutablePropTypes.map,
|
settings : ImmutablePropTypes.map,
|
||||||
notification : ImmutablePropTypes.map,
|
notification : ImmutablePropTypes.map,
|
||||||
me : PropTypes.string,
|
|
||||||
onFavourite : PropTypes.func,
|
onFavourite : PropTypes.func,
|
||||||
onReblog : PropTypes.func,
|
onReblog : PropTypes.func,
|
||||||
onModalReblog : PropTypes.func,
|
onModalReblog : PropTypes.func,
|
||||||
@@ -177,7 +171,6 @@ export default class Status extends ImmutablePureComponent {
|
|||||||
onOpenVideo : PropTypes.func,
|
onOpenVideo : PropTypes.func,
|
||||||
reblogModal : PropTypes.bool,
|
reblogModal : PropTypes.bool,
|
||||||
deleteModal : PropTypes.bool,
|
deleteModal : PropTypes.bool,
|
||||||
autoPlayGif : PropTypes.bool,
|
|
||||||
muted : PropTypes.bool,
|
muted : PropTypes.bool,
|
||||||
collapse : PropTypes.bool,
|
collapse : PropTypes.bool,
|
||||||
prepend : PropTypes.string,
|
prepend : PropTypes.string,
|
||||||
@@ -211,9 +204,7 @@ to remember to specify it here.
|
|||||||
'account',
|
'account',
|
||||||
'settings',
|
'settings',
|
||||||
'prepend',
|
'prepend',
|
||||||
'me',
|
|
||||||
'boostModal',
|
'boostModal',
|
||||||
'autoPlayGif',
|
|
||||||
'muted',
|
'muted',
|
||||||
'collapse',
|
'collapse',
|
||||||
'notification',
|
'notification',
|
||||||
@@ -287,6 +278,7 @@ properly and our intersection observer is good to go.
|
|||||||
muted,
|
muted,
|
||||||
id,
|
id,
|
||||||
intersectionObserverWrapper,
|
intersectionObserverWrapper,
|
||||||
|
prepend,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const autoCollapseSettings = settings.getIn(['collapsed', 'auto']);
|
const autoCollapseSettings = settings.getIn(['collapsed', 'auto']);
|
||||||
|
|
||||||
@@ -299,6 +291,9 @@ properly and our intersection observer is good to go.
|
|||||||
node.clientHeight > (
|
node.clientHeight > (
|
||||||
status.get('media_attachments').size && !muted ? 650 : 400
|
status.get('media_attachments').size && !muted ? 650 : 400
|
||||||
)
|
)
|
||||||
|
) || (
|
||||||
|
autoCollapseSettings.get('reblogs') &&
|
||||||
|
prepend === 'reblogged_by'
|
||||||
) || (
|
) || (
|
||||||
autoCollapseSettings.get('replies') &&
|
autoCollapseSettings.get('replies') &&
|
||||||
status.get('in_reply_to_id', null) !== null
|
status.get('in_reply_to_id', null) !== null
|
||||||
@@ -556,7 +551,6 @@ this operation are further explained in the code below.
|
|||||||
intersectionObserverWrapper,
|
intersectionObserverWrapper,
|
||||||
onOpenVideo,
|
onOpenVideo,
|
||||||
onOpenMedia,
|
onOpenMedia,
|
||||||
autoPlayGif,
|
|
||||||
notification,
|
notification,
|
||||||
...other
|
...other
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"settings.auto_collapse_lengthy": "Lengthy toots",
|
"settings.auto_collapse_lengthy": "Lengthy toots",
|
||||||
"settings.auto_collapse_media": "Toots with media",
|
"settings.auto_collapse_media": "Toots with media",
|
||||||
"settings.auto_collapse_notifications": "Notifications",
|
"settings.auto_collapse_notifications": "Notifications",
|
||||||
|
"settings.auto_collapse_reblogs": "Boosts",
|
||||||
"settings.auto_collapse_replies": "Replies",
|
"settings.auto_collapse_replies": "Replies",
|
||||||
"settings.close": "Close",
|
"settings.close": "Close",
|
||||||
"settings.collapsed_statuses": "Collapsed toots",
|
"settings.collapsed_statuses": "Collapsed toots",
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ const initialState = ImmutableMap({
|
|||||||
all : false,
|
all : false,
|
||||||
notifications : true,
|
notifications : true,
|
||||||
lengthy : true,
|
lengthy : true,
|
||||||
|
reblogs : false,
|
||||||
replies : false,
|
replies : false,
|
||||||
media : false,
|
media : false,
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -69,6 +69,10 @@ functions are:
|
|||||||
easier to read and to maintain. I leave it to the future readers of
|
easier to read and to maintain. I leave it to the future readers of
|
||||||
this code to determine the extent of my successes in this endeavor.
|
this code to determine the extent of my successes in this endeavor.
|
||||||
|
|
||||||
|
UPDATE 19 Oct 2017: We no longer allow character escapes inside our
|
||||||
|
double-quoted strings for ease of processing. We now internally use
|
||||||
|
the name "ƔAML" in our code to clarify that this is Not Quite YAML.
|
||||||
|
|
||||||
Sending love + warmth eternal,
|
Sending love + warmth eternal,
|
||||||
- kibigo [@kibi@glitch.social]
|
- kibigo [@kibi@glitch.social]
|
||||||
|
|
||||||
@@ -96,10 +100,7 @@ const ALLOWED_CHAR = unirex( // `c-printable` in the YAML 1.2 spec.
|
|||||||
compat_mode ? '[\t\n\r\x20-\x7e\x85\xa0-\ufffd]' : '[\t\n\r\x20-\x7e\x85\xa0-\ud7ff\ue000-\ufffd\u{10000}-\u{10FFFF}]'
|
compat_mode ? '[\t\n\r\x20-\x7e\x85\xa0-\ufffd]' : '[\t\n\r\x20-\x7e\x85\xa0-\ud7ff\ue000-\ufffd\u{10000}-\u{10FFFF}]'
|
||||||
);
|
);
|
||||||
const WHITE_SPACE = /[ \t]/;
|
const WHITE_SPACE = /[ \t]/;
|
||||||
const INDENTATION = / */; // Indentation must be only spaces.
|
|
||||||
const LINE_BREAK = /\r?\n|\r|<br\s*\/?>/;
|
const LINE_BREAK = /\r?\n|\r|<br\s*\/?>/;
|
||||||
const ESCAPE_CHAR = /[0abt\tnvfre "\/\\N_LP]/;
|
|
||||||
const HEXADECIMAL_CHARS = /[0-9a-fA-F]/;
|
|
||||||
const INDICATOR = /[-?:,[\]{}&#*!|>'"%@`]/;
|
const INDICATOR = /[-?:,[\]{}&#*!|>'"%@`]/;
|
||||||
const FLOW_CHAR = /[,[\]{}]/;
|
const FLOW_CHAR = /[,[\]{}]/;
|
||||||
|
|
||||||
@@ -121,7 +122,7 @@ const NEW_LINE = unirex(
|
|||||||
rexstr(ANY_WHITE_SPACE) + rexstr(LINE_BREAK)
|
rexstr(ANY_WHITE_SPACE) + rexstr(LINE_BREAK)
|
||||||
);
|
);
|
||||||
const SOME_NEW_LINES = unirex(
|
const SOME_NEW_LINES = unirex(
|
||||||
'(?:' + rexstr(ANY_WHITE_SPACE) + rexstr(LINE_BREAK) + ')+'
|
'(?:' + rexstr(NEW_LINE) + ')+'
|
||||||
);
|
);
|
||||||
const POSSIBLE_STARTS = unirex(
|
const POSSIBLE_STARTS = unirex(
|
||||||
rexstr(DOCUMENT_START) + rexstr(/<p[^<>]*>/) + '?'
|
rexstr(DOCUMENT_START) + rexstr(/<p[^<>]*>/) + '?'
|
||||||
@@ -131,22 +132,13 @@ const POSSIBLE_ENDS = unirex(
|
|||||||
rexstr(DOCUMENT_END) + '|' +
|
rexstr(DOCUMENT_END) + '|' +
|
||||||
rexstr(/<\/p>/)
|
rexstr(/<\/p>/)
|
||||||
);
|
);
|
||||||
const CHARACTER_ESCAPE = unirex(
|
const QUOTE_CHAR = unirex(
|
||||||
rexstr(/\\/) +
|
'(?=' + rexstr(NOT_LINE_BREAK) + ')[^"]'
|
||||||
'(?:' +
|
|
||||||
rexstr(ESCAPE_CHAR) + '|' +
|
|
||||||
rexstr(/x/) + rexstr(HEXADECIMAL_CHARS) + '{2}' + '|' +
|
|
||||||
rexstr(/u/) + rexstr(HEXADECIMAL_CHARS) + '{4}' + '|' +
|
|
||||||
rexstr(/U/) + rexstr(HEXADECIMAL_CHARS) + '{8}' +
|
|
||||||
')'
|
|
||||||
);
|
);
|
||||||
const ESCAPED_CHAR = unirex(
|
const ANY_QUOTE_CHAR = unirex(
|
||||||
rexstr(/(?!["\\])/) + rexstr(NOT_LINE_BREAK) + '|' +
|
rexstr(QUOTE_CHAR) + '*'
|
||||||
rexstr(CHARACTER_ESCAPE)
|
|
||||||
);
|
|
||||||
const ANY_ESCAPED_CHARS = unirex(
|
|
||||||
rexstr(ESCAPED_CHAR) + '*'
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const ESCAPED_APOS = unirex(
|
const ESCAPED_APOS = unirex(
|
||||||
'(?=' + rexstr(NOT_LINE_BREAK) + ')' + rexstr(/[^']|''/)
|
'(?=' + rexstr(NOT_LINE_BREAK) + ')' + rexstr(/[^']|''/)
|
||||||
);
|
);
|
||||||
@@ -190,72 +182,68 @@ const LATER_VALUE_CHAR = unirex(
|
|||||||
|
|
||||||
/* YAML CONSTRUCTS */
|
/* YAML CONSTRUCTS */
|
||||||
|
|
||||||
const YAML_START = unirex(
|
const ƔAML_START = unirex(
|
||||||
rexstr(ANY_WHITE_SPACE) + rexstr(/---/)
|
rexstr(ANY_WHITE_SPACE) + '---'
|
||||||
);
|
);
|
||||||
const YAML_END = unirex(
|
const ƔAML_END = unirex(
|
||||||
rexstr(ANY_WHITE_SPACE) + rexstr(/(?:---|\.\.\.)/)
|
rexstr(ANY_WHITE_SPACE) + '(?:---|\.\.\.)'
|
||||||
);
|
);
|
||||||
const YAML_LOOKAHEAD = unirex(
|
const ƔAML_LOOKAHEAD = unirex(
|
||||||
'(?=' +
|
'(?=' +
|
||||||
rexstr(YAML_START) +
|
rexstr(ƔAML_START) +
|
||||||
rexstr(ANY_ALLOWED_CHARS) + rexstr(NEW_LINE) +
|
rexstr(ANY_ALLOWED_CHARS) + rexstr(NEW_LINE) +
|
||||||
rexstr(YAML_END) + rexstr(POSSIBLE_ENDS) +
|
rexstr(ƔAML_END) + rexstr(POSSIBLE_ENDS) +
|
||||||
')'
|
')'
|
||||||
);
|
);
|
||||||
const YAML_DOUBLE_QUOTE = unirex(
|
const ƔAML_DOUBLE_QUOTE = unirex(
|
||||||
rexstr(/"/) + rexstr(ANY_ESCAPED_CHARS) + rexstr(/"/)
|
'"' + rexstr(ANY_QUOTE_CHAR) + '"'
|
||||||
);
|
);
|
||||||
const YAML_SINGLE_QUOTE = unirex(
|
const ƔAML_SINGLE_QUOTE = unirex(
|
||||||
rexstr(/'/) + rexstr(ANY_ESCAPED_APOS) + rexstr(/'/)
|
'\'' + rexstr(ANY_ESCAPED_APOS) + '\''
|
||||||
);
|
);
|
||||||
const YAML_SIMPLE_KEY = unirex(
|
const ƔAML_SIMPLE_KEY = unirex(
|
||||||
rexstr(FIRST_KEY_CHAR) + rexstr(LATER_KEY_CHAR) + '*'
|
rexstr(FIRST_KEY_CHAR) + rexstr(LATER_KEY_CHAR) + '*'
|
||||||
);
|
);
|
||||||
const YAML_SIMPLE_VALUE = unirex(
|
const ƔAML_SIMPLE_VALUE = unirex(
|
||||||
rexstr(FIRST_VALUE_CHAR) + rexstr(LATER_VALUE_CHAR) + '*'
|
rexstr(FIRST_VALUE_CHAR) + rexstr(LATER_VALUE_CHAR) + '*'
|
||||||
);
|
);
|
||||||
const YAML_KEY = unirex(
|
const ƔAML_KEY = unirex(
|
||||||
rexstr(YAML_DOUBLE_QUOTE) + '|' +
|
rexstr(ƔAML_DOUBLE_QUOTE) + '|' +
|
||||||
rexstr(YAML_SINGLE_QUOTE) + '|' +
|
rexstr(ƔAML_SINGLE_QUOTE) + '|' +
|
||||||
rexstr(YAML_SIMPLE_KEY)
|
rexstr(ƔAML_SIMPLE_KEY)
|
||||||
);
|
);
|
||||||
const YAML_VALUE = unirex(
|
const ƔAML_VALUE = unirex(
|
||||||
rexstr(YAML_DOUBLE_QUOTE) + '|' +
|
rexstr(ƔAML_DOUBLE_QUOTE) + '|' +
|
||||||
rexstr(YAML_SINGLE_QUOTE) + '|' +
|
rexstr(ƔAML_SINGLE_QUOTE) + '|' +
|
||||||
rexstr(YAML_SIMPLE_VALUE)
|
rexstr(ƔAML_SIMPLE_VALUE)
|
||||||
);
|
);
|
||||||
const YAML_SEPARATOR = unirex(
|
const ƔAML_SEPARATOR = unirex(
|
||||||
rexstr(ANY_WHITE_SPACE) +
|
rexstr(ANY_WHITE_SPACE) +
|
||||||
':' + rexstr(WHITE_SPACE) +
|
':' + rexstr(WHITE_SPACE) +
|
||||||
rexstr(ANY_WHITE_SPACE)
|
rexstr(ANY_WHITE_SPACE)
|
||||||
);
|
);
|
||||||
const YAML_LINE = unirex(
|
const ƔAML_LINE = unirex(
|
||||||
'(' + rexstr(YAML_KEY) + ')' +
|
'(' + rexstr(ƔAML_KEY) + ')' +
|
||||||
rexstr(YAML_SEPARATOR) +
|
rexstr(ƔAML_SEPARATOR) +
|
||||||
'(' + rexstr(YAML_VALUE) + ')'
|
'(' + rexstr(ƔAML_VALUE) + ')'
|
||||||
);
|
);
|
||||||
|
|
||||||
/* FRONTMATTER REGEX */
|
/* FRONTMATTER REGEX */
|
||||||
|
|
||||||
const YAML_FRONTMATTER = unirex(
|
const ƔAML_FRONTMATTER = unirex(
|
||||||
rexstr(POSSIBLE_STARTS) +
|
rexstr(POSSIBLE_STARTS) +
|
||||||
rexstr(YAML_LOOKAHEAD) +
|
rexstr(ƔAML_LOOKAHEAD) +
|
||||||
rexstr(YAML_START) + rexstr(SOME_NEW_LINES) +
|
rexstr(ƔAML_START) + rexstr(SOME_NEW_LINES) +
|
||||||
'(?:' +
|
'(?:' +
|
||||||
'(' + rexstr(INDENTATION) + ')' +
|
rexstr(ANY_WHITE_SPACE) + rexstr(ƔAML_LINE) + rexstr(SOME_NEW_LINES) +
|
||||||
rexstr(YAML_LINE) + rexstr(SOME_NEW_LINES) +
|
'){0,5}' +
|
||||||
'(?:' +
|
rexstr(ƔAML_END) + rexstr(POSSIBLE_ENDS)
|
||||||
'\\1' + rexstr(YAML_LINE) + rexstr(SOME_NEW_LINES) +
|
|
||||||
'){0,4}' +
|
|
||||||
')?' +
|
|
||||||
rexstr(YAML_END) + rexstr(POSSIBLE_ENDS)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
/* SEARCHES */
|
/* SEARCHES */
|
||||||
|
|
||||||
const FIND_YAML_LINES = unirex(
|
const FIND_ƔAML_LINE = unirex(
|
||||||
rexstr(NEW_LINE) + rexstr(INDENTATION) + rexstr(YAML_LINE)
|
rexstr(NEW_LINE) + rexstr(ANY_WHITE_SPACE) + rexstr(ƔAML_LINE)
|
||||||
);
|
);
|
||||||
|
|
||||||
/* STRING PROCESSING */
|
/* STRING PROCESSING */
|
||||||
@@ -263,47 +251,7 @@ const FIND_YAML_LINES = unirex(
|
|||||||
function processString (str) {
|
function processString (str) {
|
||||||
switch (str.charAt(0)) {
|
switch (str.charAt(0)) {
|
||||||
case '"':
|
case '"':
|
||||||
return str
|
return str.substring(1, str.length - 1);
|
||||||
.substring(1, str.length - 1)
|
|
||||||
.replace(/\\0/g, '\x00')
|
|
||||||
.replace(/\\a/g, '\x07')
|
|
||||||
.replace(/\\b/g, '\x08')
|
|
||||||
.replace(/\\t/g, '\x09')
|
|
||||||
.replace(/\\\x09/g, '\x09')
|
|
||||||
.replace(/\\n/g, '\x0a')
|
|
||||||
.replace(/\\v/g, '\x0b')
|
|
||||||
.replace(/\\f/g, '\x0c')
|
|
||||||
.replace(/\\r/g, '\x0d')
|
|
||||||
.replace(/\\e/g, '\x1b')
|
|
||||||
.replace(/\\ /g, '\x20')
|
|
||||||
.replace(/\\"/g, '\x22')
|
|
||||||
.replace(/\\\//g, '\x2f')
|
|
||||||
.replace(/\\\\/g, '\x5c')
|
|
||||||
.replace(/\\N/g, '\x85')
|
|
||||||
.replace(/\\_/g, '\xa0')
|
|
||||||
.replace(/\\L/g, '\u2028')
|
|
||||||
.replace(/\\P/g, '\u2029')
|
|
||||||
.replace(
|
|
||||||
new RegExp(
|
|
||||||
unirex(
|
|
||||||
rexstr(/\\x/) + '(' + rexstr(HEXADECIMAL_CHARS) + '{2})'
|
|
||||||
), 'gu'
|
|
||||||
), (_, n) => String.fromCodePoint('0x' + n)
|
|
||||||
)
|
|
||||||
.replace(
|
|
||||||
new RegExp(
|
|
||||||
unirex(
|
|
||||||
rexstr(/\\u/) + '(' + rexstr(HEXADECIMAL_CHARS) + '{4})'
|
|
||||||
), 'gu'
|
|
||||||
), (_, n) => String.fromCodePoint('0x' + n)
|
|
||||||
)
|
|
||||||
.replace(
|
|
||||||
new RegExp(
|
|
||||||
unirex(
|
|
||||||
rexstr(/\\U/) + '(' + rexstr(HEXADECIMAL_CHARS) + '{8})'
|
|
||||||
), 'gu'
|
|
||||||
), (_, n) => String.fromCodePoint('0x' + n)
|
|
||||||
);
|
|
||||||
case '\'':
|
case '\'':
|
||||||
return str
|
return str
|
||||||
.substring(1, str.length - 1)
|
.substring(1, str.length - 1)
|
||||||
@@ -321,15 +269,18 @@ export function processBio(content) {
|
|||||||
text: content,
|
text: content,
|
||||||
metadata: [],
|
metadata: [],
|
||||||
};
|
};
|
||||||
let yaml = content.match(YAML_FRONTMATTER);
|
let ɣaml = content.match(ƔAML_FRONTMATTER);
|
||||||
if (!yaml) return result;
|
if (!ɣaml) {
|
||||||
else yaml = yaml[0];
|
return result;
|
||||||
let start = content.search(YAML_START);
|
} else {
|
||||||
let end = start + yaml.length - yaml.search(YAML_START);
|
ɣaml = ɣaml[0];
|
||||||
result.text = content.substr(0, start) + content.substr(end);
|
}
|
||||||
|
const start = content.search(ƔAML_START);
|
||||||
|
const end = start + ɣaml.length - ɣaml.search(ƔAML_START);
|
||||||
|
result.text = content.substr(end);
|
||||||
let metadata = null;
|
let metadata = null;
|
||||||
let query = new RegExp(FIND_YAML_LINES, 'g');
|
let query = new RegExp(rexstr(FIND_ƔAML_LINE), 'g'); // Some browsers don't allow flags unless both args are strings
|
||||||
while ((metadata = query.exec(yaml))) {
|
while ((metadata = query.exec(ɣaml))) {
|
||||||
result.metadata.push([
|
result.metadata.push([
|
||||||
processString(metadata[1]),
|
processString(metadata[1]),
|
||||||
processString(metadata[2]),
|
processString(metadata[2]),
|
||||||
@@ -352,63 +303,23 @@ export function createBio(note, data) {
|
|||||||
let val = '' + data[i][1];
|
let val = '' + data[i][1];
|
||||||
|
|
||||||
// Key processing
|
// Key processing
|
||||||
if (key === (key.match(YAML_SIMPLE_KEY) || [])[0]) /* do nothing */;
|
if (key === (key.match(ƔAML_SIMPLE_KEY) || [])[0]) /* do nothing */;
|
||||||
else if (key.indexOf('\'') === -1 && key === (key.match(ANY_ESCAPED_APOS) || [])[0]) key = '\'' + key + '\'';
|
else if (key === (key.match(ANY_QUOTE_CHAR) || [])[0]) key = '"' + key + '"';
|
||||||
else {
|
else {
|
||||||
key = key
|
key = key
|
||||||
.replace(/\x00/g, '\\0')
|
.replace(/'/g, '\'\'')
|
||||||
.replace(/\x07/g, '\\a')
|
.replace(new RegExp(rexstr(NOT_ALLOWED_CHAR), compat_mode ? 'g' : 'gu'), '<EFBFBD>');
|
||||||
.replace(/\x08/g, '\\b')
|
key = '\'' + key + '\'';
|
||||||
.replace(/\x0a/g, '\\n')
|
|
||||||
.replace(/\x0b/g, '\\v')
|
|
||||||
.replace(/\x0c/g, '\\f')
|
|
||||||
.replace(/\x0d/g, '\\r')
|
|
||||||
.replace(/\x1b/g, '\\e')
|
|
||||||
.replace(/\x22/g, '\\"')
|
|
||||||
.replace(/\x5c/g, '\\\\');
|
|
||||||
let badchars = key.match(
|
|
||||||
new RegExp(rexstr(NOT_ALLOWED_CHAR), 'gu')
|
|
||||||
) || [];
|
|
||||||
for (let j = 0; j < badchars.length; j++) {
|
|
||||||
key = key.replace(
|
|
||||||
badchars[i],
|
|
||||||
'\\u' + badchars[i].codePointAt(0).toLocaleString('en', {
|
|
||||||
useGrouping: false,
|
|
||||||
minimumIntegerDigits: 4,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
key = '"' + key + '"';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Value processing
|
// Value processing
|
||||||
if (val === (val.match(YAML_SIMPLE_VALUE) || [])[0]) /* do nothing */;
|
if (val === (val.match(ƔAML_SIMPLE_VALUE) || [])[0]) /* do nothing */;
|
||||||
else if (val.indexOf('\'') === -1 && val === (val.match(ANY_ESCAPED_APOS) || [])[0]) val = '\'' + val + '\'';
|
else if (val === (val.match(ANY_QUOTE_CHAR) || [])[0]) val = '"' + val + '"';
|
||||||
else {
|
else {
|
||||||
val = val
|
key = key
|
||||||
.replace(/\x00/g, '\\0')
|
.replace(/'/g, '\'\'')
|
||||||
.replace(/\x07/g, '\\a')
|
.replace(new RegExp(rexstr(NOT_ALLOWED_CHAR), compat_mode ? 'g' : 'gu'), '<EFBFBD>');
|
||||||
.replace(/\x08/g, '\\b')
|
key = '\'' + key + '\'';
|
||||||
.replace(/\x0a/g, '\\n')
|
|
||||||
.replace(/\x0b/g, '\\v')
|
|
||||||
.replace(/\x0c/g, '\\f')
|
|
||||||
.replace(/\x0d/g, '\\r')
|
|
||||||
.replace(/\x1b/g, '\\e')
|
|
||||||
.replace(/\x22/g, '\\"')
|
|
||||||
.replace(/\x5c/g, '\\\\');
|
|
||||||
let badchars = val.match(
|
|
||||||
new RegExp(rexstr(NOT_ALLOWED_CHAR), 'gu')
|
|
||||||
) || [];
|
|
||||||
for (let j = 0; j < badchars.length; j++) {
|
|
||||||
val = val.replace(
|
|
||||||
badchars[i],
|
|
||||||
'\\u' + badchars[i].codePointAt(0).toLocaleString('en', {
|
|
||||||
useGrouping: false,
|
|
||||||
minimumIntegerDigits: 4,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
val = '"' + val + '"';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
frontmatter += key + ': ' + val + '\n';
|
frontmatter += key + ': ' + val + '\n';
|
||||||
|
|||||||
@@ -105,12 +105,13 @@ export function fetchAccountFail(id, error) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function followAccount(id) {
|
export function followAccount(id, reblogs = true) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
|
const alreadyFollowing = getState().getIn(['relationships', id, 'following']);
|
||||||
dispatch(followAccountRequest(id));
|
dispatch(followAccountRequest(id));
|
||||||
|
|
||||||
api(getState).post(`/api/v1/accounts/${id}/follow`).then(response => {
|
api(getState).post(`/api/v1/accounts/${id}/follow`, { reblogs }).then(response => {
|
||||||
dispatch(followAccountSuccess(response.data));
|
dispatch(followAccountSuccess(response.data, alreadyFollowing));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(followAccountFail(error));
|
dispatch(followAccountFail(error));
|
||||||
});
|
});
|
||||||
@@ -136,10 +137,11 @@ export function followAccountRequest(id) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function followAccountSuccess(relationship) {
|
export function followAccountSuccess(relationship, alreadyFollowing) {
|
||||||
return {
|
return {
|
||||||
type: ACCOUNT_FOLLOW_SUCCESS,
|
type: ACCOUNT_FOLLOW_SUCCESS,
|
||||||
relationship,
|
relationship,
|
||||||
|
alreadyFollowing,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
refreshHomeTimeline,
|
refreshHomeTimeline,
|
||||||
refreshCommunityTimeline,
|
refreshCommunityTimeline,
|
||||||
refreshPublicTimeline,
|
refreshPublicTimeline,
|
||||||
|
refreshDirectTimeline,
|
||||||
} from './timelines';
|
} from './timelines';
|
||||||
|
|
||||||
export const COMPOSE_CHANGE = 'COMPOSE_CHANGE';
|
export const COMPOSE_CHANGE = 'COMPOSE_CHANGE';
|
||||||
@@ -133,6 +134,8 @@ export function submitCompose() {
|
|||||||
if (response.data.in_reply_to_id === null && response.data.visibility === 'public') {
|
if (response.data.in_reply_to_id === null && response.data.visibility === 'public') {
|
||||||
insertOrRefresh('community', refreshCommunityTimeline);
|
insertOrRefresh('community', refreshCommunityTimeline);
|
||||||
insertOrRefresh('public', refreshPublicTimeline);
|
insertOrRefresh('public', refreshPublicTimeline);
|
||||||
|
} else if (response.data.visibility === 'direct') {
|
||||||
|
insertOrRefresh('direct', refreshDirectTimeline);
|
||||||
}
|
}
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
dispatch(submitComposeFail(error));
|
dispatch(submitComposeFail(error));
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ export const PINNED_STATUSES_FETCH_REQUEST = 'PINNED_STATUSES_FETCH_REQUEST';
|
|||||||
export const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS';
|
export const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS';
|
||||||
export const PINNED_STATUSES_FETCH_FAIL = 'PINNED_STATUSES_FETCH_FAIL';
|
export const PINNED_STATUSES_FETCH_FAIL = 'PINNED_STATUSES_FETCH_FAIL';
|
||||||
|
|
||||||
|
import { me } from '../initial_state';
|
||||||
|
|
||||||
export function fetchPinnedStatuses() {
|
export function fetchPinnedStatuses() {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch(fetchPinnedStatusesRequest());
|
dispatch(fetchPinnedStatusesRequest());
|
||||||
|
|
||||||
const accountId = getState().getIn(['meta', 'me']);
|
api(getState).get(`/api/v1/accounts/${me}/statuses`, { params: { pinned: true } }).then(response => {
|
||||||
api(getState).get(`/api/v1/accounts/${accountId}/statuses`, { params: { pinned: true } }).then(response => {
|
|
||||||
dispatch(fetchPinnedStatusesSuccess(response.data, null));
|
dispatch(fetchPinnedStatusesSuccess(response.data, null));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(fetchPinnedStatusesFail(error));
|
dispatch(fetchPinnedStatusesFail(error));
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import createStream from '../stream';
|
import { connectStream } from '../stream';
|
||||||
import {
|
import {
|
||||||
updateTimeline,
|
updateTimeline,
|
||||||
deleteFromTimelines,
|
deleteFromTimelines,
|
||||||
@@ -12,42 +12,19 @@ import { getLocale } from '../locales';
|
|||||||
const { messages } = getLocale();
|
const { messages } = getLocale();
|
||||||
|
|
||||||
export function connectTimelineStream (timelineId, path, pollingRefresh = null) {
|
export function connectTimelineStream (timelineId, path, pollingRefresh = null) {
|
||||||
return (dispatch, getState) => {
|
|
||||||
const streamingAPIBaseURL = getState().getIn(['meta', 'streaming_api_base_url']);
|
return connectStream (path, pollingRefresh, (dispatch, getState) => {
|
||||||
const accessToken = getState().getIn(['meta', 'access_token']);
|
|
||||||
const locale = getState().getIn(['meta', 'locale']);
|
const locale = getState().getIn(['meta', 'locale']);
|
||||||
let polling = null;
|
return {
|
||||||
|
onConnect() {
|
||||||
const setupPolling = () => {
|
|
||||||
polling = setInterval(() => {
|
|
||||||
pollingRefresh(dispatch);
|
|
||||||
}, 20000);
|
|
||||||
};
|
|
||||||
|
|
||||||
const clearPolling = () => {
|
|
||||||
if (polling) {
|
|
||||||
clearInterval(polling);
|
|
||||||
polling = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const subscription = createStream(streamingAPIBaseURL, accessToken, path, {
|
|
||||||
|
|
||||||
connected () {
|
|
||||||
if (pollingRefresh) {
|
|
||||||
clearPolling();
|
|
||||||
}
|
|
||||||
dispatch(connectTimeline(timelineId));
|
dispatch(connectTimeline(timelineId));
|
||||||
},
|
},
|
||||||
|
|
||||||
disconnected () {
|
onDisconnect() {
|
||||||
if (pollingRefresh) {
|
|
||||||
setupPolling();
|
|
||||||
}
|
|
||||||
dispatch(disconnectTimeline(timelineId));
|
dispatch(disconnectTimeline(timelineId));
|
||||||
},
|
},
|
||||||
|
|
||||||
received (data) {
|
onReceive (data) {
|
||||||
switch(data.event) {
|
switch(data.event) {
|
||||||
case 'update':
|
case 'update':
|
||||||
dispatch(updateTimeline(timelineId, JSON.parse(data.payload)));
|
dispatch(updateTimeline(timelineId, JSON.parse(data.payload)));
|
||||||
@@ -60,26 +37,8 @@ export function connectTimelineStream (timelineId, path, pollingRefresh = null)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
};
|
||||||
reconnected () {
|
|
||||||
if (pollingRefresh) {
|
|
||||||
clearPolling();
|
|
||||||
pollingRefresh(dispatch);
|
|
||||||
}
|
|
||||||
dispatch(connectTimeline(timelineId));
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const disconnect = () => {
|
|
||||||
if (subscription) {
|
|
||||||
subscription.close();
|
|
||||||
}
|
|
||||||
clearPolling();
|
|
||||||
};
|
|
||||||
|
|
||||||
return disconnect;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshHomeTimelineAndNotification (dispatch) {
|
function refreshHomeTimelineAndNotification (dispatch) {
|
||||||
@@ -92,3 +51,4 @@ export const connectCommunityStream = () => connectTimelineStream('community', '
|
|||||||
export const connectMediaStream = () => connectTimelineStream('community', 'public:local');
|
export const connectMediaStream = () => connectTimelineStream('community', 'public:local');
|
||||||
export const connectPublicStream = () => connectTimelineStream('public', 'public');
|
export const connectPublicStream = () => connectTimelineStream('public', 'public');
|
||||||
export const connectHashtagStream = (tag) => connectTimelineStream(`hashtag:${tag}`, `hashtag&tag=${tag}`);
|
export const connectHashtagStream = (tag) => connectTimelineStream(`hashtag:${tag}`, `hashtag&tag=${tag}`);
|
||||||
|
export const connectDirectStream = () => connectTimelineStream('direct', 'direct');
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ export function refreshTimeline(timelineId, path, params = {}) {
|
|||||||
export const refreshHomeTimeline = () => refreshTimeline('home', '/api/v1/timelines/home');
|
export const refreshHomeTimeline = () => refreshTimeline('home', '/api/v1/timelines/home');
|
||||||
export const refreshPublicTimeline = () => refreshTimeline('public', '/api/v1/timelines/public');
|
export const refreshPublicTimeline = () => refreshTimeline('public', '/api/v1/timelines/public');
|
||||||
export const refreshCommunityTimeline = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true });
|
export const refreshCommunityTimeline = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true });
|
||||||
|
export const refreshDirectTimeline = () => refreshTimeline('direct', '/api/v1/timelines/direct');
|
||||||
export const refreshAccountTimeline = accountId => refreshTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`);
|
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 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 const refreshHashtagTimeline = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
|
||||||
@@ -155,6 +156,7 @@ export function expandTimeline(timelineId, path, params = {}) {
|
|||||||
export const expandHomeTimeline = () => expandTimeline('home', '/api/v1/timelines/home');
|
export const expandHomeTimeline = () => expandTimeline('home', '/api/v1/timelines/home');
|
||||||
export const expandPublicTimeline = () => expandTimeline('public', '/api/v1/timelines/public');
|
export const expandPublicTimeline = () => expandTimeline('public', '/api/v1/timelines/public');
|
||||||
export const expandCommunityTimeline = () => expandTimeline('community', '/api/v1/timelines/public', { local: true });
|
export const expandCommunityTimeline = () => expandTimeline('community', '/api/v1/timelines/public', { local: true });
|
||||||
|
export const expandDirectTimeline = () => expandTimeline('direct', '/api/v1/timelines/direct');
|
||||||
export const expandAccountTimeline = accountId => expandTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`);
|
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 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 const expandHashtagTimeline = hashtag => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'intl';
|
import 'intl';
|
||||||
import 'intl/locale-data/jsonp/en.js';
|
import 'intl/locale-data/jsonp/en';
|
||||||
import 'es6-symbol/implement';
|
import 'es6-symbol/implement';
|
||||||
import includes from 'array-includes';
|
import includes from 'array-includes';
|
||||||
import assign from 'object-assign';
|
import assign from 'object-assign';
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`<Avatar /> Autoplay renders a animated avatar 1`] = `
|
||||||
|
<div
|
||||||
|
className="account__avatar"
|
||||||
|
data-avatar-of="@alice"
|
||||||
|
onMouseEnter={[Function]}
|
||||||
|
onMouseLeave={[Function]}
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"backgroundImage": "url(/animated/alice.gif)",
|
||||||
|
"backgroundSize": "100px 100px",
|
||||||
|
"height": "100px",
|
||||||
|
"width": "100px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<Avatar /> Still renders a still avatar 1`] = `
|
||||||
|
<div
|
||||||
|
className="account__avatar"
|
||||||
|
data-avatar-of="@alice"
|
||||||
|
onMouseEnter={[Function]}
|
||||||
|
onMouseLeave={[Function]}
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"backgroundImage": "url(/static/alice.jpg)",
|
||||||
|
"backgroundSize": "100px 100px",
|
||||||
|
"height": "100px",
|
||||||
|
"width": "100px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
`;
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`<AvatarOverlay renders a overlay avatar 1`] = `
|
||||||
|
<div
|
||||||
|
className="account__avatar-overlay"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="account__avatar-overlay-base"
|
||||||
|
data-avatar-of="@alice"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"backgroundImage": "url(/static/alice.jpg)",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="account__avatar-overlay-overlay"
|
||||||
|
data-avatar-of="@eve@blackhat.lair"
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"backgroundImage": "url(/static/eve.jpg)",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`<Button /> adds class "button-secondary" if props.secondary given 1`] = `
|
||||||
|
<button
|
||||||
|
className="button button-secondary"
|
||||||
|
disabled={undefined}
|
||||||
|
onClick={[Function]}
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": "36px",
|
||||||
|
"lineHeight": "36px",
|
||||||
|
"padding": "0 16px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<Button /> renders a button element 1`] = `
|
||||||
|
<button
|
||||||
|
className="button"
|
||||||
|
disabled={undefined}
|
||||||
|
onClick={[Function]}
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": "36px",
|
||||||
|
"lineHeight": "36px",
|
||||||
|
"padding": "0 16px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<Button /> renders a disabled attribute if props.disabled given 1`] = `
|
||||||
|
<button
|
||||||
|
className="button"
|
||||||
|
disabled={true}
|
||||||
|
onClick={[Function]}
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": "36px",
|
||||||
|
"lineHeight": "36px",
|
||||||
|
"padding": "0 16px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<Button /> renders class="button--block" if props.block given 1`] = `
|
||||||
|
<button
|
||||||
|
className="button button--block"
|
||||||
|
disabled={undefined}
|
||||||
|
onClick={[Function]}
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": "36px",
|
||||||
|
"lineHeight": "36px",
|
||||||
|
"padding": "0 16px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<Button /> renders the children 1`] = `
|
||||||
|
<button
|
||||||
|
className="button"
|
||||||
|
disabled={undefined}
|
||||||
|
onClick={[Function]}
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": "36px",
|
||||||
|
"lineHeight": "36px",
|
||||||
|
"padding": "0 16px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
children
|
||||||
|
</p>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<Button /> renders the given text 1`] = `
|
||||||
|
<button
|
||||||
|
className="button"
|
||||||
|
disabled={undefined}
|
||||||
|
onClick={[Function]}
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": "36px",
|
||||||
|
"lineHeight": "36px",
|
||||||
|
"padding": "0 16px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
foo
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<Button /> renders the props.text instead of children 1`] = `
|
||||||
|
<button
|
||||||
|
className="button"
|
||||||
|
disabled={undefined}
|
||||||
|
onClick={[Function]}
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": "36px",
|
||||||
|
"lineHeight": "36px",
|
||||||
|
"padding": "0 16px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
>
|
||||||
|
foo
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<Button /> renders title if props.title is given 1`] = `
|
||||||
|
<button
|
||||||
|
className="button"
|
||||||
|
disabled={undefined}
|
||||||
|
onClick={[Function]}
|
||||||
|
style={
|
||||||
|
Object {
|
||||||
|
"height": "36px",
|
||||||
|
"lineHeight": "36px",
|
||||||
|
"padding": "0 16px",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
title="foo"
|
||||||
|
/>
|
||||||
|
`;
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`<DisplayName /> renders display name + account name 1`] = `
|
||||||
|
<span
|
||||||
|
className="display-name"
|
||||||
|
>
|
||||||
|
<strong
|
||||||
|
className="display-name__html"
|
||||||
|
dangerouslySetInnerHTML={
|
||||||
|
Object {
|
||||||
|
"__html": "<p>Foo</p>",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span
|
||||||
|
className="display-name__account"
|
||||||
|
>
|
||||||
|
@
|
||||||
|
bar@baz
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
`;
|
||||||
36
app/javascript/mastodon/components/__tests__/avatar-test.js
Normal file
36
app/javascript/mastodon/components/__tests__/avatar-test.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import renderer from 'react-test-renderer';
|
||||||
|
import { fromJS } from 'immutable';
|
||||||
|
import Avatar from '../avatar';
|
||||||
|
|
||||||
|
describe('<Avatar />', () => {
|
||||||
|
const account = fromJS({
|
||||||
|
username: 'alice',
|
||||||
|
acct: 'alice',
|
||||||
|
display_name: 'Alice',
|
||||||
|
avatar: '/animated/alice.gif',
|
||||||
|
avatar_static: '/static/alice.jpg',
|
||||||
|
});
|
||||||
|
|
||||||
|
const size = 100;
|
||||||
|
|
||||||
|
describe('Autoplay', () => {
|
||||||
|
it('renders a animated avatar', () => {
|
||||||
|
const component = renderer.create(<Avatar account={account} animate size={size} />);
|
||||||
|
const tree = component.toJSON();
|
||||||
|
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Still', () => {
|
||||||
|
it('renders a still avatar', () => {
|
||||||
|
const component = renderer.create(<Avatar account={account} size={size} />);
|
||||||
|
const tree = component.toJSON();
|
||||||
|
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO add autoplay test if possible
|
||||||
|
});
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import renderer from 'react-test-renderer';
|
||||||
|
import { fromJS } from 'immutable';
|
||||||
|
import AvatarOverlay from '../avatar_overlay';
|
||||||
|
|
||||||
|
describe('<AvatarOverlay', () => {
|
||||||
|
const account = fromJS({
|
||||||
|
username: 'alice',
|
||||||
|
acct: 'alice',
|
||||||
|
display_name: 'Alice',
|
||||||
|
avatar: '/animated/alice.gif',
|
||||||
|
avatar_static: '/static/alice.jpg',
|
||||||
|
});
|
||||||
|
|
||||||
|
const friend = fromJS({
|
||||||
|
username: 'eve',
|
||||||
|
acct: 'eve@blackhat.lair',
|
||||||
|
display_name: 'Evelyn',
|
||||||
|
avatar: '/animated/eve.gif',
|
||||||
|
avatar_static: '/static/eve.jpg',
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a overlay avatar', () => {
|
||||||
|
const component = renderer.create(<AvatarOverlay account={account} friend={friend} />);
|
||||||
|
const tree = component.toJSON();
|
||||||
|
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
82
app/javascript/mastodon/components/__tests__/button-test.js
Normal file
82
app/javascript/mastodon/components/__tests__/button-test.js
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import React from 'react';
|
||||||
|
import renderer from 'react-test-renderer';
|
||||||
|
import Button from '../button';
|
||||||
|
|
||||||
|
describe('<Button />', () => {
|
||||||
|
it('renders a button element', () => {
|
||||||
|
const component = renderer.create(<Button />);
|
||||||
|
const tree = component.toJSON();
|
||||||
|
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the given text', () => {
|
||||||
|
const text = 'foo';
|
||||||
|
const component = renderer.create(<Button text={text} />);
|
||||||
|
const tree = component.toJSON();
|
||||||
|
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles click events using the given handler', () => {
|
||||||
|
const handler = jest.fn();
|
||||||
|
const button = shallow(<Button onClick={handler} />);
|
||||||
|
button.find('button').simulate('click');
|
||||||
|
|
||||||
|
expect(handler.mock.calls.length).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not handle click events if props.disabled given', () => {
|
||||||
|
const handler = jest.fn();
|
||||||
|
const button = shallow(<Button onClick={handler} disabled />);
|
||||||
|
button.find('button').simulate('click');
|
||||||
|
|
||||||
|
expect(handler.mock.calls.length).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders a disabled attribute if props.disabled given', () => {
|
||||||
|
const component = renderer.create(<Button disabled />);
|
||||||
|
const tree = component.toJSON();
|
||||||
|
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the children', () => {
|
||||||
|
const children = <p>children</p>;
|
||||||
|
const component = renderer.create(<Button>{children}</Button>);
|
||||||
|
const tree = component.toJSON();
|
||||||
|
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the props.text instead of children', () => {
|
||||||
|
const text = 'foo';
|
||||||
|
const children = <p>children</p>;
|
||||||
|
const component = renderer.create(<Button text={text}>{children}</Button>);
|
||||||
|
const tree = component.toJSON();
|
||||||
|
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders class="button--block" if props.block given', () => {
|
||||||
|
const component = renderer.create(<Button block />);
|
||||||
|
const tree = component.toJSON();
|
||||||
|
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds class "button-secondary" if props.secondary given', () => {
|
||||||
|
const component = renderer.create(<Button secondary />);
|
||||||
|
const tree = component.toJSON();
|
||||||
|
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders title if props.title is given', () => {
|
||||||
|
const component = renderer.create(<Button title='foo' />);
|
||||||
|
const tree = component.toJSON();
|
||||||
|
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import renderer from 'react-test-renderer';
|
||||||
|
import { fromJS } from 'immutable';
|
||||||
|
import DisplayName from '../display_name';
|
||||||
|
|
||||||
|
describe('<DisplayName />', () => {
|
||||||
|
it('renders display name + account name', () => {
|
||||||
|
const account = fromJS({
|
||||||
|
username: 'bar',
|
||||||
|
acct: 'bar@baz',
|
||||||
|
display_name_html: '<p>Foo</p>',
|
||||||
|
});
|
||||||
|
const component = renderer.create(<DisplayName account={account} />);
|
||||||
|
const tree = component.toJSON();
|
||||||
|
|
||||||
|
expect(tree).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -7,6 +7,7 @@ import Permalink from './permalink';
|
|||||||
import IconButton from './icon_button';
|
import IconButton from './icon_button';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import { me } from '../initial_state';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
||||||
@@ -23,7 +24,6 @@ export default class Account extends ImmutablePureComponent {
|
|||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
account: ImmutablePropTypes.map.isRequired,
|
account: ImmutablePropTypes.map.isRequired,
|
||||||
me: PropTypes.string.isRequired,
|
|
||||||
onFollow: PropTypes.func.isRequired,
|
onFollow: PropTypes.func.isRequired,
|
||||||
onBlock: PropTypes.func.isRequired,
|
onBlock: PropTypes.func.isRequired,
|
||||||
onMute: PropTypes.func.isRequired,
|
onMute: PropTypes.func.isRequired,
|
||||||
@@ -52,7 +52,7 @@ export default class Account extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { account, me, intl, hidden } = this.props;
|
const { account, intl, hidden } = this.props;
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
return <div />;
|
return <div />;
|
||||||
@@ -93,7 +93,7 @@ export default class Account extends ImmutablePureComponent {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
buttons = <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following} />;
|
buttons = <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following ? true : false} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export default class Button extends React.PureComponent {
|
|||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
style: PropTypes.object,
|
style: PropTypes.object,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
|
title: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@@ -35,26 +36,26 @@ export default class Button extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const style = {
|
let attrs = {
|
||||||
|
className: classNames('button', this.props.className, {
|
||||||
|
'button-secondary': this.props.secondary,
|
||||||
|
'button--block': this.props.block,
|
||||||
|
}),
|
||||||
|
disabled: this.props.disabled,
|
||||||
|
onClick: this.handleClick,
|
||||||
|
ref: this.setRef,
|
||||||
|
style: {
|
||||||
padding: `0 ${this.props.size / 2.25}px`,
|
padding: `0 ${this.props.size / 2.25}px`,
|
||||||
height: `${this.props.size}px`,
|
height: `${this.props.size}px`,
|
||||||
lineHeight: `${this.props.size}px`,
|
lineHeight: `${this.props.size}px`,
|
||||||
...this.props.style,
|
...this.props.style,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const className = classNames('button', this.props.className, {
|
if (this.props.title) attrs.title = this.props.title;
|
||||||
'button-secondary': this.props.secondary,
|
|
||||||
'button--block': this.props.block,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button {...attrs}>
|
||||||
className={className}
|
|
||||||
disabled={this.props.disabled}
|
|
||||||
onClick={this.handleClick}
|
|
||||||
ref={this.setRef}
|
|
||||||
style={style}
|
|
||||||
>
|
|
||||||
{this.props.text || this.props.children}
|
{this.props.text || this.props.children}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Motion from 'react-motion/lib/Motion';
|
import Motion from '../features/ui/util/optional_motion';
|
||||||
import spring from 'react-motion/lib/spring';
|
import spring from 'react-motion/lib/spring';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
|||||||
@@ -175,7 +175,9 @@ export default class ColumnHeader extends React.PureComponent {
|
|||||||
<div className={wrapperClassName}>
|
<div className={wrapperClassName}>
|
||||||
<h1 tabIndex={focusable ? 0 : null} role='button' className={buttonClassName} aria-label={title} onClick={this.handleTitleClick}>
|
<h1 tabIndex={focusable ? 0 : null} role='button' className={buttonClassName} aria-label={title} onClick={this.handleTitleClick}>
|
||||||
<i className={`fa fa-fw fa-${icon} column-header__icon`} />
|
<i className={`fa fa-fw fa-${icon} column-header__icon`} />
|
||||||
|
<span className='column-header__title'>
|
||||||
{title}
|
{title}
|
||||||
|
</span>
|
||||||
<div className='column-header__buttons'>
|
<div className='column-header__buttons'>
|
||||||
{backButton}
|
{backButton}
|
||||||
{ notifCleaning ? (
|
{ notifCleaning ? (
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import IconButton from './icon_button';
|
import IconButton from './icon_button';
|
||||||
import Overlay from 'react-overlays/lib/Overlay';
|
import Overlay from 'react-overlays/lib/Overlay';
|
||||||
import Motion from 'react-motion/lib/Motion';
|
import Motion from '../features/ui/util/optional_motion';
|
||||||
import spring from 'react-motion/lib/spring';
|
import spring from 'react-motion/lib/spring';
|
||||||
import detectPassiveEvents from 'detect-passive-events';
|
import detectPassiveEvents from 'detect-passive-events';
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Motion from 'react-motion/lib/Motion';
|
import Motion from '../features/ui/util/optional_motion';
|
||||||
import spring from 'react-motion/lib/spring';
|
import spring from 'react-motion/lib/spring';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
export default class IconButton extends React.PureComponent {
|
export default class IconButton extends React.PureComponent {
|
||||||
|
|
||||||
@@ -56,30 +57,30 @@ export default class IconButton extends React.PureComponent {
|
|||||||
style.textAlign = 'left';
|
style.textAlign = 'left';
|
||||||
}
|
}
|
||||||
|
|
||||||
const classes = ['icon-button'];
|
const {
|
||||||
|
active,
|
||||||
|
animate,
|
||||||
|
className,
|
||||||
|
disabled,
|
||||||
|
expanded,
|
||||||
|
icon,
|
||||||
|
inverted,
|
||||||
|
flip,
|
||||||
|
overlay,
|
||||||
|
pressed,
|
||||||
|
tabIndex,
|
||||||
|
title,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
if (this.props.active) {
|
const classes = classNames(className, 'icon-button', {
|
||||||
classes.push('active');
|
active,
|
||||||
}
|
disabled,
|
||||||
|
inverted,
|
||||||
|
overlayed: overlay,
|
||||||
|
});
|
||||||
|
|
||||||
if (this.props.disabled) {
|
const flipDeg = flip ? -180 : -360;
|
||||||
classes.push('disabled');
|
const rotateDeg = active ? flipDeg : 0;
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.inverted) {
|
|
||||||
classes.push('inverted');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.overlay) {
|
|
||||||
classes.push('overlayed');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.className) {
|
|
||||||
classes.push(this.props.className);
|
|
||||||
}
|
|
||||||
|
|
||||||
const flipDeg = this.props.flip ? -180 : -360;
|
|
||||||
const rotateDeg = this.props.active ? flipDeg : 0;
|
|
||||||
|
|
||||||
const motionDefaultStyle = {
|
const motionDefaultStyle = {
|
||||||
rotate: rotateDeg,
|
rotate: rotateDeg,
|
||||||
@@ -90,23 +91,42 @@ export default class IconButton extends React.PureComponent {
|
|||||||
damping: 7,
|
damping: 7,
|
||||||
};
|
};
|
||||||
const motionStyle = {
|
const motionStyle = {
|
||||||
rotate: this.props.animate ? spring(rotateDeg, springOpts) : 0,
|
rotate: animate ? spring(rotateDeg, springOpts) : 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!animate) {
|
||||||
|
// Perf optimization: avoid unnecessary <Motion> components unless
|
||||||
|
// we actually need to animate.
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
aria-label={title}
|
||||||
|
aria-pressed={pressed}
|
||||||
|
aria-expanded={expanded}
|
||||||
|
title={title}
|
||||||
|
className={classes}
|
||||||
|
onClick={this.handleClick}
|
||||||
|
style={style}
|
||||||
|
tabIndex={tabIndex}
|
||||||
|
>
|
||||||
|
<i className={`fa fa-fw fa-${icon}`} aria-hidden='true' />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Motion defaultStyle={motionDefaultStyle} style={motionStyle}>
|
<Motion defaultStyle={motionDefaultStyle} style={motionStyle}>
|
||||||
{({ rotate }) =>
|
{({ rotate }) =>
|
||||||
<button
|
<button
|
||||||
aria-label={this.props.title}
|
aria-label={title}
|
||||||
aria-pressed={this.props.pressed}
|
aria-pressed={pressed}
|
||||||
aria-expanded={this.props.expanded}
|
aria-expanded={expanded}
|
||||||
title={this.props.title}
|
title={title}
|
||||||
className={classes.join(' ')}
|
className={classes}
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
style={style}
|
style={style}
|
||||||
tabIndex={this.props.tabIndex}
|
tabIndex={tabIndex}
|
||||||
>
|
>
|
||||||
<i style={{ transform: `rotate(${rotate}deg)` }} className={`fa fa-fw fa-${this.props.icon}`} aria-hidden='true' />
|
<i style={{ transform: `rotate(${rotate}deg)` }} className={`fa fa-fw fa-${icon}`} aria-hidden='true' />
|
||||||
{this.props.label}
|
{this.props.label}
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import IconButton from './icon_button';
|
|||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
import { isIOS } from '../is_mobile';
|
import { isIOS } from '../is_mobile';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import { autoPlayGif } from '../initial_state';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' },
|
toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' },
|
||||||
@@ -26,11 +27,9 @@ class Item extends React.PureComponent {
|
|||||||
index: PropTypes.number.isRequired,
|
index: PropTypes.number.isRequired,
|
||||||
size: PropTypes.number.isRequired,
|
size: PropTypes.number.isRequired,
|
||||||
onClick: PropTypes.func.isRequired,
|
onClick: PropTypes.func.isRequired,
|
||||||
autoPlayGif: PropTypes.bool,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
autoPlayGif: false,
|
|
||||||
standalone: false,
|
standalone: false,
|
||||||
index: 0,
|
index: 0,
|
||||||
size: 1,
|
size: 1,
|
||||||
@@ -50,7 +49,7 @@ class Item extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hoverToPlay () {
|
hoverToPlay () {
|
||||||
const { attachment, autoPlayGif } = this.props;
|
const { attachment } = this.props;
|
||||||
return !autoPlayGif && attachment.get('type') === 'gifv';
|
return !autoPlayGif && attachment.get('type') === 'gifv';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +141,7 @@ class Item extends React.PureComponent {
|
|||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
} else if (attachment.get('type') === 'gifv') {
|
} else if (attachment.get('type') === 'gifv') {
|
||||||
const autoPlay = !isIOS() && this.props.autoPlayGif;
|
const autoPlay = !isIOS() && autoPlayGif;
|
||||||
|
|
||||||
thumbnail = (
|
thumbnail = (
|
||||||
<div className={classNames('media-gallery__gifv', { autoplay: autoPlay })}>
|
<div className={classNames('media-gallery__gifv', { autoplay: autoPlay })}>
|
||||||
@@ -184,11 +183,9 @@ export default class MediaGallery extends React.PureComponent {
|
|||||||
height: PropTypes.number.isRequired,
|
height: PropTypes.number.isRequired,
|
||||||
onOpenMedia: PropTypes.func.isRequired,
|
onOpenMedia: PropTypes.func.isRequired,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
autoPlayGif: PropTypes.bool,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
autoPlayGif: false,
|
|
||||||
standalone: false,
|
standalone: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -264,9 +261,9 @@ export default class MediaGallery extends React.PureComponent {
|
|||||||
const size = media.take(4).size;
|
const size = media.take(4).size;
|
||||||
|
|
||||||
if (this.isStandaloneEligible()) {
|
if (this.isStandaloneEligible()) {
|
||||||
children = <Item standalone onClick={this.handleClick} attachment={media.get(0)} autoPlayGif={this.props.autoPlayGif} />;
|
children = <Item standalone onClick={this.handleClick} attachment={media.get(0)} />;
|
||||||
} else {
|
} else {
|
||||||
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} autoPlayGif={this.props.autoPlayGif} index={i} size={size} />);
|
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} />);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { ScrollContainer } from 'react-router-scroll';
|
import { ScrollContainer } from 'react-router-scroll-4';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import IntersectionObserverArticleContainer from '../containers/intersection_observer_article_container';
|
import IntersectionObserverArticleContainer from '../containers/intersection_observer_article_container';
|
||||||
import LoadMore from './load_more';
|
import LoadMore from './load_more';
|
||||||
|
|||||||
@@ -39,9 +39,6 @@ export default class Status extends ImmutablePureComponent {
|
|||||||
onBlock: PropTypes.func,
|
onBlock: PropTypes.func,
|
||||||
onEmbed: PropTypes.func,
|
onEmbed: PropTypes.func,
|
||||||
onHeightChange: PropTypes.func,
|
onHeightChange: PropTypes.func,
|
||||||
me: PropTypes.string,
|
|
||||||
boostModal: PropTypes.bool,
|
|
||||||
autoPlayGif: PropTypes.bool,
|
|
||||||
muted: PropTypes.bool,
|
muted: PropTypes.bool,
|
||||||
hidden: PropTypes.bool,
|
hidden: PropTypes.bool,
|
||||||
onMoveUp: PropTypes.func,
|
onMoveUp: PropTypes.func,
|
||||||
@@ -57,9 +54,6 @@ export default class Status extends ImmutablePureComponent {
|
|||||||
updateOnProps = [
|
updateOnProps = [
|
||||||
'status',
|
'status',
|
||||||
'account',
|
'account',
|
||||||
'me',
|
|
||||||
'boostModal',
|
|
||||||
'autoPlayGif',
|
|
||||||
'muted',
|
'muted',
|
||||||
'hidden',
|
'hidden',
|
||||||
]
|
]
|
||||||
@@ -200,7 +194,7 @@ export default class Status extends ImmutablePureComponent {
|
|||||||
} else {
|
} else {
|
||||||
media = (
|
media = (
|
||||||
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery} >
|
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery} >
|
||||||
{Component => <Component media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={this.props.onOpenMedia} autoPlayGif={this.props.autoPlayGif} />}
|
{Component => <Component media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={this.props.onOpenMedia} />}
|
||||||
</Bundle>
|
</Bundle>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import IconButton from './icon_button';
|
|||||||
import DropdownMenuContainer from '../containers/dropdown_menu_container';
|
import DropdownMenuContainer from '../containers/dropdown_menu_container';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import { me } from '../initial_state';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
||||||
@@ -16,6 +17,7 @@ const messages = defineMessages({
|
|||||||
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
|
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
|
||||||
reply: { id: 'status.reply', defaultMessage: 'Reply' },
|
reply: { id: 'status.reply', defaultMessage: 'Reply' },
|
||||||
share: { id: 'status.share', defaultMessage: 'Share' },
|
share: { id: 'status.share', defaultMessage: 'Share' },
|
||||||
|
more: { id: 'status.more', defaultMessage: 'More' },
|
||||||
replyAll: { id: 'status.replyAll', defaultMessage: 'Reply to thread' },
|
replyAll: { id: 'status.replyAll', defaultMessage: 'Reply to thread' },
|
||||||
reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
|
reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
|
||||||
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
|
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
|
||||||
@@ -49,7 +51,6 @@ export default class StatusActionBar extends ImmutablePureComponent {
|
|||||||
onEmbed: PropTypes.func,
|
onEmbed: PropTypes.func,
|
||||||
onMuteConversation: PropTypes.func,
|
onMuteConversation: PropTypes.func,
|
||||||
onPin: PropTypes.func,
|
onPin: PropTypes.func,
|
||||||
me: PropTypes.string,
|
|
||||||
withDismiss: PropTypes.bool,
|
withDismiss: PropTypes.bool,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
@@ -58,7 +59,6 @@ export default class StatusActionBar extends ImmutablePureComponent {
|
|||||||
// evaluate to false. See react-immutable-pure-component for usage.
|
// evaluate to false. See react-immutable-pure-component for usage.
|
||||||
updateOnProps = [
|
updateOnProps = [
|
||||||
'status',
|
'status',
|
||||||
'me',
|
|
||||||
'withDismiss',
|
'withDismiss',
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ export default class StatusActionBar extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { status, me, intl, withDismiss } = this.props;
|
const { status, intl, withDismiss } = this.props;
|
||||||
|
|
||||||
const mutingConversation = status.get('muted');
|
const mutingConversation = status.get('muted');
|
||||||
const anonymousAccess = !me;
|
const anonymousAccess = !me;
|
||||||
@@ -182,7 +182,7 @@ export default class StatusActionBar extends ImmutablePureComponent {
|
|||||||
{shareButton}
|
{shareButton}
|
||||||
|
|
||||||
<div className='status__action-bar-dropdown'>
|
<div className='status__action-bar-dropdown'>
|
||||||
<DropdownMenuContainer disabled={anonymousAccess} status={status} items={menu} icon='ellipsis-h' size={18} direction='right' ariaLabel='More' />
|
<DropdownMenuContainer disabled={anonymousAccess} status={status} items={menu} icon='ellipsis-h' size={18} direction='right' ariaLabel={intl.formatMessage(messages.more)} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ export default class StatusContent extends React.PureComponent {
|
|||||||
const directionStyle = { direction: 'ltr' };
|
const directionStyle = { direction: 'ltr' };
|
||||||
const classNames = classnames('status__content', {
|
const classNames = classnames('status__content', {
|
||||||
'status__content--with-action': this.props.onClick && this.context.router,
|
'status__content--with-action': this.props.onClick && this.context.router,
|
||||||
|
'status__content--with-spoiler': status.get('spoiler_text').length > 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isRtl(status.get('search_index'))) {
|
if (isRtl(status.get('search_index'))) {
|
||||||
@@ -156,7 +157,7 @@ export default class StatusContent extends React.PureComponent {
|
|||||||
|
|
||||||
{mentionsPlaceholder}
|
{mentionsPlaceholder}
|
||||||
|
|
||||||
<div tabIndex={!hidden && 0} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} style={directionStyle} dangerouslySetInnerHTML={content} />
|
<div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} style={directionStyle} dangerouslySetInnerHTML={content} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (this.props.onClick) {
|
} else if (this.props.onClick) {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
} from '../actions/accounts';
|
} from '../actions/accounts';
|
||||||
import { openModal } from '../actions/modal';
|
import { openModal } from '../actions/modal';
|
||||||
import { initMuteModal } from '../actions/mutes';
|
import { initMuteModal } from '../actions/mutes';
|
||||||
|
import { unfollowModal } from '../initial_state';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
|
unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
|
||||||
@@ -23,8 +24,6 @@ const makeMapStateToProps = () => {
|
|||||||
|
|
||||||
const mapStateToProps = (state, props) => ({
|
const mapStateToProps = (state, props) => ({
|
||||||
account: getAccount(state, props.id),
|
account: getAccount(state, props.id),
|
||||||
me: state.getIn(['meta', 'me']),
|
|
||||||
unfollowModal: state.getIn(['meta', 'unfollow_modal']),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return mapStateToProps;
|
return mapStateToProps;
|
||||||
@@ -34,7 +33,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||||||
|
|
||||||
onFollow (account) {
|
onFollow (account) {
|
||||||
if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
|
if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
|
||||||
if (this.unfollowModal) {
|
if (unfollowModal) {
|
||||||
dispatch(openModal('CONFIRM', {
|
dispatch(openModal('CONFIRM', {
|
||||||
message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
|
message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
|
||||||
confirm: intl.formatMessage(messages.unfollowConfirm),
|
confirm: intl.formatMessage(messages.unfollowConfirm),
|
||||||
|
|||||||
@@ -6,15 +6,14 @@ import { hydrateStore } from '../actions/store';
|
|||||||
import { IntlProvider, addLocaleData } from 'react-intl';
|
import { IntlProvider, addLocaleData } from 'react-intl';
|
||||||
import { getLocale } from '../locales';
|
import { getLocale } from '../locales';
|
||||||
import Compose from '../features/standalone/compose';
|
import Compose from '../features/standalone/compose';
|
||||||
|
import initialState from '../initial_state';
|
||||||
|
|
||||||
const { localeData, messages } = getLocale();
|
const { localeData, messages } = getLocale();
|
||||||
addLocaleData(localeData);
|
addLocaleData(localeData);
|
||||||
|
|
||||||
const store = configureStore();
|
const store = configureStore();
|
||||||
const initialStateContainer = document.getElementById('initial-state');
|
|
||||||
|
|
||||||
if (initialStateContainer !== null) {
|
if (initialState) {
|
||||||
const initialState = JSON.parse(initialStateContainer.textContent);
|
|
||||||
store.dispatch(hydrateStore(initialState));
|
store.dispatch(hydrateStore(initialState));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,22 +4,18 @@ import PropTypes from 'prop-types';
|
|||||||
import configureStore from '../store/configureStore';
|
import configureStore from '../store/configureStore';
|
||||||
import { showOnboardingOnce } from '../actions/onboarding';
|
import { showOnboardingOnce } from '../actions/onboarding';
|
||||||
import { BrowserRouter, Route } from 'react-router-dom';
|
import { BrowserRouter, Route } from 'react-router-dom';
|
||||||
import { ScrollContext } from 'react-router-scroll';
|
import { ScrollContext } from 'react-router-scroll-4';
|
||||||
import UI from '../features/ui';
|
import UI from '../features/ui';
|
||||||
import { hydrateStore } from '../actions/store';
|
import { hydrateStore } from '../actions/store';
|
||||||
import { connectUserStream } from '../actions/streaming';
|
import { connectUserStream } from '../actions/streaming';
|
||||||
import { IntlProvider, addLocaleData } from 'react-intl';
|
import { IntlProvider, addLocaleData } from 'react-intl';
|
||||||
import { getLocale } from '../locales';
|
import { getLocale } from '../locales';
|
||||||
|
import initialState from '../initial_state';
|
||||||
|
|
||||||
const { localeData, messages } = getLocale();
|
const { localeData, messages } = getLocale();
|
||||||
addLocaleData(localeData);
|
addLocaleData(localeData);
|
||||||
|
|
||||||
export const store = configureStore();
|
export const store = configureStore();
|
||||||
const initialState = JSON.parse(document.getElementById('initial-state').textContent);
|
|
||||||
try {
|
|
||||||
initialState.local_settings = JSON.parse(localStorage.getItem('mastodon-settings'));
|
|
||||||
} catch (e) {
|
|
||||||
initialState.local_settings = {};
|
|
||||||
}
|
|
||||||
const hydrateAction = hydrateStore(initialState);
|
const hydrateAction = hydrateStore(initialState);
|
||||||
store.dispatch(hydrateAction);
|
store.dispatch(hydrateAction);
|
||||||
|
|
||||||
|
|||||||
@@ -17,20 +17,18 @@ import {
|
|||||||
pin,
|
pin,
|
||||||
unpin,
|
unpin,
|
||||||
} from '../actions/interactions';
|
} from '../actions/interactions';
|
||||||
import {
|
import { blockAccount } from '../actions/accounts';
|
||||||
blockAccount,
|
|
||||||
muteAccount,
|
|
||||||
} from '../actions/accounts';
|
|
||||||
import { muteStatus, unmuteStatus, deleteStatus } from '../actions/statuses';
|
import { muteStatus, unmuteStatus, deleteStatus } from '../actions/statuses';
|
||||||
|
import { initMuteModal } from '../actions/mutes';
|
||||||
import { initReport } from '../actions/reports';
|
import { initReport } from '../actions/reports';
|
||||||
import { openModal } from '../actions/modal';
|
import { openModal } from '../actions/modal';
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
|
import { boostModal, deleteModal } from '../initial_state';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
|
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
|
||||||
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
|
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
|
||||||
blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
|
blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
|
||||||
muteConfirm: { id: 'confirmations.mute.confirm', defaultMessage: 'Mute' },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const makeMapStateToProps = () => {
|
const makeMapStateToProps = () => {
|
||||||
@@ -38,10 +36,6 @@ const makeMapStateToProps = () => {
|
|||||||
|
|
||||||
const mapStateToProps = (state, props) => ({
|
const mapStateToProps = (state, props) => ({
|
||||||
status: getStatus(state, props.id),
|
status: getStatus(state, props.id),
|
||||||
me: state.getIn(['meta', 'me']),
|
|
||||||
boostModal: state.getIn(['meta', 'boost_modal']),
|
|
||||||
deleteModal: state.getIn(['meta', 'delete_modal']),
|
|
||||||
autoPlayGif: state.getIn(['meta', 'auto_play_gif']),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return mapStateToProps;
|
return mapStateToProps;
|
||||||
@@ -61,7 +55,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||||||
if (status.get('reblogged')) {
|
if (status.get('reblogged')) {
|
||||||
dispatch(unreblog(status));
|
dispatch(unreblog(status));
|
||||||
} else {
|
} else {
|
||||||
if (e.shiftKey || !this.boostModal) {
|
if (e.shiftKey || !boostModal) {
|
||||||
this.onModalReblog(status);
|
this.onModalReblog(status);
|
||||||
} else {
|
} else {
|
||||||
dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
|
dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
|
||||||
@@ -90,7 +84,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
onDelete (status) {
|
onDelete (status) {
|
||||||
if (!this.deleteModal) {
|
if (!deleteModal) {
|
||||||
dispatch(deleteStatus(status.get('id')));
|
dispatch(deleteStatus(status.get('id')));
|
||||||
} else {
|
} else {
|
||||||
dispatch(openModal('CONFIRM', {
|
dispatch(openModal('CONFIRM', {
|
||||||
@@ -126,11 +120,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
onMute (account) {
|
onMute (account) {
|
||||||
dispatch(openModal('CONFIRM', {
|
dispatch(initMuteModal(account));
|
||||||
message: <FormattedMessage id='confirmations.mute.message' defaultMessage='Are you sure you want to mute {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
|
|
||||||
confirm: intl.formatMessage(messages.muteConfirm),
|
|
||||||
onConfirm: () => dispatch(muteAccount(account.get('id'))),
|
|
||||||
}));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onMuteConversation (status) {
|
onMuteConversation (status) {
|
||||||
|
|||||||
@@ -7,15 +7,14 @@ import { IntlProvider, addLocaleData } from 'react-intl';
|
|||||||
import { getLocale } from '../locales';
|
import { getLocale } from '../locales';
|
||||||
import PublicTimeline from '../features/standalone/public_timeline';
|
import PublicTimeline from '../features/standalone/public_timeline';
|
||||||
import HashtagTimeline from '../features/standalone/hashtag_timeline';
|
import HashtagTimeline from '../features/standalone/hashtag_timeline';
|
||||||
|
import initialState from '../initial_state';
|
||||||
|
|
||||||
const { localeData, messages } = getLocale();
|
const { localeData, messages } = getLocale();
|
||||||
addLocaleData(localeData);
|
addLocaleData(localeData);
|
||||||
|
|
||||||
const store = configureStore();
|
const store = configureStore();
|
||||||
const initialStateContainer = document.getElementById('initial-state');
|
|
||||||
|
|
||||||
if (initialStateContainer !== null) {
|
if (initialState) {
|
||||||
const initialState = JSON.parse(initialStateContainer.textContent);
|
|
||||||
store.dispatch(hydrateStore(initialState));
|
store.dispatch(hydrateStore(initialState));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
|
|||||||
import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
|
import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { defineMessages, injectIntl, FormattedMessage, FormattedNumber } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedMessage, FormattedNumber } from 'react-intl';
|
||||||
|
import { me } from '../../../initial_state';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' },
|
mention: { id: 'account.mention', defaultMessage: 'Mention @{name}' },
|
||||||
@@ -19,6 +20,8 @@ const messages = defineMessages({
|
|||||||
media: { id: 'account.media', defaultMessage: 'Media' },
|
media: { id: 'account.media', defaultMessage: 'Media' },
|
||||||
blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' },
|
blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' },
|
||||||
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
|
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
|
||||||
|
hideReblogs: { id: 'account.hide_reblogs', defaultMessage: 'Hide boosts from @{name}' },
|
||||||
|
showReblogs: { id: 'account.show_reblogs', defaultMessage: 'Show boosts from @{name}' },
|
||||||
});
|
});
|
||||||
|
|
||||||
@injectIntl
|
@injectIntl
|
||||||
@@ -26,10 +29,10 @@ export default class ActionBar extends React.PureComponent {
|
|||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
account: ImmutablePropTypes.map.isRequired,
|
account: ImmutablePropTypes.map.isRequired,
|
||||||
me: PropTypes.string.isRequired,
|
|
||||||
onFollow: PropTypes.func,
|
onFollow: PropTypes.func,
|
||||||
onBlock: PropTypes.func.isRequired,
|
onBlock: PropTypes.func.isRequired,
|
||||||
onMention: PropTypes.func.isRequired,
|
onMention: PropTypes.func.isRequired,
|
||||||
|
onReblogToggle: PropTypes.func.isRequired,
|
||||||
onReport: PropTypes.func.isRequired,
|
onReport: PropTypes.func.isRequired,
|
||||||
onMute: PropTypes.func.isRequired,
|
onMute: PropTypes.func.isRequired,
|
||||||
onBlockDomain: PropTypes.func.isRequired,
|
onBlockDomain: PropTypes.func.isRequired,
|
||||||
@@ -44,7 +47,7 @@ export default class ActionBar extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { account, me, intl } = this.props;
|
const { account, intl } = this.props;
|
||||||
|
|
||||||
let menu = [];
|
let menu = [];
|
||||||
let extraInfo = '';
|
let extraInfo = '';
|
||||||
@@ -60,6 +63,15 @@ export default class ActionBar extends React.PureComponent {
|
|||||||
if (account.get('id') === me) {
|
if (account.get('id') === me) {
|
||||||
menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' });
|
menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' });
|
||||||
} else {
|
} else {
|
||||||
|
const following = account.getIn(['relationship', 'following']);
|
||||||
|
if (following) {
|
||||||
|
if (following.get('reblogs')) {
|
||||||
|
menu.push({ text: intl.formatMessage(messages.hideReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
|
||||||
|
} else {
|
||||||
|
menu.push({ text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }), action: this.props.onReblogToggle });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (account.getIn(['relationship', 'muting'])) {
|
if (account.getIn(['relationship', 'muting'])) {
|
||||||
menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.props.onMute });
|
menu.push({ text: intl.formatMessage(messages.unmute, { name: account.get('username') }), action: this.props.onMute });
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
import IconButton from '../../../components/icon_button';
|
import IconButton from '../../../components/icon_button';
|
||||||
import Motion from 'react-motion/lib/Motion';
|
import Motion from '../../ui/util/optional_motion';
|
||||||
import spring from 'react-motion/lib/spring';
|
import spring from 'react-motion/lib/spring';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import { autoPlayGif, me } from '../../../initial_state';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
||||||
@@ -17,19 +17,10 @@ const messages = defineMessages({
|
|||||||
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
|
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval. Click to cancel follow request' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const makeMapStateToProps = () => {
|
|
||||||
const mapStateToProps = state => ({
|
|
||||||
autoPlayGif: state.getIn(['meta', 'auto_play_gif']),
|
|
||||||
});
|
|
||||||
|
|
||||||
return mapStateToProps;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Avatar extends ImmutablePureComponent {
|
class Avatar extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
account: ImmutablePropTypes.map.isRequired,
|
account: ImmutablePropTypes.map.isRequired,
|
||||||
autoPlayGif: PropTypes.bool.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@@ -47,7 +38,7 @@ class Avatar extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { account, autoPlayGif } = this.props;
|
const { account } = this.props;
|
||||||
const { isHovered } = this.state;
|
const { isHovered } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -74,20 +65,17 @@ class Avatar extends ImmutablePureComponent {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@connect(makeMapStateToProps)
|
|
||||||
@injectIntl
|
@injectIntl
|
||||||
export default class Header extends ImmutablePureComponent {
|
export default class Header extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
account: ImmutablePropTypes.map,
|
account: ImmutablePropTypes.map,
|
||||||
me: PropTypes.string.isRequired,
|
|
||||||
onFollow: PropTypes.func.isRequired,
|
onFollow: PropTypes.func.isRequired,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
autoPlayGif: PropTypes.bool.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { account, me, intl } = this.props;
|
const { account, intl } = this.props;
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
return null;
|
return null;
|
||||||
@@ -127,7 +115,7 @@ export default class Header extends ImmutablePureComponent {
|
|||||||
return (
|
return (
|
||||||
<div className='account__header' style={{ backgroundImage: `url(${account.get('header')})` }}>
|
<div className='account__header' style={{ backgroundImage: `url(${account.get('header')})` }}>
|
||||||
<div>
|
<div>
|
||||||
<Avatar account={account} autoPlayGif={this.props.autoPlayGif} />
|
<Avatar account={account} />
|
||||||
|
|
||||||
<span className='account__header__display-name' dangerouslySetInnerHTML={displayNameHtml} />
|
<span className='account__header__display-name' dangerouslySetInnerHTML={displayNameHtml} />
|
||||||
<span className='account__header__username'>@{account.get('acct')} {lockedIcon}</span>
|
<span className='account__header__username'>@{account.get('acct')} {lockedIcon}</span>
|
||||||
|
|||||||
@@ -12,14 +12,13 @@ import { getAccountGallery } from '../../selectors';
|
|||||||
import MediaItem from './components/media_item';
|
import MediaItem from './components/media_item';
|
||||||
import HeaderContainer from '../account_timeline/containers/header_container';
|
import HeaderContainer from '../account_timeline/containers/header_container';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { ScrollContainer } from 'react-router-scroll';
|
import { ScrollContainer } from 'react-router-scroll-4';
|
||||||
import LoadMore from '../../components/load_more';
|
import LoadMore from '../../components/load_more';
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => ({
|
const mapStateToProps = (state, props) => ({
|
||||||
medias: getAccountGallery(state, props.params.accountId),
|
medias: getAccountGallery(state, props.params.accountId),
|
||||||
isLoading: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'isLoading']),
|
isLoading: state.getIn(['timelines', `account:${props.params.accountId}:media`, 'isLoading']),
|
||||||
hasMore: !!state.getIn(['timelines', `account:${props.params.accountId}:media`, 'next']),
|
hasMore: !!state.getIn(['timelines', `account:${props.params.accountId}:media`, 'next']),
|
||||||
autoPlayGif: state.getIn(['meta', 'auto_play_gif']),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@connect(mapStateToProps)
|
@connect(mapStateToProps)
|
||||||
@@ -31,7 +30,6 @@ export default class AccountGallery extends ImmutablePureComponent {
|
|||||||
medias: ImmutablePropTypes.list.isRequired,
|
medias: ImmutablePropTypes.list.isRequired,
|
||||||
isLoading: PropTypes.bool,
|
isLoading: PropTypes.bool,
|
||||||
hasMore: PropTypes.bool,
|
hasMore: PropTypes.bool,
|
||||||
autoPlayGif: PropTypes.bool,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
@@ -67,7 +65,7 @@ export default class AccountGallery extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { medias, autoPlayGif, isLoading, hasMore } = this.props;
|
const { medias, isLoading, hasMore } = this.props;
|
||||||
|
|
||||||
let loadMore = null;
|
let loadMore = null;
|
||||||
|
|
||||||
@@ -100,7 +98,6 @@ export default class AccountGallery extends ImmutablePureComponent {
|
|||||||
<MediaItem
|
<MediaItem
|
||||||
key={media.get('id')}
|
key={media.get('id')}
|
||||||
media={media}
|
media={media}
|
||||||
autoPlayGif={autoPlayGif}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{loadMore}
|
{loadMore}
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ export default class Header extends ImmutablePureComponent {
|
|||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
account: ImmutablePropTypes.map,
|
account: ImmutablePropTypes.map,
|
||||||
me: PropTypes.string.isRequired,
|
|
||||||
onFollow: PropTypes.func.isRequired,
|
onFollow: PropTypes.func.isRequired,
|
||||||
onBlock: PropTypes.func.isRequired,
|
onBlock: PropTypes.func.isRequired,
|
||||||
onMention: PropTypes.func.isRequired,
|
onMention: PropTypes.func.isRequired,
|
||||||
|
onReblogToggle: PropTypes.func.isRequired,
|
||||||
onReport: PropTypes.func.isRequired,
|
onReport: PropTypes.func.isRequired,
|
||||||
onMute: PropTypes.func.isRequired,
|
onMute: PropTypes.func.isRequired,
|
||||||
onBlockDomain: PropTypes.func.isRequired,
|
onBlockDomain: PropTypes.func.isRequired,
|
||||||
@@ -40,6 +40,10 @@ export default class Header extends ImmutablePureComponent {
|
|||||||
this.props.onReport(this.props.account);
|
this.props.onReport(this.props.account);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleReblogToggle = () => {
|
||||||
|
this.props.onReblogToggle(this.props.account);
|
||||||
|
}
|
||||||
|
|
||||||
handleMute = () => {
|
handleMute = () => {
|
||||||
this.props.onMute(this.props.account);
|
this.props.onMute(this.props.account);
|
||||||
}
|
}
|
||||||
@@ -61,7 +65,7 @@ export default class Header extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { account, me } = this.props;
|
const { account } = this.props;
|
||||||
|
|
||||||
if (account === null) {
|
if (account === null) {
|
||||||
return <MissingIndicator />;
|
return <MissingIndicator />;
|
||||||
@@ -71,15 +75,14 @@ export default class Header extends ImmutablePureComponent {
|
|||||||
<div className='account-timeline__header'>
|
<div className='account-timeline__header'>
|
||||||
<InnerHeader
|
<InnerHeader
|
||||||
account={account}
|
account={account}
|
||||||
me={me}
|
|
||||||
onFollow={this.handleFollow}
|
onFollow={this.handleFollow}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ActionBar
|
<ActionBar
|
||||||
account={account}
|
account={account}
|
||||||
me={me}
|
|
||||||
onBlock={this.handleBlock}
|
onBlock={this.handleBlock}
|
||||||
onMention={this.handleMention}
|
onMention={this.handleMention}
|
||||||
|
onReblogToggle={this.handleReblogToggle}
|
||||||
onReport={this.handleReport}
|
onReport={this.handleReport}
|
||||||
onMute={this.handleMute}
|
onMute={this.handleMute}
|
||||||
onBlockDomain={this.handleBlockDomain}
|
onBlockDomain={this.handleBlockDomain}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { initReport } from '../../../actions/reports';
|
|||||||
import { openModal } from '../../../actions/modal';
|
import { openModal } from '../../../actions/modal';
|
||||||
import { blockDomain, unblockDomain } from '../../../actions/domain_blocks';
|
import { blockDomain, unblockDomain } from '../../../actions/domain_blocks';
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
|
import { unfollowModal } from '../../../initial_state';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
|
unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
|
||||||
@@ -27,8 +28,6 @@ const makeMapStateToProps = () => {
|
|||||||
|
|
||||||
const mapStateToProps = (state, { accountId }) => ({
|
const mapStateToProps = (state, { accountId }) => ({
|
||||||
account: getAccount(state, accountId),
|
account: getAccount(state, accountId),
|
||||||
me: state.getIn(['meta', 'me']),
|
|
||||||
unfollowModal: state.getIn(['meta', 'unfollow_modal']),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return mapStateToProps;
|
return mapStateToProps;
|
||||||
@@ -38,7 +37,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||||||
|
|
||||||
onFollow (account) {
|
onFollow (account) {
|
||||||
if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
|
if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
|
||||||
if (this.unfollowModal) {
|
if (unfollowModal) {
|
||||||
dispatch(openModal('CONFIRM', {
|
dispatch(openModal('CONFIRM', {
|
||||||
message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
|
message: <FormattedMessage id='confirmations.unfollow.message' defaultMessage='Are you sure you want to unfollow {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
|
||||||
confirm: intl.formatMessage(messages.unfollowConfirm),
|
confirm: intl.formatMessage(messages.unfollowConfirm),
|
||||||
@@ -68,6 +67,14 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||||||
dispatch(mentionCompose(account, router));
|
dispatch(mentionCompose(account, router));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onReblogToggle (account) {
|
||||||
|
if (account.getIn(['relationship', 'following', 'reblogs'])) {
|
||||||
|
dispatch(followAccount(account.get('id'), false));
|
||||||
|
} else {
|
||||||
|
dispatch(followAccount(account.get('id'), true));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
onReport (account) {
|
onReport (account) {
|
||||||
dispatch(initReport(account));
|
dispatch(initReport(account));
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ const mapStateToProps = (state, props) => ({
|
|||||||
statusIds: state.getIn(['timelines', `account:${props.params.accountId}`, 'items'], ImmutableList()),
|
statusIds: state.getIn(['timelines', `account:${props.params.accountId}`, 'items'], ImmutableList()),
|
||||||
isLoading: state.getIn(['timelines', `account:${props.params.accountId}`, 'isLoading']),
|
isLoading: state.getIn(['timelines', `account:${props.params.accountId}`, 'isLoading']),
|
||||||
hasMore: !!state.getIn(['timelines', `account:${props.params.accountId}`, 'next']),
|
hasMore: !!state.getIn(['timelines', `account:${props.params.accountId}`, 'next']),
|
||||||
me: state.getIn(['meta', 'me']),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@connect(mapStateToProps)
|
@connect(mapStateToProps)
|
||||||
@@ -28,7 +27,6 @@ export default class AccountTimeline extends ImmutablePureComponent {
|
|||||||
statusIds: ImmutablePropTypes.list,
|
statusIds: ImmutablePropTypes.list,
|
||||||
isLoading: PropTypes.bool,
|
isLoading: PropTypes.bool,
|
||||||
hasMore: PropTypes.bool,
|
hasMore: PropTypes.bool,
|
||||||
me: PropTypes.string.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
componentWillMount () {
|
componentWillMount () {
|
||||||
@@ -50,7 +48,7 @@ export default class AccountTimeline extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { statusIds, isLoading, hasMore, me } = this.props;
|
const { statusIds, isLoading, hasMore } = this.props;
|
||||||
|
|
||||||
if (!statusIds && isLoading) {
|
if (!statusIds && isLoading) {
|
||||||
return (
|
return (
|
||||||
@@ -70,7 +68,6 @@ export default class AccountTimeline extends ImmutablePureComponent {
|
|||||||
statusIds={statusIds}
|
statusIds={statusIds}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
hasMore={hasMore}
|
hasMore={hasMore}
|
||||||
me={me}
|
|
||||||
onScrollToBottom={this.handleScrollToBottom}
|
onScrollToBottom={this.handleScrollToBottom}
|
||||||
/>
|
/>
|
||||||
</Column>
|
</Column>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { connect } from 'react-redux';
|
|||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import LoadingIndicator from '../../components/loading_indicator';
|
import LoadingIndicator from '../../components/loading_indicator';
|
||||||
import { ScrollContainer } from 'react-router-scroll';
|
import { ScrollContainer } from 'react-router-scroll-4';
|
||||||
import Column from '../ui/components/column';
|
import Column from '../ui/components/column';
|
||||||
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
|
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
|
||||||
import AccountContainer from '../../containers/account_container';
|
import AccountContainer from '../../containers/account_container';
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ReplyIndicatorContainer from '../containers/reply_indicator_container';
|
import ReplyIndicatorContainer from '../containers/reply_indicator_container';
|
||||||
import AutosuggestTextarea from '../../../components/autosuggest_textarea';
|
import AutosuggestTextarea from '../../../components/autosuggest_textarea';
|
||||||
import UploadButtonContainer from '../containers/upload_button_container';
|
|
||||||
import DoodleButtonContainer from '../containers/doodle_button_container';
|
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
import Collapsable from '../../../components/collapsable';
|
import Collapsable from '../../../components/collapsable';
|
||||||
import SpoilerButtonContainer from '../containers/spoiler_button_container';
|
import SpoilerButtonContainer from '../containers/spoiler_button_container';
|
||||||
@@ -20,6 +18,7 @@ import { isMobile } from '../../../is_mobile';
|
|||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { length } from 'stringz';
|
import { length } from 'stringz';
|
||||||
import { countableText } from '../util/counter';
|
import { countableText } from '../util/counter';
|
||||||
|
import ComposeAttachOptions from '../../../../glitch/components/compose/attach_options/index';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
|
placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' },
|
||||||
@@ -46,7 +45,6 @@ export default class ComposeForm extends ImmutablePureComponent {
|
|||||||
preselectDate: PropTypes.instanceOf(Date),
|
preselectDate: PropTypes.instanceOf(Date),
|
||||||
is_submitting: PropTypes.bool,
|
is_submitting: PropTypes.bool,
|
||||||
is_uploading: PropTypes.bool,
|
is_uploading: PropTypes.bool,
|
||||||
me: PropTypes.string,
|
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
onSubmit: PropTypes.func.isRequired,
|
onSubmit: PropTypes.func.isRequired,
|
||||||
onClearSuggestions: PropTypes.func.isRequired,
|
onClearSuggestions: PropTypes.func.isRequired,
|
||||||
@@ -58,7 +56,6 @@ export default class ComposeForm extends ImmutablePureComponent {
|
|||||||
onPickEmoji: PropTypes.func.isRequired,
|
onPickEmoji: PropTypes.func.isRequired,
|
||||||
showSearch: PropTypes.bool,
|
showSearch: PropTypes.bool,
|
||||||
settings : ImmutablePropTypes.map.isRequired,
|
settings : ImmutablePropTypes.map.isRequired,
|
||||||
filesAttached : PropTypes.bool,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@@ -156,17 +153,18 @@ export default class ComposeForm extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, onPaste, showSearch, filesAttached } = this.props;
|
const { intl, onPaste, showSearch } = this.props;
|
||||||
const disabled = this.props.is_submitting;
|
const disabled = this.props.is_submitting;
|
||||||
const maybeEye = (this.props.advanced_options && this.props.advanced_options.do_not_federate) ? ' 👁️' : '';
|
const maybeEye = (this.props.advanced_options && this.props.advanced_options.do_not_federate) ? ' 👁️' : '';
|
||||||
const text = [this.props.spoiler_text, countableText(this.props.text), maybeEye].join('');
|
const text = [this.props.spoiler_text, countableText(this.props.text), maybeEye].join('');
|
||||||
|
|
||||||
const secondaryVisibility = this.props.settings.get('side_arm');
|
const secondaryVisibility = this.props.settings.get('side_arm');
|
||||||
const isWideView = this.props.settings.get('stretch');
|
|
||||||
let showSideArm = secondaryVisibility !== 'none';
|
let showSideArm = secondaryVisibility !== 'none';
|
||||||
|
|
||||||
let publishText = '';
|
let publishText = '';
|
||||||
let publishText2 = '';
|
let publishText2 = '';
|
||||||
|
let title = '';
|
||||||
|
let title2 = '';
|
||||||
|
|
||||||
const privacyIcons = {
|
const privacyIcons = {
|
||||||
none: '',
|
none: '',
|
||||||
@@ -176,30 +174,30 @@ export default class ComposeForm extends ImmutablePureComponent {
|
|||||||
direct: 'envelope',
|
direct: 'envelope',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
title = `${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${this.props.privacy}.short` })}`;
|
||||||
|
|
||||||
if (showSideArm) {
|
if (showSideArm) {
|
||||||
|
// Enhanced behavior with dual toot buttons
|
||||||
publishText = (
|
publishText = (
|
||||||
<span>
|
<span>
|
||||||
{
|
{
|
||||||
<i
|
<i
|
||||||
className={`fa fa-${privacyIcons[this.props.privacy]}`}
|
className={`fa fa-${privacyIcons[this.props.privacy]}`}
|
||||||
style={{
|
style={{ paddingRight: '5px' }}
|
||||||
paddingRight: (filesAttached || !isWideView) ? '0' : '5px',
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
}{
|
}{intl.formatMessage(messages.publish)}
|
||||||
(filesAttached || !isWideView) ? '' :
|
|
||||||
intl.formatMessage(messages.publish)
|
|
||||||
}
|
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
title2 = `${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${secondaryVisibility}.short` })}`;
|
||||||
publishText2 = (
|
publishText2 = (
|
||||||
<i
|
<i
|
||||||
className={`fa fa-${privacyIcons[secondaryVisibility]}`}
|
className={`fa fa-${privacyIcons[secondaryVisibility]}`}
|
||||||
aria-label={`${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${secondaryVisibility}.short` })}`}
|
aria-label={title2}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
// Original vanilla behavior - no icon if public or unlisted
|
||||||
if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
|
if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
|
||||||
publishText = <span className='compose-form__publish-private'><i className='fa fa-lock' /> {intl.formatMessage(messages.publish)}</span>;
|
publishText = <span className='compose-form__publish-private'><i className='fa fa-lock' /> {intl.formatMessage(messages.publish)}</span>;
|
||||||
} else {
|
} else {
|
||||||
@@ -247,14 +245,13 @@ export default class ComposeForm extends ImmutablePureComponent {
|
|||||||
<UploadFormContainer />
|
<UploadFormContainer />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='compose-form__buttons-wrapper'>
|
|
||||||
<div className='compose-form__buttons'>
|
<div className='compose-form__buttons'>
|
||||||
<UploadButtonContainer />
|
<ComposeAttachOptions />
|
||||||
<DoodleButtonContainer />
|
|
||||||
<PrivacyDropdownContainer />
|
|
||||||
<ComposeAdvancedOptionsContainer />
|
|
||||||
<SensitiveButtonContainer />
|
<SensitiveButtonContainer />
|
||||||
|
<div className='compose-form__buttons-separator' />
|
||||||
|
<PrivacyDropdownContainer />
|
||||||
<SpoilerButtonContainer />
|
<SpoilerButtonContainer />
|
||||||
|
<ComposeAdvancedOptionsContainer />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='compose-form__publish'>
|
<div className='compose-form__publish'>
|
||||||
@@ -265,6 +262,7 @@ export default class ComposeForm extends ImmutablePureComponent {
|
|||||||
<Button
|
<Button
|
||||||
className='compose-form__publish__side-arm'
|
className='compose-form__publish__side-arm'
|
||||||
text={publishText2}
|
text={publishText2}
|
||||||
|
title={title2}
|
||||||
onClick={this.handleSubmit2}
|
onClick={this.handleSubmit2}
|
||||||
disabled={submitDisabled}
|
disabled={submitDisabled}
|
||||||
/> : ''
|
/> : ''
|
||||||
@@ -272,14 +270,13 @@ export default class ComposeForm extends ImmutablePureComponent {
|
|||||||
<Button
|
<Button
|
||||||
className='compose-form__publish__primary'
|
className='compose-form__publish__primary'
|
||||||
text={publishText}
|
text={publishText}
|
||||||
|
title={title}
|
||||||
onClick={this.handleSubmit}
|
onClick={this.handleSubmit}
|
||||||
disabled={submitDisabled}
|
disabled={submitDisabled}
|
||||||
block
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user