Compare commits

...

79 Commits

Author SHA1 Message Date
Eugen Rochko
bf50e3e5ae Fix height issue in report modal 2017-07-01 14:50:10 +02:00
Nolan Lawson
a978b88997 Faster emojify() algorithm, avoid regex replace (#4019)
* Faster emojify() algorithm, avoid regex replace

* add semicolon
2017-06-30 17:29:22 +02:00
Matt Jankowski
6dd5eac7fc Add controller spec for manifests controller (#4003) 2017-06-30 13:43:34 +02:00
Nolan Lawson
968354923e Fix webpack-dev-server on Windows (#4000)
* Fix webpack-dev-server on Windows

* Serve webpack from 0.0.0.0, access at 127.0.0.1
2017-06-30 13:43:26 +02:00
Matt Jankowski
59ddf81a45 Version bumps for gems (#4002)
* Update aws-sdk to version 2.10.4

* Update bootsnap to version 1.1.1

* Update capistrano to version 3.8.2

* Update capybara to version 2.14.4

* Update cld3 to version 3.1.3

* Update http_accept_language to version 2.1.1

* Update sidekiq to version 5.0.3

* Update rspec-sidekiq to version 3.0.3

* Update sidekiq-scheduler to version 2.1.7

* Update oj to version 3.2.0

* Update openssl to version 2.0.4

* Update pg to version 0.21.0

* Update twitter-text to version 1.14.6

* Update unicode-display_width to version 1.3.0

* Update scss_lint to version 0.54.0

* Update hamlit to version 2.8.4

* Update erubi to version 1.6.1

* Update httplog to version 0.99.4

* Update aws-sdk to version 2.10.6
2017-06-30 13:42:04 +02:00
Yamagishi Kazutoshi
3a7106f05a Fix that AdminMailer does not send (#4012) 2017-06-30 13:40:43 +02:00
Yamagishi Kazutoshi
5c7a4f0b32 Remove babel-cli (#4011) 2017-06-30 13:40:14 +02:00
Yamagishi Kazutoshi
0e09048537 Fix broken style in media gallery (regression from #3963) (#4014) 2017-06-30 13:40:00 +02:00
Akihiko Odaki (@fn_aki@pawoo.net)
7362469d89 Do not raise an error if PrecomputeFeed could not find any status (#4015) 2017-06-30 13:39:42 +02:00
abcang
1273fbf86e Rescue Addressable::URI::InvalidURIError at Remotable (#4017) 2017-06-30 13:38:36 +02:00
Yamagishi Kazutoshi
a27879c0cf Replace state to /web when root path (#4009) 2017-06-30 05:37:41 +02:00
Naoki Kosaka
049cea30b0 Fix media-gallery, overflow is hidden. (#4008) 2017-06-30 05:37:17 +02:00
abcang
b342c81c17 rescue HTTP::ConnectionError (#3992) 2017-06-29 13:04:07 +02:00
Yamagishi Kazutoshi
ead14f5bf0 Upgrade jsdom to version 11.0.0 (#3994) 2017-06-29 13:03:03 +02:00
Akihiko Odaki (@fn_aki@pawoo.net)
0a53ca444a Cover Admin::AccountsController more (#3327) 2017-06-29 01:43:10 +02:00
Akihiko Odaki (@fn_aki@pawoo.net)
f79c10162e Use multiple pairs for zadd in PrecomputeFeedService (#3990) 2017-06-29 01:25:31 +02:00
Akihiko Odaki (@fn_aki@pawoo.net)
60b2b56d38 Reduce number of commands in FeedManager#trim (#3989) 2017-06-29 01:17:26 +02:00
Eugen Rochko
b6a19e7b89 Bump version to 1.4.7 2017-06-28 17:44:17 +02:00
Eugen Rochko
71bc75e6ac Do not fail to create access token if superapp was never created (#3986) 2017-06-28 17:43:48 +02:00
Yamagishi Kazutoshi
e4fee6c138 Add Japanese translations (#3985)
ref #3929, #3935, #3949, #3981
2017-06-28 16:45:21 +02:00
Akihiko Odaki (@fn_aki@pawoo.net)
7d8e3721ae Overwrite old statuses with reblogs in PrecomputeFeedService (#3984) 2017-06-28 14:50:23 +02:00
m4sk1n
fb421a1f46 i18n: added email to activerecord.pl.yml (#3981) 2017-06-28 14:07:53 +02:00
m4sk1n
2a9805b987 i18n: Minor fix in devise.pl.yml (#3978) 2017-06-27 23:14:02 +02:00
m4sk1n
126f929c39 i18n: Use instance name in email notifications instead of Mastodon (pl) (#3976)
Signed-off-by: Marcin Mikołajczak <me@m4sk.in>
2017-06-27 23:10:43 +02:00
m4sk1n
da42bfadb5 i18n: E-mail notifications to admins about new reports (pl) (#3975) 2017-06-27 22:21:35 +02:00
m4sk1n
6ad72728f6 i18n: Turn report screen into a modal (pl) (#3974) 2017-06-27 22:14:31 +02:00
Sorin Davidoi
64d9c016bd fix(components/status): Up & down jump due to content being added to the DOM (#3972) 2017-06-27 18:43:53 +02:00
Eugen Rochko
12e7c81dd8 Turn report screen into a modal (#3965) 2017-06-27 18:07:21 +02:00
Midgard
16d0aed403 Use instance name in email notifications instead of "Mastodon" (#3763)
* Use instance name in "password changed" mail

instead of "Mastodon".

Fixes tootsuite#2620.

* Use instance name in password reset mail

instead of "Mastodon".
2017-06-27 14:22:36 +02:00
Debanshu Kundu
da9317fa56 #1456 Added rake task to add a user. (#1482) 2017-06-27 14:18:53 +02:00
Sorin Davidoi
be92babd00 Responsive images in media gallery (#3963)
* feat(components/media_gallery): Responsive images

* fix(components/media_gallery): Link to image URL
2017-06-27 13:46:37 +02:00
Yamagishi Kazutoshi
e2dd576a1b Update dependencies for Node.js (#3967)
* Update @storybook/addon-actions to v3.1.6

* Update @storybook/react to v3.1.6

* Update babel-loader to v7.1.0

* Update babel-plugin-transform-react-remove-prop-types to v0.4.6

* Update enzyme to v2.9.1

* Update fsevents to v1.1.2

* Update intersection-observer to v0.3.2

* Update npmlog to v4.1.2

* Update pg to v6.4.0

* Update postcss-loader to v2.0.6

* Update rails-ujs to v5.1.2

* Update react to v15.6.1

* Update react-addons-shallow-compare to v15.6.0

* Update react-dom to v15.6.0

* Update react-notification to v6.7.1

* Update react-test-renderer to v15.6.1

* Update react-textarea-autosize to v5.0.7

* Update redux to v3.7.1

* Update resolve-url-loader to v2.1.0

* Update sass-loader to v6.0.6

* Update sinon to v2.3.5

* Update stringz to v0.2.2

* Update uuid to v3.1.0

* Update websocket.js to v0.1.12

* Update yargs to v8.0.2

* yarn upgrade
2017-06-27 13:46:11 +02:00
Yamagishi Kazutoshi
8f2c91568c Maintain aspect ratio for preview image (#3966) 2017-06-27 13:43:53 +02:00
Yamagishi Kazutoshi
98eaa2aa27 Update Rails to v5.1.2 (#3968) 2017-06-27 13:41:03 +02:00
Eugen Rochko
42b8220632 Fix #1624 - Send e-mail notifications to admins about new reports (#3949) 2017-06-27 00:04:00 +02:00
ThibG
a91d968cab Raise an error if salmon request response is unsatisfactory (#3960) 2017-06-26 19:39:58 +02:00
m4sk1n
646de92781 i18n: Updated Polish translation (#3956)
* i18n: Updated Polish translation

* Update pl.yml
2017-06-26 17:18:45 +02:00
m4sk1n
ae2b722f55 i18n: Warning to look into the spam folder (pl) (#3955) 2017-06-26 17:10:54 +02:00
Daniel Hunsaker
7aeb9168b0 Add .gitattributes file to avoid unwanted CRLF (#3954)
When Windows checks out files, it defaults to changing line endings to CRLF. If these files are then copied to a Linux system to be run, and the endings aren't changed at some point in that process, things break. This file forces git to use LF for all text files on all systems (except the request testing specfiles) to prevent issues everywhere.
2017-06-26 13:15:24 +02:00
Alda Marteau-Hardi
f53ed108b0 Translate pin/unpin and fix some inconsistencies in gender neutral strings (#3952) 2017-06-26 13:04:36 +02:00
Yamagishi Kazutoshi
285038972b Stop using Babel with streaming server (#3950) 2017-06-26 04:49:39 +02:00
Takuya Yoshida
e5563843a2 Re-fix errorMiddleware (#3922) 2017-06-26 01:46:15 +02:00
unarist
c972e1ee1f Ignore DB_NAME for development env on streaming as well as rails side (#3948) 2017-06-26 01:45:50 +02:00
Eugen Rochko
5e8d037e27 Fix #3910 - Require OTP authentication to disable 2FA (#3935)
* Fix #3910 - Require OTP authentication to disable 2FA. Also, remove ability
to generate new OTP backup codes *after* initial backup codes were handed
out during activation

* Restore recovery code re-generation

* Improve display of some 2FA elements
2017-06-25 23:51:46 +02:00
Eugen Rochko
ed7dc1704d Bind web UI access tokens to sessions (#3940)
* Add overview of active sessions

* Better display of browser/platform name

* Improve how browser information is stored and displayed for sessions overview

* Fix test

* Fix #2347 - Bind web UI access token to session

When you logout, session also destroys the access token, so it's no longer
valid. If access token is destroyed some other way, the session is also
destroyed, requiring a re-login.

Fix #1681 - Add scheduler to remove revoked access tokens and grants

* Fix test
2017-06-25 23:51:32 +02:00
amazedkoumei
436ce03772 fix unnecessary variable (#3947) 2017-06-25 23:29:22 +02:00
Eugen Rochko
d821aba002 Rename "Credentials" page to "Security" for clarity (#3941)
* Rename "Credentials" page to "Security" for clarity

* Change "security" icon from cog to lock
2017-06-25 22:13:02 +02:00
Sorin Davidoi
4ce1540094 fix(features/compose): Handle external changes to the textarea (#3632) 2017-06-25 21:43:27 +02:00
Akihiko Odaki (@fn_aki@pawoo.net)
67243bda31 Cover Auth::RegistrationsController more (#3353) 2017-06-25 21:42:55 +02:00
Akihiko Odaki (@fn_aki@pawoo.net)
8f991831b8 Cover Admin::DomainBlocksController more (#3329)
Also domain_block fabricator now sets unique domains
2017-06-25 21:42:36 +02:00
amazedkoumei
87efa38721 more free pgconfig by .env (#3909)
* more free pgconfig for streaming by .env

* fix wrong default values

* database.yml read ENV as same as streaming server
2017-06-25 18:13:31 +02:00
Eugen Rochko
f7301bd5b9 Add overview of active sessions (#3929)
* Add overview of active sessions

* Better display of browser/platform name

* Improve how browser information is stored and displayed for sessions overview

* Fix test
2017-06-25 16:54:30 +02:00
PFM
099a3b4eac Fix "undefined" in className (#3939) 2017-06-25 16:02:56 +02:00
unarist
3d4e21f1ec Don't set ASSET_HOST on build:development (#3936)
Setting ASSET_HOST to `http://0.0.0.0:8080` makes urls in manifest.json to
be invalid, e.g. `http://0.0.0.0:8080/packs/application.js`.

Anyway, we don't need set this on build:development because assets would
be delivered from same origin in development (and w/o dev-server).
2017-06-25 12:52:42 +02:00
unarist
68dca26a5d Fix react-intl/locale-data import issue on production build (#3937)
Webpack seems to fail to import `react-intl/locale-data/*.js` if those
files has been proceed by babel, and this also breaks applying our translation.

Note that this won't be a problem on English locale, because react-intl
includes it as default and works fine without manually added locale-data.
Also this issue seems to only occurs on production build, but I'm not sure
about reason.
2017-06-25 12:49:53 +02:00
unarist
1fc096ec75 Fix elephant in onboarding modal being very small sized on small devices (#3932) 2017-06-24 23:18:32 +02:00
unarist
21c2bc119c Clean column collapsible (#3931)
* Remove unused column_collapsable.js
* Remove old styles
* Extract `> div`  style to independent class
2017-06-24 23:18:11 +02:00
Sorin Davidoi
d23293c762 feat(components/onboarding_modal): Swipe between pages (#3934) 2017-06-24 23:17:39 +02:00
unarist
138e5a0b1e Fix webpack config for Windows (#3926) 2017-06-24 14:03:52 +02:00
Yamagishi Kazutoshi
79dacea962 Fix #3924 (regression from #3906) (#3925) 2017-06-24 12:24:02 +02:00
unarist
4e6b5e7879 Use debounce for dispatch scrollTopNotification and expandNotifications (#3700) 2017-06-24 02:43:26 +02:00
Daniel Hunsaker
c0979381a4 Fix a typo and give CW'd statuses the right cursor (#3918) 2017-06-23 23:13:27 +02:00
Eugen Rochko
676f577e7e Fix webpack-dev-server until it's fixed upstream (#3916) 2017-06-23 19:40:51 +02:00
Yamagishi Kazutoshi
c1a8e3d1eb Use Class and Property Decorators (#3730)
ref https://tc39.github.io/proposal-decorators/
2017-06-23 19:36:54 +02:00
Takuya Yoshida
0c44316b22 Fix errorMiddleware to prevent "TypeError: res.writeHead is not a function" (#3913)
* Fix errorMiddleware

* Add "eslint-disable-line no-unused-vars"
2017-06-23 19:22:02 +02:00
Sorin Davidoi
2211e8d1cd Revocable sessions (#3616)
* feat: Revocable sessions

* fix: Tests using sign_in

* feat: Configuration entry for the maximum number of session activations
2017-06-23 18:50:53 +02:00
Nolan Lawson
3783cadf2d Apply babel to react-intl to remove prop-types (#3914) 2017-06-23 18:21:33 +02:00
Eugen Rochko
a071047c13 Merge branch 'sorin-davidoi-swipe-gestures' 2017-06-23 17:52:56 +02:00
Eugen Rochko
281f07244b Merge branch 'swipe-gestures' of git://github.com/sorin-davidoi/mastodon into sorin-davidoi-swipe-gestures 2017-06-23 17:52:39 +02:00
Akihiko Odaki (@fn_aki@pawoo.net)
6f34a6a77f Add index statuses on account_id and id (#3895) 2017-06-23 17:46:00 +02:00
Nolan Lawson
e078919f07 Upgrade to Webpack 3 with module concatenation (#3912) 2017-06-23 17:44:55 +02:00
Eugen Rochko
7b13e6efc2 Bump version to 1.4.6 2017-06-23 17:02:14 +02:00
Eugen Rochko
3f59238207 Add important test for full-width hashtags (#3911) 2017-06-23 17:01:53 +02:00
Yamagishi Kazutoshi
eff9416469 Remove unused variables (#3906) 2017-06-23 16:05:04 +02:00
Yamagishi Kazutoshi
6fbb3841a6 Add prefix to setting toggle ID (#3907) 2017-06-23 13:55:05 +02:00
Sorin Davidoi
d8c4781377 fix: Apply :hover, :focus and :active only when multiple columns 2017-06-23 13:48:46 +02:00
Sorin Davidoi
bc6e958229 feat: Swipeable media 2017-06-23 13:48:46 +02:00
Sorin Davidoi
a6d8d1036a feat: Swipeable columns 2017-06-23 13:48:46 +02:00
Sorin Davidoi
3d403a013d chore(yarn): Install react-swipeable 2017-06-23 13:40:58 +02:00
220 changed files with 2171 additions and 1354 deletions

View File

@@ -15,6 +15,7 @@
"plugins": [
"syntax-dynamic-import",
["transform-object-rest-spread", { "useBuiltIns": true }],
"transform-decorators-legacy",
"transform-class-properties",
[
"react-intl",

View File

@@ -1,7 +1,9 @@
---
root: true
env:
browser: true
node: false
node: true
es6: true
parser: babel-eslint
@@ -52,8 +54,14 @@ rules:
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
@@ -81,7 +89,10 @@ rules:
- 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

14
.gitattributes vendored Normal file
View File

@@ -0,0 +1,14 @@
* 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

View File

@@ -20,6 +20,7 @@ gem 'paperclip-av-transcoder', '~> 0.6'
gem 'addressable', '~> 2.5'
gem 'bootsnap'
gem 'browser'
gem 'cld3', '~> 3.1'
gem 'devise', '~> 4.2'
gem 'devise-two-factor', '~> 3.0'

View File

@@ -1,47 +1,47 @@
GEM
remote: https://rubygems.org/
specs:
actioncable (5.1.1)
actionpack (= 5.1.1)
actioncable (5.1.2)
actionpack (= 5.1.2)
nio4r (~> 2.0)
websocket-driver (~> 0.6.1)
actionmailer (5.1.1)
actionpack (= 5.1.1)
actionview (= 5.1.1)
activejob (= 5.1.1)
actionmailer (5.1.2)
actionpack (= 5.1.2)
actionview (= 5.1.2)
activejob (= 5.1.2)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0)
actionpack (5.1.1)
actionview (= 5.1.1)
activesupport (= 5.1.1)
actionpack (5.1.2)
actionview (= 5.1.2)
activesupport (= 5.1.2)
rack (~> 2.0)
rack-test (~> 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (5.1.1)
activesupport (= 5.1.1)
actionview (5.1.2)
activesupport (= 5.1.2)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.0.3)
active_record_query_trace (1.5.4)
activejob (5.1.1)
activesupport (= 5.1.1)
activejob (5.1.2)
activesupport (= 5.1.2)
globalid (>= 0.3.6)
activemodel (5.1.1)
activesupport (= 5.1.1)
activerecord (5.1.1)
activemodel (= 5.1.1)
activesupport (= 5.1.1)
activemodel (5.1.2)
activesupport (= 5.1.2)
activerecord (5.1.2)
activemodel (= 5.1.2)
activesupport (= 5.1.2)
arel (~> 8.0)
activesupport (5.1.1)
activesupport (5.1.2)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (~> 0.7)
minitest (~> 5.1)
tzinfo (~> 1.1)
addressable (2.5.1)
public_suffix (~> 2.0, >= 2.0.2)
airbrussh (1.2.0)
airbrussh (1.3.0)
sshkit (>= 1.6.1, != 1.7.0)
annotate (2.7.2)
activerecord (>= 3.2, < 6.0)
@@ -52,13 +52,13 @@ GEM
encryptor (~> 3.0.0)
av (0.9.0)
cocaine (~> 0.5.3)
aws-sdk (2.9.37)
aws-sdk-resources (= 2.9.37)
aws-sdk-core (2.9.37)
aws-sdk (2.10.6)
aws-sdk-resources (= 2.10.6)
aws-sdk-core (2.10.6)
aws-sigv4 (~> 1.0)
jmespath (~> 1.0)
aws-sdk-resources (2.9.37)
aws-sdk-core (= 2.9.37)
aws-sdk-resources (2.10.6)
aws-sdk-core (= 2.10.6)
aws-sigv4 (1.0.0)
bcrypt (3.1.11)
better_errors (2.1.1)
@@ -67,9 +67,10 @@ GEM
rack (>= 0.9.0)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
bootsnap (1.0.0)
bootsnap (1.1.1)
msgpack (~> 1.0)
brakeman (3.6.2)
browser (2.4.0)
builder (3.2.3)
bullet (5.5.1)
activesupport (>= 3.0.0)
@@ -77,7 +78,7 @@ GEM
bundler-audit (0.5.0)
bundler (~> 1.2)
thor (~> 0.18)
capistrano (3.8.1)
capistrano (3.8.2)
airbrussh (>= 1.0.0)
i18n
rake (>= 10.0.0)
@@ -93,7 +94,7 @@ GEM
sshkit (~> 1.3)
capistrano-yarn (2.0.2)
capistrano (~> 3.0)
capybara (2.14.2)
capybara (2.14.4)
addressable
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
@@ -101,7 +102,7 @@ GEM
rack-test (>= 0.5.4)
xpath (~> 2.0)
chunky_png (1.3.8)
cld3 (3.1.2)
cld3 (3.1.3)
ffi (>= 1.1.0, < 1.10.0)
climate_control (0.2.0)
cocaine (0.5.8)
@@ -141,9 +142,9 @@ GEM
thread
thread_safe
encryptor (3.0.0)
erubi (1.6.0)
erubi (1.6.1)
erubis (2.7.0)
et-orbi (1.0.4)
et-orbi (1.0.5)
tzinfo
execjs (2.7.0)
fabrication (2.16.1)
@@ -160,7 +161,7 @@ GEM
addressable (~> 2.4)
http (~> 2.0)
nokogiri (~> 1.6)
hamlit (2.8.1)
hamlit (2.8.4)
temple (>= 0.8.0)
thor
tilt
@@ -181,9 +182,9 @@ GEM
http-cookie (1.0.3)
domain_name (~> 0.5)
http-form_data (1.0.3)
http_accept_language (2.1.0)
http_accept_language (2.1.1)
http_parser.rb (0.6.0)
httplog (0.99.3)
httplog (0.99.4)
colorize
rack
i18n (0.8.4)
@@ -248,8 +249,8 @@ GEM
mini_portile2 (~> 2.2.0)
nokogumbo (1.4.13)
nokogiri
oj (3.1.0)
openssl (2.0.3)
oj (3.2.0)
openssl (2.0.4)
orm_adapter (0.5.0)
ostatus2 (2.0.1)
addressable (~> 2.4)
@@ -271,7 +272,7 @@ GEM
parallel
parser (2.4.0.0)
ast (~> 2.2)
pg (0.20.0)
pg (0.21.0)
pghero (1.7.0)
activerecord
pkg-config (1.2.3)
@@ -297,17 +298,17 @@ GEM
rack-test (0.6.3)
rack (>= 1.0)
rack-timeout (0.4.2)
rails (5.1.1)
actioncable (= 5.1.1)
actionmailer (= 5.1.1)
actionpack (= 5.1.1)
actionview (= 5.1.1)
activejob (= 5.1.1)
activemodel (= 5.1.1)
activerecord (= 5.1.1)
activesupport (= 5.1.1)
rails (5.1.2)
actioncable (= 5.1.2)
actionmailer (= 5.1.2)
actionpack (= 5.1.2)
actionview (= 5.1.2)
activejob (= 5.1.2)
activemodel (= 5.1.2)
activerecord (= 5.1.2)
activesupport (= 5.1.2)
bundler (>= 1.3.0, < 2.0)
railties (= 5.1.1)
railties (= 5.1.2)
sprockets-rails (>= 2.0.0)
rails-controller-testing (1.0.2)
actionpack (~> 5.x, >= 5.0.1)
@@ -323,9 +324,9 @@ GEM
railties (~> 5.0)
rails-settings-cached (0.6.5)
rails (>= 4.2.0)
railties (5.1.1)
actionpack (= 5.1.1)
activesupport (= 5.1.1)
railties (5.1.2)
actionpack (= 5.1.2)
activesupport (= 5.1.2)
method_source
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
@@ -373,7 +374,7 @@ GEM
rspec-expectations (~> 3.6.0)
rspec-mocks (~> 3.6.0)
rspec-support (~> 3.6.0)
rspec-sidekiq (3.0.1)
rspec-sidekiq (3.0.3)
rspec-core (~> 3.0, >= 3.0.0)
sidekiq (>= 2.4.0)
rspec-support (3.6.0)
@@ -394,10 +395,10 @@ GEM
nokogiri (>= 1.4.4)
nokogumbo (~> 1.4.1)
sass (3.4.24)
scss_lint (0.53.0)
scss_lint (0.54.0)
rake (>= 0.9, < 13)
sass (~> 3.4.20)
sidekiq (5.0.2)
sidekiq (5.0.3)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
rack-protection (>= 1.5.0)
@@ -405,7 +406,7 @@ GEM
sidekiq-bulk (0.1.1)
activesupport
sidekiq
sidekiq-scheduler (2.1.5)
sidekiq-scheduler (2.1.7)
redis (~> 3)
rufus-scheduler (~> 3.2)
sidekiq (>= 3)
@@ -442,7 +443,7 @@ GEM
thread (0.2.2)
thread_safe (0.3.6)
tilt (2.0.7)
twitter-text (1.14.5)
twitter-text (1.14.6)
unf (~> 0.1.0)
tzinfo (1.2.3)
thread_safe (~> 0.1)
@@ -453,7 +454,7 @@ GEM
unf (0.1.4)
unf_ext
unf_ext (0.0.7.4)
unicode-display_width (1.2.1)
unicode-display_width (1.3.0)
uniform_notifier (1.10.0)
warden (1.2.7)
rack (>= 1.0)
@@ -483,6 +484,7 @@ DEPENDENCIES
binding_of_caller (~> 0.7)
bootsnap
brakeman (~> 3.6)
browser
bullet (~> 5.5)
bundler-audit (~> 0.5)
capistrano (~> 3.8)

View File

@@ -17,6 +17,9 @@ class Api::V1::ReportsController < Api::BaseController
status_ids: reported_status_ids,
comment: report_params[:comment]
)
User.admins.includes(:account).each { |u| AdminMailer.new_report(u.account, @report).deliver_later }
render :show
end

View File

@@ -11,6 +11,7 @@ class ApplicationController < ActionController::Base
include UserTrackingConcern
helper_method :current_account
helper_method :current_session
helper_method :single_user_mode?
rescue_from ActionController::RoutingError, with: :not_found
@@ -68,6 +69,10 @@ class ApplicationController < ActionController::Base
@current_account ||= current_user.try(:account)
end
def current_session
@current_session ||= SessionActivation.find_by(session_id: session['auth_id'])
end
def cache_collection(raw, klass)
return raw unless klass.respond_to?(:with_includes)

View File

@@ -5,6 +5,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
before_action :check_enabled_registrations, only: [:new, :create]
before_action :configure_sign_up_params, only: [:create]
before_action :set_sessions, only: [:edit, :update]
def destroy
not_found
@@ -41,4 +42,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController
def determine_layout
%w(edit update).include?(action_name) ? 'admin' : 'auth'
end
def set_sessions
@sessions = current_user.session_activations
end
end

View File

@@ -5,7 +5,7 @@ class HomeController < ApplicationController
def index
@body_classes = 'app-body'
@token = find_or_create_access_token.token
@token = current_session.token
@web_settings = Web::Setting.find_by(user: current_user)&.data || {}
@admin = Account.find_local(Setting.site_contact_username)
@streaming_api_base_url = Rails.configuration.x.streaming_api_base_url
@@ -16,14 +16,4 @@ class HomeController < ApplicationController
def authenticate_user!
redirect_to(single_user_mode? ? account_path(Account.first) : about_path) unless user_signed_in?
end
def find_or_create_access_token
Doorkeeper::AccessToken.find_or_create_for(
Doorkeeper::Application.where(superapp: true).first,
current_user.id,
Doorkeeper::OAuth::Scopes.from_string('read write follow'),
Doorkeeper.configuration.access_token_expires_in,
Doorkeeper.configuration.refresh_token_enabled?
)
end
end

View File

@@ -7,7 +7,9 @@ module Settings
before_action :authenticate_user!
before_action :verify_otp_required, only: [:create]
def show; end
def show
@confirmation = Form::TwoFactorConfirmation.new
end
def create
current_user.otp_secret = User.generate_otp_secret(32)
@@ -16,13 +18,23 @@ module Settings
end
def destroy
current_user.otp_required_for_login = false
current_user.save!
redirect_to settings_two_factor_authentication_path
if current_user.validate_and_consume_otp!(confirmation_params[:code])
current_user.otp_required_for_login = false
current_user.save!
redirect_to settings_two_factor_authentication_path
else
flash.now[:alert] = I18n.t('two_factor_authentication.wrong_code')
@confirmation = Form::TwoFactorConfirmation.new
render :show
end
end
private
def confirmation_params
params.require(:form_two_factor_confirmation).permit(:code)
end
def verify_otp_required
redirect_to settings_two_factor_authentication_path if current_user.otp_required_for_login?
end

View File

@@ -41,4 +41,16 @@ module SettingsHelper
def hash_to_object(hash)
HashObject.new(hash)
end
def session_device_icon(session)
device = session.detection.device
if device.mobile?
'mobile'
elsif device.tablet?
'tablet'
else
'desktop'
end
end
end

View File

@@ -1,5 +1,4 @@
import api, { getLinks } from '../api';
import Immutable from 'immutable';
export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST';
export const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS';
@@ -597,7 +596,7 @@ export function authorizeFollowRequest(id) {
api(getState)
.post(`/api/v1/follow_requests/${id}/authorize`)
.then(response => dispatch(authorizeFollowRequestSuccess(id)))
.then(() => dispatch(authorizeFollowRequestSuccess(id)))
.catch(error => dispatch(authorizeFollowRequestFail(id, error)));
};
};
@@ -631,7 +630,7 @@ export function rejectFollowRequest(id) {
api(getState)
.post(`/api/v1/follow_requests/${id}/reject`)
.then(response => dispatch(rejectFollowRequestSuccess(id)))
.then(() => dispatch(rejectFollowRequestSuccess(id)))
.catch(error => dispatch(rejectFollowRequestFail(id, error)));
};
};

View File

@@ -16,7 +16,7 @@ export function blockDomain(domain, accountId) {
return (dispatch, getState) => {
dispatch(blockDomainRequest(domain));
api(getState).post('/api/v1/domain_blocks', { domain }).then(response => {
api(getState).post('/api/v1/domain_blocks', { domain }).then(() => {
dispatch(blockDomainSuccess(domain, accountId));
}).catch(err => {
dispatch(blockDomainFail(domain, err));
@@ -51,7 +51,7 @@ export function unblockDomain(domain, accountId) {
return (dispatch, getState) => {
dispatch(unblockDomainRequest(domain));
api(getState).delete('/api/v1/domain_blocks', { params: { domain } }).then(response => {
api(getState).delete('/api/v1/domain_blocks', { params: { domain } }).then(() => {
dispatch(unblockDomainSuccess(domain, accountId));
}).catch(err => {
dispatch(unblockDomainFail(domain, err));

View File

@@ -17,7 +17,7 @@ export const NOTIFICATIONS_EXPAND_FAIL = 'NOTIFICATIONS_EXPAND_FAIL';
export const NOTIFICATIONS_CLEAR = 'NOTIFICATIONS_CLEAR';
export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP';
const messages = defineMessages({
defineMessages({
mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' },
});

View File

@@ -1,4 +1,5 @@
import api from '../api';
import { openModal, closeModal } from './modal';
export const REPORT_INIT = 'REPORT_INIT';
export const REPORT_CANCEL = 'REPORT_CANCEL';
@@ -11,10 +12,14 @@ export const REPORT_STATUS_TOGGLE = 'REPORT_STATUS_TOGGLE';
export const REPORT_COMMENT_CHANGE = 'REPORT_COMMENT_CHANGE';
export function initReport(account, status) {
return {
type: REPORT_INIT,
account,
status,
return dispatch => {
dispatch({
type: REPORT_INIT,
account,
status,
});
dispatch(openModal('REPORT'));
};
};
@@ -40,7 +45,10 @@ export function submitReport() {
account_id: getState().getIn(['reports', 'new', 'account_id']),
status_ids: getState().getIn(['reports', 'new', 'status_ids']),
comment: getState().getIn(['reports', 'new', 'comment']),
}).then(response => dispatch(submitReportSuccess(response.data))).catch(error => dispatch(submitReportFail(error)));
}).then(response => {
dispatch(closeModal());
dispatch(submitReportSuccess(response.data));
}).catch(error => dispatch(submitReportFail(error)));
};
};

View File

@@ -74,7 +74,7 @@ export function deleteStatus(id) {
return (dispatch, getState) => {
dispatch(deleteStatusRequest(id));
api(getState).delete(`/api/v1/statuses/${id}`).then(response => {
api(getState).delete(`/api/v1/statuses/${id}`).then(() => {
dispatch(deleteStatusSuccess(id));
dispatch(deleteFromTimelines(id));
}).catch(error => {
@@ -152,7 +152,7 @@ export function muteStatus(id) {
return (dispatch, getState) => {
dispatch(muteStatusRequest(id));
api(getState).post(`/api/v1/statuses/${id}/mute`).then(response => {
api(getState).post(`/api/v1/statuses/${id}/mute`).then(() => {
dispatch(muteStatusSuccess(id));
}).catch(error => {
dispatch(muteStatusFail(id, error));
@@ -186,7 +186,7 @@ export function unmuteStatus(id) {
return (dispatch, getState) => {
dispatch(unmuteStatusRequest(id));
api(getState).post(`/api/v1/statuses/${id}/unmute`).then(response => {
api(getState).post(`/api/v1/statuses/${id}/unmute`).then(() => {
dispatch(unmuteStatusSuccess(id));
}).catch(error => {
dispatch(unmuteStatusFail(id, error));

View File

@@ -16,7 +16,8 @@ const messages = defineMessages({
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
});
class Account extends ImmutablePureComponent {
@injectIntl
export default class Account extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.map.isRequired,
@@ -82,5 +83,3 @@ class Account extends ImmutablePureComponent {
}
}
export default injectIntl(Account);

View File

@@ -4,7 +4,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
const filename = url => url.split('/').pop().split('#')[0].split('?')[0];
class AttachmentList extends ImmutablePureComponent {
export default class AttachmentList extends ImmutablePureComponent {
static propTypes = {
media: ImmutablePropTypes.list.isRequired,
@@ -31,5 +31,3 @@ class AttachmentList extends ImmutablePureComponent {
}
}
export default AttachmentList;

View File

@@ -31,7 +31,7 @@ const textAtCursorMatchesToken = (str, caretPosition) => {
}
};
class AutosuggestTextarea extends ImmutablePureComponent {
export default class AutosuggestTextarea extends ImmutablePureComponent {
static propTypes = {
value: PropTypes.string,
@@ -196,5 +196,3 @@ class AutosuggestTextarea extends ImmutablePureComponent {
}
}
export default AutosuggestTextarea;

View File

@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
class Avatar extends React.PureComponent {
export default class Avatar extends React.PureComponent {
static propTypes = {
src: PropTypes.string.isRequired,
@@ -66,5 +66,3 @@ class Avatar extends React.PureComponent {
}
}
export default Avatar;

View File

@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
class AvatarOverlay extends React.PureComponent {
export default class AvatarOverlay extends React.PureComponent {
static propTypes = {
staticSrc: PropTypes.string.isRequired,
@@ -28,5 +28,3 @@ class AvatarOverlay extends React.PureComponent {
}
}
export default AvatarOverlay;

View File

@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
class Button extends React.PureComponent {
export default class Button extends React.PureComponent {
static propTypes = {
text: PropTypes.node,
@@ -61,5 +61,3 @@ class Button extends React.PureComponent {
}
}
export default Button;

View File

@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import scrollTop from '../scroll';
class Column extends React.PureComponent {
export default class Column extends React.PureComponent {
static propTypes = {
children: PropTypes.node,
@@ -41,5 +41,3 @@ class Column extends React.PureComponent {
}
}
export default Column;

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
class ColumnBackButton extends React.PureComponent {
export default class ColumnBackButton extends React.PureComponent {
static contextTypes = {
router: PropTypes.object,
@@ -23,5 +23,3 @@ class ColumnBackButton extends React.PureComponent {
}
}
export default ColumnBackButton;

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
class ColumnBackButtonSlim extends React.PureComponent {
export default class ColumnBackButtonSlim extends React.PureComponent {
static contextTypes = {
router: PropTypes.object,
@@ -25,5 +25,3 @@ class ColumnBackButtonSlim extends React.PureComponent {
}
}
export default ColumnBackButtonSlim;

View File

@@ -1,52 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
class ColumnCollapsable extends React.PureComponent {
static propTypes = {
icon: PropTypes.string.isRequired,
title: PropTypes.string,
fullHeight: PropTypes.number.isRequired,
children: PropTypes.node,
onCollapse: PropTypes.func,
};
state = {
collapsed: true,
animating: false,
};
handleToggleCollapsed = () => {
const currentState = this.state.collapsed;
this.setState({ collapsed: !currentState, animating: true });
if (!currentState && this.props.onCollapse) {
this.props.onCollapse();
}
}
handleTransitionEnd = () => {
this.setState({ animating: false });
}
render () {
const { icon, title, fullHeight, children } = this.props;
const { collapsed, animating } = this.state;
return (
<div className={`column-collapsable ${collapsed ? 'collapsed' : ''}`} onTransitionEnd={this.handleTransitionEnd}>
<div role='button' tabIndex='0' title={`${title}`} className='column-collapsable__button column-icon' onClick={this.handleToggleCollapsed}>
<i className={`fa fa-${icon}`} />
</div>
<div className='column-collapsable__content' style={{ height: `${fullHeight}px` }}>
{(!collapsed || animating) && children}
</div>
</div>
);
}
}
export default ColumnCollapsable;

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import classNames from 'classnames';
import { FormattedMessage } from 'react-intl';
class ColumnHeader extends React.PureComponent {
export default class ColumnHeader extends React.PureComponent {
static contextTypes = {
router: PropTypes.object,
@@ -132,7 +132,7 @@ class ColumnHeader extends React.PureComponent {
</div>
<div className={collapsibleClassName} onTransitionEnd={this.handleTransitionEnd}>
<div>
<div className='column-header__collapsible-inner'>
{(!collapsed || animating) && collapsedContent}
</div>
</div>
@@ -141,5 +141,3 @@ class ColumnHeader extends React.PureComponent {
}
}
export default ColumnHeader;

View File

@@ -3,7 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import escapeTextContentForBrowser from 'escape-html';
import emojify from '../emoji';
class DisplayName extends React.PureComponent {
export default class DisplayName extends React.PureComponent {
static propTypes = {
account: ImmutablePropTypes.map.isRequired,
@@ -21,5 +21,3 @@ class DisplayName extends React.PureComponent {
}
}
export default DisplayName;

View File

@@ -2,7 +2,7 @@ import React from 'react';
import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';
import PropTypes from 'prop-types';
class DropdownMenu extends React.PureComponent {
export default class DropdownMenu extends React.PureComponent {
static contextTypes = {
router: PropTypes.object,
@@ -56,7 +56,7 @@ class DropdownMenu extends React.PureComponent {
return <li key={`sep-${i}`} className='dropdown__sep' />;
}
const { text, action, href = '#' } = item;
const { text, href = '#' } = item;
return (
<li className='dropdown__content-list-item' key={`${text}-${i}`}>
@@ -92,5 +92,3 @@ class DropdownMenu extends React.PureComponent {
}
}
export default DropdownMenu;

View File

@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
class ExtendedVideoPlayer extends React.PureComponent {
export default class ExtendedVideoPlayer extends React.PureComponent {
static propTypes = {
src: PropTypes.string.isRequired,
@@ -44,5 +44,3 @@ class ExtendedVideoPlayer extends React.PureComponent {
}
}
export default ExtendedVideoPlayer;

View File

@@ -3,7 +3,7 @@ import Motion from 'react-motion/lib/Motion';
import spring from 'react-motion/lib/spring';
import PropTypes from 'prop-types';
class IconButton extends React.PureComponent {
export default class IconButton extends React.PureComponent {
static propTypes = {
className: PropTypes.string,
@@ -86,5 +86,3 @@ class IconButton extends React.PureComponent {
}
}
export default IconButton;

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
class LoadMore extends React.PureComponent {
export default class LoadMore extends React.PureComponent {
static propTypes = {
onClick: PropTypes.func,
@@ -17,5 +17,3 @@ class LoadMore extends React.PureComponent {
}
}
export default LoadMore;

View File

@@ -85,14 +85,24 @@ class Item extends React.PureComponent {
let thumbnail = '';
if (attachment.get('type') === 'image') {
const previewUrl = attachment.get('preview_url');
const previewWidth = attachment.getIn(['meta', 'small', 'width']);
const originalUrl = attachment.get('url');
const originalWidth = attachment.getIn(['meta', 'original', 'width']);
const srcSet = `${originalUrl} ${originalWidth}w, ${previewUrl} ${previewWidth}w`;
const sizes = `(min-width: 1025px) ${320 * (width / 100)}px, ${width}vw`;
thumbnail = (
<a // eslint-disable-line jsx-a11y/anchor-has-content
<a
className='media-gallery__item-thumbnail'
href={attachment.get('remote_url') || attachment.get('url')}
href={attachment.get('remote_url') || originalUrl}
onClick={this.handleClick}
target='_blank'
style={{ backgroundImage: `url(${attachment.get('preview_url')})` }}
/>
>
<img src={previewUrl} srcSet={srcSet} sizes={sizes} alt='' />
</a>
);
} else if (attachment.get('type') === 'gifv') {
const autoPlay = !isIOS() && this.props.autoPlayGif;
@@ -123,7 +133,8 @@ class Item extends React.PureComponent {
}
class MediaGallery extends React.PureComponent {
@injectIntl
export default class MediaGallery extends React.PureComponent {
static propTypes = {
sensitive: PropTypes.bool,
@@ -138,7 +149,7 @@ class MediaGallery extends React.PureComponent {
visible: !this.props.sensitive,
};
handleOpen = (e) => {
handleOpen = () => {
this.setState({ visible: !this.state.visible });
}
@@ -183,5 +194,3 @@ class MediaGallery extends React.PureComponent {
}
}
export default injectIntl(MediaGallery);

View File

@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
class Permalink extends React.PureComponent {
export default class Permalink extends React.PureComponent {
static contextTypes = {
router: PropTypes.object,
@@ -25,12 +25,10 @@ class Permalink extends React.PureComponent {
const { href, children, className, ...other } = this.props;
return (
<a href={href} onClick={this.handleClick} {...other} className={'permalink ' + className}>
<a href={href} onClick={this.handleClick} {...other} className={`permalink${className ? ' ' + className : ''}`}>
{children}
</a>
);
}
}
export default Permalink;

View File

@@ -11,7 +11,8 @@ const dateFormatOptions = {
minute: '2-digit',
};
class RelativeTimestamp extends React.Component {
@injectIntl
export default class RelativeTimestamp extends React.Component {
static propTypes = {
intl: PropTypes.object.isRequired,
@@ -37,5 +38,3 @@ class RelativeTimestamp extends React.Component {
}
}
export default injectIntl(RelativeTimestamp);

View File

@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
class SettingText extends React.PureComponent {
export default class SettingText extends React.PureComponent {
static propTypes = {
settings: ImmutablePropTypes.map.isRequired,
@@ -29,5 +29,3 @@ class SettingText extends React.PureComponent {
}
}
export default SettingText;

View File

@@ -7,7 +7,6 @@ import RelativeTimestamp from './relative_timestamp';
import DisplayName from './display_name';
import MediaGallery from './media_gallery';
import VideoPlayer from './video_player';
import AttachmentList from './attachment_list';
import StatusContent from './status_content';
import StatusActionBar from './status_action_bar';
import { FormattedMessage } from 'react-intl';
@@ -16,7 +15,7 @@ import escapeTextContentForBrowser from 'escape-html';
import ImmutablePureComponent from 'react-immutable-pure-component';
import scheduleIdleTask from '../features/ui/util/schedule_idle_task';
class Status extends ImmutablePureComponent {
export default class Status extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
@@ -125,7 +124,7 @@ class Status extends ImmutablePureComponent {
saveHeight = () => {
if (this.node && this.node.children.length !== 0) {
this.height = this.node.clientHeight;
this.height = this.node.getBoundingClientRect().height;
}
}
@@ -234,5 +233,3 @@ class Status extends ImmutablePureComponent {
}
}
export default Status;

View File

@@ -22,7 +22,8 @@ const messages = defineMessages({
unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' },
});
class StatusActionBar extends ImmutablePureComponent {
@injectIntl
export default class StatusActionBar extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
@@ -86,7 +87,6 @@ class StatusActionBar extends ImmutablePureComponent {
handleReport = () => {
this.props.onReport(this.props.status);
this.context.router.history.push('/report');
}
handleConversationMuteClick = () => {
@@ -149,5 +149,3 @@ class StatusActionBar extends ImmutablePureComponent {
}
}
export default injectIntl(StatusActionBar);

View File

@@ -7,7 +7,7 @@ import { isRtl } from '../rtl';
import { FormattedMessage } from 'react-intl';
import Permalink from './permalink';
class StatusContent extends React.PureComponent {
export default class StatusContent extends React.PureComponent {
static contextTypes = {
router: PropTypes.object,
@@ -32,7 +32,6 @@ class StatusContent extends React.PureComponent {
for (var i = 0; i < links.length; ++i) {
let link = links[i];
let mention = this.props.status.get('mentions').find(item => link.href === item.get('url'));
let media = this.props.status.get('media_attachments').find(item => link.href === item.get('text_url') || (item.get('remote_url').length > 0 && link.href === item.get('remote_url')));
if (mention) {
link.addEventListener('click', this.onMentionClick.bind(this, mention), false);
@@ -136,7 +135,7 @@ class StatusContent extends React.PureComponent {
}
return (
<div className='status__content status__content--with_action' ref={this.setRef} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
<div className='status__content status__content--with-action' ref={this.setRef} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
<p style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}>
<span dangerouslySetInnerHTML={spoilerContent} />
{' '}
@@ -172,5 +171,3 @@ class StatusContent extends React.PureComponent {
}
}
export default StatusContent;

View File

@@ -8,7 +8,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper';
import { debounce } from 'lodash';
class StatusList extends ImmutablePureComponent {
export default class StatusList extends ImmutablePureComponent {
static propTypes = {
scrollKey: PropTypes.string.isRequired,
@@ -99,7 +99,7 @@ class StatusList extends ImmutablePureComponent {
}
render () {
const { statusIds, onScrollToBottom, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, emptyMessage } = this.props;
const { statusIds, scrollKey, trackScroll, shouldUpdateScroll, isLoading, hasMore, prepend, emptyMessage } = this.props;
let loadMore = null;
let scrollableArea = null;
@@ -142,5 +142,3 @@ class StatusList extends ImmutablePureComponent {
}
}
export default StatusList;

View File

@@ -11,7 +11,8 @@ const messages = defineMessages({
expand_video: { id: 'video_player.expand', defaultMessage: 'Expand video' },
});
class VideoPlayer extends React.PureComponent {
@injectIntl
export default class VideoPlayer extends React.PureComponent {
static propTypes = {
media: ImmutablePropTypes.map.isRequired,
@@ -193,5 +194,3 @@ class VideoPlayer extends React.PureComponent {
}
}
export default injectIntl(VideoPlayer);

View File

@@ -3,7 +3,6 @@ import { Provider } from 'react-redux';
import PropTypes from 'prop-types';
import configureStore from '../store/configureStore';
import {
refreshTimelineSuccess,
updateTimeline,
deleteFromTimelines,
refreshHomeTimeline,
@@ -27,7 +26,11 @@ const store = configureStore();
const initialState = JSON.parse(document.getElementById('initial-state').textContent);
store.dispatch(hydrateStore(initialState));
class Mastodon extends React.PureComponent {
export default class Mastodon extends React.PureComponent {
static propTypes = {
locale: PropTypes.string.isRequired,
};
componentDidMount() {
const { locale } = this.props;
@@ -118,9 +121,3 @@ class Mastodon extends React.PureComponent {
}
}
Mastodon.propTypes = {
locale: PropTypes.string.isRequired,
};
export default Mastodon;

View File

@@ -19,8 +19,6 @@ import {
import { muteStatus, unmuteStatus, deleteStatus } from '../actions/statuses';
import { initReport } from '../actions/reports';
import { openModal } from '../actions/modal';
import { createSelector } from 'reselect';
import { isMobile } from '../is_mobile';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
const messages = defineMessages({

View File

@@ -19,16 +19,41 @@ const unicodeToImage = str => {
});
};
const shortnameToImage = str => str.replace(emojione.regShortNames, shortname => {
if (typeof shortname === 'undefined' || shortname === '' || !(shortname in emojione.emojioneList)) {
return shortname;
const shortnameToImage = str => {
// This walks through the string from end to start, ignoring any tags (<p>, <br>, etc.)
// and replacing valid shortnames like :smile: and :wink: that _aren't_ within
// tags with an <img> version.
// The goal is to be the same as an emojione.regShortNames replacement, but faster.
// The reason we go backwards is because then we can replace substrings as we go.
let i = str.length;
let insideTag = false;
let insideShortname = false;
let shortnameEndIndex = -1;
while (i--) {
const char = str.charAt(i);
if (insideShortname && char === ':') {
const shortname = str.substring(i, shortnameEndIndex + 1);
if (shortname in emojione.emojioneList) {
const unicode = emojione.emojioneList[shortname].unicode[emojione.emojioneList[shortname].unicode.length - 1];
const alt = emojione.convert(unicode.toUpperCase());
const replacement = `<img draggable="false" class="emojione" alt="${alt}" title="${shortname}" src="/emoji/${unicode}.svg" />`;
str = str.substring(0, i) + replacement + str.substring(shortnameEndIndex + 1);
} else {
i++; // stray colon, try again
}
insideShortname = false;
} else if (insideTag && char === '<') {
insideTag = false;
} else if (char === '>') {
insideTag = true;
insideShortname = false;
} else if (!insideTag && char === ':') {
insideShortname = true;
shortnameEndIndex = i;
}
}
const unicode = emojione.emojioneList[shortname].unicode[emojione.emojioneList[shortname].unicode.length - 1];
const alt = emojione.convert(unicode.toUpperCase());
return `<img draggable="false" class="emojione" alt="${alt}" title="${shortname}" src="/emoji/${unicode}.svg" />`;
});
return str;
};
export default function emojify(text) {
return toImage(text);

View File

@@ -21,7 +21,8 @@ const messages = defineMessages({
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
});
class ActionBar extends React.PureComponent {
@injectIntl
export default class ActionBar extends React.PureComponent {
static propTypes = {
account: ImmutablePropTypes.map.isRequired,
@@ -105,5 +106,3 @@ class ActionBar extends React.PureComponent {
}
}
export default injectIntl(ActionBar);

View File

@@ -17,7 +17,7 @@ const messages = defineMessages({
});
const makeMapStateToProps = () => {
const mapStateToProps = (state, props) => ({
const mapStateToProps = state => ({
autoPlayGif: state.getIn(['meta', 'auto_play_gif']),
});
@@ -70,7 +70,9 @@ class Avatar extends ImmutablePureComponent {
}
class Header extends ImmutablePureComponent {
@connect(makeMapStateToProps)
@injectIntl
export default class Header extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.map,
@@ -140,5 +142,3 @@ class Header extends ImmutablePureComponent {
}
}
export default connect(makeMapStateToProps)(injectIntl(Header));

View File

@@ -3,7 +3,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Permalink from '../../../components/permalink';
class MediaItem extends ImmutablePureComponent {
export default class MediaItem extends ImmutablePureComponent {
static propTypes = {
media: ImmutablePropTypes.map.isRequired,
@@ -37,5 +37,3 @@ class MediaItem extends ImmutablePureComponent {
}
}
export default MediaItem;

View File

@@ -7,7 +7,6 @@ import { refreshAccountMediaTimeline, expandAccountMediaTimeline } from '../../a
import LoadingIndicator from '../../components/loading_indicator';
import Column from '../ui/components/column';
import ColumnBackButton from '../../components/column_back_button';
import Immutable from 'immutable';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { getAccountGallery } from '../../selectors';
import MediaItem from './components/media_item';
@@ -23,7 +22,8 @@ const mapStateToProps = (state, props) => ({
autoPlayGif: state.getIn(['meta', 'auto_play_gif']),
});
class AccountGallery extends ImmutablePureComponent {
@connect(mapStateToProps)
export default class AccountGallery extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
@@ -112,5 +112,3 @@ class AccountGallery extends ImmutablePureComponent {
}
}
export default connect(mapStateToProps)(AccountGallery);

View File

@@ -6,7 +6,7 @@ import ActionBar from '../../account/components/action_bar';
import MissingIndicator from '../../../components/missing_indicator';
import ImmutablePureComponent from 'react-immutable-pure-component';
class Header extends ImmutablePureComponent {
export default class Header extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.map,
@@ -38,7 +38,6 @@ class Header extends ImmutablePureComponent {
handleReport = () => {
this.props.onReport(this.props.account);
this.context.router.history.push('/report');
}
handleMute = () => {
@@ -91,5 +90,3 @@ class Header extends ImmutablePureComponent {
}
}
export default Header;

View File

@@ -19,7 +19,8 @@ const mapStateToProps = (state, props) => ({
me: state.getIn(['meta', 'me']),
});
class AccountTimeline extends ImmutablePureComponent {
@connect(mapStateToProps)
export default class AccountTimeline extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
@@ -77,5 +78,3 @@ class AccountTimeline extends ImmutablePureComponent {
}
}
export default connect(mapStateToProps)(AccountTimeline);

View File

@@ -19,7 +19,9 @@ const mapStateToProps = state => ({
accountIds: state.getIn(['user_lists', 'blocks', 'items']),
});
class Blocks extends ImmutablePureComponent {
@connect(mapStateToProps)
@injectIntl
export default class Blocks extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
@@ -66,5 +68,3 @@ class Blocks extends ImmutablePureComponent {
}
}
export default connect(mapStateToProps)(injectIntl(Blocks));

View File

@@ -2,8 +2,6 @@ import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ColumnCollapsable from '../../../components/column_collapsable';
import SettingToggle from '../../notifications/components/setting_toggle';
import SettingText from '../../../components/setting_text';
const messages = defineMessages({
@@ -11,17 +9,17 @@ const messages = defineMessages({
settings: { id: 'home.settings', defaultMessage: 'Column settings' },
});
class ColumnSettings extends React.PureComponent {
@injectIntl
export default class ColumnSettings extends React.PureComponent {
static propTypes = {
settings: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired,
onSave: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
render () {
const { settings, onChange, onSave, intl } = this.props;
const { settings, onChange, intl } = this.props;
return (
<div>
@@ -35,5 +33,3 @@ class ColumnSettings extends React.PureComponent {
}
}
export default injectIntl(ColumnSettings);

View File

@@ -1,6 +1,6 @@
import { connect } from 'react-redux';
import ColumnSettings from '../components/column_settings';
import { changeSetting, saveSettings } from '../../../actions/settings';
import { changeSetting } from '../../../actions/settings';
const mapStateToProps = state => ({
settings: state.getIn(['settings', 'community']),
@@ -12,10 +12,6 @@ const mapDispatchToProps = dispatch => ({
dispatch(changeSetting(['community', ...key], checked));
},
onSave () {
dispatch(saveSettings());
},
});
export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings);

View File

@@ -14,7 +14,6 @@ import {
} from '../../actions/timelines';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import ColumnSettingsContainer from './containers/column_settings_container';
import createStream from '../../stream';
@@ -28,7 +27,9 @@ const mapStateToProps = state => ({
accessToken: state.getIn(['meta', 'access_token']),
});
class CommunityTimeline extends React.PureComponent {
@connect(mapStateToProps)
@injectIntl
export default class CommunityTimeline extends React.PureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
@@ -142,5 +143,3 @@ class CommunityTimeline extends React.PureComponent {
}
}
export default connect(mapStateToProps)(injectIntl(CommunityTimeline));

View File

@@ -4,7 +4,7 @@ import DisplayName from '../../../components/display_name';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
class AutosuggestAccount extends ImmutablePureComponent {
export default class AutosuggestAccount extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.map.isRequired,
@@ -22,5 +22,3 @@ class AutosuggestAccount extends ImmutablePureComponent {
}
}
export default AutosuggestAccount;

View File

@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { length } from 'stringz';
class CharacterCounter extends React.PureComponent {
export default class CharacterCounter extends React.PureComponent {
static propTypes = {
text: PropTypes.string.isRequired,
@@ -23,5 +23,3 @@ class CharacterCounter extends React.PureComponent {
}
}
export default CharacterCounter;

View File

@@ -7,15 +7,13 @@ import ReplyIndicatorContainer from '../containers/reply_indicator_container';
import AutosuggestTextarea from '../../../components/autosuggest_textarea';
import { debounce } from 'lodash';
import UploadButtonContainer from '../containers/upload_button_container';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import Toggle from 'react-toggle';
import { defineMessages, injectIntl } from 'react-intl';
import Collapsable from '../../../components/collapsable';
import SpoilerButtonContainer from '../containers/spoiler_button_container';
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
import SensitiveButtonContainer from '../containers/sensitive_button_container';
import EmojiPickerDropdown from './emoji_picker_dropdown';
import UploadFormContainer from '../containers/upload_form_container';
import TextIconButton from './text_icon_button';
import WarningContainer from '../containers/warning_container';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { length } from 'stringz';
@@ -27,7 +25,8 @@ const messages = defineMessages({
publishLoud: { id: 'compose_form.publish_loud', defaultMessage: '{publish}!' },
});
class ComposeForm extends ImmutablePureComponent {
@injectIntl
export default class ComposeForm extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
@@ -68,6 +67,12 @@ class ComposeForm extends ImmutablePureComponent {
}
handleSubmit = () => {
if (this.props.text !== this.autosuggestTextarea.textarea.value) {
// Something changed the text inside the textarea (e.g. browser extensions like Grammarly)
// Update the state to match the current text
this.props.onChange(this.autosuggestTextarea.textarea.value);
}
this.props.onSubmit();
}
@@ -141,7 +146,6 @@ class ComposeForm extends ImmutablePureComponent {
const text = [this.props.spoiler_text, this.props.text].join('');
let publishText = '';
let reply_to_other = false;
if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
publishText = <span className='compose-form__publish-private'><i className='fa fa-lock' /> {intl.formatMessage(messages.publish)}</span>;
@@ -202,5 +206,3 @@ class ComposeForm extends ImmutablePureComponent {
}
}
export default injectIntl(ComposeForm);

View File

@@ -24,7 +24,8 @@ const settings = {
let EmojiPicker; // load asynchronously
class EmojiPickerDropdown extends React.PureComponent {
@injectIntl
export default class EmojiPickerDropdown extends React.PureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
@@ -52,7 +53,7 @@ class EmojiPickerDropdown extends React.PureComponent {
import(/* webpackChunkName: "emojione_picker" */ 'emojione-picker').then(TheEmojiPicker => {
EmojiPicker = TheEmojiPicker.default;
this.setState({ loading: false });
}).catch(err => {
}).catch(() => {
// TODO: show the user an error?
this.setState({ loading: false });
});
@@ -123,5 +124,3 @@ class EmojiPickerDropdown extends React.PureComponent {
}
}
export default injectIntl(EmojiPickerDropdown);

View File

@@ -1,14 +1,11 @@
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import Avatar from '../../../components/avatar';
import IconButton from '../../../components/icon_button';
import DisplayName from '../../../components/display_name';
import Permalink from '../../../components/permalink';
import { FormattedMessage } from 'react-intl';
import Link from 'react-router-dom/Link';
import ImmutablePureComponent from 'react-immutable-pure-component';
class NavigationBar extends ImmutablePureComponent {
export default class NavigationBar extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.map.isRequired,
@@ -33,5 +30,3 @@ class NavigationBar extends ImmutablePureComponent {
}
}
export default NavigationBar;

View File

@@ -20,7 +20,8 @@ const iconStyle = {
lineHeight: '27px',
};
class PrivacyDropdown extends React.PureComponent {
@injectIntl
export default class PrivacyDropdown extends React.PureComponent {
static propTypes = {
value: PropTypes.string.isRequired,
@@ -64,7 +65,7 @@ class PrivacyDropdown extends React.PureComponent {
}
render () {
const { value, onChange, intl } = this.props;
const { value, intl } = this.props;
const { open } = this.state;
const options = [
@@ -95,5 +96,3 @@ class PrivacyDropdown extends React.PureComponent {
}
}
export default injectIntl(PrivacyDropdown);

View File

@@ -12,7 +12,8 @@ const messages = defineMessages({
cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' },
});
class ReplyIndicator extends ImmutablePureComponent {
@injectIntl
export default class ReplyIndicator extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
@@ -61,5 +62,3 @@ class ReplyIndicator extends ImmutablePureComponent {
}
}
export default injectIntl(ReplyIndicator);

View File

@@ -1,12 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { defineMessages, injectIntl } from 'react-intl';
const messages = defineMessages({
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
});
class Search extends React.PureComponent {
@injectIntl
export default class Search extends React.PureComponent {
static propTypes = {
value: PropTypes.string.isRequired,
@@ -70,5 +71,3 @@ class Search extends React.PureComponent {
}
}
export default injectIntl(Search);

View File

@@ -1,12 +1,12 @@
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { FormattedMessage } from 'react-intl';
import AccountContainer from '../../../containers/account_container';
import StatusContainer from '../../../containers/status_container';
import Link from 'react-router-dom/Link';
import ImmutablePureComponent from 'react-immutable-pure-component';
class SearchResults extends ImmutablePureComponent {
export default class SearchResults extends ImmutablePureComponent {
static propTypes = {
results: ImmutablePropTypes.map.isRequired,
@@ -63,5 +63,3 @@ class SearchResults extends ImmutablePureComponent {
}
}
export default SearchResults;

View File

@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
class TextIconButton extends React.PureComponent {
export default class TextIconButton extends React.PureComponent {
static propTypes = {
label: PropTypes.string.isRequired,
@@ -27,5 +27,3 @@ class TextIconButton extends React.PureComponent {
}
}
export default TextIconButton;

View File

@@ -11,7 +11,7 @@ const messages = defineMessages({
});
const makeMapStateToProps = () => {
const mapStateToProps = (state, props) => ({
const mapStateToProps = state => ({
acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']),
});
@@ -23,7 +23,9 @@ const iconStyle = {
lineHeight: '27px',
};
class UploadButton extends ImmutablePureComponent {
@connect(makeMapStateToProps)
@injectIntl
export default class UploadButton extends ImmutablePureComponent {
static propTypes = {
disabled: PropTypes.bool,
@@ -70,5 +72,3 @@ class UploadButton extends ImmutablePureComponent {
}
}
export default connect(makeMapStateToProps)(injectIntl(UploadButton));

View File

@@ -11,7 +11,8 @@ const messages = defineMessages({
undo: { id: 'upload_form.undo', defaultMessage: 'Undo' },
});
class UploadForm extends React.PureComponent {
@injectIntl
export default class UploadForm extends React.PureComponent {
static propTypes = {
media: ImmutablePropTypes.list.isRequired,
@@ -48,5 +49,3 @@ class UploadForm extends React.PureComponent {
}
}
export default injectIntl(UploadForm);

View File

@@ -4,7 +4,7 @@ import Motion from 'react-motion/lib/Motion';
import spring from 'react-motion/lib/spring';
import { FormattedMessage } from 'react-intl';
class UploadProgress extends React.PureComponent {
export default class UploadProgress extends React.PureComponent {
static propTypes = {
active: PropTypes.bool,
@@ -40,5 +40,3 @@ class UploadProgress extends React.PureComponent {
}
}
export default UploadProgress;

View File

@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
class Warning extends React.PureComponent {
export default class Warning extends React.PureComponent {
static propTypes = {
message: PropTypes.node.isRequired,
@@ -18,5 +18,3 @@ class Warning extends React.PureComponent {
}
}
export default Warning;

View File

@@ -1,7 +1,7 @@
import { connect } from 'react-redux';
import NavigationBar from '../components/navigation_bar';
const mapStateToProps = (state, props) => {
const mapStateToProps = state => {
return {
account: state.getIn(['accounts', state.getIn(['meta', 'me'])]),
};

View File

@@ -6,7 +6,7 @@ import ReplyIndicator from '../components/reply_indicator';
const makeMapStateToProps = () => {
const getStatus = makeGetStatus();
const mapStateToProps = (state, props) => ({
const mapStateToProps = state => ({
status: getStatus(state, state.getIn(['compose', 'in_reply_to'])),
});

View File

@@ -2,7 +2,7 @@ import { connect } from 'react-redux';
import UploadForm from '../components/upload_form';
import { undoUploadCompose } from '../../../actions/compose';
const mapStateToProps = (state, props) => ({
const mapStateToProps = state => ({
media: state.getIn(['compose', 'media_attachments']),
});

View File

@@ -1,7 +1,7 @@
import { connect } from 'react-redux';
import UploadProgress from '../components/upload_progress';
const mapStateToProps = (state, props) => ({
const mapStateToProps = state => ({
active: state.getIn(['compose', 'is_uploading']),
progress: state.getIn(['compose', 'progress']),
});

View File

@@ -1,6 +1,5 @@
import React from 'react';
import ComposeFormContainer from './containers/compose_form_container';
import UploadFormContainer from './containers/upload_form_container';
import NavigationContainer from './containers/navigation_container';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
@@ -24,7 +23,9 @@ const mapStateToProps = state => ({
showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
});
class Compose extends React.PureComponent {
@connect(mapStateToProps)
@injectIntl
export default class Compose extends React.PureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
@@ -83,5 +84,3 @@ class Compose extends React.PureComponent {
}
}
export default connect(mapStateToProps)(injectIntl(Compose));

View File

@@ -20,7 +20,9 @@ const mapStateToProps = state => ({
me: state.getIn(['meta', 'me']),
});
class Favourites extends ImmutablePureComponent {
@connect(mapStateToProps)
@injectIntl
export default class Favourites extends ImmutablePureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
@@ -39,7 +41,7 @@ class Favourites extends ImmutablePureComponent {
}
render () {
const { statusIds, loaded, intl, me } = this.props;
const { loaded, intl } = this.props;
if (!loaded) {
return (
@@ -58,5 +60,3 @@ class Favourites extends ImmutablePureComponent {
}
}
export default connect(mapStateToProps)(injectIntl(Favourites));

View File

@@ -14,7 +14,8 @@ const mapStateToProps = (state, props) => ({
accountIds: state.getIn(['user_lists', 'favourited_by', Number(props.params.statusId)]),
});
class Favourites extends ImmutablePureComponent {
@connect(mapStateToProps)
export default class Favourites extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
@@ -57,5 +58,3 @@ class Favourites extends ImmutablePureComponent {
}
}
export default connect(mapStateToProps)(Favourites);

View File

@@ -14,7 +14,8 @@ const messages = defineMessages({
reject: { id: 'follow_request.reject', defaultMessage: 'Reject' },
});
class AccountAuthorize extends ImmutablePureComponent {
@injectIntl
export default class AccountAuthorize extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.map.isRequired,
@@ -47,5 +48,3 @@ class AccountAuthorize extends ImmutablePureComponent {
}
}
export default injectIntl(AccountAuthorize);

View File

@@ -14,11 +14,11 @@ const makeMapStateToProps = () => {
};
const mapDispatchToProps = (dispatch, { id }) => ({
onAuthorize (account) {
onAuthorize () {
dispatch(authorizeFollowRequest(id));
},
onReject (account) {
onReject () {
dispatch(rejectFollowRequest(id));
},
});

View File

@@ -19,7 +19,9 @@ const mapStateToProps = state => ({
accountIds: state.getIn(['user_lists', 'follow_requests', 'items']),
});
class FollowRequests extends ImmutablePureComponent {
@connect(mapStateToProps)
@injectIntl
export default class FollowRequests extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
@@ -67,5 +69,3 @@ class FollowRequests extends ImmutablePureComponent {
}
}
export default connect(mapStateToProps)(injectIntl(FollowRequests));

View File

@@ -21,7 +21,8 @@ const mapStateToProps = (state, props) => ({
hasMore: !!state.getIn(['user_lists', 'followers', Number(props.params.accountId), 'next']),
});
class Followers extends ImmutablePureComponent {
@connect(mapStateToProps)
export default class Followers extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
@@ -90,5 +91,3 @@ class Followers extends ImmutablePureComponent {
}
}
export default connect(mapStateToProps)(Followers);

View File

@@ -21,7 +21,8 @@ const mapStateToProps = (state, props) => ({
hasMore: !!state.getIn(['user_lists', 'following', Number(props.params.accountId), 'next']),
});
class Following extends ImmutablePureComponent {
@connect(mapStateToProps)
export default class Following extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
@@ -90,5 +91,3 @@ class Following extends ImmutablePureComponent {
}
}
export default connect(mapStateToProps)(Following);

View File

@@ -2,7 +2,6 @@ import React from 'react';
import Column from '../ui/components/column';
import ColumnLink from '../ui/components/column_link';
import ColumnSubheading from '../ui/components/column_subheading';
import Link from 'react-router-dom/Link';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
@@ -31,7 +30,9 @@ const mapStateToProps = state => ({
columns: state.getIn(['settings', 'columns']),
});
class GettingStarted extends ImmutablePureComponent {
@connect(mapStateToProps)
@injectIntl
export default class GettingStarted extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
@@ -106,5 +107,3 @@ class GettingStarted extends ImmutablePureComponent {
}
}
export default connect(mapStateToProps)(injectIntl(GettingStarted));

View File

@@ -11,7 +11,6 @@ import {
deleteFromTimelines,
} from '../../actions/timelines';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import { FormattedMessage } from 'react-intl';
import createStream from '../../stream';
@@ -21,7 +20,8 @@ const mapStateToProps = state => ({
accessToken: state.getIn(['meta', 'access_token']),
});
class HashtagTimeline extends React.PureComponent {
@connect(mapStateToProps)
export default class HashtagTimeline extends React.PureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
@@ -137,5 +137,3 @@ class HashtagTimeline extends React.PureComponent {
}
}
export default connect(mapStateToProps)(HashtagTimeline);

View File

@@ -2,7 +2,6 @@ import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ColumnCollapsable from '../../../components/column_collapsable';
import SettingToggle from '../../notifications/components/setting_toggle';
import SettingText from '../../../components/setting_text';
@@ -11,39 +10,37 @@ const messages = defineMessages({
settings: { id: 'home.settings', defaultMessage: 'Column settings' },
});
class ColumnSettings extends React.PureComponent {
@injectIntl
export default class ColumnSettings extends React.PureComponent {
static propTypes = {
settings: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired,
onSave: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
render () {
const { settings, onChange, onSave, intl } = this.props;
const { settings, onChange, intl } = this.props;
return (
<div>
<span className='column-settings__section'><FormattedMessage id='home.column_settings.basic' defaultMessage='Basic' /></span>
<div className='column-settings__row'>
<SettingToggle settings={settings} settingKey={['shows', 'reblog']} onChange={onChange} label={<FormattedMessage id='home.column_settings.show_reblogs' defaultMessage='Show boosts' />} />
<SettingToggle prefix='home_timeline' settings={settings} settingKey={['shows', 'reblog']} onChange={onChange} label={<FormattedMessage id='home.column_settings.show_reblogs' defaultMessage='Show boosts' />} />
</div>
<div className='column-settings__row'>
<SettingToggle settings={settings} settingKey={['shows', 'reply']} onChange={onChange} label={<FormattedMessage id='home.column_settings.show_replies' defaultMessage='Show replies' />} />
<SettingToggle prefix='home_timeline' settings={settings} settingKey={['shows', 'reply']} onChange={onChange} label={<FormattedMessage id='home.column_settings.show_replies' defaultMessage='Show replies' />} />
</div>
<span className='column-settings__section'><FormattedMessage id='home.column_settings.advanced' defaultMessage='Advanced' /></span>
<div className='column-settings__row'>
<SettingText settings={settings} settingKey={['regex', 'body']} onChange={onChange} label={intl.formatMessage(messages.filter_regex)} />
<SettingText prefix='home_timeline' settings={settings} settingKey={['regex', 'body']} onChange={onChange} label={intl.formatMessage(messages.filter_regex)} />
</div>
</div>
);
}
}
export default injectIntl(ColumnSettings);

View File

@@ -19,7 +19,9 @@ const mapStateToProps = state => ({
hasFollows: state.getIn(['accounts_counters', state.getIn(['meta', 'me']), 'following_count']) > 0,
});
class HomeTimeline extends React.PureComponent {
@connect(mapStateToProps)
@injectIntl
export default class HomeTimeline extends React.PureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
@@ -96,5 +98,3 @@ class HomeTimeline extends React.PureComponent {
}
}
export default connect(mapStateToProps)(injectIntl(HomeTimeline));

View File

@@ -19,7 +19,16 @@ const mapStateToProps = state => ({
accountIds: state.getIn(['user_lists', 'mutes', 'items']),
});
class Mutes extends ImmutablePureComponent {
@connect(mapStateToProps)
@injectIntl
export default class Mutes extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired,
};
componentWillMount () {
this.props.dispatch(fetchMutes());
@@ -59,12 +68,3 @@ class Mutes extends ImmutablePureComponent {
}
}
Mutes.propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired,
};
export default connect(mapStateToProps)(injectIntl(Mutes));

View File

@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl';
class ClearColumnButton extends React.Component {
export default class ClearColumnButton extends React.Component {
static propTypes = {
onClick: PropTypes.func.isRequired,
@@ -15,5 +15,3 @@ class ClearColumnButton extends React.Component {
}
}
export default ClearColumnButton;

View File

@@ -2,11 +2,10 @@ import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { FormattedMessage } from 'react-intl';
import ColumnCollapsable from '../../../components/column_collapsable';
import ClearColumnButton from './clear_column_button';
import SettingToggle from './setting_toggle';
class ColumnSettings extends React.PureComponent {
export default class ColumnSettings extends React.PureComponent {
static propTypes = {
settings: ImmutablePropTypes.map.isRequired,
@@ -16,7 +15,7 @@ class ColumnSettings extends React.PureComponent {
};
render () {
const { settings, onChange, onSave, onClear } = this.props;
const { settings, onChange, onClear } = this.props;
const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />;
const showStr = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />;
@@ -31,38 +30,36 @@ class ColumnSettings extends React.PureComponent {
<span className='column-settings__section'><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></span>
<div className='column-settings__row'>
<SettingToggle settings={settings} settingKey={['alerts', 'follow']} onChange={onChange} label={alertStr} />
<SettingToggle settings={settings} settingKey={['shows', 'follow']} onChange={onChange} label={showStr} />
<SettingToggle settings={settings} settingKey={['sounds', 'follow']} onChange={onChange} label={soundStr} />
<SettingToggle prefix='notifications' settings={settings} settingKey={['alerts', 'follow']} onChange={onChange} label={alertStr} />
<SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'follow']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'follow']} onChange={onChange} label={soundStr} />
</div>
<span className='column-settings__section'><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favourites:' /></span>
<div className='column-settings__row'>
<SettingToggle settings={settings} settingKey={['alerts', 'favourite']} onChange={onChange} label={alertStr} />
<SettingToggle settings={settings} settingKey={['shows', 'favourite']} onChange={onChange} label={showStr} />
<SettingToggle settings={settings} settingKey={['sounds', 'favourite']} onChange={onChange} label={soundStr} />
<SettingToggle prefix='notifications' settings={settings} settingKey={['alerts', 'favourite']} onChange={onChange} label={alertStr} />
<SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'favourite']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'favourite']} onChange={onChange} label={soundStr} />
</div>
<span className='column-settings__section'><FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' /></span>
<div className='column-settings__row'>
<SettingToggle settings={settings} settingKey={['alerts', 'mention']} onChange={onChange} label={alertStr} />
<SettingToggle settings={settings} settingKey={['shows', 'mention']} onChange={onChange} label={showStr} />
<SettingToggle settings={settings} settingKey={['sounds', 'mention']} onChange={onChange} label={soundStr} />
<SettingToggle prefix='notifications' settings={settings} settingKey={['alerts', 'mention']} onChange={onChange} label={alertStr} />
<SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'mention']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'mention']} onChange={onChange} label={soundStr} />
</div>
<span className='column-settings__section'><FormattedMessage id='notifications.column_settings.reblog' defaultMessage='Boosts:' /></span>
<div className='column-settings__row'>
<SettingToggle settings={settings} settingKey={['alerts', 'reblog']} onChange={onChange} label={alertStr} />
<SettingToggle settings={settings} settingKey={['shows', 'reblog']} onChange={onChange} label={showStr} />
<SettingToggle settings={settings} settingKey={['sounds', 'reblog']} onChange={onChange} label={soundStr} />
<SettingToggle prefix='notifications' settings={settings} settingKey={['alerts', 'reblog']} onChange={onChange} label={alertStr} />
<SettingToggle prefix='notifications' settings={settings} settingKey={['shows', 'reblog']} onChange={onChange} label={showStr} />
<SettingToggle prefix='notifications' settings={settings} settingKey={['sounds', 'reblog']} onChange={onChange} label={soundStr} />
</div>
</div>
);
}
}
export default ColumnSettings;

View File

@@ -2,14 +2,13 @@ import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import StatusContainer from '../../../containers/status_container';
import AccountContainer from '../../../containers/account_container';
import Avatar from '../../../components/avatar';
import { FormattedMessage } from 'react-intl';
import Permalink from '../../../components/permalink';
import emojify from '../../../emoji';
import escapeTextContentForBrowser from 'escape-html';
import ImmutablePureComponent from 'react-immutable-pure-component';
class Notification extends ImmutablePureComponent {
export default class Notification extends ImmutablePureComponent {
static propTypes = {
notification: ImmutablePropTypes.map.isRequired,
@@ -87,5 +86,3 @@ class Notification extends ImmutablePureComponent {
}
}
export default Notification;

View File

@@ -3,22 +3,23 @@ import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import Toggle from 'react-toggle';
class SettingToggle extends React.PureComponent {
export default class SettingToggle extends React.PureComponent {
static propTypes = {
prefix: PropTypes.string,
settings: ImmutablePropTypes.map.isRequired,
settingKey: PropTypes.array.isRequired,
label: PropTypes.node.isRequired,
onChange: PropTypes.func.isRequired,
}
onChange = (e) => {
this.props.onChange(this.props.settingKey, e.target.checked);
onChange = ({ target }) => {
this.props.onChange(this.props.settingKey, target.checked);
}
render () {
const { settings, settingKey, label, onChange } = this.props;
const id = `setting-toggle-${settingKey.join('-')}`;
const { prefix, settings, settingKey, label } = this.props;
const id = ['setting-toggle', prefix, ...settingKey].filter(Boolean).join('-');
return (
<div className='setting-toggle'>
@@ -29,5 +30,3 @@ class SettingToggle extends React.PureComponent {
}
}
export default SettingToggle;

View File

@@ -13,6 +13,7 @@ import ColumnSettingsContainer from './containers/column_settings_container';
import { createSelector } from 'reselect';
import Immutable from 'immutable';
import LoadMore from '../../components/load_more';
import { debounce } from 'lodash';
const messages = defineMessages({
title: { id: 'column.notifications', defaultMessage: 'Notifications' },
@@ -30,7 +31,9 @@ const mapStateToProps = state => ({
hasMore: !!state.getIn(['notifications', 'next']),
});
class Notifications extends React.PureComponent {
@connect(mapStateToProps)
@injectIntl
export default class Notifications extends React.PureComponent {
static propTypes = {
columnId: PropTypes.string,
@@ -48,19 +51,27 @@ class Notifications extends React.PureComponent {
trackScroll: true,
};
dispatchExpandNotifications = debounce(() => {
this.props.dispatch(expandNotifications());
}, 300, { leading: true });
dispatchScrollToTop = debounce((top) => {
this.props.dispatch(scrollTopNotifications(top));
}, 100);
handleScroll = (e) => {
const { scrollTop, scrollHeight, clientHeight } = e.target;
const offset = scrollHeight - scrollTop - clientHeight;
this._oldScrollPosition = scrollHeight - scrollTop;
if (250 > offset && !this.props.isLoading) {
if (this.props.hasMore) {
this.props.dispatch(expandNotifications());
}
} else if (scrollTop < 100) {
this.props.dispatch(scrollTopNotifications(true));
if (250 > offset && this.props.hasMore && !this.props.isLoading) {
this.dispatchExpandNotifications();
}
if (scrollTop < 100) {
this.dispatchScrollToTop(true);
} else {
this.props.dispatch(scrollTopNotifications(false));
this.dispatchScrollToTop(false);
}
}
@@ -72,7 +83,7 @@ class Notifications extends React.PureComponent {
handleLoadMore = (e) => {
e.preventDefault();
this.props.dispatch(expandNotifications());
this.dispatchExpandNotifications();
}
handlePin = () => {
@@ -173,5 +184,3 @@ class Notifications extends React.PureComponent {
}
}
export default connect(mapStateToProps)(injectIntl(Notifications));

View File

@@ -1,6 +1,6 @@
import { connect } from 'react-redux';
import ColumnSettings from '../../community_timeline/components/column_settings';
import { changeSetting, saveSettings } from '../../../actions/settings';
import { changeSetting } from '../../../actions/settings';
const mapStateToProps = state => ({
settings: state.getIn(['settings', 'public']),
@@ -12,10 +12,6 @@ const mapDispatchToProps = dispatch => ({
dispatch(changeSetting(['public', ...key], checked));
},
onSave () {
dispatch(saveSettings());
},
});
export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings);

View File

@@ -14,7 +14,6 @@ import {
} from '../../actions/timelines';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import ColumnSettingsContainer from './containers/column_settings_container';
import createStream from '../../stream';
@@ -28,7 +27,9 @@ const mapStateToProps = state => ({
accessToken: state.getIn(['meta', 'access_token']),
});
class PublicTimeline extends React.PureComponent {
@connect(mapStateToProps)
@injectIntl
export default class PublicTimeline extends React.PureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
@@ -142,5 +143,3 @@ class PublicTimeline extends React.PureComponent {
}
}
export default connect(mapStateToProps)(injectIntl(PublicTimeline));

View File

@@ -14,7 +14,8 @@ const mapStateToProps = (state, props) => ({
accountIds: state.getIn(['user_lists', 'reblogged_by', Number(props.params.statusId)]),
});
class Reblogs extends ImmutablePureComponent {
@connect(mapStateToProps)
export default class Reblogs extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
@@ -57,5 +58,3 @@ class Reblogs extends ImmutablePureComponent {
}
}
export default connect(mapStateToProps)(Reblogs);

View File

@@ -4,7 +4,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import emojify from '../../../emoji';
import Toggle from 'react-toggle';
class StatusCheckBox extends React.PureComponent {
export default class StatusCheckBox extends React.PureComponent {
static propTypes = {
status: ImmutablePropTypes.map.isRequired,
@@ -36,5 +36,3 @@ class StatusCheckBox extends React.PureComponent {
}
}
export default StatusCheckBox;

View File

@@ -15,7 +15,8 @@ const messages = defineMessages({
report: { id: 'status.report', defaultMessage: 'Report @{name}' },
});
class ActionBar extends React.PureComponent {
@injectIntl
export default class ActionBar extends React.PureComponent {
static contextTypes = {
router: PropTypes.object,
@@ -55,7 +56,6 @@ class ActionBar extends React.PureComponent {
handleReport = () => {
this.props.onReport(this.props.status);
this.context.router.history.push('/report');
}
render () {
@@ -91,5 +91,3 @@ class ActionBar extends React.PureComponent {
}
}
export default injectIntl(ActionBar);

View File

@@ -17,7 +17,7 @@ const getHostname = url => {
return parser.hostname;
};
class Card extends React.PureComponent {
export default class Card extends React.PureComponent {
static propTypes = {
card: ImmutablePropTypes.map,
@@ -97,5 +97,3 @@ class Card extends React.PureComponent {
}
}
export default Card;

View File

@@ -12,7 +12,7 @@ import { FormattedDate, FormattedNumber } from 'react-intl';
import CardContainer from '../containers/card_container';
import ImmutablePureComponent from 'react-immutable-pure-component';
class DetailedStatus extends ImmutablePureComponent {
export default class DetailedStatus extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
@@ -87,5 +87,3 @@ class DetailedStatus extends ImmutablePureComponent {
}
}
export default DetailedStatus;

View File

@@ -3,8 +3,6 @@ import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { fetchStatus } from '../../actions/statuses';
import Immutable from 'immutable';
import EmbeddedStatus from '../../components/status';
import MissingIndicator from '../../components/missing_indicator';
import DetailedStatus from './components/detailed_status';
import ActionBar from './components/action_bar';
@@ -21,17 +19,12 @@ import {
} from '../../actions/compose';
import { deleteStatus } from '../../actions/statuses';
import { initReport } from '../../actions/reports';
import {
makeGetStatus,
getStatusAncestors,
getStatusDescendants,
} from '../../selectors';
import { makeGetStatus } from '../../selectors';
import { ScrollContainer } from 'react-router-scroll';
import ColumnBackButton from '../../components/column_back_button';
import StatusContainer from '../../containers/status_container';
import { openModal } from '../../actions/modal';
import { isMobile } from '../../is_mobile';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
const messages = defineMessages({
@@ -55,7 +48,9 @@ const makeMapStateToProps = () => {
return mapStateToProps;
};
class Status extends ImmutablePureComponent {
@injectIntl
@connect(makeMapStateToProps)
export default class Status extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
@@ -159,8 +154,6 @@ class Status extends ImmutablePureComponent {
);
}
const account = status.get('account');
if (ancestorsIds && ancestorsIds.size > 0) {
ancestors = <div>{this.renderChildren(ancestorsIds)}</div>;
}
@@ -204,5 +197,3 @@ class Status extends ImmutablePureComponent {
}
}
export default injectIntl(connect(makeMapStateToProps)(Status));

View File

@@ -2,7 +2,6 @@ import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import IconButton from '../../../components/icon_button';
import Button from '../../../components/button';
import StatusContent from '../../../components/status_content';
import Avatar from '../../../components/avatar';
@@ -14,7 +13,8 @@ const messages = defineMessages({
reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
});
class BoostModal extends ImmutablePureComponent {
@injectIntl
export default class BoostModal extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
@@ -49,7 +49,7 @@ class BoostModal extends ImmutablePureComponent {
}
render () {
const { status, intl, onClose } = this.props;
const { status, intl } = this.props;
return (
<div className='modal-root__modal boost-modal'>
@@ -82,5 +82,3 @@ class BoostModal extends ImmutablePureComponent {
}
}
export default injectIntl(BoostModal);

View File

@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import { debounce } from 'lodash';
import scrollTop from '../../../scroll';
class Column extends React.PureComponent {
export default class Column extends React.PureComponent {
static propTypes = {
heading: PropTypes.string,
@@ -59,5 +59,3 @@ class Column extends React.PureComponent {
}
}
export default Column;

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