From b3442ee1085870d4948b18fa6bfab755264ca34b Mon Sep 17 00:00:00 2001 From: Samuel Elliott Date: Sat, 31 Mar 2018 04:37:26 +0100 Subject: [PATCH] Store emote database in a map --- client/src/builtin/EmoteModule.js | 135 +++++++++--------- .../styles/partials/generic/autocomplete.scss | 10 +- 2 files changed, 72 insertions(+), 73 deletions(-) diff --git a/client/src/builtin/EmoteModule.js b/client/src/builtin/EmoteModule.js index d193ea30..4b80d718 100644 --- a/client/src/builtin/EmoteModule.js +++ b/client/src/builtin/EmoteModule.js @@ -14,17 +14,16 @@ import { Utils, FileUtils, ClientLogger as Logger } from 'common'; import path from 'path'; import EmoteComponent from './EmoteComponent.vue'; -let emotes = null; -const emotesEnabled = true; const enforceWrapperFrom = (new Date('2018-05-01')).valueOf(); export default new class EmoteModule { constructor() { + this.emotes = new Map(); this.favourite_emotes = []; } - init() { + async init() { this.enabledSetting = Settings.getSetting('emotes', 'default', 'enable'); this.enabledSetting.on('setting-updated', event => { // Rerender all messages (or if we're disabling emotes, those that have emotes) @@ -33,7 +32,25 @@ export default new class EmoteModule { } }); - return this.observe(); + const dataPath = Globals.getPath('data'); + try { + const emotes = await FileUtils.readJsonFromFile(path.join(dataPath, 'emotes.json')); + for (let emote of emotes) { + const uri = emote.type === 2 ? 'https://cdn.betterttv.net/emote/:id/1x' : emote.type === 1 ? 'https://cdn.frankerfacez.com/emoticon/:id/1' : 'https://static-cdn.jtvnw.net/emoticons/v1/:id/1.0'; + emote.name = emote.id; + emote.src = uri.replace(':id', emote.value.id || emote.value); + this.emotes.set(emote.id, emote); + } + } catch (err) { + Logger.err('EmoteModule', [`Failed to load emote data. Make sure you've downloaded the emote data and placed it in ${dataPath}:`, err]); + return; + } + + try { + await this.observe(); + } catch (err) { + Logger.err('EmoteModule', ['Error patching Message', err]); + } } /** @@ -65,10 +82,6 @@ export default new class EmoteModule { return this._searchCache || (this._searchCache = {}); } - get emoteDb() { - return emotes; - } - get React() { return WebpackModules.getModuleByName('React'); } @@ -97,16 +110,16 @@ export default new class EmoteModule { if (!words) continue; let text = null; for (const [wordIndex, word] of words.entries()) { - const isEmote = this.isEmote(word); - if (isEmote) { + const emote = this.getEmote(word); + if (emote) { if (text !== null) { newMarkup.push(text); text = null; } newMarkup.push(this.React.createElement('span', { className: 'bd-emote-outer', - 'data-bdemote-name': isEmote.name, - 'data-bdemote-src': isEmote.src, + 'data-bdemote-name': emote.name, + 'data-bdemote-src': emote.src, 'data-has-wrapper': /;[\w]+;/gmi.test(word) })); continue; @@ -129,7 +142,8 @@ export default new class EmoteModule { } injectAll() { - if (!emotesEnabled) return; + if (!this.enabledSetting.value) return; + const all = document.getElementsByClassName('bd-emote-outer'); for (const ec of all) { if (ec.children.length) continue; @@ -150,48 +164,36 @@ export default new class EmoteModule { } async observe() { - const dataPath = Globals.getPath('data'); - try { - emotes = await FileUtils.readJsonFromFile(path.join(dataPath, 'emotes.json')); - } catch (err) { - Logger.err('EmoteModule', [`Failed to load emote data. Make sure you've downloaded the emote data and placed it in ${dataPath}:`, err]); - return; - } - - try { - const Message = await ReactComponents.getComponent('Message'); - this.unpatchRender = MonkeyPatch('BD:EmoteModule', Message.component.prototype).after('render', (component, args, retVal) => { - try { - // First child has all the actual text content, second is the edited timestamp - const markup = this.findByProp(retVal, 'className', 'markup'); - if (!markup) return; - Logger.log('EmoteModule', ['Message :', retVal, component]); - markup.children[0] = this.processMarkup(markup.children[0], component.props.message.editedTimestamp || component.props.message.timestamp); - } catch (err) { - Logger.err('EmoteModule', err); - } - }); - for (const message of document.querySelectorAll('.message')) { - Reflection(message).forceUpdate(); + const Message = await ReactComponents.getComponent('Message'); + this.unpatchRender = MonkeyPatch('BD:EmoteModule', Message.component.prototype).after('render', (component, args, retVal) => { + try { + // First child has all the actual text content, second is the edited timestamp + const markup = this.findByProp(retVal, 'className', 'markup'); + if (!markup) return; + markup.children[0] = this.processMarkup(markup.children[0], component.props.message.editedTimestamp || component.props.message.timestamp); + } catch (err) { + Logger.err('EmoteModule', err); } - this.injectAll(); - this.unpatchMount = MonkeyPatch('BD:EmoteModule', Message.component.prototype).after('componentDidMount', component => { - const element = this.ReactDOM.findDOMNode(component); - if (!element) return; - this.injectEmotes(element); - }); - this.unpatchUpdate = MonkeyPatch('BD:EmoteModule', Message.component.prototype).after('componentDidUpdate', component => { - const element = this.ReactDOM.findDOMNode(component); - if (!element) return; - this.injectEmotes(element); - }); - } catch (err) { - Logger.err('EmoteModule', err); + }); + for (const message of document.querySelectorAll('.message')) { + Reflection(message).forceUpdate(); } + this.injectAll(); + this.unpatchMount = MonkeyPatch('BD:EmoteModule', Message.component.prototype).after('componentDidMount', component => { + const element = this.ReactDOM.findDOMNode(component); + if (!element) return; + this.injectEmotes(element); + }); + this.unpatchUpdate = MonkeyPatch('BD:EmoteModule', Message.component.prototype).after('componentDidUpdate', component => { + const element = this.ReactDOM.findDOMNode(component); + if (!element) return; + this.injectEmotes(element); + }); } injectEmote(root) { - if (!emotesEnabled) return; + if (!this.enabledSetting.value) return; + while (root.firstChild) { root.removeChild(root.firstChild); } @@ -206,25 +208,14 @@ export default new class EmoteModule { } injectEmotes(element) { - if (!emotesEnabled || !element) return; + if (!this.enabledSetting.value || !element) return; + for (const beo of element.getElementsByClassName('bd-emote-outer')) this.injectEmote(beo); } - isEmote(word) { - if (!emotes) return null; + getEmote(word) { const name = word.replace(/;/g, ''); - const emote = emotes.find(emote => emote.id === name); - if (!emote) return null; - let { id, value } = emote; - if (value.id) value = value.id; - const uri = emote.type === 2 ? 'https://cdn.betterttv.net/emote/:id/1x' : emote.type === 1 ? 'https://cdn.frankerfacez.com/emoticon/:id/1' : 'https://static-cdn.jtvnw.net/emoticons/v1/:id/1.0'; - return { name, src: uri.replace(':id', value) }; - } - - filterTest() { - const re = new RegExp('Kappa', 'i'); - const filtered = emotes.filter(emote => re.test(emote.id)); - return filtered.slice(0, 10); + return this.emotes.get(name); } filter(regex, limit, start = 0) { @@ -232,17 +223,21 @@ export default new class EmoteModule { if (this.searchCache.hasOwnProperty(key)) return this.searchCache[key]; let index = 0; let startIndex = 0; - return this.searchCache[key] = emotes.filter(emote => { - if (index >= limit) return false; + + const matching = this.searchCache[key] = []; + for (let emote of this.emotes.values()) { + if (index >= limit) break; if (regex.test(emote.id)) { if (startIndex < start) { startIndex++; - return false; + continue; } index++; - return true; + matching.push(emote); } - }); + } + + return matching; } } diff --git a/client/src/styles/partials/generic/autocomplete.scss b/client/src/styles/partials/generic/autocomplete.scss index bfc8ebd5..75312e1e 100644 --- a/client/src/styles/partials/generic/autocomplete.scss +++ b/client/src/styles/partials/generic/autocomplete.scss @@ -25,10 +25,14 @@ &.bd-selectable { cursor: pointer; - } - &.bd-selected { - background-color: #36393f; + &:hover { + background-color: darken(#36393f, 5%); + } + + &.bd-selected { + background-color: #36393f; + } } .bd-autocomplete-title {