Emoji Typeahead Enhancements (Feedback)
* Wrap around completion menu with arrow keys * Replace emoji text when typing final colon
This commit is contained in:
parent
cd8421caf2
commit
c952d628c1
1 changed files with 48 additions and 26 deletions
|
@ -16,7 +16,7 @@ import {
|
||||||
} from 'draft-js';
|
} from 'draft-js';
|
||||||
import Measure, { ContentRect } from 'react-measure';
|
import Measure, { ContentRect } from 'react-measure';
|
||||||
import { Manager, Popper, Reference } from 'react-popper';
|
import { Manager, Popper, Reference } from 'react-popper';
|
||||||
import { clamp, noop } from 'lodash';
|
import { head, noop, trimEnd } from 'lodash';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import emojiRegex from 'emoji-regex';
|
import emojiRegex from 'emoji-regex';
|
||||||
import { Emoji } from './emoji/Emoji';
|
import { Emoji } from './emoji/Emoji';
|
||||||
|
@ -241,7 +241,18 @@ export const CompositionInput = ({
|
||||||
|
|
||||||
// Update the state to indicate emojiable text at the current position.
|
// Update the state to indicate emojiable text at the current position.
|
||||||
const newSearchText = match ? match.trim().substr(1) : '';
|
const newSearchText = match ? match.trim().substr(1) : '';
|
||||||
if (newSearchText.length >= 2 && focusRef.current) {
|
if (newSearchText.endsWith(':')) {
|
||||||
|
const bareText = trimEnd(newSearchText, ':');
|
||||||
|
const emoji = head(search(bareText));
|
||||||
|
if (emoji && bareText === emoji.short_name) {
|
||||||
|
handleEditorCommand('enter-emoji', newState, emoji);
|
||||||
|
|
||||||
|
// Prevent inserted colon from persisting to state
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
resetEmojiResults();
|
||||||
|
}
|
||||||
|
} else if (newSearchText.length >= 2 && focusRef.current) {
|
||||||
setEmojiResults(search(newSearchText, 10));
|
setEmojiResults(search(newSearchText, 10));
|
||||||
setSearchText(newSearchText);
|
setSearchText(newSearchText);
|
||||||
setEmojiResultsIndex(0);
|
setEmojiResultsIndex(0);
|
||||||
|
@ -300,19 +311,31 @@ export const CompositionInput = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dir === 'next') {
|
if (dir === 'next') {
|
||||||
setEmojiResultsIndex(
|
setEmojiResultsIndex(index => {
|
||||||
clamp(emojiResultsIndex + 1, 0, emojiResults.length - 1)
|
const next = index + 1;
|
||||||
);
|
|
||||||
|
if (next >= emojiResults.length) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dir === 'prev') {
|
if (dir === 'prev') {
|
||||||
setEmojiResultsIndex(
|
setEmojiResultsIndex(index => {
|
||||||
clamp(emojiResultsIndex - 1, 0, emojiResults.length - 1)
|
const next = index - 1;
|
||||||
);
|
|
||||||
|
if (next < 0) {
|
||||||
|
return emojiResults.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return next;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[setEmojiResultsIndex, emojiResultsIndex, emojiResults]
|
[emojiResultsIndex, emojiResults]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleEditorArrowKey = React.useCallback(
|
const handleEditorArrowKey = React.useCallback(
|
||||||
|
@ -338,21 +361,18 @@ export const CompositionInput = ({
|
||||||
[resetEmojiResults, emojiResults]
|
[resetEmojiResults, emojiResults]
|
||||||
);
|
);
|
||||||
|
|
||||||
const getWordAtCaret = React.useCallback(
|
const getWordAtCaret = React.useCallback((state = editorStateRef.current) => {
|
||||||
() => {
|
const selection = state.getSelection();
|
||||||
const selection = editorState.getSelection();
|
const index = selection.getAnchorOffset();
|
||||||
const index = selection.getAnchorOffset();
|
|
||||||
|
|
||||||
return getWordAtIndex(
|
return getWordAtIndex(
|
||||||
editorState
|
state
|
||||||
.getCurrentContent()
|
.getCurrentContent()
|
||||||
.getBlockForKey(selection.getAnchorKey())
|
.getBlockForKey(selection.getAnchorKey())
|
||||||
.getText(),
|
.getText(),
|
||||||
index
|
index
|
||||||
);
|
);
|
||||||
},
|
}, []);
|
||||||
[editorState]
|
|
||||||
);
|
|
||||||
|
|
||||||
const insertEmoji = React.useCallback(
|
const insertEmoji = React.useCallback(
|
||||||
(e: EmojiPickDataType, replaceWord: boolean = false) => {
|
(e: EmojiPickDataType, replaceWord: boolean = false) => {
|
||||||
|
@ -409,14 +429,16 @@ export const CompositionInput = ({
|
||||||
const handleEditorCommand = React.useCallback(
|
const handleEditorCommand = React.useCallback(
|
||||||
(
|
(
|
||||||
command: CompositionInputEditorCommand,
|
command: CompositionInputEditorCommand,
|
||||||
state: EditorState
|
state: EditorState,
|
||||||
|
emojiOverride?: EmojiData
|
||||||
): DraftHandleValue => {
|
): DraftHandleValue => {
|
||||||
if (command === 'enter-emoji') {
|
if (command === 'enter-emoji') {
|
||||||
const shortName = emojiResults[emojiResultsIndex].short_name;
|
const { short_name: shortName } =
|
||||||
|
emojiOverride || emojiResults[emojiResultsIndex];
|
||||||
|
|
||||||
const content = state.getCurrentContent();
|
const content = state.getCurrentContent();
|
||||||
const selection = state.getSelection();
|
const selection = state.getSelection();
|
||||||
const word = getWordAtCaret();
|
const word = getWordAtCaret(state);
|
||||||
const emojiContent = convertShortName(shortName, skinTone);
|
const emojiContent = convertShortName(shortName, skinTone);
|
||||||
const emojiEntityKey = content
|
const emojiEntityKey = content
|
||||||
.createEntity('emoji', 'IMMUTABLE', {
|
.createEntity('emoji', 'IMMUTABLE', {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue