Add favourite emotes (no UI yet)

This commit is contained in:
Samuel Elliott 2018-03-31 01:17:42 +01:00
parent f8a380fd59
commit 3f3898c774
No known key found for this signature in database
GPG Key ID: 8420C7CDE43DC4D6
3 changed files with 91 additions and 36 deletions

View File

@ -1,20 +1,33 @@
<template> <template>
<span class="bd-emotewrapper" v-tooltip="name"> <span class="bd-emotewrapper" :class="{'bd-emote-favourite': favourite, 'bd-emote-no-wrapper': !hasWrapper}" v-tooltip="name" :data-emote-name="name">
<img class="bd-emote" :src="src" :alt="`;${name};`" /> <img class="bd-emote" :src="src" :alt="`;${name};`" @click="toggleFavourite" />
<span v-if="favourite" style="font-size: 12px;"> (favourite)</span>
</span> </span>
</template> </template>
<script> <script>
import { ClientLogger as Logger } from 'common';
import EmoteModule from './EmoteModule';
export default { export default {
data() { data() {
return { return {
favourite: false EmoteModule
}; };
}, },
props: ['src', 'name'], props: ['src', 'name', 'hasWrapper'],
methods: {}, computed: {
beforeMount() { favourite() {
// Check favourite state return EmoteModule.isFavourite(this.name);
}
},
methods: {
async toggleFavourite() {
await EmoteModule.setFavourite(this.name, !this.favourite);
Logger.log('EmoteComponent', `Set emote ${this.name} as ${this.favourite ? '' : 'un'}favourite`);
this.$forceUpdate();
}
} }
} }
</script> </script>

View File

