logo

mastofe

My custom branche(s) on git.pleroma.social/pleroma/mastofe
commit: 1948f9e767c5c8f7cb52337ce777a61b5ad1a599
parent: 27ea2a88c12835b491ea5537f934983470faf781
Author: Yamagishi Kazutoshi <ykzts@desire.sh>
Date:   Sat, 22 Apr 2017 03:05:35 +0900

Remove deprecated features at React v15.5 (#1905)

* Remove deprecated features at React v15.5

- [x] React.PropTypes
- [x] react-addons-pure-render-mixin
- [x] react-addons-test-utils

* Uncommented out & Add browserify_rails options

* re-add react-addons-shallow

* Fix syntax error from resolve conflicts

* follow up 59a77923b368d48c590cd9f4a0c6b73ce972d33f

Diffstat:

Mapp/assets/javascripts/components/components/account.jsx37+++++++++++++++++++++----------------
Mapp/assets/javascripts/components/components/attachment_list.jsx14++++++--------
Mapp/assets/javascripts/components/components/autosuggest_textarea.jsx62+++++++++++++++++++++++++++++++++++---------------------------
Mapp/assets/javascripts/components/components/avatar.jsx47+++++++++++++++++++++++------------------------
Mapp/assets/javascripts/components/components/button.jsx47++++++++++++++++++++++++-----------------------
Mapp/assets/javascripts/components/components/collapsable.jsx7++++---
Mapp/assets/javascripts/components/components/column_back_button.jsx21++++++++++++---------
Mapp/assets/javascripts/components/components/column_back_button_slim.jsx21++++++++++++---------
Mapp/assets/javascripts/components/components/column_collapsable.jsx33+++++++++++++++++----------------
Mapp/assets/javascripts/components/components/display_name.jsx15++++++---------
Mapp/assets/javascripts/components/components/dropdown_menu.jsx37+++++++++++++++++++------------------
Mapp/assets/javascripts/components/components/extended_video_player.jsx36++++++++++++++++++++----------------
Mapp/assets/javascripts/components/components/icon_button.jsx59++++++++++++++++++++++++++++++-----------------------------
Mapp/assets/javascripts/components/components/load_more.jsx3++-
Mapp/assets/javascripts/components/components/media_gallery.jsx68++++++++++++++++++++++++++++++++++++--------------------------------
Mapp/assets/javascripts/components/components/permalink.jsx29++++++++++++++++++-----------
Mapp/assets/javascripts/components/components/relative_timestamp.jsx5+++--
Mapp/assets/javascripts/components/components/status.jsx58+++++++++++++++++++++++++++++++---------------------------
Mapp/assets/javascripts/components/components/status_action_bar.jsx75+++++++++++++++++++++++++++++++++++++++++++--------------------------------
Mapp/assets/javascripts/components/components/status_content.jsx52++++++++++++++++++++++++++++------------------------
Mapp/assets/javascripts/components/components/status_list.jsx67+++++++++++++++++++++++++++++++++++--------------------------------
Mapp/assets/javascripts/components/components/video_player.jsx76+++++++++++++++++++++++++++++++++++++++++-----------------------------------
Mapp/assets/javascripts/components/containers/mastodon.jsx17+++++++++--------
Mapp/assets/javascripts/components/features/account/components/action_bar.jsx30++++++++++++++----------------
Mapp/assets/javascripts/components/features/account/components/header.jsx53+++++++++++++++++++++++++++--------------------------
Mapp/assets/javascripts/components/features/account_timeline/components/header.jsx52++++++++++++++++++++++++++++++----------------------
Mapp/assets/javascripts/components/features/account_timeline/index.jsx35+++++++++++++++++++----------------
Mapp/assets/javascripts/components/features/blocks/index.jsx28++++++++++++++++------------
Mapp/assets/javascripts/components/features/community_timeline/index.jsx30++++++++++++++----------------
Mapp/assets/javascripts/components/features/compose/components/character_counter.jsx20+++++++++-----------
Mapp/assets/javascripts/components/features/compose/components/compose_form.jsx93++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Mapp/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx26+++++++++++++++-----------
Mapp/assets/javascripts/components/features/compose/components/navigation_bar.jsx14++++++--------
Mapp/assets/javascripts/components/features/compose/components/privacy_dropdown.jsx43+++++++++++++++++++++++--------------------
Mapp/assets/javascripts/components/features/compose/components/reply_indicator.jsx36++++++++++++++++++++----------------
Mapp/assets/javascripts/components/features/compose/components/search.jsx43++++++++++++++++++++++++-------------------
Mapp/assets/javascripts/components/features/compose/components/search_results.jsx15++++++---------
Mapp/assets/javascripts/components/features/compose/components/text_icon_button.jsx27+++++++++++++++------------
Mapp/assets/javascripts/components/features/compose/components/upload_button.jsx35++++++++++++++++++++---------------
Mapp/assets/javascripts/components/features/compose/components/upload_form.jsx20+++++++++-----------
Mapp/assets/javascripts/components/features/compose/components/upload_progress.jsx18++++++++----------
Mapp/assets/javascripts/components/features/compose/containers/sensitive_button_container.jsx19++++++++++---------
Mapp/assets/javascripts/components/features/compose/index.jsx26++++++++++++--------------
Mapp/assets/javascripts/components/features/favourited_statuses/index.jsx33++++++++++++++++++---------------
Mapp/assets/javascripts/components/features/favourites/index.jsx24+++++++++++-------------
Mapp/assets/javascripts/components/features/follow_requests/components/account_authorize.jsx7++++---
Mapp/assets/javascripts/components/features/follow_requests/index.jsx28++++++++++++++++------------
Mapp/assets/javascripts/components/features/followers/index.jsx32++++++++++++++++++--------------
Mapp/assets/javascripts/components/features/following/index.jsx32++++++++++++++++++--------------
Mapp/assets/javascripts/components/features/getting_started/index.jsx3++-
Mapp/assets/javascripts/components/features/hashtag_timeline/index.jsx36+++++++++++++++++-------------------
Mapp/assets/javascripts/components/features/home_timeline/components/column_settings.jsx22++++++++++------------
Mapp/assets/javascripts/components/features/home_timeline/components/setting_text.jsx24+++++++++++++++---------
Mapp/assets/javascripts/components/features/home_timeline/index.jsx20+++++++++-----------
Mapp/assets/javascripts/components/features/mutes/index.jsx28++++++++++++++++------------
Mapp/assets/javascripts/components/features/notifications/components/clear_column_button.jsx15++++++++-------
Mapp/assets/javascripts/components/features/notifications/components/column_settings.jsx26++++++++++++--------------
Mapp/assets/javascripts/components/features/notifications/components/notification.jsx23++++++++++-------------
Mapp/assets/javascripts/components/features/notifications/components/setting_toggle.jsx9+++++----
Mapp/assets/javascripts/components/features/notifications/index.jsx52++++++++++++++++++++++++++++------------------------
Mapp/assets/javascripts/components/features/public_timeline/index.jsx30++++++++++++++----------------
Mapp/assets/javascripts/components/features/reblogs/index.jsx24+++++++++++-------------
Mapp/assets/javascripts/components/features/report/components/status_check_box.jsx22++++++++++------------
Mapp/assets/javascripts/components/features/report/index.jsx48++++++++++++++++++++++++++----------------------
Mapp/assets/javascripts/components/features/status/components/action_bar.jsx60++++++++++++++++++++++++++++++++++--------------------------
Mapp/assets/javascripts/components/features/status/components/card.jsx14++++++--------
Mapp/assets/javascripts/components/features/status/components/detailed_status.jsx35+++++++++++++++++++----------------
Mapp/assets/javascripts/components/features/status/index.jsx74+++++++++++++++++++++++++++++++++++++++++++-------------------------------
Mapp/assets/javascripts/components/features/ui/components/boost_modal.jsx37+++++++++++++++++++++----------------
Mapp/assets/javascripts/components/features/ui/components/column.jsx32++++++++++++++++++--------------
Mapp/assets/javascripts/components/features/ui/components/column_header.jsx29++++++++++++++++-------------
Mapp/assets/javascripts/components/features/ui/components/column_link.jsx13+++++++------
Mapp/assets/javascripts/components/features/ui/components/columns_area.jsx16+++++++---------
Mapp/assets/javascripts/components/features/ui/components/media_modal.jsx44+++++++++++++++++++++++---------------------
Mapp/assets/javascripts/components/features/ui/components/modal_root.jsx33++++++++++++++++++---------------
Mapp/assets/javascripts/components/features/ui/components/onboarding_modal.jsx50++++++++++++++++++++++++++------------------------
Mapp/assets/javascripts/components/features/ui/components/tabs_bar.jsx4++--
Mapp/assets/javascripts/components/features/ui/components/upload_area.jsx16+++++++---------
Mapp/assets/javascripts/components/features/ui/components/video_modal.jsx22++++++++++------------
Mapp/assets/javascripts/components/features/ui/index.jsx47++++++++++++++++++++++++++---------------------
Mconfig/application.rb2+-
Mpackage.json14+++++++-------
Myarn.lock117+++++++++++++++++++++++++++++++++++++------------------------------------------
83 files changed, 1448 insertions(+), 1298 deletions(-)

diff --git a/app/assets/javascripts/components/components/account.jsx b/app/assets/javascripts/components/components/account.jsx @@ -1,5 +1,5 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; import Avatar from './avatar'; import DisplayName from './display_name'; import Permalink from './permalink'; @@ -19,30 +19,26 @@ const buttonsStyle = { height: '18px' }; -const Account = React.createClass({ +class Account extends React.PureComponent { - propTypes: { - account: ImmutablePropTypes.map.isRequired, - me: React.PropTypes.number.isRequired, - onFollow: React.PropTypes.func.isRequired, - onBlock: React.PropTypes.func.isRequired, - onMute: React.PropTypes.func.isRequired, - intl: React.PropTypes.object.isRequired - }, - - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.handleFollow = this.handleFollow.bind(this); + this.handleBlock = this.handleBlock.bind(this); + this.handleMute = this.handleMute.bind(this); + } handleFollow () { this.props.onFollow(this.props.account); - }, + } handleBlock () { this.props.onBlock(this.props.account); - }, + } handleMute () { this.props.onMute(this.props.account); - }, + } render () { const { account, me, intl } = this.props; @@ -86,6 +82,15 @@ const Account = React.createClass({ ); } -}); +} + +Account.propTypes = { + account: ImmutablePropTypes.map.isRequired, + me: PropTypes.number.isRequired, + onFollow: PropTypes.func.isRequired, + onBlock: PropTypes.func.isRequired, + onMute: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired +} export default injectIntl(Account); diff --git a/app/assets/javascripts/components/components/attachment_list.jsx b/app/assets/javascripts/components/components/attachment_list.jsx @@ -1,14 +1,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; const filename = url => url.split('/').pop().split('#')[0].split('?')[0]; -const AttachmentList = React.createClass({ - propTypes: { - media: ImmutablePropTypes.list.isRequired - }, - - mixins: [PureRenderMixin], +class AttachmentList extends React.PureComponent { render () { const { media } = this.props; @@ -29,6 +23,10 @@ const AttachmentList = React.createClass({ </div> ); } -}); +} + +AttachmentList.propTypes = { + media: ImmutablePropTypes.list.isRequired +}; export default AttachmentList; diff --git a/app/assets/javascripts/components/components/autosuggest_textarea.jsx b/app/assets/javascripts/components/components/autosuggest_textarea.jsx @@ -1,5 +1,6 @@ import AutosuggestAccountContainer from '../features/compose/containers/autosuggest_account_container'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; import { isRtl } from '../rtl'; const textAtCursorMatchesToken = (str, caretPosition) => { @@ -27,30 +28,23 @@ const textAtCursorMatchesToken = (str, caretPosition) => { } }; -const AutosuggestTextarea = React.createClass({ - - propTypes: { - value: React.PropTypes.string, - suggestions: ImmutablePropTypes.list, - disabled: React.PropTypes.bool, - placeholder: React.PropTypes.string, - onSuggestionSelected: React.PropTypes.func.isRequired, - onSuggestionsClearRequested: React.PropTypes.func.isRequired, - onSuggestionsFetchRequested: React.PropTypes.func.isRequired, - onChange: React.PropTypes.func.isRequired, - onKeyUp: React.PropTypes.func, - onKeyDown: React.PropTypes.func, - onPaste: React.PropTypes.func.isRequired, - }, - - getInitialState () { - return { +class AutosuggestTextarea extends React.Component { + + constructor (props, context) { + super(props, context); + this.state = { suggestionsHidden: false, selectedSuggestion: 0, lastToken: null, tokenStart: 0 }; - }, + this.onChange = this.onChange.bind(this); + this.onKeyDown = this.onKeyDown.bind(this); + this.onBlur = this.onBlur.bind(this); + this.onSuggestionClick = this.onSuggestionClick.bind(this); + this.setTextarea = this.setTextarea.bind(this); + this.onPaste = this.onPaste.bind(this); + } onChange (e) { const [ tokenStart, token ] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart); @@ -68,7 +62,7 @@ const AutosuggestTextarea = React.createClass({ e.target.style.height = `${e.target.scrollHeight}px`; this.props.onChange(e); - }, + } onKeyDown (e) { const { suggestions, disabled } = this.props; @@ -118,7 +112,7 @@ const AutosuggestTextarea = React.createClass({ } this.props.onKeyDown(e); - }, + } onBlur () { // If we hide the suggestions immediately, then this will prevent the @@ -128,30 +122,30 @@ const AutosuggestTextarea = React.createClass({ setTimeout(() => { this.setState({ suggestionsHidden: true }); }, 100); - }, + } onSuggestionClick (suggestion, e) { e.preventDefault(); this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion); this.textarea.focus(); - }, + } componentWillReceiveProps (nextProps) { if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden) { this.setState({ suggestionsHidden: false }); } - }, + } setTextarea (c) { this.textarea = c; - }, + } onPaste (e) { if (e.clipboardData && e.clipboardData.files.length === 1) { this.props.onPaste(e.clipboardData.files) e.preventDefault(); } - }, + } render () { const { value, suggestions, disabled, placeholder, onKeyUp } = this.props; @@ -196,6 +190,20 @@ const AutosuggestTextarea = React.createClass({ ); } -}); +}; + +AutosuggestTextarea.propTypes = { + value: PropTypes.string, + suggestions: ImmutablePropTypes.list, + disabled: PropTypes.bool, + placeholder: PropTypes.string, + onSuggestionSelected: PropTypes.func.isRequired, + onSuggestionsClearRequested: PropTypes.func.isRequired, + onSuggestionsFetchRequested: PropTypes.func.isRequired, + onChange: PropTypes.func.isRequired, + onKeyUp: PropTypes.func, + onKeyDown: PropTypes.func, + onPaste: PropTypes.func.isRequired, +}; export default AutosuggestTextarea; diff --git a/app/assets/javascripts/components/components/avatar.jsx b/app/assets/javascripts/components/components/avatar.jsx @@ -1,36 +1,23 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; -const Avatar = React.createClass({ +class Avatar extends React.PureComponent { - propTypes: { - src: React.PropTypes.string.isRequired, - staticSrc: React.PropTypes.string, - size: React.PropTypes.number.isRequired, - style: React.PropTypes.object, - animate: React.PropTypes.bool - }, - - getDefaultProps () { - return { - animate: false - }; - }, - - getInitialState () { - return { + constructor (props, context) { + super(props, context); + this.state = { hovering: false }; - }, - - mixins: [PureRenderMixin], + this.handleMouseEnter = this.handleMouseEnter.bind(this); + this.handleMouseLeave = this.handleMouseLeave.bind(this); + } handleMouseEnter () { this.setState({ hovering: true }); - }, + } handleMouseLeave () { this.setState({ hovering: false }); - }, + } render () { const { src, size, staticSrc, animate } = this.props; @@ -59,6 +46,18 @@ const Avatar = React.createClass({ ); } -}); +} + +Avatar.propTypes = { + src: PropTypes.string.isRequired, + staticSrc: PropTypes.string, + size: PropTypes.number.isRequired, + style: PropTypes.object, + animate: PropTypes.bool +}; + +Avatar.defaultProps = { + animate: false +}; export default Avatar; diff --git a/app/assets/javascripts/components/components/button.jsx b/app/assets/javascripts/components/components/button.jsx @@ -1,31 +1,17 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; - -const Button = React.createClass({ - - propTypes: { - text: React.PropTypes.node, - onClick: React.PropTypes.func, - disabled: React.PropTypes.bool, - block: React.PropTypes.bool, - secondary: React.PropTypes.bool, - size: React.PropTypes.number, - style: React.PropTypes.object, - children: React.PropTypes.node - }, - - getDefaultProps () { - return { - size: 36 - }; - }, +import PropTypes from 'prop-types'; + +class Button extends React.PureComponent { - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.handleClick = this.handleClick.bind(this); + } handleClick (e) { if (!this.props.disabled) { this.props.onClick(); } - }, + } render () { const style = { @@ -57,6 +43,21 @@ const Button = React.createClass({ ); } -}); +} + +Button.propTypes = { + text: PropTypes.node, + onClick: PropTypes.func, + disabled: PropTypes.bool, + block: PropTypes.bool, + secondary: PropTypes.bool, + size: PropTypes.number, + style: PropTypes.object, + children: PropTypes.node +}; + +Button.defaultProps = { + size: 36 +}; export default Button; diff --git a/app/assets/javascripts/components/components/collapsable.jsx b/app/assets/javascripts/components/components/collapsable.jsx @@ -1,4 +1,5 @@ import { Motion, spring } from 'react-motion'; +import PropTypes from 'prop-types'; const Collapsable = ({ fullHeight, isVisible, children }) => ( <Motion defaultStyle={{ opacity: !isVisible ? 0 : 100, height: isVisible ? fullHeight : 0 }} style={{ opacity: spring(!isVisible ? 0 : 100), height: spring(!isVisible ? 0 : fullHeight) }}> @@ -11,9 +12,9 @@ const Collapsable = ({ fullHeight, isVisible, children }) => ( ); Collapsable.propTypes = { - fullHeight: React.PropTypes.number.isRequired, - isVisible: React.PropTypes.bool.isRequired, - children: React.PropTypes.node.isRequired + fullHeight: PropTypes.number.isRequired, + isVisible: PropTypes.bool.isRequired, + children: PropTypes.node.isRequired }; export default Collapsable; diff --git a/app/assets/javascripts/components/components/column_back_button.jsx b/app/assets/javascripts/components/components/column_back_button.jsx @@ -1,23 +1,22 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; import { FormattedMessage } from 'react-intl'; +import PropTypes from 'prop-types'; const iconStyle = { display: 'inline-block', marginRight: '5px' }; -const ColumnBackButton = React.createClass({ +class ColumnBackButton extends React.PureComponent { - contextTypes: { - router: React.PropTypes.object - }, - - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.handleClick = this.handleClick.bind(this); + } handleClick () { if (window.history && window.history.length === 1) this.context.router.push("/"); else this.context.router.goBack(); - }, + } render () { return ( @@ -28,6 +27,10 @@ const ColumnBackButton = React.createClass({ ); } -}); +}; + +ColumnBackButton.contextTypes = { + router: PropTypes.object +}; export default ColumnBackButton; diff --git a/app/assets/javascripts/components/components/column_back_button_slim.jsx b/app/assets/javascripts/components/components/column_back_button_slim.jsx @@ -1,5 +1,5 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; import { FormattedMessage } from 'react-intl'; +import PropTypes from 'prop-types'; const outerStyle = { position: 'absolute', @@ -16,17 +16,16 @@ const iconStyle = { marginRight: '5px' }; -const ColumnBackButtonSlim = React.createClass({ +class ColumnBackButtonSlim extends React.PureComponent { - contextTypes: { - router: React.PropTypes.object - }, - - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.handleClick = this.handleClick.bind(this); + } handleClick () { this.context.router.push('/'); - }, + } render () { return ( @@ -39,6 +38,10 @@ const ColumnBackButtonSlim = React.createClass({ ); } -}); +} + +ColumnBackButtonSlim.contextTypes = { + router: PropTypes.object +}; export default ColumnBackButtonSlim; diff --git a/app/assets/javascripts/components/components/column_collapsable.jsx b/app/assets/javascripts/components/components/column_collapsable.jsx @@ -1,5 +1,5 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; import { Motion, spring } from 'react-motion'; +import PropTypes from 'prop-types'; const iconStyle = { fontSize: '16px', @@ -11,23 +11,16 @@ const iconStyle = { zIndex: '3' }; -const ColumnCollapsable = React.createClass({ +class ColumnCollapsable extends React.PureComponent { - propTypes: { - icon: React.PropTypes.string.isRequired, - title: React.PropTypes.string, - fullHeight: React.PropTypes.number.isRequired, - children: React.PropTypes.node, - onCollapse: React.PropTypes.func - }, - - getInitialState () { - return { + constructor (props, context) { + super(props, context); + this.state = { collapsed: true }; - }, - mixins: [PureRenderMixin], + this.handleToggleCollapsed = this.handleToggleCollapsed.bind(this); + } handleToggleCollapsed () { const currentState = this.state.collapsed; @@ -37,7 +30,7 @@ const ColumnCollapsable = React.createClass({ if (!currentState && this.props.onCollapse) { this.props.onCollapse(); } - }, + } render () { const { icon, title, fullHeight, children } = this.props; @@ -60,6 +53,14 @@ const ColumnCollapsable = React.createClass({ </div> ); } -}); +} + +ColumnCollapsable.propTypes = { + icon: PropTypes.string.isRequired, + title: PropTypes.string, + fullHeight: PropTypes.number.isRequired, + children: PropTypes.node, + onCollapse: PropTypes.func +}; export default ColumnCollapsable; diff --git a/app/assets/javascripts/components/components/display_name.jsx b/app/assets/javascripts/components/components/display_name.jsx @@ -1,15 +1,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; import escapeTextContentForBrowser from 'escape-html'; import emojify from '../emoji'; -const DisplayName = React.createClass({ - - propTypes: { - account: ImmutablePropTypes.map.isRequired - }, - - mixins: [PureRenderMixin], +class DisplayName extends React.PureComponent { render () { const displayName = this.props.account.get('display_name').length === 0 ? this.props.account.get('username') : this.props.account.get('display_name'); @@ -22,6 +15,10 @@ const DisplayName = React.createClass({ ); } -}); +}; + +DisplayName.propTypes = { + account: ImmutablePropTypes.map.isRequired +} export default DisplayName; diff --git a/app/assets/javascripts/components/components/dropdown_menu.jsx b/app/assets/javascripts/components/components/dropdown_menu.jsx @@ -1,26 +1,20 @@ import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; -const DropdownMenu = React.createClass({ +class DropdownMenu extends React.PureComponent { - propTypes: { - icon: React.PropTypes.string.isRequired, - items: React.PropTypes.array.isRequired, - size: React.PropTypes.number.isRequired, - direction: React.PropTypes.string - }, - - getDefaultProps () { - return { + constructor (props, context) { + super(props, context); + this.state = { direction: 'left' }; - }, - - mixins: [PureRenderMixin], + this.setRef = this.setRef.bind(this); + this.renderItem = this.renderItem.bind(this); + } setRef (c) { this.dropdown = c; - }, + } handleClick (i, e) { const { action } = this.props.items[i]; @@ -30,7 +24,7 @@ const DropdownMenu = React.createClass({ action(); this.dropdown.hide(); } - }, + } renderItem (item, i) { if (item === null) { @@ -46,7 +40,7 @@ const DropdownMenu = React.createClass({ </a> </li> ); - }, + } render () { const { icon, items, size, direction } = this.props; @@ -67,6 +61,13 @@ const DropdownMenu = React.createClass({ ); } -}); +} + +DropdownMenu.propTypes = { + icon: PropTypes.string.isRequired, + items: PropTypes.array.isRequired, + size: PropTypes.number.isRequired, + direction: PropTypes.string +}; export default DropdownMenu; diff --git a/app/assets/javascripts/components/components/extended_video_player.jsx b/app/assets/javascripts/components/components/extended_video_player.jsx @@ -1,33 +1,30 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; -const ExtendedVideoPlayer = React.createClass({ +class ExtendedVideoPlayer extends React.PureComponent { - propTypes: { - src: React.PropTypes.string.isRequired, - time: React.PropTypes.number, - controls: React.PropTypes.bool.isRequired, - muted: React.PropTypes.bool.isRequired - }, - - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.handleLoadedData = this.handleLoadedData.bind(this); + this.setRef = this.setRef.bind(this); + } handleLoadedData () { if (this.props.time) { this.video.currentTime = this.props.time; } - }, + } componentDidMount () { this.video.addEventListener('loadeddata', this.handleLoadedData); - }, + } componentWillUnmount () { this.video.removeEventListener('loadeddata', this.handleLoadedData); - }, + } setRef (c) { this.video = c; - }, + } render () { return ( @@ -42,8 +39,15 @@ const ExtendedVideoPlayer = React.createClass({ /> </div> ); - }, + } + +} -}); +ExtendedVideoPlayer.propTypes = { + src: PropTypes.string.isRequired, + time: PropTypes.number, + controls: PropTypes.bool.isRequired, + muted: PropTypes.bool.isRequired +}; export default ExtendedVideoPlayer; diff --git a/app/assets/javascripts/components/components/icon_button.jsx b/app/assets/javascripts/components/components/icon_button.jsx @@ -1,33 +1,12 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; import { Motion, spring } from 'react-motion'; +import PropTypes from 'prop-types'; -const IconButton = React.createClass({ - - propTypes: { - title: React.PropTypes.string.isRequired, - icon: React.PropTypes.string.isRequired, - onClick: React.PropTypes.func, - size: React.PropTypes.number, - active: React.PropTypes.bool, - style: React.PropTypes.object, - activeStyle: React.PropTypes.object, - disabled: React.PropTypes.bool, - inverted: React.PropTypes.bool, - animate: React.PropTypes.bool, - overlay: React.PropTypes.bool - }, - - getDefaultProps () { - return { - size: 18, - active: false, - disabled: false, - animate: false, - overlay: false - }; - }, +class IconButton extends React.PureComponent { - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.handleClick = this.handleClick.bind(this); + } handleClick (e) { e.preventDefault(); @@ -35,7 +14,7 @@ const IconButton = React.createClass({ if (!this.props.disabled) { this.props.onClick(e); } - }, + } render () { let style = { @@ -84,6 +63,28 @@ const IconButton = React.createClass({ ); } -}); +} + +IconButton.propTypes = { + title: PropTypes.string.isRequired, + icon: PropTypes.string.isRequired, + onClick: PropTypes.func, + size: PropTypes.number, + active: PropTypes.bool, + style: PropTypes.object, + activeStyle: PropTypes.object, + disabled: PropTypes.bool, + inverted: PropTypes.bool, + animate: PropTypes.bool, + overlay: PropTypes.bool +}; + +IconButton.defaultProps = { + size: 18, + active: false, + disabled: false, + animate: false, + overlay: false +}; export default IconButton; diff --git a/app/assets/javascripts/components/components/load_more.jsx b/app/assets/javascripts/components/components/load_more.jsx @@ -1,4 +1,5 @@ import { FormattedMessage } from 'react-intl'; +import PropTypes from 'prop-types'; const LoadMore = ({ onClick }) => ( <a href="#" className='load-more' role='button' onClick={onClick}> @@ -7,7 +8,7 @@ const LoadMore = ({ onClick }) => ( ); LoadMore.propTypes = { - onClick: React.PropTypes.func + onClick: PropTypes.func }; export default LoadMore; diff --git a/app/assets/javascripts/components/components/media_gallery.jsx b/app/assets/javascripts/components/components/media_gallery.jsx @@ -1,5 +1,5 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import IconButton from './icon_button'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { isIOS } from '../is_mobile'; @@ -72,17 +72,12 @@ const gifvThumbStyle = { cursor: 'zoom-in' }; -const Item = React.createClass({ +class Item extends React.PureComponent { - propTypes: { - attachment: ImmutablePropTypes.map.isRequired, - index: React.PropTypes.number.isRequired, - size: React.PropTypes.number.isRequired, - onClick: React.PropTypes.func.isRequired, - autoPlayGif: React.PropTypes.bool.isRequired - }, - - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.handleClick = this.handleClick.bind(this); + } handleClick (e) { const { index, onClick } = this.props; @@ -93,7 +88,7 @@ const Item = React.createClass({ } e.stopPropagation(); - }, + } render () { const { attachment, index, size } = this.props; @@ -184,34 +179,34 @@ const Item = React.createClass({ ); } -}); +} -const MediaGallery = React.createClass({ - - getInitialState () { - return { - visible: !this.props.sensitive - }; - }, +Item.propTypes = { + attachment: ImmutablePropTypes.map.isRequired, + index: PropTypes.number.isRequired, + size: PropTypes.number.isRequired, + onClick: PropTypes.func.isRequired, + autoPlayGif: PropTypes.bool.isRequired +}; - propTypes: { - sensitive: React.PropTypes.bool, - media: ImmutablePropTypes.list.isRequired, - height: React.PropTypes.number.isRequired, - onOpenMedia: React.PropTypes.func.isRequired, - intl: React.PropTypes.object.isRequired, - autoPlayGif: React.PropTypes.bool.isRequired - }, +class MediaGallery extends React.PureComponent { - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.state = { + visible: !props.sensitive + }; + this.handleOpen = this.handleOpen.bind(this); + this.handleClick = this.handleClick.bind(this); + } handleOpen (e) { this.setState({ visible: !this.state.visible }); - }, + } handleClick (index) { this.props.onOpenMedia(this.props.media, index); - }, + } render () { const { media, intl, sensitive } = this.props; @@ -249,6 +244,15 @@ const MediaGallery = React.createClass({ ); } -}); +} + +MediaGallery.propTypes = { + sensitive: PropTypes.bool, + media: ImmutablePropTypes.list.isRequired, + height: PropTypes.number.isRequired, + onOpenMedia: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + autoPlayGif: PropTypes.bool.isRequired +}; export default injectIntl(MediaGallery); diff --git a/app/assets/javascripts/components/components/permalink.jsx b/app/assets/javascripts/components/components/permalink.jsx @@ -1,21 +1,18 @@ -const Permalink = React.createClass({ +import PropTypes from 'prop-types'; - contextTypes: { - router: React.PropTypes.object - }, +class Permalink extends React.Component { - propTypes: { - href: React.PropTypes.string.isRequired, - to: React.PropTypes.string.isRequired, - children: React.PropTypes.node - }, + constructor (props, context) { + super(props, context); + this.handleClick = this.handleClick.bind(this); + } handleClick (e) { if (e.button === 0) { e.preventDefault(); this.context.router.push(this.props.to); } - }, + } render () { const { href, children, ...other } = this.props; @@ -23,6 +20,16 @@ const Permalink = React.createClass({ return <a href={href} onClick={this.handleClick} {...other}>{children}</a>; } -}); +} + +Permalink.contextTypes = { + router: PropTypes.object +}; + +Permalink.propTypes = { + href: PropTypes.string.isRequired, + to: PropTypes.string.isRequired, + children: PropTypes.node +}; export default Permalink; diff --git a/app/assets/javascripts/components/components/relative_timestamp.jsx b/app/assets/javascripts/components/components/relative_timestamp.jsx @@ -1,4 +1,5 @@ import { injectIntl, FormattedRelative } from 'react-intl'; +import PropTypes from 'prop-types'; const RelativeTimestamp = ({ intl, timestamp }) => { const date = new Date(timestamp); @@ -11,8 +12,8 @@ const RelativeTimestamp = ({ intl, timestamp }) => { }; RelativeTimestamp.propTypes = { - intl: React.PropTypes.object.isRequired, - timestamp: React.PropTypes.string.isRequired + intl: PropTypes.object.isRequired, + timestamp: PropTypes.string.isRequired }; export default injectIntl(RelativeTimestamp); diff --git a/app/assets/javascripts/components/components/status.jsx b/app/assets/javascripts/components/components/status.jsx @@ -1,7 +1,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; import Avatar from './avatar'; import RelativeTimestamp from './relative_timestamp'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; import DisplayName from './display_name'; import MediaGallery from './media_gallery'; import VideoPlayer from './video_player'; @@ -12,41 +12,25 @@ import { FormattedMessage } from 'react-intl'; import emojify from '../emoji'; import escapeTextContentForBrowser from 'escape-html'; -const Status = React.createClass({ - - contextTypes: { - router: React.PropTypes.object - }, - - propTypes: { - status: ImmutablePropTypes.map, - wrapped: React.PropTypes.bool, - onReply: React.PropTypes.func, - onFavourite: React.PropTypes.func, - onReblog: React.PropTypes.func, - onDelete: React.PropTypes.func, - onOpenMedia: React.PropTypes.func, - onOpenVideo: React.PropTypes.func, - onBlock: React.PropTypes.func, - me: React.PropTypes.number, - boostModal: React.PropTypes.bool, - autoPlayGif: React.PropTypes.bool, - muted: React.PropTypes.bool - }, - - mixins: [PureRenderMixin], +class Status extends React.PureComponent { + + constructor (props, context) { + super(props, context); + this.handleClick = this.handleClick.bind(this); + this.handleAccountClick = this.handleAccountClick.bind(this); + } handleClick () { const { status } = this.props; this.context.router.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`); - }, + } handleAccountClick (id, e) { if (e.button === 0) { e.preventDefault(); this.context.router.push(`/accounts/${id}`); } - }, + } render () { let media = ''; @@ -112,6 +96,26 @@ const Status = React.createClass({ ); } -}); +} + +Status.contextTypes = { + router: PropTypes.object +}; + +Status.propTypes = { + status: ImmutablePropTypes.map, + wrapped: PropTypes.bool, + onReply: PropTypes.func, + onFavourite: PropTypes.func, + onReblog: PropTypes.func, + onDelete: PropTypes.func, + onOpenMedia: PropTypes.func, + onOpenVideo: PropTypes.func, + onBlock: PropTypes.func, + me: PropTypes.number, + boostModal: PropTypes.bool, + autoPlayGif: PropTypes.bool, + muted: PropTypes.bool +}; export default Status; diff --git a/app/assets/javascripts/components/components/status_action_bar.jsx b/app/assets/javascripts/components/components/status_action_bar.jsx @@ -1,5 +1,5 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import IconButton from './icon_button'; import DropdownMenu from './dropdown_menu'; import { defineMessages, injectIntl } from 'react-intl'; @@ -17,64 +17,57 @@ const messages = defineMessages({ report: { id: 'status.report', defaultMessage: 'Report @{name}' } }); -const StatusActionBar = React.createClass({ - - contextTypes: { - router: React.PropTypes.object - }, - - propTypes: { - status: ImmutablePropTypes.map.isRequired, - onReply: React.PropTypes.func, - onFavourite: React.PropTypes.func, - onReblog: React.PropTypes.func, - onDelete: React.PropTypes.func, - onMention: React.PropTypes.func, - onMute: React.PropTypes.func, - onBlock: React.PropTypes.func, - onReport: React.PropTypes.func, - me: React.PropTypes.number.isRequired, - intl: React.PropTypes.object.isRequired - }, - - mixins: [PureRenderMixin], +class StatusActionBar extends React.PureComponent { + + constructor (props, context) { + super(props, context); + this.handleReplyClick = this.handleReplyClick.bind(this); + this.handleFavouriteClick = this.handleFavouriteClick.bind(this); + this.handleReblogClick = this.handleReblogClick.bind(this); + this.handleDeleteClick = this.handleDeleteClick.bind(this); + this.handleMentionClick = this.handleMentionClick.bind(this); + this.handleMuteClick = this.handleMuteClick.bind(this); + this.handleBlockClick = this.handleBlockClick.bind(this); + this.handleOpen = this.handleOpen.bind(this); + this.handleReport = this.handleReport.bind(this); + } handleReplyClick () { this.props.onReply(this.props.status, this.context.router); - }, + } handleFavouriteClick () { this.props.onFavourite(this.props.status); - }, + } handleReblogClick (e) { this.props.onReblog(this.props.status, e); - }, + } handleDeleteClick () { this.props.onDelete(this.props.status); - }, + } handleMentionClick () { this.props.onMention(this.props.status.get('account'), this.context.router); - }, + } handleMuteClick () { this.props.onMute(this.props.status.get('account')); - }, + } handleBlockClick () { this.props.onBlock(this.props.status.get('account')); - }, + } handleOpen () { this.context.router.push(`/statuses/${this.props.status.get('id')}`); - }, + } handleReport () { this.props.onReport(this.props.status); this.context.router.push('/report'); - }, + } render () { const { status, me, intl } = this.props; @@ -119,6 +112,24 @@ const StatusActionBar = React.createClass({ ); } -}); +} + +StatusActionBar.contextTypes = { + router: PropTypes.object +}; + +StatusActionBar.propTypes = { + status: ImmutablePropTypes.map.isRequired, + onReply: PropTypes.func, + onFavourite: PropTypes.func, + onReblog: PropTypes.func, + onDelete: PropTypes.func, + onMention: PropTypes.func, + onMute: PropTypes.func, + onBlock: PropTypes.func, + onReport: PropTypes.func, + me: PropTypes.number.isRequired, + intl: PropTypes.object.isRequired +}; export default injectIntl(StatusActionBar); diff --git a/app/assets/javascripts/components/components/status_content.jsx b/app/assets/javascripts/components/components/status_content.jsx @@ -1,29 +1,24 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; import escapeTextContentForBrowser from 'escape-html'; +import PropTypes from 'prop-types'; import emojify from '../emoji'; import { isRtl } from '../rtl'; import { FormattedMessage } from 'react-intl'; import Permalink from './permalink'; -const StatusContent = React.createClass({ +class StatusContent extends React.PureComponent { - contextTypes: { - router: React.PropTypes.object - }, - - propTypes: { - status: ImmutablePropTypes.map.isRequired, - onClick: React.PropTypes.func - }, - - getInitialState () { - return { + constructor (props, context) { + super(props, context); + this.state = { hidden: true }; - }, - - mixins: [PureRenderMixin], + this.onMentionClick = this.onMentionClick.bind(this); + this.onHashtagClick = this.onHashtagClick.bind(this); + this.handleMouseDown = this.handleMouseDown.bind(this) + this.handleMouseUp = this.handleMouseUp.bind(this); + this.handleSpoilerClick = this.handleSpoilerClick.bind(this); + }; componentDidMount () { const node = ReactDOM.findDOMNode(this); @@ -47,14 +42,14 @@ const StatusContent = React.createClass({ link.setAttribute('title', link.href); } } - }, + } onMentionClick (mention, e) { if (e.button === 0) { e.preventDefault(); this.context.router.push(`/accounts/${mention.get('id')}`); } - }, + } onHashtagClick (hashtag, e) { hashtag = hashtag.replace(/^#/, '').toLowerCase(); @@ -63,11 +58,11 @@ const StatusContent = React.createClass({ e.preventDefault(); this.context.router.push(`/timelines/tag/${hashtag}`); } - }, + } handleMouseDown (e) { this.startXY = [e.clientX, e.clientY]; - }, + } handleMouseUp (e) { const [ startX, startY ] = this.startXY; @@ -82,12 +77,12 @@ const StatusContent = React.createClass({ } this.startXY = null; - }, + } handleSpoilerClick (e) { e.preventDefault(); this.setState({ hidden: !this.state.hidden }); - }, + } render () { const { status } = this.props; @@ -146,8 +141,17 @@ const StatusContent = React.createClass({ /> ); } - }, + } + +} + +StatusContent.contextTypes = { + router: PropTypes.object +}; -}); +StatusContent.propTypes = { + status: ImmutablePropTypes.map.isRequired, + onClick: PropTypes.func +}; export default StatusContent; diff --git a/app/assets/javascripts/components/components/status_list.jsx b/app/assets/javascripts/components/components/status_list.jsx @@ -1,32 +1,18 @@ import Status from './status'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; import { ScrollContainer } from 'react-router-scroll'; +import PropTypes from 'prop-types'; import StatusContainer from '../containers/status_container'; import LoadMore from './load_more'; -const StatusList = React.createClass({ - - propTypes: { - statusIds: ImmutablePropTypes.list.isRequired, - onScrollToBottom: React.PropTypes.func, - onScrollToTop: React.PropTypes.func, - onScroll: React.PropTypes.func, - trackScroll: React.PropTypes.bool, - isLoading: React.PropTypes.bool, - isUnread: React.PropTypes.bool, - hasMore: React.PropTypes.bool, - prepend: React.PropTypes.node, - emptyMessage: React.PropTypes.node - }, - - getDefaultProps () { - return { - trackScroll: true - }; - }, - - mixins: [PureRenderMixin], +class StatusList extends React.PureComponent { + + constructor (props, context) { + super(props, context); + this.handleScroll = this.handleScroll.bind(this); + this.setRef = this.setRef.bind(this); + this.handleLoadMore = this.handleLoadMore.bind(this); + } handleScroll (e) { const { scrollTop, scrollHeight, clientHeight } = e.target; @@ -40,38 +26,38 @@ const StatusList = React.createClass({ } else if (this.props.onScroll) { this.props.onScroll(); } - }, + } componentDidMount () { this.attachScrollListener(); - }, + } componentDidUpdate (prevProps) { if (this.node.scrollTop > 0 && (prevProps.statusIds.size < this.props.statusIds.size && prevProps.statusIds.first() !== this.props.statusIds.first() && !!this._oldScrollPosition)) { this.node.scrollTop = this.node.scrollHeight - this._oldScrollPosition; } - }, + } componentWillUnmount () { this.detachScrollListener(); - }, + } attachScrollListener () { this.node.addEventListener('scroll', this.handleScroll); - }, + } detachScrollListener () { this.node.removeEventListener('scroll', this.handleScroll); - }, + } setRef (c) { this.node = c; - }, + } handleLoadMore (e) { e.preventDefault(); this.props.onScrollToBottom(); - }, + } render () { const { statusIds, onScrollToBottom, trackScroll, isLoading, isUnread, hasMore, prepend, emptyMessage } = this.props; @@ -123,6 +109,23 @@ const StatusList = React.createClass({ } } -}); +} + +StatusList.propTypes = { + statusIds: ImmutablePropTypes.list.isRequired, + onScrollToBottom: PropTypes.func, + onScrollToTop: PropTypes.func, + onScroll: PropTypes.func, + trackScroll: PropTypes.bool, + isLoading: PropTypes.bool, + isUnread: PropTypes.bool, + hasMore: PropTypes.bool, + prepend: PropTypes.node, + emptyMessage: PropTypes.node +}; + +StatusList.defaultProps = { + trackScroll: true +}; export default StatusList; diff --git a/app/assets/javascripts/components/components/video_player.jsx b/app/assets/javascripts/components/components/video_player.jsx @@ -1,5 +1,5 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import IconButton from './icon_button'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { isIOS } from '../is_mobile'; @@ -72,39 +72,30 @@ const expandButtonStyle = { zIndex: '100' }; -const VideoPlayer = React.createClass({ - propTypes: { - media: ImmutablePropTypes.map.isRequired, - width: React.PropTypes.number, - height: React.PropTypes.number, - sensitive: React.PropTypes.bool, - intl: React.PropTypes.object.isRequired, - autoplay: React.PropTypes.bool, - onOpenVideo: React.PropTypes.func.isRequired - }, - - getDefaultProps () { - return { - width: 239, - height: 110 - }; - }, +class VideoPlayer extends React.PureComponent { - getInitialState () { - return { + constructor (props, context) { + super(props, context); + this.state = { visible: !this.props.sensitive, preview: true, muted: true, hasAudio: true, videoError: false }; - }, - - mixins: [PureRenderMixin], + this.handleClick = this.handleClick.bind(this); + this.handleVideoClick = this.handleVideoClick.bind(this); + this.handleOpen = this.handleOpen.bind(this); + this.handleVisibility = this.handleVisibility.bind(this); + this.handleExpand = this.handleExpand.bind(this); + this.setRef = this.setRef.bind(this); + this.handleLoadedData = this.handleLoadedData.bind(this); + this.handleVideoError = this.handleVideoError.bind(this); + } handleClick () { this.setState({ muted: !this.state.muted }); - }, + } handleVideoClick (e) { e.stopPropagation(); @@ -116,37 +107,37 @@ const VideoPlayer = React.createClass({ } else { node.pause(); } - }, + } handleOpen () { this.setState({ preview: !this.state.preview }); - }, + } handleVisibility () { this.setState({ visible: !this.state.visible, preview: true }); - }, + } handleExpand () { this.video.pause(); this.props.onOpenVideo(this.props.media, this.video.currentTime); - }, + } setRef (c) { this.video = c; - }, + } handleLoadedData () { if (('WebkitAppearance' in document.documentElement.style && this.video.audioTracks.length === 0) || this.video.mozHasAudio === false) { this.setState({ hasAudio: false }); } - }, + } handleVideoError () { this.setState({ videoError: true }); - }, + } componentDidMount () { if (!this.video) { @@ -155,7 +146,7 @@ const VideoPlayer = React.createClass({ this.video.addEventListener('loadeddata', this.handleLoadedData); this.video.addEventListener('error', this.handleVideoError); - }, + } componentDidUpdate () { if (!this.video) { @@ -164,7 +155,7 @@ const VideoPlayer = React.createClass({ this.video.addEventListener('loadeddata', this.handleLoadedData); this.video.addEventListener('error', this.handleVideoError); - }, + } componentWillUnmount () { if (!this.video) { @@ -173,7 +164,7 @@ const VideoPlayer = React.createClass({ this.video.removeEventListener('loadeddata', this.handleLoadedData); this.video.removeEventListener('error', this.handleVideoError); - }, + } render () { const { media, intl, width, height, sensitive, autoplay } = this.props; @@ -247,6 +238,21 @@ const VideoPlayer = React.createClass({ ); } -}); +} + +VideoPlayer.propTypes = { + media: ImmutablePropTypes.map.isRequired, + width: PropTypes.number, + height: PropTypes.number, + sensitive: PropTypes.bool, + intl: PropTypes.object.isRequired, + autoplay: PropTypes.bool, + onOpenVideo: PropTypes.func.isRequired +}; + +VideoPlayer.defaultProps = { + width: 239, + height: 110 +}; export default injectIntl(VideoPlayer); diff --git a/app/assets/javascripts/components/containers/mastodon.jsx b/app/assets/javascripts/components/containers/mastodon.jsx @@ -1,4 +1,5 @@ import { Provider } from 'react-redux'; +import PropTypes from 'prop-types'; import configureStore from '../store/configureStore'; import { refreshTimelineSuccess, @@ -96,11 +97,7 @@ addLocaleData([ ...id, ]); -const Mastodon = React.createClass({ - - propTypes: { - locale: React.PropTypes.string.isRequired - }, +class Mastodon extends React.Component { componentDidMount() { const { locale } = this.props; @@ -145,14 +142,14 @@ const Mastodon = React.createClass({ } store.dispatch(showOnboardingOnce()); - }, + } componentWillUnmount () { if (typeof this.subscription !== 'undefined') { this.subscription.close(); this.subscription = null; } - }, + } render () { const { locale } = this.props; @@ -195,6 +192,10 @@ const Mastodon = React.createClass({ ); } -}); +} + +Mastodon.propTypes = { + locale: PropTypes.string.isRequired +}; export default Mastodon; diff --git a/app/assets/javascripts/components/features/account/components/action_bar.jsx b/app/assets/javascripts/components/features/account/components/action_bar.jsx @@ -1,5 +1,5 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; import DropdownMenu from '../../../components/dropdown_menu'; import { Link } from 'react-router'; import { defineMessages, injectIntl, FormattedMessage, FormattedNumber } from 'react-intl'; @@ -28,20 +28,7 @@ const outerLinksStyle = { lineHeight: '18px' }; -const ActionBar = React.createClass({ - - propTypes: { - account: ImmutablePropTypes.map.isRequired, - me: React.PropTypes.number.isRequired, - onFollow: React.PropTypes.func, - onBlock: React.PropTypes.func.isRequired, - onMention: React.PropTypes.func.isRequired, - onReport: React.PropTypes.func.isRequired, - onMute: React.PropTypes.func.isRequired, - intl: React.PropTypes.object.isRequired - }, - - mixins: [PureRenderMixin], +class ActionBar extends React.PureComponent { render () { const { account, me, intl } = this.props; @@ -100,6 +87,17 @@ const ActionBar = React.createClass({ ); } -}); +} + +ActionBar.propTypes = { + account: ImmutablePropTypes.map.isRequired, + me: PropTypes.number.isRequired, + onFollow: PropTypes.func, + onBlock: PropTypes.func.isRequired, + onMention: PropTypes.func.isRequired, + onReport: PropTypes.func.isRequired, + onMute: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired +}; export default injectIntl(ActionBar); diff --git a/app/assets/javascripts/components/features/account/components/header.jsx b/app/assets/javascripts/components/features/account/components/header.jsx @@ -1,5 +1,5 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; import emojify from '../../../emoji'; import escapeTextContentForBrowser from 'escape-html'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; @@ -21,30 +21,28 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -const Avatar = React.createClass({ +class Avatar extends React.PureComponent { - propTypes: { - account: ImmutablePropTypes.map.isRequired, - autoPlayGif: React.PropTypes.bool.isRequired - }, - - getInitialState () { - return { + constructor (props, context) { + super(props, context); + + this.state = { isHovered: false }; - }, - - mixins: [PureRenderMixin], + + this.handleMouseOver = this.handleMouseOver.bind(this); + this.handleMouseOut = this.handleMouseOut.bind(this); + } 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; @@ -69,19 +67,14 @@ const Avatar = React.createClass({ ); } -}); +} -const Header = React.createClass({ - - propTypes: { - account: ImmutablePropTypes.map, - me: React.PropTypes.number.isRequired, - onFollow: React.PropTypes.func.isRequired, - intl: React.PropTypes.object.isRequired, - autoPlayGif: React.PropTypes.bool.isRequired - }, +Avatar.propTypes = { + account: ImmutablePropTypes.map.isRequired, + autoPlayGif: PropTypes.bool.isRequired +}; - mixins: [PureRenderMixin], +class Header extends React.Component { render () { const { account, me, intl } = this.props; @@ -142,6 +135,14 @@ const Header = React.createClass({ ); } -}); +} + +Header.propTypes = { + account: ImmutablePropTypes.map, + me: PropTypes.number.isRequired, + onFollow: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + autoPlayGif: PropTypes.bool.isRequired +}; export default connect(makeMapStateToProps)(injectIntl(Header)); diff --git a/app/assets/javascripts/components/features/account_timeline/components/header.jsx b/app/assets/javascripts/components/features/account_timeline/components/header.jsx @@ -1,46 +1,40 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; import InnerHeader from '../../account/components/header'; import ActionBar from '../../account/components/action_bar'; import MissingIndicator from '../../../components/missing_indicator'; -const Header = React.createClass({ - contextTypes: { - router: React.PropTypes.object - }, +class Header extends React.PureComponent { - propTypes: { - account: ImmutablePropTypes.map, - me: React.PropTypes.number.isRequired, - onFollow: React.PropTypes.func.isRequired, - onBlock: React.PropTypes.func.isRequired, - onMention: React.PropTypes.func.isRequired, - onReport: React.PropTypes.func.isRequired, - onMute: React.PropTypes.func.isRequired - }, - - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.handleFollow = this.handleFollow.bind(this); + this.handleBlock = this.handleBlock.bind(this); + this.handleMention = this.handleMention.bind(this); + this.handleReport = this.handleReport.bind(this); + this.handleMute = this.handleMute.bind(this); + } handleFollow () { this.props.onFollow(this.props.account); - }, + } handleBlock () { this.props.onBlock(this.props.account); - }, + } handleMention () { this.props.onMention(this.props.account, this.context.router); - }, + } handleReport () { this.props.onReport(this.props.account); this.context.router.push('/report'); - }, + } handleMute() { this.props.onMute(this.props.account); - }, + } render () { const { account, me } = this.props; @@ -68,6 +62,20 @@ const Header = React.createClass({ </div> ); } -}); +} + +Header.propTypes = { + account: ImmutablePropTypes.map, + me: PropTypes.number.isRequired, + onFollow: PropTypes.func.isRequired, + onBlock: PropTypes.func.isRequired, + onMention: PropTypes.func.isRequired, + onReport: PropTypes.func.isRequired, + onMute: PropTypes.func.isRequired +}; + +Header.contextTypes = { + router: PropTypes.object +}; export default Header; diff --git a/app/assets/javascripts/components/features/account_timeline/index.jsx b/app/assets/javascripts/components/features/account_timeline/index.jsx @@ -1,6 +1,6 @@ import { connect } from 'react-redux'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; import { fetchAccount, fetchAccountTimeline, @@ -20,36 +20,30 @@ const mapStateToProps = (state, props) => ({ me: state.getIn(['meta', 'me']) }); -const AccountTimeline = React.createClass({ +class AccountTimeline extends React.PureComponent { - propTypes: { - params: React.PropTypes.object.isRequired, - dispatch: React.PropTypes.func.isRequired, - statusIds: ImmutablePropTypes.list, - isLoading: React.PropTypes.bool, - hasMore: React.PropTypes.bool, - me: React.PropTypes.number.isRequired - }, - - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.handleScrollToBottom = this.handleScrollToBottom.bind(this); + } componentWillMount () { this.props.dispatch(fetchAccount(Number(this.props.params.accountId))); this.props.dispatch(fetchAccountTimeline(Number(this.props.params.accountId))); - }, + } componentWillReceiveProps(nextProps) { if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) { this.props.dispatch(fetchAccount(Number(nextProps.params.accountId))); this.props.dispatch(fetchAccountTimeline(Number(nextProps.params.accountId))); } - }, + } handleScrollToBottom () { if (!this.props.isLoading && this.props.hasMore) { this.props.dispatch(expandAccountTimeline(Number(this.props.params.accountId))); } - }, + } render () { const { statusIds, isLoading, hasMore, me } = this.props; @@ -78,6 +72,15 @@ const AccountTimeline = React.createClass({ ); } -}); +} + +AccountTimeline.propTypes = { + params: PropTypes.object.isRequired, + dispatch: PropTypes.func.isRequired, + statusIds: ImmutablePropTypes.list, + isLoading: PropTypes.bool, + hasMore: PropTypes.bool, + me: PropTypes.number.isRequired +}; export default connect(mapStateToProps)(AccountTimeline); diff --git a/app/assets/javascripts/components/features/blocks/index.jsx b/app/assets/javascripts/components/features/blocks/index.jsx @@ -1,6 +1,6 @@ import { connect } from 'react-redux'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; import LoadingIndicator from '../../components/loading_indicator'; import { ScrollContainer } from 'react-router-scroll'; import Column from '../ui/components/column'; @@ -17,19 +17,16 @@ const mapStateToProps = state => ({ accountIds: state.getIn(['user_lists', 'blocks', 'items']) }); -const Blocks = React.createClass({ - propTypes: { - params: React.PropTypes.object.isRequired, - dispatch: React.PropTypes.func.isRequired, - accountIds: ImmutablePropTypes.list, - intl: React.PropTypes.object.isRequired - }, +class Blocks extends React.PureComponent { - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.handleScroll = this.handleScroll.bind(this); + } componentWillMount () { this.props.dispatch(fetchBlocks()); - }, + } handleScroll (e) { const { scrollTop, scrollHeight, clientHeight } = e.target; @@ -37,7 +34,7 @@ const Blocks = React.createClass({ if (scrollTop === scrollHeight - clientHeight) { this.props.dispatch(expandBlocks()); } - }, + } render () { const { intl, accountIds } = this.props; @@ -63,6 +60,13 @@ const Blocks = React.createClass({ </Column> ); } -}); +} + +Blocks.propTypes = { + params: PropTypes.object.isRequired, + dispatch: PropTypes.func.isRequired, + accountIds: ImmutablePropTypes.list, + intl: PropTypes.object.isRequired +}; export default connect(mapStateToProps)(injectIntl(Blocks)); diff --git a/app/assets/javascripts/components/features/community_timeline/index.jsx b/app/assets/javascripts/components/features/community_timeline/index.jsx @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import StatusListContainer from '../ui/containers/status_list_container'; import Column from '../ui/components/column'; import { @@ -25,17 +25,7 @@ const mapStateToProps = state => ({ let subscription; -const CommunityTimeline = React.createClass({ - - propTypes: { - dispatch: React.PropTypes.func.isRequired, - intl: React.PropTypes.object.isRequired, - streamingAPIBaseURL: React.PropTypes.string.isRequired, - accessToken: React.PropTypes.string.isRequired, - hasUnread: React.PropTypes.bool - }, - - mixins: [PureRenderMixin], +class CommunityTimeline extends React.PureComponent { componentDidMount () { const { dispatch, streamingAPIBaseURL, accessToken } = this.props; @@ -72,14 +62,14 @@ const CommunityTimeline = React.createClass({ } }); - }, + } componentWillUnmount () { // if (typeof subscription !== 'undefined') { // subscription.close(); // subscription = null; // } - }, + } render () { const { intl, hasUnread } = this.props; @@ -90,8 +80,16 @@ const CommunityTimeline = React.createClass({ <StatusListContainer type='community' emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />} /> </Column> ); - }, + } -}); +} + +CommunityTimeline.propTypes = { + dispatch: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + streamingAPIBaseURL: PropTypes.string.isRequired, + accessToken: PropTypes.string.isRequired, + hasUnread: PropTypes.bool +}; export default connect(mapStateToProps)(injectIntl(CommunityTimeline)); diff --git a/app/assets/javascripts/components/features/compose/components/character_counter.jsx b/app/assets/javascripts/components/features/compose/components/character_counter.jsx @@ -1,20 +1,13 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; -const CharacterCounter = React.createClass({ - - propTypes: { - text: React.PropTypes.string.isRequired, - max: React.PropTypes.number.isRequired - }, - - mixins: [PureRenderMixin], +class CharacterCounter extends React.PureComponent { checkRemainingText (diff) { if (diff <= 0) { return <span style={{ fontSize: '16px', cursor: 'default', color: '#ff5050' }}>{diff}</span>; } return <span style={{ fontSize: '16px', cursor: 'default' }}>{diff}</span>; - }, + } render () { const diff = this.props.max - this.props.text.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, "_").length; @@ -22,6 +15,11 @@ const CharacterCounter = React.createClass({ return this.checkRemainingText(diff); } -}); +} + +CharacterCounter.propTypes = { + text: PropTypes.string.isRequired, + max: PropTypes.number.isRequired +} export default CharacterCounter; diff --git a/app/assets/javascripts/components/features/compose/components/compose_form.jsx b/app/assets/javascripts/components/features/compose/components/compose_form.jsx @@ -1,7 +1,7 @@ import CharacterCounter from './character_counter'; import Button from '../../../components/button'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; import ReplyIndicatorContainer from '../containers/reply_indicator_container'; import AutosuggestTextarea from '../../../components/autosuggest_textarea'; import { debounce } from 'react-decoration'; @@ -22,67 +22,53 @@ const messages = defineMessages({ publish: { id: 'compose_form.publish', defaultMessage: 'Toot' } }); -const ComposeForm = React.createClass({ - - propTypes: { - intl: React.PropTypes.object.isRequired, - text: React.PropTypes.string.isRequired, - suggestion_token: React.PropTypes.string, - suggestions: ImmutablePropTypes.list, - spoiler: React.PropTypes.bool, - privacy: React.PropTypes.string, - spoiler_text: React.PropTypes.string, - focusDate: React.PropTypes.instanceOf(Date), - preselectDate: React.PropTypes.instanceOf(Date), - is_submitting: React.PropTypes.bool, - is_uploading: React.PropTypes.bool, - me: React.PropTypes.number, - needsPrivacyWarning: React.PropTypes.bool, - mentionedDomains: React.PropTypes.array.isRequired, - onChange: React.PropTypes.func.isRequired, - onSubmit: React.PropTypes.func.isRequired, - onClearSuggestions: React.PropTypes.func.isRequired, - onFetchSuggestions: React.PropTypes.func.isRequired, - onSuggestionSelected: React.PropTypes.func.isRequired, - onChangeSpoilerText: React.PropTypes.func.isRequired, - onPaste: React.PropTypes.func.isRequired, - onPickEmoji: React.PropTypes.func.isRequired - }, - - mixins: [PureRenderMixin], +class ComposeForm extends React.PureComponent { + + constructor (props, context) { + super(props, context); + this.handleChange = this.handleChange.bind(this); + this.handleKeyDown = this.handleKeyDown.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(this); + this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(this); + this.onSuggestionSelected = this.onSuggestionSelected.bind(this); + this.handleChangeSpoilerText = this.handleChangeSpoilerText.bind(this); + this.setAutosuggestTextarea = this.setAutosuggestTextarea.bind(this); + this.handleEmojiPick = this.handleEmojiPick.bind(this); + } handleChange (e) { this.props.onChange(e.target.value); - }, + } handleKeyDown (e) { if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) { this.props.onSubmit(); } - }, + } handleSubmit () { this.autosuggestTextarea.textarea.style.height = "auto"; this.props.onSubmit(); - }, + } onSuggestionsClearRequested () { this.props.onClearSuggestions(); - }, + } @debounce(500) onSuggestionsFetchRequested (token) { this.props.onFetchSuggestions(token); - }, + } onSuggestionSelected (tokenStart, token, value) { this._restoreCaret = null; this.props.onSuggestionSelected(tokenStart, token, value); - }, + } handleChangeSpoilerText (e) { this.props.onChangeSpoilerText(e.target.value); - }, + } componentWillReceiveProps (nextProps) { // If this is the update where we've finished uploading, @@ -90,7 +76,7 @@ const ComposeForm = React.createClass({ if (!nextProps.is_uploading && this.props.is_uploading) { this._restoreCaret = this.autosuggestTextarea.textarea.selectionStart; } - }, + } componentDidUpdate (prevProps) { // This statement does several things: @@ -117,17 +103,17 @@ const ComposeForm = React.createClass({ this.autosuggestTextarea.textarea.setSelectionRange(selectionStart, selectionEnd); this.autosuggestTextarea.textarea.focus(); } - }, + } setAutosuggestTextarea (c) { this.autosuggestTextarea = c; - }, + } handleEmojiPick (data) { const position = this.autosuggestTextarea.textarea.selectionStart; this._restoreCaret = position + data.shortname.length + 1; this.props.onPickEmoji(position, data); - }, + } render () { const { intl, needsPrivacyWarning, mentionedDomains, onPaste } = this.props; @@ -207,6 +193,31 @@ const ComposeForm = React.createClass({ ); } -}); +} + +ComposeForm.propTypes = { + intl: PropTypes.object.isRequired, + text: PropTypes.string.isRequired, + suggestion_token: PropTypes.string, + suggestions: ImmutablePropTypes.list, + spoiler: PropTypes.bool, + privacy: PropTypes.string, + spoiler_text: PropTypes.string, + focusDate: PropTypes.instanceOf(Date), + preselectDate: PropTypes.instanceOf(Date), + is_submitting: PropTypes.bool, + is_uploading: PropTypes.bool, + me: PropTypes.number, + needsPrivacyWarning: PropTypes.bool, + mentionedDomains: PropTypes.array.isRequired, + onChange: PropTypes.func.isRequired, + onSubmit: PropTypes.func.isRequired, + onClearSuggestions: PropTypes.func.isRequired, + onFetchSuggestions: PropTypes.func.isRequired, + onSuggestionSelected: PropTypes.func.isRequired, + onChangeSpoilerText: PropTypes.func.isRequired, + onPaste: PropTypes.func.isRequired, + onPickEmoji: PropTypes.func.isRequired +}; export default injectIntl(ComposeForm); diff --git a/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx b/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx @@ -1,6 +1,6 @@ import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown'; import EmojiPicker from 'emojione-picker'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; const messages = defineMessages({ @@ -19,23 +19,22 @@ const style = { top: '5px' }; -const EmojiPickerDropdown = React.createClass({ +class EmojiPickerDropdown extends React.PureComponent { - propTypes: { - intl: React.PropTypes.object.isRequired, - onPickEmoji: React.PropTypes.func.isRequired - }, - - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.setRef = this.setRef.bind(this); + this.handleChange = this.handleChange.bind(this); + } setRef (c) { this.dropdown = c; - }, + } handleChange (data) { this.dropdown.hide(); this.props.onPickEmoji(data); - }, + } render () { const { intl } = this.props; @@ -53,6 +52,11 @@ const EmojiPickerDropdown = React.createClass({ ); } -}); +} + +EmojiPickerDropdown.propTypes = { + intl: PropTypes.object.isRequired, + onPickEmoji: PropTypes.func.isRequired +}; export default injectIntl(EmojiPickerDropdown); diff --git a/app/assets/javascripts/components/features/compose/components/navigation_bar.jsx b/app/assets/javascripts/components/features/compose/components/navigation_bar.jsx @@ -1,4 +1,3 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; import ImmutablePropTypes from 'react-immutable-proptypes'; import Avatar from '../../../components/avatar'; import IconButton from '../../../components/icon_button'; @@ -7,12 +6,7 @@ import Permalink from '../../../components/permalink'; import { FormattedMessage } from 'react-intl'; import { Link } from 'react-router'; -const NavigationBar = React.createClass({ - propTypes: { - account: ImmutablePropTypes.map.isRequired - }, - - mixins: [PureRenderMixin], +class NavigationBar extends React.PureComponent { render () { return ( @@ -27,6 +21,10 @@ const NavigationBar = React.createClass({ ); } -}); +} + +NavigationBar.propTypes = { + account: ImmutablePropTypes.map.isRequired +}; export default NavigationBar; diff --git a/app/assets/javascripts/components/features/compose/components/privacy_dropdown.jsx b/app/assets/javascripts/components/features/compose/components/privacy_dropdown.jsx @@ -1,4 +1,4 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import { injectIntl, defineMessages } from 'react-intl'; import IconButton from '../../../components/icon_button'; @@ -19,51 +19,48 @@ const iconStyle = { height: null }; -const PrivacyDropdown = React.createClass({ +class PrivacyDropdown extends React.PureComponent { - propTypes: { - value: React.PropTypes.string.isRequired, - onChange: React.PropTypes.func.isRequired, - intl: React.PropTypes.object.isRequired - }, - - getInitialState () { - return { + constructor (props, context) { + super(props, context); + this.state = { open: false }; - }, - - mixins: [PureRenderMixin], + this.handleToggle = this.handleToggle.bind(this); + this.handleClick = this.handleClick.bind(this); + this.onGlobalClick = this.onGlobalClick.bind(this); + this.setRef = this.setRef.bind(this); + } handleToggle () { this.setState({ open: !this.state.open }); - }, + } handleClick (value, e) { e.preventDefault(); this.setState({ open: false }); this.props.onChange(value); - }, + } onGlobalClick (e) { if (e.target !== this.node && !this.node.contains(e.target) && this.state.open) { this.setState({ open: false }); } - }, + } componentDidMount () { window.addEventListener('click', this.onGlobalClick); window.addEventListener('touchstart', this.onGlobalClick); - }, + } componentWillUnmount () { window.removeEventListener('click', this.onGlobalClick); window.removeEventListener('touchstart', this.onGlobalClick); - }, + } setRef (c) { this.node = c; - }, + } render () { const { value, onChange, intl } = this.props; @@ -96,6 +93,12 @@ const PrivacyDropdown = React.createClass({ ); } -}); +} + +PrivacyDropdown.propTypes = { + value: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired +}; export default injectIntl(PrivacyDropdown); diff --git a/app/assets/javascripts/components/features/compose/components/reply_indicator.jsx b/app/assets/javascripts/components/features/compose/components/reply_indicator.jsx @@ -1,5 +1,5 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; import Avatar from '../../../components/avatar'; import IconButton from '../../../components/icon_button'; import DisplayName from '../../../components/display_name'; @@ -10,30 +10,24 @@ const messages = defineMessages({ cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' } }); -const ReplyIndicator = React.createClass({ +class ReplyIndicator extends React.PureComponent { - contextTypes: { - router: React.PropTypes.object - }, - - propTypes: { - status: ImmutablePropTypes.map, - onCancel: React.PropTypes.func.isRequired, - intl: React.PropTypes.object.isRequired - }, - - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.handleClick = this.handleClick.bind(this); + this.handleAccountClick = this.handleAccountClick.bind(this); + } handleClick () { this.props.onCancel(); - }, + } handleAccountClick (e) { if (e.button === 0) { e.preventDefault(); this.context.router.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); } - }, + } render () { const { status, intl } = this.props; @@ -60,6 +54,16 @@ const ReplyIndicator = React.createClass({ ); } -}); +} + +ReplyIndicator.contextTypes = { + router: PropTypes.object +}; + +ReplyIndicator.propTypes = { + status: ImmutablePropTypes.map, + onCancel: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired +}; export default injectIntl(ReplyIndicator); diff --git a/app/assets/javascripts/components/features/compose/components/search.jsx b/app/assets/javascripts/components/features/compose/components/search.jsx @@ -1,48 +1,43 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; const messages = defineMessages({ placeholder: { id: 'search.placeholder', defaultMessage: 'Search' } }); -const Search = React.createClass({ +class Search extends React.PureComponent { - propTypes: { - value: React.PropTypes.string.isRequired, - submitted: React.PropTypes.bool, - onChange: React.PropTypes.func.isRequired, - onSubmit: React.PropTypes.func.isRequired, - onClear: React.PropTypes.func.isRequired, - onShow: React.PropTypes.func.isRequired, - intl: React.PropTypes.object.isRequired - }, - - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.handleChange = this.handleChange.bind(this); + this.handleKeyDown = this.handleKeyDown.bind(this); + this.handleFocus = this.handleFocus.bind(this); + } handleChange (e) { this.props.onChange(e.target.value); - }, + } handleClear (e) { e.preventDefault(); this.props.onClear(); - }, + } handleKeyDown (e) { if (e.key === 'Enter') { e.preventDefault(); this.props.onSubmit(); } - }, + } noop () { - }, + } handleFocus () { this.props.onShow(); - }, + } render () { const { intl, value, submitted } = this.props; @@ -68,6 +63,16 @@ const Search = React.createClass({ ); } -}); +} + +Search.propTypes = { + value: PropTypes.string.isRequired, + submitted: PropTypes.bool, + onChange: PropTypes.func.isRequired, + onSubmit: PropTypes.func.isRequired, + onClear: PropTypes.func.isRequired, + onShow: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired +}; export default injectIntl(Search); diff --git a/app/assets/javascripts/components/features/compose/components/search_results.jsx b/app/assets/javascripts/components/features/compose/components/search_results.jsx @@ -1,17 +1,10 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import AccountContainer from '../../../containers/account_container'; import StatusContainer from '../../../containers/status_container'; import { Link } from 'react-router'; -const SearchResults = React.createClass({ - - propTypes: { - results: ImmutablePropTypes.map.isRequired - }, - - mixins: [PureRenderMixin], +class SearchResults extends React.PureComponent { render () { const { results } = this.props; @@ -63,6 +56,10 @@ const SearchResults = React.createClass({ ); } -}); +} + +SearchResults.propTypes = { + results: ImmutablePropTypes.map.isRequired +}; export default SearchResults; diff --git a/app/assets/javascripts/components/features/compose/components/text_icon_button.jsx b/app/assets/javascripts/components/features/compose/components/text_icon_button.jsx @@ -1,20 +1,16 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; -const TextIconButton = React.createClass({ +class TextIconButton extends React.PureComponent { - propTypes: { - label: React.PropTypes.string.isRequired, - title: React.PropTypes.string, - active: React.PropTypes.bool, - onClick: React.PropTypes.func.isRequired - }, - - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.handleClick = this.handleClick.bind(this); + } handleClick (e) { e.preventDefault(); this.props.onClick(); - }, + } render () { const { label, title, active } = this.props; @@ -26,6 +22,13 @@ const TextIconButton = React.createClass({ ); } -}); +} + +TextIconButton.propTypes = { + label: PropTypes.string.isRequired, + title: PropTypes.string, + active: PropTypes.bool, + onClick: PropTypes.func.isRequired +}; export default TextIconButton; diff --git a/app/assets/javascripts/components/features/compose/components/upload_button.jsx b/app/assets/javascripts/components/features/compose/components/upload_button.jsx @@ -1,5 +1,5 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; import IconButton from '../../../components/icon_button'; +import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; const messages = defineMessages({ @@ -11,31 +11,28 @@ const iconStyle = { height: null }; -const UploadButton = React.createClass({ +class UploadButton extends React.PureComponent { - propTypes: { - disabled: React.PropTypes.bool, - onSelectFile: React.PropTypes.func.isRequired, - style: React.PropTypes.object, - resetFileKey: React.PropTypes.number, - intl: React.PropTypes.object.isRequired - }, - - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.handleChange = this.handleChange.bind(this); + this.handleClick = this.handleClick.bind(this); + this.setRef = this.setRef.bind(this); + } handleChange (e) { if (e.target.files.length > 0) { this.props.onSelectFile(e.target.files); } - }, + } handleClick () { this.fileElement.click(); - }, + } setRef (c) { this.fileElement = c; - }, + } render () { const { intl, resetFileKey, disabled } = this.props; @@ -48,6 +45,14 @@ const UploadButton = React.createClass({ ); } -}); +} + +UploadButton.propTypes = { + disabled: PropTypes.bool, + onSelectFile: PropTypes.func.isRequired, + style: PropTypes.object, + resetFileKey: PropTypes.number, + intl: PropTypes.object.isRequired +}; export default injectIntl(UploadButton); diff --git a/app/assets/javascripts/components/features/compose/components/upload_form.jsx b/app/assets/javascripts/components/features/compose/components/upload_form.jsx @@ -1,5 +1,5 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; import IconButton from '../../../components/icon_button'; import { defineMessages, injectIntl } from 'react-intl'; import UploadProgressContainer from '../containers/upload_progress_container'; @@ -9,15 +9,7 @@ const messages = defineMessages({ undo: { id: 'upload_form.undo', defaultMessage: 'Undo' } }); -const UploadForm = React.createClass({ - - propTypes: { - media: ImmutablePropTypes.list.isRequired, - onRemoveFile: React.PropTypes.func.isRequired, - intl: React.PropTypes.object.isRequired - }, - - mixins: [PureRenderMixin], +class UploadForm extends React.PureComponent { render () { const { intl, media } = this.props; @@ -42,6 +34,12 @@ const UploadForm = React.createClass({ ); } -}); +} + +UploadForm.propTypes = { + media: ImmutablePropTypes.list.isRequired, + onRemoveFile: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired +}; export default injectIntl(UploadForm); diff --git a/app/assets/javascripts/components/features/compose/components/upload_progress.jsx b/app/assets/javascripts/components/features/compose/components/upload_progress.jsx @@ -1,15 +1,8 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import { Motion, spring } from 'react-motion'; import { FormattedMessage } from 'react-intl'; -const UploadProgress = React.createClass({ - - propTypes: { - active: React.PropTypes.bool, - progress: React.PropTypes.number - }, - - mixins: [PureRenderMixin], +class UploadProgress extends React.PureComponent { render () { const { active, progress } = this.props; @@ -39,6 +32,11 @@ const UploadProgress = React.createClass({ ); } -}); +} + +UploadProgress.propTypes = { + active: PropTypes.bool, + progress: PropTypes.number +}; export default UploadProgress; diff --git a/app/assets/javascripts/components/features/compose/containers/sensitive_button_container.jsx b/app/assets/javascripts/components/features/compose/containers/sensitive_button_container.jsx @@ -1,4 +1,5 @@ import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; import TextIconButton from '../components/text_icon_button'; import { changeComposeSensitivity } from '../../../actions/compose'; import { Motion, spring } from 'react-motion'; @@ -21,14 +22,7 @@ const mapDispatchToProps = dispatch => ({ }); -const SensitiveButton = React.createClass({ - - propTypes: { - visible: React.PropTypes.bool, - active: React.PropTypes.bool, - onClick: React.PropTypes.func.isRequired, - intl: React.PropTypes.object.isRequired - }, +class SensitiveButton extends React.PureComponent { render () { const { visible, active, onClick, intl } = this.props; @@ -44,6 +38,13 @@ const SensitiveButton = React.createClass({ ); } -}); +} + +SensitiveButton.propTypes = { + visible: PropTypes.bool, + active: PropTypes.bool, + onClick: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired +}; export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(SensitiveButton)); diff --git a/app/assets/javascripts/components/features/compose/index.jsx b/app/assets/javascripts/components/features/compose/index.jsx @@ -1,7 +1,7 @@ import ComposeFormContainer from './containers/compose_form_container'; import UploadFormContainer from './containers/upload_form_container'; import NavigationContainer from './containers/navigation_container'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { mountCompose, unmountCompose } from '../../actions/compose'; import { Link } from 'react-router'; @@ -22,24 +22,15 @@ const mapStateToProps = state => ({ showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']) }); -const Compose = React.createClass({ - - propTypes: { - dispatch: React.PropTypes.func.isRequired, - withHeader: React.PropTypes.bool, - showSearch: React.PropTypes.bool, - intl: React.PropTypes.object.isRequired - }, - - mixins: [PureRenderMixin], +class Compose extends React.PureComponent { componentDidMount () { this.props.dispatch(mountCompose()); - }, + } componentWillUnmount () { this.props.dispatch(unmountCompose()); - }, + } render () { const { withHeader, showSearch, intl } = this.props; @@ -82,6 +73,13 @@ const Compose = React.createClass({ ); } -}); +} + +Compose.propTypes = { + dispatch: PropTypes.func.isRequired, + withHeader: PropTypes.bool, + showSearch: PropTypes.bool, + intl: PropTypes.object.isRequired +}; export default connect(mapStateToProps)(injectIntl(Compose)); diff --git a/app/assets/javascripts/components/features/favourited_statuses/index.jsx b/app/assets/javascripts/components/features/favourited_statuses/index.jsx @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import LoadingIndicator from '../../components/loading_indicator'; import { fetchFavouritedStatuses, expandFavouritedStatuses } from '../../actions/favourites'; @@ -18,26 +18,20 @@ const mapStateToProps = state => ({ me: state.getIn(['meta', 'me']) }); -const Favourites = React.createClass({ +class Favourites extends React.PureComponent { - propTypes: { - params: React.PropTypes.object.isRequired, - dispatch: React.PropTypes.func.isRequired, - statusIds: ImmutablePropTypes.list.isRequired, - loaded: React.PropTypes.bool, - intl: React.PropTypes.object.isRequired, - me: React.PropTypes.number.isRequired - }, - - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.handleScrollToBottom = this.handleScrollToBottom.bind(this); + } componentWillMount () { this.props.dispatch(fetchFavouritedStatuses()); - }, + } handleScrollToBottom () { this.props.dispatch(expandFavouritedStatuses()); - }, + } render () { const { statusIds, loaded, intl, me } = this.props; @@ -58,6 +52,15 @@ const Favourites = React.createClass({ ); } -}); +} + +Favourites.propTypes = { + params: PropTypes.object.isRequired, + dispatch: PropTypes.func.isRequired, + statusIds: ImmutablePropTypes.list.isRequired, + loaded: PropTypes.bool, + intl: PropTypes.object.isRequired, + me: PropTypes.number.isRequired +}; export default connect(mapStateToProps)(injectIntl(Favourites)); diff --git a/app/assets/javascripts/components/features/favourites/index.jsx b/app/assets/javascripts/components/features/favourites/index.jsx @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import LoadingIndicator from '../../components/loading_indicator'; import { fetchFavourites } from '../../actions/interactions'; @@ -12,25 +12,17 @@ const mapStateToProps = (state, props) => ({ accountIds: state.getIn(['user_lists', 'favourited_by', Number(props.params.statusId)]) }); -const Favourites = React.createClass({ - - propTypes: { - params: React.PropTypes.object.isRequired, - dispatch: React.PropTypes.func.isRequired, - accountIds: ImmutablePropTypes.list - }, - - mixins: [PureRenderMixin], +class Favourites extends React.PureComponent { componentWillMount () { this.props.dispatch(fetchFavourites(Number(this.props.params.statusId))); - }, + } componentWillReceiveProps(nextProps) { if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { this.props.dispatch(fetchFavourites(Number(nextProps.params.statusId))); } - }, + } render () { const { accountIds } = this.props; @@ -56,6 +48,12 @@ const Favourites = React.createClass({ ); } -}); +} + +Favourites.propTypes = { + params: PropTypes.object.isRequired, + dispatch: PropTypes.func.isRequired, + accountIds: ImmutablePropTypes.list +}; export default connect(mapStateToProps)(Favourites); diff --git a/app/assets/javascripts/components/features/follow_requests/components/account_authorize.jsx b/app/assets/javascripts/components/features/follow_requests/components/account_authorize.jsx @@ -1,3 +1,4 @@ +import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import Permalink from '../../../components/permalink'; import Avatar from '../../../components/avatar'; @@ -50,9 +51,9 @@ const AccountAuthorize = ({ intl, account, onAuthorize, onReject }) => { AccountAuthorize.propTypes = { account: ImmutablePropTypes.map.isRequired, - onAuthorize: React.PropTypes.func.isRequired, - onReject: React.PropTypes.func.isRequired, - intl: React.PropTypes.object.isRequired + onAuthorize: PropTypes.func.isRequired, + onReject: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired }; export default injectIntl(AccountAuthorize); diff --git a/app/assets/javascripts/components/features/follow_requests/index.jsx b/app/assets/javascripts/components/features/follow_requests/index.jsx @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import LoadingIndicator from '../../components/loading_indicator'; import { ScrollContainer } from 'react-router-scroll'; @@ -17,19 +17,16 @@ const mapStateToProps = state => ({ accountIds: state.getIn(['user_lists', 'follow_requests', 'items']) }); -const FollowRequests = React.createClass({ - propTypes: { - params: React.PropTypes.object.isRequired, - dispatch: React.PropTypes.func.isRequired, - accountIds: ImmutablePropTypes.list, - intl: React.PropTypes.object.isRequired - }, +class FollowRequests extends React.PureComponent { - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.handleScroll = this.handleScroll.bind(this); + } componentWillMount () { this.props.dispatch(fetchFollowRequests()); - }, + } handleScroll (e) { const { scrollTop, scrollHeight, clientHeight } = e.target; @@ -37,7 +34,7 @@ const FollowRequests = React.createClass({ if (scrollTop === scrollHeight - clientHeight) { this.props.dispatch(expandFollowRequests()); } - }, + } render () { const { intl, accountIds } = this.props; @@ -63,6 +60,13 @@ const FollowRequests = React.createClass({ </Column> ); } -}); +} + +FollowRequests.propTypes = { + params: PropTypes.object.isRequired, + dispatch: PropTypes.func.isRequired, + accountIds: ImmutablePropTypes.list, + intl: PropTypes.object.isRequired +}; export default connect(mapStateToProps)(injectIntl(FollowRequests)); diff --git a/app/assets/javascripts/components/features/followers/index.jsx b/app/assets/javascripts/components/features/followers/index.jsx @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import LoadingIndicator from '../../components/loading_indicator'; import { @@ -18,27 +18,25 @@ const mapStateToProps = (state, props) => ({ accountIds: state.getIn(['user_lists', 'followers', Number(props.params.accountId), 'items']) }); -const Followers = React.createClass({ +class Followers extends React.PureComponent { - propTypes: { - params: React.PropTypes.object.isRequired, - dispatch: React.PropTypes.func.isRequired, - accountIds: ImmutablePropTypes.list - }, - - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.handleScroll = this.handleScroll.bind(this); + this.handleLoadMore = this.handleLoadMore.bind(this); + } componentWillMount () { this.props.dispatch(fetchAccount(Number(this.props.params.accountId))); this.props.dispatch(fetchFollowers(Number(this.props.params.accountId))); - }, + } componentWillReceiveProps(nextProps) { if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) { this.props.dispatch(fetchAccount(Number(nextProps.params.accountId))); this.props.dispatch(fetchFollowers(Number(nextProps.params.accountId))); } - }, + } handleScroll (e) { const { scrollTop, scrollHeight, clientHeight } = e.target; @@ -46,12 +44,12 @@ const Followers = React.createClass({ if (scrollTop === scrollHeight - clientHeight) { this.props.dispatch(expandFollowers(Number(this.props.params.accountId))); } - }, + } handleLoadMore (e) { e.preventDefault(); this.props.dispatch(expandFollowers(Number(this.props.params.accountId))); - }, + } render () { const { accountIds } = this.props; @@ -81,6 +79,12 @@ const Followers = React.createClass({ ); } -}); +} + +Followers.propTypes = { + params: PropTypes.object.isRequired, + dispatch: PropTypes.func.isRequired, + accountIds: ImmutablePropTypes.list +}; export default connect(mapStateToProps)(Followers); diff --git a/app/assets/javascripts/components/features/following/index.jsx b/app/assets/javascripts/components/features/following/index.jsx @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import LoadingIndicator from '../../components/loading_indicator'; import { @@ -18,27 +18,25 @@ const mapStateToProps = (state, props) => ({ accountIds: state.getIn(['user_lists', 'following', Number(props.params.accountId), 'items']) }); -const Following = React.createClass({ +class Following extends React.PureComponent { - propTypes: { - params: React.PropTypes.object.isRequired, - dispatch: React.PropTypes.func.isRequired, - accountIds: ImmutablePropTypes.list - }, - - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.handleScroll = this.handleScroll.bind(this); + this.handleLoadMore = this.handleLoadMore.bind(this); + } componentWillMount () { this.props.dispatch(fetchAccount(Number(this.props.params.accountId))); this.props.dispatch(fetchFollowing(Number(this.props.params.accountId))); - }, + } componentWillReceiveProps(nextProps) { if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) { this.props.dispatch(fetchAccount(Number(nextProps.params.accountId))); this.props.dispatch(fetchFollowing(Number(nextProps.params.accountId))); } - }, + } handleScroll (e) { const { scrollTop, scrollHeight, clientHeight } = e.target; @@ -46,12 +44,12 @@ const Following = React.createClass({ if (scrollTop === scrollHeight - clientHeight) { this.props.dispatch(expandFollowing(Number(this.props.params.accountId))); } - }, + } handleLoadMore (e) { e.preventDefault(); this.props.dispatch(expandFollowing(Number(this.props.params.accountId))); - }, + } render () { const { accountIds } = this.props; @@ -81,6 +79,12 @@ const Following = React.createClass({ ); } -}); +} + +Following.propTypes = { + params: PropTypes.object.isRequired, + dispatch: PropTypes.func.isRequired, + accountIds: ImmutablePropTypes.list +}; export default connect(mapStateToProps)(Following); diff --git a/app/assets/javascripts/components/features/getting_started/index.jsx b/app/assets/javascripts/components/features/getting_started/index.jsx @@ -3,6 +3,7 @@ import ColumnLink from '../ui/components/column_link'; import { Link } from 'react-router'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; const messages = defineMessages({ @@ -53,7 +54,7 @@ const GettingStarted = ({ intl, me }) => { }; GettingStarted.propTypes = { - intl: React.PropTypes.object.isRequired, + intl: PropTypes.object.isRequired, me: ImmutablePropTypes.map.isRequired }; diff --git a/app/assets/javascripts/components/features/hashtag_timeline/index.jsx b/app/assets/javascripts/components/features/hashtag_timeline/index.jsx @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import StatusListContainer from '../ui/containers/status_list_container'; import Column from '../ui/components/column'; import { @@ -17,17 +17,7 @@ const mapStateToProps = state => ({ accessToken: state.getIn(['meta', 'access_token']) }); -const HashtagTimeline = React.createClass({ - - propTypes: { - params: React.PropTypes.object.isRequired, - dispatch: React.PropTypes.func.isRequired, - streamingAPIBaseURL: React.PropTypes.string.isRequired, - accessToken: React.PropTypes.string.isRequired, - hasUnread: React.PropTypes.bool - }, - - mixins: [PureRenderMixin], +class HashtagTimeline extends React.PureComponent { _subscribe (dispatch, id) { const { streamingAPIBaseURL, accessToken } = this.props; @@ -46,14 +36,14 @@ const HashtagTimeline = React.createClass({ } }); - }, + } _unsubscribe () { if (typeof this.subscription !== 'undefined') { this.subscription.close(); this.subscription = null; } - }, + } componentDidMount () { const { dispatch } = this.props; @@ -61,7 +51,7 @@ const HashtagTimeline = React.createClass({ dispatch(refreshTimeline('tag', id)); this._subscribe(dispatch, id); - }, + } componentWillReceiveProps (nextProps) { if (nextProps.params.id !== this.props.params.id) { @@ -69,11 +59,11 @@ const HashtagTimeline = React.createClass({ this._unsubscribe(); this._subscribe(this.props.dispatch, nextProps.params.id); } - }, + } componentWillUnmount () { this._unsubscribe(); - }, + } render () { const { id, hasUnread } = this.props.params; @@ -84,8 +74,16 @@ const HashtagTimeline = React.createClass({ <StatusListContainer type='tag' id={id} emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />} /> </Column> ); - }, + } -}); +} + +HashtagTimeline.propTypes = { + params: PropTypes.object.isRequired, + dispatch: PropTypes.func.isRequired, + streamingAPIBaseURL: PropTypes.string.isRequired, + accessToken: PropTypes.string.isRequired, + hasUnread: PropTypes.bool +}; export default connect(mapStateToProps)(HashtagTimeline); diff --git a/app/assets/javascripts/components/features/home_timeline/components/column_settings.jsx b/app/assets/javascripts/components/features/home_timeline/components/column_settings.jsx @@ -1,4 +1,4 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ColumnCollapsable from '../../../components/column_collapsable'; @@ -25,16 +25,7 @@ const rowStyle = { }; -const ColumnSettings = React.createClass({ - - propTypes: { - settings: ImmutablePropTypes.map.isRequired, - onChange: React.PropTypes.func.isRequired, - onSave: React.PropTypes.func.isRequired, - intl: React.PropTypes.object.isRequired - }, - - mixins: [PureRenderMixin], +class ColumnSettings extends React.PureComponent { render () { const { settings, onChange, onSave, intl } = this.props; @@ -62,6 +53,13 @@ const ColumnSettings = React.createClass({ ); } -}); +} + +ColumnSettings.propTypes = { + settings: ImmutablePropTypes.map.isRequired, + onChange: PropTypes.func.isRequired, + onSave: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired +} export default injectIntl(ColumnSettings); diff --git a/app/assets/javascripts/components/features/home_timeline/components/setting_text.jsx b/app/assets/javascripts/components/features/home_timeline/components/setting_text.jsx @@ -1,3 +1,4 @@ +import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; const style = { @@ -9,18 +10,16 @@ const style = { width: '100%' }; -const SettingText = React.createClass({ +class SettingText extends React.PureComponent { - propTypes: { - settings: ImmutablePropTypes.map.isRequired, - settingKey: React.PropTypes.array.isRequired, - label: React.PropTypes.string.isRequired, - onChange: React.PropTypes.func.isRequired - }, + constructor (props, context) { + super(props, context); + this.handleChange = this.handleChange.bind(this); + } handleChange (e) { this.props.onChange(this.props.settingKey, e.target.value) - }, + } render () { const { settings, settingKey, label } = this.props; @@ -36,6 +35,13 @@ const SettingText = React.createClass({ ); } -}); +} + +SettingText.propTypes = { + settings: ImmutablePropTypes.map.isRequired, + settingKey: PropTypes.array.isRequired, + label: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired +}; export default SettingText; diff --git a/app/assets/javascripts/components/features/home_timeline/index.jsx b/app/assets/javascripts/components/features/home_timeline/index.jsx @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import StatusListContainer from '../ui/containers/status_list_container'; import Column from '../ui/components/column'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; @@ -14,14 +14,7 @@ const mapStateToProps = state => ({ hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0 }); -const HomeTimeline = React.createClass({ - - propTypes: { - intl: React.PropTypes.object.isRequired, - hasUnread: React.PropTypes.bool - }, - - mixins: [PureRenderMixin], +class HomeTimeline extends React.PureComponent { render () { const { intl, hasUnread } = this.props; @@ -32,8 +25,13 @@ const HomeTimeline = React.createClass({ <StatusListContainer {...this.props} type='home' emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage="You aren't following anyone yet. Visit {public} or use search to get started and meet other users." values={{ public: <Link to='/timelines/public'><FormattedMessage id='empty_column.home.public_timeline' defaultMessage='the public timeline' /></Link> }} />} /> </Column> ); - }, + } -}); +} + +HomeTimeline.propTypes = { + intl: PropTypes.object.isRequired, + hasUnread: PropTypes.bool +}; export default connect(mapStateToProps)(injectIntl(HomeTimeline)); diff --git a/app/assets/javascripts/components/features/mutes/index.jsx b/app/assets/javascripts/components/features/mutes/index.jsx @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import LoadingIndicator from '../../components/loading_indicator'; import { ScrollContainer } from 'react-router-scroll'; @@ -17,19 +17,16 @@ const mapStateToProps = state => ({ accountIds: state.getIn(['user_lists', 'mutes', 'items']) }); -const Mutes = React.createClass({ - propTypes: { - params: React.PropTypes.object.isRequired, - dispatch: React.PropTypes.func.isRequired, - accountIds: ImmutablePropTypes.list, - intl: React.PropTypes.object.isRequired - }, +class Mutes extends React.PureComponent { - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.handleScroll = this.handleScroll.bind(this); + } componentWillMount () { this.props.dispatch(fetchMutes()); - }, + } handleScroll (e) { const { scrollTop, scrollHeight, clientHeight } = e.target; @@ -37,7 +34,7 @@ const Mutes = React.createClass({ if (scrollTop === scrollHeight - clientHeight) { this.props.dispatch(expandMutes()); } - }, + } render () { const { intl, accountIds } = this.props; @@ -63,6 +60,13 @@ const Mutes = React.createClass({ </Column> ); } -}); +} + +Mutes.propTypes = { + params: PropTypes.object.isRequired, + dispatch: PropTypes.func.isRequired, + accountIds: ImmutablePropTypes.list, + intl: PropTypes.object.isRequired +}; export default connect(mapStateToProps)(injectIntl(Mutes)); diff --git a/app/assets/javascripts/components/features/notifications/components/clear_column_button.jsx b/app/assets/javascripts/components/features/notifications/components/clear_column_button.jsx @@ -1,15 +1,11 @@ +import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; const messages = defineMessages({ clear: { id: 'notifications.clear', defaultMessage: 'Clear notifications' } }); -const ClearColumnButton = React.createClass({ - - propTypes: { - onClick: React.PropTypes.func.isRequired, - intl: React.PropTypes.object.isRequired - }, +class ClearColumnButton extends React.Component { render () { const { intl } = this.props; @@ -20,6 +16,11 @@ const ClearColumnButton = React.createClass({ </div> ); } -}) +} + +ClearColumnButton.propTypes = { + onClick: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired +}; export default injectIntl(ClearColumnButton); diff --git a/app/assets/javascripts/components/features/notifications/components/column_settings.jsx b/app/assets/javascripts/components/features/notifications/components/column_settings.jsx @@ -1,4 +1,4 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import ColumnCollapsable from '../../../components/column_collapsable'; @@ -23,18 +23,7 @@ const rowStyle = { }; -const ColumnSettings = React.createClass({ - - propTypes: { - settings: ImmutablePropTypes.map.isRequired, - onChange: React.PropTypes.func.isRequired, - onSave: React.PropTypes.func.isRequired, - intl: React.PropTypes.shape({ - formatMessage: React.PropTypes.func.isRequired - }).isRequired - }, - - mixins: [PureRenderMixin], +class ColumnSettings extends React.PureComponent { render () { const { settings, intl, onChange, onSave } = this.props; @@ -82,6 +71,15 @@ const ColumnSettings = React.createClass({ ); } -}); +} + +ColumnSettings.propTypes = { + settings: ImmutablePropTypes.map.isRequired, + onChange: PropTypes.func.isRequired, + onSave: PropTypes.func.isRequired, + intl: PropTypes.shape({ + formatMessage: PropTypes.func.isRequired + }).isRequired +}; export default injectIntl(ColumnSettings); diff --git a/app/assets/javascripts/components/features/notifications/components/notification.jsx b/app/assets/javascripts/components/features/notifications/components/notification.jsx @@ -1,4 +1,3 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; import ImmutablePropTypes from 'react-immutable-proptypes'; import StatusContainer from '../../../containers/status_container'; import AccountContainer from '../../../containers/account_container'; @@ -11,13 +10,7 @@ const linkStyle = { fontWeight: '500' }; -const Notification = React.createClass({ - - propTypes: { - notification: ImmutablePropTypes.map.isRequired - }, - - mixins: [PureRenderMixin], +class Notification extends React.PureComponent { renderFollow (account, link) { return ( @@ -33,11 +26,11 @@ const Notification = React.createClass({ <AccountContainer id={account.get('id')} withNote={false} /> </div> ); - }, + } renderMention (notification) { return <StatusContainer id={notification.get('status')} />; - }, + } renderFavourite (notification, link) { return ( @@ -53,7 +46,7 @@ const Notification = React.createClass({ <StatusContainer id={notification.get('status')} muted={true} /> </div> ); - }, + } renderReblog (notification, link) { return ( @@ -69,7 +62,7 @@ const Notification = React.createClass({ <StatusContainer id={notification.get('status')} muted={true} /> </div> ); - }, + } render () { // eslint-disable-line consistent-return const { notification } = this.props; @@ -90,6 +83,10 @@ const Notification = React.createClass({ } } -}); +} + +Notification.propTypes = { + notification: ImmutablePropTypes.map.isRequired +}; export default Notification; diff --git a/app/assets/javascripts/components/features/notifications/components/setting_toggle.jsx b/app/assets/javascripts/components/features/notifications/components/setting_toggle.jsx @@ -1,3 +1,4 @@ +import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import Toggle from 'react-toggle'; @@ -23,10 +24,10 @@ const SettingToggle = ({ settings, settingKey, label, onChange, htmlFor = '' }) SettingToggle.propTypes = { settings: ImmutablePropTypes.map.isRequired, - settingKey: React.PropTypes.array.isRequired, - label: React.PropTypes.node.isRequired, - onChange: React.PropTypes.func.isRequired, - htmlFor: React.PropTypes.string + settingKey: PropTypes.array.isRequired, + label: PropTypes.node.isRequired, + onChange: PropTypes.func.isRequired, + htmlFor: PropTypes.string }; export default SettingToggle; diff --git a/app/assets/javascripts/components/features/notifications/index.jsx b/app/assets/javascripts/components/features/notifications/index.jsx @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import Column from '../ui/components/column'; import { expandNotifications, clearNotifications, scrollTopNotifications } from '../../actions/notifications'; @@ -28,24 +28,15 @@ const mapStateToProps = state => ({ isUnread: state.getIn(['notifications', 'unread']) > 0 }); -const Notifications = React.createClass({ +class Notifications extends React.PureComponent { - propTypes: { - notifications: ImmutablePropTypes.list.isRequired, - dispatch: React.PropTypes.func.isRequired, - trackScroll: React.PropTypes.bool, - intl: React.PropTypes.object.isRequired, - isLoading: React.PropTypes.bool, - isUnread: React.PropTypes.bool - }, - - getDefaultProps () { - return { - trackScroll: true - }; - }, - - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.handleScroll = this.handleScroll.bind(this); + this.handleLoadMore = this.handleLoadMore.bind(this); + this.handleClear = this.handleClear.bind(this); + this.setRef = this.setRef.bind(this); + } handleScroll (e) { const { scrollTop, scrollHeight, clientHeight } = e.target; @@ -59,28 +50,28 @@ const Notifications = React.createClass({ } else { this.props.dispatch(scrollTopNotifications(false)); } - }, + } componentDidUpdate (prevProps) { if (this.node.scrollTop > 0 && (prevProps.notifications.size < this.props.notifications.size && prevProps.notifications.first() !== this.props.notifications.first() && !!this._oldScrollPosition)) { this.node.scrollTop = this.node.scrollHeight - this._oldScrollPosition; } - }, + } handleLoadMore (e) { e.preventDefault(); this.props.dispatch(expandNotifications()); - }, + } handleClear () { if (window.confirm(this.props.intl.formatMessage(messages.confirm))) { this.props.dispatch(clearNotifications()); } - }, + } setRef (c) { this.node = c; - }, + } render () { const { intl, notifications, trackScroll, isLoading, isUnread } = this.props; @@ -137,6 +128,19 @@ const Notifications = React.createClass({ } } -}); +} + +Notifications.propTypes = { + notifications: ImmutablePropTypes.list.isRequired, + dispatch: PropTypes.func.isRequired, + trackScroll: PropTypes.bool, + intl: PropTypes.object.isRequired, + isLoading: PropTypes.bool, + isUnread: PropTypes.bool +}; + +Notifications.defaultProps = { + trackScroll: true +}; export default connect(mapStateToProps)(injectIntl(Notifications)); diff --git a/app/assets/javascripts/components/features/public_timeline/index.jsx b/app/assets/javascripts/components/features/public_timeline/index.jsx @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import StatusListContainer from '../ui/containers/status_list_container'; import Column from '../ui/components/column'; import { @@ -25,17 +25,7 @@ const mapStateToProps = state => ({ let subscription; -const PublicTimeline = React.createClass({ - - propTypes: { - dispatch: React.PropTypes.func.isRequired, - intl: React.PropTypes.object.isRequired, - streamingAPIBaseURL: React.PropTypes.string.isRequired, - accessToken: React.PropTypes.string.isRequired, - hasUnread: React.PropTypes.bool - }, - - mixins: [PureRenderMixin], +class PublicTimeline extends React.PureComponent { componentDidMount () { const { dispatch, streamingAPIBaseURL, accessToken } = this.props; @@ -72,14 +62,14 @@ const PublicTimeline = React.createClass({ } }); - }, + } componentWillUnmount () { // if (typeof subscription !== 'undefined') { // subscription.close(); // subscription = null; // } - }, + } render () { const { intl, hasUnread } = this.props; @@ -90,8 +80,16 @@ const PublicTimeline = React.createClass({ <StatusListContainer type='public' 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> ); - }, + } -}); +} + +PublicTimeline.propTypes = { + dispatch: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + streamingAPIBaseURL: PropTypes.string.isRequired, + accessToken: PropTypes.string.isRequired, + hasUnread: PropTypes.bool +}; export default connect(mapStateToProps)(injectIntl(PublicTimeline)); diff --git a/app/assets/javascripts/components/features/reblogs/index.jsx b/app/assets/javascripts/components/features/reblogs/index.jsx @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import LoadingIndicator from '../../components/loading_indicator'; import { fetchReblogs } from '../../actions/interactions'; @@ -12,25 +12,17 @@ const mapStateToProps = (state, props) => ({ accountIds: state.getIn(['user_lists', 'reblogged_by', Number(props.params.statusId)]) }); -const Reblogs = React.createClass({ - - propTypes: { - params: React.PropTypes.object.isRequired, - dispatch: React.PropTypes.func.isRequired, - accountIds: ImmutablePropTypes.list - }, - - mixins: [PureRenderMixin], +class Reblogs extends React.PureComponent { componentWillMount () { this.props.dispatch(fetchReblogs(Number(this.props.params.statusId))); - }, + } componentWillReceiveProps(nextProps) { if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { this.props.dispatch(fetchReblogs(Number(nextProps.params.statusId))); } - }, + } render () { const { accountIds } = this.props; @@ -56,6 +48,12 @@ const Reblogs = React.createClass({ ); } -}); +} + +Reblogs.propTypes = { + params: PropTypes.object.isRequired, + dispatch: PropTypes.func.isRequired, + accountIds: ImmutablePropTypes.list +}; export default connect(mapStateToProps)(Reblogs); diff --git a/app/assets/javascripts/components/features/report/components/status_check_box.jsx b/app/assets/javascripts/components/features/report/components/status_check_box.jsx @@ -1,18 +1,9 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import emojify from '../../../emoji'; import Toggle from 'react-toggle'; -const StatusCheckBox = React.createClass({ - - propTypes: { - status: ImmutablePropTypes.map.isRequired, - checked: React.PropTypes.bool, - onToggle: React.PropTypes.func.isRequired, - disabled: React.PropTypes.bool - }, - - mixins: [PureRenderMixin], +class StatusCheckBox extends React.PureComponent { render () { const { status, checked, onToggle, disabled } = this.props; @@ -37,6 +28,13 @@ const StatusCheckBox = React.createClass({ ); } -}); +} + +StatusCheckBox.propTypes = { + status: ImmutablePropTypes.map.isRequired, + checked: PropTypes.bool, + onToggle: PropTypes.func.isRequired, + disabled: PropTypes.bool +}; export default StatusCheckBox; diff --git a/app/assets/javascripts/components/features/report/index.jsx b/app/assets/javascripts/components/features/report/index.jsx @@ -1,7 +1,7 @@ import { connect } from 'react-redux'; import { cancelReport, changeReportComment, submitReport } from '../../actions/reports'; import { fetchAccountTimeline } from '../../actions/accounts'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import Column from '../ui/components/column'; import Button from '../../components/button'; @@ -38,28 +38,19 @@ const textareaStyle = { marginBottom: '10px' }; -const Report = React.createClass({ +class Report extends React.PureComponent { - contextTypes: { - router: React.PropTypes.object - }, - - propTypes: { - isSubmitting: React.PropTypes.bool, - account: ImmutablePropTypes.map, - statusIds: ImmutablePropTypes.orderedSet.isRequired, - comment: React.PropTypes.string.isRequired, - dispatch: React.PropTypes.func.isRequired, - intl: React.PropTypes.object.isRequired - }, - - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.handleCommentChange = this.handleCommentChange.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + } componentWillMount () { if (!this.props.account) { this.context.router.replace('/'); } - }, + } componentDidMount () { if (!this.props.account) { @@ -67,22 +58,22 @@ const Report = React.createClass({ } this.props.dispatch(fetchAccountTimeline(this.props.account.get('id'))); - }, + } componentWillReceiveProps (nextProps) { if (this.props.account !== nextProps.account && nextProps.account) { this.props.dispatch(fetchAccountTimeline(nextProps.account.get('id'))); } - }, + } handleCommentChange (e) { this.props.dispatch(changeReportComment(e.target.value)); - }, + } handleSubmit () { this.props.dispatch(submitReport()); this.context.router.replace('/'); - }, + } render () { const { account, comment, intl, statusIds, isSubmitting } = this.props; @@ -126,6 +117,19 @@ const Report = React.createClass({ ); } -}); +} + +Report.contextTypes = { + router: PropTypes.object +}; + +Report.propTypes = { + isSubmitting: PropTypes.bool, + account: ImmutablePropTypes.map, + statusIds: ImmutablePropTypes.orderedSet.isRequired, + comment: PropTypes.string.isRequired, + dispatch: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired +}; export default connect(makeMapStateToProps)(injectIntl(Report)); diff --git a/app/assets/javascripts/components/features/status/components/action_bar.jsx b/app/assets/javascripts/components/features/status/components/action_bar.jsx @@ -1,4 +1,4 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import IconButton from '../../../components/icon_button'; import ImmutablePropTypes from 'react-immutable-proptypes'; import DropdownMenu from '../../../components/dropdown_menu'; @@ -13,50 +13,42 @@ const messages = defineMessages({ report: { id: 'status.report', defaultMessage: 'Report @{name}' } }); -const ActionBar = React.createClass({ +class ActionBar extends React.PureComponent { - contextTypes: { - router: React.PropTypes.object - }, - - propTypes: { - status: ImmutablePropTypes.map.isRequired, - onReply: React.PropTypes.func.isRequired, - onReblog: React.PropTypes.func.isRequired, - onFavourite: React.PropTypes.func.isRequired, - onDelete: React.PropTypes.func.isRequired, - onMention: React.PropTypes.func.isRequired, - onReport: React.PropTypes.func, - me: React.PropTypes.number.isRequired, - intl: React.PropTypes.object.isRequired - }, - - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.handleReplyClick = this.handleReplyClick.bind(this); + this.handleReblogClick = this.handleReblogClick.bind(this); + this.handleFavouriteClick = this.handleFavouriteClick.bind(this); + this.handleDeleteClick = this.handleDeleteClick.bind(this); + this.handleMentionClick = this.handleMentionClick.bind(this); + this.handleReport = this.handleReport.bind(this); + } handleReplyClick () { this.props.onReply(this.props.status); - }, + } handleReblogClick (e) { this.props.onReblog(this.props.status, e); - }, + } handleFavouriteClick () { this.props.onFavourite(this.props.status); - }, + } handleDeleteClick () { this.props.onDelete(this.props.status); - }, + } handleMentionClick () { this.props.onMention(this.props.status.get('account'), this.context.router); - }, + } handleReport () { this.props.onReport(this.props.status); this.context.router.push('/report'); - }, + } render () { const { status, me, intl } = this.props; @@ -85,6 +77,22 @@ const ActionBar = React.createClass({ ); } -}); +} + +ActionBar.contextTypes = { + router: PropTypes.object +}; + +ActionBar.propTypes = { + status: ImmutablePropTypes.map.isRequired, + onReply: PropTypes.func.isRequired, + onReblog: PropTypes.func.isRequired, + onFavourite: PropTypes.func.isRequired, + onDelete: PropTypes.func.isRequired, + onMention: PropTypes.func.isRequired, + onReport: PropTypes.func, + me: PropTypes.number.isRequired, + intl: PropTypes.object.isRequired +}; export default injectIntl(ActionBar); diff --git a/app/assets/javascripts/components/features/status/components/card.jsx b/app/assets/javascripts/components/features/status/components/card.jsx @@ -1,4 +1,3 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; import ImmutablePropTypes from 'react-immutable-proptypes'; const contentStyle = { @@ -28,12 +27,7 @@ const getHostname = url => { return parser.hostname; }; -const Card = React.createClass({ - propTypes: { - card: ImmutablePropTypes.map - }, - - mixins: [PureRenderMixin], +class Card extends React.PureComponent { render () { const { card } = this.props; @@ -64,6 +58,10 @@ const Card = React.createClass({ </a> ); } -}); +} + +Card.propTypes = { + card: ImmutablePropTypes.map +}; export default Card; diff --git a/app/assets/javascripts/components/features/status/components/detailed_status.jsx b/app/assets/javascripts/components/features/status/components/detailed_status.jsx @@ -1,4 +1,4 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import Avatar from '../../../components/avatar'; import DisplayName from '../../../components/display_name'; @@ -10,20 +10,12 @@ import { Link } from 'react-router'; import { FormattedDate, FormattedNumber } from 'react-intl'; import CardContainer from '../containers/card_container'; -const DetailedStatus = React.createClass({ +class DetailedStatus extends React.PureComponent { - contextTypes: { - router: React.PropTypes.object - }, - - propTypes: { - status: ImmutablePropTypes.map.isRequired, - onOpenMedia: React.PropTypes.func.isRequired, - onOpenVideo: React.PropTypes.func.isRequired, - autoPlayGif: React.PropTypes.bool, - }, - - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.handleAccountClick = this.handleAccountClick.bind(this); + } handleAccountClick (e) { if (e.button === 0) { @@ -32,7 +24,7 @@ const DetailedStatus = React.createClass({ } e.stopPropagation(); - }, + } render () { const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status; @@ -74,6 +66,17 @@ const DetailedStatus = React.createClass({ ); } -}); +} + +DetailedStatus.contextTypes = { + router: PropTypes.object +}; + +DetailedStatus.propTypes = { + status: ImmutablePropTypes.map.isRequired, + onOpenMedia: PropTypes.func.isRequired, + onOpenVideo: PropTypes.func.isRequired, + autoPlayGif: PropTypes.bool, +}; export default DetailedStatus; diff --git a/app/assets/javascripts/components/features/status/index.jsx b/app/assets/javascripts/components/features/status/index.jsx @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { fetchStatus } from '../../actions/statuses'; import Immutable from 'immutable'; @@ -46,33 +46,30 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -const Status = React.createClass({ - contextTypes: { - router: React.PropTypes.object - }, - - propTypes: { - params: React.PropTypes.object.isRequired, - dispatch: React.PropTypes.func.isRequired, - status: ImmutablePropTypes.map, - ancestorsIds: ImmutablePropTypes.list, - descendantsIds: ImmutablePropTypes.list, - me: React.PropTypes.number, - boostModal: React.PropTypes.bool, - autoPlayGif: React.PropTypes.bool - }, - - mixins: [PureRenderMixin], +class Status extends React.PureComponent { + + constructor (props, context) { + super(props, context); + this.handleFavouriteClick = this.handleFavouriteClick.bind(this); + this.handleReplyClick = this.handleReplyClick.bind(this); + this.handleModalReblog = this.handleModalReblog.bind(this); + this.handleReblogClick = this.handleReblogClick.bind(this); + this.handleDeleteClick = this.handleDeleteClick.bind(this); + this.handleMentionClick = this.handleMentionClick.bind(this); + this.handleOpenMedia = this.handleOpenMedia.bind(this); + this.handleOpenVideo = this.handleOpenVideo.bind(this); + this.handleReport = this.handleReport.bind(this); + } componentWillMount () { this.props.dispatch(fetchStatus(Number(this.props.params.statusId))); - }, + } componentWillReceiveProps (nextProps) { if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) { this.props.dispatch(fetchStatus(Number(nextProps.params.statusId))); } - }, + } handleFavouriteClick (status) { if (status.get('favourited')) { @@ -80,15 +77,15 @@ const Status = React.createClass({ } else { this.props.dispatch(favourite(status)); } - }, + } handleReplyClick (status) { this.props.dispatch(replyCompose(status, this.context.router)); - }, + } handleModalReblog (status) { this.props.dispatch(reblog(status)); - }, + } handleReblogClick (status, e) { if (status.get('reblogged')) { @@ -100,31 +97,31 @@ const Status = React.createClass({ this.props.dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog })); } } - }, + } handleDeleteClick (status) { this.props.dispatch(deleteStatus(status.get('id'))); - }, + } handleMentionClick (account, router) { this.props.dispatch(mentionCompose(account, router)); - }, + } handleOpenMedia (media, index) { this.props.dispatch(openModal('MEDIA', { media, index })); - }, + } handleOpenVideo (media, time) { this.props.dispatch(openModal('VIDEO', { media, time })); - }, + } handleReport (status) { this.props.dispatch(initReport(status.get('account'), status)); - }, + } renderChildren (list) { return list.map(id => <StatusContainer key={id} id={id} />); - }, + } render () { let ancestors, descendants; @@ -167,6 +164,21 @@ const Status = React.createClass({ ); } -}); +} + +Status.contextTypes = { + router: PropTypes.object +}; + +Status.propTypes = { + params: PropTypes.object.isRequired, + dispatch: PropTypes.func.isRequired, + status: ImmutablePropTypes.map, + ancestorsIds: ImmutablePropTypes.list, + descendantsIds: ImmutablePropTypes.list, + me: PropTypes.number, + boostModal: PropTypes.bool, + autoPlayGif: PropTypes.bool +}; export default connect(makeMapStateToProps)(Status); diff --git a/app/assets/javascripts/components/features/ui/components/boost_modal.jsx b/app/assets/javascripts/components/features/ui/components/boost_modal.jsx @@ -1,5 +1,5 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import IconButton from '../../../components/icon_button'; import Button from '../../../components/button'; @@ -12,24 +12,18 @@ const messages = defineMessages({ reblog: { id: 'status.reblog', defaultMessage: 'Boost' } }); -const BoostModal = React.createClass({ - contextTypes: { - router: React.PropTypes.object - }, +class BoostModal extends React.PureComponent { - propTypes: { - status: ImmutablePropTypes.map.isRequired, - onReblog: React.PropTypes.func.isRequired, - onClose: React.PropTypes.func.isRequired, - intl: React.PropTypes.object.isRequired - }, - - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.handleReblog = this.handleReblog.bind(this); + this.handleAccountClick = this.handleAccountClick.bind(this); + } handleReblog() { this.props.onReblog(this.props.status); this.props.onClose(); - }, + } handleAccountClick (e) { if (e.button === 0) { @@ -37,7 +31,7 @@ const BoostModal = React.createClass({ this.props.onClose(); this.context.router.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); } - }, + } render () { const { status, intl, onClose } = this.props; @@ -72,6 +66,17 @@ const BoostModal = React.createClass({ ); } -}); +} + +BoostModal.contextTypes = { + router: PropTypes.object +}; + +BoostModal.propTypes = { + status: ImmutablePropTypes.map.isRequired, + onReblog: PropTypes.func.isRequired, + onClose: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired +}; export default injectIntl(BoostModal); diff --git a/app/assets/javascripts/components/features/ui/components/column.jsx b/app/assets/javascripts/components/features/ui/components/column.jsx @@ -1,5 +1,5 @@ import ColumnHeader from './column_header'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; const easingOutQuint = (x, t, b, c, d) => c*((t=t/d-1)*t*t*t*t + 1) + b; @@ -29,17 +29,13 @@ const scrollTop = (node) => { }; }; -const Column = React.createClass({ +class Column extends React.PureComponent { - propTypes: { - heading: React.PropTypes.string, - icon: React.PropTypes.string, - children: React.PropTypes.node, - active: React.PropTypes.bool, - hideHeadingOnMobile: React.PropTypes.bool - }, - - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.handleHeaderClick = this.handleHeaderClick.bind(this); + this.handleWheel = this.handleWheel.bind(this); + } handleHeaderClick () { const scrollable = ReactDOM.findDOMNode(this).querySelector('.scrollable'); @@ -47,13 +43,13 @@ const Column = React.createClass({ return; } this._interruptScrollAnimation = scrollTop(scrollable); - }, + } handleWheel () { if (typeof this._interruptScrollAnimation !== 'undefined') { this._interruptScrollAnimation(); } - }, + } render () { const { heading, icon, children, active, hideHeadingOnMobile } = this.props; @@ -72,6 +68,14 @@ const Column = React.createClass({ ); } -}); +} + +Column.propTypes = { + heading: PropTypes.string, + icon: PropTypes.string, + children: PropTypes.node, + active: PropTypes.bool, + hideHeadingOnMobile: PropTypes.bool +}; export default Column; diff --git a/app/assets/javascripts/components/features/ui/components/column_header.jsx b/app/assets/javascripts/components/features/ui/components/column_header.jsx @@ -1,20 +1,15 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types' -const ColumnHeader = React.createClass({ +class ColumnHeader extends React.PureComponent { - propTypes: { - icon: React.PropTypes.string, - type: React.PropTypes.string, - active: React.PropTypes.bool, - onClick: React.PropTypes.func, - hideOnMobile: React.PropTypes.bool - }, - - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.handleClick = this.handleClick.bind(this); + } handleClick () { this.props.onClick(); - }, + } render () { const { type, active, hideOnMobile } = this.props; @@ -33,6 +28,14 @@ const ColumnHeader = React.createClass({ ); } -}); +} + +ColumnHeader.propTypes = { + icon: PropTypes.string, + type: PropTypes.string, + active: PropTypes.bool, + onClick: PropTypes.func, + hideOnMobile: PropTypes.bool +}; export default ColumnHeader; diff --git a/app/assets/javascripts/components/features/ui/components/column_link.jsx b/app/assets/javascripts/components/features/ui/components/column_link.jsx @@ -1,3 +1,4 @@ +import PropTypes from 'prop-types'; import { Link } from 'react-router'; const outerStyle = { @@ -30,12 +31,12 @@ const ColumnLink = ({ icon, text, to, href, method, hideOnMobile }) => { }; ColumnLink.propTypes = { - icon: React.PropTypes.string.isRequired, - text: React.PropTypes.string.isRequired, - to: React.PropTypes.string, - href: React.PropTypes.string, - method: React.PropTypes.string, - hideOnMobile: React.PropTypes.bool + icon: PropTypes.string.isRequired, + text: PropTypes.string.isRequired, + to: PropTypes.string, + href: PropTypes.string, + method: PropTypes.string, + hideOnMobile: PropTypes.bool }; export default ColumnLink; diff --git a/app/assets/javascripts/components/features/ui/components/columns_area.jsx b/app/assets/javascripts/components/features/ui/components/columns_area.jsx @@ -1,4 +1,4 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; const style = { display: 'flex', @@ -6,13 +6,7 @@ const style = { overflowX: 'auto' }; -const ColumnsArea = React.createClass({ - - propTypes: { - children: React.PropTypes.node - }, - - mixins: [PureRenderMixin], +class ColumnsArea extends React.PureComponent { render () { return ( @@ -22,6 +16,10 @@ const ColumnsArea = React.createClass({ ); } -}); +} + +ColumnsArea.propTypes = { + children: PropTypes.node +}; export default ColumnsArea; diff --git a/app/assets/javascripts/components/features/ui/components/media_modal.jsx b/app/assets/javascripts/components/features/ui/components/media_modal.jsx @@ -1,6 +1,6 @@ import LoadingIndicator from '../../../components/loading_indicator'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; import ExtendedVideoPlayer from '../../../components/extended_video_player'; import ImageLoader from 'react-imageloader'; import { defineMessages, injectIntl } from 'react-intl'; @@ -44,30 +44,25 @@ const closeStyle = { right: '4px' }; -const MediaModal = React.createClass({ +class MediaModal extends React.PureComponent { - propTypes: { - media: ImmutablePropTypes.list.isRequired, - index: React.PropTypes.number.isRequired, - onClose: React.PropTypes.func.isRequired, - intl: React.PropTypes.object.isRequired - }, - - getInitialState () { - return { + constructor (props, context) { + super(props, context); + this.state = { index: null }; - }, - - mixins: [PureRenderMixin], + this.handleNextClick = this.handleNextClick.bind(this); + this.handlePrevClick = this.handlePrevClick.bind(this); + this.handleKeyUp = this.handleKeyUp.bind(this); + } handleNextClick () { this.setState({ index: (this.getIndex() + 1) % this.props.media.size}); - }, + } handlePrevClick () { this.setState({ index: (this.getIndex() - 1) % this.props.media.size}); - }, + } handleKeyUp (e) { switch(e.key) { @@ -78,19 +73,19 @@ const MediaModal = React.createClass({ this.handleNextClick(); break; } - }, + } componentDidMount () { window.addEventListener('keyup', this.handleKeyUp, false); - }, + } componentWillUnmount () { window.removeEventListener('keyup', this.handleKeyUp); - }, + } getIndex () { return this.state.index !== null ? this.state.index : this.props.index; - }, + } render () { const { media, intl, onClose } = this.props; @@ -128,6 +123,13 @@ const MediaModal = React.createClass({ ); } -}); +} + +MediaModal.propTypes = { + media: ImmutablePropTypes.list.isRequired, + index: PropTypes.number.isRequired, + onClose: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired +}; export default injectIntl(MediaModal); diff --git a/app/assets/javascripts/components/features/ui/components/modal_root.jsx b/app/assets/javascripts/components/features/ui/components/modal_root.jsx @@ -1,4 +1,4 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import MediaModal from './media_modal'; import OnboardingModal from './onboarding_modal'; import VideoModal from './video_modal'; @@ -12,37 +12,34 @@ const MODAL_COMPONENTS = { 'BOOST': BoostModal }; -const ModalRoot = React.createClass({ +class ModalRoot extends React.PureComponent { - propTypes: { - type: React.PropTypes.string, - props: React.PropTypes.object, - onClose: React.PropTypes.func.isRequired - }, - - mixins: [PureRenderMixin], + constructor (props, context) { + super(props, context); + this.handleKeyUp = this.handleKeyUp.bind(this); + } handleKeyUp (e) { if (e.key === 'Escape' && !!this.props.type) { this.props.onClose(); } - }, + } componentDidMount () { window.addEventListener('keyup', this.handleKeyUp, false); - }, + } componentWillUnmount () { window.removeEventListener('keyup', this.handleKeyUp); - }, + } willEnter () { return { opacity: 0, scale: 0.98 }; - }, + } willLeave () { return { opacity: spring(0), scale: spring(0.98) }; - }, + } render () { const { type, props, onClose } = this.props; @@ -81,6 +78,12 @@ const ModalRoot = React.createClass({ ); } -}); +} + +ModalRoot.propTypes = { + type: PropTypes.string, + props: PropTypes.object, + onClose: PropTypes.func.isRequired +}; export default ModalRoot; diff --git a/app/assets/javascripts/components/features/ui/components/onboarding_modal.jsx b/app/assets/javascripts/components/features/ui/components/onboarding_modal.jsx @@ -1,5 +1,5 @@ import { connect } from 'react-redux'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import Permalink from '../../../components/permalink'; @@ -32,8 +32,8 @@ const PageOne = ({ acct, domain }) => ( ); PageOne.propTypes = { - acct: React.PropTypes.string.isRequired, - domain: React.PropTypes.string.isRequired + acct: PropTypes.string.isRequired, + domain: PropTypes.string.isRequired }; const PageTwo = () => ( @@ -85,7 +85,7 @@ const PageThree = ({ me, domain }) => ( PageThree.propTypes = { me: ImmutablePropTypes.map.isRequired, - domain: React.PropTypes.string.isRequired + domain: PropTypes.string.isRequired }; const PageFour = ({ domain, intl }) => ( @@ -119,8 +119,8 @@ const PageFour = ({ domain, intl }) => ( ); PageFour.propTypes = { - domain: React.PropTypes.string.isRequired, - intl: React.PropTypes.object.isRequired + domain: PropTypes.string.isRequired, + intl: PropTypes.object.isRequired }; const PageSix = ({ admin }) => { @@ -157,33 +157,27 @@ const mapStateToProps = state => ({ domain: state.getIn(['meta', 'domain']) }); -const OnboardingModal = React.createClass({ +class OnboardingModal extends React.PureComponent { - propTypes: { - onClose: React.PropTypes.func.isRequired, - intl: React.PropTypes.object.isRequired, - me: ImmutablePropTypes.map.isRequired, - domain: React.PropTypes.string.isRequired, - admin: ImmutablePropTypes.map - }, - - getInitialState () { - return { + constructor (props, context) { + super(props, context); + this.state = { currentIndex: 0 }; - }, - - mixins: [PureRenderMixin], + this.handleSkip = this.handleSkip.bind(this); + this.handleDot = this.handleDot.bind(this); + this.handleNext = this.handleNext.bind(this); + } handleSkip (e) { e.preventDefault(); this.props.onClose(); - }, + } handleDot (i, e) { e.preventDefault(); this.setState({ currentIndex: i }); - }, + } handleNext (maxNum, e) { e.preventDefault(); @@ -193,7 +187,7 @@ const OnboardingModal = React.createClass({ } else { this.props.onClose(); } - }, + } render () { const { me, admin, domain, intl } = this.props; @@ -251,6 +245,14 @@ const OnboardingModal = React.createClass({ ); } -}); +} + +OnboardingModal.propTypes = { + onClose: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + me: ImmutablePropTypes.map.isRequired, + domain: PropTypes.string.isRequired, + admin: ImmutablePropTypes.map +} export default connect(mapStateToProps)(injectIntl(OnboardingModal)); diff --git a/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx b/app/assets/javascripts/components/features/ui/components/tabs_bar.jsx @@ -1,7 +1,7 @@ import { Link } from 'react-router'; import { FormattedMessage } from 'react-intl'; -const TabsBar = React.createClass({ +class TabsBar extends React.PureComponent { render () { return ( @@ -18,6 +18,6 @@ const TabsBar = React.createClass({ ); } -}); +} export default TabsBar; diff --git a/app/assets/javascripts/components/features/ui/components/upload_area.jsx b/app/assets/javascripts/components/features/ui/components/upload_area.jsx @@ -1,14 +1,8 @@ -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import { Motion, spring } from 'react-motion'; import { FormattedMessage } from 'react-intl'; -const UploadArea = React.createClass({ - - propTypes: { - active: React.PropTypes.bool - }, - - mixins: [PureRenderMixin], +class UploadArea extends React.PureComponent { render () { const { active } = this.props; @@ -27,6 +21,10 @@ const UploadArea = React.createClass({ ); } -}); +} + +UploadArea.propTypes = { + active: PropTypes.bool +}; export default UploadArea; diff --git a/app/assets/javascripts/components/features/ui/components/video_modal.jsx b/app/assets/javascripts/components/features/ui/components/video_modal.jsx @@ -1,6 +1,6 @@ import LoadingIndicator from '../../../components/loading_indicator'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; import ExtendedVideoPlayer from '../../../components/extended_video_player'; import { defineMessages, injectIntl } from 'react-intl'; import IconButton from '../../../components/icon_button'; @@ -16,16 +16,7 @@ const closeStyle = { right: '4px' }; -const VideoModal = React.createClass({ - - propTypes: { - media: ImmutablePropTypes.map.isRequired, - time: React.PropTypes.number, - onClose: React.PropTypes.func.isRequired, - intl: React.PropTypes.object.isRequired - }, - - mixins: [PureRenderMixin], +class VideoModal extends React.PureComponent { render () { const { media, intl, time, onClose } = this.props; @@ -42,6 +33,13 @@ const VideoModal = React.createClass({ ); } -}); +} + +VideoModal.propTypes = { + media: ImmutablePropTypes.map.isRequired, + time: PropTypes.number, + onClose: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired +}; export default injectIntl(VideoModal); diff --git a/app/assets/javascripts/components/features/ui/index.jsx b/app/assets/javascripts/components/features/ui/index.jsx @@ -1,6 +1,6 @@ import ColumnsArea from './components/columns_area'; import NotificationsContainer from './containers/notifications_container'; -import PureRenderMixin from 'react-addons-pure-render-mixin'; +import PropTypes from 'prop-types'; import LoadingBarContainer from './containers/loading_bar_container'; import HomeTimeline from '../home_timeline'; import Compose from '../compose'; @@ -15,26 +15,26 @@ import { refreshTimeline } from '../../actions/timelines'; import { refreshNotifications } from '../../actions/notifications'; import UploadArea from './components/upload_area'; -const UI = React.createClass({ +class UI extends React.PureComponent { - propTypes: { - dispatch: React.PropTypes.func.isRequired, - children: React.PropTypes.node - }, - - getInitialState () { - return { + constructor (props, context) { + super(props, context); + this.state = { width: window.innerWidth, draggingOver: false }; - }, - - mixins: [PureRenderMixin], + this.handleResize = this.handleResize.bind(this); + this.handleDragEnter = this.handleDragEnter.bind(this); + this.handleDragOver = this.handleDragOver.bind(this); + this.handleDrop = this.handleDrop.bind(this); + this.handleDragLeave = this.handleDragLeave.bind(this); + this.setRef = this.setRef.bind(this); + } @debounce(500) handleResize () { this.setState({ width: window.innerWidth }); - }, + } handleDragEnter (e) { e.preventDefault(); @@ -50,7 +50,7 @@ const UI = React.createClass({ if (e.dataTransfer && e.dataTransfer.items.length > 0) { this.setState({ draggingOver: true }); } - }, + } handleDragOver (e) { e.preventDefault(); @@ -63,7 +63,7 @@ const UI = React.createClass({ } return false; - }, + } handleDrop (e) { e.preventDefault(); @@ -73,7 +73,7 @@ const UI = React.createClass({ if (e.dataTransfer && e.dataTransfer.files.length === 1) { this.props.dispatch(uploadCompose(e.dataTransfer.files)); } - }, + } handleDragLeave (e) { e.preventDefault(); @@ -86,7 +86,7 @@ const UI = React.createClass({ } this.setState({ draggingOver: false }); - }, + } componentWillMount () { window.addEventListener('resize', this.handleResize, { passive: true }); @@ -97,7 +97,7 @@ const UI = React.createClass({ this.props.dispatch(refreshTimeline('home')); this.props.dispatch(refreshNotifications()); - }, + } componentWillUnmount () { window.removeEventListener('resize', this.handleResize); @@ -105,11 +105,11 @@ const UI = React.createClass({ document.removeEventListener('dragover', this.handleDragOver); document.removeEventListener('drop', this.handleDrop); document.removeEventListener('dragleave', this.handleDragLeave); - }, + } setRef (c) { this.node = c; - }, + } render () { const { width, draggingOver } = this.state; @@ -148,6 +148,11 @@ const UI = React.createClass({ ); } -}); +} + +UI.propTypes = { + dispatch: PropTypes.func.isRequired, + children: PropTypes.node +}; export default connect()(UI); diff --git a/config/application.rb b/config/application.rb @@ -73,7 +73,7 @@ module Mastodon config.middleware.use Rack::Deflater config.browserify_rails.source_map_environments << 'development' - config.browserify_rails.commandline_options = '--transform [ babelify --presets [ es2015 react ] ] --extension=".jsx"' + config.browserify_rails.commandline_options = '--transform [ babelify --presets [ es2015 react ] --plugins [ transform-decorators-legacy ] ] --extension=".jsx"' config.browserify_rails.evaluate_node_modules = true config.to_prepare do diff --git a/package.json b/package.json @@ -28,7 +28,7 @@ "dotenv": "^4.0.0", "emojione": "^2.2.7", "emojione-picker": "^2.0.1", - "enzyme": "^2.7.1", + "enzyme": "^2.8.2", "es6-promise": "^3.2.1", "escape-html": "^1.0.3", "eventsource": "^0.2.1", @@ -41,26 +41,26 @@ "node-sass": "^4.5.2", "npmlog": "^4.0.2", "pg": "^6.1.2", - "react": "^15.4.2", + "prop-types": "^15.5.8", + "react": "^15.5.4", "react-addons-perf": "^15.4.2", - "react-addons-pure-render-mixin": "^15.4.2", - "react-addons-shallow-compare": "^15.4.2", - "react-addons-test-utils": "^15.4.2", + "react-addons-shallow-compare": "^15.5.2", "react-autosuggest": "^7.0.1", "react-decoration": "^1.4.0", - "react-dom": "^15.4.2", + "react-dom": "^15.5.4", "react-imageloader": "^2.1.0", "react-immutable-proptypes": "^2.1.0", "react-intl": "^2.1.5", "react-motion": "^0.4.5", "react-notification": "^6.6.0", "react-proxy": "^1.1.8", - "react-redux": "^5.0.3", + "react-redux": "^5.0.4", "react-redux-loading-bar": "2.4.1", "react-router": "^2.8.0", "react-router-scroll": "^0.3.2", "react-simple-dropdown": "^1.1.4", "react-storybook-addon-intl": "^0.1.0", + "react-test-renderer": "^15.5.4", "react-toggle": "^2.1.1", "redis": "^2.6.5", "redux": "^3.6.0", diff --git a/yarn.lock b/yarn.lock @@ -1254,7 +1254,7 @@ babel-runtime@6.x.x, babel-runtime@^6.0.0, babel-runtime@^6.11.6, babel-runtime@ core-js "^2.4.0" regenerator-runtime "^0.10.0" -babel-template@^6.16.0, babel-template@^6.23.0: +babel-template@^6.16.0, babel-template@^6.22.0, babel-template@^6.23.0, babel-template@^6.3.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.23.0.tgz#04d4f270adbb3aa704a8143ae26faa529238e638" dependencies: @@ -1264,26 +1264,6 @@ babel-template@^6.16.0, babel-template@^6.23.0: babylon "^6.11.0" lodash "^4.2.0" -babel-template@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.22.0.tgz#403d110905a4626b317a2a1fcb8f3b73204b2edb" - dependencies: - babel-runtime "^6.22.0" - babel-traverse "^6.22.0" - babel-types "^6.22.0" - babylon "^6.11.0" - lodash "^4.2.0" - -babel-template@^6.3.0: - version "6.16.0" - resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.16.0.tgz#e149dd1a9f03a35f817ddbc4d0481988e7ebc8ca" - dependencies: - babel-runtime "^6.9.0" - babel-traverse "^6.16.0" - babel-types "^6.16.0" - babylon "^6.11.0" - lodash "^4.2.0" - babel-traverse@^6.16.0, babel-traverse@^6.22.0, babel-traverse@^6.22.1, babel-traverse@^6.23.0, babel-traverse@^6.23.1: version "6.23.1" resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.23.1.tgz#d3cb59010ecd06a97d81310065f966b699e14f48" @@ -1974,6 +1954,13 @@ create-hmac@^1.1.0, create-hmac@^1.1.2: create-hash "^1.1.0" inherits "^2.0.1" +create-react-class@^15.5.1: + version "15.5.2" + resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.5.2.tgz#6a8758348df660b88326a0e764d569f274aad681" + dependencies: + fbjs "^0.8.9" + object-assign "^4.1.1" + cross-spawn@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982" @@ -2409,9 +2396,9 @@ entities@^1.1.1, entities@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" -enzyme@^2.7.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-2.7.1.tgz#76370e1d99e91f73091bb8c4314b7c128cc2d621" +enzyme@^2.8.2: + version "2.8.2" + resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-2.8.2.tgz#6c8bcb05012abc4aa4bc3213fb23780b9b5b1714" dependencies: cheerio "^0.22.0" function.prototype.name "^1.0.0" @@ -2421,6 +2408,7 @@ enzyme@^2.7.1: object.assign "^4.0.4" object.entries "^1.0.3" object.values "^1.0.3" + prop-types "^15.5.4" uuid "^2.0.3" errno@^0.1.3: @@ -2435,16 +2423,7 @@ error-ex@^1.2.0: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.3.2: - version "1.6.1" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.6.1.tgz#bb8a2064120abcf928a086ea3d9043114285ec99" - dependencies: - es-to-primitive "^1.1.1" - function-bind "^1.1.0" - is-callable "^1.1.3" - is-regex "^1.0.3" - -es-abstract@^1.4.3, es-abstract@^1.5.0, es-abstract@^1.5.1, es-abstract@^1.7.0: +es-abstract@^1.3.2, es-abstract@^1.4.3, es-abstract@^1.5.0, es-abstract@^1.5.1, es-abstract@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.7.0.tgz#dfade774e01bfcd97f96180298c449c8623fb94c" dependencies: @@ -2762,16 +2741,16 @@ fastparse@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8" -fbjs@^0.8.1, fbjs@^0.8.4: - version "0.8.5" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.5.tgz#f69ba8a876096cb1b9bffe4d7c1e71c19d39d008" +fbjs@^0.8.4, fbjs@^0.8.9: + version "0.8.12" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.12.tgz#10b5d92f76d45575fd63a217d4ea02bea2f8ed04" dependencies: core-js "^1.0.0" - immutable "^3.7.6" isomorphic-fetch "^2.1.1" loose-envify "^1.0.0" object-assign "^4.1.0" promise "^7.1.1" + setimmediate "^1.0.5" ua-parser-js "^0.7.9" figures@^1.3.5: @@ -3221,7 +3200,7 @@ ignore@^3.2.0: version "3.2.7" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.2.7.tgz#4810ca5f1d8eca5595213a34b94f2eb4ed926bbd" -immutable@^3.7.6, immutable@^3.8.1: +immutable@^3.8.1: version "3.8.1" resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.1.tgz#200807f11ab0f72710ea485542de088075f68cd2" @@ -4373,6 +4352,10 @@ object-assign@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + object-is@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.1.tgz#0aa60ec9989a0b3ed795cf4d06f62cf1ad6539b6" @@ -5017,6 +5000,12 @@ promise@^7.1.1: dependencies: asap "~2.0.3" +prop-types@^15.0.0, prop-types@^15.5.4, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@~15.5.7: + version "15.5.8" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.8.tgz#6b7b2e141083be38c8595aa51fc55775c7199394" + dependencies: + fbjs "^0.8.9" + proxy-addr@~1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.3.tgz#dc97502f5722e888467b3fa2297a7b1ff47df074" @@ -5131,23 +5120,16 @@ react-addons-perf@^15.4.2: fbjs "^0.8.4" object-assign "^4.1.0" -react-addons-pure-render-mixin@>=0.14.0, react-addons-pure-render-mixin@^15.4.2: +react-addons-pure-render-mixin@>=0.14.0: version "15.4.2" resolved "https://registry.yarnpkg.com/react-addons-pure-render-mixin/-/react-addons-pure-render-mixin-15.4.2.tgz#a8433c71c45e2368503721921dc47bdaf1fbabcd" dependencies: fbjs "^0.8.4" object-assign "^4.1.0" -react-addons-shallow-compare@^15.4.2: - version "15.4.2" - resolved "https://registry.yarnpkg.com/react-addons-shallow-compare/-/react-addons-shallow-compare-15.4.2.tgz#027ffd9720e3a1e0b328dcd8fc62e214a0d174a5" - dependencies: - fbjs "^0.8.4" - object-assign "^4.1.0" - -react-addons-test-utils@^15.4.2: - version "15.4.2" - resolved "https://registry.yarnpkg.com/react-addons-test-utils/-/react-addons-test-utils-15.4.2.tgz#93bcaa718fcae7360d42e8fb1c09756cc36302a2" +react-addons-shallow-compare@^15.5.2: + version "15.5.2" + resolved "https://registry.yarnpkg.com/react-addons-shallow-compare/-/react-addons-shallow-compare-15.5.2.tgz#7cb0ee7acc8d7c93fcc202df0bf47ba916a7bdad" dependencies: fbjs "^0.8.4" object-assign "^4.1.0" @@ -5190,13 +5172,14 @@ react-docgen@^2.12.1: node-dir "^0.1.10" recast "^0.11.5" -react-dom@^15.4.2: - version "15.4.2" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.4.2.tgz#015363f05b0a1fd52ae9efdd3a0060d90695208f" +react-dom@^15.5.4: + version "15.5.4" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.5.4.tgz#ba0c28786fd52ed7e4f2135fe0288d462aef93da" dependencies: - fbjs "^0.8.1" + fbjs "^0.8.9" loose-envify "^1.1.0" object-assign "^4.1.0" + prop-types "~15.5.7" react-element-to-jsx-string@^5.0.0: version "5.0.7" @@ -5299,15 +5282,17 @@ react-redux@^4.4.5: lodash "^4.2.0" loose-envify "^1.1.0" -react-redux@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.3.tgz#86c3b68d56e74294a42e2a740ab66117ef6c019f" +react-redux@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.4.tgz#1563babadcfb2672f57f9ceaa439fb16bf85d55b" dependencies: + create-react-class "^15.5.1" hoist-non-react-statics "^1.0.3" invariant "^2.0.0" lodash "^4.2.0" lodash-es "^4.2.0" loose-envify "^1.1.0" + prop-types "^15.0.0" react-router-scroll@^0.3.2: version "0.3.2" @@ -5350,6 +5335,13 @@ react-stubber@^1.0.0: dependencies: babel-runtime "^6.5.0" +react-test-renderer@^15.5.4: + version "15.5.4" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-15.5.4.tgz#d4ebb23f613d685ea8f5390109c2d20fbf7c83bc" + dependencies: + fbjs "^0.8.9" + object-assign "^4.1.0" + react-themeable@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/react-themeable/-/react-themeable-1.1.0.tgz#7d4466dd9b2b5fa75058727825e9f152ba379a0e" @@ -5372,13 +5364,14 @@ react-virtualized@^8.11.4: dom-helpers "^2.4.0 || ^3.0.0" loose-envify "^1.3.0" -react@^15.4.2: - version "15.4.2" - resolved "https://registry.yarnpkg.com/react/-/react-15.4.2.tgz#41f7991b26185392ba9bae96c8889e7e018397ef" +react@^15.5.4: + version "15.5.4" + resolved "https://registry.yarnpkg.com/react/-/react-15.5.4.tgz#fa83eb01506ab237cdc1c8c3b1cea8de012bf047" dependencies: - fbjs "^0.8.4" + fbjs "^0.8.9" loose-envify "^1.1.0" object-assign "^4.1.0" + prop-types "^15.5.7" read-only-stream@^2.0.0: version "2.0.0" @@ -5791,7 +5784,7 @@ set-immediate-shim@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" -setimmediate@^1.0.4: +setimmediate@^1.0.4, setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"