Compare commits

..

5 Commits

Author SHA1 Message Date
Eugen Rochko
a0f7453c6e Bump version to 1.3.3 2017-05-06 13:53:43 +02:00
Eugen Rochko
46a1e16f21 Fix Scheduler::SubscriptionsScheduler (#2834)
* Fix Scheduler::SubscriptionsScheduler, add worker test for it

* Change production log level of Sidekiq to "warn" instead of "info"
2017-05-06 13:52:42 +02:00
Eugen Rochko
f3f7a3840a Fix #2706 - Always respond with 200 to PuSH payloads (#2733)
Fix #2196 - Respond with 201 when Salmon accepted, 400 when unverified
Fix #2629 - Correctly handle confirm_domain? for local accounts
Unify rules for extracting author acct from XML, prefer <email>, fall back
to <name> + <uri> (see also #2017, #2172)
2017-05-05 22:01:04 +02:00
Eugen Rochko
7539254e96 Likely fix #2458, fix #2031 - handle out-of-order deletes for statuses (#2734)
* Likely fix #2458, fix #2031 - handle out-of-order deletes for statuses

If a delete arrives before the original status, cache that information
for 6h, and if the original status arrives in that window, ignore it

* Add test case
2017-05-05 21:58:29 +02:00
Eugen Rochko
456478c4e1 More robust PuSH subscription refreshes (#2799)
* Fix #2473 - Use sidekiq scheduler to refresh PuSH subscriptions instead of cron

Fix an issue where / in domain would raise exception in TagManager#normalize_domain

PuSH subscriptions refresh done in a round-robin way to avoid hammering a single
server's hub in sequence. Correct handling of failures/retries through Sidekiq (see
also #2613). Optimize Account#with_followers scope. Also, since subscriptions
are now delegated to Sidekiq jobs, an uncaught exception will not stop the entire
refreshing operation halfway through

Fix #2702 - Correct user agent header on outgoing http requests

* Add test for SubscribeService

* Extract #expiring_accounts into method

* Make mastodon:push:refresh no-op

* Queues are now defined in sidekiq.yml

* Queues are now in sidekiq.yml
2017-05-05 21:53:01 +02:00
1577 changed files with 21692 additions and 62929 deletions

View File

@@ -1,65 +1,7 @@
{ {
"presets": [ "presets": ["es2015", "react"],
"react",
[
"env",
{
"loose": true,
"modules": false,
"targets": {
"browsers": ["last 2 versions", "IE >= 11", "iOS >= 9"]
}
}
]
],
"plugins": [ "plugins": [
"syntax-dynamic-import",
["transform-object-rest-spread", { "useBuiltIns": true }],
"transform-decorators-legacy", "transform-decorators-legacy",
"transform-class-properties", "transform-object-rest-spread"
[ ]
"react-intl",
{
"messagesDir": "./build/messages"
}
],
"preval"
],
"env": {
"development": {
"plugins": [
"transform-react-jsx-source",
"transform-react-jsx-self"
]
},
"production": {
"plugins": [
"lodash",
[
"transform-react-remove-prop-types",
{
"mode": "remove",
"removeImport": true,
"additionalLibraries": [
"react-immutable-proptypes"
]
}
],
"transform-react-inline-elements",
[
"transform-runtime",
{
"helpers": true,
"polyfill": false,
"regenerator": false
}
]
]
},
"test": {
"plugins": [
"transform-es2015-modules-commonjs"
]
}
}
} }

View File

@@ -1,3 +1,2 @@
https://github.com/heroku/heroku-buildpack-apt
https://github.com/Scalingo/nodejs-buildpack https://github.com/Scalingo/nodejs-buildpack
https://github.com/Scalingo/ruby-buildpack https://github.com/Scalingo/ruby-buildpack

View File

@@ -1,21 +1,14 @@
engines: engines:
brakeman: duplication:
enabled: true enabled: false
bundler-audit: rubocop:
enabled: true enabled: true
duplication: eslint:
enabled: false enabled: true
eslint:
enabled: true
rubocop:
enabled: true
scss-lint:
enabled: true
ratings: ratings:
paths: paths:
- "**.rb" - "**.rb"
- "**.js" - "**.js"
- "**.scss"
exclude_paths: exclude_paths:
- spec/ - spec/
- vendor/asset - vendor/asset

View File

@@ -2,12 +2,10 @@
.env.* .env.*
public/system public/system
public/assets public/assets
public/packs
node_modules node_modules
storybook
neo4j neo4j
vendor/bundle vendor/bundle
.DS_Store .DS_Store
*.swp *.swp
*~ *~
postgres
redis

View File

@@ -1,111 +0,0 @@
# Service dependencies
# You may set REDIS_URL instead for more advanced options
REDIS_HOST=$DATA_REDIS_HOST
REDIS_PORT=6379
# REDIS_DB=0
# You may set DATABASE_URL instead for more advanced options
DB_HOST=$DATA_DB_HOST
DB_USER=$DATA_DB_USER
DB_NAME=gonano
DB_PASS=$DATA_DB_PASS
DB_PORT=5432
DATABASE_URL=postgresql://$DATA_DB_USER:$DATA_DB_PASS@$DATA_DB_HOST/gonano
# Federation
# Note: Changing LOCAL_DOMAIN or LOCAL_HTTPS at a later time will cause unwanted side effects.
# LOCAL_DOMAIN should *NOT* contain the protocol part of the domain e.g https://example.com.
LOCAL_DOMAIN=${APP_NAME}.nanoapp.io
LOCAL_HTTPS=false
# Use this only if you need to run mastodon on a different domain than the one used for federation.
# You can read more about this option on https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Serving_a_different_domain.md
# DO *NOT* USE THIS UNLESS YOU KNOW *EXACTLY* WHAT YOU ARE DOING.
# WEB_DOMAIN=mastodon.example.com
# Use this if you want to have several aliases handler@example1.com
# handler@example2.com etc. for the same user. LOCAL_DOMAIN should not
# be added. Comma separated values
# ALTERNATE_DOMAINS=example1.com,example2.com
# Application secrets
# Generate each with the `rake secret` task (`nanobox run bundle exec rake secret`)
PAPERCLIP_SECRET=$PAPERCLIP_SECRET
SECRET_KEY_BASE=$SECRET_KEY_BASE
OTP_SECRET=$OTP_SECRET
# Registrations
# Single user mode will disable registrations and redirect frontpage to the first profile
# SINGLE_USER_MODE=true
# Prevent registrations with following e-mail domains
# EMAIL_DOMAIN_BLACKLIST=example1.com|example2.de|etc
# Only allow registrations with the following e-mail domains
# EMAIL_DOMAIN_WHITELIST=example1.com|example2.de|etc
# Optionally change default language
# DEFAULT_LOCALE=de
# E-mail configuration
# Note: Mailgun and SparkPost (https://sparkpo.st/smtp) each have good free tiers
# If you want to use an SMTP server without authentication (e.g local Postfix relay)
# then set SMTP_AUTH_METHOD and SMTP_OPENSSL_VERIFY_MODE to 'none' and
# *comment* SMTP_LOGIN and SMTP_PASSWORD (leaving them blank is not enough).
SMTP_SERVER=$SMTP_SERVER
SMTP_PORT=587
SMTP_LOGIN=$SMTP_LOGIN
SMTP_PASSWORD=$SMTP_PASSWORD
SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
#SMTP_DOMAIN= # defaults to LOCAL_DOMAIN
#SMTP_DELIVERY_METHOD=smtp # delivery method can also be sendmail
#SMTP_AUTH_METHOD=plain
#SMTP_CA_FILE=/etc/ssl/certs/ca-certificates.crt
#SMTP_OPENSSL_VERIFY_MODE=peer
#SMTP_ENABLE_STARTTLS_AUTO=true
# Optional user upload path and URL (images, avatars). Default is :rails_root/public/system. If you set this variable, you are responsible for making your HTTP server (eg. nginx) serve these files.
# PAPERCLIP_ROOT_PATH=/var/lib/mastodon/public-system
# PAPERCLIP_ROOT_URL=/system
# Optional asset host for multi-server setups
# CDN_HOST=https://assets.example.com
# S3 (optional)
# S3_ENABLED=true
# S3_BUCKET=
# AWS_ACCESS_KEY_ID=
# AWS_SECRET_ACCESS_KEY=
# S3_REGION=
# S3_PROTOCOL=http
# S3_HOSTNAME=192.168.1.123:9000
# S3 (Minio Config (optional) Please check Minio instance for details)
# S3_ENABLED=true
# S3_BUCKET=
# AWS_ACCESS_KEY_ID=
# AWS_SECRET_ACCESS_KEY=
# S3_REGION=
# S3_PROTOCOL=https
# S3_HOSTNAME=
# S3_ENDPOINT=
# S3_SIGNATURE_VERSION=
# Optional alias for S3 if you want to use Cloudfront or Cloudflare in front
# S3_CLOUDFRONT_HOST=
# Streaming API integration
# STREAMING_API_BASE_URL=
# Advanced settings
# If you need to use pgBouncer, you need to disable prepared statements:
# PREPARED_STATEMENTS=false
# Cluster number setting for streaming API server.
# If you comment out following line, cluster number will be `numOfCpuCores - 1`.
STREAMING_CLUSTER_NUM=1
# Docker mastodon user
# If you use Docker, you may want to assign UID/GID manually.
# UID=1000
# GID=1000

View File

@@ -1,8 +1,7 @@
# Service dependencies # Service dependencies
# You may set REDIS_URL instead for more advanced options
REDIS_HOST=redis REDIS_HOST=redis
REDIS_PORT=6379 REDIS_PORT=6379
# You may set DATABASE_URL instead for more advanced options # REDIS_DB=0
DB_HOST=db DB_HOST=db
DB_USER=postgres DB_USER=postgres
DB_NAME=postgres DB_NAME=postgres
@@ -10,38 +9,19 @@ DB_PASS=
DB_PORT=5432 DB_PORT=5432
# Federation # Federation
# Note: Changing LOCAL_DOMAIN or LOCAL_HTTPS at a later time will cause unwanted side effects. LOCAL_DOMAIN=example.com
# LOCAL_DOMAIN should *NOT* contain the protocol part of the domain e.g https://example.com.
LOCAL_DOMAIN=example.com
LOCAL_HTTPS=true LOCAL_HTTPS=true
# Use this only if you need to run mastodon on a different domain than the one used for federation. # Use this only if you need to run mastodon on a different domain than the one used for federation.
# You can read more about this option on https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Serving_a_different_domain.md # Do not use this unless you know exactly what you are doing.
# DO *NOT* USE THIS UNLESS YOU KNOW *EXACTLY* WHAT YOU ARE DOING.
# WEB_DOMAIN=mastodon.example.com # WEB_DOMAIN=mastodon.example.com
# Use this if you want to have several aliases handler@example1.com
# handler@example2.com etc. for the same user. LOCAL_DOMAIN should not
# be added. Comma separated values
# ALTERNATE_DOMAINS=example1.com,example2.com
# Application secrets # Application secrets
# Generate each with the `RAILS_ENV=production bundle exec rake secret` task (`docker-compose run --rm web rake secret` if you use docker compose) # Generate each with the `rake secret` task (`docker-compose run --rm web rake secret` if you use docker compose)
PAPERCLIP_SECRET= PAPERCLIP_SECRET=
SECRET_KEY_BASE= SECRET_KEY_BASE=
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 `RAILS_ENV=production bundle exec rake mastodon:webpush:generate_vapid_key` task (`docker-compose run --rm web rake mastodon:webpush:generate_vapid_key` if you use docker compose)
#
# For more information visit https://rossta.net/blog/using-the-web-push-api-with-vapid.html
VAPID_PRIVATE_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
@@ -56,8 +36,8 @@ VAPID_PUBLIC_KEY=
# E-mail configuration # E-mail configuration
# Note: Mailgun and SparkPost (https://sparkpo.st/smtp) each have good free tiers # Note: Mailgun and SparkPost (https://sparkpo.st/smtp) each have good free tiers
# If you want to use an SMTP server without authentication (e.g local Postfix relay) # If you want to use an SMTP server without authentication (e.g local Postfix relay)
# then set SMTP_AUTH_METHOD and SMTP_OPENSSL_VERIFY_MODE to 'none' and # then set SMTP_AUTH_METHOD to 'none' and *comment* SMTP_LOGIN and SMTP_PASSWORD.
# *comment* SMTP_LOGIN and SMTP_PASSWORD (leaving them blank is not enough). # Leaving them blank is not enough for authentication method 'none'.
SMTP_SERVER=smtp.mailgun.org SMTP_SERVER=smtp.mailgun.org
SMTP_PORT=587 SMTP_PORT=587
SMTP_LOGIN= SMTP_LOGIN=
@@ -66,17 +46,16 @@ SMTP_FROM_ADDRESS=notifications@example.com
#SMTP_DOMAIN= # defaults to LOCAL_DOMAIN #SMTP_DOMAIN= # defaults to LOCAL_DOMAIN
#SMTP_DELIVERY_METHOD=smtp # delivery method can also be sendmail #SMTP_DELIVERY_METHOD=smtp # delivery method can also be sendmail
#SMTP_AUTH_METHOD=plain #SMTP_AUTH_METHOD=plain
#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
# PAPERCLIP_ROOT_URL=/system # PAPERCLIP_ROOT_URL=/system
# Optional asset host for multi-server setups # Optional asset host for multi-server setups
# CDN_HOST=https://assets.example.com # CDN_HOST=assets.example.com
# S3 (optional) # S3 (optional)
# S3_ENABLED=true # S3_ENABLED=true
@@ -98,23 +77,6 @@ SMTP_FROM_ADDRESS=notifications@example.com
# 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=
@@ -128,8 +90,3 @@ SMTP_FROM_ADDRESS=notifications@example.com
# Cluster number setting for streaming API server. # Cluster number setting for streaming API server.
# If you comment out following line, cluster number will be `numOfCpuCores - 1`. # If you comment out following line, cluster number will be `numOfCpuCores - 1`.
STREAMING_CLUSTER_NUM=1 STREAMING_CLUSTER_NUM=1
# Docker mastodon user
# If you use Docker, you may want to assign UID/GID manually.
# UID=1000
# GID=1000

79
.eslintrc.json Normal file
View File

@@ -0,0 +1,79 @@
{
"env": {
"browser": true,
"node": false,
"es6": true
},
"parser": "babel-eslint",
"plugins": [
"react",
"jsx-a11y"
],
"parserOptions": {
"sourceType": "module",
"ecmaFeatures": {
"arrowFunctions": true,
"jsx": true,
"destructuring": true,
"modules": true,
"spread": true
}
},
"rules": {
"no-cond-assign": 2,
"no-console": 1,
"no-irregular-whitespace": 2,
"no-unreachable": 2,
"valid-typeof": 2,
"consistent-return": 2,
"dot-notation": 2,
"eqeqeq": 2,
"no-fallthrough": 2,
"no-unused-expressions": 2,
"strict": 0,
"no-catch-shadow": 2,
"indent": [1, 2],
"brace-style": 1,
"comma-spacing": [1, {"before": false, "after": true}],
"comma-style": [1, "last"],
"no-mixed-spaces-and-tabs": 1,
"no-nested-ternary": 1,
"no-trailing-spaces": 1,
"react/jsx-wrap-multilines": 2,
"react/self-closing-comp": 2,
"react/prop-types": 2,
"react/no-multi-comp": 0,
"jsx-a11y/accessible-emoji": 1,
"jsx-a11y/anchor-has-content": 1,
"jsx-a11y/aria-activedescendant-has-tabindex": 1,
"jsx-a11y/aria-props": 1,
"jsx-a11y/aria-proptypes": 1,
"jsx-a11y/aria-role": 1,
"jsx-a11y/aria-unsupported-elements": 1,
"jsx-a11y/heading-has-content": 1,
"jsx-a11y/href-no-hash": 1,
"jsx-a11y/html-has-lang": 1,
"jsx-a11y/iframe-has-title": 1,
"jsx-a11y/img-has-alt": 1,
"jsx-a11y/img-redundant-alt": 1,
"jsx-a11y/label-has-for": 1,
"jsx-a11y/mouse-events-have-key-events": 1,
"jsx-a11y/no-access-key": 1,
"jsx-a11y/no-distracting-elements": 1,
"jsx-a11y/no-onchange": 1,
"jsx-a11y/no-redundant-roles": 1,
"jsx-a11y/onclick-has-focus": 1,
"jsx-a11y/onclick-has-role": 1,
"jsx-a11y/role-has-required-aria-props": 1,
"jsx-a11y/role-supports-aria-props": 1,
"jsx-a11y/scope": 1,
"jsx-a11y/tabindex-no-positive": 1
}
}

View File

@@ -1,127 +0,0 @@
---
root: true
env:
browser: true
node: true
es6: true
parser: babel-eslint
plugins:
- react
- jsx-a11y
parserOptions:
sourceType: module
ecmaFeatures:
arrowFunctions: true
jsx: true
destructuring: true
modules: true
spread: true
rules:
brace-style: warn
comma-dangle:
- error
- always-multiline
comma-spacing:
- warn
- before: false
after: true
comma-style:
- warn
- last
consistent-return: error
dot-notation: error
eqeqeq: error
indent:
- warn
- 2
jsx-quotes:
- error
- prefer-single
no-catch-shadow: error
no-cond-assign: error
no-console:
- warn
- allow:
- error
- warn
no-fallthrough: error
no-irregular-whitespace: error
no-mixed-spaces-and-tabs: warn
no-nested-ternary: warn
no-trailing-spaces: warn
no-undef: error
no-unreachable: error
no-unused-expressions: error
no-unused-vars:
- error
- vars: all
args: after-used
ignoreRestSiblings: true
object-curly-spacing:
- error
- always
padded-blocks:
- error
- classes: always
quotes:
- error
- single
semi: error
strict: off
valid-typeof: error
react/jsx-boolean-value: error
react/jsx-closing-bracket-location:
- error
- line-aligned
react/jsx-curly-spacing: error
react/jsx-equals-spacing: error
react/jsx-first-prop-new-line:
- error
- multiline-multiprop
react/jsx-indent:
- error
- 2
react/jsx-no-bind: error
react/jsx-no-duplicate-props: error
react/jsx-no-undef: error
react/jsx-tag-spacing: error
react/jsx-uses-react: error
react/jsx-uses-vars: error
react/jsx-wrap-multilines: error
react/no-multi-comp: off
react/no-string-refs: error
react/prop-types: error
react/self-closing-comp: error
jsx-a11y/accessible-emoji: warn
jsx-a11y/anchor-has-content: warn
jsx-a11y/aria-activedescendant-has-tabindex: warn
jsx-a11y/aria-props: warn
jsx-a11y/aria-proptypes: warn
jsx-a11y/aria-role: warn
jsx-a11y/aria-unsupported-elements: warn
jsx-a11y/heading-has-content: warn
jsx-a11y/href-no-hash: warn
jsx-a11y/html-has-lang: warn
jsx-a11y/iframe-has-title: warn
jsx-a11y/img-has-alt: warn
jsx-a11y/img-redundant-alt: warn
jsx-a11y/label-has-for: off
jsx-a11y/mouse-events-have-key-events: warn
jsx-a11y/no-access-key: warn
jsx-a11y/no-distracting-elements: warn
jsx-a11y/no-onchange: warn
jsx-a11y/no-redundant-roles: warn
jsx-a11y/onclick-has-focus: warn
jsx-a11y/onclick-has-role: warn
jsx-a11y/role-has-required-aria-props: warn
jsx-a11y/role-supports-aria-props: off
jsx-a11y/scope: warn
jsx-a11y/tabindex-no-positive: warn

View File

@@ -1 +0,0 @@
procfile: Procfile.dev

14
.gitattributes vendored
View File

@@ -1,14 +0,0 @@
* text=auto eol=lf
*.eot -text
*.gif -text
*.gz -text
*.ico -text
*.jpg -text
*.mp3 -text
*.ogg -text
*.png -text
*.ttf -text
*.webm -text
*.woff -text
*.woff2 -text
spec/fixtures/requests/** -text !eol

16
.gitignore vendored
View File

@@ -19,12 +19,10 @@
coverage coverage
public/system public/system
public/assets public/assets
public/packs
public/packs-test
.env .env
.env.production .env.production
node_modules/ node_modules/
build/ neo4j/
# Ignore Vagrant files # Ignore Vagrant files
.vagrant/ .vagrant/
@@ -34,7 +32,6 @@ config/deploy/*
# Ignore IDE files # Ignore IDE files
.vscode/ .vscode/
.idea/
# Ignore postgres + redis volume optionally created by docker-compose # Ignore postgres + redis volume optionally created by docker-compose
postgres postgres
@@ -46,14 +43,3 @@ redis
# Ignore vim files # Ignore vim files
*~ *~
*.swp *.swp
# Ignore npm debug log
npm-debug.log
# Ignore yarn log files
yarn-error.log
yarn-debug.log
# Ignore Docker option files
docker-compose.override.yml

View File

@@ -1,108 +0,0 @@
# Whether to ignore frontmatter at the beginning of HAML documents for
# frameworks such as Jekyll/Middleman
skip_frontmatter: false
exclude:
- 'vendor/**/*'
- 'spec/**/*'
- 'lib/templates/**/*'
- 'app/views/kaminari/**/*'
linters:
AltText:
enabled: false
ClassAttributeWithStaticValue:
enabled: true
ClassesBeforeIds:
enabled: true
ConsecutiveComments:
enabled: true
ConsecutiveSilentScripts:
enabled: true
max_consecutive: 2
EmptyObjectReference:
enabled: true
EmptyScript:
enabled: true
FinalNewline:
enabled: true
present: true
HtmlAttributes:
enabled: true
ImplicitDiv:
enabled: true
LeadingCommentSpace:
enabled: true
LineLength:
enabled: false
max: 80
MultilinePipe:
enabled: true
MultilineScript:
enabled: true
ObjectReferenceAttributes:
enabled: true
RuboCop:
enabled: true
# These cops are incredibly noisy when it comes to HAML templates, so we
# ignore them.
ignored_cops:
- Lint/BlockAlignment
- Lint/EndAlignment
- Lint/Void
- Metrics/BlockLength
- Metrics/LineLength
- Style/AlignParameters
- Style/BlockNesting
- Style/ElseAlignment
- Style/EndOfLine
- Style/FileName
- Style/FinalNewline
- Style/FrozenStringLiteralComment
- Style/IfUnlessModifier
- Style/IndentationWidth
- Style/Next
- Style/TrailingBlankLines
- Style/TrailingWhitespace
- Style/WhileUntilModifier
RubyComments:
enabled: true
SpaceBeforeScript:
enabled: true
SpaceInsideHashAttributes:
enabled: true
style: space
Indentation:
enabled: true
character: space # or tab
TagName:
enabled: true
TrailingWhitespace:
enabled: true
UnnecessaryInterpolation:
enabled: true
UnnecessaryStringOutput:
enabled: true

View File

@@ -1,19 +0,0 @@
.DS_Store
.git/
.gitignore
.bundle/
.cache/
config/deploy/*
coverage
docs/
.env
log/*.log
neo4j/
node_modules/
public/assets/
public/system/
spec/
tmp/
.vagrant/
vendor/bundle/

View File

@@ -1,9 +0,0 @@
plugins:
postcss-smart-import: {}
precss: {}
autoprefixer:
browsers:
- last 2 versions
- IE >= 11
- iOS >= 9
postcss-object-fit-images: {}

View File

@@ -1 +0,0 @@
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/app/.apt/lib/x86_64-linux-gnu:/app/.apt/usr/lib/x86_64-linux-gnu/mesa:/app/.apt/usr/lib/x86_64-linux-gnu/pulseaudio

View File

@@ -1,46 +1,14 @@
AllCops: Rails:
TargetRubyVersion: 2.3 Enabled: true
Exclude:
- 'spec/**/*'
- 'db/**/*'
- 'app/views/**/*'
- 'config/**/*'
- 'bin/*'
- 'Rakefile'
- 'node_modules/**/*'
- 'Vagrantfile'
- 'vendor/**/*'
- 'lib/json_ld/*'
Bundler/OrderedGems: Style/PerlBackrefs:
AutoCorrect: false
Style/ClassAndModuleChildren:
Enabled: false Enabled: false
Layout/AccessModifierIndentation:
EnforcedStyle: indent
Layout/EmptyLineAfterMagicComment:
Enabled: false
Layout/SpaceInsideHashLiteralBraces:
EnforcedStyle: space
Metrics/AbcSize:
Max: 100
Metrics/BlockLength:
Max: 35
Exclude:
- 'lib/tasks/**/*'
Metrics/BlockNesting: Metrics/BlockNesting:
Max: 3 Max: 2
Metrics/ClassLength:
CountComments: false
Max: 300
Metrics/CyclomaticComplexity:
Max: 25
Metrics/LineLength: Metrics/LineLength:
AllowURI: true AllowURI: true
@@ -48,30 +16,37 @@ Metrics/LineLength:
Metrics/MethodLength: Metrics/MethodLength:
CountComments: false CountComments: false
Max: 10
Metrics/AbcSize:
Max: 100
Metrics/BlockNesting:
Max: 3
Metrics/ClassLength:
CountComments: false
Max: 200
Metrics/CyclomaticComplexity:
Max: 15
Metrics/MethodLength:
Max: 55 Max: 55
Metrics/ModuleLength: Metrics/ModuleLength:
CountComments: false CountComments: false
Max: 200 Max: 200
Metrics/PerceivedComplexity:
Max: 10
Metrics/ParameterLists: Metrics/ParameterLists:
Max: 5 Max: 4
CountKeywordArgs: true CountKeywordArgs: true
Metrics/PerceivedComplexity: Style/AccessModifierIndentation:
Max: 20 EnforcedStyle: indent
Rails:
Enabled: true
Rails/HasAndBelongsToMany:
Enabled: false
Rails/SkipsModelValidations:
Enabled: false
Style/ClassAndModuleChildren:
Enabled: false
Style/CollectionMethods: Style/CollectionMethods:
Enabled: true Enabled: true
@@ -87,25 +62,29 @@ Style/DoubleNegation:
Style/FrozenStringLiteralComment: Style/FrozenStringLiteralComment:
Enabled: true Enabled: true
Style/GuardClause: Style/SpaceInsideHashLiteralBraces:
EnforcedStyle: space
Style/TrailingCommaInLiteral:
EnforcedStyleForMultiline: 'comma'
Style/RegexpLiteral:
Enabled: false Enabled: false
Style/Lambda: Style/Lambda:
Enabled: false Enabled: false
Style/PercentLiteralDelimiters: Rails/HasAndBelongsToMany:
PreferredDelimiters:
'%i': '()'
'%w': '()'
Style/PerlBackrefs:
AutoCorrect: false
Style/RegexpLiteral:
Enabled: false Enabled: false
Style/SymbolArray: AllCops:
Enabled: false TargetRubyVersion: 2.3
Exclude:
Style/TrailingCommaInLiteral: - 'spec/**/*'
EnforcedStyleForMultiline: 'comma' - 'db/**/*'
- 'app/views/**/*'
- 'config/**/*'
- 'bin/*'
- 'Rakefile'
- 'node_modules/**/*'
- 'Vagrantfile'

View File

@@ -1,264 +0,0 @@
# Linter Documentation:
# https://github.com/brigade/scss-lint/blob/v0.42.2/lib/scss_lint/linter/README.md
scss_files: 'app/javascript/styles/**/*.scss'
exclude:
- app/javascript/styles/reset.scss
linters:
# Reports when you use improper spacing around ! (the "bang") in !default,
# !global, !important, and !optional flags.
BangFormat:
enabled: false
# Whether or not to prefer `border: 0` over `border: none`.
BorderZero:
enabled: false
# Reports when you define a rule set using a selector with chained classes
# (a.k.a. adjoining classes).
ChainedClasses:
enabled: false
# Prefer hexadecimal color codes over color keywords.
# (e.g. `color: green` is a color keyword)
ColorKeyword:
enabled: false
# Prefer color literals (keywords or hexadecimal codes) to be used only in
# variable declarations. They should be referred to via variables everywhere
# else.
ColorVariable:
enabled: true
# Which form of comments to prefer in CSS.
Comment:
enabled: false
# Reports @debug statements (which you probably left behind accidentally).
DebugStatement:
enabled: false
# Rule sets should be ordered as follows:
# - @extend declarations
# - @include declarations without inner @content
# - properties, @include declarations with inner @content
# - nested rule sets.
DeclarationOrder:
enabled: false
# `scss-lint:disable` control comments should be preceded by a comment
# explaining why these linters are being disabled for this file.
# See https://github.com/brigade/scss-lint#disabling-linters-via-source for
# more information.
DisableLinterReason:
enabled: true
# Reports when you define the same property twice in a single rule set.
DuplicateProperty:
enabled: false
# Separate rule, function, and mixin declarations with empty lines.
EmptyLineBetweenBlocks:
enabled: true
# Reports when you have an empty rule set.
EmptyRule:
enabled: true
# Reports when you have an @extend directive.
ExtendDirective:
enabled: false
# Files should always have a final newline. This results in better diffs
# when adding lines to the file, since SCM systems such as git won't
# think that you touched the last line.
FinalNewline:
enabled: false
# HEX colors should use three-character values where possible.
HexLength:
enabled: false
# HEX color values should use lower-case colors to differentiate between
# letters and numbers, e.g. `#E3E3E3` vs. `#e3e3e3`.
HexNotation:
enabled: true
# Avoid using ID selectors.
IdSelector:
enabled: false
# The basenames of @imported SCSS partials should not begin with an
# underscore and should not include the filename extension.
ImportPath:
enabled: false
# Avoid using !important in properties. It is usually indicative of a
# misunderstanding of CSS specificity and can lead to brittle code.
ImportantRule:
enabled: false
# Indentation should always be done in increments of 2 spaces.
Indentation:
enabled: true
width: 2
# Don't write leading zeros for numeric values with a decimal point.
LeadingZero:
enabled: false
# Reports when you define the same selector twice in a single sheet.
MergeableSelector:
enabled: false
# Functions, mixins, variables, and placeholders should be declared
# with all lowercase letters and hyphens instead of underscores.
NameFormat:
enabled: false
# Avoid nesting selectors too deeply.
NestingDepth:
enabled: false
# Always use placeholder selectors in @extend.
PlaceholderInExtend:
enabled: false
# Sort properties in a strict order.
PropertySortOrder:
enabled: false
# Reports when you use an unknown or disabled CSS property
# (ignoring vendor-prefixed properties).
PropertySpelling:
enabled: false
# Configure which units are allowed for property values.
PropertyUnits:
enabled: false
# Pseudo-elements, like ::before, and ::first-letter, should be declared
# with two colons. Pseudo-classes, like :hover and :first-child, should
# be declared with one colon.
PseudoElement:
enabled: true
# Avoid qualifying elements in selectors (also known as "tag-qualifying").
QualifyingElement:
enabled: false
# Don't write selectors with a depth of applicability greater than 3.
SelectorDepth:
enabled: false
# Selectors should always use hyphenated-lowercase, rather than camelCase or
# snake_case.
SelectorFormat:
enabled: false
convention: hyphenated_lowercase
# Prefer the shortest shorthand form possible for properties that support it.
Shorthand:
enabled: true
# Each property should have its own line, except in the special case of
# single line rulesets.
SingleLinePerProperty:
enabled: true
allow_single_line_rule_sets: true
# Split selectors onto separate lines after each comma, and have each
# individual selector occupy a single line.
SingleLinePerSelector:
enabled: true
# Commas in lists should be followed by a space.
SpaceAfterComma:
enabled: false
# Properties should be formatted with a single space separating the colon
# from the property's value.
SpaceAfterPropertyColon:
enabled: true
# Properties should be formatted with no space between the name and the
# colon.
SpaceAfterPropertyName:
enabled: true
# Variables should be formatted with a single space separating the colon
# from the variable's value.
SpaceAfterVariableColon:
enabled: true
# Variables should be formatted with no space between the name and the
# colon.
SpaceAfterVariableName:
enabled: false
# Operators should be formatted with a single space on both sides of an
# infix operator.
SpaceAroundOperator:
enabled: true
# Opening braces should be preceded by a single space.
SpaceBeforeBrace:
enabled: true
# Parentheses should not be padded with spaces.
SpaceBetweenParens:
enabled: false
# Enforces that string literals should be written with a consistent form
# of quotes (single or double).
StringQuotes:
enabled: false
# Property values, @extend, @include, and @import directives, and variable
# declarations should always end with a semicolon.
TrailingSemicolon:
enabled: true
# Reports lines containing trailing whitespace.
TrailingWhitespace:
enabled: true
# Don't write trailing zeros for numeric values with a decimal point.
TrailingZero:
enabled: false
# Don't use the `all` keyword to specify transition properties.
TransitionAll:
enabled: false
# Numeric values should not contain unnecessary fractional portions.
UnnecessaryMantissa:
enabled: false
# Do not use parent selector references (&) when they would otherwise
# be unnecessary.
UnnecessaryParentReference:
enabled: false
# URLs should be valid and not contain protocols or domain names.
UrlFormat:
enabled: true
# URLs should always be enclosed within quotes.
UrlQuotes:
enabled: true
# Properties, like color and font, are easier to read and maintain
# when defined using variables rather than literals.
VariableForProperty:
enabled: false
# Avoid vendor prefixes. Or rather: don't write them yourself.
VendorPrefix:
enabled: false
# Omit length units on zero values, e.g. `0px` vs. `0`.
ZeroUnit:
enabled: true

View File

@@ -2,3 +2,4 @@ node_modules/
.cache/ .cache/
docs/ docs/
spec/ spec/
storybook/

View File

@@ -3,12 +3,9 @@ cache:
bundler: true bundler: true
yarn: true yarn: true
directories: directories:
- node_modules - node_modules
- public/assets
- public/packs-test
- tmp/cache/babel-loader
dist: trusty dist: trusty
sudo: required sudo: false
notifications: notifications:
email: false email: false
@@ -18,10 +15,7 @@ env:
- LOCAL_DOMAIN=cb6e6126.ngrok.io - LOCAL_DOMAIN=cb6e6126.ngrok.io
- LOCAL_HTTPS=true - LOCAL_HTTPS=true
- RAILS_ENV=test - RAILS_ENV=test
- NOKOGIRI_USE_SYSTEM_LIBRARIES=true - CXX=g++-4.8
- PARALLEL_TEST_PROCESSORS=2
- "PATH=$HOME:$PATH"
addons: addons:
postgresql: 9.4 postgresql: 9.4
apt: apt:
@@ -29,11 +23,8 @@ addons:
- ubuntu-toolchain-r-test - ubuntu-toolchain-r-test
- trusty-media - trusty-media
packages: packages:
- g++-4.8
- ffmpeg - ffmpeg
- g++-6
- libprotobuf-dev
- protobuf-compiler
- libicu-dev
rvm: rvm:
- 2.3.4 - 2.3.4
@@ -42,18 +33,18 @@ rvm:
services: services:
- redis-server - redis-server
bundler_args: --without development production --retry=3 --jobs=3
install: install:
- nvm install - nvm install
- npm install -g yarn - npm install -g yarn
- bundle install --path=vendor/bundle --without development production --retry=3 --jobs=16 - bundle install
- yarn install - yarn install
before_script: before_script:
- bundle exec rake parallel:create parallel:load_schema parallel:prepare - bundle exec rails db:create db:migrate
- bundle exec rails assets:precompile
- ln -s /usr/bin/x86_64-linux-gnu-g++-6 "$HOME/g++"
script: script:
- travis_retry bundle exec parallel_test spec/ --group-by filesize --type rspec - bundle exec rspec
- npm test - npm test
- bundle exec i18n-tasks unused - i18n-tasks unused

10
Aptfile
View File

@@ -1,10 +0,0 @@
ffmpeg
libicu[0-9][0-9]
libicu-dev
libidn11
libidn11-dev
libpq-dev
libprotobuf-dev
libxdamage1
libxfixes3
protobuf-compiler

View File

@@ -1,15 +0,0 @@
# CODEOWNERS for tootsuite/mastodon
# Translators
# To add translator, copy these lines, replace `fr` with appropriate language code and replace `@żelipapą` with user's GitHub nickname preceded by `@` sign or e-mail address.
# /app/javascript/mastodon/locales/fr.json @żelipapą
# /app/views/user_mailer/*.fr.html.erb @żelipapą
# /app/views/user_mailer/*.fr.text.erb @żelipapą
# /config/locales/*.fr.yml @żelipapą
# /config/locales/fr.yml @żelipapą
/app/javascript/mastodon/locales/pl.json @m4sk1n
/app/views/user_mailer/*.pl.html.erb @m4sk1n
/app/views/user_mailer/*.pl.text.erb @m4sk1n
/config/locales/*.pl.yml @m4sk1n
/config/locales/pl.yml @m4sk1n

View File

@@ -1,4 +1,3 @@
# frozen_string_literal: true
require 'capistrano/setup' require 'capistrano/setup'
require 'capistrano/deploy' require 'capistrano/deploy'
require 'capistrano/scm/git' require 'capistrano/scm/git'
@@ -9,6 +8,7 @@ require 'capistrano/rbenv'
require 'capistrano/bundler' require 'capistrano/bundler'
require 'capistrano/yarn' require 'capistrano/yarn'
require 'capistrano/rails/assets' require 'capistrano/rails/assets'
require 'capistrano/faster_assets'
require 'capistrano/rails/migrations' require 'capistrano/rails/migrations'
Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r } Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }

View File

@@ -1,69 +1,44 @@
FROM ruby:2.4.1-alpine3.6 FROM ruby:2.4.1-alpine
LABEL maintainer="https://github.com/tootsuite/mastodon" \ LABEL maintainer="https://github.com/tootsuite/mastodon" \
description="A GNU Social-compatible microblogging server" description="A GNU Social-compatible microblogging server"
ENV UID=991 GID=991 \ ENV RAILS_ENV=production \
RAILS_SERVE_STATIC_FILES=true \ NODE_ENV=production
RAILS_ENV=production NODE_ENV=production
ARG LIBICONV_VERSION=1.15
ARG LIBICONV_DOWNLOAD_SHA256=ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc8913178
EXPOSE 3000 4000 EXPOSE 3000 4000
WORKDIR /mastodon WORKDIR /mastodon
RUN apk -U upgrade \
&& apk add -t build-dependencies \
build-base \
icu-dev \
libidn-dev \
libtool \
postgresql-dev \
protobuf-dev \
python \
&& apk add \
ca-certificates \
ffmpeg \
file \
git \
icu-libs \
imagemagick \
libidn \
libpq \
nodejs-npm \
nodejs \
protobuf \
su-exec \
tini \
yarn \
&& update-ca-certificates \
&& wget -O libiconv.tar.gz "http://ftp.gnu.org/pub/gnu/libiconv/libiconv-$LIBICONV_VERSION.tar.gz" \
&& echo "$LIBICONV_DOWNLOAD_SHA256 *libiconv.tar.gz" | sha256sum -c - \
&& mkdir -p /tmp/src \
&& tar -xzf libiconv.tar.gz -C /tmp/src \
&& rm libiconv.tar.gz \
&& cd /tmp/src/libiconv-$LIBICONV_VERSION \
&& ./configure --prefix=/usr/local \
&& make -j$(getconf _NPROCESSORS_ONLN)\
&& make install \
&& libtool --finish /usr/local/lib \
&& cd /mastodon \
&& rm -rf /tmp/* /var/cache/apk/*
COPY Gemfile Gemfile.lock package.json yarn.lock /mastodon/ COPY Gemfile Gemfile.lock package.json yarn.lock /mastodon/
RUN bundle config build.nokogiri --with-iconv-lib=/usr/local/lib --with-iconv-include=/usr/local/include \ RUN echo "@edge https://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories \
&& bundle install -j$(getconf _NPROCESSORS_ONLN) --deployment --without test development \ && BUILD_DEPS=" \
&& yarn --ignore-optional --pure-lockfile postgresql-dev \
libxml2-dev \
libxslt-dev \
python \
build-base" \
&& apk -U upgrade && apk add \
$BUILD_DEPS \
nodejs@edge \
nodejs-npm@edge \
libpq \
libxml2 \
libxslt \
ffmpeg \
file \
imagemagick@edge \
ca-certificates \
&& npm install -g npm@3 && npm install -g yarn \
&& bundle install --deployment --without test development \
&& yarn --ignore-optional \
&& yarn cache clean \
&& npm -g cache clean \
&& update-ca-certificates \
&& apk del $BUILD_DEPS \
&& rm -rf /tmp/* /var/cache/apk/*
COPY . /mastodon COPY . /mastodon
COPY docker_entrypoint.sh /usr/local/bin/run VOLUME /mastodon/public/system /mastodon/public/assets
RUN chmod +x /usr/local/bin/run
VOLUME /mastodon/public/system /mastodon/public/assets /mastodon/public/packs
ENTRYPOINT ["/usr/local/bin/run"]

172
Gemfile
View File

@@ -3,116 +3,104 @@
source 'https://rubygems.org' source 'https://rubygems.org'
ruby '>= 2.3.0', '< 2.5.0' ruby '>= 2.3.0', '< 2.5.0'
gem 'pkg-config', '~> 1.2' gem 'pkg-config'
gem 'puma', '~> 3.10' gem 'rails', '~> 5.0.2'
gem 'rails', '~> 5.1.4' gem 'sass-rails', '~> 5.0'
gem 'uglifier', '~> 3.2' gem 'uglifier', '>= 1.3.0'
gem 'jquery-rails'
gem 'puma'
gem 'hamlit-rails', '~> 0.2' gem 'hamlit-rails'
gem 'pg', '~> 0.20' gem 'pg'
gem 'pghero', '~> 1.7' gem 'pghero'
gem 'dotenv-rails', '~> 2.2' gem 'dotenv-rails'
gem 'font-awesome-rails'
gem 'best_in_place', '~> 3.0.1'
gem 'aws-sdk', '~> 2.9'
gem 'fog-openstack', '~> 0.1'
gem 'paperclip', '~> 5.1' gem 'paperclip', '~> 5.1'
gem 'paperclip-av-transcoder', '~> 0.6' gem 'paperclip-av-transcoder'
gem 'aws-sdk', '>= 2.0'
gem 'active_model_serializers', '~> 0.10' gem 'addressable'
gem 'addressable', '~> 2.5' gem 'devise'
gem 'bootsnap' gem 'devise-two-factor'
gem 'browser' gem 'doorkeeper'
gem 'charlock_holmes', '~> 0.7.5' gem 'fast_blank'
gem 'iso-639' gem 'goldfinger'
gem 'cld3', '~> 3.2.0' gem 'hiredis'
gem 'devise', '~> 4.2' gem 'htmlentities'
gem 'devise-two-factor', '~> 3.0' gem 'http'
gem 'doorkeeper', '~> 4.2' gem 'http_accept_language'
gem 'fast_blank', '~> 1.0' gem 'httplog'
gem 'goldfinger', '~> 2.0' gem 'kaminari'
gem 'hiredis', '~> 0.6' gem 'link_header'
gem 'redis-namespace', '~> 1.5' gem 'local_time'
gem 'htmlentities', '~> 4.3' gem 'nokogiri'
gem 'http', '~> 2.2' gem 'oj'
gem 'http_accept_language', '~> 2.1'
gem 'httplog', '~> 0.99'
gem 'idn-ruby', require: 'idn'
gem 'kaminari', '~> 1.0'
gem 'link_header', '~> 0.0'
gem 'mime-types', '~> 3.1'
gem 'nokogiri', '~> 1.7'
gem 'oj', '~> 3.0'
gem 'ostatus2', '~> 2.0' gem 'ostatus2', '~> 2.0'
gem 'ox', '~> 2.5' gem 'ox'
gem 'pundit', '~> 1.1' gem 'rabl'
gem 'rabl', '~> 0.13' gem 'rack-attack'
gem 'rack-attack', '~> 5.0' gem 'rack-cors', require: 'rack/cors'
gem 'rack-cors', '~> 0.4', require: 'rack/cors' gem 'rack-timeout'
gem 'rack-timeout', '~> 0.4' gem 'rails-i18n'
gem 'rails-i18n', '~> 5.0' gem 'rails-settings-cached'
gem 'rails-settings-cached', '~> 0.6' gem 'redis', '~>3.2', require: ['redis', 'redis/connection/hiredis']
gem 'redis', '~> 3.3', require: ['redis', 'redis/connection/hiredis'] gem 'rqrcode'
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock' gem 'ruby-oembed', require: 'oembed'
gem 'rqrcode', '~> 0.10' gem 'sanitize'
gem 'ruby-oembed', '~> 0.12', require: 'oembed' gem 'sidekiq'
gem 'sanitize', '~> 4.4' gem 'sidekiq-scheduler'
gem 'sidekiq', '~> 5.0' gem 'sidekiq-unique-jobs'
gem 'sidekiq-scheduler', '~> 2.1' gem 'simple-navigation'
gem 'sidekiq-unique-jobs', '~> 5.0' gem 'simple_form'
gem 'sidekiq-bulk', '~>0.1.1' gem 'sprockets-rails', require: 'sprockets/railtie'
gem 'simple-navigation', '~> 4.0' gem 'statsd-instrument'
gem 'simple_form', '~> 3.4' gem 'twitter-text'
gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie' gem 'tzinfo-data'
gem 'statsd-instrument', '~> 2.1' gem 'whatlanguage'
gem 'twitter-text', '~> 1.14'
gem 'tzinfo-data', '~> 1.2017'
gem 'webpacker', '~> 2.0'
gem 'webpush'
gem 'json-ld-preloaded', '~> 2.2.1' gem 'react-rails'
gem 'rdf-normalize', '~> 0.3.1' gem 'browserify-rails'
gem 'autoprefixer-rails'
group :development, :test do group :development, :test do
gem 'fabrication', '~> 2.16' gem 'rspec-rails'
gem 'fuubar', '~> 2.2' gem 'pry-rails'
gem 'i18n-tasks', '~> 0.9', require: false gem 'fuubar'
gem 'pry-rails', '~> 0.3' gem 'fabrication'
gem 'rspec-rails', '~> 3.6' gem 'i18n-tasks', '~> 0.9.6'
end end
group :test do group :test do
gem 'capybara', '~> 2.14' gem 'capybara'
gem 'climate_control', '~> 0.2' gem 'faker'
gem 'faker', '~> 1.7' gem 'microformats2'
gem 'microformats', '~> 4.0' gem 'rails-controller-testing'
gem 'rails-controller-testing', '~> 1.0' gem 'rspec-sidekiq'
gem 'rspec-sidekiq', '~> 3.0' gem 'simplecov', require: false
gem 'simplecov', '~> 0.14', require: false gem 'webmock'
gem 'webmock', '~> 3.0'
gem 'parallel_tests', '~> 2.14'
end end
group :development do group :development do
gem 'active_record_query_trace', '~> 1.5'
gem 'annotate', '~> 2.7'
gem 'better_errors', '~> 2.1'
gem 'binding_of_caller', '~> 0.7'
gem 'bullet', '~> 5.5'
gem 'letter_opener', '~> 1.4'
gem 'letter_opener_web', '~> 1.3'
gem 'rubocop', require: false gem 'rubocop', require: false
gem 'brakeman', '~> 3.6', require: false gem 'better_errors'
gem 'bundler-audit', '~> 0.5', require: false gem 'binding_of_caller'
gem 'scss_lint', '~> 0.53', require: false gem 'letter_opener'
gem 'letter_opener_web'
gem 'bullet'
gem 'active_record_query_trace'
gem 'capistrano', '~> 3.8' gem 'capistrano', '3.8.0'
gem 'capistrano-rails', '~> 1.2' gem 'capistrano-rails'
gem 'capistrano-rbenv', '~> 2.1' gem 'capistrano-rbenv'
gem 'capistrano-yarn', '~> 2.0' gem 'capistrano-yarn'
gem 'capistrano-faster-assets', '~> 1.0'
end end
group :production do group :production do
gem 'lograge', '~> 0.5' gem 'rails_12factor'
gem 'redis-rails', '~> 5.0' gem 'redis-rails'
gem 'lograge'
end end

View File

@@ -1,89 +1,87 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (5.1.4) actioncable (5.0.2)
actionpack (= 5.1.4) actionpack (= 5.0.2)
nio4r (~> 2.0) nio4r (>= 1.2, < 3.0)
websocket-driver (~> 0.6.1) websocket-driver (~> 0.6.1)
actionmailer (5.1.4) actionmailer (5.0.2)
actionpack (= 5.1.4) actionpack (= 5.0.2)
actionview (= 5.1.4) actionview (= 5.0.2)
activejob (= 5.1.4) activejob (= 5.0.2)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
actionpack (5.1.4) actionpack (5.0.2)
actionview (= 5.1.4) actionview (= 5.0.2)
activesupport (= 5.1.4) activesupport (= 5.0.2)
rack (~> 2.0) rack (~> 2.0)
rack-test (>= 0.6.3) rack-test (~> 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2) rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (5.1.4) actionview (5.0.2)
activesupport (= 5.1.4) activesupport (= 5.0.2)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubis (~> 2.7.0)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3) rails-html-sanitizer (~> 1.0, >= 1.0.3)
active_model_serializers (0.10.6)
actionpack (>= 4.1, < 6)
activemodel (>= 4.1, < 6)
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.2)
active_record_query_trace (1.5.4) active_record_query_trace (1.5.4)
activejob (5.1.4) activejob (5.0.2)
activesupport (= 5.1.4) activesupport (= 5.0.2)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (5.1.4) activemodel (5.0.2)
activesupport (= 5.1.4) activesupport (= 5.0.2)
activerecord (5.1.4) activerecord (5.0.2)
activemodel (= 5.1.4) activemodel (= 5.0.2)
activesupport (= 5.1.4) activesupport (= 5.0.2)
arel (~> 8.0) arel (~> 7.0)
activesupport (5.1.4) activesupport (5.0.2)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (~> 0.7) i18n (~> 0.7)
minitest (~> 5.1) minitest (~> 5.1)
tzinfo (~> 1.1) tzinfo (~> 1.1)
addressable (2.5.2) addressable (2.5.1)
public_suffix (>= 2.0.2, < 4.0) public_suffix (~> 2.0, >= 2.0.2)
airbrussh (1.3.0) airbrussh (1.2.0)
sshkit (>= 1.6.1, != 1.7.0) sshkit (>= 1.6.1, != 1.7.0)
annotate (2.7.2) arel (7.1.4)
activerecord (>= 3.2, < 6.0)
rake (>= 10.4, < 13.0)
arel (8.0.0)
ast (2.3.0) ast (2.3.0)
attr_encrypted (3.0.3) attr_encrypted (3.0.3)
encryptor (~> 3.0.0) encryptor (~> 3.0.0)
autoprefixer-rails (6.7.7.2)
execjs
av (0.9.0) av (0.9.0)
cocaine (~> 0.5.3) cocaine (~> 0.5.3)
aws-sdk (2.10.21) aws-sdk (2.9.12)
aws-sdk-resources (= 2.10.21) aws-sdk-resources (= 2.9.12)
aws-sdk-core (2.10.21) aws-sdk-core (2.9.12)
aws-sigv4 (~> 1.0) aws-sigv4 (~> 1.0)
jmespath (~> 1.0) jmespath (~> 1.0)
aws-sdk-resources (2.10.21) aws-sdk-resources (2.9.12)
aws-sdk-core (= 2.10.21) aws-sdk-core (= 2.9.12)
aws-sigv4 (1.0.1) aws-sigv4 (1.0.0)
babel-source (5.8.35)
babel-transpiler (0.7.0)
babel-source (>= 4.0, < 6)
execjs (~> 2.0)
bcrypt (3.1.11) bcrypt (3.1.11)
best_in_place (3.0.3)
actionpack (>= 3.2)
railties (>= 3.2)
better_errors (2.1.1) better_errors (2.1.1)
coderay (>= 1.0.0) coderay (>= 1.0.0)
erubis (>= 2.6.6) erubis (>= 2.6.6)
rack (>= 0.9.0) rack (>= 0.9.0)
binding_of_caller (0.7.2) binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1) debug_inspector (>= 0.0.1)
bootsnap (1.1.2) browserify-rails (4.1.0)
msgpack (~> 1.0) addressable (>= 2.4.0)
brakeman (3.7.2) railties (>= 4.0.0, < 5.1)
browser (2.4.0) sprockets (>= 3.6.0)
builder (3.2.3) builder (3.2.3)
bullet (5.5.1) bullet (5.5.1)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
uniform_notifier (~> 1.10.0) uniform_notifier (~> 1.10.0)
bundler-audit (0.6.0) capistrano (3.8.0)
bundler (~> 1.2)
thor (~> 0.18)
capistrano (3.8.2)
airbrussh (>= 1.0.0) airbrussh (>= 1.0.0)
i18n i18n
rake (>= 10.0.0) rake (>= 10.0.0)
@@ -91,7 +89,9 @@ GEM
capistrano-bundler (1.2.0) capistrano-bundler (1.2.0)
capistrano (~> 3.1) capistrano (~> 3.1)
sshkit (~> 1.2) sshkit (~> 1.2)
capistrano-rails (1.3.0) capistrano-faster-assets (1.0.2)
capistrano (>= 3.1)
capistrano-rails (1.2.3)
capistrano (~> 3.1) capistrano (~> 3.1)
capistrano-bundler (~> 1.1) capistrano-bundler (~> 1.1)
capistrano-rbenv (2.1.1) capistrano-rbenv (2.1.1)
@@ -99,34 +99,36 @@ GEM
sshkit (~> 1.3) sshkit (~> 1.3)
capistrano-yarn (2.0.2) capistrano-yarn (2.0.2)
capistrano (~> 3.0) capistrano (~> 3.0)
capybara (2.14.4) capybara (2.13.0)
addressable addressable
mime-types (>= 1.16) mime-types (>= 1.16)
nokogiri (>= 1.3.3) nokogiri (>= 1.3.3)
rack (>= 1.0.0) rack (>= 1.0.0)
rack-test (>= 0.5.4) rack-test (>= 0.5.4)
xpath (~> 2.0) xpath (~> 2.0)
case_transform (0.2)
activesupport
charlock_holmes (0.7.5)
chunky_png (1.3.8) chunky_png (1.3.8)
cld3 (3.2.0) climate_control (0.1.0)
ffi (>= 1.1.0, < 1.10.0)
climate_control (0.2.0)
cocaine (0.5.8) cocaine (0.5.8)
climate_control (>= 0.0.3, < 1.0) climate_control (>= 0.0.3, < 1.0)
coderay (1.1.1) coderay (1.1.1)
coffee-rails (4.2.1)
coffee-script (>= 2.2.0)
railties (>= 4.0.0, < 5.2.x)
coffee-script (2.4.1)
coffee-script-source
execjs
coffee-script-source (1.12.2)
colorize (0.8.1) colorize (0.8.1)
concurrent-ruby (1.0.5) concurrent-ruby (1.0.5)
connection_pool (2.2.1) connection_pool (2.2.1)
crack (0.4.3) crack (0.4.3)
safe_yaml (~> 1.0.0) safe_yaml (~> 1.0.0)
crass (1.0.2) crass (1.0.2)
debug_inspector (0.0.3) debug_inspector (0.0.2)
devise (4.3.0) devise (4.2.1)
bcrypt (~> 3.0) bcrypt (~> 3.0)
orm_adapter (~> 0.1) orm_adapter (~> 0.1)
railties (>= 4.1.0, < 5.2) railties (>= 4.1.0, < 5.1)
responders responders
warden (~> 1.2.3) warden (~> 1.2.3)
devise-two-factor (3.0.0) devise-two-factor (3.0.0)
@@ -139,51 +141,37 @@ GEM
docile (1.1.5) docile (1.1.5)
domain_name (0.5.20170404) domain_name (0.5.20170404)
unf (>= 0.0.5, < 1.0.0) unf (>= 0.0.5, < 1.0.0)
doorkeeper (4.2.6) doorkeeper (4.2.5)
railties (>= 4.2) railties (>= 4.2)
dotenv (2.2.1) dotenv (2.2.0)
dotenv-rails (2.2.1) dotenv-rails (2.2.0)
dotenv (= 2.2.1) dotenv (= 2.2.0)
railties (>= 3.2, < 5.2) railties (>= 3.2, < 5.1)
easy_translate (0.5.0) easy_translate (0.5.0)
json json
thread thread
thread_safe thread_safe
encryptor (3.0.0) encryptor (3.0.0)
erubi (1.6.1)
erubis (2.7.0) erubis (2.7.0)
et-orbi (1.0.5) et-orbi (1.0.3)
tzinfo tzinfo
excon (0.58.0)
execjs (2.7.0) execjs (2.7.0)
fabrication (2.16.2) fabrication (2.16.1)
faker (1.7.3) faker (1.7.3)
i18n (~> 0.5) i18n (~> 0.5)
fast_blank (1.0.0) fast_blank (1.0.0)
ffi (1.9.18) font-awesome-rails (4.7.0.1)
fog-core (1.45.0) railties (>= 3.2, < 5.1)
builder
excon (~> 0.58)
formatador (~> 0.2)
fog-json (1.0.2)
fog-core (~> 1.0)
multi_json (~> 1.10)
fog-openstack (0.1.21)
fog-core (>= 1.40)
fog-json (>= 1.0)
ipaddress (>= 0.8)
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.0)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
goldfinger (2.0.1) goldfinger (1.2.0)
addressable (~> 2.5) addressable (~> 2.4)
http (~> 2.2) http (~> 2.0)
nokogiri (~> 1.8) nokogiri (~> 1.6)
oj (~> 3.0) hamlit (2.8.1)
hamlit (2.8.4)
temple (>= 0.8.0) temple (>= 0.8.0)
thor thor
tilt tilt
@@ -192,12 +180,9 @@ GEM
activesupport (>= 4.0.1) activesupport (>= 4.0.1)
hamlit (>= 1.2.0) hamlit (>= 1.2.0)
railties (>= 4.0.1) railties (>= 4.0.1)
hamster (3.0.0) hashdiff (0.3.2)
concurrent-ruby (~> 1.0)
hashdiff (0.3.5)
highline (1.7.8) highline (1.7.8)
hiredis (0.6.1) hiredis (0.6.1)
hkdf (0.3.0)
htmlentities (4.3.4) htmlentities (4.3.4)
http (2.2.2) http (2.2.2)
addressable (~> 2.3) addressable (~> 2.3)
@@ -206,14 +191,14 @@ GEM
http_parser.rb (~> 0.6.0) http_parser.rb (~> 0.6.0)
http-cookie (1.0.3) http-cookie (1.0.3)
domain_name (~> 0.5) domain_name (~> 0.5)
http-form_data (1.0.3) http-form_data (1.0.1)
http_accept_language (2.1.1) http_accept_language (2.1.0)
http_parser.rb (0.6.0) http_parser.rb (0.6.0)
httplog (0.99.7) httplog (0.99.3)
colorize colorize
rack rack
i18n (0.8.6) i18n (0.8.1)
i18n-tasks (0.9.16) i18n-tasks (0.9.13)
activesupport (>= 4.0.2) activesupport (>= 4.0.2)
ast (>= 2.1.0) ast (>= 2.1.0)
easy_translate (>= 0.5.0) easy_translate (>= 0.5.0)
@@ -223,20 +208,12 @@ GEM
parser (>= 2.2.3.0) parser (>= 2.2.3.0)
rainbow (~> 2.2) rainbow (~> 2.2)
terminal-table (>= 1.5.1) terminal-table (>= 1.5.1)
idn-ruby (0.1.0)
ipaddress (0.8.3)
iso-639 (0.2.8)
jmespath (1.3.1) jmespath (1.3.1)
jquery-rails (4.3.1)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
json (2.1.0) json (2.1.0)
json-ld (2.1.5)
multi_json (~> 1.12)
rdf (~> 2.2)
json-ld-preloaded (2.2.1)
json-ld (~> 2.1, >= 2.1.5)
multi_json (~> 1.11)
rdf (~> 2.2)
jsonapi-renderer (0.1.3)
jwt (1.5.6)
kaminari (1.0.1) kaminari (1.0.1)
activesupport (>= 4.1.0) activesupport (>= 4.1.0)
kaminari-actionview (= 1.0.1) kaminari-actionview (= 1.0.1)
@@ -258,45 +235,44 @@ 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.5.1) local_time (1.0.3)
actionpack (>= 4, < 5.2) coffee-rails
activesupport (>= 4, < 5.2) lograge (0.4.1)
railties (>= 4, < 5.2) actionpack (>= 4, < 5.1)
activesupport (>= 4, < 5.1)
railties (>= 4, < 5.1)
loofah (2.0.3) loofah (2.0.3)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
mail (2.6.6) mail (2.6.5)
mime-types (>= 1.16, < 4) mime-types (>= 1.16, < 4)
mario-redis-lock (1.2.0)
redis (~> 3, >= 3.0.5)
method_source (0.8.2) method_source (0.8.2)
microformats (4.0.7) microformats2 (2.1.0)
activesupport
json json
nokogiri nokogiri
mime-types (3.1) mime-types (3.1)
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521) mime-types-data (3.2016.0521)
mimemagic (0.3.2) mimemagic (0.3.2)
mini_portile2 (2.2.0) mini_portile2 (2.1.0)
minitest (5.10.3) minitest (5.10.1)
msgpack (1.1.0)
multi_json (1.12.1)
net-scp (1.2.1) net-scp (1.2.1)
net-ssh (>= 2.6.5) net-ssh (>= 2.6.5)
net-ssh (4.1.0) net-ssh (4.1.0)
nio4r (2.1.0) nio4r (2.0.0)
nokogiri (1.8.0) nokogiri (1.7.1)
mini_portile2 (~> 2.2.0) mini_portile2 (~> 2.1.0)
nokogumbo (1.4.13) nokogumbo (1.4.10)
nokogiri nokogiri
oj (3.3.4) oj (3.0.2)
openssl (2.0.4) openssl (2.0.3)
orm_adapter (0.5.0) orm_adapter (0.5.0)
ostatus2 (2.0.1) ostatus2 (2.0.0)
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.5.0) ox (2.4.13)
paperclip (5.1.0) paperclip (5.1.0)
activemodel (>= 4.2.0) activemodel (>= 4.2.0)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
@@ -306,15 +282,12 @@ GEM
paperclip-av-transcoder (0.6.4) paperclip-av-transcoder (0.6.4)
av (~> 0.9.0) av (~> 0.9.0)
paperclip (>= 2.5.2) paperclip (>= 2.5.2)
parallel (1.11.2)
parallel_tests (2.14.2)
parallel
parser (2.4.0.0) parser (2.4.0.0)
ast (~> 2.2) ast (~> 2.2)
pg (0.21.0) pg (0.20.0)
pghero (1.7.0) pghero (1.6.5)
activerecord activerecord
pkg-config (1.2.4) pkg-config (1.2.0)
powerpack (0.1.1) powerpack (0.1.1)
pry (0.10.4) pry (0.10.4)
coderay (~> 1.1.0) coderay (~> 1.1.0)
@@ -322,71 +295,73 @@ GEM
slop (~> 3.4) 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 (2.0.5)
puma (3.10.0) puma (3.8.2)
pundit (1.1.0)
activesupport (>= 3.0.0)
rabl (0.13.1) rabl (0.13.1)
activesupport (>= 2.3.14) activesupport (>= 2.3.14)
rack (2.0.3) rack (2.0.1)
rack-attack (5.0.1) rack-attack (5.0.1)
rack rack
rack-cors (0.4.1) rack-cors (0.4.1)
rack-protection (2.0.0) rack-protection (1.5.3)
rack rack
rack-test (0.7.0) rack-test (0.6.3)
rack (>= 1.0, < 3) rack (>= 1.0)
rack-timeout (0.4.2) rack-timeout (0.4.2)
rails (5.1.4) rails (5.0.2)
actioncable (= 5.1.4) actioncable (= 5.0.2)
actionmailer (= 5.1.4) actionmailer (= 5.0.2)
actionpack (= 5.1.4) actionpack (= 5.0.2)
actionview (= 5.1.4) actionview (= 5.0.2)
activejob (= 5.1.4) activejob (= 5.0.2)
activemodel (= 5.1.4) activemodel (= 5.0.2)
activerecord (= 5.1.4) activerecord (= 5.0.2)
activesupport (= 5.1.4) activesupport (= 5.0.2)
bundler (>= 1.3.0) bundler (>= 1.3.0, < 2.0)
railties (= 5.1.4) railties (= 5.0.2)
sprockets-rails (>= 2.0.0) sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.2) rails-controller-testing (1.0.1)
actionpack (~> 5.x, >= 5.0.1) actionpack (~> 5.x)
actionview (~> 5.x, >= 5.0.1) actionview (~> 5.x)
activesupport (~> 5.x) activesupport (~> 5.x)
rails-dom-testing (2.0.3) rails-dom-testing (2.0.2)
activesupport (>= 4.2.0) activesupport (>= 4.2.0, < 6.0)
nokogiri (>= 1.6) nokogiri (~> 1.6)
rails-html-sanitizer (1.0.3) rails-html-sanitizer (1.0.3)
loofah (~> 2.0) loofah (~> 2.0)
rails-i18n (5.0.4) rails-i18n (5.0.3)
i18n (~> 0.7) i18n (~> 0.7)
railties (~> 5.0) railties (~> 5.0)
rails-settings-cached (0.6.6) rails-settings-cached (0.6.5)
rails (>= 4.2.0) rails (>= 4.2.0)
railties (5.1.4) rails_12factor (0.0.3)
actionpack (= 5.1.4) rails_serve_static_assets
activesupport (= 5.1.4) rails_stdout_logging
rails_serve_static_assets (0.0.5)
rails_stdout_logging (0.0.5)
railties (5.0.2)
actionpack (= 5.0.2)
activesupport (= 5.0.2)
method_source method_source
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0) thor (>= 0.18.1, < 2.0)
rainbow (2.2.2) rainbow (2.2.2)
rake rake
rake (12.0.0) rake (12.0.0)
rdf (2.2.8) react-rails (1.11.0)
hamster (~> 3.0) babel-transpiler (>= 0.7.0)
link_header (~> 0.0, >= 0.0.8) connection_pool
rdf-normalize (0.3.2) execjs
rdf (~> 2.0) railties (>= 3.2)
tilt
redis (3.3.3) redis (3.3.3)
redis-actionpack (5.0.1) redis-actionpack (5.0.1)
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, < 1.4.0)
redis-activesupport (5.0.3) redis-activesupport (5.0.2)
activesupport (>= 3, < 6) activesupport (>= 3, < 6)
redis-store (~> 1.3.0) redis-store (~> 1.3.0)
redis-namespace (1.5.3)
redis (~> 3.0, >= 3.0.4)
redis-rack (2.0.2) redis-rack (2.0.2)
rack (>= 1.5, < 3) rack (>= 1.5, < 3)
redis-store (>= 1.2, < 1.4) redis-store (>= 1.2, < 1.4)
@@ -396,34 +371,32 @@ GEM
redis-store (>= 1.2, < 2) redis-store (>= 1.2, < 2)
redis-store (1.3.0) redis-store (1.3.0)
redis (>= 2.2) redis (>= 2.2)
responders (2.4.0) responders (2.3.0)
actionpack (>= 4.2.0, < 5.3) railties (>= 4.2.0, < 5.1)
railties (>= 4.2.0, < 5.3)
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.5.4)
rspec-support (~> 3.6.0) rspec-support (~> 3.5.0)
rspec-expectations (3.6.0) rspec-expectations (3.5.0)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.6.0) rspec-support (~> 3.5.0)
rspec-mocks (3.6.0) rspec-mocks (3.5.0)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.6.0) rspec-support (~> 3.5.0)
rspec-rails (3.6.0) rspec-rails (3.5.2)
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.5.0)
rspec-expectations (~> 3.6.0) rspec-expectations (~> 3.5.0)
rspec-mocks (~> 3.6.0) rspec-mocks (~> 3.5.0)
rspec-support (~> 3.6.0) rspec-support (~> 3.5.0)
rspec-sidekiq (3.0.3) rspec-sidekiq (3.0.0)
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.5.0)
rubocop (0.49.1) rubocop (0.48.1)
parallel (~> 1.10)
parser (>= 2.3.3.1, < 3.0) parser (>= 2.3.3.1, < 3.0)
powerpack (~> 0.1) powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0) rainbow (>= 1.99.1, < 3.0)
@@ -431,63 +404,63 @@ GEM
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.1) ruby-progressbar (1.8.1)
rufus-scheduler (3.4.2) rufus-scheduler (3.4.0)
et-orbi (~> 1.0) et-orbi (~> 1.0)
safe_yaml (1.0.4) safe_yaml (1.0.4)
sanitize (4.5.0) sanitize (4.4.0)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.4.4) nokogiri (>= 1.4.4)
nokogumbo (~> 1.4.1) nokogumbo (~> 1.4.1)
sass (3.4.24) sass (3.4.23)
scss_lint (0.54.0) sass-rails (5.0.6)
rake (>= 0.9, < 13) railties (>= 4.0.0, < 6)
sass (~> 3.4.20) sass (~> 3.1)
sidekiq (5.0.4) sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0)
tilt (>= 1.1, < 3)
sidekiq (4.2.10)
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.2, >= 3.2.1)
sidekiq-bulk (0.1.1) sidekiq-scheduler (2.1.4)
activesupport
sidekiq
sidekiq-scheduler (2.1.8)
redis (~> 3) redis (~> 3)
rufus-scheduler (~> 3.2) rufus-scheduler (~> 3.2)
sidekiq (>= 3) sidekiq (>= 3)
tilt (>= 1.4.0) tilt (>= 1.4.0)
sidekiq-unique-jobs (5.0.9) sidekiq-unique-jobs (5.0.0)
sidekiq (>= 4.0, <= 6.0) sidekiq (>= 4.0)
thor (~> 0) thor
simple-navigation (4.0.5) simple-navigation (4.0.5)
activesupport (>= 2.3.2) activesupport (>= 2.3.2)
simple_form (3.5.0) simple_form (3.4.0)
actionpack (> 4, < 5.2) actionpack (> 4, < 5.1)
activemodel (> 4, < 5.2) activemodel (> 4, < 5.1)
simplecov (0.14.1) simplecov (0.14.1)
docile (~> 1.1.0) docile (~> 1.1.0)
json (>= 1.8, < 3) json (>= 1.8, < 3)
simplecov-html (~> 0.10.0) simplecov-html (~> 0.10.0)
simplecov-html (0.10.1) simplecov-html (0.10.0)
slop (3.6.0) 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)
sprockets-rails (3.2.1) sprockets-rails (3.2.0)
actionpack (>= 4.0) actionpack (>= 4.0)
activesupport (>= 4.0) activesupport (>= 4.0)
sprockets (>= 3.0.0) sprockets (>= 3.0.0)
sshkit (1.13.1) sshkit (1.13.1)
net-scp (>= 1.1.2) net-scp (>= 1.1.2)
net-ssh (>= 2.8.0) net-ssh (>= 2.8.0)
statsd-instrument (2.1.4) statsd-instrument (2.1.2)
temple (0.8.0) temple (0.8.0)
terminal-table (1.8.0) terminal-table (1.7.3)
unicode-display_width (~> 1.1, >= 1.1.1) unicode-display_width (~> 1.1.1)
thor (0.20.0) thor (0.19.4)
thread (0.2.2) thread (0.2.2)
thread_safe (0.3.6) thread_safe (0.3.6)
tilt (2.0.8) tilt (2.0.7)
twitter-text (1.14.7) twitter-text (1.14.5)
unf (~> 0.1.0) unf (~> 0.1.0)
tzinfo (1.2.3) tzinfo (1.2.3)
thread_safe (~> 0.1) thread_safe (~> 0.1)
@@ -498,7 +471,7 @@ GEM
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.7.4) unf_ext (0.0.7.4)
unicode-display_width (1.3.0) unicode-display_width (1.1.3)
uniform_notifier (1.10.0) uniform_notifier (1.10.0)
warden (1.2.7) warden (1.2.7)
rack (>= 1.0) rack (>= 1.0)
@@ -506,121 +479,103 @@ GEM
addressable (>= 2.3.6) addressable (>= 2.3.6)
crack (>= 0.3.2) crack (>= 0.3.2)
hashdiff hashdiff
webpacker (2.0)
activesupport (>= 4.2)
multi_json (~> 1.2)
railties (>= 4.2)
webpush (0.3.2)
hkdf (~> 0.2)
jwt
websocket-driver (0.6.5) websocket-driver (0.6.5)
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.2) websocket-extensions (0.1.2)
xpath (2.1.0) whatlanguage (1.0.6)
xpath (2.0.0)
nokogiri (~> 1.3) nokogiri (~> 1.3)
PLATFORMS PLATFORMS
ruby ruby
DEPENDENCIES DEPENDENCIES
active_model_serializers (~> 0.10) active_record_query_trace
active_record_query_trace (~> 1.5) addressable
addressable (~> 2.5) autoprefixer-rails
annotate (~> 2.7) aws-sdk (>= 2.0)
aws-sdk (~> 2.9) best_in_place (~> 3.0.1)
better_errors (~> 2.1) better_errors
binding_of_caller (~> 0.7) binding_of_caller
bootsnap browserify-rails
brakeman (~> 3.6) bullet
browser capistrano (= 3.8.0)
bullet (~> 5.5) capistrano-faster-assets (~> 1.0)
bundler-audit (~> 0.5) capistrano-rails
capistrano (~> 3.8) capistrano-rbenv
capistrano-rails (~> 1.2) capistrano-yarn
capistrano-rbenv (~> 2.1) capybara
capistrano-yarn (~> 2.0) devise
capybara (~> 2.14) devise-two-factor
charlock_holmes (~> 0.7.5) doorkeeper
cld3 (~> 3.2.0) dotenv-rails
climate_control (~> 0.2) fabrication
devise (~> 4.2) faker
devise-two-factor (~> 3.0) fast_blank
doorkeeper (~> 4.2) font-awesome-rails
dotenv-rails (~> 2.2) fuubar
fabrication (~> 2.16) goldfinger
faker (~> 1.7) hamlit-rails
fast_blank (~> 1.0) hiredis
fog-openstack (~> 0.1) htmlentities
fuubar (~> 2.2) http
goldfinger (~> 2.0) http_accept_language
hamlit-rails (~> 0.2) httplog
hiredis (~> 0.6) i18n-tasks (~> 0.9.6)
htmlentities (~> 4.3) jquery-rails
http (~> 2.2) kaminari
http_accept_language (~> 2.1) letter_opener
httplog (~> 0.99) letter_opener_web
i18n-tasks (~> 0.9) link_header
idn-ruby local_time
iso-639 lograge
json-ld-preloaded (~> 2.2.1) microformats2
kaminari (~> 1.0) nokogiri
letter_opener (~> 1.4) oj
letter_opener_web (~> 1.3)
link_header (~> 0.0)
lograge (~> 0.5)
mario-redis-lock (~> 1.2)
microformats (~> 4.0)
mime-types (~> 3.1)
nokogiri (~> 1.7)
oj (~> 3.0)
ostatus2 (~> 2.0) ostatus2 (~> 2.0)
ox (~> 2.5) ox
paperclip (~> 5.1) paperclip (~> 5.1)
paperclip-av-transcoder (~> 0.6) paperclip-av-transcoder
parallel_tests (~> 2.14) pg
pg (~> 0.20) pghero
pghero (~> 1.7) pkg-config
pkg-config (~> 1.2) pry-rails
pry-rails (~> 0.3) puma
puma (~> 3.10) rabl
pundit (~> 1.1) rack-attack
rabl (~> 0.13) rack-cors
rack-attack (~> 5.0) rack-timeout
rack-cors (~> 0.4) rails (~> 5.0.2)
rack-timeout (~> 0.4) rails-controller-testing
rails (~> 5.1.4) rails-i18n
rails-controller-testing (~> 1.0) rails-settings-cached
rails-i18n (~> 5.0) rails_12factor
rails-settings-cached (~> 0.6) react-rails
rdf-normalize (~> 0.3.1) redis (~> 3.2)
redis (~> 3.3) redis-rails
redis-namespace (~> 1.5) rqrcode
redis-rails (~> 5.0) rspec-rails
rqrcode (~> 0.10) rspec-sidekiq
rspec-rails (~> 3.6)
rspec-sidekiq (~> 3.0)
rubocop rubocop
ruby-oembed (~> 0.12) ruby-oembed
sanitize (~> 4.4) sanitize
scss_lint (~> 0.53) sass-rails (~> 5.0)
sidekiq (~> 5.0) sidekiq
sidekiq-bulk (~> 0.1.1) sidekiq-scheduler
sidekiq-scheduler (~> 2.1) sidekiq-unique-jobs
sidekiq-unique-jobs (~> 5.0) simple-navigation
simple-navigation (~> 4.0) simple_form
simple_form (~> 3.4) simplecov
simplecov (~> 0.14) sprockets-rails
sprockets-rails (~> 3.2) statsd-instrument
statsd-instrument (~> 2.1) twitter-text
twitter-text (~> 1.14) tzinfo-data
tzinfo-data (~> 1.2017) uglifier (>= 1.3.0)
uglifier (~> 3.2) webmock
webmock (~> 3.0) whatlanguage
webpacker (~> 2.0)
webpush
RUBY VERSION RUBY VERSION
ruby 2.4.1p111 ruby 2.4.1p111
BUNDLED WITH BUNDLED WITH
1.15.4 1.14.6

View File

@@ -1,4 +0,0 @@
web: PORT=3000 bundle exec puma -C config/puma.rb
sidekiq: PORT=3000 bundle exec sidekiq
stream: PORT=4000 yarn run start
webpack: ./bin/webpack-dev-server --host 0.0.0.0

View File

@@ -1,4 +1,4 @@
![Mastodon](https://i.imgur.com/NhZc40l.png) Mastodon
======== ========
[![Build Status](http://img.shields.io/travis/tootsuite/mastodon.svg)][travis] [![Build Status](http://img.shields.io/travis/tootsuite/mastodon.svg)][travis]
@@ -9,11 +9,11 @@
Mastodon is a free, open-source social network server. A decentralized solution to commercial platforms, it avoids the risks of a single company monopolizing your communication. Anyone can run Mastodon and participate in the social network seamlessly. Mastodon is a free, open-source social network server. A decentralized solution to commercial platforms, it avoids the risks of a single company monopolizing your communication. Anyone can run Mastodon and participate in the social network seamlessly.
An alternative implementation of the GNU social project. Based on [ActivityStreams](https://en.wikipedia.org/wiki/Activity_Streams_(format)), [Webfinger](https://en.wikipedia.org/wiki/WebFinger), [WebSub](https://en.wikipedia.org/wiki/WebSub) and [Salmon](https://en.wikipedia.org/wiki/Salmon_(protocol)). An alternative implementation of the GNU social project. Based on [ActivityStreams](https://en.wikipedia.org/wiki/Activity_Streams_(format)), [Webfinger](https://en.wikipedia.org/wiki/WebFinger), [PubsubHubbub](https://en.wikipedia.org/wiki/PubSubHubbub) and [Salmon](https://en.wikipedia.org/wiki/Salmon_(protocol)).
Click on the screenshot to watch a demo of the UI: Click on the screenshot to watch a demo of the UI:
[![Screenshot](https://i.imgur.com/pG3Nnz3.jpg)][youtube_demo] [![Screenshot](https://i.imgur.com/T2q5V65.png)][youtube_demo]
[youtube_demo]: https://www.youtube.com/watch?v=YO1jQ8_rAMU [youtube_demo]: https://www.youtube.com/watch?v=YO1jQ8_rAMU
@@ -34,7 +34,7 @@ If you would like, you can [support the development of this project on Patreon][
## Features ## Features
- **Fully interoperable with GNU social and any OStatus platform** - **Fully interoperable with GNU social and any OStatus platform**
Whatever implements Atom feeds, ActivityStreams, Salmon, WebSub and Webfinger is part of the network Whatever implements Atom feeds, ActivityStreams, Salmon, PubSubHubbub and Webfinger is part of the network
- **Real-time timeline updates** - **Real-time timeline updates**
See the updates of people you're following appear in real-time in the UI via WebSockets See the updates of people you're following appear in real-time in the UI via WebSockets
- **Federated thread resolving** - **Federated thread resolving**
@@ -47,10 +47,6 @@ If you would like, you can [support the development of this project on Patreon][
Mastodon tries to be as fast and responsive as possible, so all long-running tasks that can be delegated to background processing, are Mastodon tries to be as fast and responsive as possible, so all long-running tasks that can be delegated to background processing, are
- **Deployable via Docker** - **Deployable via Docker**
You don't need to mess with dependencies and configuration if you want to try Mastodon, if you have Docker and Docker Compose the deployment is extremely easy You don't need to mess with dependencies and configuration if you want to try Mastodon, if you have Docker and Docker Compose the deployment is extremely easy
## Development
Please follow the [development guide](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Development-guide.md) from the documentation repository.
## Deployment ## Deployment

37
Vagrantfile vendored
View File

@@ -1,8 +1,6 @@
# -*- mode: ruby -*- # -*- mode: ruby -*-
# vi: set ft=ruby : # vi: set ft=ruby :
ENV["PORT"] ||= "3000"
$provision = <<SCRIPT $provision = <<SCRIPT
cd /vagrant # This is where the host folder/repo is mounted cd /vagrant # This is where the host folder/repo is mounted
@@ -12,10 +10,10 @@ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
sudo apt-add-repository 'deb https://dl.yarnpkg.com/debian/ stable main' sudo apt-add-repository 'deb https://dl.yarnpkg.com/debian/ stable main'
# Add repo for NodeJS # Add repo for NodeJS
curl -sL https://deb.nodesource.com/setup_6.x | sudo bash - curl -sL https://deb.nodesource.com/setup_4.x | sudo bash -
# Add firewall rule to redirect 80 to PORT and save # Add firewall rule to redirect 80 to 3000 and save
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port #{ENV["PORT"]} sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 3000
echo iptables-persistent iptables-persistent/autosave_v4 boolean true | sudo debconf-set-selections echo iptables-persistent iptables-persistent/autosave_v4 boolean true | sudo debconf-set-selections
echo iptables-persistent iptables-persistent/autosave_v6 boolean true | sudo debconf-set-selections echo iptables-persistent iptables-persistent/autosave_v6 boolean true | sudo debconf-set-selections
sudo apt-get install iptables-persistent -y sudo apt-get install iptables-persistent -y
@@ -33,45 +31,38 @@ sudo apt-get install \
redis-tools \ redis-tools \
postgresql \ postgresql \
postgresql-contrib \ postgresql-contrib \
protobuf-compiler \
yarn \ yarn \
libicu-dev \
libidn11-dev \
libprotobuf-dev \
libreadline-dev \ libreadline-dev \
-y -y
# Install rvm # Install rvm
cd /vagrant
read RUBY_VERSION < .ruby-version read RUBY_VERSION < .ruby-version
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
curl -sSL https://raw.githubusercontent.com/rvm/rvm/stable/binscripts/rvm-installer | bash -s stable --ruby=$RUBY_VERSION curl -sSL https://get.rvm.io | bash -s stable --ruby=$RUBY_VERSION
source /home/vagrant/.rvm/scripts/rvm source /home/vagrant/.rvm/scripts/rvm
# Install Ruby
rvm install ruby-$RUBY_VERSION
# Configure database # Configure database
sudo -u postgres createuser -U postgres vagrant -s sudo -u postgres createuser -U postgres vagrant -s
sudo -u postgres createdb -U postgres mastodon_development sudo -u postgres createdb -U postgres mastodon_development
# Install gems and node modules # Install gems and node modules
gem install bundler foreman gem install bundler
bundle install bundle install
yarn install yarn install
# Build Mastodon # Build Mastodon
export $(cat ".env.vagrant" | xargs) export $(cat ".env.vagrant" | xargs)
bundle exec rails db:setup bundle exec rails db:setup
bundle exec rails assets:precompile
# Configure automatic loading of environment variable
echo 'export $(cat "/vagrant/.env.vagrant" | xargs)' >> ~/.bash_profile
SCRIPT SCRIPT
$start = <<SCRIPT $start = <<SCRIPT
echo 'To start server' cd /vagrant
echo ' $ vagrant ssh -c "cd /vagrant && foreman start"' export $(cat ".env.vagrant" | xargs)
rails s -d -b 0.0.0.0
SCRIPT SCRIPT
@@ -83,7 +74,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", "1024"]
# 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
@@ -113,10 +104,8 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.synced_folder ".", "/vagrant" config.vm.synced_folder ".", "/vagrant"
end end
# Otherwise, you can access the site at http://localhost:3000 and http://localhost:4000 , http://localhost:8080 # Otherwise, you can access the site at http://localhost:3000
config.vm.network :forwarded_port, guest: 3000, host: 3000 config.vm.network :forwarded_port, guest: 80, host: 3000
config.vm.network :forwarded_port, guest: 4000, host: 4000
config.vm.network :forwarded_port, guest: 8080, host: 8080
# Full provisioning script, only runs on first 'vagrant up' or with 'vagrant provision' # Full provisioning script, only runs on first 'vagrant up' or with 'vagrant provision'
config.vm.provision :shell, inline: $provision, privileged: false config.vm.provision :shell, inline: $provision, privileged: false

View File

@@ -2,7 +2,7 @@
"name": "Mastodon", "name": "Mastodon",
"description": "A GNU Social-compatible microblogging server", "description": "A GNU Social-compatible microblogging server",
"repository": "https://github.com/tootsuite/mastodon", "repository": "https://github.com/tootsuite/mastodon",
"logo": "https://github.com/tootsuite.png", "logo": "https://github.com/tootsuite/mastodon/raw/master/app/assets/images/logo.png",
"env": { "env": {
"HEROKU": { "HEROKU": {
"description": "Leave this as true", "description": "Leave this as true",
@@ -94,9 +94,6 @@
} }
}, },
"buildpacks": [ "buildpacks": [
{
"url": "https://github.com/heroku/heroku-buildpack-apt"
},
{ {
"url": "heroku/nodejs" "url": "heroku/nodejs"
}, },

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 339 KiB

After

Width:  |  Height:  |  Size: 339 KiB

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

View File

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

BIN
app/assets/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" height="1000" width="1000"><path d="M500 0a500 500 0 0 0-353.553 146.447 500 500 0 1 0 707.106 707.106A500 500 0 0 0 500 0zm-.059 280.05h107.12c-19.071 13.424-26.187 51.016-27.12 73.843V562.05c0 44.32-35.68 80-80 80s-80-35.68-80-80v-202c0-44.32 35.68-80 80-80zm-.441 52c-15.464 0-28 12.537-28 28 0 15.465 12.536 28 28 28s28-12.535 28-28c0-15.463-12.536-28-28-28zm-279.059 7.9c44.32 0 80 35.68 80 80v206.157c.933 22.827 8.049 60.42 27.12 73.842H220.44c-44.32 0-80-35.68-80-80v-200c0-44.32 35.68-80 80-80zm559.12 0c44.32 0 80 35.68 80 80v200c0 44.32-35.68 80-80 80H672.44c19.071-13.424 26.187-51.016 27.12-73.843V419.95c0-44.32 35.68-80 80-80zM220 392c-15.464 0-28 12.536-28 28s12.536 28 28 28 28-12.536 28-28-12.536-28-28-28zm560 0c-15.464 0-28 12.536-28 28s12.536 28 28 28 28-12.536 28-28-12.536-28-28-28zm-280.5 40.05c-15.464 0-28 12.537-28 28 0 15.465 12.536 28 28 28s28-12.535 28-28c0-15.463-12.536-28-28-28zM220 491.95c-15.464 0-28 12.535-28 28 0 15.463 12.536 28 28 28s28-12.537 28-28c0-15.465-12.536-28-28-28zm560 0c-15.464 0-28 12.535-28 28 0 15.463 12.536 28 28 28s28-12.537 28-28c0-15.465-12.536-28-28-28zM499.5 532c-15.464 0-28 12.536-28 28s12.536 28 28 28 28-12.536 28-28-12.536-28-28-28zM220 591.95c-15.464 0-28 12.535-28 28 0 15.463 12.536 28 28 28s28-12.537 28-28c0-15.465-12.536-28-28-28zm560 0c-15.464 0-28 12.535-28 28 0 15.463 12.536 28 28 28s28-12.537 28-28c0-15.465-12.536-28-28-28z" fill="#189efc"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

View File

Before

Width:  |  Height:  |  Size: 174 B

After

Width:  |  Height:  |  Size: 174 B

View File

@@ -0,0 +1,15 @@
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery2
//= require jquery_ujs
//= require components

View File

@@ -0,0 +1,9 @@
//= require jquery2
//= require jquery_ujs
//= require extras
//= require best_in_place
//= require local_time
$(function () {
$(".best_in_place").best_in_place();
});

View File

@@ -0,0 +1,15 @@
//= require_self
//= require react_ujs
window.React = require('react');
window.ReactDOM = require('react-dom');
window.Perf = require('react-addons-perf');
if (!window.Intl) {
require('intl');
require('intl/locale-data/jsonp/en.js');
}
//= require_tree ./components
window.Mastodon = require('./components/containers/mastodon');

View File

@@ -1,4 +1,5 @@
import api, { getLinks } from '../api'; import api, { getLinks } from '../api'
import Immutable from 'immutable';
export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST'; export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST';
export const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS'; export const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS';
@@ -28,6 +29,14 @@ export const ACCOUNT_UNMUTE_REQUEST = 'ACCOUNT_UNMUTE_REQUEST';
export const ACCOUNT_UNMUTE_SUCCESS = 'ACCOUNT_UNMUTE_SUCCESS'; export const ACCOUNT_UNMUTE_SUCCESS = 'ACCOUNT_UNMUTE_SUCCESS';
export const ACCOUNT_UNMUTE_FAIL = 'ACCOUNT_UNMUTE_FAIL'; export const ACCOUNT_UNMUTE_FAIL = 'ACCOUNT_UNMUTE_FAIL';
export const ACCOUNT_TIMELINE_FETCH_REQUEST = 'ACCOUNT_TIMELINE_FETCH_REQUEST';
export const ACCOUNT_TIMELINE_FETCH_SUCCESS = 'ACCOUNT_TIMELINE_FETCH_SUCCESS';
export const ACCOUNT_TIMELINE_FETCH_FAIL = 'ACCOUNT_TIMELINE_FETCH_FAIL';
export const ACCOUNT_TIMELINE_EXPAND_REQUEST = 'ACCOUNT_TIMELINE_EXPAND_REQUEST';
export const ACCOUNT_TIMELINE_EXPAND_SUCCESS = 'ACCOUNT_TIMELINE_EXPAND_SUCCESS';
export const ACCOUNT_TIMELINE_EXPAND_FAIL = 'ACCOUNT_TIMELINE_EXPAND_FAIL';
export const FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST'; export const FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST';
export const FOLLOWERS_FETCH_SUCCESS = 'FOLLOWERS_FETCH_SUCCESS'; export const FOLLOWERS_FETCH_SUCCESS = 'FOLLOWERS_FETCH_SUCCESS';
export const FOLLOWERS_FETCH_FAIL = 'FOLLOWERS_FETCH_FAIL'; export const FOLLOWERS_FETCH_FAIL = 'FOLLOWERS_FETCH_FAIL';
@@ -82,17 +91,60 @@ export function fetchAccount(id) {
}; };
}; };
export function fetchAccountTimeline(id, replace = false) {
return (dispatch, getState) => {
const ids = getState().getIn(['timelines', 'accounts_timelines', id, 'items'], Immutable.List());
const newestId = ids.size > 0 ? ids.first() : null;
let params = '';
let skipLoading = false;
if (newestId !== null && !replace) {
params = `?since_id=${newestId}`;
skipLoading = true;
}
dispatch(fetchAccountTimelineRequest(id, skipLoading));
api(getState).get(`/api/v1/accounts/${id}/statuses${params}`).then(response => {
dispatch(fetchAccountTimelineSuccess(id, response.data, replace, skipLoading));
}).catch(error => {
dispatch(fetchAccountTimelineFail(id, error, skipLoading));
});
};
};
export function expandAccountTimeline(id) {
return (dispatch, getState) => {
const lastId = getState().getIn(['timelines', 'accounts_timelines', id, 'items'], Immutable.List()).last();
dispatch(expandAccountTimelineRequest(id));
api(getState).get(`/api/v1/accounts/${id}/statuses`, {
params: {
limit: 10,
max_id: lastId
}
}).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(expandAccountTimelineSuccess(id, response.data, next));
}).catch(error => {
dispatch(expandAccountTimelineFail(id, error));
});
};
};
export function fetchAccountRequest(id) { export function fetchAccountRequest(id) {
return { return {
type: ACCOUNT_FETCH_REQUEST, type: ACCOUNT_FETCH_REQUEST,
id, id
}; };
}; };
export function fetchAccountSuccess(account) { export function fetchAccountSuccess(account) {
return { return {
type: ACCOUNT_FETCH_SUCCESS, type: ACCOUNT_FETCH_SUCCESS,
account, account
}; };
}; };
@@ -101,7 +153,7 @@ export function fetchAccountFail(id, error) {
type: ACCOUNT_FETCH_FAIL, type: ACCOUNT_FETCH_FAIL,
id, id,
error, error,
skipAlert: true, skipAlert: true
}; };
}; };
@@ -126,48 +178,100 @@ export function unfollowAccount(id) {
}).catch(error => { }).catch(error => {
dispatch(unfollowAccountFail(error)); dispatch(unfollowAccountFail(error));
}); });
}; }
}; };
export function followAccountRequest(id) { export function followAccountRequest(id) {
return { return {
type: ACCOUNT_FOLLOW_REQUEST, type: ACCOUNT_FOLLOW_REQUEST,
id, id
}; };
}; };
export function followAccountSuccess(relationship) { export function followAccountSuccess(relationship) {
return { return {
type: ACCOUNT_FOLLOW_SUCCESS, type: ACCOUNT_FOLLOW_SUCCESS,
relationship, relationship
}; };
}; };
export function followAccountFail(error) { export function followAccountFail(error) {
return { return {
type: ACCOUNT_FOLLOW_FAIL, type: ACCOUNT_FOLLOW_FAIL,
error, error
}; };
}; };
export function unfollowAccountRequest(id) { export function unfollowAccountRequest(id) {
return { return {
type: ACCOUNT_UNFOLLOW_REQUEST, type: ACCOUNT_UNFOLLOW_REQUEST,
id, id
}; };
}; };
export function unfollowAccountSuccess(relationship) { export function unfollowAccountSuccess(relationship) {
return { return {
type: ACCOUNT_UNFOLLOW_SUCCESS, type: ACCOUNT_UNFOLLOW_SUCCESS,
relationship, relationship
}; };
}; };
export function unfollowAccountFail(error) { export function unfollowAccountFail(error) {
return { return {
type: ACCOUNT_UNFOLLOW_FAIL, type: ACCOUNT_UNFOLLOW_FAIL,
error
};
};
export function fetchAccountTimelineRequest(id, skipLoading) {
return {
type: ACCOUNT_TIMELINE_FETCH_REQUEST,
id,
skipLoading
};
};
export function fetchAccountTimelineSuccess(id, statuses, replace, skipLoading) {
return {
type: ACCOUNT_TIMELINE_FETCH_SUCCESS,
id,
statuses,
replace,
skipLoading
};
};
export function fetchAccountTimelineFail(id, error, skipLoading) {
return {
type: ACCOUNT_TIMELINE_FETCH_FAIL,
id,
error, error,
skipLoading,
skipAlert: error.response.status === 404
};
};
export function expandAccountTimelineRequest(id) {
return {
type: ACCOUNT_TIMELINE_EXPAND_REQUEST,
id
};
};
export function expandAccountTimelineSuccess(id, statuses, next) {
return {
type: ACCOUNT_TIMELINE_EXPAND_SUCCESS,
id,
statuses,
next
};
};
export function expandAccountTimelineFail(id, error) {
return {
type: ACCOUNT_TIMELINE_EXPAND_FAIL,
id,
error
}; };
}; };
@@ -199,7 +303,7 @@ export function unblockAccount(id) {
export function blockAccountRequest(id) { export function blockAccountRequest(id) {
return { return {
type: ACCOUNT_BLOCK_REQUEST, type: ACCOUNT_BLOCK_REQUEST,
id, id
}; };
}; };
@@ -207,35 +311,35 @@ export function blockAccountSuccess(relationship, statuses) {
return { return {
type: ACCOUNT_BLOCK_SUCCESS, type: ACCOUNT_BLOCK_SUCCESS,
relationship, relationship,
statuses, statuses
}; };
}; };
export function blockAccountFail(error) { export function blockAccountFail(error) {
return { return {
type: ACCOUNT_BLOCK_FAIL, type: ACCOUNT_BLOCK_FAIL,
error, error
}; };
}; };
export function unblockAccountRequest(id) { export function unblockAccountRequest(id) {
return { return {
type: ACCOUNT_UNBLOCK_REQUEST, type: ACCOUNT_UNBLOCK_REQUEST,
id, id
}; };
}; };
export function unblockAccountSuccess(relationship) { export function unblockAccountSuccess(relationship) {
return { return {
type: ACCOUNT_UNBLOCK_SUCCESS, type: ACCOUNT_UNBLOCK_SUCCESS,
relationship, relationship
}; };
}; };
export function unblockAccountFail(error) { export function unblockAccountFail(error) {
return { return {
type: ACCOUNT_UNBLOCK_FAIL, type: ACCOUNT_UNBLOCK_FAIL,
error, error
}; };
}; };
@@ -268,7 +372,7 @@ export function unmuteAccount(id) {
export function muteAccountRequest(id) { export function muteAccountRequest(id) {
return { return {
type: ACCOUNT_MUTE_REQUEST, type: ACCOUNT_MUTE_REQUEST,
id, id
}; };
}; };
@@ -276,35 +380,35 @@ export function muteAccountSuccess(relationship, statuses) {
return { return {
type: ACCOUNT_MUTE_SUCCESS, type: ACCOUNT_MUTE_SUCCESS,
relationship, relationship,
statuses, statuses
}; };
}; };
export function muteAccountFail(error) { export function muteAccountFail(error) {
return { return {
type: ACCOUNT_MUTE_FAIL, type: ACCOUNT_MUTE_FAIL,
error, error
}; };
}; };
export function unmuteAccountRequest(id) { export function unmuteAccountRequest(id) {
return { return {
type: ACCOUNT_UNMUTE_REQUEST, type: ACCOUNT_UNMUTE_REQUEST,
id, id
}; };
}; };
export function unmuteAccountSuccess(relationship) { export function unmuteAccountSuccess(relationship) {
return { return {
type: ACCOUNT_UNMUTE_SUCCESS, type: ACCOUNT_UNMUTE_SUCCESS,
relationship, relationship
}; };
}; };
export function unmuteAccountFail(error) { export function unmuteAccountFail(error) {
return { return {
type: ACCOUNT_UNMUTE_FAIL, type: ACCOUNT_UNMUTE_FAIL,
error, error
}; };
}; };
@@ -327,7 +431,7 @@ export function fetchFollowers(id) {
export function fetchFollowersRequest(id) { export function fetchFollowersRequest(id) {
return { return {
type: FOLLOWERS_FETCH_REQUEST, type: FOLLOWERS_FETCH_REQUEST,
id, id
}; };
}; };
@@ -336,7 +440,7 @@ export function fetchFollowersSuccess(id, accounts, next) {
type: FOLLOWERS_FETCH_SUCCESS, type: FOLLOWERS_FETCH_SUCCESS,
id, id,
accounts, accounts,
next, next
}; };
}; };
@@ -344,7 +448,7 @@ export function fetchFollowersFail(id, error) {
return { return {
type: FOLLOWERS_FETCH_FAIL, type: FOLLOWERS_FETCH_FAIL,
id, id,
error, error
}; };
}; };
@@ -372,7 +476,7 @@ export function expandFollowers(id) {
export function expandFollowersRequest(id) { export function expandFollowersRequest(id) {
return { return {
type: FOLLOWERS_EXPAND_REQUEST, type: FOLLOWERS_EXPAND_REQUEST,
id, id
}; };
}; };
@@ -381,7 +485,7 @@ export function expandFollowersSuccess(id, accounts, next) {
type: FOLLOWERS_EXPAND_SUCCESS, type: FOLLOWERS_EXPAND_SUCCESS,
id, id,
accounts, accounts,
next, next
}; };
}; };
@@ -389,7 +493,7 @@ export function expandFollowersFail(id, error) {
return { return {
type: FOLLOWERS_EXPAND_FAIL, type: FOLLOWERS_EXPAND_FAIL,
id, id,
error, error
}; };
}; };
@@ -411,7 +515,7 @@ export function fetchFollowing(id) {
export function fetchFollowingRequest(id) { export function fetchFollowingRequest(id) {
return { return {
type: FOLLOWING_FETCH_REQUEST, type: FOLLOWING_FETCH_REQUEST,
id, id
}; };
}; };
@@ -420,7 +524,7 @@ export function fetchFollowingSuccess(id, accounts, next) {
type: FOLLOWING_FETCH_SUCCESS, type: FOLLOWING_FETCH_SUCCESS,
id, id,
accounts, accounts,
next, next
}; };
}; };
@@ -428,7 +532,7 @@ export function fetchFollowingFail(id, error) {
return { return {
type: FOLLOWING_FETCH_FAIL, type: FOLLOWING_FETCH_FAIL,
id, id,
error, error
}; };
}; };
@@ -456,7 +560,7 @@ export function expandFollowing(id) {
export function expandFollowingRequest(id) { export function expandFollowingRequest(id) {
return { return {
type: FOLLOWING_EXPAND_REQUEST, type: FOLLOWING_EXPAND_REQUEST,
id, id
}; };
}; };
@@ -465,7 +569,7 @@ export function expandFollowingSuccess(id, accounts, next) {
type: FOLLOWING_EXPAND_SUCCESS, type: FOLLOWING_EXPAND_SUCCESS,
id, id,
accounts, accounts,
next, next
}; };
}; };
@@ -473,7 +577,7 @@ export function expandFollowingFail(id, error) {
return { return {
type: FOLLOWING_EXPAND_FAIL, type: FOLLOWING_EXPAND_FAIL,
id, id,
error, error
}; };
}; };
@@ -500,7 +604,7 @@ export function fetchRelationshipsRequest(ids) {
return { return {
type: RELATIONSHIPS_FETCH_REQUEST, type: RELATIONSHIPS_FETCH_REQUEST,
ids, ids,
skipLoading: true, skipLoading: true
}; };
}; };
@@ -508,7 +612,7 @@ export function fetchRelationshipsSuccess(relationships) {
return { return {
type: RELATIONSHIPS_FETCH_SUCCESS, type: RELATIONSHIPS_FETCH_SUCCESS,
relationships, relationships,
skipLoading: true, skipLoading: true
}; };
}; };
@@ -516,7 +620,7 @@ export function fetchRelationshipsFail(error) {
return { return {
type: RELATIONSHIPS_FETCH_FAIL, type: RELATIONSHIPS_FETCH_FAIL,
error, error,
skipLoading: true, skipLoading: true
}; };
}; };
@@ -526,14 +630,14 @@ export function fetchFollowRequests() {
api(getState).get('/api/v1/follow_requests').then(response => { api(getState).get('/api/v1/follow_requests').then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(fetchFollowRequestsSuccess(response.data, next ? next.uri : null)); dispatch(fetchFollowRequestsSuccess(response.data, next ? next.uri : null))
}).catch(error => dispatch(fetchFollowRequestsFail(error))); }).catch(error => dispatch(fetchFollowRequestsFail(error)));
}; };
}; };
export function fetchFollowRequestsRequest() { export function fetchFollowRequestsRequest() {
return { return {
type: FOLLOW_REQUESTS_FETCH_REQUEST, type: FOLLOW_REQUESTS_FETCH_REQUEST
}; };
}; };
@@ -541,14 +645,14 @@ export function fetchFollowRequestsSuccess(accounts, next) {
return { return {
type: FOLLOW_REQUESTS_FETCH_SUCCESS, type: FOLLOW_REQUESTS_FETCH_SUCCESS,
accounts, accounts,
next, next
}; };
}; };
export function fetchFollowRequestsFail(error) { export function fetchFollowRequestsFail(error) {
return { return {
type: FOLLOW_REQUESTS_FETCH_FAIL, type: FOLLOW_REQUESTS_FETCH_FAIL,
error, error
}; };
}; };
@@ -564,14 +668,14 @@ export function expandFollowRequests() {
api(getState).get(url).then(response => { api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(expandFollowRequestsSuccess(response.data, next ? next.uri : null)); dispatch(expandFollowRequestsSuccess(response.data, next ? next.uri : null))
}).catch(error => dispatch(expandFollowRequestsFail(error))); }).catch(error => dispatch(expandFollowRequestsFail(error)));
}; };
}; };
export function expandFollowRequestsRequest() { export function expandFollowRequestsRequest() {
return { return {
type: FOLLOW_REQUESTS_EXPAND_REQUEST, type: FOLLOW_REQUESTS_EXPAND_REQUEST
}; };
}; };
@@ -579,14 +683,14 @@ export function expandFollowRequestsSuccess(accounts, next) {
return { return {
type: FOLLOW_REQUESTS_EXPAND_SUCCESS, type: FOLLOW_REQUESTS_EXPAND_SUCCESS,
accounts, accounts,
next, next
}; };
}; };
export function expandFollowRequestsFail(error) { export function expandFollowRequestsFail(error) {
return { return {
type: FOLLOW_REQUESTS_EXPAND_FAIL, type: FOLLOW_REQUESTS_EXPAND_FAIL,
error, error
}; };
}; };
@@ -596,7 +700,7 @@ export function authorizeFollowRequest(id) {
api(getState) api(getState)
.post(`/api/v1/follow_requests/${id}/authorize`) .post(`/api/v1/follow_requests/${id}/authorize`)
.then(() => dispatch(authorizeFollowRequestSuccess(id))) .then(response => dispatch(authorizeFollowRequestSuccess(id)))
.catch(error => dispatch(authorizeFollowRequestFail(id, error))); .catch(error => dispatch(authorizeFollowRequestFail(id, error)));
}; };
}; };
@@ -604,14 +708,14 @@ export function authorizeFollowRequest(id) {
export function authorizeFollowRequestRequest(id) { export function authorizeFollowRequestRequest(id) {
return { return {
type: FOLLOW_REQUEST_AUTHORIZE_REQUEST, type: FOLLOW_REQUEST_AUTHORIZE_REQUEST,
id, id
}; };
}; };
export function authorizeFollowRequestSuccess(id) { export function authorizeFollowRequestSuccess(id) {
return { return {
type: FOLLOW_REQUEST_AUTHORIZE_SUCCESS, type: FOLLOW_REQUEST_AUTHORIZE_SUCCESS,
id, id
}; };
}; };
@@ -619,7 +723,7 @@ export function authorizeFollowRequestFail(id, error) {
return { return {
type: FOLLOW_REQUEST_AUTHORIZE_FAIL, type: FOLLOW_REQUEST_AUTHORIZE_FAIL,
id, id,
error, error
}; };
}; };
@@ -630,7 +734,7 @@ export function rejectFollowRequest(id) {
api(getState) api(getState)
.post(`/api/v1/follow_requests/${id}/reject`) .post(`/api/v1/follow_requests/${id}/reject`)
.then(() => dispatch(rejectFollowRequestSuccess(id))) .then(response => dispatch(rejectFollowRequestSuccess(id)))
.catch(error => dispatch(rejectFollowRequestFail(id, error))); .catch(error => dispatch(rejectFollowRequestFail(id, error)));
}; };
}; };
@@ -638,14 +742,14 @@ export function rejectFollowRequest(id) {
export function rejectFollowRequestRequest(id) { export function rejectFollowRequestRequest(id) {
return { return {
type: FOLLOW_REQUEST_REJECT_REQUEST, type: FOLLOW_REQUEST_REJECT_REQUEST,
id, id
}; };
}; };
export function rejectFollowRequestSuccess(id) { export function rejectFollowRequestSuccess(id) {
return { return {
type: FOLLOW_REQUEST_REJECT_SUCCESS, type: FOLLOW_REQUEST_REJECT_SUCCESS,
id, id
}; };
}; };
@@ -653,6 +757,6 @@ export function rejectFollowRequestFail(id, error) {
return { return {
type: FOLLOW_REQUEST_REJECT_FAIL, type: FOLLOW_REQUEST_REJECT_FAIL,
id, id,
error, error
}; };
}; };

View File

@@ -5,13 +5,13 @@ export const ALERT_CLEAR = 'ALERT_CLEAR';
export function dismissAlert(alert) { export function dismissAlert(alert) {
return { return {
type: ALERT_DISMISS, type: ALERT_DISMISS,
alert, alert
}; };
}; };
export function clearAlert() { export function clearAlert() {
return { return {
type: ALERT_CLEAR, type: ALERT_CLEAR
}; };
}; };
@@ -19,6 +19,6 @@ export function showAlert(title, message) {
return { return {
type: ALERT_SHOW, type: ALERT_SHOW,
title, title,
message, message
}; };
}; };

View File

@@ -1,4 +1,4 @@
import api, { getLinks } from '../api'; import api, { getLinks } from '../api'
import { fetchRelationships } from './accounts'; import { fetchRelationships } from './accounts';
export const BLOCKS_FETCH_REQUEST = 'BLOCKS_FETCH_REQUEST'; export const BLOCKS_FETCH_REQUEST = 'BLOCKS_FETCH_REQUEST';
@@ -23,7 +23,7 @@ export function fetchBlocks() {
export function fetchBlocksRequest() { export function fetchBlocksRequest() {
return { return {
type: BLOCKS_FETCH_REQUEST, type: BLOCKS_FETCH_REQUEST
}; };
}; };
@@ -31,14 +31,14 @@ export function fetchBlocksSuccess(accounts, next) {
return { return {
type: BLOCKS_FETCH_SUCCESS, type: BLOCKS_FETCH_SUCCESS,
accounts, accounts,
next, next
}; };
}; };
export function fetchBlocksFail(error) { export function fetchBlocksFail(error) {
return { return {
type: BLOCKS_FETCH_FAIL, type: BLOCKS_FETCH_FAIL,
error, error
}; };
}; };
@@ -62,7 +62,7 @@ export function expandBlocks() {
export function expandBlocksRequest() { export function expandBlocksRequest() {
return { return {
type: BLOCKS_EXPAND_REQUEST, type: BLOCKS_EXPAND_REQUEST
}; };
}; };
@@ -70,13 +70,13 @@ export function expandBlocksSuccess(accounts, next) {
return { return {
type: BLOCKS_EXPAND_SUCCESS, type: BLOCKS_EXPAND_SUCCESS,
accounts, accounts,
next, next
}; };
}; };
export function expandBlocksFail(error) { export function expandBlocksFail(error) {
return { return {
type: BLOCKS_EXPAND_FAIL, type: BLOCKS_EXPAND_FAIL,
error, error
}; };
}; };

View File

@@ -28,7 +28,7 @@ export function fetchStatusCardRequest(id) {
return { return {
type: STATUS_CARD_FETCH_REQUEST, type: STATUS_CARD_FETCH_REQUEST,
id, id,
skipLoading: true, skipLoading: true
}; };
}; };
@@ -37,7 +37,7 @@ export function fetchStatusCardSuccess(id, card) {
type: STATUS_CARD_FETCH_SUCCESS, type: STATUS_CARD_FETCH_SUCCESS,
id, id,
card, card,
skipLoading: true, skipLoading: true
}; };
}; };
@@ -47,6 +47,6 @@ export function fetchStatusCardFail(id, error) {
id, id,
error, error,
skipLoading: true, skipLoading: true,
skipAlert: true, skipAlert: true
}; };
}; };

View File

@@ -1,11 +1,8 @@
import api from '../api'; import api from '../api';
import { import { updateTimeline } from './timelines';
updateTimeline,
refreshHomeTimeline, import * as emojione from 'emojione';
refreshCommunityTimeline,
refreshPublicTimeline,
} from './timelines';
export const COMPOSE_CHANGE = 'COMPOSE_CHANGE'; export const COMPOSE_CHANGE = 'COMPOSE_CHANGE';
export const COMPOSE_SUBMIT_REQUEST = 'COMPOSE_SUBMIT_REQUEST'; export const COMPOSE_SUBMIT_REQUEST = 'COMPOSE_SUBMIT_REQUEST';
@@ -32,14 +29,13 @@ export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE';
export const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE'; export const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE';
export const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE'; export const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE';
export const COMPOSE_LISTABILITY_CHANGE = 'COMPOSE_LISTABILITY_CHANGE'; export const COMPOSE_LISTABILITY_CHANGE = 'COMPOSE_LISTABILITY_CHANGE';
export const COMPOSE_COMPOSING_CHANGE = 'COMPOSE_COMPOSING_CHANGE';
export const COMPOSE_EMOJI_INSERT = 'COMPOSE_EMOJI_INSERT'; export const COMPOSE_EMOJI_INSERT = 'COMPOSE_EMOJI_INSERT';
export function changeCompose(text) { export function changeCompose(text) {
return { return {
type: COMPOSE_CHANGE, type: COMPOSE_CHANGE,
text: text, text: text
}; };
}; };
@@ -47,7 +43,7 @@ export function replyCompose(status, router) {
return (dispatch, getState) => { return (dispatch, getState) => {
dispatch({ dispatch({
type: COMPOSE_REPLY, type: COMPOSE_REPLY,
status: status, status: status
}); });
if (!getState().getIn(['compose', 'mounted'])) { if (!getState().getIn(['compose', 'mounted'])) {
@@ -58,7 +54,7 @@ export function replyCompose(status, router) {
export function cancelReplyCompose() { export function cancelReplyCompose() {
return { return {
type: COMPOSE_REPLY_CANCEL, type: COMPOSE_REPLY_CANCEL
}; };
}; };
@@ -66,7 +62,7 @@ export function mentionCompose(account, router) {
return (dispatch, getState) => { return (dispatch, getState) => {
dispatch({ dispatch({
type: COMPOSE_MENTION, type: COMPOSE_MENTION,
account: account, account: account
}); });
if (!getState().getIn(['compose', 'mounted'])) { if (!getState().getIn(['compose', 'mounted'])) {
@@ -77,43 +73,36 @@ export function mentionCompose(account, router) {
export function submitCompose() { export function submitCompose() {
return function (dispatch, getState) { return function (dispatch, getState) {
const status = getState().getIn(['compose', 'text'], ''); const status = emojione.shortnameToUnicode(getState().getIn(['compose', 'text'], ''));
if (!status || !status.length) { if (!status || !status.length) {
return; return;
} }
dispatch(submitComposeRequest()); dispatch(submitComposeRequest());
api(getState).post('/api/v1/statuses', { api(getState).post('/api/v1/statuses', {
status, status,
in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null), in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
media_ids: getState().getIn(['compose', 'media_attachments']).map(item => item.get('id')), media_ids: getState().getIn(['compose', 'media_attachments']).map(item => item.get('id')),
sensitive: getState().getIn(['compose', 'sensitive']), sensitive: getState().getIn(['compose', 'sensitive']),
spoiler_text: getState().getIn(['compose', 'spoiler_text'], ''), spoiler_text: getState().getIn(['compose', 'spoiler_text'], ''),
visibility: getState().getIn(['compose', 'privacy']), visibility: getState().getIn(['compose', 'privacy'])
}, { }, {
headers: { headers: {
'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']), 'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey'])
}, }
}).then(function (response) { }).then(function (response) {
dispatch(submitComposeSuccess({ ...response.data })); dispatch(submitComposeSuccess({ ...response.data }));
// To make the app more responsive, immediately get the status into the columns // To make the app more responsive, immediately get the status into the columns
dispatch(updateTimeline('home', { ...response.data }));
const insertOrRefresh = (timelineId, refreshAction) => {
if (getState().getIn(['timelines', timelineId, 'online'])) {
dispatch(updateTimeline(timelineId, { ...response.data }));
} else if (getState().getIn(['timelines', timelineId, 'loaded'])) {
dispatch(refreshAction());
}
};
insertOrRefresh('home', refreshHomeTimeline);
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); if (getState().getIn(['timelines', 'community', 'loaded'])) {
insertOrRefresh('public', refreshPublicTimeline); dispatch(updateTimeline('community', { ...response.data }));
}
if (getState().getIn(['timelines', 'public', 'loaded'])) {
dispatch(updateTimeline('public', { ...response.data }));
}
} }
}).catch(function (error) { }).catch(function (error) {
dispatch(submitComposeFail(error)); dispatch(submitComposeFail(error));
@@ -123,21 +112,21 @@ export function submitCompose() {
export function submitComposeRequest() { export function submitComposeRequest() {
return { return {
type: COMPOSE_SUBMIT_REQUEST, type: COMPOSE_SUBMIT_REQUEST
}; };
}; };
export function submitComposeSuccess(status) { export function submitComposeSuccess(status) {
return { return {
type: COMPOSE_SUBMIT_SUCCESS, type: COMPOSE_SUBMIT_SUCCESS,
status: status, status: status
}; };
}; };
export function submitComposeFail(error) { export function submitComposeFail(error) {
return { return {
type: COMPOSE_SUBMIT_FAIL, type: COMPOSE_SUBMIT_FAIL,
error: error, error: error
}; };
}; };
@@ -155,7 +144,7 @@ export function uploadCompose(files) {
api(getState).post('/api/v1/media', data, { api(getState).post('/api/v1/media', data, {
onUploadProgress: function (e) { onUploadProgress: function (e) {
dispatch(uploadComposeProgress(e.loaded, e.total)); dispatch(uploadComposeProgress(e.loaded, e.total));
}, }
}).then(function (response) { }).then(function (response) {
dispatch(uploadComposeSuccess(response.data)); dispatch(uploadComposeSuccess(response.data));
}).catch(function (error) { }).catch(function (error) {
@@ -167,7 +156,7 @@ export function uploadCompose(files) {
export function uploadComposeRequest() { export function uploadComposeRequest() {
return { return {
type: COMPOSE_UPLOAD_REQUEST, type: COMPOSE_UPLOAD_REQUEST,
skipLoading: true, skipLoading: true
}; };
}; };
@@ -175,7 +164,7 @@ export function uploadComposeProgress(loaded, total) {
return { return {
type: COMPOSE_UPLOAD_PROGRESS, type: COMPOSE_UPLOAD_PROGRESS,
loaded: loaded, loaded: loaded,
total: total, total: total
}; };
}; };
@@ -183,7 +172,7 @@ export function uploadComposeSuccess(media) {
return { return {
type: COMPOSE_UPLOAD_SUCCESS, type: COMPOSE_UPLOAD_SUCCESS,
media: media, media: media,
skipLoading: true, skipLoading: true
}; };
}; };
@@ -191,20 +180,20 @@ export function uploadComposeFail(error) {
return { return {
type: COMPOSE_UPLOAD_FAIL, type: COMPOSE_UPLOAD_FAIL,
error: error, error: error,
skipLoading: true, skipLoading: true
}; };
}; };
export function undoUploadCompose(media_id) { export function undoUploadCompose(media_id) {
return { return {
type: COMPOSE_UPLOAD_UNDO, type: COMPOSE_UPLOAD_UNDO,
media_id: media_id, media_id: media_id
}; };
}; };
export function clearComposeSuggestions() { export function clearComposeSuggestions() {
return { return {
type: COMPOSE_SUGGESTIONS_CLEAR, type: COMPOSE_SUGGESTIONS_CLEAR
}; };
}; };
@@ -214,8 +203,8 @@ export function fetchComposeSuggestions(token) {
params: { params: {
q: token, q: token,
resolve: false, resolve: false,
limit: 4, limit: 4
}, }
}).then(response => { }).then(response => {
dispatch(readyComposeSuggestions(token, response.data)); dispatch(readyComposeSuggestions(token, response.data));
}); });
@@ -226,7 +215,7 @@ export function readyComposeSuggestions(token, accounts) {
return { return {
type: COMPOSE_SUGGESTIONS_READY, type: COMPOSE_SUGGESTIONS_READY,
token, token,
accounts, accounts
}; };
}; };
@@ -238,20 +227,20 @@ export function selectComposeSuggestion(position, token, accountId) {
type: COMPOSE_SUGGESTION_SELECT, type: COMPOSE_SUGGESTION_SELECT,
position, position,
token, token,
completion, completion
}); });
}; };
}; };
export function mountCompose() { export function mountCompose() {
return { return {
type: COMPOSE_MOUNT, type: COMPOSE_MOUNT
}; };
}; };
export function unmountCompose() { export function unmountCompose() {
return { return {
type: COMPOSE_UNMOUNT, type: COMPOSE_UNMOUNT
}; };
}; };
@@ -263,21 +252,21 @@ export function changeComposeSensitivity() {
export function changeComposeSpoilerness() { export function changeComposeSpoilerness() {
return { return {
type: COMPOSE_SPOILERNESS_CHANGE, type: COMPOSE_SPOILERNESS_CHANGE
}; };
}; };
export function changeComposeSpoilerText(text) { export function changeComposeSpoilerText(text) {
return { return {
type: COMPOSE_SPOILER_TEXT_CHANGE, type: COMPOSE_SPOILER_TEXT_CHANGE,
text, text
}; };
}; };
export function changeComposeVisibility(value) { export function changeComposeVisibility(value) {
return { return {
type: COMPOSE_VISIBILITY_CHANGE, type: COMPOSE_VISIBILITY_CHANGE,
value, value
}; };
}; };
@@ -285,13 +274,6 @@ export function insertEmojiCompose(position, emoji) {
return { return {
type: COMPOSE_EMOJI_INSERT, type: COMPOSE_EMOJI_INSERT,
position, position,
emoji, emoji
}; };
}; };
export function changeComposing(value) {
return {
type: COMPOSE_COMPOSING_CHANGE,
value,
};
}

View File

@@ -1,4 +1,4 @@
import api, { getLinks } from '../api'; import api, { getLinks } from '../api'
export const FAVOURITED_STATUSES_FETCH_REQUEST = 'FAVOURITED_STATUSES_FETCH_REQUEST'; export const FAVOURITED_STATUSES_FETCH_REQUEST = 'FAVOURITED_STATUSES_FETCH_REQUEST';
export const FAVOURITED_STATUSES_FETCH_SUCCESS = 'FAVOURITED_STATUSES_FETCH_SUCCESS'; export const FAVOURITED_STATUSES_FETCH_SUCCESS = 'FAVOURITED_STATUSES_FETCH_SUCCESS';
@@ -23,7 +23,7 @@ export function fetchFavouritedStatuses() {
export function fetchFavouritedStatusesRequest() { export function fetchFavouritedStatusesRequest() {
return { return {
type: FAVOURITED_STATUSES_FETCH_REQUEST, type: FAVOURITED_STATUSES_FETCH_REQUEST
}; };
}; };
@@ -31,14 +31,14 @@ export function fetchFavouritedStatusesSuccess(statuses, next) {
return { return {
type: FAVOURITED_STATUSES_FETCH_SUCCESS, type: FAVOURITED_STATUSES_FETCH_SUCCESS,
statuses, statuses,
next, next
}; };
}; };
export function fetchFavouritedStatusesFail(error) { export function fetchFavouritedStatusesFail(error) {
return { return {
type: FAVOURITED_STATUSES_FETCH_FAIL, type: FAVOURITED_STATUSES_FETCH_FAIL,
error, error
}; };
}; };
@@ -63,7 +63,7 @@ export function expandFavouritedStatuses() {
export function expandFavouritedStatusesRequest() { export function expandFavouritedStatusesRequest() {
return { return {
type: FAVOURITED_STATUSES_EXPAND_REQUEST, type: FAVOURITED_STATUSES_EXPAND_REQUEST
}; };
}; };
@@ -71,13 +71,13 @@ export function expandFavouritedStatusesSuccess(statuses, next) {
return { return {
type: FAVOURITED_STATUSES_EXPAND_SUCCESS, type: FAVOURITED_STATUSES_EXPAND_SUCCESS,
statuses, statuses,
next, next
}; };
}; };
export function expandFavouritedStatusesFail(error) { export function expandFavouritedStatusesFail(error) {
return { return {
type: FAVOURITED_STATUSES_EXPAND_FAIL, type: FAVOURITED_STATUSES_EXPAND_FAIL,
error, error
}; };
}; };

View File

@@ -1,4 +1,4 @@
import api from '../api'; import api from '../api'
export const REBLOG_REQUEST = 'REBLOG_REQUEST'; export const REBLOG_REQUEST = 'REBLOG_REQUEST';
export const REBLOG_SUCCESS = 'REBLOG_SUCCESS'; export const REBLOG_SUCCESS = 'REBLOG_SUCCESS';
@@ -24,14 +24,6 @@ export const FAVOURITES_FETCH_REQUEST = 'FAVOURITES_FETCH_REQUEST';
export const FAVOURITES_FETCH_SUCCESS = 'FAVOURITES_FETCH_SUCCESS'; export const FAVOURITES_FETCH_SUCCESS = 'FAVOURITES_FETCH_SUCCESS';
export const FAVOURITES_FETCH_FAIL = 'FAVOURITES_FETCH_FAIL'; export const FAVOURITES_FETCH_FAIL = 'FAVOURITES_FETCH_FAIL';
export const PIN_REQUEST = 'PIN_REQUEST';
export const PIN_SUCCESS = 'PIN_SUCCESS';
export const PIN_FAIL = 'PIN_FAIL';
export const UNPIN_REQUEST = 'UNPIN_REQUEST';
export const UNPIN_SUCCESS = 'UNPIN_SUCCESS';
export const UNPIN_FAIL = 'UNPIN_FAIL';
export function reblog(status) { export function reblog(status) {
return function (dispatch, getState) { return function (dispatch, getState) {
dispatch(reblogRequest(status)); dispatch(reblogRequest(status));
@@ -61,7 +53,7 @@ export function unreblog(status) {
export function reblogRequest(status) { export function reblogRequest(status) {
return { return {
type: REBLOG_REQUEST, type: REBLOG_REQUEST,
status: status, status: status
}; };
}; };
@@ -69,7 +61,7 @@ export function reblogSuccess(status, response) {
return { return {
type: REBLOG_SUCCESS, type: REBLOG_SUCCESS,
status: status, status: status,
response: response, response: response
}; };
}; };
@@ -77,14 +69,14 @@ export function reblogFail(status, error) {
return { return {
type: REBLOG_FAIL, type: REBLOG_FAIL,
status: status, status: status,
error: error, error: error
}; };
}; };
export function unreblogRequest(status) { export function unreblogRequest(status) {
return { return {
type: UNREBLOG_REQUEST, type: UNREBLOG_REQUEST,
status: status, status: status
}; };
}; };
@@ -92,7 +84,7 @@ export function unreblogSuccess(status, response) {
return { return {
type: UNREBLOG_SUCCESS, type: UNREBLOG_SUCCESS,
status: status, status: status,
response: response, response: response
}; };
}; };
@@ -100,7 +92,7 @@ export function unreblogFail(status, error) {
return { return {
type: UNREBLOG_FAIL, type: UNREBLOG_FAIL,
status: status, status: status,
error: error, error: error
}; };
}; };
@@ -131,7 +123,7 @@ export function unfavourite(status) {
export function favouriteRequest(status) { export function favouriteRequest(status) {
return { return {
type: FAVOURITE_REQUEST, type: FAVOURITE_REQUEST,
status: status, status: status
}; };
}; };
@@ -139,7 +131,7 @@ export function favouriteSuccess(status, response) {
return { return {
type: FAVOURITE_SUCCESS, type: FAVOURITE_SUCCESS,
status: status, status: status,
response: response, response: response
}; };
}; };
@@ -147,14 +139,14 @@ export function favouriteFail(status, error) {
return { return {
type: FAVOURITE_FAIL, type: FAVOURITE_FAIL,
status: status, status: status,
error: error, error: error
}; };
}; };
export function unfavouriteRequest(status) { export function unfavouriteRequest(status) {
return { return {
type: UNFAVOURITE_REQUEST, type: UNFAVOURITE_REQUEST,
status: status, status: status
}; };
}; };
@@ -162,7 +154,7 @@ export function unfavouriteSuccess(status, response) {
return { return {
type: UNFAVOURITE_SUCCESS, type: UNFAVOURITE_SUCCESS,
status: status, status: status,
response: response, response: response
}; };
}; };
@@ -170,7 +162,7 @@ export function unfavouriteFail(status, error) {
return { return {
type: UNFAVOURITE_FAIL, type: UNFAVOURITE_FAIL,
status: status, status: status,
error: error, error: error
}; };
}; };
@@ -189,7 +181,7 @@ export function fetchReblogs(id) {
export function fetchReblogsRequest(id) { export function fetchReblogsRequest(id) {
return { return {
type: REBLOGS_FETCH_REQUEST, type: REBLOGS_FETCH_REQUEST,
id, id
}; };
}; };
@@ -197,14 +189,14 @@ export function fetchReblogsSuccess(id, accounts) {
return { return {
type: REBLOGS_FETCH_SUCCESS, type: REBLOGS_FETCH_SUCCESS,
id, id,
accounts, accounts
}; };
}; };
export function fetchReblogsFail(id, error) { export function fetchReblogsFail(id, error) {
return { return {
type: REBLOGS_FETCH_FAIL, type: REBLOGS_FETCH_FAIL,
error, error
}; };
}; };
@@ -223,7 +215,7 @@ export function fetchFavourites(id) {
export function fetchFavouritesRequest(id) { export function fetchFavouritesRequest(id) {
return { return {
type: FAVOURITES_FETCH_REQUEST, type: FAVOURITES_FETCH_REQUEST,
id, id
}; };
}; };
@@ -231,83 +223,13 @@ export function fetchFavouritesSuccess(id, accounts) {
return { return {
type: FAVOURITES_FETCH_SUCCESS, type: FAVOURITES_FETCH_SUCCESS,
id, id,
accounts, accounts
}; };
}; };
export function fetchFavouritesFail(id, error) { export function fetchFavouritesFail(id, error) {
return { return {
type: FAVOURITES_FETCH_FAIL, type: FAVOURITES_FETCH_FAIL,
error, error
};
};
export function pin(status) {
return (dispatch, getState) => {
dispatch(pinRequest(status));
api(getState).post(`/api/v1/statuses/${status.get('id')}/pin`).then(response => {
dispatch(pinSuccess(status, response.data));
}).catch(error => {
dispatch(pinFail(status, error));
});
};
};
export function pinRequest(status) {
return {
type: PIN_REQUEST,
status,
};
};
export function pinSuccess(status, response) {
return {
type: PIN_SUCCESS,
status,
response,
};
};
export function pinFail(status, error) {
return {
type: PIN_FAIL,
status,
error,
};
};
export function unpin (status) {
return (dispatch, getState) => {
dispatch(unpinRequest(status));
api(getState).post(`/api/v1/statuses/${status.get('id')}/unpin`).then(response => {
dispatch(unpinSuccess(status, response.data));
}).catch(error => {
dispatch(unpinFail(status, error));
});
};
};
export function unpinRequest(status) {
return {
type: UNPIN_REQUEST,
status,
};
};
export function unpinSuccess(status, response) {
return {
type: UNPIN_SUCCESS,
status,
response,
};
};
export function unpinFail(status, error) {
return {
type: UNPIN_FAIL,
status,
error,
}; };
}; };

View File

@@ -5,12 +5,12 @@ export function openModal(type, props) {
return { return {
type: MODAL_OPEN, type: MODAL_OPEN,
modalType: type, modalType: type,
modalProps: props, modalProps: props
}; };
}; };
export function closeModal() { export function closeModal() {
return { return {
type: MODAL_CLOSE, type: MODAL_CLOSE
}; };
}; };

View File

@@ -1,4 +1,4 @@
import api, { getLinks } from '../api'; import api, { getLinks } from '../api'
import { fetchRelationships } from './accounts'; import { fetchRelationships } from './accounts';
export const MUTES_FETCH_REQUEST = 'MUTES_FETCH_REQUEST'; export const MUTES_FETCH_REQUEST = 'MUTES_FETCH_REQUEST';
@@ -23,7 +23,7 @@ export function fetchMutes() {
export function fetchMutesRequest() { export function fetchMutesRequest() {
return { return {
type: MUTES_FETCH_REQUEST, type: MUTES_FETCH_REQUEST
}; };
}; };
@@ -31,14 +31,14 @@ export function fetchMutesSuccess(accounts, next) {
return { return {
type: MUTES_FETCH_SUCCESS, type: MUTES_FETCH_SUCCESS,
accounts, accounts,
next, next
}; };
}; };
export function fetchMutesFail(error) { export function fetchMutesFail(error) {
return { return {
type: MUTES_FETCH_FAIL, type: MUTES_FETCH_FAIL,
error, error
}; };
}; };
@@ -62,7 +62,7 @@ export function expandMutes() {
export function expandMutesRequest() { export function expandMutesRequest() {
return { return {
type: MUTES_EXPAND_REQUEST, type: MUTES_EXPAND_REQUEST
}; };
}; };
@@ -70,13 +70,13 @@ export function expandMutesSuccess(accounts, next) {
return { return {
type: MUTES_EXPAND_SUCCESS, type: MUTES_EXPAND_SUCCESS,
accounts, accounts,
next, next
}; };
}; };
export function expandMutesFail(error) { export function expandMutesFail(error) {
return { return {
type: MUTES_EXPAND_FAIL, type: MUTES_EXPAND_FAIL,
error, error
}; };
}; };

View File

@@ -1,8 +1,8 @@
import api, { getLinks } from '../api'; import api, { getLinks } from '../api'
import { List as ImmutableList } from 'immutable'; import Immutable from 'immutable';
import IntlMessageFormat from 'intl-messageformat'; import IntlMessageFormat from 'intl-messageformat';
import { fetchRelationships } from './accounts'; import { fetchRelationships } from './accounts';
import { defineMessages } from 'react-intl';
export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE'; export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE';
@@ -17,10 +17,6 @@ export const NOTIFICATIONS_EXPAND_FAIL = 'NOTIFICATIONS_EXPAND_FAIL';
export const NOTIFICATIONS_CLEAR = 'NOTIFICATIONS_CLEAR'; export const NOTIFICATIONS_CLEAR = 'NOTIFICATIONS_CLEAR';
export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP'; export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP';
defineMessages({
mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' },
});
const fetchRelatedRelationships = (dispatch, notifications) => { const fetchRelatedRelationships = (dispatch, notifications) => {
const accountIds = notifications.filter(item => item.type === 'follow').map(item => item.account.id); const accountIds = notifications.filter(item => item.type === 'follow').map(item => item.account.id);
@@ -29,12 +25,6 @@ const fetchRelatedRelationships = (dispatch, notifications) => {
} }
}; };
const unescapeHTML = (html) => {
const wrapper = document.createElement('div');
wrapper.innerHTML = html;
return wrapper.textContent;
};
export function updateNotifications(notification, intlMessages, intlLocale) { export function updateNotifications(notification, intlMessages, intlLocale) {
return (dispatch, getState) => { return (dispatch, getState) => {
const showAlert = getState().getIn(['settings', 'notifications', 'alerts', notification.type], true); const showAlert = getState().getIn(['settings', 'notifications', 'alerts', notification.type], true);
@@ -45,7 +35,7 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
notification, notification,
account: notification.account, account: notification.account,
status: notification.status, status: notification.status,
meta: playSound ? { sound: 'boop' } : undefined, meta: playSound ? { sound: 'boop' } : undefined
}); });
fetchRelatedRelationships(dispatch, [notification]); fetchRelatedRelationships(dispatch, [notification]);
@@ -53,13 +43,9 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
// Desktop notifications // Desktop notifications
if (typeof window.Notification !== 'undefined' && showAlert) { if (typeof window.Notification !== 'undefined' && showAlert) {
const title = new IntlMessageFormat(intlMessages[`notification.${notification.type}`], intlLocale).format({ name: notification.account.display_name.length > 0 ? notification.account.display_name : notification.account.username }); const title = new IntlMessageFormat(intlMessages[`notification.${notification.type}`], intlLocale).format({ name: notification.account.display_name.length > 0 ? notification.account.display_name : notification.account.username });
const body = (notification.status && notification.status.spoiler_text.length > 0) ? notification.status.spoiler_text : unescapeHTML(notification.status ? notification.status.content : ''); const body = (notification.status && notification.status.spoiler_text.length > 0) ? notification.status.spoiler_text : $('<p>').html(notification.status ? notification.status.content : '').text();
const notify = new Notification(title, { body, icon: notification.account.avatar, tag: notification.id }); new Notification(title, { body, icon: notification.account.avatar, tag: notification.id });
notify.addEventListener('click', () => {
window.focus();
notify.close();
});
} }
}; };
}; };
@@ -68,78 +54,68 @@ const excludeTypesFromSettings = state => state.getIn(['settings', 'notification
export function refreshNotifications() { export function refreshNotifications() {
return (dispatch, getState) => { return (dispatch, getState) => {
dispatch(refreshNotificationsRequest());
const params = {}; const params = {};
const ids = getState().getIn(['notifications', 'items']); const ids = getState().getIn(['notifications', 'items']);
let skipLoading = false;
if (ids.size > 0) { if (ids.size > 0) {
params.since_id = ids.first().get('id'); params.since_id = ids.first().get('id');
} }
if (getState().getIn(['notifications', 'loaded'])) {
skipLoading = true;
}
params.exclude_types = excludeTypesFromSettings(getState()); params.exclude_types = excludeTypesFromSettings(getState());
dispatch(refreshNotificationsRequest(skipLoading));
api(getState).get('/api/v1/notifications', { params }).then(response => { api(getState).get('/api/v1/notifications', { params }).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(refreshNotificationsSuccess(response.data, skipLoading, next ? next.uri : null)); dispatch(refreshNotificationsSuccess(response.data, next ? next.uri : null));
fetchRelatedRelationships(dispatch, response.data); fetchRelatedRelationships(dispatch, response.data);
}).catch(error => { }).catch(error => {
dispatch(refreshNotificationsFail(error, skipLoading)); dispatch(refreshNotificationsFail(error));
}); });
}; };
}; };
export function refreshNotificationsRequest(skipLoading) { export function refreshNotificationsRequest() {
return { return {
type: NOTIFICATIONS_REFRESH_REQUEST, type: NOTIFICATIONS_REFRESH_REQUEST
skipLoading,
}; };
}; };
export function refreshNotificationsSuccess(notifications, skipLoading, next) { export function refreshNotificationsSuccess(notifications, next) {
return { return {
type: NOTIFICATIONS_REFRESH_SUCCESS, type: NOTIFICATIONS_REFRESH_SUCCESS,
notifications, notifications,
accounts: notifications.map(item => item.account), accounts: notifications.map(item => item.account),
statuses: notifications.map(item => item.status).filter(status => !!status), statuses: notifications.map(item => item.status).filter(status => !!status),
skipLoading, next
next,
}; };
}; };
export function refreshNotificationsFail(error, skipLoading) { export function refreshNotificationsFail(error) {
return { return {
type: NOTIFICATIONS_REFRESH_FAIL, type: NOTIFICATIONS_REFRESH_FAIL,
error, error
skipLoading,
}; };
}; };
export function expandNotifications() { export function expandNotifications() {
return (dispatch, getState) => { return (dispatch, getState) => {
const items = getState().getIn(['notifications', 'items'], ImmutableList()); const url = getState().getIn(['notifications', 'next'], null);
if (getState().getIn(['notifications', 'isLoading']) || items.size === 0) { if (url === null || getState().getIn(['notifications', 'isLoading'])) {
return; return;
} }
const params = {
max_id: items.last().get('id'),
limit: 20,
exclude_types: excludeTypesFromSettings(getState()),
};
dispatch(expandNotificationsRequest()); dispatch(expandNotificationsRequest());
api(getState).get('/api/v1/notifications', { params }).then(response => { const params = {};
params.exclude_types = excludeTypesFromSettings(getState());
api(getState).get(url, params).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null)); dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null));
fetchRelatedRelationships(dispatch, response.data); fetchRelatedRelationships(dispatch, response.data);
}).catch(error => { }).catch(error => {
@@ -150,7 +126,7 @@ export function expandNotifications() {
export function expandNotificationsRequest() { export function expandNotificationsRequest() {
return { return {
type: NOTIFICATIONS_EXPAND_REQUEST, type: NOTIFICATIONS_EXPAND_REQUEST
}; };
}; };
@@ -160,21 +136,21 @@ export function expandNotificationsSuccess(notifications, next) {
notifications, notifications,
accounts: notifications.map(item => item.account), accounts: notifications.map(item => item.account),
statuses: notifications.map(item => item.status).filter(status => !!status), statuses: notifications.map(item => item.status).filter(status => !!status),
next, next
}; };
}; };
export function expandNotificationsFail(error) { export function expandNotificationsFail(error) {
return { return {
type: NOTIFICATIONS_EXPAND_FAIL, type: NOTIFICATIONS_EXPAND_FAIL,
error, error
}; };
}; };
export function clearNotifications() { export function clearNotifications() {
return (dispatch, getState) => { return (dispatch, getState) => {
dispatch({ dispatch({
type: NOTIFICATIONS_CLEAR, type: NOTIFICATIONS_CLEAR
}); });
api(getState).post('/api/v1/notifications/clear'); api(getState).post('/api/v1/notifications/clear');
@@ -184,6 +160,6 @@ export function clearNotifications() {
export function scrollTopNotifications(top) { export function scrollTopNotifications(top) {
return { return {
type: NOTIFICATIONS_SCROLL_TOP, type: NOTIFICATIONS_SCROLL_TOP,
top, top
}; };
}; };

View File

@@ -1,5 +1,4 @@
import api from '../api'; import api from '../api';
import { openModal, closeModal } from './modal';
export const REPORT_INIT = 'REPORT_INIT'; export const REPORT_INIT = 'REPORT_INIT';
export const REPORT_CANCEL = 'REPORT_CANCEL'; export const REPORT_CANCEL = 'REPORT_CANCEL';
@@ -12,20 +11,16 @@ export const REPORT_STATUS_TOGGLE = 'REPORT_STATUS_TOGGLE';
export const REPORT_COMMENT_CHANGE = 'REPORT_COMMENT_CHANGE'; export const REPORT_COMMENT_CHANGE = 'REPORT_COMMENT_CHANGE';
export function initReport(account, status) { export function initReport(account, status) {
return dispatch => { return {
dispatch({ type: REPORT_INIT,
type: REPORT_INIT, account,
account, status
status,
});
dispatch(openModal('REPORT'));
}; };
}; };
export function cancelReport() { export function cancelReport() {
return { return {
type: REPORT_CANCEL, type: REPORT_CANCEL
}; };
}; };
@@ -44,37 +39,34 @@ export function submitReport() {
api(getState).post('/api/v1/reports', { api(getState).post('/api/v1/reports', {
account_id: getState().getIn(['reports', 'new', 'account_id']), account_id: getState().getIn(['reports', 'new', 'account_id']),
status_ids: getState().getIn(['reports', 'new', 'status_ids']), status_ids: getState().getIn(['reports', 'new', 'status_ids']),
comment: getState().getIn(['reports', 'new', 'comment']), comment: getState().getIn(['reports', 'new', 'comment'])
}).then(response => { }).then(response => dispatch(submitReportSuccess(response.data))).catch(error => dispatch(submitReportFail(error)));
dispatch(closeModal());
dispatch(submitReportSuccess(response.data));
}).catch(error => dispatch(submitReportFail(error)));
}; };
}; };
export function submitReportRequest() { export function submitReportRequest() {
return { return {
type: REPORT_SUBMIT_REQUEST, type: REPORT_SUBMIT_REQUEST
}; };
}; };
export function submitReportSuccess(report) { export function submitReportSuccess(report) {
return { return {
type: REPORT_SUBMIT_SUCCESS, type: REPORT_SUBMIT_SUCCESS,
report, report
}; };
}; };
export function submitReportFail(error) { export function submitReportFail(error) {
return { return {
type: REPORT_SUBMIT_FAIL, type: REPORT_SUBMIT_FAIL,
error, error
}; };
}; };
export function changeReportComment(comment) { export function changeReportComment(comment) {
return { return {
type: REPORT_COMMENT_CHANGE, type: REPORT_COMMENT_CHANGE,
comment, comment
}; };
}; };

View File

@@ -1,4 +1,4 @@
import api from '../api'; import api from '../api'
export const SEARCH_CHANGE = 'SEARCH_CHANGE'; export const SEARCH_CHANGE = 'SEARCH_CHANGE';
export const SEARCH_CLEAR = 'SEARCH_CLEAR'; export const SEARCH_CLEAR = 'SEARCH_CLEAR';
@@ -11,13 +11,13 @@ export const SEARCH_FETCH_FAIL = 'SEARCH_FETCH_FAIL';
export function changeSearch(value) { export function changeSearch(value) {
return { return {
type: SEARCH_CHANGE, type: SEARCH_CHANGE,
value, value
}; };
}; };
export function clearSearch() { export function clearSearch() {
return { return {
type: SEARCH_CLEAR, type: SEARCH_CLEAR
}; };
}; };
@@ -34,8 +34,8 @@ export function submitSearch() {
api(getState).get('/api/v1/search', { api(getState).get('/api/v1/search', {
params: { params: {
q: value, q: value,
resolve: true, resolve: true
}, }
}).then(response => { }).then(response => {
dispatch(fetchSearchSuccess(response.data)); dispatch(fetchSearchSuccess(response.data));
}).catch(error => { }).catch(error => {
@@ -46,7 +46,7 @@ export function submitSearch() {
export function fetchSearchRequest() { export function fetchSearchRequest() {
return { return {
type: SEARCH_FETCH_REQUEST, type: SEARCH_FETCH_REQUEST
}; };
}; };
@@ -55,19 +55,19 @@ export function fetchSearchSuccess(results) {
type: SEARCH_FETCH_SUCCESS, type: SEARCH_FETCH_SUCCESS,
results, results,
accounts: results.accounts, accounts: results.accounts,
statuses: results.statuses, statuses: results.statuses
}; };
}; };
export function fetchSearchFail(error) { export function fetchSearchFail(error) {
return { return {
type: SEARCH_FETCH_FAIL, type: SEARCH_FETCH_FAIL,
error, error
}; };
}; };
export function showSearch() { export function showSearch() {
return { return {
type: SEARCH_SHOW, type: SEARCH_SHOW
}; };
}; };

View File

@@ -3,21 +3,17 @@ import axios from 'axios';
export const SETTING_CHANGE = 'SETTING_CHANGE'; export const SETTING_CHANGE = 'SETTING_CHANGE';
export function changeSetting(key, value) { export function changeSetting(key, value) {
return dispatch => { return {
dispatch({ type: SETTING_CHANGE,
type: SETTING_CHANGE, key,
key, value
value,
});
dispatch(saveSettings());
}; };
}; };
export function saveSettings() { export function saveSettings() {
return (_, getState) => { return (_, getState) => {
axios.put('/api/web/settings', { axios.put('/api/web/settings', {
data: getState().get('settings').toJS(), data: getState().get('settings').toJS()
}); });
}; };
}; };

View File

@@ -15,19 +15,11 @@ export const CONTEXT_FETCH_REQUEST = 'CONTEXT_FETCH_REQUEST';
export const CONTEXT_FETCH_SUCCESS = 'CONTEXT_FETCH_SUCCESS'; export const CONTEXT_FETCH_SUCCESS = 'CONTEXT_FETCH_SUCCESS';
export const CONTEXT_FETCH_FAIL = 'CONTEXT_FETCH_FAIL'; export const CONTEXT_FETCH_FAIL = 'CONTEXT_FETCH_FAIL';
export const STATUS_MUTE_REQUEST = 'STATUS_MUTE_REQUEST';
export const STATUS_MUTE_SUCCESS = 'STATUS_MUTE_SUCCESS';
export const STATUS_MUTE_FAIL = 'STATUS_MUTE_FAIL';
export const STATUS_UNMUTE_REQUEST = 'STATUS_UNMUTE_REQUEST';
export const STATUS_UNMUTE_SUCCESS = 'STATUS_UNMUTE_SUCCESS';
export const STATUS_UNMUTE_FAIL = 'STATUS_UNMUTE_FAIL';
export function fetchStatusRequest(id, skipLoading) { export function fetchStatusRequest(id, skipLoading) {
return { return {
type: STATUS_FETCH_REQUEST, type: STATUS_FETCH_REQUEST,
id, id,
skipLoading, skipLoading
}; };
}; };
@@ -56,7 +48,7 @@ export function fetchStatusSuccess(status, skipLoading) {
return { return {
type: STATUS_FETCH_SUCCESS, type: STATUS_FETCH_SUCCESS,
status, status,
skipLoading, skipLoading
}; };
}; };
@@ -66,7 +58,7 @@ export function fetchStatusFail(id, error, skipLoading) {
id, id,
error, error,
skipLoading, skipLoading,
skipAlert: true, skipAlert: true
}; };
}; };
@@ -74,7 +66,7 @@ export function deleteStatus(id) {
return (dispatch, getState) => { return (dispatch, getState) => {
dispatch(deleteStatusRequest(id)); dispatch(deleteStatusRequest(id));
api(getState).delete(`/api/v1/statuses/${id}`).then(() => { api(getState).delete(`/api/v1/statuses/${id}`).then(response => {
dispatch(deleteStatusSuccess(id)); dispatch(deleteStatusSuccess(id));
dispatch(deleteFromTimelines(id)); dispatch(deleteFromTimelines(id));
}).catch(error => { }).catch(error => {
@@ -86,14 +78,14 @@ export function deleteStatus(id) {
export function deleteStatusRequest(id) { export function deleteStatusRequest(id) {
return { return {
type: STATUS_DELETE_REQUEST, type: STATUS_DELETE_REQUEST,
id: id, id: id
}; };
}; };
export function deleteStatusSuccess(id) { export function deleteStatusSuccess(id) {
return { return {
type: STATUS_DELETE_SUCCESS, type: STATUS_DELETE_SUCCESS,
id: id, id: id
}; };
}; };
@@ -101,7 +93,7 @@ export function deleteStatusFail(id, error) {
return { return {
type: STATUS_DELETE_FAIL, type: STATUS_DELETE_FAIL,
id: id, id: id,
error: error, error: error
}; };
}; };
@@ -113,7 +105,7 @@ export function fetchContext(id) {
dispatch(fetchContextSuccess(id, response.data.ancestors, response.data.descendants)); dispatch(fetchContextSuccess(id, response.data.ancestors, response.data.descendants));
}).catch(error => { }).catch(error => {
if (error.response && error.response.status === 404) { if (error.response.status === 404) {
dispatch(deleteFromTimelines(id)); dispatch(deleteFromTimelines(id));
} }
@@ -125,7 +117,7 @@ export function fetchContext(id) {
export function fetchContextRequest(id) { export function fetchContextRequest(id) {
return { return {
type: CONTEXT_FETCH_REQUEST, type: CONTEXT_FETCH_REQUEST,
id, id
}; };
}; };
@@ -135,7 +127,7 @@ export function fetchContextSuccess(id, ancestors, descendants) {
id, id,
ancestors, ancestors,
descendants, descendants,
statuses: ancestors.concat(descendants), statuses: ancestors.concat(descendants)
}; };
}; };
@@ -144,74 +136,6 @@ export function fetchContextFail(id, error) {
type: CONTEXT_FETCH_FAIL, type: CONTEXT_FETCH_FAIL,
id, id,
error, error,
skipAlert: true, skipAlert: true
};
};
export function muteStatus(id) {
return (dispatch, getState) => {
dispatch(muteStatusRequest(id));
api(getState).post(`/api/v1/statuses/${id}/mute`).then(() => {
dispatch(muteStatusSuccess(id));
}).catch(error => {
dispatch(muteStatusFail(id, error));
});
};
};
export function muteStatusRequest(id) {
return {
type: STATUS_MUTE_REQUEST,
id,
};
};
export function muteStatusSuccess(id) {
return {
type: STATUS_MUTE_SUCCESS,
id,
};
};
export function muteStatusFail(id, error) {
return {
type: STATUS_MUTE_FAIL,
id,
error,
};
};
export function unmuteStatus(id) {
return (dispatch, getState) => {
dispatch(unmuteStatusRequest(id));
api(getState).post(`/api/v1/statuses/${id}/unmute`).then(() => {
dispatch(unmuteStatusSuccess(id));
}).catch(error => {
dispatch(unmuteStatusFail(id, error));
});
};
};
export function unmuteStatusRequest(id) {
return {
type: STATUS_UNMUTE_REQUEST,
id,
};
};
export function unmuteStatusSuccess(id) {
return {
type: STATUS_UNMUTE_SUCCESS,
id,
};
};
export function unmuteStatusFail(id, error) {
return {
type: STATUS_UNMUTE_FAIL,
id,
error,
}; };
}; };

View File

@@ -1,11 +1,10 @@
import { Iterable, fromJS } from 'immutable'; import Immutable from 'immutable';
export const STORE_HYDRATE = 'STORE_HYDRATE'; export const STORE_HYDRATE = 'STORE_HYDRATE';
export const STORE_HYDRATE_LAZY = 'STORE_HYDRATE_LAZY';
const convertState = rawState => const convertState = rawState =>
fromJS(rawState, (k, v) => Immutable.fromJS(rawState, (k, v) =>
Iterable.isIndexed(v) ? v.toList() : v.toMap().mapKeys(x => Immutable.Iterable.isIndexed(v) ? v.toList() : v.toMap().mapKeys(x =>
Number.isNaN(x * 1) ? x : x * 1)); Number.isNaN(x * 1) ? x : x * 1));
export function hydrateStore(rawState) { export function hydrateStore(rawState) {
@@ -13,6 +12,6 @@ export function hydrateStore(rawState) {
return { return {
type: STORE_HYDRATE, type: STORE_HYDRATE,
state, state
}; };
}; };

View File

@@ -1,5 +1,5 @@
import api, { getLinks } from '../api'; import api, { getLinks } from '../api'
import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; import Immutable from 'immutable';
export const TIMELINE_UPDATE = 'TIMELINE_UPDATE'; export const TIMELINE_UPDATE = 'TIMELINE_UPDATE';
export const TIMELINE_DELETE = 'TIMELINE_DELETE'; export const TIMELINE_DELETE = 'TIMELINE_DELETE';
@@ -23,7 +23,7 @@ export function refreshTimelineSuccess(timeline, statuses, skipLoading, next) {
timeline, timeline,
statuses, statuses,
skipLoading, skipLoading,
next, next
}; };
}; };
@@ -35,7 +35,7 @@ export function updateTimeline(timeline, status) {
type: TIMELINE_UPDATE, type: TIMELINE_UPDATE,
timeline, timeline,
status, status,
references, references
}); });
}; };
}; };
@@ -51,98 +51,98 @@ export function deleteFromTimelines(id) {
id, id,
accountId, accountId,
references, references,
reblogOf, reblogOf
}); });
}; };
}; };
export function refreshTimelineRequest(timeline, skipLoading) { export function refreshTimelineRequest(timeline, id, skipLoading) {
return { return {
type: TIMELINE_REFRESH_REQUEST, type: TIMELINE_REFRESH_REQUEST,
timeline, timeline,
skipLoading, id,
skipLoading
}; };
}; };
export function refreshTimeline(timelineId, path, params = {}) { export function refreshTimeline(timeline, id = null) {
return function (dispatch, getState) { return function (dispatch, getState) {
const timeline = getState().getIn(['timelines', timelineId], ImmutableMap()); if (getState().getIn(['timelines', timeline, 'isLoading'])) {
if (timeline.get('isLoading') || timeline.get('online')) {
return; return;
} }
const ids = timeline.get('items', ImmutableList()); const ids = getState().getIn(['timelines', timeline, 'items'], Immutable.List());
const newestId = ids.size > 0 ? ids.first() : null; const newestId = ids.size > 0 ? ids.first() : null;
let params = getState().getIn(['timelines', timeline, 'params'], {});
const path = getState().getIn(['timelines', timeline, 'path'])(id);
let skipLoading = timeline.get('loaded'); let skipLoading = false;
if (newestId !== null) { if (newestId !== null && getState().getIn(['timelines', timeline, 'loaded']) && (id === null || getState().getIn(['timelines', timeline, 'id']) === id)) {
params.since_id = newestId; if (id === null && getState().getIn(['timelines', timeline, 'online'])) {
// Skip refreshing when timeline is live anyway
return;
}
params = { ...params, since_id: newestId };
skipLoading = true;
} }
dispatch(refreshTimelineRequest(timelineId, skipLoading)); dispatch(refreshTimelineRequest(timeline, id, skipLoading));
api(getState).get(path, { params }).then(response => { api(getState).get(path, { params }).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(refreshTimelineSuccess(timelineId, response.data, skipLoading, next ? next.uri : null)); dispatch(refreshTimelineSuccess(timeline, response.data, skipLoading, next ? next.uri : null));
}).catch(error => { }).catch(error => {
dispatch(refreshTimelineFail(timelineId, error, skipLoading)); dispatch(refreshTimelineFail(timeline, error, skipLoading));
}); });
}; };
}; };
export const refreshHomeTimeline = () => refreshTimeline('home', '/api/v1/timelines/home');
export const refreshPublicTimeline = () => refreshTimeline('public', '/api/v1/timelines/public');
export const refreshCommunityTimeline = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true });
export const refreshAccountTimeline = accountId => refreshTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`);
export const refreshAccountMediaTimeline = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true });
export const refreshHashtagTimeline = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
export function refreshTimelineFail(timeline, error, skipLoading) { export function refreshTimelineFail(timeline, error, skipLoading) {
return { return {
type: TIMELINE_REFRESH_FAIL, type: TIMELINE_REFRESH_FAIL,
timeline, timeline,
error, error,
skipLoading, skipLoading
skipAlert: error.response && error.response.status === 404,
}; };
}; };
export function expandTimeline(timelineId, path, params = {}) { export function expandTimeline(timeline) {
return (dispatch, getState) => { return (dispatch, getState) => {
const timeline = getState().getIn(['timelines', timelineId], ImmutableMap()); if (getState().getIn(['timelines', timeline, 'isLoading'])) {
const ids = timeline.get('items', ImmutableList());
if (timeline.get('isLoading') || ids.size === 0) {
return; return;
} }
params.max_id = ids.last(); if (getState().getIn(['timelines', timeline, 'items']).size === 0) {
params.limit = 10; return;
}
dispatch(expandTimelineRequest(timelineId)); const path = getState().getIn(['timelines', timeline, 'path'])(getState().getIn(['timelines', timeline, 'id']));
const params = getState().getIn(['timelines', timeline, 'params'], {});
const lastId = getState().getIn(['timelines', timeline, 'items']).last();
api(getState).get(path, { params }).then(response => { dispatch(expandTimelineRequest(timeline));
api(getState).get(path, {
params: {
...params,
max_id: lastId,
limit: 10
}
}).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null)); dispatch(expandTimelineSuccess(timeline, response.data, next ? next.uri : null));
}).catch(error => { }).catch(error => {
dispatch(expandTimelineFail(timelineId, error)); dispatch(expandTimelineFail(timeline, error));
}); });
}; };
}; };
export const expandHomeTimeline = () => expandTimeline('home', '/api/v1/timelines/home');
export const expandPublicTimeline = () => expandTimeline('public', '/api/v1/timelines/public');
export const expandCommunityTimeline = () => expandTimeline('community', '/api/v1/timelines/public', { local: true });
export const expandAccountTimeline = accountId => expandTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`);
export const expandAccountMediaTimeline = accountId => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true });
export const expandHashtagTimeline = hashtag => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`);
export function expandTimelineRequest(timeline) { export function expandTimelineRequest(timeline) {
return { return {
type: TIMELINE_EXPAND_REQUEST, type: TIMELINE_EXPAND_REQUEST,
timeline, timeline
}; };
}; };
@@ -151,7 +151,7 @@ export function expandTimelineSuccess(timeline, statuses, next) {
type: TIMELINE_EXPAND_SUCCESS, type: TIMELINE_EXPAND_SUCCESS,
timeline, timeline,
statuses, statuses,
next, next
}; };
}; };
@@ -159,7 +159,7 @@ export function expandTimelineFail(timeline, error) {
return { return {
type: TIMELINE_EXPAND_FAIL, type: TIMELINE_EXPAND_FAIL,
timeline, timeline,
error, error
}; };
}; };
@@ -167,20 +167,20 @@ export function scrollTopTimeline(timeline, top) {
return { return {
type: TIMELINE_SCROLL_TOP, type: TIMELINE_SCROLL_TOP,
timeline, timeline,
top, top
}; };
}; };
export function connectTimeline(timeline) { export function connectTimeline(timeline) {
return { return {
type: TIMELINE_CONNECT, type: TIMELINE_CONNECT,
timeline, timeline
}; };
}; };
export function disconnectTimeline(timeline) { export function disconnectTimeline(timeline) {
return { return {
type: TIMELINE_DISCONNECT, type: TIMELINE_DISCONNECT,
timeline, timeline
}; };
}; };

View File

@@ -13,7 +13,7 @@ export const getLinks = response => {
export default getState => axios.create({ export default getState => axios.create({
headers: { headers: {
'Authorization': `Bearer ${getState().getIn(['meta', 'access_token'], '')}`, 'Authorization': `Bearer ${getState().getIn(['meta', 'access_token'], '')}`
}, },
transformResponse: [function (data) { transformResponse: [function (data) {
@@ -22,5 +22,5 @@ export default getState => axios.create({
} catch(Exception) { } catch(Exception) {
return data; return data;
} }
}], }]
}); });

View File

@@ -1,4 +1,3 @@
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Avatar from './avatar'; import Avatar from './avatar';
@@ -6,57 +5,43 @@ import DisplayName from './display_name';
import Permalink from './permalink'; 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';
const messages = defineMessages({ const messages = defineMessages({
follow: { id: 'account.follow', defaultMessage: 'Follow' }, follow: { id: 'account.follow', defaultMessage: 'Follow' },
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' }, requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' },
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }, unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }
}); });
@injectIntl class Account extends React.PureComponent {
export default class Account extends ImmutablePureComponent {
static propTypes = { constructor (props, context) {
account: ImmutablePropTypes.map.isRequired, super(props, context);
me: PropTypes.number.isRequired, this.handleFollow = this.handleFollow.bind(this);
onFollow: PropTypes.func.isRequired, this.handleBlock = this.handleBlock.bind(this);
onBlock: PropTypes.func.isRequired, this.handleMute = this.handleMute.bind(this);
onMute: PropTypes.func.isRequired, }
intl: PropTypes.object.isRequired,
hidden: PropTypes.bool,
};
handleFollow = () => { handleFollow () {
this.props.onFollow(this.props.account); this.props.onFollow(this.props.account);
} }
handleBlock = () => { handleBlock () {
this.props.onBlock(this.props.account); this.props.onBlock(this.props.account);
} }
handleMute = () => { handleMute () {
this.props.onMute(this.props.account); this.props.onMute(this.props.account);
} }
render () { render () {
const { account, me, intl, hidden } = this.props; const { account, me, intl } = this.props;
if (!account) { if (!account) {
return <div />; return <div />;
} }
if (hidden) {
return (
<div>
{account.get('display_name')}
{account.get('username')}
</div>
);
}
let buttons; let buttons;
if (account.get('id') !== me && account.get('relationship', null) !== null) { if (account.get('id') !== me && account.get('relationship', null) !== null) {
@@ -66,11 +51,11 @@ export default class Account extends ImmutablePureComponent {
const muting = account.getIn(['relationship', 'muting']); const muting = account.getIn(['relationship', 'muting']);
if (requested) { if (requested) {
buttons = <IconButton disabled icon='hourglass' title={intl.formatMessage(messages.requested)} />; buttons = <IconButton disabled={true} icon='hourglass' title={intl.formatMessage(messages.requested)} />
} else if (blocking) { } else if (blocking) {
buttons = <IconButton active icon='unlock-alt' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.handleBlock} />; buttons = <IconButton active={true} icon='unlock-alt' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.handleBlock} />;
} else if (muting) { } else if (muting) {
buttons = <IconButton active icon='volume-up' title={intl.formatMessage(messages.unmute, { name: account.get('username') })} onClick={this.handleMute} />; buttons = <IconButton active={true} icon='volume-up' title={intl.formatMessage(messages.unmute, { name: account.get('username') })} onClick={this.handleMute} />;
} 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} />;
} }
@@ -80,7 +65,7 @@ export default class Account extends ImmutablePureComponent {
<div className='account'> <div className='account'>
<div className='account__wrapper'> <div className='account__wrapper'>
<Permalink key={account.get('id')} className='account__display-name' href={account.get('url')} to={`/accounts/${account.get('id')}`}> <Permalink key={account.get('id')} className='account__display-name' href={account.get('url')} to={`/accounts/${account.get('id')}`}>
<div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div> <div className='account__avatar-wrapper'><Avatar src={account.get('avatar')} staticSrc={account.get('avatar_static')} size={36} /></div>
<DisplayName account={account} /> <DisplayName account={account} />
</Permalink> </Permalink>
@@ -93,3 +78,14 @@ export default class Account extends ImmutablePureComponent {
} }
} }
Account.propTypes = {
account: ImmutablePropTypes.map.isRequired,
me: PropTypes.number.isRequired,
onFollow: PropTypes.func.isRequired,
onBlock: PropTypes.func.isRequired,
onMute: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired
}
export default injectIntl(Account);

View File

@@ -1,14 +1,8 @@
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
const filename = url => url.split('/').pop().split('#')[0].split('?')[0]; const filename = url => url.split('/').pop().split('#')[0].split('?')[0];
export default class AttachmentList extends ImmutablePureComponent { class AttachmentList extends React.PureComponent {
static propTypes = {
media: ImmutablePropTypes.list.isRequired,
};
render () { render () {
const { media } = this.props; const { media } = this.props;
@@ -29,5 +23,10 @@ export default class AttachmentList extends ImmutablePureComponent {
</div> </div>
); );
} }
} }
AttachmentList.propTypes = {
media: ImmutablePropTypes.list.isRequired
};
export default AttachmentList;

View File

@@ -1,10 +1,7 @@
import React from 'react';
import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container'; import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { isRtl } from '../rtl'; import { isRtl } from '../rtl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Textarea from 'react-textarea-autosize';
const textAtCursorMatchesToken = (str, caretPosition) => { const textAtCursorMatchesToken = (str, caretPosition) => {
let word; let word;
@@ -31,35 +28,25 @@ const textAtCursorMatchesToken = (str, caretPosition) => {
} }
}; };
export default class AutosuggestTextarea extends ImmutablePureComponent { class AutosuggestTextarea extends React.Component {
static propTypes = { constructor (props, context) {
value: PropTypes.string, super(props, context);
suggestions: ImmutablePropTypes.list, this.state = {
disabled: PropTypes.bool, suggestionsHidden: false,
placeholder: PropTypes.string, selectedSuggestion: 0,
onSuggestionSelected: PropTypes.func.isRequired, lastToken: null,
onSuggestionsClearRequested: PropTypes.func.isRequired, tokenStart: 0
onSuggestionsFetchRequested: PropTypes.func.isRequired, };
onChange: PropTypes.func.isRequired, this.onChange = this.onChange.bind(this);
onKeyUp: PropTypes.func, this.onKeyDown = this.onKeyDown.bind(this);
onKeyDown: PropTypes.func, this.onBlur = this.onBlur.bind(this);
onPaste: PropTypes.func.isRequired, this.onSuggestionClick = this.onSuggestionClick.bind(this);
autoFocus: PropTypes.bool, this.setTextarea = this.setTextarea.bind(this);
}; this.onPaste = this.onPaste.bind(this);
}
static defaultProps = { onChange (e) {
autoFocus: true,
};
state = {
suggestionsHidden: false,
selectedSuggestion: 0,
lastToken: null,
tokenStart: 0,
};
onChange = (e) => {
const [ tokenStart, token ] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart); const [ tokenStart, token ] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart);
if (token !== null && this.state.lastToken !== token) { if (token !== null && this.state.lastToken !== token) {
@@ -70,10 +57,14 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
this.props.onSuggestionsClearRequested(); this.props.onSuggestionsClearRequested();
} }
// auto-resize textarea
e.target.style.height = 'auto';
e.target.style.height = `${e.target.scrollHeight}px`;
this.props.onChange(e); this.props.onChange(e);
} }
onKeyDown = (e) => { onKeyDown (e) {
const { suggestions, disabled } = this.props; const { suggestions, disabled } = this.props;
const { selectedSuggestion, suggestionsHidden } = this.state; const { selectedSuggestion, suggestionsHidden } = this.state;
@@ -123,12 +114,17 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
this.props.onKeyDown(e); this.props.onKeyDown(e);
} }
onBlur = () => { onBlur () {
this.setState({ suggestionsHidden: true }); // If we hide the suggestions immediately, then this will prevent the
// onClick for the suggestions themselves from firing.
// Setting a short window for that to take place before hiding the
// suggestions ensures that can't happen.
setTimeout(() => {
this.setState({ suggestionsHidden: true });
}, 100);
} }
onSuggestionClick = (e) => { onSuggestionClick (suggestion, e) {
const suggestion = Number(e.currentTarget.getAttribute('data-index'));
e.preventDefault(); e.preventDefault();
this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion); this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion);
this.textarea.focus(); this.textarea.focus();
@@ -140,19 +136,19 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
} }
} }
setTextarea = (c) => { setTextarea (c) {
this.textarea = c; this.textarea = c;
} }
onPaste = (e) => { onPaste (e) {
if (e.clipboardData && e.clipboardData.files.length === 1) { if (e.clipboardData && e.clipboardData.files.length === 1) {
this.props.onPaste(e.clipboardData.files); this.props.onPaste(e.clipboardData.files)
e.preventDefault(); e.preventDefault();
} }
} }
render () { render () {
const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus } = this.props; const { value, suggestions, disabled, placeholder, onKeyUp } = this.props;
const { suggestionsHidden, selectedSuggestion } = this.state; const { suggestionsHidden, selectedSuggestion } = this.state;
const style = { direction: 'ltr' }; const style = { direction: 'ltr' };
@@ -162,34 +158,29 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
return ( return (
<div className='autosuggest-textarea'> <div className='autosuggest-textarea'>
<label> <textarea
<span style={{ display: 'none' }}>{placeholder}</span> ref={this.setTextarea}
<Textarea className='autosuggest-textarea__textarea'
inputRef={this.setTextarea} disabled={disabled}
className='autosuggest-textarea__textarea' placeholder={placeholder}
disabled={disabled} autoFocus={true}
placeholder={placeholder} value={value}
autoFocus={autoFocus} onChange={this.onChange}
value={value} onKeyDown={this.onKeyDown}
onChange={this.onChange} onKeyUp={onKeyUp}
onKeyDown={this.onKeyDown} onBlur={this.onBlur}
onKeyUp={onKeyUp} onPaste={this.onPaste}
onBlur={this.onBlur} style={style}
onPaste={this.onPaste} />
style={style}
/>
</label>
<div className={`autosuggest-textarea__suggestions ${suggestionsHidden || suggestions.isEmpty() ? '' : 'autosuggest-textarea__suggestions--visible'}`}> <div style={{ display: (suggestions.size > 0 && !suggestionsHidden) ? 'block' : 'none' }} className='autosuggest-textarea__suggestions'>
{suggestions.map((suggestion, i) => ( {suggestions.map((suggestion, i) => (
<div <div
role='button' role='button'
tabIndex='0' tabIndex='0'
key={suggestion} key={suggestion}
data-index={suggestion}
className={`autosuggest-textarea__suggestions__item ${i === selectedSuggestion ? 'selected' : ''}`} className={`autosuggest-textarea__suggestions__item ${i === selectedSuggestion ? 'selected' : ''}`}
onMouseDown={this.onSuggestionClick} onClick={this.onSuggestionClick.bind(this, suggestion)}>
>
<AutosuggestAccountContainer id={suggestion} /> <AutosuggestAccountContainer id={suggestion} />
</div> </div>
))} ))}
@@ -198,4 +189,20 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
); );
} }
} };
AutosuggestTextarea.propTypes = {
value: PropTypes.string,
suggestions: ImmutablePropTypes.list,
disabled: PropTypes.bool,
placeholder: PropTypes.string,
onSuggestionSelected: PropTypes.func.isRequired,
onSuggestionsClearRequested: PropTypes.func.isRequired,
onSuggestionsFetchRequested: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
onKeyUp: PropTypes.func,
onKeyDown: PropTypes.func,
onPaste: PropTypes.func.isRequired,
};
export default AutosuggestTextarea;

View File

@@ -0,0 +1,63 @@
import PropTypes from 'prop-types';
class Avatar extends React.PureComponent {
constructor (props, context) {
super(props, context);
this.state = {
hovering: false
};
this.handleMouseEnter = this.handleMouseEnter.bind(this);
this.handleMouseLeave = this.handleMouseLeave.bind(this);
}
handleMouseEnter () {
this.setState({ hovering: true });
}
handleMouseLeave () {
this.setState({ hovering: false });
}
render () {
const { src, size, staticSrc, animate } = this.props;
const { hovering } = this.state;
const style = {
...this.props.style,
width: `${size}px`,
height: `${size}px`,
backgroundSize: `${size}px ${size}px`
};
if (hovering || animate) {
style.backgroundImage = `url(${src})`;
} else {
style.backgroundImage = `url(${staticSrc})`;
}
return (
<div
className='account__avatar'
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
style={style}
/>
);
}
}
Avatar.propTypes = {
src: PropTypes.string.isRequired,
staticSrc: PropTypes.string,
size: PropTypes.number.isRequired,
style: PropTypes.object,
animate: PropTypes.bool
};
Avatar.defaultProps = {
animate: false
};
export default Avatar;

View File

@@ -0,0 +1,49 @@
import PropTypes from 'prop-types';
class Button extends React.PureComponent {
constructor (props, context) {
super(props, context);
this.handleClick = this.handleClick.bind(this);
}
handleClick (e) {
if (!this.props.disabled) {
this.props.onClick();
}
}
render () {
const style = {
display: this.props.block ? 'block' : 'inline-block',
width: this.props.block ? '100%' : 'auto',
padding: `0 ${this.props.size / 2.25}px`,
height: `${this.props.size}px`,
lineHeight: `${this.props.size}px`
};
return (
<button className={`button ${this.props.secondary ? 'button-secondary' : ''}`} disabled={this.props.disabled} onClick={this.handleClick} style={{ ...style, ...this.props.style }}>
{this.props.text || this.props.children}
</button>
);
}
}
Button.propTypes = {
text: PropTypes.node,
onClick: PropTypes.func,
disabled: PropTypes.bool,
block: PropTypes.bool,
secondary: PropTypes.bool,
size: PropTypes.number,
style: PropTypes.object,
children: PropTypes.node
};
Button.defaultProps = {
size: 36
};
export default Button;

View File

@@ -1,6 +1,4 @@
import React from 'react'; import { Motion, spring } from 'react-motion';
import Motion from 'react-motion/lib/Motion';
import spring from 'react-motion/lib/spring';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
const Collapsable = ({ fullHeight, isVisible, children }) => ( const Collapsable = ({ fullHeight, isVisible, children }) => (
@@ -16,7 +14,7 @@ const Collapsable = ({ fullHeight, isVisible, children }) => (
Collapsable.propTypes = { Collapsable.propTypes = {
fullHeight: PropTypes.number.isRequired, fullHeight: PropTypes.number.isRequired,
isVisible: PropTypes.bool.isRequired, isVisible: PropTypes.bool.isRequired,
children: PropTypes.node.isRequired, children: PropTypes.node.isRequired
}; };
export default Collapsable; export default Collapsable;

View File

@@ -0,0 +1,31 @@
import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
class ColumnBackButton extends React.PureComponent {
constructor (props, context) {
super(props, context);
this.handleClick = this.handleClick.bind(this);
}
handleClick () {
if (window.history && window.history.length === 1) this.context.router.push("/");
else this.context.router.goBack();
}
render () {
return (
<div role='button' tabIndex='0' onClick={this.handleClick} className='column-back-button'>
<i className='fa fa-fw fa-chevron-left column-back-button__icon'/>
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
</div>
);
}
};
ColumnBackButton.contextTypes = {
router: PropTypes.object
};
export default ColumnBackButton;

Some files were not shown because too many files have changed in this diff Show More