Compare commits

...

52 Commits

Author SHA1 Message Date
kibigo!
4d0fde3d93 Timeline should be a feature, not component 2017-06-24 17:10:55 -07:00
kibigo!
ff0ecca84c Removing vestigial hasUnread, pt. II 2017-06-24 16:37:02 -07:00
kibigo!
cdd7f792cb Removed vestigial hasUnread 2017-06-24 16:15:43 -07:00
kibigo!
77908849bb Unify timeline code 2017-06-24 15:55:55 -07:00
beatrix-bitrot
8b23bf7cbd clean up old avatar class 2017-06-24 03:51:01 +00:00
Matthew Walsh
f1a60d4b81 Unified avatar styling
Avatars now have consistent styling across all pages; border radius can be adjusted with a SASS variable ($ui-avatar-border-size)
2017-06-24 03:03:27 +00:00
kibigo!
2513d92c54 Un-hide dropdown menu ;P 2017-06-23 19:39:44 -07:00
kibigo!
414dfb3955 ESLint improvements for Profile Metadata 2017-06-23 18:43:30 -07:00
Gô Shoemake
67adbcc60c Reblog support for collapsed toots 2017-06-23 18:23:26 -07:00
beatrix-bitrot
453b9c6e7e missing punctuation 2017-06-23 22:01:04 +00:00
beatrix
d9b9bb8c5e glitch the getting started image 2017-06-23 21:50:45 +00:00
kibigo!
40ecbfd4a9 Very minor styling improvements to toot-collapsing 2017-06-23 21:50:45 +00:00
kibigo!
4fe45dda9a Updates height upon collapsing 2017-06-23 21:50:45 +00:00
kibigo!
4bd7482a7a Minor collapsing button improvements~ 2017-06-23 21:50:45 +00:00
kibigo!
93c52301ad Collapsable toots [1/??] 2017-06-23 21:50:45 +00:00
kibigo!
0d3ec19e89 Profile Metadata HACK 😈 2017-06-23 21:45:14 +00:00
Go Shoemake
62a75891ab Fixes drawer so stuff doesn't overflow 2017-06-23 21:45:14 +00:00
Charlotte Fields
b27842dc70 cybre cleanup 2017-06-23 21:45:14 +00:00
Chronister
39b6b37b74 cybrespace to 1.4.2 2017-06-23 21:45:14 +00:00
Chronister
65528fc54e All cybrespace changes through 5/28 2017-06-23 21:45:14 +00:00
Charlotte Fields
382572c213 adding cybre changes 2017-06-23 21:45:14 +00:00
beatrix-bitrot
9bc593d675 update local modifications for cors and cp 2017-06-23 21:45:14 +00:00
beatrix-bitrot
09f7ad3614 silly readme update to test automated deploys 2017-06-23 21:45:14 +00:00
beatrix-bitrot
7c2ea42cd5 update README.md 2017-06-23 21:45:14 +00:00
beatrix
ea785d0baf Update README.md 2017-06-23 21:45:14 +00:00
Beatrix Bitrot
a337c5dbe5 CORS tweaks 2017-06-23 21:45:14 +00: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
Ratmir Karabut
9ca02a00a6 Update Russian translation (#3902)
* Add Russian translation (ru)

* Fix a missing comma

* Fix the wording for better consistency

* Update Russian translation

* Arrange Russian setting alphabetically

* Fix syntax error

* Update Russian translation

* Fix formatting error

* Update Russian translation

* Update Russian translation

* Update ru.jsx

* Fix syntax error

* Remove two_factor_auth.warning (appears obsolete)

* Add missing strings in ru.yml

A lot of new strings translated, especially for the newly added admin section

* Fix translation consistency

* Update Russian translation

* Update Russian translation (pluralizations)

* Update Russian translation

* Update Russian translation

* Update Russian translation (pin)

* Update Russian translation (account deletion)

* Fix extra line
2017-06-22 23:28:58 +02:00
nightpool
3e8e9c8ae4 Use the stable RVM installer (#3901)
as mentioned by ElvenSpellmaker here: https://github.com/rvm/rvm/issues/4068

Adds a workaround for the issue mentioned by @abcang here: https://github.com/tootsuite/mastodon/pull/3897#issuecomment-310436668 and makes sure that we're using the stable installer to install the stable version.
2017-06-22 23:28:52 +02:00
Charlotte Fields
7bc1805827 fixed vagrantfile (#3897) 2017-06-22 18:35:27 +02:00
Akihiko Odaki (@fn_aki@pawoo.net)
e27f792c24 Some minor change and spec for Account (#3813)
* Introduce domains method to Account relation

Account had followers_domains method, which was excessively specific.
Let relation of Account have domains method instead.

* Move follow_mapping in Account to AccountInteractions

* Introduce shared examples for AccountAvatar inclusion

* Cover Account more
2017-06-22 18:34:27 +02:00
Quent-in
98fab24bea Update of doorkeeper.oc.yml (#3896)
Just some adjustements
2017-06-22 13:39:13 +02:00
Eugen Rochko
f566c47dda Fix travis builds
https://github.com/travis-ci/travis-ci/issues/7941#issuecomment-310320597
2017-06-22 13:24:00 +02:00
Eugen Rochko
0190aac240 Fix regression from #3842 (#3892)
* Fix regression from #3842

Simplify the query by omitting all direct statuses. Private statuses
are allowed because they are from accounts we are following (so
by definition)

Resolves #3887 (alternative)

* Adjust test
2017-06-22 02:38:50 +02:00
unarist
cc382c5006 Don't attach IntersectionObserver for wrapped statuses (#3883)
(This patch has been merged as bugfix and reverted, but still valuable as
improvement)

Previously, we've attached IntersectionObserver twice for boosted statuses:
wrapper Status and wrapped Status. but wrapped Status don't need to manage
intersection and visibility by itself, because it's a part of wrapper Status.
2017-06-21 06:47:36 +02:00
168 changed files with 1705 additions and 1187 deletions

View File

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

View File

@@ -1,7 +1,9 @@
--- ---
root: true
env: env:
browser: true browser: true
node: false node: true
es6: true es6: true
parser: babel-eslint parser: babel-eslint
@@ -52,8 +54,14 @@ rules:
no-mixed-spaces-and-tabs: warn no-mixed-spaces-and-tabs: warn
no-nested-ternary: warn no-nested-ternary: warn
no-trailing-spaces: warn no-trailing-spaces: warn
no-undef: error
no-unreachable: error no-unreachable: error
no-unused-expressions: error no-unused-expressions: error
no-unused-vars:
- error
- vars: all
args: after-used
ignoreRestSiblings: true
object-curly-spacing: object-curly-spacing:
- error - error
- always - always
@@ -81,7 +89,10 @@ rules:
- 2 - 2
react/jsx-no-bind: error react/jsx-no-bind: error
react/jsx-no-duplicate-props: error react/jsx-no-duplicate-props: error
react/jsx-no-undef: error
react/jsx-tag-spacing: error react/jsx-tag-spacing: error
react/jsx-uses-react: error
react/jsx-uses-vars: error
react/jsx-wrap-multilines: error react/jsx-wrap-multilines: error
react/no-multi-comp: off react/no-multi-comp: off
react/no-string-refs: error react/no-string-refs: error

View File

@@ -7,7 +7,7 @@ cache:
- public/assets - public/assets
- public/packs-test - public/packs-test
dist: trusty dist: trusty
sudo: false sudo: required
notifications: notifications:
email: false email: false

View File

@@ -1,70 +1,5 @@
Mastodon Mastodon Glitch Edition
======== ========
Now with automated deploys!
[![Build Status](http://img.shields.io/travis/tootsuite/mastodon.svg)][travis] So here's the deal: we all work on this code, and then it runs on dev.glitch.social and anyone who uses that does so absolutely at their own risk. can you dig it?
[![Code Climate](https://img.shields.io/codeclimate/github/tootsuite/mastodon.svg)][code_climate]
[travis]: https://travis-ci.org/tootsuite/mastodon
[code_climate]: https://codeclimate.com/github/tootsuite/mastodon
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), [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:
[![Screenshot](https://i.imgur.com/pG3Nnz3.jpg)][youtube_demo]
[youtube_demo]: https://www.youtube.com/watch?v=YO1jQ8_rAMU
The project focus is a clean REST API and a good user interface. Ruby on Rails is used for the back-end, while React.js and Redux are used for the dynamic front-end. A static front-end for public resources (profiles and statuses) is also provided.
If you would like, you can [support the development of this project on Patreon][patreon]. Alternatively, you can donate to this BTC address: `17j2g7vpgHhLuXhN4bueZFCvdxxieyRVWd`
[patreon]: https://www.patreon.com/user?u=619786
## Resources
- [List of Mastodon instances](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/List-of-Mastodon-instances.md)
- [Use this tool to find Twitter friends on Mastodon](https://mastodon-bridge.herokuapp.com)
- [API overview](https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md)
- [Frequently Asked Questions](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/FAQ.md)
- [List of apps](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md)
## Features
- **Fully interoperable with GNU social and any OStatus platform**
Whatever implements Atom feeds, ActivityStreams, Salmon, PubSubHubbub and Webfinger is part of the network
- **Real-time timeline updates**
See the updates of people you're following appear in real-time in the UI via WebSockets
- **Federated thread resolving**
If someone you follow replies to a user unknown to the server, the server fetches the full thread so you can view it without leaving the UI
- **Media attachments like images and WebM**
Upload and view images and WebM videos attached to the updates
- **OAuth2 and a straightforward REST API**
Mastodon acts as an OAuth2 provider so 3rd party apps can use the API, which is RESTful and simple
- **Background processing for long-running tasks**
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**
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
There are guides in the documentation repository for [deploying on various platforms](https://github.com/tootsuite/documentation#running-mastodon).
## Contributing
You can open issues for bugs you've found or features you think are missing. You can also submit pull requests to this repository. [Here are the guidelines for code contributions](CONTRIBUTING.md)
**IRC channel**: #mastodon on irc.freenode.net
## Extra credits
- The [Emoji One](https://github.com/Ranks/emojione) pack has been used for the emojis
- The error page image courtesy of [Dopatwo](https://www.youtube.com/user/dopatwo)
![Mastodon error image](https://mastodon.social/oops.png)

5
Vagrantfile vendored
View File

@@ -42,9 +42,12 @@ sudo apt-get install \
# Install rvm # Install rvm
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://get.rvm.io | bash -s stable --ruby=$RUBY_VERSION curl -sSL https://raw.githubusercontent.com/rvm/rvm/stable/binscripts/rvm-installer | 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -1,5 +1,4 @@
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';
@@ -597,7 +596,7 @@ export function authorizeFollowRequest(id) {
api(getState) api(getState)
.post(`/api/v1/follow_requests/${id}/authorize`) .post(`/api/v1/follow_requests/${id}/authorize`)
.then(response => dispatch(authorizeFollowRequestSuccess(id))) .then(() => dispatch(authorizeFollowRequestSuccess(id)))
.catch(error => dispatch(authorizeFollowRequestFail(id, error))); .catch(error => dispatch(authorizeFollowRequestFail(id, error)));
}; };
}; };
@@ -631,7 +630,7 @@ export function rejectFollowRequest(id) {
api(getState) api(getState)
.post(`/api/v1/follow_requests/${id}/reject`) .post(`/api/v1/follow_requests/${id}/reject`)
.then(response => dispatch(rejectFollowRequestSuccess(id))) .then(() => dispatch(rejectFollowRequestSuccess(id)))
.catch(error => dispatch(rejectFollowRequestFail(id, error))); .catch(error => dispatch(rejectFollowRequestFail(id, error)));
}; };
}; };

View File

@@ -16,7 +16,7 @@ export function blockDomain(domain, accountId) {
return (dispatch, getState) => { return (dispatch, getState) => {
dispatch(blockDomainRequest(domain)); 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)); dispatch(blockDomainSuccess(domain, accountId));
}).catch(err => { }).catch(err => {
dispatch(blockDomainFail(domain, err)); dispatch(blockDomainFail(domain, err));
@@ -51,7 +51,7 @@ export function unblockDomain(domain, accountId) {
return (dispatch, getState) => { return (dispatch, getState) => {
dispatch(unblockDomainRequest(domain)); 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)); dispatch(unblockDomainSuccess(domain, accountId));
}).catch(err => { }).catch(err => {
dispatch(unblockDomainFail(domain, 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_CLEAR = 'NOTIFICATIONS_CLEAR';
export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP'; export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP';
const messages = defineMessages({ defineMessages({
mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' }, mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' },
}); });

View File

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

View File

@@ -16,7 +16,8 @@ const messages = defineMessages({
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }, unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
}); });
class Account extends ImmutablePureComponent { @injectIntl
export default class Account extends ImmutablePureComponent {
static propTypes = { static propTypes = {
account: ImmutablePropTypes.map.isRequired, 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]; const filename = url => url.split('/').pop().split('#')[0].split('?')[0];
class AttachmentList extends ImmutablePureComponent { export default class AttachmentList extends ImmutablePureComponent {
static propTypes = { static propTypes = {
media: ImmutablePropTypes.list.isRequired, 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 = { static propTypes = {
value: PropTypes.string, 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 React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
class Avatar extends React.PureComponent { export default class Avatar extends React.PureComponent {
static propTypes = { static propTypes = {
src: PropTypes.string.isRequired, 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 React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
class AvatarOverlay extends React.PureComponent { export default class AvatarOverlay extends React.PureComponent {
static propTypes = { static propTypes = {
staticSrc: PropTypes.string.isRequired, 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 PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
class Button extends React.PureComponent { export default class Button extends React.PureComponent {
static propTypes = { static propTypes = {
text: PropTypes.node, 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 PropTypes from 'prop-types';
import scrollTop from '../scroll'; import scrollTop from '../scroll';
class Column extends React.PureComponent { export default class Column extends React.PureComponent {
static propTypes = { static propTypes = {
children: PropTypes.node, 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 { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
class ColumnBackButton extends React.PureComponent { export default class ColumnBackButton extends React.PureComponent {
static contextTypes = { static contextTypes = {
router: PropTypes.object, 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 { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
class ColumnBackButtonSlim extends React.PureComponent { export default class ColumnBackButtonSlim extends React.PureComponent {
static contextTypes = { static contextTypes = {
router: PropTypes.object, router: PropTypes.object,
@@ -25,5 +25,3 @@ class ColumnBackButtonSlim extends React.PureComponent {
} }
} }
export default ColumnBackButtonSlim;

View File

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

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import classNames from 'classnames'; import classNames from 'classnames';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
class ColumnHeader extends React.PureComponent { export default class ColumnHeader extends React.PureComponent {
static contextTypes = { static contextTypes = {
router: PropTypes.object, router: PropTypes.object,
@@ -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 escapeTextContentForBrowser from 'escape-html';
import emojify from '../emoji'; import emojify from '../emoji';
class DisplayName extends React.PureComponent { export default class DisplayName extends React.PureComponent {
static propTypes = { static propTypes = {
account: ImmutablePropTypes.map.isRequired, 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 Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
class DropdownMenu extends React.PureComponent { export default class DropdownMenu extends React.PureComponent {
static contextTypes = { static contextTypes = {
router: PropTypes.object, router: PropTypes.object,
@@ -56,7 +56,7 @@ class DropdownMenu extends React.PureComponent {
return <li key={`sep-${i}`} className='dropdown__sep' />; return <li key={`sep-${i}`} className='dropdown__sep' />;
} }
const { text, action, href = '#' } = item; const { text, href = '#' } = item;
return ( return (
<li className='dropdown__content-list-item' key={`${text}-${i}`}> <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 React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
class ExtendedVideoPlayer extends React.PureComponent { export default class ExtendedVideoPlayer extends React.PureComponent {
static propTypes = { static propTypes = {
src: PropTypes.string.isRequired, 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 spring from 'react-motion/lib/spring';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
class IconButton extends React.PureComponent { export default class IconButton extends React.PureComponent {
static propTypes = { static propTypes = {
className: PropTypes.string, className: PropTypes.string,
@@ -17,6 +17,7 @@ class IconButton extends React.PureComponent {
disabled: PropTypes.bool, disabled: PropTypes.bool,
inverted: PropTypes.bool, inverted: PropTypes.bool,
animate: PropTypes.bool, animate: PropTypes.bool,
flip: PropTypes.bool,
overlay: PropTypes.bool, overlay: PropTypes.bool,
}; };
@@ -69,7 +70,7 @@ class IconButton extends React.PureComponent {
} }
return ( return (
<Motion defaultStyle={{ rotate: this.props.active ? -360 : 0 }} style={{ rotate: this.props.animate ? spring(this.props.active ? -360 : 0, { stiffness: 120, damping: 7 }) : 0 }}> <Motion defaultStyle={{ rotate: this.props.active ? (this.props.flip ? -180 : -360) : 0 }} style={{ rotate: this.props.animate ? spring(this.props.active ? (this.props.flip ? -180 : -360) : 0, { stiffness: this.props.flip ? 60 : 120, damping: 7 }) : 0 }}>
{({ rotate }) => {({ rotate }) =>
<button <button
aria-label={this.props.title} aria-label={this.props.title}
@@ -86,5 +87,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 { FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
class LoadMore extends React.PureComponent { export default class LoadMore extends React.PureComponent {
static propTypes = { static propTypes = {
onClick: PropTypes.func, onClick: PropTypes.func,
@@ -17,5 +17,3 @@ class LoadMore extends React.PureComponent {
} }
} }
export default LoadMore;

View File

@@ -123,7 +123,8 @@ class Item extends React.PureComponent {
} }
class MediaGallery extends React.PureComponent { @injectIntl
export default class MediaGallery extends React.PureComponent {
static propTypes = { static propTypes = {
sensitive: PropTypes.bool, sensitive: PropTypes.bool,
@@ -138,7 +139,7 @@ class MediaGallery extends React.PureComponent {
visible: !this.props.sensitive, visible: !this.props.sensitive,
}; };
handleOpen = (e) => { handleOpen = () => {
this.setState({ visible: !this.state.visible }); this.setState({ visible: !this.state.visible });
} }
@@ -183,5 +184,3 @@ class MediaGallery extends React.PureComponent {
} }
} }
export default injectIntl(MediaGallery);

View File

@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
class Permalink extends React.PureComponent { export default class Permalink extends React.PureComponent {
static contextTypes = { static contextTypes = {
router: PropTypes.object, router: PropTypes.object,
@@ -32,5 +32,3 @@ class Permalink extends React.PureComponent {
} }
} }
export default Permalink;

View File

@@ -11,7 +11,8 @@ const dateFormatOptions = {
minute: '2-digit', minute: '2-digit',
}; };
class RelativeTimestamp extends React.Component { @injectIntl
export default class RelativeTimestamp extends React.Component {
static propTypes = { static propTypes = {
intl: PropTypes.object.isRequired, 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 PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
class SettingText extends React.PureComponent { export default class SettingText extends React.PureComponent {
static propTypes = { static propTypes = {
settings: ImmutablePropTypes.map.isRequired, settings: ImmutablePropTypes.map.isRequired,
@@ -29,5 +29,3 @@ class SettingText extends React.PureComponent {
} }
} }
export default SettingText;

View File

@@ -3,20 +3,24 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Avatar from './avatar'; import Avatar from './avatar';
import AvatarOverlay from './avatar_overlay'; import AvatarOverlay from './avatar_overlay';
import RelativeTimestamp from './relative_timestamp';
import DisplayName from './display_name'; import DisplayName from './display_name';
import MediaGallery from './media_gallery'; import MediaGallery from './media_gallery';
import VideoPlayer from './video_player'; import VideoPlayer from './video_player';
import AttachmentList from './attachment_list';
import StatusContent from './status_content'; import StatusContent from './status_content';
import StatusActionBar from './status_action_bar'; import StatusActionBar from './status_action_bar';
import { FormattedMessage } from 'react-intl'; import IconButton from './icon_button';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import emojify from '../emoji'; import emojify from '../emoji';
import escapeTextContentForBrowser from 'escape-html'; import escapeTextContentForBrowser from 'escape-html';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import scheduleIdleTask from '../features/ui/util/schedule_idle_task'; import scheduleIdleTask from '../features/ui/util/schedule_idle_task';
class Status extends ImmutablePureComponent { const messages = defineMessages({
collapse: { id: 'status.collapse', defaultMessage: 'Collapse' },
uncollapse: { id: 'status.uncollapse', defaultMessage: 'Uncollapse' },
});
class StatusUnextended extends ImmutablePureComponent {
static contextTypes = { static contextTypes = {
router: PropTypes.object, router: PropTypes.object,
@@ -38,12 +42,14 @@ class Status extends ImmutablePureComponent {
autoPlayGif: PropTypes.bool, autoPlayGif: PropTypes.bool,
muted: PropTypes.bool, muted: PropTypes.bool,
intersectionObserverWrapper: PropTypes.object, intersectionObserverWrapper: PropTypes.object,
intl: PropTypes.object.isRequired,
}; };
state = { state = {
isExpanded: false, isExpanded: false,
isIntersecting: true, // assume intersecting until told otherwise isIntersecting: true, // assume intersecting until told otherwise
isHidden: false, // set to true in requestIdleCallback to trigger un-render isHidden: false, // set to true in requestIdleCallback to trigger un-render
isCollapsed: false,
} }
// Avoid checking props that are functions (and whose equality will always // Avoid checking props that are functions (and whose equality will always
@@ -61,7 +67,11 @@ class Status extends ImmutablePureComponent {
updateOnStates = ['isExpanded'] updateOnStates = ['isExpanded']
shouldComponentUpdate (nextProps, nextState) { shouldComponentUpdate (nextProps, nextState) {
if (!nextState.isIntersecting && nextState.isHidden) { if (nextState.isCollapsed !== this.state.isCollapsed) {
// If the collapsed state of the element has changed then we definitely
// need to re-update.
return true;
} else if (!nextState.isIntersecting && nextState.isHidden) {
// It's only if we're not intersecting (i.e. offscreen) and isHidden is true // It's only if we're not intersecting (i.e. offscreen) and isHidden is true
// that either "isIntersecting" or "isHidden" matter, and then they're // that either "isIntersecting" or "isHidden" matter, and then they're
// the only things that matter. // the only things that matter.
@@ -75,7 +85,13 @@ class Status extends ImmutablePureComponent {
return super.shouldComponentUpdate(nextProps, nextState); return super.shouldComponentUpdate(nextProps, nextState);
} }
componentDidUpdate (prevProps, prevState) {
if (prevState.isCollapsed !== this.state.isCollapsed) this.saveHeight();
}
componentDidMount () { componentDidMount () {
const node = this.node;
if (!this.props.intersectionObserverWrapper) { if (!this.props.intersectionObserverWrapper) {
// TODO: enable IntersectionObserver optimization for notification statuses. // TODO: enable IntersectionObserver optimization for notification statuses.
// These are managed in notifications/index.js rather than status_list.js // These are managed in notifications/index.js rather than status_list.js
@@ -87,6 +103,8 @@ class Status extends ImmutablePureComponent {
this.handleIntersection this.handleIntersection
); );
if (node.clientHeight > 400 && !(this.props.status.get('reblog', null) !== null && typeof this.props.status.get('reblog') === 'object')) this.setState({ isCollapsed: true });
this.componentMounted = true; this.componentMounted = true;
} }
@@ -151,11 +169,18 @@ class Status extends ImmutablePureComponent {
this.setState({ isExpanded: !this.state.isExpanded }); this.setState({ isExpanded: !this.state.isExpanded });
}; };
handleCollapsedClick = () => {
this.setState({ isCollapsed: !this.state.isCollapsed });
}
render () { render () {
let media = null; let media = null;
let statusAvatar; let statusAvatar;
const { status, account, ...other } = this.props;
const { isExpanded, isIntersecting, isHidden } = this.state; // Exclude intersectionObserverWrapper from `other` variable
// because intersection is managed in here.
const { status, account, intersectionObserverWrapper, intl, ...other } = this.props;
const { isExpanded, isIntersecting, isHidden, isCollapsed } = this.state;
if (status === null) { if (status === null) {
return null; return null;
@@ -208,9 +233,17 @@ class Status extends ImmutablePureComponent {
} }
return ( return (
<div className={`status ${this.props.muted ? 'muted' : ''} status-${status.get('visibility')}`} data-id={status.get('id')} ref={this.handleRef}> <div className={`status ${this.props.muted ? 'muted' : ''} status-${status.get('visibility')} ${isCollapsed ? 'status-collapsed' : ''}`} data-id={status.get('id')} ref={this.handleRef}>
<div className='status__info'> <div className='status__info'>
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
<IconButton
className='status__collapse-button'
animate flip
active={isCollapsed}
title={isCollapsed ? intl.formatMessage(messages.uncollapse) : intl.formatMessage(messages.collapse)}
icon='angle-double-up'
onClick={this.handleCollapsedClick}
/>
<a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} className='status__display-name'> <a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} className='status__display-name'>
<div className='status__avatar'> <div className='status__avatar'>
@@ -219,17 +252,19 @@ class Status extends ImmutablePureComponent {
<DisplayName account={status.get('account')} /> <DisplayName account={status.get('account')} />
</a> </a>
</div> </div>
<StatusContent status={status} onClick={this.handleClick} expanded={isExpanded} onExpandedToggle={this.handleExpandedToggle} onHeightUpdate={this.saveHeight} /> <StatusContent status={status} onClick={this.handleClick} expanded={isExpanded} onExpandedToggle={this.handleExpandedToggle} onHeightUpdate={this.saveHeight} />
{media} {isCollapsed ? null : media}
<StatusActionBar {...this.props} /> {isCollapsed ? null : <StatusActionBar status={status} account={account} {...other} />}
</div> </div>
); );
} }
} }
const Status = injectIntl(StatusUnextended);
export default Status; export default Status;

View File

@@ -5,6 +5,7 @@ import IconButton from './icon_button';
import DropdownMenu from './dropdown_menu'; import DropdownMenu from './dropdown_menu';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import RelativeTimestamp from './relative_timestamp';
const messages = defineMessages({ const messages = defineMessages({
delete: { id: 'status.delete', defaultMessage: 'Delete' }, delete: { id: 'status.delete', defaultMessage: 'Delete' },
@@ -22,7 +23,8 @@ const messages = defineMessages({
unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' }, unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' },
}); });
class StatusActionBar extends ImmutablePureComponent { @injectIntl
export default class StatusActionBar extends ImmutablePureComponent {
static contextTypes = { static contextTypes = {
router: PropTypes.object, router: PropTypes.object,
@@ -144,10 +146,10 @@ class StatusActionBar extends ImmutablePureComponent {
<div className='status__action-bar-dropdown'> <div className='status__action-bar-dropdown'>
<DropdownMenu items={menu} icon='ellipsis-h' size={18} direction='right' ariaLabel='More' /> <DropdownMenu items={menu} icon='ellipsis-h' size={18} direction='right' ariaLabel='More' />
</div> </div>
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
</div> </div>
); );
} }
} }
export default injectIntl(StatusActionBar);

View File

@@ -7,7 +7,7 @@ import { isRtl } from '../rtl';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import Permalink from './permalink'; import Permalink from './permalink';
class StatusContent extends React.PureComponent { export default class StatusContent extends React.PureComponent {
static contextTypes = { static contextTypes = {
router: PropTypes.object, router: PropTypes.object,
@@ -32,7 +32,6 @@ class StatusContent extends React.PureComponent {
for (var i = 0; i < links.length; ++i) { for (var i = 0; i < links.length; ++i) {
let link = links[i]; let link = links[i];
let mention = this.props.status.get('mentions').find(item => link.href === item.get('url')); 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) { if (mention) {
link.addEventListener('click', this.onMentionClick.bind(this, mention), false); link.addEventListener('click', this.onMentionClick.bind(this, mention), false);
@@ -136,7 +135,7 @@ class StatusContent extends React.PureComponent {
} }
return ( 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 }}> <p style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}>
<span dangerouslySetInnerHTML={spoilerContent} /> <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 IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
class StatusList extends ImmutablePureComponent { export default class StatusList extends ImmutablePureComponent {
static propTypes = { static propTypes = {
scrollKey: PropTypes.string.isRequired, scrollKey: PropTypes.string.isRequired,
@@ -99,7 +99,7 @@ class StatusList extends ImmutablePureComponent {
} }
render () { 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 loadMore = null;
let scrollableArea = 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' }, expand_video: { id: 'video_player.expand', defaultMessage: 'Expand video' },
}); });
class VideoPlayer extends React.PureComponent { @injectIntl
export default class VideoPlayer extends React.PureComponent {
static propTypes = { static propTypes = {
media: ImmutablePropTypes.map.isRequired, 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 PropTypes from 'prop-types';
import configureStore from '../store/configureStore'; import configureStore from '../store/configureStore';
import { import {
refreshTimelineSuccess,
updateTimeline, updateTimeline,
deleteFromTimelines, deleteFromTimelines,
refreshHomeTimeline, refreshHomeTimeline,
@@ -27,7 +26,11 @@ const store = configureStore();
const initialState = JSON.parse(document.getElementById('initial-state').textContent); const initialState = JSON.parse(document.getElementById('initial-state').textContent);
store.dispatch(hydrateStore(initialState)); store.dispatch(hydrateStore(initialState));
class Mastodon extends React.PureComponent { export default class Mastodon extends React.PureComponent {
static propTypes = {
locale: PropTypes.string.isRequired,
};
componentDidMount() { componentDidMount() {
const { locale } = this.props; 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 { muteStatus, unmuteStatus, deleteStatus } from '../actions/statuses';
import { initReport } from '../actions/reports'; import { initReport } from '../actions/reports';
import { openModal } from '../actions/modal'; import { openModal } from '../actions/modal';
import { createSelector } from 'reselect';
import { isMobile } from '../is_mobile';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
const messages = defineMessages({ const messages = defineMessages({

View File

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

View File

@@ -5,9 +5,7 @@ import emojify from '../../../emoji';
import escapeTextContentForBrowser from 'escape-html'; import escapeTextContentForBrowser from 'escape-html';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import IconButton from '../../../components/icon_button'; import IconButton from '../../../components/icon_button';
import Motion from 'react-motion/lib/Motion'; import Avatar from '../../../components/avatar';
import spring from 'react-motion/lib/spring';
import { connect } from 'react-redux';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
const messages = defineMessages({ const messages = defineMessages({
@@ -16,68 +14,65 @@ const messages = defineMessages({
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' }, requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' },
}); });
const makeMapStateToProps = () => { /*
const mapStateToProps = (state, props) => ({ THIS IS A MESS BECAUSE EFFING MASTODON AND ITS EFFING HTML BIOS
autoPlayGif: state.getIn(['meta', 'auto_play_gif']), INSTEAD OF JUST STORING EVERYTHING IN PLAIN EFFING TEXT ! ! ! !
}); BLANK LINES ALSO WON'T WORK BECAUSE RIGHT NOW MASTODON CONVERTS
THOSE INTO `<P>` ELEMENTS INSTEAD OF LEAVING IT AS `<BR><BR>` !
TL:DR; THIS IS LARGELY A HACK. WITH BETTER BACKEND STUFF WE CAN
IMPROVE THIS BY BETTER PREDICTING HOW THE METADATA WILL BE SENT
WHILE MAINTAINING BASIC PLAIN-TEXT PROCESSING. THE OTHER OPTION
IS TO TURN ALL BIOS INTO PLAIN-TEXT VIA A TREE-WALKER, AND THEN
PROCESS THE YAML AND LINKS AND EVERYTHING OURSELVES. THIS WOULD
BE INCREDIBLY COMPLICATED, AND IT WOULD BE A MILLION TIMES LESS
DIFFICULT IF MASTODON JUST GAVE US PLAIN-TEXT BIOS (WHICH QUITE
FRANKLY MAKES THE MOST SENSE SINCE THAT'S WHAT USERS PROVIDE IN
SETTINGS) TO BEGIN WITH AND LEFT ALL PROCESSING TO THE FRONTEND
TO HANDLE ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !
ANYWAY I KNOW WHAT NEEDS TO BE DONE REGARDING BACKEND STUFF BUT
I'M NOT SMART ENOUGH TO FIGURE OUT HOW TO ACTUALLY IMPLEMENT IT
SO FEEL FREE TO @ ME IF YOU NEED MY IDEAS REGARDING THAT. UNTIL
THEN WE'LL JUST HAVE TO MAKE DO WITH THIS MESSY AND UNFORTUNATE
HACKING ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !
return mapStateToProps; with love,
@kibi@glitch.social <3
*/
const NEW_LINE = /(?:^|\r?\n|<br\s*\/?>)/g;
const YAML_OPENER = /---/;
const YAML_CLOSER = /(?:---|\.\.\.)/;
const YAML_STRING = /(?:"(?:[^"\n]){1,32}"|'(?:[^'\n]){1,32}'|(?:[^'":\n]){1,32})/g;
const YAML_LINE = new RegExp('\\s*' + YAML_STRING.source + '\\s*:\\s*' + YAML_STRING.source + '\\s*', 'g');
const BIO_REGEX = new RegExp(NEW_LINE.source + '*' + YAML_OPENER.source + NEW_LINE.source + '+(?:' + YAML_LINE.source + NEW_LINE.source + '+){0,4}' + YAML_CLOSER.source + NEW_LINE.source + '*');
const processBio = (data) => {
let props = { text: data, metadata: [] };
let yaml = data.match(BIO_REGEX);
if (!yaml) return props;
else yaml = yaml[0];
let start = props.text.indexOf(yaml);
let end = start + yaml.length;
props.text = props.text.substr(0, start) + props.text.substr(end);
yaml = yaml.replace(NEW_LINE, '\n');
let metadata = (yaml ? yaml.match(YAML_LINE) : []) || [];
for (let i = 0; i < metadata.length; i++) {
let result = metadata[i].match(YAML_STRING);
if (result[0][0] === '"' || result[0][0] === '\'') result[0] = result[0].substr(1, result[0].length - 2);
if (result[1][0] === '"' || result[1][0] === '\'') result[0] = result[1].substr(1, result[1].length - 2);
props.metadata.push(result);
}
return props;
}; };
class Avatar extends ImmutablePureComponent { @injectIntl
export default class Header extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.map.isRequired,
autoPlayGif: PropTypes.bool.isRequired,
};
state = {
isHovered: false,
};
handleMouseOver = () => {
if (this.state.isHovered) return;
this.setState({ isHovered: true });
}
handleMouseOut = () => {
if (!this.state.isHovered) return;
this.setState({ isHovered: false });
}
render () {
const { account, autoPlayGif } = this.props;
const { isHovered } = this.state;
return (
<Motion defaultStyle={{ radius: 90 }} style={{ radius: spring(isHovered ? 30 : 90, { stiffness: 180, damping: 12 }) }}>
{({ radius }) =>
<a // eslint-disable-line jsx-a11y/anchor-has-content
href={account.get('url')}
className='account__header__avatar'
target='_blank'
rel='noopener'
style={{ borderRadius: `${radius}px`, backgroundImage: `url(${autoPlayGif || isHovered ? account.get('avatar') : account.get('avatar_static')})` }}
onMouseOver={this.handleMouseOver}
onMouseOut={this.handleMouseOut}
onFocus={this.handleMouseOver}
onBlur={this.handleMouseOut}
/>
}
</Motion>
);
}
}
class Header extends ImmutablePureComponent {
static propTypes = { static propTypes = {
account: ImmutablePropTypes.map, account: ImmutablePropTypes.map,
me: PropTypes.number.isRequired, me: PropTypes.number.isRequired,
onFollow: PropTypes.func.isRequired, onFollow: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
autoPlayGif: PropTypes.bool.isRequired,
}; };
render () { render () {
@@ -120,25 +115,47 @@ class Header extends ImmutablePureComponent {
lockedIcon = <i className='fa fa-lock' />; lockedIcon = <i className='fa fa-lock' />;
} }
const content = { __html: emojify(account.get('note')) }; const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) }; const { text, metadata } = processBio(account.get('note'));
return ( return (
<div className='account__header' style={{ backgroundImage: `url(${account.get('header')})` }}> <div className='account__header__wrapper'>
<div> <div className='account__header' style={{ backgroundImage: `url(${account.get('header')})` }}>
<Avatar account={account} autoPlayGif={this.props.autoPlayGif} /> <div>
<a href={account.get('url')} target='_blank' rel='noopener'>
<span className='account__header__avatar'><Avatar src={account.get('avatar')} animate size={90} /></span>
<span className='account__header__display-name' dangerouslySetInnerHTML={displayNameHTML} />
</a>
<span className='account__header__username'>@{account.get('acct')} {lockedIcon}</span>
<div className='account__header__content' dangerouslySetInnerHTML={{ __html: emojify(text) }} />
<span className='account__header__display-name' dangerouslySetInnerHTML={displayNameHTML} /> {info}
<span className='account__header__username'>@{account.get('acct')} {lockedIcon}</span> {actionBtn}
<div className='account__header__content' dangerouslySetInnerHTML={content} /> </div>
{info}
{actionBtn}
</div> </div>
{metadata.length && (
<div className='account__metadata'>
{(() => {
let data = [];
for (let i = 0; i < metadata.length; i++) {
data.push(
<div
className='account__metadata-item'
title={metadata[i][0] + ':' + metadata[i][1]}
key={i}
>
<span dangerouslySetInnerHTML={{ __html: emojify(metadata[i][0]) }} />
<strong dangerouslySetInnerHTML={{ __html: emojify(metadata[i][1]) }} />
</div>
);
}
return data;
})()}
</div>
) || null}
</div> </div>
); );
} }
} }
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 ImmutablePureComponent from 'react-immutable-pure-component';
import Permalink from '../../../components/permalink'; import Permalink from '../../../components/permalink';
class MediaItem extends ImmutablePureComponent { export default class MediaItem extends ImmutablePureComponent {
static propTypes = { static propTypes = {
media: ImmutablePropTypes.map.isRequired, 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 LoadingIndicator from '../../components/loading_indicator';
import Column from '../ui/components/column'; import Column from '../ui/components/column';
import ColumnBackButton from '../../components/column_back_button'; import ColumnBackButton from '../../components/column_back_button';
import Immutable from 'immutable';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { getAccountGallery } from '../../selectors'; import { getAccountGallery } from '../../selectors';
import MediaItem from './components/media_item'; import MediaItem from './components/media_item';
@@ -23,7 +22,8 @@ const mapStateToProps = (state, props) => ({
autoPlayGif: state.getIn(['meta', 'auto_play_gif']), autoPlayGif: state.getIn(['meta', 'auto_play_gif']),
}); });
class AccountGallery extends ImmutablePureComponent { @connect(mapStateToProps)
export default class AccountGallery extends ImmutablePureComponent {
static propTypes = { static propTypes = {
params: PropTypes.object.isRequired, 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 MissingIndicator from '../../../components/missing_indicator';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
class Header extends ImmutablePureComponent { export default class Header extends ImmutablePureComponent {
static propTypes = { static propTypes = {
account: ImmutablePropTypes.map, account: ImmutablePropTypes.map,
@@ -91,5 +91,3 @@ class Header extends ImmutablePureComponent {
} }
} }
export default Header;

View File

@@ -19,7 +19,8 @@ const mapStateToProps = (state, props) => ({
me: state.getIn(['meta', 'me']), me: state.getIn(['meta', 'me']),
}); });
class AccountTimeline extends ImmutablePureComponent { @connect(mapStateToProps)
export default class AccountTimeline extends ImmutablePureComponent {
static propTypes = { static propTypes = {
params: PropTypes.object.isRequired, 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']), accountIds: state.getIn(['user_lists', 'blocks', 'items']),
}); });
class Blocks extends ImmutablePureComponent { @connect(mapStateToProps)
@injectIntl
export default class Blocks extends ImmutablePureComponent {
static propTypes = { static propTypes = {
params: PropTypes.object.isRequired, 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 PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; 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'; import SettingText from '../../../components/setting_text';
const messages = defineMessages({ const messages = defineMessages({
@@ -11,17 +9,17 @@ const messages = defineMessages({
settings: { id: 'home.settings', defaultMessage: 'Column settings' }, settings: { id: 'home.settings', defaultMessage: 'Column settings' },
}); });
class ColumnSettings extends React.PureComponent { @injectIntl
export default class ColumnSettings extends React.PureComponent {
static propTypes = { static propTypes = {
settings: ImmutablePropTypes.map.isRequired, settings: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
onSave: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
}; };
render () { render () {
const { settings, onChange, onSave, intl } = this.props; const { settings, onChange, intl } = this.props;
return ( return (
<div> <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 { connect } from 'react-redux';
import ColumnSettings from '../components/column_settings'; import ColumnSettings from '../components/column_settings';
import { changeSetting, saveSettings } from '../../../actions/settings'; import { changeSetting } from '../../../actions/settings';
const mapStateToProps = state => ({ const mapStateToProps = state => ({
settings: state.getIn(['settings', 'community']), settings: state.getIn(['settings', 'community']),
@@ -12,10 +12,6 @@ const mapDispatchToProps = dispatch => ({
dispatch(changeSetting(['community', ...key], checked)); dispatch(changeSetting(['community', ...key], checked));
}, },
onSave () {
dispatch(saveSettings());
},
}); });
export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings); export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings);

View File

@@ -1,146 +1,45 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import StatusListContainer from '../ui/containers/status_list_container';
import Column from '../../components/column';
import ColumnHeader from '../../components/column_header';
import { import {
refreshCommunityTimeline, refreshCommunityTimeline,
expandCommunityTimeline, expandCommunityTimeline,
updateTimeline,
deleteFromTimelines,
connectTimeline,
disconnectTimeline,
} from '../../actions/timelines'; } from '../../actions/timelines';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import ColumnSettingsContainer from './containers/column_settings_container'; import ColumnSettingsContainer from './containers/column_settings_container';
import createStream from '../../stream'; import Timeline from '../timeline';
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'column.community', defaultMessage: 'Local timeline' }, title: { id: 'column.community', defaultMessage: 'Local timeline' },
}); });
const mapStateToProps = state => ({ @injectIntl
hasUnread: state.getIn(['timelines', 'community', 'unread']) > 0, export default class CommunityTimeline extends React.PureComponent {
streamingAPIBaseURL: state.getIn(['meta', 'streaming_api_base_url']),
accessToken: state.getIn(['meta', 'access_token']),
});
class CommunityTimeline extends React.PureComponent {
static propTypes = { static propTypes = {
dispatch: PropTypes.func.isRequired,
columnId: PropTypes.string, columnId: PropTypes.string,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
streamingAPIBaseURL: PropTypes.string.isRequired,
accessToken: PropTypes.string.isRequired,
hasUnread: PropTypes.bool,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
}; };
handlePin = () => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(removeColumn(columnId));
} else {
dispatch(addColumn('COMMUNITY', {}));
}
}
handleMove = (dir) => {
const { columnId, dispatch } = this.props;
dispatch(moveColumn(columnId, dir));
}
handleHeaderClick = () => {
this.column.scrollTop();
}
componentDidMount () {
const { dispatch, streamingAPIBaseURL, accessToken } = this.props;
dispatch(refreshCommunityTimeline());
if (typeof this._subscription !== 'undefined') {
return;
}
this._subscription = createStream(streamingAPIBaseURL, accessToken, 'public:local', {
connected () {
dispatch(connectTimeline('community'));
},
reconnected () {
dispatch(connectTimeline('community'));
},
disconnected () {
dispatch(disconnectTimeline('community'));
},
received (data) {
switch(data.event) {
case 'update':
dispatch(updateTimeline('community', JSON.parse(data.payload)));
break;
case 'delete':
dispatch(deleteFromTimelines(data.payload));
break;
}
},
});
}
componentWillUnmount () {
if (typeof this._subscription !== 'undefined') {
this._subscription.close();
this._subscription = null;
}
}
setRef = c => {
this.column = c;
}
handleLoadMore = () => {
this.props.dispatch(expandCommunityTimeline());
}
render () { render () {
const { intl, hasUnread, columnId, multiColumn } = this.props; const { intl, columnId, multiColumn } = this.props;
const pinned = !!columnId;
return ( return (
<Column ref={this.setRef}> <Timeline
<ColumnHeader expand={expandCommunityTimeline}
icon='users' refresh={refreshCommunityTimeline}
active={hasUnread} streamId='public:local'
title={intl.formatMessage(messages.title)} columnName='COMMUNITY'
onPin={this.handlePin} columnId={columnId}
onMove={this.handleMove} mulitColumn={multiColumn}
onClick={this.handleHeaderClick} emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />}
pinned={pinned} icon='users'
multiColumn={multiColumn} title={intl.formatMessage(messages.title)}
> settings={<ColumnSettingsContainer />}
<ColumnSettingsContainer /> scrollName='community_timeline'
</ColumnHeader> timelineId='community'
/>
<StatusListContainer
trackScroll={!pinned}
scrollKey={`community_timeline-${columnId}`}
timelineId='community'
loadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />}
/>
</Column>
); );
} }
} }
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 ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
class AutosuggestAccount extends ImmutablePureComponent { export default class AutosuggestAccount extends ImmutablePureComponent {
static propTypes = { static propTypes = {
account: ImmutablePropTypes.map.isRequired, 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 PropTypes from 'prop-types';
import { length } from 'stringz'; import { length } from 'stringz';
class CharacterCounter extends React.PureComponent { export default class CharacterCounter extends React.PureComponent {
static propTypes = { static propTypes = {
text: PropTypes.string.isRequired, 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 AutosuggestTextarea from '../../../components/autosuggest_textarea';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import UploadButtonContainer from '../containers/upload_button_container'; import UploadButtonContainer from '../containers/upload_button_container';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
import Toggle from 'react-toggle';
import Collapsable from '../../../components/collapsable'; import Collapsable from '../../../components/collapsable';
import SpoilerButtonContainer from '../containers/spoiler_button_container'; import SpoilerButtonContainer from '../containers/spoiler_button_container';
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container'; import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
import SensitiveButtonContainer from '../containers/sensitive_button_container'; import SensitiveButtonContainer from '../containers/sensitive_button_container';
import EmojiPickerDropdown from './emoji_picker_dropdown'; import EmojiPickerDropdown from './emoji_picker_dropdown';
import UploadFormContainer from '../containers/upload_form_container'; import UploadFormContainer from '../containers/upload_form_container';
import TextIconButton from './text_icon_button';
import WarningContainer from '../containers/warning_container'; import WarningContainer from '../containers/warning_container';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { length } from 'stringz'; import { length } from 'stringz';
@@ -27,7 +25,8 @@ const messages = defineMessages({
publishLoud: { id: 'compose_form.publish_loud', defaultMessage: '{publish}!' }, publishLoud: { id: 'compose_form.publish_loud', defaultMessage: '{publish}!' },
}); });
class ComposeForm extends ImmutablePureComponent { @injectIntl
export default class ComposeForm extends ImmutablePureComponent {
static propTypes = { static propTypes = {
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
@@ -141,7 +140,6 @@ class ComposeForm extends ImmutablePureComponent {
const text = [this.props.spoiler_text, this.props.text].join(''); const text = [this.props.spoiler_text, this.props.text].join('');
let publishText = ''; let publishText = '';
let reply_to_other = false;
if (this.props.privacy === 'private' || this.props.privacy === 'direct') { if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
publishText = <span className='compose-form__publish-private'><i className='fa fa-lock' /> {intl.formatMessage(messages.publish)}</span>; publishText = <span className='compose-form__publish-private'><i className='fa fa-lock' /> {intl.formatMessage(messages.publish)}</span>;
@@ -202,5 +200,3 @@ class ComposeForm extends ImmutablePureComponent {
} }
} }
export default injectIntl(ComposeForm);

View File

@@ -24,7 +24,8 @@ const settings = {
let EmojiPicker; // load asynchronously let EmojiPicker; // load asynchronously
class EmojiPickerDropdown extends React.PureComponent { @injectIntl
export default class EmojiPickerDropdown extends React.PureComponent {
static propTypes = { static propTypes = {
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
@@ -52,7 +53,7 @@ class EmojiPickerDropdown extends React.PureComponent {
import(/* webpackChunkName: "emojione_picker" */ 'emojione-picker').then(TheEmojiPicker => { import(/* webpackChunkName: "emojione_picker" */ 'emojione-picker').then(TheEmojiPicker => {
EmojiPicker = TheEmojiPicker.default; EmojiPicker = TheEmojiPicker.default;
this.setState({ loading: false }); this.setState({ loading: false });
}).catch(err => { }).catch(() => {
// TODO: show the user an error? // TODO: show the user an error?
this.setState({ loading: false }); 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 React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import Avatar from '../../../components/avatar'; import Avatar from '../../../components/avatar';
import IconButton from '../../../components/icon_button';
import DisplayName from '../../../components/display_name';
import Permalink from '../../../components/permalink'; import Permalink from '../../../components/permalink';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import Link from 'react-router-dom/Link';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
class NavigationBar extends ImmutablePureComponent { export default class NavigationBar extends ImmutablePureComponent {
static propTypes = { static propTypes = {
account: ImmutablePropTypes.map.isRequired, 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', lineHeight: '27px',
}; };
class PrivacyDropdown extends React.PureComponent { @injectIntl
export default class PrivacyDropdown extends React.PureComponent {
static propTypes = { static propTypes = {
value: PropTypes.string.isRequired, value: PropTypes.string.isRequired,
@@ -64,7 +65,7 @@ class PrivacyDropdown extends React.PureComponent {
} }
render () { render () {
const { value, onChange, intl } = this.props; const { value, intl } = this.props;
const { open } = this.state; const { open } = this.state;
const options = [ 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' }, cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' },
}); });
class ReplyIndicator extends ImmutablePureComponent { @injectIntl
export default class ReplyIndicator extends ImmutablePureComponent {
static contextTypes = { static contextTypes = {
router: PropTypes.object, 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 React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
const messages = defineMessages({ const messages = defineMessages({
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' }, placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
}); });
class Search extends React.PureComponent { @injectIntl
export default class Search extends React.PureComponent {
static propTypes = { static propTypes = {
value: PropTypes.string.isRequired, 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 React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; 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 AccountContainer from '../../../containers/account_container';
import StatusContainer from '../../../containers/status_container'; import StatusContainer from '../../../containers/status_container';
import Link from 'react-router-dom/Link'; import Link from 'react-router-dom/Link';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
class SearchResults extends ImmutablePureComponent { export default class SearchResults extends ImmutablePureComponent {
static propTypes = { static propTypes = {
results: ImmutablePropTypes.map.isRequired, 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 React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
class TextIconButton extends React.PureComponent { export default class TextIconButton extends React.PureComponent {
static propTypes = { static propTypes = {
label: PropTypes.string.isRequired, 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 makeMapStateToProps = () => {
const mapStateToProps = (state, props) => ({ const mapStateToProps = state => ({
acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']), acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']),
}); });
@@ -23,7 +23,9 @@ const iconStyle = {
lineHeight: '27px', lineHeight: '27px',
}; };
class UploadButton extends ImmutablePureComponent { @connect(makeMapStateToProps)
@injectIntl
export default class UploadButton extends ImmutablePureComponent {
static propTypes = { static propTypes = {
disabled: PropTypes.bool, 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' }, undo: { id: 'upload_form.undo', defaultMessage: 'Undo' },
}); });
class UploadForm extends React.PureComponent { @injectIntl
export default class UploadForm extends React.PureComponent {
static propTypes = { static propTypes = {
media: ImmutablePropTypes.list.isRequired, 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 spring from 'react-motion/lib/spring';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
class UploadProgress extends React.PureComponent { export default class UploadProgress extends React.PureComponent {
static propTypes = { static propTypes = {
active: PropTypes.bool, 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 React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
class Warning extends React.PureComponent { export default class Warning extends React.PureComponent {
static propTypes = { static propTypes = {
message: PropTypes.node.isRequired, 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 { connect } from 'react-redux';
import NavigationBar from '../components/navigation_bar'; import NavigationBar from '../components/navigation_bar';
const mapStateToProps = (state, props) => { const mapStateToProps = state => {
return { return {
account: state.getIn(['accounts', state.getIn(['meta', 'me'])]), account: state.getIn(['accounts', state.getIn(['meta', 'me'])]),
}; };

View File

@@ -6,7 +6,7 @@ import ReplyIndicator from '../components/reply_indicator';
const makeMapStateToProps = () => { const makeMapStateToProps = () => {
const getStatus = makeGetStatus(); const getStatus = makeGetStatus();
const mapStateToProps = (state, props) => ({ const mapStateToProps = state => ({
status: getStatus(state, state.getIn(['compose', 'in_reply_to'])), 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 UploadForm from '../components/upload_form';
import { undoUploadCompose } from '../../../actions/compose'; import { undoUploadCompose } from '../../../actions/compose';
const mapStateToProps = (state, props) => ({ const mapStateToProps = state => ({
media: state.getIn(['compose', 'media_attachments']), media: state.getIn(['compose', 'media_attachments']),
}); });

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import LoadingIndicator from '../../components/loading_indicator'; import LoadingIndicator from '../../components/loading_indicator';
import { fetchFavouritedStatuses, expandFavouritedStatuses } from '../../actions/favourites'; import { fetchFavouritedStatuses, expandFavouritedStatuses } from '../../actions/favourites';
import Column from '../ui/components/column'; import Column from '../ui/components/column';
@@ -15,19 +14,17 @@ const messages = defineMessages({
}); });
const mapStateToProps = state => ({ const mapStateToProps = state => ({
statusIds: state.getIn(['status_lists', 'favourites', 'items']),
loaded: state.getIn(['status_lists', 'favourites', 'loaded']), loaded: state.getIn(['status_lists', 'favourites', 'loaded']),
me: state.getIn(['meta', 'me']),
}); });
class Favourites extends ImmutablePureComponent { @connect(mapStateToProps)
@injectIntl
export default class Favourites extends ImmutablePureComponent {
static propTypes = { static propTypes = {
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
statusIds: ImmutablePropTypes.list.isRequired,
loaded: PropTypes.bool, loaded: PropTypes.bool,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
me: PropTypes.number.isRequired,
}; };
componentWillMount () { componentWillMount () {
@@ -39,7 +36,7 @@ class Favourites extends ImmutablePureComponent {
} }
render () { render () {
const { statusIds, loaded, intl, me } = this.props; const { loaded, intl } = this.props;
if (!loaded) { if (!loaded) {
return ( return (
@@ -58,5 +55,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)]), 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 = { static propTypes = {
params: PropTypes.object.isRequired, 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' }, reject: { id: 'follow_request.reject', defaultMessage: 'Reject' },
}); });
class AccountAuthorize extends ImmutablePureComponent { @injectIntl
export default class AccountAuthorize extends ImmutablePureComponent {
static propTypes = { static propTypes = {
account: ImmutablePropTypes.map.isRequired, 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 }) => ({ const mapDispatchToProps = (dispatch, { id }) => ({
onAuthorize (account) { onAuthorize () {
dispatch(authorizeFollowRequest(id)); dispatch(authorizeFollowRequest(id));
}, },
onReject (account) { onReject () {
dispatch(rejectFollowRequest(id)); dispatch(rejectFollowRequest(id));
}, },
}); });

View File

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

View File

@@ -1,141 +1,54 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import StatusListContainer from '../ui/containers/status_list_container';
import Column from '../../components/column';
import ColumnHeader from '../../components/column_header';
import { import {
refreshHashtagTimeline, refreshHashtagTimeline,
expandHashtagTimeline, expandHashtagTimeline,
updateTimeline,
deleteFromTimelines,
} from '../../actions/timelines'; } from '../../actions/timelines';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import createStream from '../../stream'; import Timeline from '../timeline';
const mapStateToProps = state => ({ export default class HashtagTimeline extends React.PureComponent {
hasUnread: state.getIn(['timelines', 'tag', 'unread']) > 0,
streamingAPIBaseURL: state.getIn(['meta', 'streaming_api_base_url']),
accessToken: state.getIn(['meta', 'access_token']),
});
class HashtagTimeline extends React.PureComponent {
static propTypes = { static propTypes = {
params: PropTypes.object.isRequired, params: PropTypes.object.isRequired,
columnId: PropTypes.string, columnId: PropTypes.string,
dispatch: PropTypes.func.isRequired,
streamingAPIBaseURL: PropTypes.string.isRequired,
accessToken: PropTypes.string.isRequired,
hasUnread: PropTypes.bool,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
}; };
handlePin = () => { componentWillMount () {
const { columnId, dispatch } = this.props; const id = this.props.params.id;
this.expand = () => expandHashtagTimeline(id);
if (columnId) { this.refresh = () => refreshHashtagTimeline(id);
dispatch(removeColumn(columnId));
} else {
dispatch(addColumn('HASHTAG', { id: this.props.params.id }));
}
}
handleMove = (dir) => {
const { columnId, dispatch } = this.props;
dispatch(moveColumn(columnId, dir));
}
handleHeaderClick = () => {
this.column.scrollTop();
}
_subscribe (dispatch, id) {
const { streamingAPIBaseURL, accessToken } = this.props;
this.subscription = createStream(streamingAPIBaseURL, accessToken, `hashtag&tag=${id}`, {
received (data) {
switch(data.event) {
case 'update':
dispatch(updateTimeline(`hashtag:${id}`, JSON.parse(data.payload)));
break;
case 'delete':
dispatch(deleteFromTimelines(data.payload));
break;
}
},
});
}
_unsubscribe () {
if (typeof this.subscription !== 'undefined') {
this.subscription.close();
this.subscription = null;
}
}
componentDidMount () {
const { dispatch } = this.props;
const { id } = this.props.params;
dispatch(refreshHashtagTimeline(id));
this._subscribe(dispatch, id);
} }
componentWillReceiveProps (nextProps) { componentWillReceiveProps (nextProps) {
if (nextProps.params.id !== this.props.params.id) { if (nextProps.params.id !== this.props.params.id) {
this.props.dispatch(refreshHashtagTimeline(nextProps.params.id)); const id = nextProps.params.id;
this._unsubscribe(); this.expand = () => expandHashtagTimeline(id);
this._subscribe(this.props.dispatch, nextProps.params.id); this.refresh = () => refreshHashtagTimeline(id);
} }
} }
componentWillUnmount () {
this._unsubscribe();
}
setRef = c => {
this.column = c;
}
handleLoadMore = () => {
this.props.dispatch(expandHashtagTimeline(this.props.params.id));
}
render () { render () {
const { hasUnread, columnId, multiColumn } = this.props; const { columnId, multiColumn } = this.props;
const { id } = this.props.params; const { id } = this.props.params;
const pinned = !!columnId;
return ( return (
<Column ref={this.setRef}> <Timeline
<ColumnHeader expand={this.expand}
icon='hashtag' refresh={this.refresh}
active={hasUnread} streamId={`hashtag&tag=${id}`}
title={id} columnName='HASHTAG'
onPin={this.handlePin} columnProps={{ id }}
onMove={this.handleMove} columnId={columnId}
onClick={this.handleHeaderClick} mulitColumn={multiColumn}
pinned={pinned} emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />}
multiColumn={multiColumn} icon='hashtag'
showBackButton title={id}
/> scrollName='hashtag_timeline'
timelineId={`hashtag:${id}`}
<StatusListContainer />
trackScroll={!pinned}
scrollKey={`hashtag_timeline-${columnId}`}
timelineId={`hashtag:${id}`}
loadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />}
/>
</Column>
); );
} }
} }
export default connect(mapStateToProps)(HashtagTimeline);

View File

@@ -2,7 +2,6 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ColumnCollapsable from '../../../components/column_collapsable';
import SettingToggle from '../../notifications/components/setting_toggle'; import SettingToggle from '../../notifications/components/setting_toggle';
import SettingText from '../../../components/setting_text'; import SettingText from '../../../components/setting_text';
@@ -11,39 +10,37 @@ const messages = defineMessages({
settings: { id: 'home.settings', defaultMessage: 'Column settings' }, settings: { id: 'home.settings', defaultMessage: 'Column settings' },
}); });
class ColumnSettings extends React.PureComponent { @injectIntl
export default class ColumnSettings extends React.PureComponent {
static propTypes = { static propTypes = {
settings: ImmutablePropTypes.map.isRequired, settings: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
onSave: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
}; };
render () { render () {
const { settings, onChange, onSave, intl } = this.props; const { settings, onChange, intl } = this.props;
return ( return (
<div> <div>
<span className='column-settings__section'><FormattedMessage id='home.column_settings.basic' defaultMessage='Basic' /></span> <span className='column-settings__section'><FormattedMessage id='home.column_settings.basic' defaultMessage='Basic' /></span>
<div className='column-settings__row'> <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>
<div className='column-settings__row'> <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> </div>
<span className='column-settings__section'><FormattedMessage id='home.column_settings.advanced' defaultMessage='Advanced' /></span> <span className='column-settings__section'><FormattedMessage id='home.column_settings.advanced' defaultMessage='Advanced' /></span>
<div className='column-settings__row'> <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>
</div> </div>
); );
} }
} }
export default injectIntl(ColumnSettings);

View File

@@ -2,12 +2,9 @@ import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { expandHomeTimeline } from '../../actions/timelines'; import { expandHomeTimeline } from '../../actions/timelines';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import StatusListContainer from '../ui/containers/status_list_container';
import Column from '../../components/column';
import ColumnHeader from '../../components/column_header';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ColumnSettingsContainer from './containers/column_settings_container'; import ColumnSettingsContainer from './containers/column_settings_container';
import Timeline from '../timeline';
import Link from 'react-router-dom/Link'; import Link from 'react-router-dom/Link';
const messages = defineMessages({ const messages = defineMessages({
@@ -15,51 +12,22 @@ const messages = defineMessages({
}); });
const mapStateToProps = state => ({ const mapStateToProps = state => ({
hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0,
hasFollows: state.getIn(['accounts_counters', state.getIn(['meta', 'me']), 'following_count']) > 0, 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 = { static propTypes = {
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
hasUnread: PropTypes.bool,
hasFollows: PropTypes.bool, hasFollows: PropTypes.bool,
columnId: PropTypes.string, columnId: PropTypes.string,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
}; };
handlePin = () => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(removeColumn(columnId));
} else {
dispatch(addColumn('HOME', {}));
}
}
handleMove = (dir) => {
const { columnId, dispatch } = this.props;
dispatch(moveColumn(columnId, dir));
}
handleHeaderClick = () => {
this.column.scrollTop();
}
setRef = c => {
this.column = c;
}
handleLoadMore = () => {
this.props.dispatch(expandHomeTimeline());
}
render () { render () {
const { intl, hasUnread, hasFollows, columnId, multiColumn } = this.props; const { intl, hasFollows, columnId, multiColumn } = this.props;
const pinned = !!columnId;
let emptyMessage; let emptyMessage;
@@ -70,31 +38,19 @@ class HomeTimeline extends React.PureComponent {
} }
return ( return (
<Column ref={this.setRef}> <Timeline
<ColumnHeader expand={expandHomeTimeline}
icon='home' columnName='HOME'
active={hasUnread} columnId={columnId}
title={intl.formatMessage(messages.title)} mulitColumn={multiColumn}
onPin={this.handlePin} emptyMessage={emptyMessage}
onMove={this.handleMove} icon='home'
onClick={this.handleHeaderClick} title={intl.formatMessage(messages.title)}
pinned={pinned} settings={<ColumnSettingsContainer />}
multiColumn={multiColumn} scrollName='home_timeline'
> timelineId='home'
<ColumnSettingsContainer /> />
</ColumnHeader>
<StatusListContainer
trackScroll={!pinned}
scrollKey={`home_timeline-${columnId}`}
loadMore={this.handleLoadMore}
timelineId='home'
emptyMessage={emptyMessage}
/>
</Column>
); );
} }
} }
export default connect(mapStateToProps)(injectIntl(HomeTimeline));

View File

@@ -19,7 +19,16 @@ const mapStateToProps = state => ({
accountIds: state.getIn(['user_lists', 'mutes', 'items']), 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 () { componentWillMount () {
this.props.dispatch(fetchMutes()); 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 PropTypes from 'prop-types';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
class ClearColumnButton extends React.Component { export default class ClearColumnButton extends React.Component {
static propTypes = { static propTypes = {
onClick: PropTypes.func.isRequired, 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 PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import ColumnCollapsable from '../../../components/column_collapsable';
import ClearColumnButton from './clear_column_button'; import ClearColumnButton from './clear_column_button';
import SettingToggle from './setting_toggle'; import SettingToggle from './setting_toggle';
class ColumnSettings extends React.PureComponent { export default class ColumnSettings extends React.PureComponent {
static propTypes = { static propTypes = {
settings: ImmutablePropTypes.map.isRequired, settings: ImmutablePropTypes.map.isRequired,
@@ -16,7 +15,7 @@ class ColumnSettings extends React.PureComponent {
}; };
render () { 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 alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />;
const showStr = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />; 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> <span className='column-settings__section'><FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' /></span>
<div className='column-settings__row'> <div className='column-settings__row'>
<SettingToggle settings={settings} settingKey={['alerts', 'follow']} onChange={onChange} label={alertStr} /> <SettingToggle prefix='notifications' settings={settings} settingKey={['alerts', 'follow']} onChange={onChange} label={alertStr} />
<SettingToggle settings={settings} settingKey={['shows', 'follow']} onChange={onChange} label={showStr} /> <SettingToggle prefix='notifications' 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={['sounds', 'follow']} onChange={onChange} label={soundStr} />
</div> </div>
<span className='column-settings__section'><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favourites:' /></span> <span className='column-settings__section'><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favourites:' /></span>
<div className='column-settings__row'> <div className='column-settings__row'>
<SettingToggle settings={settings} settingKey={['alerts', 'favourite']} onChange={onChange} label={alertStr} /> <SettingToggle prefix='notifications' settings={settings} settingKey={['alerts', 'favourite']} onChange={onChange} label={alertStr} />
<SettingToggle settings={settings} settingKey={['shows', 'favourite']} onChange={onChange} label={showStr} /> <SettingToggle prefix='notifications' 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={['sounds', 'favourite']} onChange={onChange} label={soundStr} />
</div> </div>
<span className='column-settings__section'><FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' /></span> <span className='column-settings__section'><FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' /></span>
<div className='column-settings__row'> <div className='column-settings__row'>
<SettingToggle settings={settings} settingKey={['alerts', 'mention']} onChange={onChange} label={alertStr} /> <SettingToggle prefix='notifications' settings={settings} settingKey={['alerts', 'mention']} onChange={onChange} label={alertStr} />
<SettingToggle settings={settings} settingKey={['shows', 'mention']} onChange={onChange} label={showStr} /> <SettingToggle prefix='notifications' 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={['sounds', 'mention']} onChange={onChange} label={soundStr} />
</div> </div>
<span className='column-settings__section'><FormattedMessage id='notifications.column_settings.reblog' defaultMessage='Boosts:' /></span> <span className='column-settings__section'><FormattedMessage id='notifications.column_settings.reblog' defaultMessage='Boosts:' /></span>
<div className='column-settings__row'> <div className='column-settings__row'>
<SettingToggle settings={settings} settingKey={['alerts', 'reblog']} onChange={onChange} label={alertStr} /> <SettingToggle prefix='notifications' settings={settings} settingKey={['alerts', 'reblog']} onChange={onChange} label={alertStr} />
<SettingToggle settings={settings} settingKey={['shows', 'reblog']} onChange={onChange} label={showStr} /> <SettingToggle prefix='notifications' 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={['sounds', 'reblog']} onChange={onChange} label={soundStr} />
</div> </div>
</div> </div>
); );
} }
} }
export default ColumnSettings;

View File

@@ -2,14 +2,13 @@ import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import StatusContainer from '../../../containers/status_container'; import StatusContainer from '../../../containers/status_container';
import AccountContainer from '../../../containers/account_container'; import AccountContainer from '../../../containers/account_container';
import Avatar from '../../../components/avatar';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import Permalink from '../../../components/permalink'; import Permalink from '../../../components/permalink';
import emojify from '../../../emoji'; import emojify from '../../../emoji';
import escapeTextContentForBrowser from 'escape-html'; import escapeTextContentForBrowser from 'escape-html';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
class Notification extends ImmutablePureComponent { export default class Notification extends ImmutablePureComponent {
static propTypes = { static propTypes = {
notification: ImmutablePropTypes.map.isRequired, 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 ImmutablePropTypes from 'react-immutable-proptypes';
import Toggle from 'react-toggle'; import Toggle from 'react-toggle';
class SettingToggle extends React.PureComponent { export default class SettingToggle extends React.PureComponent {
static propTypes = { static propTypes = {
prefix: PropTypes.string,
settings: ImmutablePropTypes.map.isRequired, settings: ImmutablePropTypes.map.isRequired,
settingKey: PropTypes.array.isRequired, settingKey: PropTypes.array.isRequired,
label: PropTypes.node.isRequired, label: PropTypes.node.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
} }
onChange = (e) => { onChange = ({ target }) => {
this.props.onChange(this.props.settingKey, e.target.checked); this.props.onChange(this.props.settingKey, target.checked);
} }
render () { render () {
const { settings, settingKey, label, onChange } = this.props; const { prefix, settings, settingKey, label } = this.props;
const id = `setting-toggle-${settingKey.join('-')}`; const id = ['setting-toggle', prefix, ...settingKey].filter(Boolean).join('-');
return ( return (
<div className='setting-toggle'> <div className='setting-toggle'>
@@ -29,5 +30,3 @@ class SettingToggle extends React.PureComponent {
} }
} }
export default SettingToggle;

View File

@@ -30,7 +30,9 @@ const mapStateToProps = state => ({
hasMore: !!state.getIn(['notifications', 'next']), hasMore: !!state.getIn(['notifications', 'next']),
}); });
class Notifications extends React.PureComponent { @connect(mapStateToProps)
@injectIntl
export default class Notifications extends React.PureComponent {
static propTypes = { static propTypes = {
columnId: PropTypes.string, columnId: PropTypes.string,
@@ -173,5 +175,3 @@ class Notifications extends React.PureComponent {
} }
} }
export default connect(mapStateToProps)(injectIntl(Notifications));

View File

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

View File

@@ -1,146 +1,45 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import StatusListContainer from '../ui/containers/status_list_container';
import Column from '../../components/column';
import ColumnHeader from '../../components/column_header';
import { import {
refreshPublicTimeline, refreshPublicTimeline,
expandPublicTimeline, expandPublicTimeline,
updateTimeline,
deleteFromTimelines,
connectTimeline,
disconnectTimeline,
} from '../../actions/timelines'; } from '../../actions/timelines';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import ColumnSettingsContainer from './containers/column_settings_container'; import ColumnSettingsContainer from './containers/column_settings_container';
import createStream from '../../stream'; import Timeline from '../timeline';
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'column.public', defaultMessage: 'Federated timeline' }, title: { id: 'column.public', defaultMessage: 'Federated timeline' },
}); });
const mapStateToProps = state => ({ @injectIntl
hasUnread: state.getIn(['timelines', 'public', 'unread']) > 0, export default class PublicTimeline extends React.PureComponent {
streamingAPIBaseURL: state.getIn(['meta', 'streaming_api_base_url']),
accessToken: state.getIn(['meta', 'access_token']),
});
class PublicTimeline extends React.PureComponent {
static propTypes = { static propTypes = {
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
columnId: PropTypes.string, columnId: PropTypes.string,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
streamingAPIBaseURL: PropTypes.string.isRequired,
accessToken: PropTypes.string.isRequired,
hasUnread: PropTypes.bool,
}; };
handlePin = () => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(removeColumn(columnId));
} else {
dispatch(addColumn('PUBLIC', {}));
}
}
handleMove = (dir) => {
const { columnId, dispatch } = this.props;
dispatch(moveColumn(columnId, dir));
}
handleHeaderClick = () => {
this.column.scrollTop();
}
componentDidMount () {
const { dispatch, streamingAPIBaseURL, accessToken } = this.props;
dispatch(refreshPublicTimeline());
if (typeof this._subscription !== 'undefined') {
return;
}
this._subscription = createStream(streamingAPIBaseURL, accessToken, 'public', {
connected () {
dispatch(connectTimeline('public'));
},
reconnected () {
dispatch(connectTimeline('public'));
},
disconnected () {
dispatch(disconnectTimeline('public'));
},
received (data) {
switch(data.event) {
case 'update':
dispatch(updateTimeline('public', JSON.parse(data.payload)));
break;
case 'delete':
dispatch(deleteFromTimelines(data.payload));
break;
}
},
});
}
componentWillUnmount () {
if (typeof this._subscription !== 'undefined') {
this._subscription.close();
this._subscription = null;
}
}
setRef = c => {
this.column = c;
}
handleLoadMore = () => {
this.props.dispatch(expandPublicTimeline());
}
render () { render () {
const { intl, columnId, hasUnread, multiColumn } = this.props; const { intl, columnId, multiColumn } = this.props;
const pinned = !!columnId;
return ( return (
<Column ref={this.setRef}> <Timeline
<ColumnHeader expand={expandPublicTimeline}
icon='globe' refresh={refreshPublicTimeline}
active={hasUnread} streamId='public'
title={intl.formatMessage(messages.title)} columnName='PUBLIC'
onPin={this.handlePin} columnId={columnId}
onMove={this.handleMove} mulitColumn={multiColumn}
onClick={this.handleHeaderClick} emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other instances to fill it up' />}
pinned={pinned} icon='globe'
multiColumn={multiColumn} title={intl.formatMessage(messages.title)}
> settings={<ColumnSettingsContainer />}
<ColumnSettingsContainer /> scrollName='public_timeline'
</ColumnHeader> timelineId='public'
/>
<StatusListContainer
timelineId='public'
loadMore={this.handleLoadMore}
trackScroll={!pinned}
scrollKey={`public_timeline-${columnId}`}
emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other instances to fill it up' />}
/>
</Column>
); );
} }
} }
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)]), 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 = { static propTypes = {
params: PropTypes.object.isRequired, 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 emojify from '../../../emoji';
import Toggle from 'react-toggle'; import Toggle from 'react-toggle';
class StatusCheckBox extends React.PureComponent { export default class StatusCheckBox extends React.PureComponent {
static propTypes = { static propTypes = {
status: ImmutablePropTypes.map.isRequired, status: ImmutablePropTypes.map.isRequired,
@@ -36,5 +36,3 @@ class StatusCheckBox extends React.PureComponent {
} }
} }
export default StatusCheckBox;

View File

@@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { cancelReport, changeReportComment, submitReport } from '../../actions/reports'; import { changeReportComment, submitReport } from '../../actions/reports';
import { refreshAccountTimeline } from '../../actions/timelines'; import { refreshAccountTimeline } from '../../actions/timelines';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
@@ -35,7 +35,9 @@ const makeMapStateToProps = () => {
return mapStateToProps; return mapStateToProps;
}; };
class Report extends React.PureComponent { @connect(makeMapStateToProps)
@injectIntl
export default class Report extends React.PureComponent {
static contextTypes = { static contextTypes = {
router: PropTypes.object, router: PropTypes.object,
@@ -121,5 +123,3 @@ class Report extends React.PureComponent {
} }
} }
export default connect(makeMapStateToProps)(injectIntl(Report));

View File

@@ -15,7 +15,8 @@ const messages = defineMessages({
report: { id: 'status.report', defaultMessage: 'Report @{name}' }, report: { id: 'status.report', defaultMessage: 'Report @{name}' },
}); });
class ActionBar extends React.PureComponent { @injectIntl
export default class ActionBar extends React.PureComponent {
static contextTypes = { static contextTypes = {
router: PropTypes.object, router: PropTypes.object,
@@ -91,5 +92,3 @@ class ActionBar extends React.PureComponent {
} }
} }
export default injectIntl(ActionBar);

View File

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

View File

@@ -0,0 +1,179 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import StatusListContainer from '../ui/containers/status_list_container';
import Column from '../../components/column';
import ColumnHeader from '../../components/column_header';
import {
updateTimeline,
deleteFromTimelines,
connectTimeline,
disconnectTimeline,
} from '../../actions/timelines';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import createStream from '../../stream';
const mapStateToProps = (state, ownprops) => ({
streamingAPIBaseURL: state.getIn(['meta', 'streaming_api_base_url']),
accessToken: state.getIn(['meta', 'access_token']),
hasUnread: state.getIn(['timelines', ownprops.timelineId, 'unread']) > 0,
});
@connect(mapStateToProps)
export default class Timeline extends React.PureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
streamingAPIBaseURL: PropTypes.string.isRequired,
accessToken: PropTypes.string.isRequired,
expand: PropTypes.func.isRequired,
refresh: PropTypes.func,
streamId: PropTypes.string,
hasUnread: PropTypes.bool,
columnName: PropTypes.string.isRequired,
columnProps: PropTypes.object,
columnId: PropTypes.string,
multiColumn: PropTypes.bool,
emptyMessage: PropTypes.oneOfType([
PropTypes.element,
PropTypes.string,
]),
icon: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
settings: PropTypes.element,
scrollName: PropTypes.string.isRequired,
timelineId: PropTypes.string.isRequired,
};
handlePin = () => {
const { columnName, columnProps, columnId, dispatch } = this.props;
if (columnId) {
dispatch(removeColumn(columnId));
} else {
dispatch(addColumn(columnName, columnProps || {}));
}
}
handleMove = (dir) => {
const { columnId, dispatch } = this.props;
dispatch(moveColumn(columnId, dir));
}
handleHeaderClick = () => {
this.column.scrollTop();
}
setRef = c => {
this.column = c;
}
handleLoadMore = () => {
this.props.dispatch(this.props.expand());
}
_subscribe (dispatch, streamId, timelineId) {
const { streamingAPIBaseURL, accessToken } = this.props;
if (!streamId || !timelineId) return;
this.subscription = createStream(streamingAPIBaseURL, accessToken, streamId, {
connected () {
dispatch(connectTimeline(timelineId));
},
reconnected () {
dispatch(connectTimeline(timelineId));
},
disconnected () {
dispatch(disconnectTimeline(timelineId));
},
received (data) {
switch(data.event) {
case 'update':
dispatch(updateTimeline(timelineId, JSON.parse(data.payload)));
break;
case 'delete':
dispatch(deleteFromTimelines(data.payload));
break;
}
},
});
}
_unsubscribe () {
if (typeof this.subscription !== 'undefined') {
this.subscription.close();
this.subscription = null;
}
}
componentDidMount () {
const { dispatch, refresh, streamId, timelineId } = this.props;
if (typeof refresh !== 'function') return;
dispatch(refresh());
this._subscribe(dispatch, streamId, timelineId);
}
componentWillReceiveProps (nextProps) {
if (nextProps.streamId !== this.props.streamId || nextProps.timelineId !== this.props.timelineId) {
if (typeof refresh !== 'function') return;
this.props.dispatch(this.props.refresh());
this._unsubscribe();
this._subscribe(this.props.dispatch, nextProps.streamId, nextProps.timelineId);
}
}
componentWillUnmount () {
this._unsubscribe();
}
render () {
const {
hasUnread,
columnId,
multiColumn,
emptyMessage,
icon,
title,
settings,
scrollName,
timelineId,
} = this.props;
const pinned = !!columnId;
return (
<Column ref={this.setRef}>
<ColumnHeader
icon={icon}
active={hasUnread}
title={title}
onPin={this.handlePin}
onMove={this.handleMove}
onClick={this.handleHeaderClick}
pinned={pinned}
multiColumn={multiColumn}
>
{settings}
</ColumnHeader>
<StatusListContainer
trackScroll={!pinned}
scrollKey={`${scrollName}-${columnId}`}
loadMore={this.handleLoadMore}
timelineId={timelineId}
emptyMessage={emptyMessage}
/>
</Column>
);
}
}

View File

@@ -2,7 +2,6 @@ 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 { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import IconButton from '../../../components/icon_button';
import Button from '../../../components/button'; import Button from '../../../components/button';
import StatusContent from '../../../components/status_content'; import StatusContent from '../../../components/status_content';
import Avatar from '../../../components/avatar'; import Avatar from '../../../components/avatar';
@@ -14,7 +13,8 @@ const messages = defineMessages({
reblog: { id: 'status.reblog', defaultMessage: 'Boost' }, reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
}); });
class BoostModal extends ImmutablePureComponent { @injectIntl
export default class BoostModal extends ImmutablePureComponent {
static contextTypes = { static contextTypes = {
router: PropTypes.object, router: PropTypes.object,
@@ -49,7 +49,7 @@ class BoostModal extends ImmutablePureComponent {
} }
render () { render () {
const { status, intl, onClose } = this.props; const { status, intl } = this.props;
return ( return (
<div className='modal-root__modal boost-modal'> <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 { debounce } from 'lodash';
import scrollTop from '../../../scroll'; import scrollTop from '../../../scroll';
class Column extends React.PureComponent { export default class Column extends React.PureComponent {
static propTypes = { static propTypes = {
heading: PropTypes.string, heading: PropTypes.string,
@@ -59,5 +59,3 @@ class Column extends React.PureComponent {
} }
} }
export default Column;

View File

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

View File

@@ -2,12 +2,14 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import ReactSwipeable from 'react-swipeable';
import HomeTimeline from '../../home_timeline'; import HomeTimeline from '../../home_timeline';
import Notifications from '../../notifications'; import Notifications from '../../notifications';
import PublicTimeline from '../../public_timeline'; import PublicTimeline from '../../public_timeline';
import CommunityTimeline from '../../community_timeline'; import CommunityTimeline from '../../community_timeline';
import HashtagTimeline from '../../hashtag_timeline'; import HashtagTimeline from '../../hashtag_timeline';
import Compose from '../../compose'; import Compose from '../../compose';
import { getPreviousLink, getNextLink } from './tabs_bar';
const componentMap = { const componentMap = {
'COMPOSE': Compose, 'COMPOSE': Compose,
@@ -18,7 +20,11 @@ const componentMap = {
'HASHTAG': HashtagTimeline, 'HASHTAG': HashtagTimeline,
}; };
class ColumnsArea extends ImmutablePureComponent { export default class ColumnsArea extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object.isRequired,
};
static propTypes = { static propTypes = {
columns: ImmutablePropTypes.list.isRequired, columns: ImmutablePropTypes.list.isRequired,
@@ -26,14 +32,30 @@ class ColumnsArea extends ImmutablePureComponent {
children: PropTypes.node, children: PropTypes.node,
}; };
handleRightSwipe = () => {
const previousLink = getPreviousLink(this.context.router.history.location.pathname);
if (previousLink) {
this.context.router.history.push(previousLink);
}
}
handleLeftSwipe = () => {
const previousLink = getNextLink(this.context.router.history.location.pathname);
if (previousLink) {
this.context.router.history.push(previousLink);
}
};
render () { render () {
const { columns, children, singleColumn } = this.props; const { columns, children, singleColumn } = this.props;
if (singleColumn) { if (singleColumn) {
return ( return (
<div className='columns-area'> <ReactSwipeable onSwipedLeft={this.handleLeftSwipe} onSwipedRight={this.handleRightSwipe} className='columns-area'>
{children} {children}
</div> </ReactSwipeable>
); );
} }
@@ -51,5 +73,3 @@ class ColumnsArea extends ImmutablePureComponent {
} }
} }
export default ColumnsArea;

View File

@@ -1,9 +1,10 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { injectIntl, FormattedMessage } from 'react-intl';
import Button from '../../../components/button'; import Button from '../../../components/button';
class ConfirmationModal extends React.PureComponent { @injectIntl
export default class ConfirmationModal extends React.PureComponent {
static propTypes = { static propTypes = {
message: PropTypes.node.isRequired, message: PropTypes.node.isRequired,
@@ -50,5 +51,3 @@ class ConfirmationModal extends React.PureComponent {
} }
} }
export default injectIntl(ConfirmationModal);

View File

@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
class ImageLoader extends React.PureComponent { export default class ImageLoader extends React.PureComponent {
static propTypes = { static propTypes = {
alt: PropTypes.string, alt: PropTypes.string,
@@ -41,7 +41,7 @@ class ImageLoader extends React.PureComponent {
render() { render() {
const { alt, src, previewSrc, width, height } = this.props; const { alt, src, previewSrc, width, height } = this.props;
const { loading, error } = this.state; const { loading } = this.state;
return ( return (
<div className='image-loader'> <div className='image-loader'>
@@ -65,5 +65,3 @@ class ImageLoader extends React.PureComponent {
} }
} }
export default ImageLoader;

View File

@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import LoadingIndicator from '../../../components/loading_indicator'; import ReactSwipeable from 'react-swipeable';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ExtendedVideoPlayer from '../../../components/extended_video_player'; import ExtendedVideoPlayer from '../../../components/extended_video_player';
@@ -12,7 +12,8 @@ const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' }, close: { id: 'lightbox.close', defaultMessage: 'Close' },
}); });
class MediaModal extends ImmutablePureComponent { @injectIntl
export default class MediaModal extends ImmutablePureComponent {
static propTypes = { static propTypes = {
media: ImmutablePropTypes.list.isRequired, media: ImmutablePropTypes.list.isRequired,
@@ -84,7 +85,9 @@ class MediaModal extends ImmutablePureComponent {
<div className='media-modal__content'> <div className='media-modal__content'>
<IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} /> <IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={16} />
{content} <ReactSwipeable onSwipedRight={this.handlePrevClick} onSwipedLeft={this.handleNextClick}>
{content}
</ReactSwipeable>
</div> </div>
{rightNav} {rightNav}
@@ -93,5 +96,3 @@ class MediaModal extends ImmutablePureComponent {
} }
} }
export default injectIntl(MediaModal);

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