From d6eacb79c6e94f8b9d962c040b3b709a551e4e52 Mon Sep 17 00:00:00 2001 From: Renaud Chaput <renchap@gmail.com> Date: Thu, 19 Oct 2023 19:44:55 +0200 Subject: [PATCH] [Glitch] Upgrade to react-router v5 Port 1b70d7ed7c0fd3a9fcf028bf76b8c62ac8b3897f to glitch-soc Co-authored-by: Claire <claire.github-309c@sitedethib.com> Signed-off-by: Claire <claire.github-309c@sitedethib.com> --- .../glitch/components/column_back_button.jsx | 21 +++---- .../components/column_back_button_slim.jsx | 20 ++++--- .../glitch/components/column_header.jsx | 23 +++---- .../glitch/components/dropdown_menu.jsx | 20 +++---- .../flavours/glitch/components/modal_root.jsx | 12 ++-- .../glitch/components/navigation_portal.jsx | 36 ----------- .../glitch/components/navigation_portal.tsx | 25 ++++++++ .../flavours/glitch/components/permalink.jsx | 13 ++-- .../flavours/glitch/components/router.tsx | 28 ++++++--- .../glitch/components/scrollable_list.jsx | 37 +++++++++--- .../flavours/glitch/components/status.jsx | 27 ++++----- .../glitch/components/status_action_bar.jsx | 25 ++++---- .../glitch/components/status_content.jsx | 7 ++- .../account/components/featured_tags.jsx | 4 -- .../features/account/components/header.jsx | 13 ++-- .../account_timeline/components/header.jsx | 16 ++--- .../components/moved_note.jsx | 16 ++--- .../features/community_timeline/index.jsx | 1 - .../compose/components/compose_form.jsx | 14 ++--- .../compose/components/reply_indicator.jsx | 12 +--- .../features/compose/components/search.jsx | 40 ++++++------- .../features/compose/components/upload.jsx | 4 -- .../components/conversation.jsx | 22 ++++--- .../glitch/features/directory/index.jsx | 4 -- .../glitch/features/explore/index.jsx | 1 - .../features/follow_recommendations/index.jsx | 14 ++--- .../components/announcements.jsx | 26 ++++---- .../glitch/features/getting_started/index.jsx | 1 - .../features/getting_started_misc/index.jsx | 1 - .../glitch/features/list_timeline/index.jsx | 13 ++-- .../notifications/components/admin_report.jsx | 18 +++--- .../notifications/components/admin_signup.jsx | 18 +++--- .../notifications/components/follow.jsx | 18 +++--- .../components/follow_request.jsx | 13 ++-- .../containers/notification_container.js | 6 +- .../picture_in_picture/components/footer.jsx | 17 +++--- .../glitch/features/public_timeline/index.jsx | 1 - .../features/status/components/action_bar.jsx | 16 ++--- .../status/components/detailed_status.jsx | 25 ++++---- .../flavours/glitch/features/status/index.jsx | 21 ++++--- .../features/ui/components/audio_modal.jsx | 4 -- .../features/ui/components/boost_modal.jsx | 12 ++-- .../features/ui/components/columns_area.jsx | 5 -- .../ui/components/favourite_modal.jsx | 11 ++-- .../features/ui/components/list_panel.jsx | 4 +- .../features/ui/components/media_modal.jsx | 4 -- .../ui/components/navigation_panel.jsx | 3 +- .../flavours/glitch/features/ui/index.jsx | 6 +- .../features/ui/util/react_router_helpers.jsx | 33 ++++------ .../flavours/glitch/utils/dom_helpers.js | 7 --- .../flavours/glitch/utils/react_router.jsx | 60 +++++++++++++++++++ 51 files changed, 413 insertions(+), 385 deletions(-) delete mode 100644 app/javascript/flavours/glitch/components/navigation_portal.jsx create mode 100644 app/javascript/flavours/glitch/components/navigation_portal.tsx delete mode 100644 app/javascript/flavours/glitch/utils/dom_helpers.js create mode 100644 app/javascript/flavours/glitch/utils/react_router.jsx diff --git a/app/javascript/flavours/glitch/components/column_back_button.jsx b/app/javascript/flavours/glitch/components/column_back_button.jsx index 5a11a3d5ea..0562fae29b 100644 --- a/app/javascript/flavours/glitch/components/column_back_button.jsx +++ b/app/javascript/flavours/glitch/components/column_back_button.jsx @@ -4,26 +4,25 @@ import { createPortal } from 'react-dom'; import { FormattedMessage } from 'react-intl'; -import { Icon } from 'flavours/glitch/components/icon'; +import { withRouter } from 'react-router-dom'; +import { Icon } from 'flavours/glitch/components/icon'; +import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; -export default class ColumnBackButton extends PureComponent { - - static contextTypes = { - router: PropTypes.object, - }; +class ColumnBackButton extends PureComponent { static propTypes = { multiColumn: PropTypes.bool, + ...WithRouterPropTypes, }; handleClick = () => { - const { router } = this.context; + const { history } = this.props; - if (router.history.location?.state?.fromMastodon) { - router.history.goBack(); + if (history.location?.state?.fromMastodon) { + history.goBack(); } else { - router.history.push('/'); + history.push('/'); } }; @@ -57,3 +56,5 @@ export default class ColumnBackButton extends PureComponent { } } + +export default withRouter(ColumnBackButton); diff --git a/app/javascript/flavours/glitch/components/column_back_button_slim.jsx b/app/javascript/flavours/glitch/components/column_back_button_slim.jsx index 7b3bac45f9..4e4185387d 100644 --- a/app/javascript/flavours/glitch/components/column_back_button_slim.jsx +++ b/app/javascript/flavours/glitch/components/column_back_button_slim.jsx @@ -1,25 +1,27 @@ -import PropTypes from 'prop-types'; import { PureComponent } from 'react'; import { FormattedMessage } from 'react-intl'; +import { withRouter } from 'react-router-dom'; + import { Icon } from 'flavours/glitch/components/icon'; +import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; -export default class ColumnBackButtonSlim extends PureComponent { +class ColumnBackButtonSlim extends PureComponent { - static contextTypes = { - router: PropTypes.object, + static propTypes = { + ...WithRouterPropTypes, }; handleClick = () => { - const { router } = this.context; + const { location, history } = this.props; // Check if there is a previous page in the app to go back to per https://stackoverflow.com/a/70532858/9703201 // When upgrading to V6, check `location.key !== 'default'` instead per https://github.com/remix-run/history/blob/main/docs/api-reference.md#location - if (router.route.location.key) { - router.history.goBack(); + if (location.key) { + history.goBack(); } else { - router.history.push('/'); + history.push('/'); } }; @@ -35,3 +37,5 @@ export default class ColumnBackButtonSlim extends PureComponent { } } + +export default withRouter(ColumnBackButtonSlim); diff --git a/app/javascript/flavours/glitch/components/column_header.jsx b/app/javascript/flavours/glitch/components/column_header.jsx index 8a68036e9c..fa13f181d0 100644 --- a/app/javascript/flavours/glitch/components/column_header.jsx +++ b/app/javascript/flavours/glitch/components/column_header.jsx @@ -5,8 +5,10 @@ import { createPortal } from 'react-dom'; import { FormattedMessage, injectIntl, defineMessages } from 'react-intl'; import classNames from 'classnames'; +import { withRouter } from 'react-router-dom'; -import { Icon } from 'flavours/glitch/components/icon'; +import { Icon } from 'flavours/glitch/components/icon'; +import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; const messages = defineMessages({ show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' }, @@ -18,7 +20,6 @@ const messages = defineMessages({ class ColumnHeader extends PureComponent { static contextTypes = { - router: PropTypes.object, identity: PropTypes.object, }; @@ -38,6 +39,7 @@ class ColumnHeader extends PureComponent { onClick: PropTypes.func, appendContent: PropTypes.node, collapseIssues: PropTypes.bool, + ...WithRouterPropTypes, }; state = { @@ -63,12 +65,12 @@ class ColumnHeader extends PureComponent { }; handleBackClick = () => { - const { router } = this.context; + const { history } = this.props; - if (router.history.location?.state?.fromMastodon) { - router.history.goBack(); + if (history.location?.state?.fromMastodon) { + history.goBack(); } else { - router.history.push('/'); + history.push('/'); } }; @@ -78,15 +80,14 @@ class ColumnHeader extends PureComponent { handlePin = () => { if (!this.props.pinned) { - this.context.router.history.replace('/'); + this.props.history.replace('/'); } this.props.onPin(); }; render () { - const { router } = this.context; - const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues } = this.props; + const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues, history } = this.props; const { collapsed, animating } = this.state; const wrapperClassName = classNames('column-header__wrapper', { @@ -129,7 +130,7 @@ class ColumnHeader extends PureComponent { pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='plus' /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>; } - if (!pinned && ((multiColumn && router.history.location?.state?.fromMastodon) || showBackButton)) { + if (!pinned && ((multiColumn && history.location?.state?.fromMastodon) || showBackButton)) { backButton = ( <button onClick={this.handleBackClick} className='column-header__back-button'> <Icon id='chevron-left' className='column-back-button__icon' fixedWidth /> @@ -215,4 +216,4 @@ class ColumnHeader extends PureComponent { } -export default injectIntl(ColumnHeader); +export default injectIntl(withRouter(ColumnHeader)); diff --git a/app/javascript/flavours/glitch/components/dropdown_menu.jsx b/app/javascript/flavours/glitch/components/dropdown_menu.jsx index 0416df5d45..9d744cb495 100644 --- a/app/javascript/flavours/glitch/components/dropdown_menu.jsx +++ b/app/javascript/flavours/glitch/components/dropdown_menu.jsx @@ -2,13 +2,16 @@ import PropTypes from 'prop-types'; import { PureComponent, cloneElement, Children } from 'react'; import classNames from 'classnames'; +import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { supportsPassiveEvents } from 'detect-passive-events'; import Overlay from 'react-overlays/Overlay'; -import { CircularProgress } from "./circular_progress"; +import { CircularProgress } from 'flavours/glitch/components/circular_progress'; +import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; + import { IconButton } from './icon_button'; const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true; @@ -16,10 +19,6 @@ let id = 0; class DropdownMenu extends PureComponent { - static contextTypes = { - router: PropTypes.object, - }; - static propTypes = { items: PropTypes.oneOfType([PropTypes.array, ImmutablePropTypes.list]).isRequired, loading: PropTypes.bool, @@ -159,11 +158,7 @@ class DropdownMenu extends PureComponent { } -export default class Dropdown extends PureComponent { - - static contextTypes = { - router: PropTypes.object, - }; +class Dropdown extends PureComponent { static propTypes = { children: PropTypes.node, @@ -183,6 +178,7 @@ export default class Dropdown extends PureComponent { renderItem: PropTypes.func, renderHeader: PropTypes.func, onItemClick: PropTypes.func, + ...WithRouterPropTypes }; static defaultProps = { @@ -250,7 +246,7 @@ export default class Dropdown extends PureComponent { item.action(); } else if (item && item.to) { e.preventDefault(); - this.context.router.history.push(item.to); + this.props.history.push(item.to); } }; @@ -338,3 +334,5 @@ export default class Dropdown extends PureComponent { } } + +export default withRouter(Dropdown); diff --git a/app/javascript/flavours/glitch/components/modal_root.jsx b/app/javascript/flavours/glitch/components/modal_root.jsx index a99c51f924..cebc1040a7 100644 --- a/app/javascript/flavours/glitch/components/modal_root.jsx +++ b/app/javascript/flavours/glitch/components/modal_root.jsx @@ -2,14 +2,13 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; import 'wicg-inert'; + import { multiply } from 'color-blend'; import { createBrowserHistory } from 'history'; -export default class ModalRoot extends PureComponent { +import { WithOptionalRouterPropTypes, withOptionalRouter } from 'flavours/glitch/utils/react_router'; - static contextTypes = { - router: PropTypes.object, - }; +class ModalRoot extends PureComponent { static propTypes = { children: PropTypes.node, @@ -21,6 +20,7 @@ export default class ModalRoot extends PureComponent { }), noEsc: PropTypes.bool, ignoreFocus: PropTypes.bool, + ...WithOptionalRouterPropTypes, }; activeElement = this.props.children ? document.activeElement : null; @@ -56,7 +56,7 @@ export default class ModalRoot extends PureComponent { componentDidMount () { window.addEventListener('keyup', this.handleKeyUp, false); window.addEventListener('keydown', this.handleKeyDown, false); - this.history = this.context.router ? this.context.router.history : createBrowserHistory(); + this.history = this.props.history || createBrowserHistory(); if (this.props.children) { this._handleModalOpen(); @@ -160,3 +160,5 @@ export default class ModalRoot extends PureComponent { } } + +export default withOptionalRouter(ModalRoot); diff --git a/app/javascript/flavours/glitch/components/navigation_portal.jsx b/app/javascript/flavours/glitch/components/navigation_portal.jsx deleted file mode 100644 index e142a3ec60..0000000000 --- a/app/javascript/flavours/glitch/components/navigation_portal.jsx +++ /dev/null @@ -1,36 +0,0 @@ -import { PureComponent } from 'react'; - -import { Switch, Route, withRouter } from 'react-router-dom'; - -import AccountNavigation from 'flavours/glitch/features/account/navigation'; -import Trends from 'flavours/glitch/features/getting_started/containers/trends_container'; -import { showTrends } from 'flavours/glitch/initial_state'; - -const DefaultNavigation = () => ( - showTrends ? ( - <> - <div className='flex-spacer' /> - <Trends /> - </> - ) : null -); - -class NavigationPortal extends PureComponent { - - render () { - return ( - <Switch> - <Route path='/@:acct' exact component={AccountNavigation} /> - <Route path='/@:acct/tagged/:tagged?' exact component={AccountNavigation} /> - <Route path='/@:acct/with_replies' exact component={AccountNavigation} /> - <Route path='/@:acct/followers' exact component={AccountNavigation} /> - <Route path='/@:acct/following' exact component={AccountNavigation} /> - <Route path='/@:acct/media' exact component={AccountNavigation} /> - <Route component={DefaultNavigation} /> - </Switch> - ); - } - -} - -export default withRouter(NavigationPortal); diff --git a/app/javascript/flavours/glitch/components/navigation_portal.tsx b/app/javascript/flavours/glitch/components/navigation_portal.tsx new file mode 100644 index 0000000000..223cc24232 --- /dev/null +++ b/app/javascript/flavours/glitch/components/navigation_portal.tsx @@ -0,0 +1,25 @@ +import { Switch, Route } from 'react-router-dom'; + +import AccountNavigation from 'flavours/glitch/features/account/navigation'; +import Trends from 'flavours/glitch/features/getting_started/containers/trends_container'; +import { showTrends } from 'flavours/glitch/initial_state'; + +const DefaultNavigation: React.FC = () => + showTrends ? ( + <> + <div className='flex-spacer' /> + <Trends /> + </> + ) : null; + +export const NavigationPortal: React.FC = () => ( + <Switch> + <Route path='/@:acct' exact component={AccountNavigation} /> + <Route path='/@:acct/tagged/:tagged?' exact component={AccountNavigation} /> + <Route path='/@:acct/with_replies' exact component={AccountNavigation} /> + <Route path='/@:acct/followers' exact component={AccountNavigation} /> + <Route path='/@:acct/following' exact component={AccountNavigation} /> + <Route path='/@:acct/media' exact component={AccountNavigation} /> + <Route component={DefaultNavigation} /> + </Switch> +); diff --git a/app/javascript/flavours/glitch/components/permalink.jsx b/app/javascript/flavours/glitch/components/permalink.jsx index fa33ce066a..5226895415 100644 --- a/app/javascript/flavours/glitch/components/permalink.jsx +++ b/app/javascript/flavours/glitch/components/permalink.jsx @@ -1,11 +1,9 @@ import PropTypes from 'prop-types'; import { PureComponent } from 'react'; -export default class Permalink extends PureComponent { +import { withOptionalRouter, WithOptionalRouterPropTypes } from 'flavours/glitch/utils/react_router'; - static contextTypes = { - router: PropTypes.object, - }; +class Permalink extends PureComponent { static propTypes = { className: PropTypes.string, @@ -13,6 +11,7 @@ export default class Permalink extends PureComponent { to: PropTypes.string.isRequired, children: PropTypes.node, onInterceptClick: PropTypes.func, + ...WithOptionalRouterPropTypes, }; handleClick = (e) => { @@ -22,9 +21,9 @@ export default class Permalink extends PureComponent { return; } - if (this.context.router) { + if (this.props.history) { e.preventDefault(); - this.context.router.history.push(this.props.to); + this.props.history.push(this.props.to); } } }; @@ -47,3 +46,5 @@ export default class Permalink extends PureComponent { } } + +export default withOptionalRouter(Permalink); diff --git a/app/javascript/flavours/glitch/components/router.tsx b/app/javascript/flavours/glitch/components/router.tsx index f093716517..93d4c0e936 100644 --- a/app/javascript/flavours/glitch/components/router.tsx +++ b/app/javascript/flavours/glitch/components/router.tsx @@ -1,15 +1,18 @@ import type { PropsWithChildren } from 'react'; import React from 'react'; -import { createBrowserHistory } from 'history'; import { Router as OriginalRouter } from 'react-router'; +import type { LocationDescriptor, Path } from 'history'; +import { createBrowserHistory } from 'history'; + import { layoutFromWindow } from 'flavours/glitch/is_mobile'; interface MastodonLocationState { fromMastodon?: boolean; mastodonModalKey?: string; } +type HistoryPath = Path | LocationDescriptor<MastodonLocationState>; const browserHistory = createBrowserHistory< MastodonLocationState | undefined @@ -17,25 +20,36 @@ const browserHistory = createBrowserHistory< const originalPush = browserHistory.push.bind(browserHistory); const originalReplace = browserHistory.replace.bind(browserHistory); -browserHistory.push = (path: string, state?: MastodonLocationState) => { +function extractRealPath(path: HistoryPath) { + if (typeof path === 'string') return path; + else return path.pathname; +} + +browserHistory.push = (path: HistoryPath, state?: MastodonLocationState) => { state = state ?? {}; state.fromMastodon = true; - if (layoutFromWindow() === 'multi-column' && !path.startsWith('/deck')) { - originalPush(`/deck${path}`, state); + const realPath = extractRealPath(path); + if (!realPath) return; + + if (layoutFromWindow() === 'multi-column' && !realPath.startsWith('/deck')) { + originalPush(`/deck${realPath}`, state); } else { originalPush(path, state); } }; -browserHistory.replace = (path: string, state?: MastodonLocationState) => { +browserHistory.replace = (path: HistoryPath, state?: MastodonLocationState) => { if (browserHistory.location.state?.fromMastodon) { state = state ?? {}; state.fromMastodon = true; } - if (layoutFromWindow() === 'multi-column' && !path.startsWith('/deck')) { - originalReplace(`/deck${path}`, state); + const realPath = extractRealPath(path); + if (!realPath) return; + + if (layoutFromWindow() === 'multi-column' && !realPath.startsWith('/deck')) { + originalReplace(`/deck${realPath}`, state); } else { originalReplace(path, state); } diff --git a/app/javascript/flavours/glitch/components/scrollable_list.jsx b/app/javascript/flavours/glitch/components/scrollable_list.jsx index 2a746c5bd1..a281e56154 100644 --- a/app/javascript/flavours/glitch/components/scrollable_list.jsx +++ b/app/javascript/flavours/glitch/components/scrollable_list.jsx @@ -2,6 +2,7 @@ import PropTypes from 'prop-types'; import { Children, cloneElement, PureComponent } from 'react'; import classNames from 'classnames'; +import { useLocation } from 'react-router-dom'; import { List as ImmutableList } from 'immutable'; import { connect } from 'react-redux'; @@ -34,11 +35,32 @@ const mapStateToProps = (state, { scrollKey }) => { }; }; -class ScrollableList extends PureComponent { +// This component only exists to be able to call useLocation() +const IOArticleContainerWrapper = ({id, index, listLength, intersectionObserverWrapper, trackScroll, scrollKey, children}) => { + const location = useLocation(); - static contextTypes = { - router: PropTypes.object, - }; + return (<IntersectionObserverArticleContainer + id={id} + index={index} + listLength={listLength} + intersectionObserverWrapper={intersectionObserverWrapper} + saveHeightKey={trackScroll ? `${location.key}:${scrollKey}` : null} + > + {children} + </IntersectionObserverArticleContainer>); +}; + +IOArticleContainerWrapper.propTypes = { + id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + index: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + listLength: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + scrollKey: PropTypes.string.isRequired, + intersectionObserverWrapper: PropTypes.object.isRequired, + trackScroll: PropTypes.bool.isRequired, + children: PropTypes.node, +}; + +class ScrollableList extends PureComponent { static propTypes = { scrollKey: PropTypes.string.isRequired, @@ -331,13 +353,14 @@ class ScrollableList extends PureComponent { {loadPending} {Children.map(this.props.children, (child, index) => ( - <IntersectionObserverArticleContainer + <IOArticleContainerWrapper key={child.key} id={child.key} index={index} listLength={childrenCount} intersectionObserverWrapper={this.intersectionObserverWrapper} - saveHeightKey={trackScroll ? `${this.context.router.route.location.key}:${scrollKey}` : null} + trackScroll={trackScroll} + scrollKey={scrollKey} > {cloneElement(child, { getScrollPosition: this.getScrollPosition, @@ -345,7 +368,7 @@ class ScrollableList extends PureComponent { cachedMediaWidth: this.state.cachedMediaWidth, cacheMediaWidth: this.cacheMediaWidth, })} - </IntersectionObserverArticleContainer> + </IOArticleContainerWrapper> ))} {loadMore} diff --git a/app/javascript/flavours/glitch/components/status.jsx b/app/javascript/flavours/glitch/components/status.jsx index 36abc69930..96f6857961 100644 --- a/app/javascript/flavours/glitch/components/status.jsx +++ b/app/javascript/flavours/glitch/components/status.jsx @@ -14,6 +14,7 @@ import PollContainer from 'flavours/glitch/containers/poll_container'; import NotificationOverlayContainer from 'flavours/glitch/features/notifications/containers/overlay_container'; import { displayMedia } from 'flavours/glitch/initial_state'; import { autoUnfoldCW } from 'flavours/glitch/utils/content_warning'; +import { withOptionalRouter, WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import Card from '../features/status/components/card'; import Bundle from '../features/ui/components/bundle'; @@ -67,10 +68,6 @@ export const defaultMediaVisibility = (status, settings) => { class Status extends ImmutablePureComponent { - static contextTypes = { - router: PropTypes.object, - }; - static propTypes = { containerId: PropTypes.string, id: PropTypes.string, @@ -117,6 +114,7 @@ class Status extends ImmutablePureComponent { inUse: PropTypes.bool, available: PropTypes.bool, }), + ...WithRouterPropTypes, }; state = { @@ -356,10 +354,9 @@ class Status extends ImmutablePureComponent { // Otherwise, we open the url handed to us in `destination`, if // applicable. parseClick = (e, destination) => { - const { router } = this.context; - const { status } = this.props; + const { status, history } = this.props; const { isCollapsed } = this.state; - if (!router) return; + if (!history) return; if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey)) { if (isCollapsed) this.setCollapsed(false); @@ -377,7 +374,7 @@ class Status extends ImmutablePureComponent { status.getIn(['reblog', 'id'], status.get('id')) }`; } - router.history.push(destination); + history.push(destination); } e.preventDefault(); } @@ -431,7 +428,7 @@ class Status extends ImmutablePureComponent { handleHotkeyReply = e => { e.preventDefault(); - this.props.onReply(this.props.status, this.context.router.history); + this.props.onReply(this.props.status, this.props.history); }; handleHotkeyFavourite = (e) => { @@ -448,16 +445,16 @@ class Status extends ImmutablePureComponent { handleHotkeyMention = e => { e.preventDefault(); - this.props.onMention(this.props.status.get('account'), this.context.router.history); + this.props.onMention(this.props.status.get('account'), this.props.history); }; handleHotkeyOpen = () => { const status = this.props.status; - this.context.router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`); + this.props.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`); }; handleHotkeyOpenProfile = () => { - this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`); + this.props.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`); }; handleHotkeyMoveUp = e => { @@ -514,7 +511,6 @@ class Status extends ImmutablePureComponent { parseClick, setCollapsed, } = this; - const { router } = this.context; const { intl, status, @@ -533,6 +529,7 @@ class Status extends ImmutablePureComponent { previousId, nextInReplyToId, rootId, + history, ...other } = this.props; const { isCollapsed } = this.state; @@ -828,7 +825,7 @@ class Status extends ImmutablePureComponent { onExpandedToggle={this.handleExpandedToggle} onTranslate={this.handleTranslate} parseClick={parseClick} - disabled={!router} + disabled={!history} tagLinks={settings.get('tag_misleading_links')} rewriteMentions={settings.get('rewrite_mentions')} /> @@ -854,4 +851,4 @@ class Status extends ImmutablePureComponent { } -export default injectIntl(Status); +export default withOptionalRouter(injectIntl(Status)); diff --git a/app/javascript/flavours/glitch/components/status_action_bar.jsx b/app/javascript/flavours/glitch/components/status_action_bar.jsx index 8d32c6b5d7..b8dd63b270 100644 --- a/app/javascript/flavours/glitch/components/status_action_bar.jsx +++ b/app/javascript/flavours/glitch/components/status_action_bar.jsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; import classNames from 'classnames'; +import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; @@ -11,6 +12,7 @@ import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_cont import { me } from 'flavours/glitch/initial_state'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions'; import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links'; +import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import { IconButton } from './icon_button'; import { RelativeTimestamp } from './relative_timestamp'; @@ -53,7 +55,6 @@ const messages = defineMessages({ class StatusActionBar extends ImmutablePureComponent { static contextTypes = { - router: PropTypes.object, identity: PropTypes.object, }; @@ -80,6 +81,7 @@ class StatusActionBar extends ImmutablePureComponent { showReplyCount: PropTypes.bool, scrollKey: PropTypes.string, intl: PropTypes.object.isRequired, + ...WithRouterPropTypes, }; // Avoid checking props that are functions (and whose equality will always @@ -95,7 +97,7 @@ class StatusActionBar extends ImmutablePureComponent { const { signedIn } = this.context.identity; if (signedIn) { - this.props.onReply(this.props.status, this.context.router.history); + this.props.onReply(this.props.status, this.props.history); } else { this.props.onInteractionModal('reply', this.props.status); } @@ -132,15 +134,15 @@ class StatusActionBar extends ImmutablePureComponent { }; handleDeleteClick = () => { - this.props.onDelete(this.props.status, this.context.router.history); + this.props.onDelete(this.props.status, this.props.history); }; handleRedraftClick = () => { - this.props.onDelete(this.props.status, this.context.router.history, true); + this.props.onDelete(this.props.status, this.props.history, true); }; handleEditClick = () => { - this.props.onEdit(this.props.status, this.context.router.history); + this.props.onEdit(this.props.status, this.props.history); }; handlePinClick = () => { @@ -148,11 +150,11 @@ class StatusActionBar extends ImmutablePureComponent { }; handleMentionClick = () => { - this.props.onMention(this.props.status.get('account'), this.context.router.history); + this.props.onMention(this.props.status.get('account'), this.props.history); }; handleDirectClick = () => { - this.props.onDirect(this.props.status.get('account'), this.context.router.history); + this.props.onDirect(this.props.status.get('account'), this.props.history); }; handleMuteClick = () => { @@ -164,12 +166,7 @@ class StatusActionBar extends ImmutablePureComponent { }; handleOpen = () => { - let state = { ...this.context.router.history.location.state }; - if (state.mastodonModalKey) { - this.context.router.history.replace(`/@${this.props.status.getIn(['account', 'acct'])}/${this.props.status.get('id')}`); - } else { - this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}/${this.props.status.get('id')}`); - } + this.props.history.push(`/@${this.props.status.getIn(['account', 'acct'])}/${this.props.status.get('id')}`); }; handleEmbed = () => { @@ -340,4 +337,4 @@ class StatusActionBar extends ImmutablePureComponent { } -export default injectIntl(StatusActionBar); +export default withRouter(injectIntl(StatusActionBar)); diff --git a/app/javascript/flavours/glitch/components/status_content.jsx b/app/javascript/flavours/glitch/components/status_content.jsx index 4719d7dcc7..03b2799168 100644 --- a/app/javascript/flavours/glitch/components/status_content.jsx +++ b/app/javascript/flavours/glitch/components/status_content.jsx @@ -4,6 +4,7 @@ import { PureComponent } from 'react'; import { FormattedMessage, injectIntl } from 'react-intl'; import classnames from 'classnames'; +import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; @@ -131,6 +132,10 @@ class StatusContent extends PureComponent { rewriteMentions: PropTypes.string, languages: ImmutablePropTypes.map, intl: PropTypes.object, + // from react-router + match: PropTypes.object.isRequired, + location: PropTypes.object.isRequired, + history: PropTypes.object.isRequired }; static defaultProps = { @@ -472,4 +477,4 @@ class StatusContent extends PureComponent { } -export default connect(mapStateToProps)(injectIntl(StatusContent)); +export default withRouter(connect(mapStateToProps)(injectIntl(StatusContent))); diff --git a/app/javascript/flavours/glitch/features/account/components/featured_tags.jsx b/app/javascript/flavours/glitch/features/account/components/featured_tags.jsx index 87e88f2fa8..4ddf0dc524 100644 --- a/app/javascript/flavours/glitch/features/account/components/featured_tags.jsx +++ b/app/javascript/flavours/glitch/features/account/components/featured_tags.jsx @@ -14,10 +14,6 @@ const messages = defineMessages({ class FeaturedTags extends ImmutablePureComponent { - static contextTypes = { - router: PropTypes.object, - }; - static propTypes = { account: ImmutablePropTypes.map, featuredTags: ImmutablePropTypes.list, diff --git a/app/javascript/flavours/glitch/features/account/components/header.jsx b/app/javascript/flavours/glitch/features/account/components/header.jsx index acebd9f322..9a20eaf6ea 100644 --- a/app/javascript/flavours/glitch/features/account/components/header.jsx +++ b/app/javascript/flavours/glitch/features/account/components/header.jsx @@ -4,6 +4,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import { Helmet } from 'react-helmet'; +import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; @@ -16,6 +17,7 @@ import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_cont import { autoPlayGif, me, domain } from 'flavours/glitch/initial_state'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions'; import { preferencesLink, profileLink, accountAdminLink } from 'flavours/glitch/utils/backend_links'; +import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import AccountNoteContainer from '../containers/account_note_container'; import FollowRequestNoteContainer from '../containers/follow_request_note_container'; @@ -81,10 +83,6 @@ const dateFormatOptions = { class Header extends ImmutablePureComponent { - static contextTypes = { - identity: PropTypes.object, - }; - static propTypes = { account: ImmutablePropTypes.map, identity_props: ImmutablePropTypes.list, @@ -107,6 +105,11 @@ class Header extends ImmutablePureComponent { intl: PropTypes.object.isRequired, domain: PropTypes.string.isRequired, hidden: PropTypes.bool, + ...WithRouterPropTypes, + }; + + static contextTypes = { + identity: PropTypes.object, }; openEditProfile = () => { @@ -406,4 +409,4 @@ class Header extends ImmutablePureComponent { } -export default injectIntl(Header); +export default withRouter(injectIntl(Header)); diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/header.jsx b/app/javascript/flavours/glitch/features/account_timeline/components/header.jsx index 717114d5c6..1e91ffa112 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/components/header.jsx +++ b/app/javascript/flavours/glitch/features/account_timeline/components/header.jsx @@ -2,18 +2,19 @@ import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; -import { NavLink } from 'react-router-dom'; +import { NavLink, withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import ActionBar from 'flavours/glitch/features/account/components/action_bar'; import InnerHeader from 'flavours/glitch/features/account/components/header'; +import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import MemorialNote from './memorial_note'; import MovedNote from './moved_note'; -export default class Header extends ImmutablePureComponent { +class Header extends ImmutablePureComponent { static propTypes = { account: ImmutablePropTypes.map, @@ -34,10 +35,7 @@ export default class Header extends ImmutablePureComponent { hideTabs: PropTypes.bool, domain: PropTypes.string.isRequired, hidden: PropTypes.bool, - }; - - static contextTypes = { - router: PropTypes.object, + ...WithRouterPropTypes, }; handleFollow = () => { @@ -49,11 +47,11 @@ export default class Header extends ImmutablePureComponent { }; handleMention = () => { - this.props.onMention(this.props.account, this.context.router.history); + this.props.onMention(this.props.account, this.props.history); }; handleDirect = () => { - this.props.onDirect(this.props.account, this.context.router.history); + this.props.onDirect(this.props.account, this.props.history); }; handleReport = () => { @@ -162,3 +160,5 @@ export default class Header extends ImmutablePureComponent { } } + +export default withRouter(Header); diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.jsx b/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.jsx index 2e10ea94af..5d32f5a90f 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.jsx +++ b/app/javascript/flavours/glitch/features/account_timeline/components/moved_note.jsx @@ -1,30 +1,28 @@ -import PropTypes from 'prop-types'; - import { FormattedMessage } from 'react-intl'; +import { withRouter } from 'react-router-dom'; + import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { Icon } from 'flavours/glitch/components/icon'; +import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import AvatarOverlay from '../../../components/avatar_overlay'; import { DisplayName } from '../../../components/display_name'; -export default class MovedNote extends ImmutablePureComponent { - - static contextTypes = { - router: PropTypes.object, - }; +class MovedNote extends ImmutablePureComponent { static propTypes = { from: ImmutablePropTypes.map.isRequired, to: ImmutablePropTypes.map.isRequired, + ...WithRouterPropTypes, }; handleAccountClick = e => { if (e.button === 0) { e.preventDefault(); - this.context.router.history.push(`/@${this.props.to.get('acct')}`); + this.props.history.push(`/@${this.props.to.get('acct')}`); } e.stopPropagation(); @@ -50,3 +48,5 @@ export default class MovedNote extends ImmutablePureComponent { } } + +export default withRouter(MovedNote); diff --git a/app/javascript/flavours/glitch/features/community_timeline/index.jsx b/app/javascript/flavours/glitch/features/community_timeline/index.jsx index b65e41637c..f9436df0d0 100644 --- a/app/javascript/flavours/glitch/features/community_timeline/index.jsx +++ b/app/javascript/flavours/glitch/features/community_timeline/index.jsx @@ -44,7 +44,6 @@ class CommunityTimeline extends PureComponent { }; static contextTypes = { - router: PropTypes.object, identity: PropTypes.object, }; diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.jsx b/app/javascript/flavours/glitch/features/compose/components/compose_form.jsx index 53e1bf79ae..e3ed207043 100644 --- a/app/javascript/flavours/glitch/features/compose/components/compose_form.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.jsx @@ -9,6 +9,7 @@ import { length } from 'stringz'; import { maxChars } from 'flavours/glitch/initial_state'; import { isMobile } from 'flavours/glitch/is_mobile'; +import { WithOptionalRouterPropTypes, withOptionalRouter } from 'flavours/glitch/utils/react_router'; import AutosuggestInput from '../../../components/autosuggest_input'; import AutosuggestTextarea from '../../../components/autosuggest_textarea'; @@ -38,11 +39,6 @@ const messages = defineMessages({ }); class ComposeForm extends ImmutablePureComponent { - - static contextTypes = { - router: PropTypes.object, - }; - static propTypes = { intl: PropTypes.object.isRequired, text: PropTypes.string, @@ -70,7 +66,6 @@ class ComposeForm extends ImmutablePureComponent { isInReply: PropTypes.bool, singleColumn: PropTypes.bool, lang: PropTypes.string, - advancedOptions: ImmutablePropTypes.map, layout: PropTypes.string, media: ImmutablePropTypes.list, @@ -82,6 +77,7 @@ class ComposeForm extends ImmutablePureComponent { onChangeSpoilerness: PropTypes.func, onChangeVisibility: PropTypes.func, onMediaDescriptionConfirm: PropTypes.func, + ...WithOptionalRouterPropTypes }; static defaultProps = { @@ -129,12 +125,12 @@ class ComposeForm extends ImmutablePureComponent { // Submit unless there are media with missing descriptions if (mediaDescriptionConfirmation && onMediaDescriptionConfirm && media && media.some(item => !item.get('description'))) { const firstWithoutDescription = media.find(item => !item.get('description')); - onMediaDescriptionConfirm(this.context.router ? this.context.router.history : null, firstWithoutDescription.get('id'), overriddenVisibility); + onMediaDescriptionConfirm(this.props.history || null, firstWithoutDescription.get('id'), overriddenVisibility); } else if (onSubmit) { if (onChangeVisibility && overriddenVisibility) { onChangeVisibility(overriddenVisibility); } - onSubmit(this.context.router ? this.context.router.history : null); + onSubmit(this.props.history || null); } }; @@ -390,4 +386,4 @@ class ComposeForm extends ImmutablePureComponent { } -export default injectIntl(ComposeForm); +export default withOptionalRouter(injectIntl(ComposeForm)); diff --git a/app/javascript/flavours/glitch/features/compose/components/reply_indicator.jsx b/app/javascript/flavours/glitch/features/compose/components/reply_indicator.jsx index 8a1089bce7..51da8f297d 100644 --- a/app/javascript/flavours/glitch/features/compose/components/reply_indicator.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/reply_indicator.jsx @@ -1,4 +1,3 @@ -// Package imports. import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; @@ -6,18 +5,13 @@ import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -// Components. import AttachmentList from 'flavours/glitch/components/attachment_list'; import { IconButton } from 'flavours/glitch/components/icon_button'; import AccountContainer from 'flavours/glitch/containers/account_container'; -// Messages. -const messages = defineMessages({ - cancel: { - defaultMessage: 'Cancel', - id: 'reply_indicator.cancel', - }, -}); +const messages = defineMessages({ + cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' }, +}); class ReplyIndicator extends ImmutablePureComponent { diff --git a/app/javascript/flavours/glitch/features/compose/components/search.jsx b/app/javascript/flavours/glitch/features/compose/components/search.jsx index a06b83f6f2..09627bb4fa 100644 --- a/app/javascript/flavours/glitch/features/compose/components/search.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/search.jsx @@ -4,14 +4,14 @@ import { PureComponent } from 'react'; import { defineMessages, injectIntl, FormattedMessage, FormattedList } from 'react-intl'; import classNames from 'classnames'; +import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; - -import { Icon } from 'flavours/glitch/components/icon'; +import { Icon } from 'flavours/glitch/components/icon'; import { domain, searchEnabled } from 'flavours/glitch/initial_state'; -import { focusRoot } from 'flavours/glitch/utils/dom_helpers'; import { HASHTAG_REGEX } from 'flavours/glitch/utils/hashtags'; +import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; const messages = defineMessages({ placeholder: { id: 'search.placeholder', defaultMessage: 'Search' }, @@ -32,7 +32,6 @@ const labelForRecentSearch = search => { class Search extends PureComponent { static contextTypes = { - router: PropTypes.object.isRequired, identity: PropTypes.object.isRequired, }; @@ -50,6 +49,7 @@ class Search extends PureComponent { openInRoute: PropTypes.bool, intl: PropTypes.object.isRequired, singleColumn: PropTypes.bool, + ...WithRouterPropTypes, }; state = { @@ -122,8 +122,7 @@ class Search extends PureComponent { switch(e.key) { case 'Escape': e.preventDefault(); - - focusRoot(); + this._unfocus(); break; case 'ArrowDown': @@ -170,32 +169,29 @@ class Search extends PureComponent { }; handleHashtagClick = () => { - const { router } = this.context; - const { value, onClickSearchResult } = this.props; + const { value, onClickSearchResult, history } = this.props; const query = value.trim().replace(/^#/, ''); - router.history.push(`/tags/${query}`); + history.push(`/tags/${query}`); onClickSearchResult(query, 'hashtag'); this._unfocus(); }; handleAccountClick = () => { - const { router } = this.context; - const { value, onClickSearchResult } = this.props; + const { value, onClickSearchResult, history } = this.props; const query = value.trim().replace(/^@/, ''); - router.history.push(`/@${query}`); + history.push(`/@${query}`); onClickSearchResult(query, 'account'); this._unfocus(); }; handleURLClick = () => { - const { router } = this.context; - const { onOpenURL } = this.props; + const { onOpenURL, history } = this.props; - onOpenURL(router.history); + onOpenURL(history); this._unfocus(); }; @@ -208,13 +204,12 @@ class Search extends PureComponent { }; handleRecentSearchClick = search => { - const { onChange } = this.props; - const { router } = this.context; + const { onChange, history } = this.props; if (search.get('type') === 'account') { - router.history.push(`/@${search.get('q')}`); + history.push(`/@${search.get('q')}`); } else if (search.get('type') === 'hashtag') { - router.history.push(`/tags/${search.get('q')}`); + history.push(`/tags/${search.get('q')}`); } else { onChange(search.get('q')); this._submit(search.get('type')); @@ -246,8 +241,7 @@ class Search extends PureComponent { } _submit (type) { - const { onSubmit, openInRoute, value, onClickSearchResult } = this.props; - const { router } = this.context; + const { onSubmit, openInRoute, value, onClickSearchResult, history } = this.props; onSubmit(type); @@ -256,7 +250,7 @@ class Search extends PureComponent { } if (openInRoute) { - router.history.push('/search'); + history.push('/search'); } this._unfocus(); @@ -403,4 +397,4 @@ class Search extends PureComponent { } -export default injectIntl(Search); +export default withRouter(injectIntl(Search)); diff --git a/app/javascript/flavours/glitch/features/compose/components/upload.jsx b/app/javascript/flavours/glitch/features/compose/components/upload.jsx index 5685759f8d..91f649a131 100644 --- a/app/javascript/flavours/glitch/features/compose/components/upload.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/upload.jsx @@ -13,10 +13,6 @@ import Motion from '../../ui/util/optional_motion'; export default class Upload extends ImmutablePureComponent { - static contextTypes = { - router: PropTypes.object, - }; - static propTypes = { media: ImmutablePropTypes.map.isRequired, onUndo: PropTypes.func.isRequired, diff --git a/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx b/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx index 0a2262f281..0fdd175560 100644 --- a/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx +++ b/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import classNames from 'classnames'; +import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; @@ -17,6 +18,7 @@ import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp import StatusContent from 'flavours/glitch/components/status_content'; import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container'; import { autoPlayGif } from 'flavours/glitch/initial_state'; +import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; const messages = defineMessages({ more: { id: 'status.more', defaultMessage: 'More' }, @@ -30,10 +32,6 @@ const messages = defineMessages({ class Conversation extends ImmutablePureComponent { - static contextTypes = { - router: PropTypes.object, - }; - static propTypes = { conversationId: PropTypes.string.isRequired, accounts: ImmutablePropTypes.list.isRequired, @@ -45,6 +43,7 @@ class Conversation extends ImmutablePureComponent { markRead: PropTypes.func.isRequired, delete: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, + ...WithRouterPropTypes, }; state = { @@ -52,9 +51,8 @@ class Conversation extends ImmutablePureComponent { }; parseClick = (e, destination) => { - const { router } = this.context; - const { lastStatus, unread, markRead } = this.props; - if (!router) return; + const { history, lastStatus, unread, markRead } = this.props; + if (!history) return; if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey)) { if (destination === undefined) { @@ -63,7 +61,7 @@ class Conversation extends ImmutablePureComponent { } destination = `/statuses/${lastStatus.get('id')}`; } - router.history.push(destination); + history.push(destination); e.preventDefault(); } }; @@ -95,7 +93,7 @@ class Conversation extends ImmutablePureComponent { }; handleClick = () => { - if (!this.context.router) { + if (!this.props.history) { return; } @@ -105,7 +103,7 @@ class Conversation extends ImmutablePureComponent { markRead(); } - this.context.router.history.push(`/@${lastStatus.getIn(['account', 'acct'])}/${lastStatus.get('id')}`); + this.props.history.push(`/@${lastStatus.getIn(['account', 'acct'])}/${lastStatus.get('id')}`); }; handleMarkAsRead = () => { @@ -113,7 +111,7 @@ class Conversation extends ImmutablePureComponent { }; handleReply = () => { - this.props.reply(this.props.lastStatus, this.context.router.history); + this.props.reply(this.props.lastStatus, this.props.history); }; handleDelete = () => { @@ -232,4 +230,4 @@ class Conversation extends ImmutablePureComponent { } -export default injectIntl(Conversation); +export default withRouter(injectIntl(Conversation)); diff --git a/app/javascript/flavours/glitch/features/directory/index.jsx b/app/javascript/flavours/glitch/features/directory/index.jsx index e1db3180fa..92e80bf956 100644 --- a/app/javascript/flavours/glitch/features/directory/index.jsx +++ b/app/javascript/flavours/glitch/features/directory/index.jsx @@ -36,10 +36,6 @@ const mapStateToProps = state => ({ class Directory extends PureComponent { - static contextTypes = { - router: PropTypes.object, - }; - static propTypes = { isLoading: PropTypes.bool, accountIds: ImmutablePropTypes.list.isRequired, diff --git a/app/javascript/flavours/glitch/features/explore/index.jsx b/app/javascript/flavours/glitch/features/explore/index.jsx index 4a916c9958..6eeba46ce7 100644 --- a/app/javascript/flavours/glitch/features/explore/index.jsx +++ b/app/javascript/flavours/glitch/features/explore/index.jsx @@ -34,7 +34,6 @@ const mapStateToProps = state => ({ class Explore extends PureComponent { static contextTypes = { - router: PropTypes.object, identity: PropTypes.object, }; diff --git a/app/javascript/flavours/glitch/features/follow_recommendations/index.jsx b/app/javascript/flavours/glitch/features/follow_recommendations/index.jsx index 194784a7a5..037794e18b 100644 --- a/app/javascript/flavours/glitch/features/follow_recommendations/index.jsx +++ b/app/javascript/flavours/glitch/features/follow_recommendations/index.jsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import { Helmet } from 'react-helmet'; +import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; @@ -14,6 +15,7 @@ import { fetchSuggestions } from 'flavours/glitch/actions/suggestions'; import { markAsPartial } from 'flavours/glitch/actions/timelines'; import Button from 'flavours/glitch/components/button'; import Column from 'flavours/glitch/features/ui/components/column'; +import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import imageGreeting from 'mastodon/../images/elephant_ui_greeting.svg'; import Account from './components/account'; @@ -25,14 +27,11 @@ const mapStateToProps = state => ({ class FollowRecommendations extends ImmutablePureComponent { - static contextTypes = { - router: PropTypes.object.isRequired, - }; - static propTypes = { dispatch: PropTypes.func.isRequired, suggestions: ImmutablePropTypes.list, isLoading: PropTypes.bool, + ...WithRouterPropTypes, }; componentDidMount () { @@ -56,8 +55,7 @@ class FollowRecommendations extends ImmutablePureComponent { } handleDone = () => { - const { dispatch } = this.props; - const { router } = this.context; + const { history, dispatch } = this.props; dispatch(requestBrowserPermission((permission) => { if (permission === 'granted') { @@ -71,7 +69,7 @@ class FollowRecommendations extends ImmutablePureComponent { } })); - router.history.push('/home'); + history.push('/home'); }; render () { @@ -118,4 +116,4 @@ class FollowRecommendations extends ImmutablePureComponent { } -export default connect(mapStateToProps)(FollowRecommendations); +export default withRouter(connect(mapStateToProps)(FollowRecommendations)); diff --git a/app/javascript/flavours/glitch/features/getting_started/components/announcements.jsx b/app/javascript/flavours/glitch/features/getting_started/components/announcements.jsx index fd17f8c9d7..1037e8de1b 100644 --- a/app/javascript/flavours/glitch/features/getting_started/components/announcements.jsx +++ b/app/javascript/flavours/glitch/features/getting_started/components/announcements.jsx @@ -4,6 +4,7 @@ import { PureComponent } from 'react'; import { defineMessages, injectIntl, FormattedMessage, FormattedDate } from 'react-intl'; import classNames from 'classnames'; +import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; @@ -19,24 +20,19 @@ import EmojiPickerDropdown from 'flavours/glitch/features/compose/containers/emo import unicodeMapping from 'flavours/glitch/features/emoji/emoji_unicode_mapping_light'; import { autoPlayGif, reduceMotion, disableSwiping, mascot } from 'flavours/glitch/initial_state'; import { assetHost } from 'flavours/glitch/utils/config'; +import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import elephantUIPlane from 'mastodon/../images/elephant_ui_plane.svg'; - - const messages = defineMessages({ close: { id: 'lightbox.close', defaultMessage: 'Close' }, previous: { id: 'lightbox.previous', defaultMessage: 'Previous' }, next: { id: 'lightbox.next', defaultMessage: 'Next' }, }); -class Content extends ImmutablePureComponent { - - static contextTypes = { - router: PropTypes.object, - }; - +class ContentWithRouter extends ImmutablePureComponent { static propTypes = { announcement: ImmutablePropTypes.map.isRequired, + ...WithRouterPropTypes, }; setRef = c => { @@ -91,25 +87,25 @@ class Content extends ImmutablePureComponent { } onMentionClick = (mention, e) => { - if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { + if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); - this.context.router.history.push(`/@${mention.get('acct')}`); + this.props.history.push(`/@${mention.get('acct')}`); } }; onHashtagClick = (hashtag, e) => { hashtag = hashtag.replace(/^#/, ''); - if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { + if (this.props.history&& e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); - this.context.router.history.push(`/tags/${hashtag}`); + this.props.history.push(`/tags/${hashtag}`); } }; onStatusClick = (status, e) => { - if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { + if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) { e.preventDefault(); - this.context.router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`); + this.props.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`); } }; @@ -155,6 +151,8 @@ class Content extends ImmutablePureComponent { } +const Content = withRouter(ContentWithRouter); + class Emoji extends PureComponent { static propTypes = { diff --git a/app/javascript/flavours/glitch/features/getting_started/index.jsx b/app/javascript/flavours/glitch/features/getting_started/index.jsx index 9a8ee3f091..0987ad9352 100644 --- a/app/javascript/flavours/glitch/features/getting_started/index.jsx +++ b/app/javascript/flavours/glitch/features/getting_started/index.jsx @@ -88,7 +88,6 @@ const badgeDisplay = (number, limit) => { class GettingStarted extends ImmutablePureComponent { static contextTypes = { - router: PropTypes.object.isRequired, identity: PropTypes.object, }; diff --git a/app/javascript/flavours/glitch/features/getting_started_misc/index.jsx b/app/javascript/flavours/glitch/features/getting_started_misc/index.jsx index d7500fd822..2c2fbc05c0 100644 --- a/app/javascript/flavours/glitch/features/getting_started_misc/index.jsx +++ b/app/javascript/flavours/glitch/features/getting_started_misc/index.jsx @@ -28,7 +28,6 @@ const messages = defineMessages({ class GettingStartedMisc extends ImmutablePureComponent { static contextTypes = { - router: PropTypes.object.isRequired, identity: PropTypes.object, }; diff --git a/app/javascript/flavours/glitch/features/list_timeline/index.jsx b/app/javascript/flavours/glitch/features/list_timeline/index.jsx index d407e8a6ea..594cecaf74 100644 --- a/app/javascript/flavours/glitch/features/list_timeline/index.jsx +++ b/app/javascript/flavours/glitch/features/list_timeline/index.jsx @@ -4,6 +4,7 @@ import { PureComponent } from 'react'; import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; import { Helmet } from 'react-helmet'; +import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; @@ -22,6 +23,7 @@ import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator'; import { RadioButton } from 'flavours/glitch/components/radio_button'; import BundleColumnError from 'flavours/glitch/features/ui/components/bundle_column_error'; import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container'; +import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; const messages = defineMessages({ deleteMessage: { id: 'confirmations.delete_list.message', defaultMessage: 'Are you sure you want to permanently delete this list?' }, @@ -38,10 +40,6 @@ const mapStateToProps = (state, props) => ({ class ListTimeline extends PureComponent { - static contextTypes = { - router: PropTypes.object, - }; - static propTypes = { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, @@ -50,6 +48,7 @@ class ListTimeline extends PureComponent { multiColumn: PropTypes.bool, list: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]), intl: PropTypes.object.isRequired, + ...WithRouterPropTypes, }; handlePin = () => { @@ -59,7 +58,7 @@ class ListTimeline extends PureComponent { dispatch(removeColumn(columnId)); } else { dispatch(addColumn('LIST', { id: this.props.params.id })); - this.context.router.history.push('/'); + this.props.history.push('/'); } }; @@ -137,7 +136,7 @@ class ListTimeline extends PureComponent { if (columnId) { dispatch(removeColumn(columnId)); } else { - this.context.router.history.push('/lists'); + this.props.history.push('/lists'); } }, }, @@ -242,4 +241,4 @@ class ListTimeline extends PureComponent { } -export default connect(mapStateToProps)(injectIntl(ListTimeline)); +export default withRouter(connect(mapStateToProps)(injectIntl(ListTimeline))); diff --git a/app/javascript/flavours/glitch/features/notifications/components/admin_report.jsx b/app/javascript/flavours/glitch/features/notifications/components/admin_report.jsx index 9f63b3ce79..213f319fc9 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/admin_report.jsx +++ b/app/javascript/flavours/glitch/features/notifications/components/admin_report.jsx @@ -1,25 +1,24 @@ -// Package imports. import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import classNames from 'classnames'; +import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { HotKeys } from 'react-hotkeys'; - -// Our imports. import { Icon } from 'flavours/glitch/components/icon'; import Permalink from 'flavours/glitch/components/permalink'; +import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import NotificationOverlayContainer from '../containers/overlay_container'; import Report from './report'; -export default class AdminReport extends ImmutablePureComponent { +class AdminReport extends ImmutablePureComponent { static propTypes = { hidden: PropTypes.bool, @@ -28,6 +27,7 @@ export default class AdminReport extends ImmutablePureComponent { notification: ImmutablePropTypes.map.isRequired, unread: PropTypes.bool, report: ImmutablePropTypes.map.isRequired, + ...WithRouterPropTypes, }; handleMoveUp = () => { @@ -45,15 +45,15 @@ export default class AdminReport extends ImmutablePureComponent { }; handleOpenProfile = () => { - const { notification } = this.props; - this.context.router.history.push(`/@${notification.getIn(['account', 'acct'])}`); + const { history, notification } = this.props; + history.push(`/@${notification.getIn(['account', 'acct'])}`); }; handleMention = e => { e.preventDefault(); - const { notification, onMention } = this.props; - onMention(notification.get('account'), this.context.router.history); + const { history, notification, onMention } = this.props; + onMention(notification.get('account'), history); }; getHandlers () { @@ -111,3 +111,5 @@ export default class AdminReport extends ImmutablePureComponent { } } + +export default withRouter(AdminReport); diff --git a/app/javascript/flavours/glitch/features/notifications/components/admin_signup.jsx b/app/javascript/flavours/glitch/features/notifications/components/admin_signup.jsx index 8036f783a7..9d9a5f0c06 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/admin_signup.jsx +++ b/app/javascript/flavours/glitch/features/notifications/components/admin_signup.jsx @@ -1,24 +1,23 @@ -// Package imports. import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import classNames from 'classnames'; +import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { HotKeys } from 'react-hotkeys'; - -// Our imports. import { Icon } from 'flavours/glitch/components/icon'; import Permalink from 'flavours/glitch/components/permalink'; import AccountContainer from 'flavours/glitch/containers/account_container'; +import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import NotificationOverlayContainer from '../containers/overlay_container'; -export default class NotificationFollow extends ImmutablePureComponent { +class NotificationAdminSignup extends ImmutablePureComponent { static propTypes = { hidden: PropTypes.bool, @@ -26,6 +25,7 @@ export default class NotificationFollow extends ImmutablePureComponent { account: ImmutablePropTypes.map.isRequired, notification: ImmutablePropTypes.map.isRequired, unread: PropTypes.bool, + ...WithRouterPropTypes, }; handleMoveUp = () => { @@ -43,15 +43,15 @@ export default class NotificationFollow extends ImmutablePureComponent { }; handleOpenProfile = () => { - const { notification } = this.props; - this.context.router.history.push(`/@${notification.getIn(['account', 'acct'])}`); + const { history, notification } = this.props; + history.push(`/@${notification.getIn(['account', 'acct'])}`); }; handleMention = e => { e.preventDefault(); - const { notification, onMention } = this.props; - onMention(notification.get('account'), this.context.router.history); + const { history, notification, onMention } = this.props; + onMention(notification.get('account'), history); }; getHandlers () { @@ -104,3 +104,5 @@ export default class NotificationFollow extends ImmutablePureComponent { } } + +export default withRouter(NotificationAdminSignup); diff --git a/app/javascript/flavours/glitch/features/notifications/components/follow.jsx b/app/javascript/flavours/glitch/features/notifications/components/follow.jsx index 7e2749d66d..06603d8e3e 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/follow.jsx +++ b/app/javascript/flavours/glitch/features/notifications/components/follow.jsx @@ -1,24 +1,23 @@ -// Package imports. import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import classNames from 'classnames'; +import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { HotKeys } from 'react-hotkeys'; - -// Our imports. import { Icon } from 'flavours/glitch/components/icon'; import Permalink from 'flavours/glitch/components/permalink'; import AccountContainer from 'flavours/glitch/containers/account_container'; +import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import NotificationOverlayContainer from '../containers/overlay_container'; -export default class NotificationFollow extends ImmutablePureComponent { +class NotificationFollow extends ImmutablePureComponent { static propTypes = { hidden: PropTypes.bool, @@ -26,6 +25,7 @@ export default class NotificationFollow extends ImmutablePureComponent { account: ImmutablePropTypes.map.isRequired, notification: ImmutablePropTypes.map.isRequired, unread: PropTypes.bool, + ...WithRouterPropTypes, }; handleMoveUp = () => { @@ -43,15 +43,15 @@ export default class NotificationFollow extends ImmutablePureComponent { }; handleOpenProfile = () => { - const { notification } = this.props; - this.context.router.history.push(`/@${notification.getIn(['account', 'acct'])}`); + const { history, notification } = this.props; + history.push(`/@${notification.getIn(['account', 'acct'])}`); }; handleMention = e => { e.preventDefault(); - const { notification, onMention } = this.props; - onMention(notification.get('account'), this.context.router.history); + const { history, notification, onMention } = this.props; + onMention(notification.get('account'), history); }; getHandlers () { @@ -104,3 +104,5 @@ export default class NotificationFollow extends ImmutablePureComponent { } } + +export default withRouter(NotificationFollow); diff --git a/app/javascript/flavours/glitch/features/notifications/components/follow_request.jsx b/app/javascript/flavours/glitch/features/notifications/components/follow_request.jsx index d5ae9dd3f3..831095200a 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/follow_request.jsx +++ b/app/javascript/flavours/glitch/features/notifications/components/follow_request.jsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import classNames from 'classnames'; +import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; @@ -14,6 +15,7 @@ import { DisplayName } from 'flavours/glitch/components/display_name'; import { Icon } from 'flavours/glitch/components/icon'; import { IconButton } from 'flavours/glitch/components/icon_button'; import Permalink from 'flavours/glitch/components/permalink'; +import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import NotificationOverlayContainer from '../containers/overlay_container'; @@ -31,6 +33,7 @@ class FollowRequest extends ImmutablePureComponent { intl: PropTypes.object.isRequired, notification: ImmutablePropTypes.map.isRequired, unread: PropTypes.bool, + ...WithRouterPropTypes, }; handleMoveUp = () => { @@ -48,15 +51,15 @@ class FollowRequest extends ImmutablePureComponent { }; handleOpenProfile = () => { - const { notification } = this.props; - this.context.router.history.push(`/@${notification.getIn(['account', 'acct'])}`); + const { history, notification } = this.props; + history.push(`/@${notification.getIn(['account', 'acct'])}`); }; handleMention = e => { e.preventDefault(); - const { notification, onMention } = this.props; - onMention(notification.get('account'), this.context.router.history); + const { history, notification, onMention } = this.props; + onMention(notification.get('account'), history); }; getHandlers () { @@ -135,4 +138,4 @@ class FollowRequest extends ImmutablePureComponent { } -export default injectIntl(FollowRequest); +export default withRouter(injectIntl(FollowRequest)); diff --git a/app/javascript/flavours/glitch/features/notifications/containers/notification_container.js b/app/javascript/flavours/glitch/features/notifications/containers/notification_container.js index b39d1169f2..2fda4ac3a2 100644 --- a/app/javascript/flavours/glitch/features/notifications/containers/notification_container.js +++ b/app/javascript/flavours/glitch/features/notifications/containers/notification_container.js @@ -1,7 +1,5 @@ -// Package imports. import { connect } from 'react-redux'; -// Our imports. import { mentionCompose } from 'flavours/glitch/actions/compose'; import { makeGetNotification } from 'flavours/glitch/selectors'; @@ -19,8 +17,8 @@ const makeMapStateToProps = () => { }; const mapDispatchToProps = dispatch => ({ - onMention: (account, router) => { - dispatch(mentionCompose(account, router)); + onMention: (account, history) => { + dispatch(mentionCompose(account, history)); }, }); diff --git a/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.jsx b/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.jsx index 77574dea09..51d7c10668 100644 --- a/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.jsx +++ b/app/javascript/flavours/glitch/features/picture_in_picture/components/footer.jsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; import classNames from 'classnames'; +import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; @@ -15,6 +16,7 @@ import { openModal } from 'flavours/glitch/actions/modal'; import { IconButton } from 'flavours/glitch/components/icon_button'; import { me, boostModal } from 'flavours/glitch/initial_state'; import { makeGetStatus } from 'flavours/glitch/selectors'; +import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; const messages = defineMessages({ reply: { id: 'status.reply', defaultMessage: 'Reply' }, @@ -44,7 +46,6 @@ const makeMapStateToProps = () => { class Footer extends ImmutablePureComponent { static contextTypes = { - router: PropTypes.object, identity: PropTypes.object, }; @@ -57,17 +58,17 @@ class Footer extends ImmutablePureComponent { showReplyCount: PropTypes.bool, withOpenButton: PropTypes.bool, onClose: PropTypes.func, + ...WithRouterPropTypes, }; _performReply = () => { - const { dispatch, status, onClose } = this.props; - const { router } = this.context; + const { dispatch, status, onClose, history } = this.props; if (onClose) { onClose(true); } - dispatch(replyCompose(status, router.history)); + dispatch(replyCompose(status, history)); }; handleReplyClick = () => { @@ -151,9 +152,7 @@ class Footer extends ImmutablePureComponent { }; handleOpenClick = e => { - const { router } = this.context; - - if (e.button !== 0 || !router) { + if (e.button !== 0 || !history) { return; } @@ -163,7 +162,7 @@ class Footer extends ImmutablePureComponent { onClose(); } - router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`); + history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`); }; render () { @@ -229,4 +228,4 @@ class Footer extends ImmutablePureComponent { } -export default connect(makeMapStateToProps)(injectIntl(Footer)); +export default withRouter(connect(makeMapStateToProps)(injectIntl(Footer))); diff --git a/app/javascript/flavours/glitch/features/public_timeline/index.jsx b/app/javascript/flavours/glitch/features/public_timeline/index.jsx index 83ada8d3c0..dc5d53f9d4 100644 --- a/app/javascript/flavours/glitch/features/public_timeline/index.jsx +++ b/app/javascript/flavours/glitch/features/public_timeline/index.jsx @@ -48,7 +48,6 @@ class PublicTimeline extends PureComponent { }; static contextTypes = { - router: PropTypes.object, identity: PropTypes.object, }; diff --git a/app/javascript/flavours/glitch/features/status/components/action_bar.jsx b/app/javascript/flavours/glitch/features/status/components/action_bar.jsx index 5d48224fb7..9d8e0c945b 100644 --- a/app/javascript/flavours/glitch/features/status/components/action_bar.jsx +++ b/app/javascript/flavours/glitch/features/status/components/action_bar.jsx @@ -4,6 +4,7 @@ import { PureComponent } from 'react'; import { defineMessages, injectIntl } from 'react-intl'; import classNames from 'classnames'; +import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; @@ -12,6 +13,7 @@ import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_cont import { me } from 'flavours/glitch/initial_state'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'flavours/glitch/permissions'; import { accountAdminLink, statusAdminLink } from 'flavours/glitch/utils/backend_links'; +import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; const messages = defineMessages({ delete: { id: 'status.delete', defaultMessage: 'Delete' }, @@ -46,7 +48,6 @@ const messages = defineMessages({ class ActionBar extends PureComponent { static contextTypes = { - router: PropTypes.object, identity: PropTypes.object, }; @@ -67,6 +68,7 @@ class ActionBar extends PureComponent { onPin: PropTypes.func, onEmbed: PropTypes.func, intl: PropTypes.object.isRequired, + ...WithRouterPropTypes, }; handleReplyClick = () => { @@ -86,23 +88,23 @@ class ActionBar extends PureComponent { }; handleDeleteClick = () => { - this.props.onDelete(this.props.status, this.context.router.history); + this.props.onDelete(this.props.status, this.props.history); }; handleRedraftClick = () => { - this.props.onDelete(this.props.status, this.context.router.history, true); + this.props.onDelete(this.props.status, this.props.history, true); }; handleEditClick = () => { - this.props.onEdit(this.props.status, this.context.router.history); + this.props.onEdit(this.props.status, this.props.history); }; handleDirectClick = () => { - this.props.onDirect(this.props.status.get('account'), this.context.router.history); + this.props.onDirect(this.props.status.get('account'), this.props.history); }; handleMentionClick = () => { - this.props.onMention(this.props.status.get('account'), this.context.router.history); + this.props.onMention(this.props.status.get('account'), this.props.history); }; handleMuteClick = () => { @@ -234,4 +236,4 @@ class ActionBar extends PureComponent { } -export default injectIntl(ActionBar); +export default withRouter(injectIntl(ActionBar)); diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx b/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx index 1770436ae7..962a86da20 100644 --- a/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx +++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import { injectIntl, FormattedDate } from 'react-intl'; import classNames from 'classnames'; -import { Link } from 'react-router-dom'; +import { Link, withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; @@ -21,20 +21,14 @@ import VisibilityIcon from 'flavours/glitch/components/status_visibility_icon'; import PollContainer from 'flavours/glitch/containers/poll_container'; import Audio from 'flavours/glitch/features/audio'; import Video from 'flavours/glitch/features/video'; +import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import scheduleIdleTask from '../../ui/util/schedule_idle_task'; import Card from './card'; - - - class DetailedStatus extends ImmutablePureComponent { - static contextTypes = { - router: PropTypes.object, - }; - static propTypes = { status: ImmutablePropTypes.map, settings: ImmutablePropTypes.map.isRequired, @@ -54,6 +48,7 @@ class DetailedStatus extends ImmutablePureComponent { }), onToggleMediaVisibility: PropTypes.func, intl: PropTypes.object.isRequired, + ...WithRouterPropTypes, }; state = { @@ -61,18 +56,18 @@ class DetailedStatus extends ImmutablePureComponent { }; handleAccountClick = (e) => { - if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey) && this.context.router) { + if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey) && this.props.history) { e.preventDefault(); - this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`); + this.props.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`); } e.stopPropagation(); }; parseClick = (e, destination) => { - if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey) && this.context.router) { + if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey) && this.props.history) { e.preventDefault(); - this.context.router.history.push(destination); + this.props.history.push(destination); } e.stopPropagation(); @@ -253,7 +248,7 @@ class DetailedStatus extends ImmutablePureComponent { if (!['unlisted', 'public'].includes(status.get('visibility'))) { reblogLink = null; - } else if (this.context.router) { + } else if (this.props.history) { reblogLink = ( <> {' ยท '} @@ -279,7 +274,7 @@ class DetailedStatus extends ImmutablePureComponent { ); } - if (this.context.router) { + if (this.props.history) { favouriteLink = ( <Link to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}/favourites`} className='detailed-status__link'> <Icon id='star' /> @@ -344,4 +339,4 @@ class DetailedStatus extends ImmutablePureComponent { } -export default injectIntl(DetailedStatus); +export default withRouter(injectIntl(DetailedStatus)); diff --git a/app/javascript/flavours/glitch/features/status/index.jsx b/app/javascript/flavours/glitch/features/status/index.jsx index 25666aeea5..72a7e4009f 100644 --- a/app/javascript/flavours/glitch/features/status/index.jsx +++ b/app/javascript/flavours/glitch/features/status/index.jsx @@ -4,6 +4,7 @@ import { defineMessages, injectIntl } from 'react-intl'; import classNames from 'classnames'; import { Helmet } from 'react-helmet'; +import { withRouter } from 'react-router-dom'; import Immutable from 'immutable'; import ImmutablePropTypes from 'react-immutable-proptypes'; @@ -55,6 +56,7 @@ import Column from 'flavours/glitch/features/ui/components/column'; import { boostModal, favouriteModal, deleteModal } from 'flavours/glitch/initial_state'; import { makeGetStatus, makeGetPictureInPicture } from 'flavours/glitch/selectors'; import { autoUnfoldCW } from 'flavours/glitch/utils/content_warning'; +import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import ColumnHeader from '../../components/column_header'; import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../ui/util/fullscreen'; @@ -62,6 +64,7 @@ import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from import ActionBar from './components/action_bar'; import DetailedStatus from './components/detailed_status'; + const messages = defineMessages({ deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' }, @@ -182,7 +185,6 @@ const titleFromStatus = (intl, status) => { class Status extends ImmutablePureComponent { static contextTypes = { - router: PropTypes.object, identity: PropTypes.object, }; @@ -202,6 +204,7 @@ class Status extends ImmutablePureComponent { inUse: PropTypes.bool, available: PropTypes.bool, }), + ...WithRouterPropTypes }; state = { @@ -322,11 +325,11 @@ class Status extends ImmutablePureComponent { message: intl.formatMessage(messages.replyMessage), confirm: intl.formatMessage(messages.replyConfirm), onDoNotAsk: () => dispatch(changeLocalSetting(['confirm_before_clearing_draft'], false)), - onConfirm: () => dispatch(replyCompose(status, this.context.router.history)), + onConfirm: () => dispatch(replyCompose(status, this.props.history)), }, })); } else { - dispatch(replyCompose(status, this.context.router.history)); + dispatch(replyCompose(status, this.props.history)); } } else { dispatch(openModal({ @@ -403,12 +406,12 @@ class Status extends ImmutablePureComponent { this.props.dispatch(editStatus(status.get('id'), history)); }; - handleDirectClick = (account, router) => { - this.props.dispatch(directCompose(account, router)); + handleDirectClick = (account, history) => { + this.props.dispatch(directCompose(account, history)); }; - handleMentionClick = (account, router) => { - this.props.dispatch(mentionCompose(account, router)); + handleMentionClick = (account, history) => { + this.props.dispatch(mentionCompose(account, history)); }; handleOpenMedia = (media, index, lang) => { @@ -530,7 +533,7 @@ class Status extends ImmutablePureComponent { }; handleHotkeyOpenProfile = () => { - this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`); + this.props.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`); }; handleMoveUp = id => { @@ -783,4 +786,4 @@ class Status extends ImmutablePureComponent { } -export default injectIntl(connect(makeMapStateToProps)(Status)); +export default withRouter(injectIntl(connect(makeMapStateToProps)(Status))); diff --git a/app/javascript/flavours/glitch/features/ui/components/audio_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/audio_modal.jsx index a6e15e5439..09897c1634 100644 --- a/app/javascript/flavours/glitch/features/ui/components/audio_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/audio_modal.jsx @@ -26,10 +26,6 @@ class AudioModal extends ImmutablePureComponent { onChangeBackgroundColor: PropTypes.func.isRequired, }; - static contextTypes = { - router: PropTypes.object, - }; - render () { const { media, status, accountStaticAvatar, onClose } = this.props; const options = this.props.options || {}; diff --git a/app/javascript/flavours/glitch/features/ui/components/boost_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/boost_modal.jsx index b1c424ce34..d48553b4cf 100644 --- a/app/javascript/flavours/glitch/features/ui/components/boost_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/boost_modal.jsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import classNames from 'classnames'; +import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; @@ -18,6 +19,7 @@ import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp import StatusContent from 'flavours/glitch/components/status_content'; import VisibilityIcon from 'flavours/glitch/components/status_visibility_icon'; import PrivacyDropdown from 'flavours/glitch/features/compose/components/privacy_dropdown'; +import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; const messages = defineMessages({ cancel_reblog: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' }, @@ -39,17 +41,13 @@ const mapDispatchToProps = dispatch => { }; class BoostModal extends ImmutablePureComponent { - - static contextTypes = { - router: PropTypes.object, - }; - static propTypes = { status: ImmutablePropTypes.map.isRequired, onReblog: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired, missingMediaDescription: PropTypes.bool, intl: PropTypes.object.isRequired, + ...WithRouterPropTypes, }; componentDidMount() { @@ -65,7 +63,7 @@ class BoostModal extends ImmutablePureComponent { if (e.button === 0) { e.preventDefault(); this.props.onClose(); - this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`); + this.props.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`); } }; @@ -137,4 +135,4 @@ class BoostModal extends ImmutablePureComponent { } -export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(BoostModal)); +export default withRouter(connect(mapStateToProps, mapDispatchToProps)(injectIntl(BoostModal))); diff --git a/app/javascript/flavours/glitch/features/ui/components/columns_area.jsx b/app/javascript/flavours/glitch/features/ui/components/columns_area.jsx index 6eb5ee0e52..98303ef2d5 100644 --- a/app/javascript/flavours/glitch/features/ui/components/columns_area.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/columns_area.jsx @@ -45,11 +45,6 @@ const componentMap = { }; export default class ColumnsArea extends ImmutablePureComponent { - - static contextTypes = { - router: PropTypes.object.isRequired, - }; - static propTypes = { columns: ImmutablePropTypes.list.isRequired, singleColumn: PropTypes.bool, diff --git a/app/javascript/flavours/glitch/features/ui/components/favourite_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/favourite_modal.jsx index fa955b0483..0c4683ecf1 100644 --- a/app/javascript/flavours/glitch/features/ui/components/favourite_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/favourite_modal.jsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import classNames from 'classnames'; +import { withRouter } from 'react-router-dom'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; @@ -15,6 +16,7 @@ import { Icon } from 'flavours/glitch/components/icon'; import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp'; import StatusContent from 'flavours/glitch/components/status_content'; import VisibilityIcon from 'flavours/glitch/components/status_visibility_icon'; +import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; const messages = defineMessages({ favourite: { id: 'status.favourite', defaultMessage: 'Favourite' }, @@ -22,15 +24,12 @@ const messages = defineMessages({ class FavouriteModal extends ImmutablePureComponent { - static contextTypes = { - router: PropTypes.object, - }; - static propTypes = { status: ImmutablePropTypes.map.isRequired, onFavourite: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, + ...WithRouterPropTypes, }; componentDidMount() { @@ -46,7 +45,7 @@ class FavouriteModal extends ImmutablePureComponent { if (e.button === 0) { e.preventDefault(); this.props.onClose(); - this.context.router.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`); + this.props.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`); } }; @@ -100,4 +99,4 @@ class FavouriteModal extends ImmutablePureComponent { } -export default injectIntl(FavouriteModal); +export default withRouter(injectIntl(FavouriteModal)); diff --git a/app/javascript/flavours/glitch/features/ui/components/list_panel.jsx b/app/javascript/flavours/glitch/features/ui/components/list_panel.jsx index e6830e544a..9210bb4f39 100644 --- a/app/javascript/flavours/glitch/features/ui/components/list_panel.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/list_panel.jsx @@ -1,7 +1,5 @@ import PropTypes from 'prop-types'; -import { withRouter } from 'react-router-dom'; - import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; @@ -55,4 +53,4 @@ class ListPanel extends ImmutablePureComponent { } -export default withRouter(connect(mapStateToProps)(ListPanel)); +export default connect(mapStateToProps)(ListPanel); diff --git a/app/javascript/flavours/glitch/features/ui/components/media_modal.jsx b/app/javascript/flavours/glitch/features/ui/components/media_modal.jsx index 344f97fc00..29821c388f 100644 --- a/app/javascript/flavours/glitch/features/ui/components/media_modal.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/media_modal.jsx @@ -27,10 +27,6 @@ const messages = defineMessages({ class MediaModal extends ImmutablePureComponent { - static contextTypes = { - router: PropTypes.object, - }; - static propTypes = { media: ImmutablePropTypes.list.isRequired, statusId: PropTypes.string, diff --git a/app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx b/app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx index 601732b8bb..f42fdf8f5e 100644 --- a/app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx +++ b/app/javascript/flavours/glitch/features/ui/components/navigation_panel.jsx @@ -3,7 +3,7 @@ import { Component } from 'react'; import { defineMessages, injectIntl } from 'react-intl'; -import NavigationPortal from 'flavours/glitch/components/navigation_portal'; +import { NavigationPortal } from 'flavours/glitch/components/navigation_portal'; import { timelinePreview, trendsEnabled } from 'flavours/glitch/initial_state'; import { transientSingleColumn } from 'flavours/glitch/is_mobile'; import { preferencesLink } from 'flavours/glitch/utils/backend_links'; @@ -36,7 +36,6 @@ const messages = defineMessages({ class NavigationPanel extends Component { static contextTypes = { - router: PropTypes.object.isRequired, identity: PropTypes.object.isRequired, }; diff --git a/app/javascript/flavours/glitch/features/ui/index.jsx b/app/javascript/flavours/glitch/features/ui/index.jsx index 44f394657a..35b2a6db4e 100644 --- a/app/javascript/flavours/glitch/features/ui/index.jsx +++ b/app/javascript/flavours/glitch/features/ui/index.jsx @@ -22,6 +22,7 @@ import { expandHomeTimeline } from 'flavours/glitch/actions/timelines'; import PermaLink from 'flavours/glitch/components/permalink'; import PictureInPicture from 'flavours/glitch/features/picture_in_picture'; import { layoutFromWindow } from 'flavours/glitch/is_mobile'; +import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import initialState, { me, owner, singleUserMode, trendsEnabled, trendsAsLanding } from '../../initial_state'; @@ -269,8 +270,6 @@ class UI extends Component { hasMediaAttachments: PropTypes.bool, canUploadMore: PropTypes.bool, match: PropTypes.object.isRequired, - location: PropTypes.object.isRequired, - history: PropTypes.object.isRequired, intl: PropTypes.object.isRequired, dropdownMenuIsOpen: PropTypes.bool, unreadNotifications: PropTypes.number, @@ -280,6 +279,7 @@ class UI extends Component { layout: PropTypes.string.isRequired, firstLaunch: PropTypes.bool, username: PropTypes.string, + ...WithRouterPropTypes, }; state = { @@ -416,7 +416,7 @@ class UI extends Component { // On first launch, redirect to the follow recommendations page if (signedIn && this.props.firstLaunch) { - this.context.router.history.replace('/start'); + this.props.history.replace('/start'); // TODO: this.props.dispatch(closeOnboarding()); } diff --git a/app/javascript/flavours/glitch/features/ui/util/react_router_helpers.jsx b/app/javascript/flavours/glitch/features/ui/util/react_router_helpers.jsx index d0d034cbd4..0847aa4435 100644 --- a/app/javascript/flavours/glitch/features/ui/util/react_router_helpers.jsx +++ b/app/javascript/flavours/glitch/features/ui/util/react_router_helpers.jsx @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; -import { Component, PureComponent, cloneElement, Children } from 'react'; +import { Component, cloneElement, Children } from 'react'; -import { Switch, Route } from 'react-router-dom'; +import { Switch, Route, useLocation } from 'react-router-dom'; import StackTrace from 'stacktrace-js'; @@ -10,27 +10,20 @@ import ColumnLoading from 'flavours/glitch/features/ui/components/column_loading import BundleContainer from 'flavours/glitch/features/ui/containers/bundle_container'; // Small wrapper to pass multiColumn to the route components -export class WrappedSwitch extends PureComponent { - static contextTypes = { - router: PropTypes.object, - }; +export const WrappedSwitch = ({ multiColumn, children }) => { + const location = useLocation(); - render () { - const { multiColumn, children } = this.props; - const { location } = this.context.router.route; + const decklessLocation = multiColumn && location.pathname.startsWith('/deck') + ? {...location, pathname: location.pathname.slice(5)} + : location; - const decklessLocation = multiColumn && location.pathname.startsWith('/deck') - ? {...location, pathname: location.pathname.slice(5)} - : location; + return ( + <Switch location={decklessLocation}> + {Children.map(children, child => child ? cloneElement(child, { multiColumn }) : null)} + </Switch> + ); +}; - return ( - <Switch location={decklessLocation}> - {Children.map(children, child => child ? cloneElement(child, { multiColumn }) : null)} - </Switch> - ); - } - -} WrappedSwitch.propTypes = { multiColumn: PropTypes.bool, diff --git a/app/javascript/flavours/glitch/utils/dom_helpers.js b/app/javascript/flavours/glitch/utils/dom_helpers.js deleted file mode 100644 index bc5e200fcc..0000000000 --- a/app/javascript/flavours/glitch/utils/dom_helpers.js +++ /dev/null @@ -1,7 +0,0 @@ -// Focuses the root element. -export function focusRoot () { - let e; - if (document && (e = document.querySelector('.ui')) && (e = e.parentElement)) { - e.focus(); - } -} diff --git a/app/javascript/flavours/glitch/utils/react_router.jsx b/app/javascript/flavours/glitch/utils/react_router.jsx new file mode 100644 index 0000000000..a56883270b --- /dev/null +++ b/app/javascript/flavours/glitch/utils/react_router.jsx @@ -0,0 +1,60 @@ +import PropTypes from "prop-types"; + +import { __RouterContext } from "react-router"; + +import hoistStatics from "hoist-non-react-statics"; + +export const WithRouterPropTypes = { + match: PropTypes.object.isRequired, + location: PropTypes.object.isRequired, + history: PropTypes.object.isRequired, +}; + +export const WithOptionalRouterPropTypes = { + match: PropTypes.object, + location: PropTypes.object, + history: PropTypes.object, +}; + +// This is copied from https://github.com/remix-run/react-router/blob/v5.3.4/packages/react-router/modules/withRouter.js +// but does not fail if called outside of a React Router context +export function withOptionalRouter(Component) { + const displayName = `withRouter(${Component.displayName || Component.name})`; + const C = props => { + const { wrappedComponentRef, ...remainingProps } = props; + + return ( + <__RouterContext.Consumer> + {context => { + if(context) + return ( + <Component + {...remainingProps} + {...context} + ref={wrappedComponentRef} + /> + ); + else + return ( + <Component + {...remainingProps} + ref={wrappedComponentRef} + /> + ); + }} + </__RouterContext.Consumer> + ); + }; + + C.displayName = displayName; + C.WrappedComponent = Component; + C.propTypes = { + wrappedComponentRef: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.func, + PropTypes.object + ]) + }; + + return hoistStatics(C, Component); +}