2017-05-02 19:04:16 -05:00
import React from 'react' ;
2016-09-19 16:25:59 -05:00
import NotificationsContainer from './containers/notifications_container' ;
2017-04-21 13:05:35 -05:00
import PropTypes from 'prop-types' ;
2016-11-21 03:03:55 -06:00
import LoadingBarContainer from './containers/loading_bar_container' ;
import ModalContainer from './containers/modal_container' ;
2017-01-19 03:54:18 -06:00
import { connect } from 'react-redux' ;
2017-08-29 07:16:21 -05:00
import { Redirect , withRouter } from 'react-router-dom' ;
2017-12-04 01:26:40 -06:00
import { isMobile } from 'flavours/glitch/util/is_mobile' ;
2017-05-06 04:05:32 -05:00
import { debounce } from 'lodash' ;
2017-12-04 01:26:40 -06:00
import { uploadCompose , resetCompose } from 'flavours/glitch/actions/compose' ;
2018-05-27 12:10:37 -05:00
import { expandHomeTimeline } from 'flavours/glitch/actions/timelines' ;
2018-09-06 10:47:33 -05:00
import { expandNotifications , notificationsSetVisibility } from 'flavours/glitch/actions/notifications' ;
2018-07-08 13:04:53 -05:00
import { fetchFilters } from 'flavours/glitch/actions/filters' ;
2017-12-04 01:26:40 -06:00
import { clearHeight } from 'flavours/glitch/actions/height_cache' ;
2019-09-06 06:55:51 -05:00
import { submitMarkers } from 'flavours/glitch/actions/markers' ;
2017-12-04 01:26:40 -06:00
import { WrappedSwitch , WrappedRoute } from 'flavours/glitch/util/react_router_helpers' ;
2017-03-23 21:50:30 -05:00
import UploadArea from './components/upload_area' ;
2017-06-03 18:39:38 -05:00
import ColumnsAreaContainer from './containers/columns_area_container' ;
2017-09-09 10:18:21 -05:00
import classNames from 'classnames' ;
2018-09-06 09:09:57 -05:00
import Favico from 'favico.js' ;
2017-07-07 17:06:02 -05:00
import {
2019-04-19 13:14:32 -05:00
Compose ,
2017-07-07 17:06:02 -05:00
Status ,
GettingStarted ,
2017-12-20 09:50:29 -06:00
KeyboardShortcuts ,
2017-07-07 17:06:02 -05:00
PublicTimeline ,
CommunityTimeline ,
AccountTimeline ,
AccountGallery ,
HomeTimeline ,
Followers ,
Following ,
Reblogs ,
Favourites ,
2017-10-15 23:02:39 -05:00
DirectTimeline ,
2017-07-07 17:06:02 -05:00
HashtagTimeline ,
2017-07-08 10:22:24 -05:00
Notifications ,
2017-07-07 17:06:02 -05:00
FollowRequests ,
GenericNotFound ,
FavouritedStatuses ,
2018-04-11 12:42:25 -05:00
BookmarkedStatuses ,
2017-12-08 19:40:49 -06:00
ListTimeline ,
2017-07-07 17:06:02 -05:00
Blocks ,
2018-03-04 14:46:27 -06:00
DomainBlocks ,
2017-07-07 17:06:02 -05:00
Mutes ,
2017-09-07 02:58:11 -05:00
PinnedStatuses ,
2017-12-08 20:13:08 -06:00
Lists ,
2019-05-25 14:27:00 -05:00
Search ,
2017-12-12 00:01:17 -06:00
GettingStartedMisc ,
2019-08-29 17:14:36 -05:00
Directory ,
2017-12-04 01:26:40 -06:00
} from 'flavours/glitch/util/async-components' ;
2017-10-05 18:07:59 -05:00
import { HotKeys } from 'react-hotkeys' ;
2019-06-12 10:18:45 -05:00
import { me } from 'flavours/glitch/util/initial_state' ;
2019-11-24 08:57:27 -06:00
import { defineMessages , FormattedMessage , injectIntl } from 'react-intl' ;
2017-07-07 17:06:02 -05: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.
2017-07-12 04:03:17 -05:00
import '../../../glitch/components/status' ;
2017-06-20 13:40:03 -05:00
2017-11-09 07:34:41 -06:00
const messages = defineMessages ( {
beforeUnload : { id : 'ui.beforeunload' , defaultMessage : 'Your draft will be lost if you leave Mastodon.' } ,
} ) ;
2017-07-06 15:39:56 -05:00
const mapStateToProps = state => ( {
2018-09-22 16:08:03 -05:00
hasComposingText : state . getIn ( [ 'compose' , 'text' ] ) . trim ( ) . length !== 0 ,
hasMediaAttachments : state . getIn ( [ 'compose' , 'media_attachments' ] ) . size > 0 ,
2019-09-16 13:42:19 -05:00
canUploadMore : ! state . getIn ( [ 'compose' , 'media_attachments' ] ) . some ( x => [ 'audio' , 'video' ] . includes ( x . get ( 'type' ) ) ) && state . getIn ( [ 'compose' , 'media_attachments' ] ) . size < 4 ,
2017-11-17 00:11:01 -06:00
layout : state . getIn ( [ 'local_settings' , 'layout' ] ) ,
isWide : state . getIn ( [ 'local_settings' , 'stretch' ] ) ,
2017-11-17 00:24:22 -06:00
navbarUnder : state . getIn ( [ 'local_settings' , 'navbar_under' ] ) ,
2018-03-30 05:45:23 -05:00
dropdownMenuIsOpen : state . getIn ( [ 'dropdown_menu' , 'openId' ] ) !== null ,
2018-09-06 09:09:57 -05:00
unreadNotifications : state . getIn ( [ 'notifications' , 'unread' ] ) ,
2018-09-06 13:55:11 -05:00
showFaviconBadge : state . getIn ( [ 'local_settings' , 'notifications' , 'favicon_badge' ] ) ,
2019-04-27 10:41:49 -05:00
hicolorPrivacyIcons : state . getIn ( [ 'local_settings' , 'hicolor_privacy_icons' ] ) ,
2019-11-24 09:36:04 -06:00
moved : state . getIn ( [ 'accounts' , me , 'moved' ] ) && state . getIn ( [ 'accounts' , state . getIn ( [ 'accounts' , me , 'moved' ] ) , 'acct' ] ) ,
2017-07-06 15:39:56 -05:00
} ) ;
2017-10-05 18:07:59 -05:00
const keyMap = {
2017-12-20 09:50:29 -06:00
help : '?' ,
2017-10-05 18:07:59 -05:00
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' ,
goToNotifications : 'g n' ,
goToLocal : 'g l' ,
goToFederated : 'g t' ,
2017-10-22 20:45:35 -05:00
goToDirect : 'g d' ,
2017-10-05 18:07:59 -05:00
goToStart : 'g s' ,
goToFavourites : 'g f' ,
goToPinned : 'g p' ,
goToProfile : 'g u' ,
goToBlocked : 'g b' ,
goToMuted : 'g m' ,
2018-08-20 04:12:19 -05:00
goToRequests : 'g r' ,
2017-11-27 15:17:12 -06:00
toggleSpoiler : 'x' ,
2019-04-27 12:08:38 -05:00
bookmark : 'd' ,
2019-04-27 12:17:42 -05:00
toggleCollapse : 'shift+x' ,
2019-05-26 11:58:14 -05:00
toggleSensitive : 'h' ,
2017-10-05 18:07:59 -05:00
} ;
2019-08-28 09:28:55 -05:00
class SwitchingColumnsArea extends React . PureComponent {
static propTypes = {
children : PropTypes . node ,
layout : PropTypes . string ,
location : PropTypes . object ,
navbarUnder : PropTypes . bool ,
onLayoutChange : PropTypes . func . isRequired ,
} ;
state = {
mobile : isMobile ( window . innerWidth , this . props . layout ) ,
} ;
componentWillReceiveProps ( nextProps ) {
if ( nextProps . layout !== this . props . layout ) {
this . setState ( { mobile : isMobile ( window . innerWidth , nextProps . layout ) } ) ;
}
}
componentWillMount ( ) {
window . addEventListener ( 'resize' , this . handleResize , { passive : true } ) ;
2019-07-19 02:25:22 -05:00
if ( this . state . mobile ) {
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 ) ;
}
2019-08-28 09:28:55 -05:00
}
2019-07-19 02:25:22 -05:00
componentDidUpdate ( prevProps , prevState ) {
2019-08-28 09:28:55 -05:00
if ( ! [ this . props . location . pathname , '/' ] . includes ( prevProps . location . pathname ) ) {
this . node . handleChildrenContentChange ( ) ;
}
2019-07-19 02:25:22 -05:00
if ( prevState . mobile !== this . state . mobile ) {
document . body . classList . toggle ( 'layout-single-column' , this . state . mobile ) ;
document . body . classList . toggle ( 'layout-multiple-columns' , ! this . state . mobile ) ;
}
2019-08-28 09:28:55 -05:00
}
componentWillUnmount ( ) {
window . removeEventListener ( 'resize' , this . handleResize ) ;
}
2019-08-25 08:48:50 -05:00
handleLayoutChange = debounce ( ( ) => {
2019-08-28 09:28:55 -05:00
// The cached heights are no longer accurate, invalidate
this . props . onLayoutChange ( ) ;
} , 500 , {
trailing : true ,
2019-08-25 08:48:50 -05:00
} )
handleResize = ( ) => {
const mobile = isMobile ( window . innerWidth , this . props . layout ) ;
if ( mobile !== this . state . mobile ) {
this . handleLayoutChange . cancel ( ) ;
this . props . onLayoutChange ( ) ;
this . setState ( { mobile } ) ;
} else {
this . handleLayoutChange ( ) ;
}
}
2019-08-28 09:28:55 -05:00
setRef = c => {
2019-11-04 05:58:19 -06:00
if ( c ) {
this . node = c . getWrappedInstance ( ) ;
}
2019-08-28 09:28:55 -05:00
}
render ( ) {
const { children , navbarUnder } = this . props ;
const singleColumn = this . state . mobile ;
const redirect = singleColumn ? < Redirect from = '/' to = '/timelines/home' exact / > : < Redirect from = '/' to = '/getting-started' exact / > ;
return (
< ColumnsAreaContainer ref = { this . setRef } singleColumn = { singleColumn } navbarUnder = { navbarUnder } >
< WrappedSwitch >
{ redirect }
< WrappedRoute path = '/getting-started' component = { GettingStarted } content = { children } / >
< WrappedRoute path = '/keyboard-shortcuts' component = { KeyboardShortcuts } content = { children } / >
< WrappedRoute path = '/timelines/home' component = { HomeTimeline } content = { children } / >
< WrappedRoute path = '/timelines/public' exact component = { PublicTimeline } content = { children } / >
< WrappedRoute path = '/timelines/public/local' exact component = { CommunityTimeline } content = { children } / >
< WrappedRoute path = '/timelines/direct' component = { DirectTimeline } content = { children } / >
< WrappedRoute path = '/timelines/tag/:id' component = { HashtagTimeline } content = { children } / >
< WrappedRoute path = '/timelines/list/:id' component = { ListTimeline } content = { children } / >
< WrappedRoute path = '/notifications' component = { Notifications } content = { children } / >
< WrappedRoute path = '/favourites' component = { FavouritedStatuses } content = { children } / >
< WrappedRoute path = '/bookmarks' component = { BookmarkedStatuses } content = { children } / >
< WrappedRoute path = '/pinned' component = { PinnedStatuses } content = { children } / >
< WrappedRoute path = '/search' component = { Search } content = { children } / >
2019-08-29 17:14:36 -05:00
< WrappedRoute path = '/directory' component = { Directory } content = { children } componentParams = { { shouldUpdateScroll : this . shouldUpdateScroll } } / >
2019-08-28 09:28:55 -05:00
< WrappedRoute path = '/statuses/new' component = { Compose } content = { children } / >
< WrappedRoute path = '/statuses/:statusId' exact component = { Status } content = { children } / >
< WrappedRoute path = '/statuses/:statusId/reblogs' component = { Reblogs } content = { children } / >
< WrappedRoute path = '/statuses/:statusId/favourites' component = { Favourites } content = { children } / >
< WrappedRoute path = '/accounts/:accountId' exact component = { AccountTimeline } content = { children } / >
< WrappedRoute path = '/accounts/:accountId/with_replies' component = { AccountTimeline } content = { children } componentParams = { { withReplies : true } } / >
< WrappedRoute path = '/accounts/:accountId/followers' component = { Followers } content = { children } / >
< WrappedRoute path = '/accounts/:accountId/following' component = { Following } content = { children } / >
< WrappedRoute path = '/accounts/:accountId/media' component = { AccountGallery } content = { children } / >
< WrappedRoute path = '/follow_requests' component = { FollowRequests } content = { children } / >
< WrappedRoute path = '/blocks' component = { Blocks } content = { children } / >
< WrappedRoute path = '/domain_blocks' component = { DomainBlocks } content = { children } / >
< WrappedRoute path = '/mutes' component = { Mutes } content = { children } / >
< WrappedRoute path = '/lists' component = { Lists } content = { children } / >
< WrappedRoute path = '/getting-started-misc' component = { GettingStartedMisc } content = { children } / >
< WrappedRoute component = { GenericNotFound } content = { children } / >
< / W r a p p e d S w i t c h >
< / C o l u m n s A r e a C o n t a i n e r >
) ;
} ;
}
export default @ connect ( mapStateToProps )
2017-11-09 07:34:41 -06:00
@ injectIntl
2017-08-29 07:16:21 -05:00
@ withRouter
2019-08-28 09:28:55 -05:00
class UI extends React . Component {
2016-09-19 16:25:59 -05:00
2017-05-12 07:44:10 -05:00
static propTypes = {
dispatch : PropTypes . func . isRequired ,
2017-05-20 10:31:47 -05:00
children : PropTypes . node ,
2017-06-24 19:07:25 -05:00
layout : PropTypes . string ,
2017-06-29 00:00:54 -05:00
isWide : PropTypes . bool ,
2017-07-06 15:39:56 -05:00
systemFontUi : PropTypes . bool ,
2017-07-22 12:51:34 -05:00
navbarUnder : PropTypes . bool ,
2017-07-20 18:38:24 -05:00
isComposing : PropTypes . bool ,
2017-11-09 07:34:41 -06:00
hasComposingText : PropTypes . bool ,
2018-09-22 16:08:03 -05:00
hasMediaAttachments : PropTypes . bool ,
2019-09-16 13:42:19 -05:00
canUploadMore : PropTypes . bool ,
2018-07-24 13:33:17 -05:00
match : PropTypes . object . isRequired ,
location : PropTypes . object . isRequired ,
history : PropTypes . object . isRequired ,
2017-11-09 07:34:41 -06:00
intl : PropTypes . object . isRequired ,
2018-03-30 05:45:23 -05:00
dropdownMenuIsOpen : PropTypes . bool ,
2018-09-06 09:09:57 -05:00
unreadNotifications : PropTypes . number ,
2018-09-06 13:55:11 -05:00
showFaviconBadge : PropTypes . bool ,
2019-11-24 09:36:04 -06:00
moved : PropTypes . string ,
2017-05-12 07:44:10 -05:00
} ;
state = {
2017-05-20 10:31:47 -05:00
draggingOver : false ,
2017-05-12 07:44:10 -05:00
} ;
2016-09-19 16:25:59 -05:00
2017-11-09 07:34:41 -06:00
handleBeforeUnload = ( e ) => {
2019-09-06 06:55:51 -05:00
const { intl , dispatch , hasComposingText , hasMediaAttachments } = this . props ;
dispatch ( submitMarkers ( ) ) ;
2017-11-09 07:34:41 -06:00
2018-09-22 16:08:03 -05:00
if ( hasComposingText || hasMediaAttachments ) {
2017-11-09 07:34:41 -06: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 ) ;
}
}
2019-08-28 09:28:55 -05:00
handleLayoutChange = ( ) => {
2017-08-07 13:32:03 -05:00
// The cached heights are no longer accurate, invalidate
2017-09-13 03:24:33 -05:00
this . props . dispatch ( clearHeight ( ) ) ;
2019-08-28 09:28:55 -05:00
}
2016-12-06 12:18:37 -06:00
2017-05-12 07:44:10 -05:00
handleDragEnter = ( e ) => {
2017-03-31 04:48:25 -05:00
e . preventDefault ( ) ;
if ( ! this . dragTargets ) {
this . dragTargets = [ ] ;
}
if ( this . dragTargets . indexOf ( e . target ) === - 1 ) {
this . dragTargets . push ( e . target ) ;
}
2019-09-16 13:42:19 -05:00
if ( e . dataTransfer && e . dataTransfer . types . includes ( 'Files' ) && this . props . canUploadMore ) {
2017-04-01 15:11:28 -05:00
this . setState ( { draggingOver : true } ) ;
}
2017-04-21 13:05:35 -05:00
}
2017-03-31 04:48:25 -05:00
2017-05-12 07:44:10 -05:00
handleDragOver = ( e ) => {
2019-01-17 16:27:51 -06:00
if ( this . dataTransferIsText ( e . dataTransfer ) ) return false ;
2016-12-11 16:35:06 -06:00
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
2017-03-31 04:48:25 -05:00
try {
e . dataTransfer . dropEffect = 'copy' ;
} catch ( err ) {
2016-12-11 16:35:06 -06:00
}
2017-03-31 04:48:25 -05:00
return false ;
2017-04-21 13:05:35 -05:00
}
2016-12-11 16:35:06 -06:00
2017-05-12 07:44:10 -05:00
handleDrop = ( e ) => {
2019-01-17 16:27:51 -06:00
if ( this . dataTransferIsText ( e . dataTransfer ) ) return ;
2019-09-16 13:42:19 -05:00
2016-12-11 16:35:06 -06:00
e . preventDefault ( ) ;
2017-03-28 07:17:24 -05:00
this . setState ( { draggingOver : false } ) ;
2019-01-16 07:50:17 -06:00
this . dragTargets = [ ] ;
2017-03-28 07:17:24 -05:00
2019-09-16 13:42:19 -05:00
if ( e . dataTransfer && e . dataTransfer . files . length >= 1 && this . props . canUploadMore ) {
2016-12-11 16:35:06 -06:00
this . props . dispatch ( uploadCompose ( e . dataTransfer . files ) ) ;
}
2017-04-21 13:05:35 -05:00
}
2016-12-11 16:35:06 -06:00
2017-05-12 07:44:10 -05:00
handleDragLeave = ( e ) => {
2017-03-31 04:48:25 -05: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-23 21:50:30 -05:00
this . setState ( { draggingOver : false } ) ;
2017-04-21 13:05:35 -05:00
}
2017-03-23 21:50:30 -05:00
2019-01-17 16:27:51 -06:00
dataTransferIsText = ( dataTransfer ) => {
2019-10-02 10:10:56 -05:00
return ( dataTransfer && Array . from ( dataTransfer . types ) . filter ( ( type ) => type === 'text/plain' ) . length === 1 ) ;
2019-01-17 16:27:51 -06:00
}
2017-05-12 07:44:10 -05:00
closeUploadModal = ( ) => {
2017-04-24 13:19:33 -05:00
this . setState ( { draggingOver : false } ) ;
}
2017-07-27 22:06:01 -05:00
handleServiceWorkerPostMessage = ( { data } ) => {
if ( data . type === 'navigate' ) {
2018-07-24 13:33:17 -05:00
this . props . history . push ( data . path ) ;
2017-07-27 22:06:01 -05:00
} else {
2017-08-24 05:15:36 -05:00
console . warn ( 'Unknown message type:' , data . type ) ;
2017-07-27 22:06:01 -05:00
}
}
2018-09-06 10:47:33 -05:00
handleVisibilityChange = ( ) => {
const visibility = ! document [ this . visibilityHiddenProp ] ;
this . props . dispatch ( notificationsSetVisibility ( visibility ) ) ;
}
2016-12-06 12:18:37 -06:00
componentWillMount ( ) {
2018-09-06 10:47:33 -05:00
if ( typeof document . hidden !== 'undefined' ) { // Opera 12.10 and Firefox 18 and later support
this . visibilityHiddenProp = 'hidden' ;
this . visibilityChange = 'visibilitychange' ;
} else if ( typeof document . msHidden !== 'undefined' ) {
this . visibilityHiddenProp = 'msHidden' ;
this . visibilityChange = 'msvisibilitychange' ;
} else if ( typeof document . webkitHidden !== 'undefined' ) {
this . visibilityHiddenProp = 'webkitHidden' ;
this . visibilityChange = 'webkitvisibilitychange' ;
}
if ( this . visibilityChange !== undefined ) {
document . addEventListener ( this . visibilityChange , this . handleVisibilityChange , false ) ;
this . handleVisibilityChange ( ) ;
}
2017-11-09 07:34:41 -06:00
window . addEventListener ( 'beforeunload' , this . handleBeforeUnload , false ) ;
2017-03-31 04:48:25 -05: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 13:19:33 -05:00
document . addEventListener ( 'dragend' , this . handleDragEnd , false ) ;
2017-01-19 03:54:18 -06:00
2017-07-27 22:06:01 -05:00
if ( 'serviceWorker' in navigator ) {
navigator . serviceWorker . addEventListener ( 'message' , this . handleServiceWorkerPostMessage ) ;
}
2018-09-06 09:09:57 -05:00
this . favicon = new Favico ( { animation : "none" } ) ;
2018-05-27 12:10:37 -05:00
this . props . dispatch ( expandHomeTimeline ( ) ) ;
2018-05-27 12:30:52 -05:00
this . props . dispatch ( expandNotifications ( ) ) ;
2018-07-08 13:04:53 -05:00
setTimeout ( ( ) => this . props . dispatch ( fetchFilters ( ) ) , 500 ) ;
2017-04-21 13:05:35 -05:00
}
2016-12-06 12:18:37 -06:00
2017-10-05 18:07:59 -05:00
componentDidMount ( ) {
this . hotkeys . _ _mousetrap _ _ . stopCallback = ( e , element ) => {
2017-10-11 09:31:07 -05:00
return [ 'TEXTAREA' , 'SELECT' , 'INPUT' ] . includes ( element . tagName ) ;
2017-10-05 18:07:59 -05:00
} ;
}
2017-08-29 07:16:21 -05:00
componentDidUpdate ( prevProps ) {
2018-09-06 13:55:11 -05:00
if ( this . props . unreadNotifications != prevProps . unreadNotifications ||
this . props . showFaviconBadge != prevProps . showFaviconBadge ) {
2018-09-06 09:09:57 -05:00
if ( this . favicon ) {
2019-08-28 13:55:23 -05:00
try {
this . favicon . badge ( this . props . showFaviconBadge ? this . props . unreadNotifications : 0 ) ;
} catch ( err ) {
console . error ( err ) ;
}
2018-09-06 09:09:57 -05:00
}
}
2017-08-29 07:16:21 -05:00
}
2016-12-06 12:18:37 -06:00
componentWillUnmount ( ) {
2018-09-06 10:47:33 -05:00
if ( this . visibilityChange !== undefined ) {
document . removeEventListener ( this . visibilityChange , this . handleVisibilityChange ) ;
}
2017-11-09 07:34:41 -06:00
window . removeEventListener ( 'beforeunload' , this . handleBeforeUnload ) ;
2017-03-31 04:48:25 -05:00
document . removeEventListener ( 'dragenter' , this . handleDragEnter ) ;
document . removeEventListener ( 'dragover' , this . handleDragOver ) ;
document . removeEventListener ( 'drop' , this . handleDrop ) ;
document . removeEventListener ( 'dragleave' , this . handleDragLeave ) ;
2017-04-24 13:19:33 -05:00
document . removeEventListener ( 'dragend' , this . handleDragEnd ) ;
2017-04-21 13:05:35 -05:00
}
2017-03-31 04:48:25 -05:00
2017-09-21 21:59:17 -05:00
setRef = c => {
2017-03-31 04:48:25 -05:00
this . node = c ;
2017-04-21 13:05:35 -05:00
}
2016-12-06 12:18:37 -06:00
2017-10-05 18:07:59 -05:00
handleHotkeyNew = e => {
e . preventDefault ( ) ;
2019-06-02 03:05:54 -05:00
const element = this . node . querySelector ( '.compose-form__autosuggest-wrapper textarea' ) ;
2017-10-05 18:07:59 -05:00
if ( element ) {
element . focus ( ) ;
}
}
handleHotkeySearch = e => {
e . preventDefault ( ) ;
2019-06-27 15:30:55 -05:00
const element = this . node . querySelector ( '.search__input' ) ;
2017-10-05 18:07:59 -05:00
if ( element ) {
element . focus ( ) ;
}
}
handleHotkeyForceNew = e => {
this . handleHotkeyNew ( e ) ;
this . props . dispatch ( resetCompose ( ) ) ;
}
handleHotkeyFocusColumn = e => {
const index = ( e . key * 1 ) + 1 ; // First child is drawer, skip that
const column = this . node . querySelector ( ` .column:nth-child( ${ index } ) ` ) ;
2019-04-15 13:40:05 -05:00
if ( ! column ) return ;
const container = column . querySelector ( '.scrollable' ) ;
2017-10-05 18:07:59 -05:00
2019-04-15 13:40:05 -05:00
if ( container ) {
const status = container . querySelector ( '.focusable' ) ;
2017-10-05 18:07:59 -05:00
if ( status ) {
2019-04-15 13:40:05 -05:00
if ( container . scrollTop > status . offsetTop ) {
status . scrollIntoView ( true ) ;
}
2017-10-05 18:07:59 -05:00
status . focus ( ) ;
}
}
}
handleHotkeyBack = ( ) => {
2018-05-23 07:17:05 -05:00
// if history is exhausted, or we would leave mastodon, just go to root.
if ( window . history . state ) {
2018-07-24 13:33:17 -05:00
this . props . history . goBack ( ) ;
2017-10-05 18:07:59 -05:00
} else {
2018-07-24 13:33:17 -05:00
this . props . history . push ( '/' ) ;
2017-10-05 18:07:59 -05:00
}
}
setHotkeysRef = c => {
this . hotkeys = c ;
}
2017-12-20 09:50:29 -06:00
handleHotkeyToggleHelp = ( ) => {
if ( this . props . location . pathname === '/keyboard-shortcuts' ) {
2018-07-24 13:33:17 -05:00
this . props . history . goBack ( ) ;
2017-12-20 09:50:29 -06:00
} else {
2018-07-24 13:33:17 -05:00
this . props . history . push ( '/keyboard-shortcuts' ) ;
2017-12-20 09:50:29 -06:00
}
}
2017-10-05 18:07:59 -05:00
handleHotkeyGoToHome = ( ) => {
2018-07-24 13:33:17 -05:00
this . props . history . push ( '/timelines/home' ) ;
2017-10-05 18:07:59 -05:00
}
handleHotkeyGoToNotifications = ( ) => {
2018-07-24 13:33:17 -05:00
this . props . history . push ( '/notifications' ) ;
2017-10-05 18:07:59 -05:00
}
handleHotkeyGoToLocal = ( ) => {
2018-07-24 13:33:17 -05:00
this . props . history . push ( '/timelines/public/local' ) ;
2017-10-05 18:07:59 -05:00
}
handleHotkeyGoToFederated = ( ) => {
2018-07-24 13:33:17 -05:00
this . props . history . push ( '/timelines/public' ) ;
2017-10-05 18:07:59 -05:00
}
2017-10-22 20:45:35 -05:00
handleHotkeyGoToDirect = ( ) => {
2018-07-24 13:33:17 -05:00
this . props . history . push ( '/timelines/direct' ) ;
2017-10-22 20:45:35 -05:00
}
2017-10-05 18:07:59 -05:00
handleHotkeyGoToStart = ( ) => {
2018-07-24 13:33:17 -05:00
this . props . history . push ( '/getting-started' ) ;
2017-10-05 18:07:59 -05:00
}
handleHotkeyGoToFavourites = ( ) => {
2018-07-24 13:33:17 -05:00
this . props . history . push ( '/favourites' ) ;
2017-10-05 18:07:59 -05:00
}
handleHotkeyGoToPinned = ( ) => {
2018-07-24 13:33:17 -05:00
this . props . history . push ( '/pinned' ) ;
2017-10-05 18:07:59 -05:00
}
handleHotkeyGoToProfile = ( ) => {
2018-07-24 13:33:17 -05:00
this . props . history . push ( ` /accounts/ ${ me } ` ) ;
2017-10-05 18:07:59 -05:00
}
handleHotkeyGoToBlocked = ( ) => {
2018-07-24 13:33:17 -05:00
this . props . history . push ( '/blocks' ) ;
2017-10-05 18:07:59 -05:00
}
handleHotkeyGoToMuted = ( ) => {
2018-07-24 13:33:17 -05:00
this . props . history . push ( '/mutes' ) ;
2017-09-21 21:59:17 -05:00
}
2018-08-20 04:12:19 -05:00
handleHotkeyGoToRequests = ( ) => {
this . props . history . push ( '/follow_requests' ) ;
}
2016-09-19 16:25:59 -05:00
render ( ) {
2019-08-28 09:28:55 -05:00
const { draggingOver } = this . state ;
2019-11-24 08:57:27 -06:00
const { children , layout , isWide , navbarUnder , location , dropdownMenuIsOpen , moved } = this . props ;
2017-03-23 21:50:30 -05:00
2017-06-24 19:07:25 -05:00
const columnsClass = layout => {
switch ( layout ) {
case 'single' :
return 'single-column' ;
case 'multiple' :
2017-06-24 21:12:34 -05:00
return 'multi-columns' ;
2017-06-24 19:07:25 -05:00
default :
return 'auto-columns' ;
}
2017-06-24 21:12:34 -05:00
} ;
2017-06-24 14:22:55 -05:00
2017-07-12 04:03:17 -05:00
const className = classNames ( 'ui' , columnsClass ( layout ) , {
'wide' : isWide ,
2017-07-06 15:39:56 -05:00
'system-font' : this . props . systemFontUi ,
2017-07-22 13:41:21 -05:00
'navbar-under' : navbarUnder ,
2019-04-27 10:41:49 -05:00
'hicolor-privacy-icons' : this . props . hicolorPrivacyIcons ,
2017-07-06 15:39:56 -05:00
} ) ;
2017-03-23 21:50:30 -05:00
2017-10-05 18:07:59 -05:00
const handlers = {
2017-12-20 09:50:29 -06:00
help : this . handleHotkeyToggleHelp ,
2017-10-05 18:07:59 -05:00
new : this . handleHotkeyNew ,
search : this . handleHotkeySearch ,
forceNew : this . handleHotkeyForceNew ,
focusColumn : this . handleHotkeyFocusColumn ,
back : this . handleHotkeyBack ,
goToHome : this . handleHotkeyGoToHome ,
goToNotifications : this . handleHotkeyGoToNotifications ,
goToLocal : this . handleHotkeyGoToLocal ,
goToFederated : this . handleHotkeyGoToFederated ,
2017-10-22 20:45:35 -05:00
goToDirect : this . handleHotkeyGoToDirect ,
2017-10-05 18:07:59 -05:00
goToStart : this . handleHotkeyGoToStart ,
goToFavourites : this . handleHotkeyGoToFavourites ,
goToPinned : this . handleHotkeyGoToPinned ,
goToProfile : this . handleHotkeyGoToProfile ,
goToBlocked : this . handleHotkeyGoToBlocked ,
goToMuted : this . handleHotkeyGoToMuted ,
2018-08-20 04:12:19 -05:00
goToRequests : this . handleHotkeyGoToRequests ,
2017-10-05 18:07:59 -05:00
} ;
2016-09-19 16:25:59 -05:00
return (
2018-10-10 10:23:40 -05:00
< HotKeys keyMap = { keyMap } handlers = { handlers } ref = { this . setHotkeysRef } attach = { window } focused >
2018-03-30 05:45:23 -05:00
< div className = { className } ref = { this . setRef } style = { { pointerEvents : dropdownMenuIsOpen ? 'none' : null } } >
2019-11-24 08:57:27 -06:00
{ moved && ( < div className = 'flash-message alert' >
2019-11-24 09:36:04 -06:00
< FormattedMessage id = 'moved_to_warning' defaultMessage = 'This account is marked as moved to {moved_to}, and may thus not accept new follows.' values = { { moved _to : moved } } / >
2019-11-24 08:57:27 -06:00
< / d i v > ) }
2019-08-28 09:28:55 -05:00
< SwitchingColumnsArea location = { location } layout = { layout } navbarUnder = { navbarUnder } onLayoutChange = { this . handleLayoutChange } >
{ children }
< / S w i t c h i n g C o l u m n s A r e a >
2017-10-05 18:07:59 -05:00
< NotificationsContainer / >
< LoadingBarContainer className = 'loading-bar' / >
< ModalContainer / >
< UploadArea active = { draggingOver } onClose = { this . closeUploadModal } / >
< / d i v >
< / H o t K e y s >
2016-09-19 16:25:59 -05:00
) ;
}
2017-04-21 13:05:35 -05:00
}