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