Autocomplete emoji names (#1250)

* generalize autoComplete function

* autocomplete emoji names

* isolate the state of each token

* minor fix

* save emojiNames in state
This commit is contained in:
Meisam 2021-07-23 23:15:25 +02:00 committed by GitHub
parent 377bf529ad
commit bb09c0d187
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -44,6 +44,7 @@ export default class ChatInput extends Component {
hasSentFirstChatMessage: getLocalStorage(KEY_CHAT_FIRST_MESSAGE_SENT), hasSentFirstChatMessage: getLocalStorage(KEY_CHAT_FIRST_MESSAGE_SENT),
emojiPicker: null, emojiPicker: null,
emojiList: null, emojiList: null,
emojiNames: null,
}; };
this.handleEmojiButtonClick = this.handleEmojiButtonClick.bind(this); this.handleEmojiButtonClick = this.handleEmojiButtonClick.bind(this);
@ -74,6 +75,7 @@ export default class ChatInput extends Component {
}) })
.then((json) => { .then((json) => {
const emojiList = json; const emojiList = json;
const emojiNames = emojiList.map(emoji => emoji.name);
const emojiPicker = new EmojiButton({ const emojiPicker = new EmojiButton({
zIndex: 100, zIndex: 100,
theme: 'owncast', // see chat.css theme: 'owncast', // see chat.css
@ -94,7 +96,7 @@ export default class ChatInput extends Component {
this.formMessageInput.current.focus(); this.formMessageInput.current.focus();
replaceCaret(this.formMessageInput.current); replaceCaret(this.formMessageInput.current);
}); });
this.setState({ emojiList, emojiPicker }); this.setState({ emojiNames, emojiList, emojiPicker });
}) })
.catch((error) => { .catch((error) => {
// this.handleNetworkingError(`Emoji Fetch: ${error}`); // this.handleNetworkingError(`Emoji Fetch: ${error}`);
@ -135,37 +137,44 @@ export default class ChatInput extends Component {
}, 100); }, 100);
} }
// autocomplete user names // autocomplete text from the given "list". "token" marks the start of word lookup.
autoCompleteNames() { autoComplete(token, list) {
const { chatUserNames } = this.props;
const { inputHTML } = this.state; const { inputHTML } = this.state;
const position = getCaretPosition(this.formMessageInput.current); const position = getCaretPosition(this.formMessageInput.current);
const at = inputHTML.lastIndexOf('@', position - 1); const at = inputHTML.lastIndexOf(token, position - 1);
if (at === -1) { if (at === -1) {
return false; return false;
} }
let partial = inputHTML.substring(at + 1, position).trim(); let partial = inputHTML.substring(at + 1, position).trim();
if (partial === this.suggestion) { if (this.partial === undefined) {
partial = this.partial; this.partial = [];
} else {
this.partial = partial;
} }
const possibilities = chatUserNames.filter(function (username) { if (partial === this.suggestion) {
return username.toLowerCase().startsWith(partial.toLowerCase()); partial = this.partial[token];
} else {
this.partial[token] = partial;
}
const possibilities = list.filter(function (item) {
return item.toLowerCase().startsWith(partial.toLowerCase());
}); });
if (this.completionIndex === undefined) {
this.completionIndex = [];
}
if ( if (
this.completionIndex === undefined || this.completionIndex[token] === undefined ||
++this.completionIndex >= possibilities.length ++this.completionIndex[token] >= possibilities.length
) { ) {
this.completionIndex = 0; this.completionIndex[token] = 0;
} }
if (possibilities.length > 0) { if (possibilities.length > 0) {
this.suggestion = possibilities[this.completionIndex]; this.suggestion = possibilities[this.completionIndex[token]];
const newHTML = const newHTML =
inputHTML.substring(0, at + 1) + inputHTML.substring(0, at + 1) +
@ -216,7 +225,12 @@ export default class ChatInput extends Component {
this.prepNewLine = true; this.prepNewLine = true;
} }
if (key === 'Tab') { if (key === 'Tab') {
if (this.autoCompleteNames()) { const { chatUserNames } = this.props;
const { emojiNames } = this.state;
if (this.autoComplete('@', chatUserNames)) {
event.preventDefault();
}
if (this.autoComplete(':', emojiNames)) {
event.preventDefault(); event.preventDefault();
} }
} }