mirror of https://github.com/mastodon/mastodon
Create HotKeys component
This commit is contained in:
parent
d4e094987e
commit
cadfbb70ed
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,119 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { useState, useCallback, forwardRef } from 'react';
|
||||
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
|
||||
const keyMap = {
|
||||
help: '?',
|
||||
new: 'n',
|
||||
search: 's',
|
||||
forceNew: 'option+n',
|
||||
toggleComposeSpoilers: 'option+x',
|
||||
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',
|
||||
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',
|
||||
};
|
||||
|
||||
const HotKeys = forwardRef(({ handlers, children }, ref) => {
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
|
||||
const stopCallback = (event, element) => {
|
||||
return ['TEXTAREA', 'SELECT', 'INPUT'].includes(element.tagName);
|
||||
};
|
||||
|
||||
const handleFocus = useCallback(() => {
|
||||
setIsFocused(true);
|
||||
}, []);
|
||||
|
||||
const handleBlur = useCallback(() => {
|
||||
setIsFocused(false);
|
||||
}, []);
|
||||
|
||||
const useRegisterHotkey = (keys, action) => {
|
||||
useHotkeys(
|
||||
keys,
|
||||
(event) => {
|
||||
try {
|
||||
const element = event.target;
|
||||
if (!stopCallback(event, element)) {
|
||||
handlers[action](event);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`"${action}" is not defined here`);
|
||||
}
|
||||
},
|
||||
{ enabled: isFocused }
|
||||
);
|
||||
};
|
||||
|
||||
useRegisterHotkey(Array.isArray(keyMap.help) ? keyMap.help : [keyMap.help], 'help');
|
||||
useRegisterHotkey(Array.isArray(keyMap.new) ? keyMap.new : [keyMap.new], 'new');
|
||||
useRegisterHotkey(Array.isArray(keyMap.search) ? keyMap.search : [keyMap.search], 'search');
|
||||
useRegisterHotkey(Array.isArray(keyMap.forceNew) ? keyMap.forceNew : [keyMap.forceNew], 'forceNew');
|
||||
useRegisterHotkey(Array.isArray(keyMap.toggleComposeSpoilers) ? keyMap.toggleComposeSpoilers : [keyMap.toggleComposeSpoilers], 'toggleComposeSpoilers');
|
||||
useRegisterHotkey(Array.isArray(keyMap.focusColumn) ? keyMap.focusColumn : [keyMap.focusColumn], 'focusColumn');
|
||||
useRegisterHotkey(Array.isArray(keyMap.reply) ? keyMap.reply : [keyMap.reply], 'reply');
|
||||
useRegisterHotkey(Array.isArray(keyMap.favourite) ? keyMap.favourite : [keyMap.favourite], 'favourite');
|
||||
useRegisterHotkey(Array.isArray(keyMap.boost) ? keyMap.boost : [keyMap.boost], 'boost');
|
||||
useRegisterHotkey(Array.isArray(keyMap.mention) ? keyMap.mention : [keyMap.mention], 'mention');
|
||||
useRegisterHotkey(Array.isArray(keyMap.open) ? keyMap.open : [keyMap.open], 'open');
|
||||
useRegisterHotkey(Array.isArray(keyMap.openProfile) ? keyMap.openProfile : [keyMap.openProfile], 'openProfile');
|
||||
useRegisterHotkey(Array.isArray(keyMap.moveDown) ? keyMap.moveDown : [keyMap.moveDown], 'moveDown');
|
||||
useRegisterHotkey(Array.isArray(keyMap.moveUp) ? keyMap.moveUp : [keyMap.moveUp], 'moveUp');
|
||||
useRegisterHotkey(Array.isArray(keyMap.back) ? keyMap.back : [keyMap.back], 'back');
|
||||
useRegisterHotkey(Array.isArray(keyMap.goToHome) ? keyMap.goToHome : [keyMap.goToHome], 'goToHome');
|
||||
useRegisterHotkey(Array.isArray(keyMap.goToNotifications) ? keyMap.goToNotifications : [keyMap.goToNotifications], 'goToNotifications');
|
||||
useRegisterHotkey(Array.isArray(keyMap.goToLocal) ? keyMap.goToLocal : [keyMap.goToLocal], 'goToLocal');
|
||||
useRegisterHotkey(Array.isArray(keyMap.goToFederated) ? keyMap.goToFederated : [keyMap.goToFederated], 'goToFederated');
|
||||
useRegisterHotkey(Array.isArray(keyMap.goToDirect) ? keyMap.goToDirect : [keyMap.goToDirect], 'goToDirect');
|
||||
useRegisterHotkey(Array.isArray(keyMap.goToStart) ? keyMap.goToStart : [keyMap.goToStart], 'goToStart');
|
||||
useRegisterHotkey(Array.isArray(keyMap.goToFavourites) ? keyMap.goToFavourites : [keyMap.goToFavourites], 'goToFavourites');
|
||||
useRegisterHotkey(Array.isArray(keyMap.goToPinned) ? keyMap.goToPinned : [keyMap.goToPinned], 'goToPinned');
|
||||
useRegisterHotkey(Array.isArray(keyMap.goToProfile) ? keyMap.goToProfile : [keyMap.goToProfile], 'goToProfile');
|
||||
useRegisterHotkey(Array.isArray(keyMap.goToBlocked) ? keyMap.goToBlocked : [keyMap.goToBlocked], 'goToBlocked');
|
||||
useRegisterHotkey(Array.isArray(keyMap.goToMuted) ? keyMap.goToMuted : [keyMap.goToMuted], 'goToMuted');
|
||||
useRegisterHotkey(Array.isArray(keyMap.goToRequests) ? keyMap.goToRequests : [keyMap.goToRequests], 'goToRequests');
|
||||
useRegisterHotkey(Array.isArray(keyMap.toggleHidden) ? keyMap.toggleHidden : [keyMap.toggleHidden], 'toggleHidden');
|
||||
useRegisterHotkey(Array.isArray(keyMap.toggleSensitive) ? keyMap.toggleSensitive : [keyMap.toggleSensitive], 'toggleSensitive');
|
||||
useRegisterHotkey(Array.isArray(keyMap.openMedia) ? keyMap.openMedia : [keyMap.openMedia], 'openMedia');
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
tabIndex={-1}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
HotKeys.propTypes = {
|
||||
handlers: PropTypes.object.isRequired,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
export default HotKeys;
|
|
@ -7,12 +7,12 @@ import classNames from 'classnames';
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
|
||||
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
|
||||
import PushPinIcon from '@/material-icons/400-24px/push_pin.svg?react';
|
||||
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
||||
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
||||
import HotKeys from 'mastodon/components/hotkeys';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_placeholder';
|
||||
import { withOptionalRouter, WithOptionalRouterPropTypes } from 'mastodon/utils/react_router';
|
||||
|
|
|
@ -11,8 +11,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
|
||||
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
||||
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
||||
import { replyCompose } from 'mastodon/actions/compose';
|
||||
|
@ -21,6 +19,7 @@ import { openModal } from 'mastodon/actions/modal';
|
|||
import { muteStatus, unmuteStatus, revealStatus, hideStatus } from 'mastodon/actions/statuses';
|
||||
import AttachmentList from 'mastodon/components/attachment_list';
|
||||
import AvatarComposite from 'mastodon/components/avatar_composite';
|
||||
import HotKeys from 'mastodon/components/hotkeys';
|
||||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
|
||||
import StatusContent from 'mastodon/components/status_content';
|
||||
|
|
|
@ -8,7 +8,6 @@ import { Link, withRouter } from 'react-router-dom';
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
|
||||
import EditIcon from '@/material-icons/400-24px/edit.svg?react';
|
||||
import FlagIcon from '@/material-icons/400-24px/flag-fill.svg?react';
|
||||
|
@ -18,6 +17,7 @@ import PersonIcon from '@/material-icons/400-24px/person-fill.svg?react';
|
|||
import PersonAddIcon from '@/material-icons/400-24px/person_add-fill.svg?react';
|
||||
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
||||
import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
|
||||
import HotKeys from 'mastodon/components/hotkeys';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import AccountContainer from 'mastodon/containers/account_container';
|
||||
import StatusContainer from 'mastodon/containers/status_container';
|
||||
|
|
|
@ -12,10 +12,10 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
|
||||
import VisibilityIcon from '@/material-icons/400-24px/visibility.svg?react';
|
||||
import VisibilityOffIcon from '@/material-icons/400-24px/visibility_off.svg?react';
|
||||
import HotKeys from 'mastodon/components/hotkeys';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||
import ScrollContainer from 'mastodon/containers/scroll_container';
|
||||
|
|
|
@ -9,11 +9,11 @@ import { Redirect, Route, withRouter } from 'react-router-dom';
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import { debounce } from 'lodash';
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
|
||||
import { focusApp, unfocusApp, changeLayout } from 'mastodon/actions/app';
|
||||
import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodon/actions/markers';
|
||||
import { INTRODUCTION_VERSION } from 'mastodon/actions/onboarding';
|
||||
import HotKeys from 'mastodon/components/hotkeys';
|
||||
import { PictureInPicture } from 'mastodon/features/picture_in_picture';
|
||||
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
|
||||
import { layoutFromWindow } from 'mastodon/is_mobile';
|
||||
|
@ -87,39 +87,6 @@ const mapStateToProps = state => ({
|
|||
username: state.getIn(['accounts', me, 'username']),
|
||||
});
|
||||
|
||||
const keyMap = {
|
||||
help: '?',
|
||||
new: 'n',
|
||||
search: ['s', '/'],
|
||||
forceNew: 'option+n',
|
||||
toggleComposeSpoilers: 'option+x',
|
||||
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',
|
||||
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',
|
||||
};
|
||||
|
||||
class SwitchingColumnsArea extends PureComponent {
|
||||
static propTypes = {
|
||||
identity: identityContextPropShape,
|
||||
|
@ -407,10 +374,6 @@ class UI extends PureComponent {
|
|||
|
||||
setTimeout(() => this.props.dispatch(fetchServer()), 3000);
|
||||
}
|
||||
|
||||
this.hotkeys.__mousetrap__.stopCallback = (e, element) => {
|
||||
return ['TEXTAREA', 'SELECT', 'INPUT'].includes(element.tagName);
|
||||
};
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
|
@ -575,7 +538,7 @@ class UI extends PureComponent {
|
|||
};
|
||||
|
||||
return (
|
||||
<HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef} attach={window} focused>
|
||||
<HotKeys handlers={handlers} ref={this.setHotkeysRef}>
|
||||
<div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef}>
|
||||
<Header />
|
||||
|
||||
|
|
|
@ -95,6 +95,7 @@
|
|||
"react-dom": "^18.2.0",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-hotkeys": "^1.1.4",
|
||||
"react-hotkeys-hook": "^4.5.0",
|
||||
"react-immutable-proptypes": "^2.2.0",
|
||||
"react-immutable-pure-component": "^2.2.2",
|
||||
"react-intl": "^6.4.2",
|
||||
|
|
Loading…
Reference in New Issue