2018-03-06 07:45:31 +01:00
|
|
|
import classNames from 'classnames';
|
2017-05-03 02:04:16 +02:00
|
|
|
import React from 'react';
|
2018-07-29 16:52:06 +02:00
|
|
|
import { HotKeys } from 'react-hotkeys';
|
|
|
|
import { defineMessages, injectIntl } from 'react-intl';
|
|
|
|
import { connect } from 'react-redux';
|
|
|
|
import { Redirect, withRouter } from 'react-router-dom';
|
2017-04-21 20:05:35 +02:00
|
|
|
import PropTypes from 'prop-types';
|
2018-07-29 16:52:06 +02:00
|
|
|
import NotificationsContainer from './containers/notifications_container';
|
2016-11-21 10:03:55 +01:00
|
|
|
import LoadingBarContainer from './containers/loading_bar_container';
|
|
|
|
import ModalContainer from './containers/modal_container';
|
2017-01-19 10:54:18 +01:00
|
|
|
import { isMobile } from '../../is_mobile';
|
2017-05-06 11:05:32 +02:00
|
|
|
import { debounce } from 'lodash';
|
2020-01-07 12:21:34 +01:00
|
|
|
import { resetCompose, uploadCompose } from '../../actions/compose';
|
2018-03-24 15:25:15 +01:00
|
|
|
import { expandHomeTimeline } from '../../actions/timelines';
|
2018-03-24 22:07:23 +01:00
|
|
|
import { expandNotifications } from '../../actions/notifications';
|
2018-06-29 15:34:36 +02:00
|
|
|
import { fetchFilters } from '../../actions/filters';
|
2017-09-13 10:24:33 +02:00
|
|
|
import { clearHeight } from '../../actions/height_cache';
|
2019-08-13 12:22:16 +02:00
|
|
|
import { focusApp, unfocusApp } from 'mastodon/actions/app';
|
2019-09-06 13:55:51 +02:00
|
|
|
import { submitMarkers } from 'mastodon/actions/markers';
|
2020-01-07 12:21:34 +01:00
|
|
|
import { WrappedRoute, WrappedSwitch } from './util/react_router_helpers';
|
2017-03-24 03:50:30 +01:00
|
|
|
import UploadArea from './components/upload_area';
|
2017-06-04 01:39:38 +02:00
|
|
|
import ColumnsAreaContainer from './containers/columns_area_container';
|
2019-08-13 12:22:16 +02:00
|
|
|
import DocumentTitle from './components/document_title';
|
2017-07-08 00:06:02 +02:00
|
|
|
import {
|
|
|
|
AccountGallery,
|
2020-01-07 12:21:34 +01:00
|
|
|
AccountTimeline,
|
|
|
|
Blocks,
|
|
|
|
BookmarkedStatuses,
|
|
|
|
CommunityTimeline,
|
|
|
|
Compose,
|
|
|
|
Directory,
|
|
|
|
DirectTimeline,
|
|
|
|
DomainBlocks,
|
|
|
|
FavouritedStatuses,
|
|
|
|
Favourites,
|
2017-07-08 00:06:02 +02:00
|
|
|
Followers,
|
|
|
|
Following,
|
|
|
|
FollowRequests,
|
|
|
|
GenericNotFound,
|
2020-01-07 12:21:34 +01:00
|
|
|
GettingStarted,
|
|
|
|
HashtagTimeline,
|
|
|
|
HomeTimeline,
|
|
|
|
KeyboardShortcuts,
|
|
|
|
Lists,
|
2017-11-25 00:35:37 +01:00
|
|
|
ListTimeline,
|
2017-07-08 00:06:02 +02:00
|
|
|
Mutes,
|
2020-01-07 12:21:34 +01:00
|
|
|
Notifications,
|
2017-09-07 09:58:11 +02:00
|
|
|
PinnedStatuses,
|
2020-01-07 12:21:34 +01:00
|
|
|
PublicTimeline,
|
|
|
|
Reblogs,
|
2019-05-25 21:27:00 +02:00
|
|
|
Search,
|
2020-01-07 12:21:34 +01:00
|
|
|
Status,
|
2017-07-08 00:06:02 +02:00
|
|
|
} from './util/async-components';
|
2020-01-07 12:21:34 +01:00
|
|
|
import { forceSingleColumn, me } from '../../initial_state';
|
2019-05-04 17:36:43 +02:00
|
|
|
import { previewState as previewMediaState } from './components/media_modal';
|
|
|
|
import { previewState as previewVideoState } from './components/video_modal';
|
2017-07-08 00:06:02 +02:00
|
|
|
// Dummy import, to make sure that <Status /> ends up in the application bundle.
|
|
|
|
// Without this it ends up in ~8 very commonly used bundles.
|
|
|
|
import '../../components/status';
|
2017-06-20 20:40:03 +02:00
|
|
|
|
2017-11-09 14:34:41 +01:00
|
|
|
const messages = defineMessages({
|
|
|
|
beforeUnload: { id: 'ui.beforeunload', defaultMessage: 'Your draft will be lost if you leave Mastodon.' },
|
|
|
|
});
|
|
|
|
|
2017-07-06 22:39:56 +02:00
|
|
|
const mapStateToProps = state => ({
|
2020-01-07 12:21:34 +01:00
|
|
|
isComposing : state.getIn(['compose', 'is_composing']),
|
|
|
|
hasComposingText : state.getIn(['compose', 'text']).trim().length !== 0,
|
2018-09-21 18:54:17 +02:00
|
|
|
hasMediaAttachments: state.getIn(['compose', 'media_attachments']).size > 0,
|
2020-01-07 12:21:34 +01:00
|
|
|
canUploadMore : !state.getIn(['compose', 'media_attachments']).some(x => ['audio', 'video'].includes(x.get('type'))) && state.getIn(['compose', 'media_attachments']).size < 4,
|
|
|
|
dropdownMenuIsOpen : state.getIn(['dropdown_menu', 'openId']) !== null,
|
2017-07-06 22:39:56 +02:00
|
|
|
});
|
|
|
|
|
2017-10-06 01:07:59 +02:00
|
|
|
const keyMap = {
|
2020-01-07 12:21:34 +01:00
|
|
|
help : '?',
|
|
|
|
new : 'n',
|
|
|
|
search : 's',
|
|
|
|
forceNew : 'option+n',
|
|
|
|
focusColumn : ['1', '2', '3', '4', '5', '6', '7', '8', '9'],
|
|
|
|
reply : 'r',
|
|
|
|
favourite : 'f',
|
|
|
|
boost : 'b',
|
|
|
|
mention : 'm',
|
|
|
|
open : ['enter', 'o'],
|
|
|
|
openProfile : 'p',
|
|
|
|
moveDown : ['down', 'j'],
|
|
|
|
moveUp : ['up', 'k'],
|
|
|
|
back : 'backspace',
|
|
|
|
goToHome : 'g h',
|
2017-10-06 01:07:59 +02:00
|
|
|
goToNotifications: 'g n',
|
2020-01-07 12:21:34 +01:00
|
|
|
goToLocal : 'g l',
|
|
|
|
goToFederated : 'g t',
|
|
|
|
goToDirect : 'g d',
|
|
|
|
goToStart : 'g s',
|
|
|
|
goToFavourites : 'g f',
|
|
|
|
goToPinned : 'g p',
|
|
|
|
goToProfile : 'g u',
|
|
|
|
goToBlocked : 'g b',
|
|
|
|
goToMuted : 'g m',
|
|
|
|
goToRequests : 'g r',
|
|
|
|
toggleHidden : 'x',
|
|
|
|
toggleSensitive : 'h',
|
|
|
|
openMedia : 'e',
|
2017-10-06 01:07:59 +02:00
|
|
|
};
|
|
|
|
|
2018-03-06 07:45:31 +01:00
|
|
|
class SwitchingColumnsArea extends React.PureComponent {
|
|
|
|
|
|
|
|
static propTypes = {
|
2020-01-07 12:21:34 +01:00
|
|
|
children : PropTypes.node,
|
|
|
|
location : PropTypes.object,
|
2018-03-06 07:45:31 +01:00
|
|
|
onLayoutChange: PropTypes.func.isRequired,
|
|
|
|
};
|
|
|
|
|
|
|
|
state = {
|
|
|
|
mobile: isMobile(window.innerWidth),
|
|
|
|
};
|
2020-01-07 12:21:34 +01:00
|
|
|
handleLayoutChange = debounce(() => {
|
|
|
|
// The cached heights are no longer accurate, invalidate
|
|
|
|
this.props.onLayoutChange();
|
|
|
|
}, 500, {
|
|
|
|
trailing: true,
|
|
|
|
});
|
2018-03-06 07:45:31 +01:00
|
|
|
|
2020-01-07 12:21:34 +01:00
|
|
|
componentWillMount() {
|
2018-03-06 07:45:31 +01:00
|
|
|
window.addEventListener('resize', this.handleResize, { passive: true });
|
2019-07-19 09:25:22 +02:00
|
|
|
|
|
|
|
if (this.state.mobile || forceSingleColumn) {
|
|
|
|
document.body.classList.toggle('layout-single-column', true);
|
|
|
|
document.body.classList.toggle('layout-multiple-columns', false);
|
|
|
|
} else {
|
|
|
|
document.body.classList.toggle('layout-single-column', false);
|
|
|
|
document.body.classList.toggle('layout-multiple-columns', true);
|
|
|
|
}
|
2018-03-06 07:45:31 +01:00
|
|
|
}
|
|
|
|
|
2020-01-07 12:21:34 +01:00
|
|
|
componentDidUpdate(prevProps, prevState) {
|
2018-03-06 07:45:31 +01:00
|
|
|
if (![this.props.location.pathname, '/'].includes(prevProps.location.pathname)) {
|
|
|
|
this.node.handleChildrenContentChange();
|
|
|
|
}
|
2019-07-19 09:25:22 +02:00
|
|
|
|
|
|
|
if (prevState.mobile !== this.state.mobile && !forceSingleColumn) {
|
|
|
|
document.body.classList.toggle('layout-single-column', this.state.mobile);
|
|
|
|
document.body.classList.toggle('layout-multiple-columns', !this.state.mobile);
|
|
|
|
}
|
2018-03-06 07:45:31 +01:00
|
|
|
}
|
|
|
|
|
2020-01-07 12:21:34 +01:00
|
|
|
componentWillUnmount() {
|
2018-03-06 07:45:31 +01:00
|
|
|
window.removeEventListener('resize', this.handleResize);
|
|
|
|
}
|
|
|
|
|
2020-01-07 12:21:34 +01:00
|
|
|
shouldUpdateScroll(_, { location }) {
|
2019-05-04 17:36:43 +02:00
|
|
|
return location.state !== previewMediaState && location.state !== previewVideoState;
|
2018-07-29 16:52:06 +02:00
|
|
|
}
|
|
|
|
|
2019-08-25 15:48:50 +02:00
|
|
|
handleResize = () => {
|
|
|
|
const mobile = isMobile(window.innerWidth);
|
|
|
|
|
|
|
|
if (mobile !== this.state.mobile) {
|
|
|
|
this.handleLayoutChange.cancel();
|
|
|
|
this.props.onLayoutChange();
|
|
|
|
this.setState({ mobile });
|
|
|
|
} else {
|
|
|
|
this.handleLayoutChange();
|
|
|
|
}
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2018-03-06 07:45:31 +01:00
|
|
|
|
|
|
|
setRef = c => {
|
2019-11-04 12:58:19 +01:00
|
|
|
if (c) {
|
|
|
|
this.node = c.getWrappedInstance();
|
|
|
|
}
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2018-03-06 07:45:31 +01:00
|
|
|
|
2020-01-07 12:21:34 +01:00
|
|
|
render() {
|
2019-05-25 21:27:00 +02:00
|
|
|
const { children } = this.props;
|
2018-03-06 07:45:31 +01:00
|
|
|
const { mobile } = this.state;
|
2019-05-23 01:35:22 +02:00
|
|
|
const singleColumn = forceSingleColumn || mobile;
|
2020-01-07 12:21:34 +01:00
|
|
|
const redirect = singleColumn ? (<Redirect
|
|
|
|
from='/'
|
|
|
|
to='/timelines/home'
|
|
|
|
exact
|
|
|
|
/>) : (<Redirect
|
|
|
|
from='/'
|
|
|
|
to='/getting-started'
|
|
|
|
exact
|
|
|
|
/>);
|
2018-03-06 07:45:31 +01:00
|
|
|
|
|
|
|
return (
|
2020-01-07 12:21:34 +01:00
|
|
|
<ColumnsAreaContainer
|
|
|
|
ref={this.setRef}
|
|
|
|
singleColumn={singleColumn}
|
|
|
|
>
|
|
|
|
<WrappedSwitch >
|
2018-05-30 18:42:06 +02:00
|
|
|
{redirect}
|
2020-01-07 12:21:34 +01:00
|
|
|
<WrappedRoute
|
|
|
|
path='/tk-example'
|
|
|
|
component={GettingStarted}
|
|
|
|
content={children}
|
|
|
|
/>
|
|
|
|
<WrappedRoute
|
|
|
|
path='/getting-started'
|
|
|
|
component={GettingStarted}
|
|
|
|
content={children}
|
|
|
|
/>
|
|
|
|
<WrappedRoute
|
|
|
|
path='/keyboard-shortcuts'
|
|
|
|
component={KeyboardShortcuts}
|
|
|
|
content={children}
|
|
|
|
/>
|
|
|
|
<WrappedRoute
|
|
|
|
path='/timelines/home'
|
|
|
|
component={HomeTimeline}
|
|
|
|
content={children}
|
|
|
|
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
|
|
|
|
/>
|
|
|
|
<WrappedRoute
|
|
|
|
path='/timelines/public'
|
|
|
|
exact
|
|
|
|
component={PublicTimeline}
|
|
|
|
content={children}
|
|
|
|
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
|
|
|
|
/>
|
|
|
|
<WrappedRoute
|
|
|
|
path='/timelines/public/local'
|
|
|
|
exact
|
|
|
|
component={CommunityTimeline}
|
|
|
|
content={children}
|
|
|
|
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
|
|
|
|
/>
|
|
|
|
<WrappedRoute
|
|
|
|
path='/timelines/direct'
|
|
|
|
component={DirectTimeline}
|
|
|
|
content={children}
|
|
|
|
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
|
|
|
|
/>
|
|
|
|
<WrappedRoute
|
|
|
|
path='/timelines/tag/:id'
|
|
|
|
component={HashtagTimeline}
|
|
|
|
content={children}
|
|
|
|
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
|
|
|
|
/>
|
|
|
|
<WrappedRoute
|
|
|
|
path='/timelines/list/:id'
|
|
|
|
component={ListTimeline}
|
|
|
|
content={children}
|
|
|
|
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
|
|
|
|
/>
|
|
|
|
|
|
|
|
<WrappedRoute
|
|
|
|
path='/notifications'
|
|
|
|
component={Notifications}
|
|
|
|
content={children}
|
|
|
|
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
|
|
|
|
/>
|
|
|
|
<WrappedRoute
|
|
|
|
path='/favourites'
|
|
|
|
component={FavouritedStatuses}
|
|
|
|
content={children}
|
|
|
|
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
|
|
|
|
/>
|
|
|
|
<WrappedRoute
|
|
|
|
path='/bookmarks'
|
|
|
|
component={BookmarkedStatuses}
|
|
|
|
content={children}
|
|
|
|
/>
|
|
|
|
<WrappedRoute
|
|
|
|
path='/pinned'
|
|
|
|
component={PinnedStatuses}
|
|
|
|
content={children}
|
|
|
|
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
|
|
|
|
/>
|
|
|
|
|
|
|
|
<WrappedRoute
|
|
|
|
path='/search'
|
|
|
|
component={Search}
|
|
|
|
content={children}
|
|
|
|
/>
|
|
|
|
<WrappedRoute
|
|
|
|
path='/directory'
|
|
|
|
component={Directory}
|
|
|
|
content={children}
|
|
|
|
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
|
|
|
|
/>
|
|
|
|
|
|
|
|
<WrappedRoute
|
|
|
|
path='/statuses/new'
|
|
|
|
component={Compose}
|
|
|
|
content={children}
|
|
|
|
/>
|
|
|
|
<WrappedRoute
|
|
|
|
path='/statuses/:statusId'
|
|
|
|
exact
|
|
|
|
component={Status}
|
|
|
|
content={children}
|
|
|
|
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
|
|
|
|
/>
|
|
|
|
<WrappedRoute
|
|
|
|
path='/statuses/:statusId/reblogs'
|
|
|
|
component={Reblogs}
|
|
|
|
content={children}
|
|
|
|
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
|
|
|
|
/>
|
|
|
|
<WrappedRoute
|
|
|
|
path='/statuses/:statusId/favourites'
|
|
|
|
component={Favourites}
|
|
|
|
content={children}
|
|
|
|
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
|
|
|
|
/>
|
|
|
|
|
|
|
|
<WrappedRoute
|
|
|
|
path='/accounts/:accountId'
|
|
|
|
exact
|
|
|
|
component={AccountTimeline}
|
|
|
|
content={children}
|
|
|
|
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
|
|
|
|
/>
|
|
|
|
<WrappedRoute
|
|
|
|
path='/accounts/:accountId/with_replies'
|
|
|
|
component={AccountTimeline}
|
|
|
|
content={children}
|
|
|
|
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll, withReplies: true }}
|
|
|
|
/>
|
|
|
|
<WrappedRoute
|
|
|
|
path='/accounts/:accountId/followers'
|
|
|
|
component={Followers}
|
|
|
|
content={children}
|
|
|
|
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
|
|
|
|
/>
|
|
|
|
<WrappedRoute
|
|
|
|
path='/accounts/:accountId/following'
|
|
|
|
component={Following}
|
|
|
|
content={children}
|
|
|
|
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
|
|
|
|
/>
|
|
|
|
<WrappedRoute
|
|
|
|
path='/accounts/:accountId/media'
|
|
|
|
component={AccountGallery}
|
|
|
|
content={children}
|
|
|
|
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
|
|
|
|
/>
|
|
|
|
|
|
|
|
<WrappedRoute
|
|
|
|
path='/follow_requests'
|
|
|
|
component={FollowRequests}
|
|
|
|
content={children}
|
|
|
|
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
|
|
|
|
/>
|
|
|
|
<WrappedRoute
|
|
|
|
path='/blocks'
|
|
|
|
component={Blocks}
|
|
|
|
content={children}
|
|
|
|
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
|
|
|
|
/>
|
|
|
|
<WrappedRoute
|
|
|
|
path='/domain_blocks'
|
|
|
|
component={DomainBlocks}
|
|
|
|
content={children}
|
|
|
|
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
|
|
|
|
/>
|
|
|
|
<WrappedRoute
|
|
|
|
path='/mutes'
|
|
|
|
component={Mutes}
|
|
|
|
content={children}
|
|
|
|
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
|
|
|
|
/>
|
|
|
|
<WrappedRoute
|
|
|
|
path='/lists'
|
|
|
|
component={Lists}
|
|
|
|
content={children}
|
|
|
|
componentParams={{ shouldUpdateScroll: this.shouldUpdateScroll }}
|
|
|
|
/>
|
|
|
|
|
|
|
|
<WrappedRoute
|
|
|
|
component={GenericNotFound}
|
|
|
|
content={children}
|
|
|
|
/>
|
|
|
|
</WrappedSwitch >
|
|
|
|
</ColumnsAreaContainer >
|
2018-03-06 07:45:31 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2018-09-14 17:59:48 +02:00
|
|
|
export default @connect(mapStateToProps)
|
2017-11-09 14:34:41 +01:00
|
|
|
@injectIntl
|
2017-08-29 14:16:21 +02:00
|
|
|
@withRouter
|
2018-09-14 17:59:48 +02:00
|
|
|
class UI extends React.PureComponent {
|
2016-09-19 23:25:59 +02:00
|
|
|
|
2017-07-28 05:06:01 +02:00
|
|
|
static contextTypes = {
|
|
|
|
router: PropTypes.object.isRequired,
|
2017-09-22 04:59:17 +02:00
|
|
|
};
|
2017-07-28 05:06:01 +02:00
|
|
|
|
2017-05-12 14:44:10 +02:00
|
|
|
static propTypes = {
|
2020-01-07 12:21:34 +01:00
|
|
|
dispatch : PropTypes.func.isRequired,
|
|
|
|
children : PropTypes.node,
|
|
|
|
isComposing : PropTypes.bool,
|
|
|
|
hasComposingText : PropTypes.bool,
|
2018-09-21 18:54:17 +02:00
|
|
|
hasMediaAttachments: PropTypes.bool,
|
2020-01-07 12:21:34 +01:00
|
|
|
canUploadMore : PropTypes.bool,
|
|
|
|
location : PropTypes.object,
|
|
|
|
intl : PropTypes.object.isRequired,
|
|
|
|
dropdownMenuIsOpen : PropTypes.bool,
|
2017-05-12 14:44:10 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
state = {
|
2017-05-20 17:31:47 +02:00
|
|
|
draggingOver: false,
|
2017-05-12 14:44:10 +02:00
|
|
|
};
|
2016-09-19 23:25:59 +02:00
|
|
|
|
2019-08-13 12:22:16 +02:00
|
|
|
handleBeforeUnload = e => {
|
2019-09-06 13:55:51 +02:00
|
|
|
const { intl, dispatch, isComposing, hasComposingText, hasMediaAttachments } = this.props;
|
|
|
|
|
|
|
|
dispatch(submitMarkers());
|
2017-11-09 14:34:41 +01:00
|
|
|
|
2018-09-21 18:54:17 +02:00
|
|
|
if (isComposing && (hasComposingText || hasMediaAttachments)) {
|
2017-11-09 14:34:41 +01:00
|
|
|
// Setting returnValue to any string causes confirmation dialog.
|
|
|
|
// Many browsers no longer display this text to users,
|
|
|
|
// but we set user-friendly message for other browsers, e.g. Edge.
|
|
|
|
e.returnValue = intl.formatMessage(messages.beforeUnload);
|
|
|
|
}
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2017-11-09 14:34:41 +01:00
|
|
|
|
2019-08-13 12:22:16 +02:00
|
|
|
handleWindowFocus = () => {
|
|
|
|
this.props.dispatch(focusApp());
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2019-08-13 12:22:16 +02:00
|
|
|
|
|
|
|
handleWindowBlur = () => {
|
|
|
|
this.props.dispatch(unfocusApp());
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2019-08-13 12:22:16 +02:00
|
|
|
|
2018-03-06 07:45:31 +01:00
|
|
|
handleLayoutChange = () => {
|
2017-08-07 20:32:03 +02:00
|
|
|
// The cached heights are no longer accurate, invalidate
|
2017-09-13 10:24:33 +02:00
|
|
|
this.props.dispatch(clearHeight());
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2016-12-06 19:18:37 +01:00
|
|
|
|
2017-05-12 14:44:10 +02:00
|
|
|
handleDragEnter = (e) => {
|
2017-03-31 11:48:25 +02:00
|
|
|
e.preventDefault();
|
|
|
|
|
|
|
|
if (!this.dragTargets) {
|
|
|
|
this.dragTargets = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.dragTargets.indexOf(e.target) === -1) {
|
|
|
|
this.dragTargets.push(e.target);
|
|
|
|
}
|
|
|
|
|
2019-09-16 20:42:19 +02:00
|
|
|
if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files') && this.props.canUploadMore) {
|
2017-04-01 22:11:28 +02:00
|
|
|
this.setState({ draggingOver: true });
|
|
|
|
}
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2017-03-31 11:48:25 +02:00
|
|
|
|
2017-05-12 14:44:10 +02:00
|
|
|
handleDragOver = (e) => {
|
2019-01-17 23:27:51 +01:00
|
|
|
if (this.dataTransferIsText(e.dataTransfer)) return false;
|
2019-09-16 20:42:19 +02:00
|
|
|
|
2016-12-11 23:35:06 +01:00
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
|
2017-03-31 11:48:25 +02:00
|
|
|
try {
|
|
|
|
e.dataTransfer.dropEffect = 'copy';
|
|
|
|
} catch (err) {
|
2016-12-11 23:35:06 +01:00
|
|
|
|
|
|
|
}
|
2017-03-31 11:48:25 +02:00
|
|
|
|
|
|
|
return false;
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2016-12-11 23:35:06 +01:00
|
|
|
|
2017-05-12 14:44:10 +02:00
|
|
|
handleDrop = (e) => {
|
2019-01-17 23:27:51 +01:00
|
|
|
if (this.dataTransferIsText(e.dataTransfer)) return;
|
2019-09-16 20:42:19 +02:00
|
|
|
|
2016-12-11 23:35:06 +01:00
|
|
|
e.preventDefault();
|
|
|
|
|
2017-03-28 14:17:24 +02:00
|
|
|
this.setState({ draggingOver: false });
|
2019-01-14 08:44:26 +01:00
|
|
|
this.dragTargets = [];
|
2017-03-28 14:17:24 +02:00
|
|
|
|
2019-09-16 20:42:19 +02:00
|
|
|
if (e.dataTransfer && e.dataTransfer.files.length >= 1 && this.props.canUploadMore) {
|
2016-12-11 23:35:06 +01:00
|
|
|
this.props.dispatch(uploadCompose(e.dataTransfer.files));
|
|
|
|
}
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2016-12-11 23:35:06 +01:00
|
|
|
|
2017-05-12 14:44:10 +02:00
|
|
|
handleDragLeave = (e) => {
|
2017-03-31 11:48:25 +02:00
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
|
|
|
|
this.dragTargets = this.dragTargets.filter(el => el !== e.target && this.node.contains(el));
|
|
|
|
|
|
|
|
if (this.dragTargets.length > 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-03-24 03:50:30 +01:00
|
|
|
this.setState({ draggingOver: false });
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2017-03-24 03:50:30 +01:00
|
|
|
|
2019-01-17 23:27:51 +01:00
|
|
|
dataTransferIsText = (dataTransfer) => {
|
2019-10-02 17:10:56 +02:00
|
|
|
return (dataTransfer && Array.from(dataTransfer.types).filter((type) => type === 'text/plain').length === 1);
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2019-01-17 23:27:51 +01:00
|
|
|
|
2017-05-12 14:44:10 +02:00
|
|
|
closeUploadModal = () => {
|
2017-04-24 20:19:33 +02:00
|
|
|
this.setState({ draggingOver: false });
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2017-04-24 20:19:33 +02:00
|
|
|
|
2017-07-28 05:06:01 +02:00
|
|
|
handleServiceWorkerPostMessage = ({ data }) => {
|
|
|
|
if (data.type === 'navigate') {
|
|
|
|
this.context.router.history.push(data.path);
|
|
|
|
} else {
|
2017-08-24 12:15:36 +02:00
|
|
|
console.warn('Unknown message type:', data.type);
|
2017-07-28 05:06:01 +02:00
|
|
|
}
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2017-07-28 05:06:01 +02:00
|
|
|
|
2020-01-07 12:21:34 +01:00
|
|
|
componentWillMount() {
|
2019-08-13 12:22:16 +02:00
|
|
|
window.addEventListener('focus', this.handleWindowFocus, false);
|
|
|
|
window.addEventListener('blur', this.handleWindowBlur, false);
|
2017-11-09 14:34:41 +01:00
|
|
|
window.addEventListener('beforeunload', this.handleBeforeUnload, false);
|
2018-12-17 11:07:17 +01:00
|
|
|
|
2017-03-31 11:48:25 +02:00
|
|
|
document.addEventListener('dragenter', this.handleDragEnter, false);
|
|
|
|
document.addEventListener('dragover', this.handleDragOver, false);
|
|
|
|
document.addEventListener('drop', this.handleDrop, false);
|
|
|
|
document.addEventListener('dragleave', this.handleDragLeave, false);
|
2017-04-24 20:19:33 +02:00
|
|
|
document.addEventListener('dragend', this.handleDragEnd, false);
|
2017-01-19 10:54:18 +01:00
|
|
|
|
2020-01-07 12:21:34 +01:00
|
|
|
if ('serviceWorker' in navigator) {
|
2017-07-28 05:06:01 +02:00
|
|
|
navigator.serviceWorker.addEventListener('message', this.handleServiceWorkerPostMessage);
|
|
|
|
}
|
|
|
|
|
2018-12-17 11:07:17 +01:00
|
|
|
if (typeof window.Notification !== 'undefined' && Notification.permission === 'default') {
|
|
|
|
window.setTimeout(() => Notification.requestPermission(), 120 * 1000);
|
|
|
|
}
|
|
|
|
|
2018-03-24 15:25:15 +01:00
|
|
|
this.props.dispatch(expandHomeTimeline());
|
2018-03-24 22:07:23 +01:00
|
|
|
this.props.dispatch(expandNotifications());
|
2018-12-17 11:07:17 +01:00
|
|
|
|
2018-06-29 15:34:36 +02:00
|
|
|
setTimeout(() => this.props.dispatch(fetchFilters()), 500);
|
2017-04-21 20:05:35 +02:00
|
|
|
}
|
2016-12-06 19:18:37 +01:00
|
|
|
|
2020-01-07 12:21:34 +01:00
|
|
|
componentDidMount() {
|
2017-10-06 01:07:59 +02:00
|
|
|
this.hotkeys.__mousetrap__.stopCallback = (e, element) => {
|
2017-10-11 16:31:07 +02:00
|
|
|
return ['TEXTAREA', 'SELECT', 'INPUT'].includes(element.tagName);
|
2017-10-06 01:07:59 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-01-07 12:21:34 +01:00
|
|
|
componentWillUnmount() {
|
2019-08-13 12:22:16 +02:00
|
|
|
window.removeEventListener('focus', this.handleWindowFocus);
|
|
|
|
window.removeEventListener('blur', this.handleWindowBlur);
|
2017-11-09 14:34:41 +01:00
|
|
|
window.removeEventListener('beforeunload', this.handleBeforeUnload);
|
2019-08-13 12:22:16 +02:00
|
|
|
|
2017-03-31 11:48:25 +02:00
|
|
|
document.removeEventListener('dragenter', this.handleDragEnter);
|
|
|
|
document.removeEventListener('dragover', this.handleDragOver);
|
|
|
|
document.removeEventListener('drop', this.handleDrop);
|
|
|
|
document.removeEventListener('dragleave', this.handleDragLeave);
|
2017-04-24 20:19:33 +02:00
|
|
|
document.removeEventListener('dragend', this.handleDragEnd);
|
2017-04-21 20:05:35 +02:00
|
|
|
}
|
2017-03-31 11:48:25 +02:00
|
|
|
|
2017-09-22 04:59:17 +02:00
|
|
|
setRef = c => {
|
2017-03-31 11:48:25 +02:00
|
|
|
this.node = c;
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2016-12-06 19:18:37 +01:00
|
|
|
|
2017-10-06 01:07:59 +02:00
|
|
|
handleHotkeyNew = e => {
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
|
|
const element = this.node.querySelector('.compose-form__autosuggest-wrapper textarea');
|
|
|
|
|
|
|
|
if (element) {
|
|
|
|
element.focus();
|
|
|
|
}
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2017-10-06 01:07:59 +02:00
|
|
|
|
|
|
|
handleHotkeySearch = e => {
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
|
|
const element = this.node.querySelector('.search__input');
|
|
|
|
|
|
|
|
if (element) {
|
|
|
|
element.focus();
|
|
|
|
}
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2017-10-06 01:07:59 +02:00
|
|
|
|
|
|
|
handleHotkeyForceNew = e => {
|
|
|
|
this.handleHotkeyNew(e);
|
|
|
|
this.props.dispatch(resetCompose());
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2017-10-06 01:07:59 +02:00
|
|
|
|
|
|
|
handleHotkeyFocusColumn = e => {
|
2020-01-07 12:21:34 +01:00
|
|
|
const index = (e.key * 1) + 1; // First child is drawer, skip that
|
2017-10-06 01:07:59 +02:00
|
|
|
const column = this.node.querySelector(`.column:nth-child(${index})`);
|
2019-05-03 06:20:36 +02:00
|
|
|
if (!column) return;
|
|
|
|
const container = column.querySelector('.scrollable');
|
2017-10-06 01:07:59 +02:00
|
|
|
|
2019-05-03 06:20:36 +02:00
|
|
|
if (container) {
|
|
|
|
const status = container.querySelector('.focusable');
|
2017-10-06 01:07:59 +02:00
|
|
|
|
|
|
|
if (status) {
|
2019-05-03 06:20:36 +02:00
|
|
|
if (container.scrollTop > status.offsetTop) {
|
|
|
|
status.scrollIntoView(true);
|
|
|
|
}
|
2017-10-06 01:07:59 +02:00
|
|
|
status.focus();
|
|
|
|
}
|
|
|
|
}
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2017-10-06 01:07:59 +02:00
|
|
|
|
|
|
|
handleHotkeyBack = () => {
|
|
|
|
if (window.history && window.history.length === 1) {
|
|
|
|
this.context.router.history.push('/');
|
|
|
|
} else {
|
|
|
|
this.context.router.history.goBack();
|
|
|
|
}
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2017-10-06 01:07:59 +02:00
|
|
|
|
|
|
|
setHotkeysRef = c => {
|
|
|
|
this.hotkeys = c;
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2017-10-06 01:07:59 +02:00
|
|
|
|
2017-11-27 21:31:58 +01:00
|
|
|
handleHotkeyToggleHelp = () => {
|
|
|
|
if (this.props.location.pathname === '/keyboard-shortcuts') {
|
|
|
|
this.context.router.history.goBack();
|
|
|
|
} else {
|
|
|
|
this.context.router.history.push('/keyboard-shortcuts');
|
|
|
|
}
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2017-11-27 21:31:58 +01:00
|
|
|
|
2017-10-06 01:07:59 +02:00
|
|
|
handleHotkeyGoToHome = () => {
|
|
|
|
this.context.router.history.push('/timelines/home');
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2017-10-06 01:07:59 +02:00
|
|
|
|
|
|
|
handleHotkeyGoToNotifications = () => {
|
|
|
|
this.context.router.history.push('/notifications');
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2017-10-06 01:07:59 +02:00
|
|
|
|
|
|
|
handleHotkeyGoToLocal = () => {
|
|
|
|
this.context.router.history.push('/timelines/public/local');
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2017-10-06 01:07:59 +02:00
|
|
|
|
|
|
|
handleHotkeyGoToFederated = () => {
|
|
|
|
this.context.router.history.push('/timelines/public');
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2017-10-06 01:07:59 +02:00
|
|
|
|
2018-04-18 13:09:06 +02:00
|
|
|
handleHotkeyGoToDirect = () => {
|
|
|
|
this.context.router.history.push('/timelines/direct');
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2018-04-18 13:09:06 +02:00
|
|
|
|
2017-10-06 01:07:59 +02:00
|
|
|
handleHotkeyGoToStart = () => {
|
|
|
|
this.context.router.history.push('/getting-started');
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2017-10-06 01:07:59 +02:00
|
|
|
|
|
|
|
handleHotkeyGoToFavourites = () => {
|
|
|
|
this.context.router.history.push('/favourites');
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2017-10-06 01:07:59 +02:00
|
|
|
|
|
|
|
handleHotkeyGoToPinned = () => {
|
|
|
|
this.context.router.history.push('/pinned');
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2017-10-06 01:07:59 +02:00
|
|
|
|
|
|
|
handleHotkeyGoToProfile = () => {
|
2017-10-31 03:27:48 +01:00
|
|
|
this.context.router.history.push(`/accounts/${me}`);
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2017-10-06 01:07:59 +02:00
|
|
|
|
|
|
|
handleHotkeyGoToBlocked = () => {
|
|
|
|
this.context.router.history.push('/blocks');
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2017-10-06 01:07:59 +02:00
|
|
|
|
|
|
|
handleHotkeyGoToMuted = () => {
|
|
|
|
this.context.router.history.push('/mutes');
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2017-09-22 04:59:17 +02:00
|
|
|
|
2018-08-20 03:44:16 +02:00
|
|
|
handleHotkeyGoToRequests = () => {
|
|
|
|
this.context.router.history.push('/follow_requests');
|
2020-01-07 12:21:34 +01:00
|
|
|
};
|
2018-08-20 03:44:16 +02:00
|
|
|
|
2020-01-07 12:21:34 +01:00
|
|
|
render() {
|
2018-03-06 07:45:31 +01:00
|
|
|
const { draggingOver } = this.state;
|
2019-05-25 21:27:00 +02:00
|
|
|
const { children, isComposing, location, dropdownMenuIsOpen } = this.props;
|
2017-03-24 03:50:30 +01:00
|
|
|
|
2017-10-06 01:07:59 +02:00
|
|
|
const handlers = {
|
2020-01-07 12:21:34 +01:00
|
|
|
help : this.handleHotkeyToggleHelp,
|
|
|
|
new : this.handleHotkeyNew,
|
|
|
|
search : this.handleHotkeySearch,
|
|
|
|
forceNew : this.handleHotkeyForceNew,
|
|
|
|
focusColumn : this.handleHotkeyFocusColumn,
|
|
|
|
back : this.handleHotkeyBack,
|
|
|
|
goToHome : this.handleHotkeyGoToHome,
|
2017-10-06 01:07:59 +02:00
|
|
|
goToNotifications: this.handleHotkeyGoToNotifications,
|
2020-01-07 12:21:34 +01:00
|
|
|
goToLocal : this.handleHotkeyGoToLocal,
|
|
|
|
goToFederated : this.handleHotkeyGoToFederated,
|
|
|
|
goToDirect : this.handleHotkeyGoToDirect,
|
|
|
|
goToStart : this.handleHotkeyGoToStart,
|
|
|
|
goToFavourites : this.handleHotkeyGoToFavourites,
|
|
|
|
goToPinned : this.handleHotkeyGoToPinned,
|
|
|
|
goToProfile : this.handleHotkeyGoToProfile,
|
|
|
|
goToBlocked : this.handleHotkeyGoToBlocked,
|
|
|
|
goToMuted : this.handleHotkeyGoToMuted,
|
|
|
|
goToRequests : this.handleHotkeyGoToRequests,
|
2017-10-06 01:07:59 +02:00
|
|
|
};
|
|
|
|
|
2016-09-19 23:25:59 +02:00
|
|
|
return (
|
2020-01-07 12:21:34 +01:00
|
|
|
<HotKeys
|
|
|
|
keyMap={keyMap}
|
|
|
|
handlers={handlers}
|
|
|
|
ref={this.setHotkeysRef}
|
|
|
|
attach={window}
|
|
|
|
focused
|
|
|
|
>
|
|
|
|
<div
|
|
|
|
className={classNames('ui', { 'is-composing': isComposing })}
|
|
|
|
ref={this.setRef}
|
|
|
|
style={{ pointerEvents: dropdownMenuIsOpen ? 'none' : null }}
|
|
|
|
>
|
|
|
|
<SwitchingColumnsArea
|
|
|
|
location={location}
|
|
|
|
onLayoutChange={this.handleLayoutChange}
|
|
|
|
>
|
2018-03-06 07:45:31 +01:00
|
|
|
{children}
|
2020-01-07 12:21:34 +01:00
|
|
|
</SwitchingColumnsArea >
|
2017-10-06 01:07:59 +02:00
|
|
|
|
|
|
|
<NotificationsContainer />
|
|
|
|
<LoadingBarContainer className='loading-bar' />
|
|
|
|
<ModalContainer />
|
2020-01-07 12:21:34 +01:00
|
|
|
<UploadArea
|
|
|
|
active={draggingOver}
|
|
|
|
onClose={this.closeUploadModal}
|
|
|
|
/>
|
2019-08-13 12:22:16 +02:00
|
|
|
<DocumentTitle />
|
2020-01-07 12:21:34 +01:00
|
|
|
</div >
|
|
|
|
</HotKeys >
|
2016-09-19 23:25:59 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2017-04-21 20:05:35 +02:00
|
|
|
}
|