Add favourite emotes (no UI yet)
This commit is contained in:
parent
f8a380fd59
commit
3f3898c774
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue