diff --git a/app/soapbox/components/emoji_selector.js b/app/soapbox/components/emoji_selector.js
index d288f901a..7fd52d965 100644
--- a/app/soapbox/components/emoji_selector.js
+++ b/app/soapbox/components/emoji_selector.js
@@ -1,5 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
+import { HotKeys } from 'react-hotkeys';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import emojify from 'soapbox/features/emoji/emoji';
@@ -35,6 +36,8 @@ class EmojiSelector extends ImmutablePureComponent {
}
handleKeyUp = i => e => {
+ const { onUnfocus } = this.props;
+
switch (e.key) {
case 'Left':
case 'ArrowLeft':
@@ -48,33 +51,54 @@ class EmojiSelector extends ImmutablePureComponent {
this.node.querySelector(`.emoji-react-selector__emoji:nth-child(${i + 2})`).focus();
}
break;
+ case 'Escape':
+ onUnfocus();
+ break;
}
}
+ handleReact = emoji => () => {
+ const { onReact, focused, onUnfocus } = this.props;
+
+ onReact(emoji)();
+
+ if (focused) {
+ onUnfocus();
+ }
+ }
+
+ handlers = {
+ open: () => {},
+ };
+
setRef = c => {
this.node = c;
}
render() {
- const { onReact, visible, focused, allowedEmoji } = this.props;
+ const { visible, focused, allowedEmoji } = this.props;
return (
-
- {allowedEmoji.map((emoji, i) => (
-
- ))}
-
+
+ {allowedEmoji.map((emoji, i) => (
+
+ ))}
+
+
);
}
diff --git a/app/soapbox/components/status.js b/app/soapbox/components/status.js
index 1ff0b1e03..fcdddccb2 100644
--- a/app/soapbox/components/status.js
+++ b/app/soapbox/components/status.js
@@ -108,6 +108,7 @@ class Status extends ImmutablePureComponent {
state = {
showMedia: defaultMediaVisibility(this.props.status, this.props.displayMedia),
statusId: undefined,
+ emojiSelectorFocused: false,
};
// Track height changes we know about to compensate scrolling
@@ -255,6 +256,27 @@ class Status extends ImmutablePureComponent {
this.handleToggleMediaVisibility();
}
+ handleHotkeyReact = () => {
+ this._expandEmojiSelector();
+ }
+
+ handleEmojiSelectorExpand = e => {
+ if (e.key === 'Enter') {
+ this._expandEmojiSelector();
+ }
+ e.preventDefault();
+ }
+
+ handleEmojiSelectorUnfocus = () => {
+ this.setState({ emojiSelectorFocused: false });
+ }
+
+ _expandEmojiSelector = () => {
+ this.setState({ emojiSelectorFocused: true });
+ const firstEmoji = this.node.querySelector('.emoji-react-selector .emoji-react-selector__emoji');
+ firstEmoji.focus();
+ };
+
_properStatus() {
const { status } = this.props;
@@ -278,6 +300,7 @@ class Status extends ImmutablePureComponent {
let { status, account, ...other } = this.props;
+
if (status === null) {
return null;
}
@@ -443,6 +466,7 @@ class Status extends ImmutablePureComponent {
moveDown: this.handleHotkeyMoveDown,
toggleHidden: this.handleHotkeyToggleHidden,
toggleSensitive: this.handleHotkeyToggleSensitive,
+ react: this.handleHotkeyReact,
};
const statusUrl = `/@${status.getIn(['account', 'acct'])}/posts/${status.get('id')}`;
@@ -506,7 +530,13 @@ class Status extends ImmutablePureComponent {
)}
-
+
diff --git a/app/soapbox/components/status_action_bar.js b/app/soapbox/components/status_action_bar.js
index b58991ea3..206170e24 100644
--- a/app/soapbox/components/status_action_bar.js
+++ b/app/soapbox/components/status_action_bar.js
@@ -91,6 +91,10 @@ class StatusActionBar extends ImmutablePureComponent {
isStaff: PropTypes.bool.isRequired,
isAdmin: PropTypes.bool.isRequired,
allowedEmoji: ImmutablePropTypes.list,
+ emojiSelectorFocused: PropTypes.bool,
+ handleEmojiSelectorUnfocus: PropTypes.func.isRequired,
+ emojiSelectorFocused: PropTypes.bool,
+ handleEmojiSelectorUnfocus: PropTypes.func.isRequired,
};
static defaultProps = {
@@ -106,6 +110,7 @@ class StatusActionBar extends ImmutablePureComponent {
updateOnProps = [
'status',
'withDismiss',
+ 'emojiSelectorFocused',
]
handleReplyClick = () => {
@@ -359,7 +364,7 @@ class StatusActionBar extends ImmutablePureComponent {
}
render() {
- const { status, intl, allowedEmoji } = this.props;
+ const { status, intl, allowedEmoji, emojiSelectorFocused, handleEmojiSelectorUnfocus } = this.props;
const { emojiSelectorVisible } = this.state;
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
@@ -422,7 +427,12 @@ class StatusActionBar extends ImmutablePureComponent {
onMouseLeave={this.handleLikeButtonLeave}
ref={this.setRef}
>
-
+
{
if (featured) {
- return this.props.featuredStatusIds.indexOf(id);
+ return this.props.featuredStatusIds.keySeq().findIndex(key => key === id);
} else {
- return this.props.statusIds.indexOf(id) + this.getFeaturedStatusCount();
+ return this.props.statusIds.keySeq().findIndex(key => key === id) + this.getFeaturedStatusCount();
}
}
diff --git a/app/soapbox/features/status/components/action_bar.js b/app/soapbox/features/status/components/action_bar.js
index 7bcda9cf9..8cc300e5d 100644
--- a/app/soapbox/features/status/components/action_bar.js
+++ b/app/soapbox/features/status/components/action_bar.js
@@ -100,6 +100,9 @@ class ActionBar extends React.PureComponent {
isStaff: PropTypes.bool.isRequired,
isAdmin: PropTypes.bool.isRequired,
allowedEmoji: ImmutablePropTypes.list,
+ emojiSelectorFocused: PropTypes.bool,
+ handleEmojiSelectorExpand: PropTypes.func.isRequired,
+ handleEmojiSelectorUnfocus: PropTypes.func.isRequired,
};
static defaultProps = {
@@ -175,19 +178,6 @@ class ActionBar extends React.PureComponent {
};
}
- handleEmojiSelectorExpand = e => {
- if (e.key === 'Enter') {
- this.setState({ emojiSelectorFocused: true });
- const firstEmoji = this.node.querySelector('.emoji-react-selector .emoji-react-selector__emoji');
- firstEmoji.focus();
- }
- e.preventDefault();
- }
-
- handleEmojiSelectorUnfocus = () => {
- this.setState({ emojiSelectorFocused: false });
- }
-
handleHotkeyEmoji = () => {
const { emojiSelectorVisible } = this.state;
@@ -283,13 +273,13 @@ class ActionBar extends React.PureComponent {
componentDidMount() {
document.addEventListener('click', e => {
if (this.node && !this.node.contains(e.target))
- this.setState({ emojiSelectorVisible: false, emojiSelectorFocused: false });
+ this.setState({ emojiSelectorVisible: false, emojiSelectorFocused: false });
});
}
render() {
- const { status, intl, me, isStaff, isAdmin, allowedEmoji } = this.props;
- const { emojiSelectorVisible, emojiSelectorFocused } = this.state;
+ const { status, intl, me, isStaff, isAdmin, allowedEmoji, emojiSelectorFocused, handleEmojiSelectorExpand, handleEmojiSelectorUnfocus } = this.props;
+ const { emojiSelectorVisible } = this.state;
const ownAccount = status.getIn(['account', 'id']) === me;
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
@@ -403,7 +393,7 @@ class ActionBar extends React.PureComponent {
onReact={this.handleReactClick}
visible={emojiSelectorVisible}
focused={emojiSelectorFocused}
- onUnfocus={this.handleEmojiSelectorUnfocus}
+ onUnfocus={handleEmojiSelectorUnfocus}
/>
@@ -435,4 +425,5 @@ class ActionBar extends React.PureComponent {
}
-export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ActionBar));
+export default injectIntl(
+ connect(mapStateToProps, mapDispatchToProps)(ActionBar));
diff --git a/app/soapbox/features/status/index.js b/app/soapbox/features/status/index.js
index 08135b7f9..0ecf98e8c 100644
--- a/app/soapbox/features/status/index.js
+++ b/app/soapbox/features/status/index.js
@@ -146,6 +146,7 @@ class Status extends ImmutablePureComponent {
fullscreen: false,
showMedia: defaultMediaVisibility(this.props.status, this.props.displayMedia),
loadedStatusId: undefined,
+ emojiSelectorFocused: false,
};
componentDidMount() {
@@ -363,6 +364,10 @@ class Status extends ImmutablePureComponent {
this.handleToggleMediaVisibility();
}
+ handleHotkeyReact = () => {
+ this._expandEmojiSelector();
+ }
+
handleMoveUp = id => {
const { status, ancestorsIds, descendantsIds } = this.props;
@@ -397,6 +402,23 @@ class Status extends ImmutablePureComponent {
}
}
+ handleEmojiSelectorExpand = e => {
+ if (e.key === 'Enter') {
+ this._expandEmojiSelector();
+ }
+ e.preventDefault();
+ }
+
+ handleEmojiSelectorUnfocus = () => {
+ this.setState({ emojiSelectorFocused: false });
+ }
+
+ _expandEmojiSelector = () => {
+ this.setState({ emojiSelectorFocused: true });
+ const firstEmoji = this.status.querySelector('.emoji-react-selector .emoji-react-selector__emoji');
+ firstEmoji.focus();
+ };
+
_selectChild(index, align_top) {
const container = this.node;
const element = container.querySelectorAll('.focusable')[index];
@@ -445,6 +467,10 @@ class Status extends ImmutablePureComponent {
this.node = c;
}
+ setStatusRef = c => {
+ this.status = c;
+ }
+
componentDidUpdate(prevProps, prevState) {
const { params, status } = this.props;
const { ancestorsIds } = prevProps;
@@ -510,6 +536,7 @@ class Status extends ImmutablePureComponent {
openProfile: this.handleHotkeyOpenProfile,
toggleHidden: this.handleHotkeyToggleHidden,
toggleSensitive: this.handleHotkeyToggleSensitive,
+ react: this.handleHotkeyReact,
};
return (
@@ -537,7 +564,7 @@ class Status extends ImmutablePureComponent {
{ancestors}
-
+
diff --git a/app/soapbox/features/ui/components/hotkeys_modal.js b/app/soapbox/features/ui/components/hotkeys_modal.js
index 319e2e0e5..c646a0923 100644
--- a/app/soapbox/features/ui/components/hotkeys_modal.js
+++ b/app/soapbox/features/ui/components/hotkeys_modal.js
@@ -49,6 +49,10 @@ class HotkeysModal extends ImmutablePureComponent {
f
+
+ e
+
+
b
diff --git a/app/soapbox/locales/pl.json b/app/soapbox/locales/pl.json
index 2f0b6e1bc..5a87d306a 100644
--- a/app/soapbox/locales/pl.json
+++ b/app/soapbox/locales/pl.json
@@ -390,6 +390,7 @@
"keyboard_shortcuts.notifications": "aby otworzyć kolumnę powiadomień",
"keyboard_shortcuts.pinned": "aby przejść do listy przypiętych wpisów",
"keyboard_shortcuts.profile": "aby przejść do profilu autora wpisu",
+ "keyboard_shortcuts.react": "aby zareagować na wpis",
"keyboard_shortcuts.reply": "aby odpowiedzieć",
"keyboard_shortcuts.requests": "aby przejść do listy próśb o możliwość śledzenia",
"keyboard_shortcuts.search": "aby przejść do pola wyszukiwania",