diff --git a/src/sites/twitch-twilight/modules/chat/input.jsx b/src/sites/twitch-twilight/modules/chat/input.jsx index d0572463..0a6bb4e7 100644 --- a/src/sites/twitch-twilight/modules/chat/input.jsx +++ b/src/sites/twitch-twilight/modules/chat/input.jsx @@ -42,12 +42,31 @@ export default class Input extends Module { } }); + this.settings.add('chat.tab-complete.emotes-without-colon', { + default: false, + ui: { + path: 'Chat > Input >> Tab Completion', + title: 'Allow tab-completion of emotes without typing a colon. (:)', + description: 'This will prevent the tab-completion of usernames without the @ prefix.', + component: 'setting-check-box' + } + }); + + this.settings.add('chat.tab-complete.limit-results', { + default: true, + ui: { + path: 'Chat > Input >> Tab Completion', + title: 'Limit tab-completion results to 25.', + component: 'setting-check-box' + } + }); + // Components this.ChatInput = this.fine.define( 'chat-input', - n => n && n.setChatInputRef && n.setAutocompleteInputRef, + n => n && n.setChatInputRef && n.setLocalAutocompleteInputRef, Twilight.CHAT_ROUTES ); @@ -57,10 +76,24 @@ export default class Input extends Module { n => n && n.getMatchedEmotes, Twilight.CHAT_ROUTES ); + + + this.MentionSuggestions = this.fine.define( + 'tab-mention-suggestions', + n => n && n.getMentions && n.renderMention, + Twilight.CHAT_ROUTES + ); } async onEnable() { this.chat.context.on('changed:chat.actions.room', () => this.ChatInput.forceUpdate()); + this.chat.context.on('changed:chat.tab-complete.emotes-without-colon', enabled => { + for (const inst of this.EmoteSuggestions.instances) + inst.canBeTriggeredByTab = enabled; + + for (const inst of this.MentionSuggestions.instances) + inst.canBeTriggeredByTab = !enabled; + }); const React = await this.web_munch.findModule('react'), createElement = React && React.createElement; @@ -119,8 +152,14 @@ export default class Input extends Module { this.overrideEmoteMatcher(inst); }); + this.MentionSuggestions.ready((cls, instances) => { + for(const inst of instances) + this.overrideMentionMatcher(inst); + }); + this.ChatInput.on('update', this.updateEmoteCompletion, this); this.EmoteSuggestions.on('mount', this.overrideEmoteMatcher, this); + this.MentionSuggestions.on('mount', this.overrideMentionMatcher, this); } @@ -136,12 +175,28 @@ export default class Input extends Module { } + // eslint-disable-next-line class-methods-use-this + overrideMentionMatcher(inst) { + if ( inst._ffz_override ) + return; + + inst.canBeTriggeredByTab = !this.chat.context.get('chat.tab-complete.emotes-without-colon'); + } + + overrideEmoteMatcher(inst) { if ( inst._ffz_override ) return; - const t = this, - old_get_matched = inst.getMatchedEmotes; + const t = this; + + inst.canBeTriggeredByTab = this.chat.context.get('chat.tab-complete.emotes-without-colon'); + + inst.getMatches = function(input, pressedTab) { + return pressedTab + ? input.length < 2 ? null : inst.getMatchedEmotes(input) + : input.startsWith(':') ? input.length < 3 ? null : inst.getMatchedEmotes(input) : null; + } inst.doesEmoteMatchTerm = function(emote, term) { const emote_name = emote.name || emote.token, @@ -157,7 +212,9 @@ export default class Input extends Module { } inst.getMatchedEmotes = function(input) { - let results = old_get_matched.call(this, input); + const limitResults = t.chat.context.get('chat.tab-complete.limit-results'); + + let results = t.getTwitchEmoteSuggestions(input, this); if ( t.chat.context.get('chat.tab-complete.ffz-emotes') ) { const ffz_emotes = t.getEmoteSuggestions(input, this); @@ -166,13 +223,13 @@ export default class Input extends Module { } if ( ! t.chat.context.get('chat.tab-complete.emoji') ) - return results; + return limitResults && results.length > 25 ? results.slice(0, 25) : results; const emoji = t.getEmojiSuggestions(input, this); if ( Array.isArray(emoji) && emoji.length ) results = Array.isArray(results) ? results.concat(emoji) : emoji; - return results; + return limitResults && results.length > 25 ? results.slice(0, 25) : results; } const React = this.web_munch.getModule('react'), @@ -194,8 +251,38 @@ export default class Input extends Module { } } + + // eslint-disable-next-line class-methods-use-this + getTwitchEmoteSuggestions(input, inst) { + const hydratedEmotes = inst.hydrateEmotes(inst.props.emotes); + if (!Array.isArray(hydratedEmotes)) { + return []; + } + + const results = []; + const search = input.startsWith(':') ? input.slice(1) : input; + for (const set of hydratedEmotes) { + if (set && Array.isArray(set.emotes)) { + for (const emote of set.emotes) { + if (inst.doesEmoteMatchTerm(emote, search)) { + results.push({ + current: input, + replacement: emote.token, + element: inst.renderEmoteSuggestion(emote) + }); + } + } + } + } + return results; + } + getEmojiSuggestions(input, inst) { + if (!input.startsWith(':')) { + return []; + } + let search = input.slice(1).toLowerCase(); const style = this.chat.context.get('chat.emoji.style'), tone = this.settings.provider.get('emoji-tone', null), @@ -242,7 +329,7 @@ export default class Input extends Module { return []; } - const search = input.slice(1), + const search = input.startsWith(':') ? input.slice(1) : input, results = [], emotes = this.emotes.getEmotes( user && user.id,