@ -8,42 +8,76 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import { Events, Globals, WebpackModules, ReactComponents, MonkeyPatch } from 'modules'; import { Events, Settings, Globals, WebpackModules, ReactComponents, MonkeyPatch } from 'modules';
import { DOM, VueInjector, Reflection } from 'ui'; import { DOM, VueInjector, Reflection } from 'ui';
import { FileUtils, ClientLogger as Logger } from 'common'; import { Utils, FileUtils, ClientLogger as Logger } from 'common';
import path from 'path'; import path from 'path';
import EmoteComponent from './EmoteComponent.vue'; import EmoteComponent from './EmoteComponent.vue';
let emotes = null; let emotes = null;
const emotesEnabled = true; const emotesEnabled = true;
const enforceWrapperFrom = (new Date('2018-05-01')).valueOf();
export default class { export default new class EmoteModule {
static get searchCache() { constructor() {
this.favourite_emotes = [];
}
/**
* Sets an emote as favourite.
* @param {String} emote The name of the emote
* @param {Boolean} favourite The new favourite state
* @param {Boolean} save Whether to save settings
* @return {Promise}
*/
setFavourite(emote, favourite, save = true) {
if (favourite && !this.favourite_emotes.includes(emote)) this.favourite_emotes.push(emote);
if (!favourite) Utils.removeFromArray(this.favourite_emotes, emote);
if (save) return Settings.saveSettings();
}
addFavourite(emote, save = true) {
return this.setFavourite(emote, true, save);
}
removeFavourite(emote, save = true) {
return this.setFavourite(emote, false, save);
}
isFavourite(emote) {
return this.favourite_emotes.includes(emote);
}
get searchCache() {
return this._searchCache || (this._searchCache = {}); return this._searchCache || (this._searchCache = {});
} }
static get emoteDb() { get emoteDb() {
return emotes; return emotes;
} }
static get React() { get React() {
return WebpackModules.getModuleByName('React'); return WebpackModules.getModuleByName('React');
} }
static get ReactDOM() { get ReactDOM() {
return WebpackModules.getModuleByName('ReactDOM'); return WebpackModules.getModuleByName('ReactDOM');
} }
static processMarkup(markup) { processMarkup(markup, timestamp) {
if (!emotesEnabled) return markup; // TODO Get it from setttings if (!emotesEnabled) return markup; // TODO Get it from setttings
timestamp = timestamp.valueOf();
const allowNoWrapper = timestamp < enforceWrapperFrom;
const newMarkup = []; const newMarkup = [];
for (const child of markup) { for (const child of markup) {
if ('string' !== typeof child) { if (typeof child !== 'string') {
newMarkup.push(child); newMarkup.push(child);
continue; continue;
} }
if (!this.testWord(child)) { if (!this.testWord(child) && !allowNoWrapper) {
newMarkup.push(child); newMarkup.push(child);
continue; continue;
} }
@ -57,7 +91,12 @@ export default class {
newMarkup.push(text); newMarkup.push(text);
text = null; text = null;
} }
newMarkup.push(this.React.createElement('span', { className: 'bd-emote-outer', 'data-bdemote-name': isEmote.name, 'data-bdemote-src': isEmote.src })); newMarkup.push(this.React.createElement('span', {
className: 'bd-emote-outer',
'data-bdemote-name': isEmote.name,
'data-bdemote-src': isEmote.src,
'data-has-wrapper': /;[\w]+;/gmi.test(word)
}));
continue; continue;
} }
if (text === null) { if (text === null) {
@ -73,12 +112,11 @@ export default class {
return newMarkup; return newMarkup;
} }
static testWord(word) { testWord(word) {
if (!/;[\w]+;/gmi.test(word)) return false; return !/;[\w]+;/gmi.test(word);
return true;
} }
static injectAll() { injectAll() {
if (!emotesEnabled) return; if (!emotesEnabled) return;
const all = document.getElementsByClassName('bd-emote-outer'); const all = document.getElementsByClassName('bd-emote-outer');
for (const ec of all) { for (const ec of all) {
@ -87,7 +125,7 @@ export default class {
} }
} }
static findByProp(obj, what, value) { findByProp(obj, what, value) {
if (obj.hasOwnProperty(what) && obj[what] === value) return obj; if (obj.hasOwnProperty(what) && obj[what] === value) return obj;
if (obj.props && !obj.children) return this.findByProp(obj.props, what, value); if (obj.props && !obj.children) return this.findByProp(obj.props, what, value);
if (!obj.children || !obj.children.length) return null; if (!obj.children || !obj.children.length) return null;
@ -99,7 +137,7 @@ export default class {
return null; return null;
} }
static async observe() { async observe() {
const dataPath = Globals.getPath('data'); const dataPath = Globals.getPath('data');
try { try {
emotes = await FileUtils.readJsonFromFile(path.join(dataPath, 'emotes.json')); emotes = await FileUtils.readJsonFromFile(path.join(dataPath, 'emotes.json'));
@ -115,7 +153,8 @@ export default class {
// First child has all the actual text content, second is the edited timestamp // First child has all the actual text content, second is the edited timestamp
const markup = this.findByProp(retVal, 'className', 'markup'); const markup = this.findByProp(retVal, 'className', 'markup');
if (!markup) return; if (!markup) return;
markup.children[0] = this.processMarkup(markup.children[0]); Logger.log('EmoteModule', ['Message :', retVal, component]);
markup.children[0] = this.processMarkup(markup.children[0], component.props.message.editedTimestamp || component.props.message.timestamp);
} catch (err) { } catch (err) {
Logger.err('EmoteModule', err); Logger.err('EmoteModule', err);
} }
@ -139,27 +178,27 @@ export default class {
} }
} }
static injectEmote(root) { injectEmote(root) {
if (!emotesEnabled) return; if (!emotesEnabled) return;
while (root.firstChild) { while (root.firstChild) {
root.removeChild(root.firstChild); root.removeChild(root.firstChild);
} }
const { bdemoteName, bdemoteSrc } = root.dataset; const { bdemoteName, bdemoteSrc, hasWrapper } = root.dataset;
if (!bdemoteName || !bdemoteSrc) return; if (!bdemoteName || !bdemoteSrc) return;
VueInjector.inject(root, { VueInjector.inject(root, {
components: { EmoteComponent }, components: { EmoteComponent },
data: { src: bdemoteSrc, name: bdemoteName }, data: { src: bdemoteSrc, name: bdemoteName, hasWrapper },
template: '<EmoteComponent :src="src" :name="name" />' template: '<EmoteComponent :src="src" :name="name" :hasWrapper="hasWrapper" />'
}, DOM.createElement('span')); }, DOM.createElement('span'));
root.classList.add('bd-is-emote'); root.classList.add('bd-is-emote');
} }
static injectEmotes(element) { injectEmotes(element) {
if (!emotesEnabled || !element) return; if (!emotesEnabled || !element) return;
for (const beo of element.getElementsByClassName('bd-emote-outer')) this.injectEmote(beo); for (const beo of element.getElementsByClassName('bd-emote-outer')) this.injectEmote(beo);
} }
static isEmote(word) { isEmote(word) {
if (!emotes) return null; if (!emotes) return null;
const name = word.replace(/;/g, ''); const name = word.replace(/;/g, '');
const emote = emotes.find(emote => emote.id === name); const emote = emotes.find(emote => emote.id === name);
@ -170,13 +209,13 @@ export default class {
return { name, src: uri.replace(':id', value) }; return { name, src: uri.replace(':id', value) };
} }
static filterTest() { filterTest() {
const re = new RegExp('Kappa', 'i'); const re = new RegExp('Kappa', 'i');
const filtered = emotes.filter(emote => re.test(emote.id)); const filtered = emotes.filter(emote => re.test(emote.id));
return filtered.slice(0, 10); return filtered.slice(0, 10);
} }
static filter(regex, limit, start = 0) { filter(regex, limit, start = 0) {
const key = `${regex}:${limit}:${start}`; const key = `${regex}:${limit}:${start}`;
if (this.searchCache.hasOwnProperty(key)) return this.searchCache[key]; if (this.searchCache.hasOwnProperty(key)) return this.searchCache[key];
let index = 0; let index = 0;

View File

@ -8,8 +8,9 @@
* LICENSE file in the root directory of this source tree. * LICENSE file in the root directory of this source tree.
*/ */
import { Utils, FileUtils, ClientLogger as Logger } from 'common'; import { EmoteModule } from 'builtin';
import { SettingsSet, SettingUpdatedEvent } from 'structs'; import { SettingsSet, SettingUpdatedEvent } from 'structs';
import { Utils, FileUtils, ClientLogger as Logger } from 'common';
import path from 'path'; import path from 'path';
import Globals from './globals'; import Globals from './globals';
import CssEditor from './csseditor'; import CssEditor from './csseditor';
@ -47,7 +48,7 @@ export default new class Settings {
const settingsPath = path.resolve(this.dataPath, 'user.settings.json'); const settingsPath = path.resolve(this.dataPath, 'user.settings.json');
const user_config = await FileUtils.readJsonFromFile(settingsPath); const user_config = await FileUtils.readJsonFromFile(settingsPath);
const { settings, scss, css, css_editor_files, scss_error, css_editor_bounds } = user_config; const { settings, scss, css, css_editor_files, scss_error, css_editor_bounds, favourite_emotes } = user_config;
for (let set of this.settings) { for (let set of this.settings) {
const newSet = settings.find(s => s.id === set.id); const newSet = settings.find(s => s.id === set.id);
@ -58,6 +59,7 @@ export default new class Settings {
CssEditor.setState(scss, css, css_editor_files, scss_error); CssEditor.setState(scss, css, css_editor_files, scss_error);
CssEditor.editor_bounds = css_editor_bounds || {}; CssEditor.editor_bounds = css_editor_bounds || {};
EmoteModule.favourite_emotes = favourite_emotes;
} catch (err) { } catch (err) {
// There was an error loading settings // There was an error loading settings
// This probably means that the user doesn't have any settings yet // This probably means that the user doesn't have any settings yet
@ -79,7 +81,8 @@ export default new class Settings {
css: CssEditor.css, css: CssEditor.css,
css_editor_files: CssEditor.files, css_editor_files: CssEditor.files,
scss_error: CssEditor.error, scss_error: CssEditor.error,
css_editor_bounds: CssEditor.editor_bounds css_editor_bounds: CssEditor.editor_bounds,
favourite_emotes: EmoteModule.favourite_emotes
}); });
for (let set of this.settings) { for (let set of this.settings) {