Autocomplete
This commit is contained in:
parent
0316bbc1cd
commit
22740fb16c
|
@ -1,16 +0,0 @@
|
|||
/**
|
||||
* BetterDiscord Emote Autocomplete Component
|
||||
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
|
||||
* All rights reserved.
|
||||
* https://betterdiscord.net
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
<template>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {}
|
||||
</script>
|
|
@ -12,8 +12,18 @@ import BuiltinModule from './BuiltinModule';
|
|||
import path from 'path';
|
||||
import { Utils, FileUtils, ClientLogger as Logger } from 'common';
|
||||
import { Settings, Globals, WebpackModules, ReactComponents, MonkeyPatch, Cache } from 'modules';
|
||||
import { VueInjector } from 'ui';
|
||||
|
||||
import Emote from './EmoteComponent.js';
|
||||
import Autocomplete from '../ui/components/common/Autocomplete.vue';
|
||||
|
||||
import GlobalAc from '../ui/autocomplete';
|
||||
|
||||
const EMOTE_SOURCES = [
|
||||
'https://static-cdn.jtvnw.net/emoticons/v1/:id/1.0',
|
||||
'https://cdn.frankerfacez.com/emoticon/:id/1',
|
||||
'https://cdn.betterttv.net/emote/:id/1x'
|
||||
]
|
||||
|
||||
export default new class EmoteModule extends BuiltinModule {
|
||||
|
||||
|
@ -27,13 +37,14 @@ export default new class EmoteModule extends BuiltinModule {
|
|||
|
||||
async enabled() {
|
||||
|
||||
GlobalAc.add(';', this);
|
||||
|
||||
if (!this.database.size) {
|
||||
await this.loadLocalDb();
|
||||
}
|
||||
|
||||
this.patchMessageContent();
|
||||
const selector = `.${WebpackModules.getClassName('channelTextArea', 'emojiButton')}`;
|
||||
const cta = await ReactComponents.getComponent('ChannelTextArea', { selector });
|
||||
const cta = await ReactComponents.getComponent('ChannelTextArea', { selector: WebpackModules.getSelector('channelTextArea', 'emojiButton') });
|
||||
MonkeyPatch('BD:EMOTEMODULE', cta.component.prototype).before('handleSubmit', this.handleChannelTextAreaSubmit.bind(this));
|
||||
}
|
||||
|
||||
|
@ -43,7 +54,6 @@ export default new class EmoteModule extends BuiltinModule {
|
|||
|
||||
processMarkup(markup) {
|
||||
const newMarkup = [];
|
||||
window.markup = markup;
|
||||
const jumboable = !markup.some(child => {
|
||||
if (typeof child !== 'string') return false;
|
||||
|
||||
|
@ -101,7 +111,6 @@ export default new class EmoteModule extends BuiltinModule {
|
|||
}
|
||||
|
||||
afterRenderMessageContent(component, args, retVal) {
|
||||
console.log(component);
|
||||
const markup = Utils.findInReactTree(retVal, filter =>
|
||||
filter &&
|
||||
filter.className &&
|
||||
|
@ -141,4 +150,23 @@ export default new class EmoteModule extends BuiltinModule {
|
|||
return new Emote(type, id, name);
|
||||
}
|
||||
|
||||
search(regex, limit = 10) {
|
||||
if (typeof regex === 'string') regex = new RegExp(regex, 'i');
|
||||
const matching = [];
|
||||
|
||||
for (const [key, value] of this.database.entries()) {
|
||||
if (matching.length >= limit) break;
|
||||
if (regex.test(key)) matching.push({ key, value })
|
||||
}
|
||||
|
||||
return {
|
||||
actype: 'imagetext',
|
||||
items: matching.map(match => {
|
||||
match.value.src = EMOTE_SOURCES[match.value.type].replace(':id', match.value.id);
|
||||
match.value.replaceWith = `;${match.key};`;
|
||||
return match;
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
import { Settings, Globals, WebpackModules, ReactComponents, MonkeyPatch, Cache } from 'modules';
|
||||
import { VueInjector } from 'ui';
|
||||
|
||||
import AutocompleteComponent from './components/common/Autocomplete.vue';
|
||||
import { Utils } from 'common';
|
||||
|
||||
export default new class AutoComplete {
|
||||
|
||||
constructor() {}
|
||||
|
||||
get sets() {
|
||||
return this._sets || (this._sets = {});
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.cta = await ReactComponents.getComponent('ChannelTextArea', { selector: WebpackModules.getSelector('channelTextArea', 'emojiButton') });
|
||||
MonkeyPatch('BD:EMOTEMODULE', this.cta.component.prototype).after('render', this.channelTextAreaAfterRender.bind(this));
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
channelTextAreaAfterRender(component, args, retVal) {
|
||||
const inner = Utils.findInReactTree(retVal, filter => filter.className && filter.className.includes('inner'));
|
||||
if (!inner.children) return;
|
||||
inner.children.splice(0, 0, VueInjector.createReactElement(AutocompleteComponent, {
|
||||
controller: this
|
||||
}));
|
||||
}
|
||||
|
||||
add(prefix, controller) {
|
||||
if (!this.initialized) this.init();
|
||||
if (this.sets.hasOwnProperty(prefix)) return;
|
||||
this.sets[prefix] = controller;
|
||||
}
|
||||
|
||||
validPrefix(prefix) {
|
||||
return this.sets.hasOwnProperty(prefix);
|
||||
}
|
||||
|
||||
items(prefix, sterm) {
|
||||
if (!this.validPrefix(prefix)) return [];
|
||||
return this.sets[prefix].search(sterm);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* BetterDiscord Emote Autocomplete Component
|
||||
* BetterDiscord Autocomplete Component
|
||||
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
|
||||
* All rights reserved.
|
||||
* https://betterdiscord.net
|
||||
|
@ -9,24 +9,21 @@
|
|||
*/
|
||||
|
||||
<template>
|
||||
<div class="bd-autocomplete" :class="{'bd-active': emotes && emotes.length}">
|
||||
<div v-if="emotes && emotes.length" class="bd-autocompleteInner">
|
||||
<div class="bd-autocomplete">
|
||||
<div v-if="search.items.length" class="bd-autocompleteInner">
|
||||
<div class="bd-autocompleteRow">
|
||||
<div class="bd-autocompleteSelector">
|
||||
<div class="bd-autocompleteTitle">
|
||||
Emotes Matching:
|
||||
<strong>{{title}}</strong>
|
||||
Matching:
|
||||
<strong>{{sterm}}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="(emote, index) in emotes" class="bd-autocompleteRow" :key="index">
|
||||
<div class="bd-autocompleteSelector bd-selectable" :class="{'bd-selected': index === selectedIndex, 'bd-emoteFavourite': isFavourite(emote)}" @mouseover="selected = emote.id" @click="inject(emote)">
|
||||
<div v-for="(item, index) in search.items" class="bd-autocompleteRow" @mouseover="selectedIndex = index" @click="inject">
|
||||
<div class="bd-autocompleteSelector bd-selectable" :class="{'bd-selected': index === selectedIndex}">
|
||||
<div class="bd-autocompleteField">
|
||||
<img :src="emote.src" :alt="emote.name" />
|
||||
<div class="bd-flexGrow">{{emote.id}}</div>
|
||||
<div class="bd-emoteFavouriteButton" :class="{'bd-active': isFavourite(emote)}" @click.stop="toggleFavourite(emote)">
|
||||
<MiStar :size="16" />
|
||||
</div>
|
||||
<img v-if="search.actype === 'imagetext'" :src="item.value.src" :alt="item.key" />
|
||||
<div class="bd-flexGrow">{{item.key}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -35,142 +32,91 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { EmoteModule } from 'builtin';
|
||||
import { Events, Settings } from 'modules';
|
||||
import { DOMManip as Manip } from 'ui';
|
||||
import { MiStar } from './MaterialIcon';
|
||||
|
||||
import { WebpackModules, DiscordApi } from 'modules';
|
||||
export default {
|
||||
components: {
|
||||
MiStar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
EmoteModule,
|
||||
emotes: [],
|
||||
title: '',
|
||||
selIndex: 0,
|
||||
selected: '',
|
||||
open: false,
|
||||
selectedIndex: 0,
|
||||
fsterm: '',
|
||||
sterm: '',
|
||||
settingUpdatedHandler: null
|
||||
};
|
||||
search: { type: null, items: [] },
|
||||
selectedIndex: 0,
|
||||
textArea: null
|
||||
}
|
||||
},
|
||||
props: ['prefix', 'controller', 'textarea'],
|
||||
computed: {
|
||||
},
|
||||
created() {
|
||||
const enabledSetting = Settings.getSetting('emotes', 'default', 'enable');
|
||||
enabledSetting.on('setting-updated', this.settingUpdatedHandler = event => {
|
||||
if (event.value) return this.addEventListeners();
|
||||
this.removeEventListeners();
|
||||
this.reset();
|
||||
});
|
||||
|
||||
if (enabledSetting.value) this.addEventListeners();
|
||||
window.addEventListener('keydown', this.keyDown);
|
||||
window.addEventListener('keyup', this.keyUp);
|
||||
},
|
||||
destroyed() {
|
||||
if (this.settingUpdatedHandler) {
|
||||
const enabledSetting = Settings.getSetting('emotes', 'default', 'enable');
|
||||
enabledSetting.removeListener('setting-updated', this.settingUpdatedHandler);
|
||||
}
|
||||
|
||||
this.removeEventListeners();
|
||||
window.removeEventListener('keydown', this.keyDown);
|
||||
window.removeEventListener('keyup', this.keyUp);
|
||||
},
|
||||
methods: {
|
||||
addEventListeners() {
|
||||
window.addEventListener('keydown', this.prevents);
|
||||
const ta = document.querySelector('.chat textarea');
|
||||
if (!ta) return;
|
||||
ta.addEventListener('keydown', this.setCaret);
|
||||
ta.addEventListener('keyup', this.searchEmotes);
|
||||
},
|
||||
removeEventListeners() {
|
||||
window.removeEventListener('keydown', this.prevents);
|
||||
const ta = document.querySelector('.chat textarea');
|
||||
if (!ta) return;
|
||||
ta.removeEventListener('keydown', this.setCaret);
|
||||
ta.removeEventListener('keyup', this.searchEmotes);
|
||||
},
|
||||
prevents(e) {
|
||||
keyDown(e) {
|
||||
if (!this.open) return;
|
||||
if (e.which === 27) this.reset();
|
||||
else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') this.traverse(e);
|
||||
else if (e.key !== 'Tab' && e.key !== 'Enter') return;
|
||||
|
||||
const { which, key } = e;
|
||||
|
||||
if (key === 'ArrowDown' || key === 'ArrowUp') this.traverse(key);
|
||||
else if (key !== 'Tab' && key !== 'Enter') return;
|
||||
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
},
|
||||
setCaret(e) {
|
||||
this.caret = e.target.selectionEnd;
|
||||
},
|
||||
getEmoteSrc(emote) {
|
||||
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 uri.replace(':id', value);
|
||||
},
|
||||
isFavourite(emote) {
|
||||
return EmoteModule.isFavourite(emote);
|
||||
},
|
||||
toggleFavourite(emote) {
|
||||
return EmoteModule.setFavourite(emote, !this.isFavourite(emote));
|
||||
},
|
||||
searchEmotes(e) {
|
||||
if (e.which === 27 || e.key === 'ArrowDown' || e.key === 'ArrowUp') return;
|
||||
if (e.key === 'Tab' || e.key === 'Enter' && this.open) {
|
||||
const selected = this.emotes[this.selectedIndex];
|
||||
if (!selected) return;
|
||||
this.inject(selected);
|
||||
this.reset();
|
||||
keyUp(e) {
|
||||
const { which, key, target } = e;
|
||||
if (which === 27 || key === 'ArrowDown' || key === 'ArrowUp') return;
|
||||
if ((key === 'Tab' || key === 'Enter') && this.open) {
|
||||
this.inject();
|
||||
return;
|
||||
}
|
||||
|
||||
const { selectionEnd, value } = e.target;
|
||||
this.sterm = value.substr(0, selectionEnd).split(/\s+/g).pop();
|
||||
const { selectionEnd, value } = target;
|
||||
const sterm = value.slice(0, selectionEnd).split(/\s+/g).pop();
|
||||
|
||||
if (!this.sterm.startsWith(';')) {
|
||||
this.reset();
|
||||
const prefix = sterm.slice(0, 1);
|
||||
const search = this.controller.items(prefix, sterm.slice(1));
|
||||
const { type, items } = search;
|
||||
|
||||
if (!items || !items.length) {
|
||||
this.open = false;
|
||||
this.search = { type: null, items: [] };
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.sterm.length < 4) {
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
this.title = this.sterm.substr(1);
|
||||
this.emotes = EmoteModule.filter(new RegExp(this.sterm.substr(1), 'i'), 10);
|
||||
this.open = this.emotes.length;
|
||||
},
|
||||
traverse(e) {
|
||||
if (!this.open) return;
|
||||
if (e.key === 'ArrowUp') {
|
||||
this.selectedIndex = (this.selectedIndex - 1) < 0 ? Math.min(this.emotes.length, 10) - 1 : this.selectedIndex - 1;
|
||||
return;
|
||||
}
|
||||
if (e.key === 'ArrowDown') {
|
||||
this.selectedIndex = (this.selectedIndex + 1) >= Math.min(this.emotes.length, 10) ? 0 : this.selectedIndex + 1;
|
||||
return;
|
||||
}
|
||||
return;
|
||||
},
|
||||
reset() {
|
||||
this.emotes = [];
|
||||
this.title = '';
|
||||
this.selIndex = 0;
|
||||
this.selected = '';
|
||||
this.open = false;
|
||||
this.textArea = target;
|
||||
this.selectedIndex = 0;
|
||||
this.sterm = '';
|
||||
this.fsterm = sterm;
|
||||
this.sterm = sterm.slice(1);
|
||||
this.open = true;
|
||||
this.search = search;
|
||||
},
|
||||
inject(emote) {
|
||||
const ta = document.querySelector('.chat textarea');
|
||||
if (!ta) return;
|
||||
const { selectionEnd, value } = ta;
|
||||
const en = `;${emote.id};`;
|
||||
let substr = value.substr(0, selectionEnd);
|
||||
substr = substr.replace(new RegExp(this.sterm + '$'), en);
|
||||
traverse(key) {
|
||||
if (!this.open) return;
|
||||
if (key === 'ArrowUp') {
|
||||
this.selectedIndex = (this.selectedIndex - 1) < 0 ? Math.min(this.search.items.length, 10) - 1 : this.selectedIndex - 1;
|
||||
return;
|
||||
}
|
||||
if (key === 'ArrowDown') {
|
||||
this.selectedIndex = (this.selectedIndex + 1) >= Math.min(this.search.items.length, 10) ? 0 : this.selectedIndex + 1;
|
||||
return;
|
||||
}
|
||||
},
|
||||
inject() {
|
||||
if (!this.textArea) return;
|
||||
const { selectionEnd, value } = this.textArea;
|
||||
|
||||
Manip.setText(substr + value.substr(selectionEnd, value.length), false);
|
||||
ta.selectionEnd = ta.selectionStart = selectionEnd + en.length - this.sterm.length;
|
||||
this.reset();
|
||||
let substr = value.substr(0, selectionEnd);
|
||||
substr = substr.replace(new RegExp(this.fsterm + '$'), this.search.items[this.selectedIndex].value.replaceWith);
|
||||
|
||||
WebpackModules.getModuleByName('DraftActions').saveDraft(DiscordApi.currentChannel.id, substr + value.substr(selectionEnd, value.length));
|
||||
this.textArea.selectionEnd = this.textArea.selectionStart = selectionEnd + this.search.items[this.selectedIndex].value.replaceWith.length - this.fsterm.length;
|
||||
this.open = false;
|
||||
this.search = { type: null, items: [] };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
/**
|
||||
* BetterDiscord Emote Autocomplete Component
|
||||
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
|
||||
* All rights reserved.
|
||||
* https://betterdiscord.net
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
<template>
|
||||
<div class="bd-autocomplete" :class="{'bd-active': emotes && emotes.length}">
|
||||
<div v-if="emotes && emotes.length" class="bd-autocompleteInner">
|
||||
<div class="bd-autocompleteRow">
|
||||
<div class="bd-autocompleteSelector">
|
||||
<div class="bd-autocompleteTitle">
|
||||
Emotes Matching:
|
||||
<strong>{{title}}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="(emote, index) in emotes" class="bd-autocompleteRow" :key="index">
|
||||
<div class="bd-autocompleteSelector bd-selectable" :class="{'bd-selected': index === selectedIndex, 'bd-emoteFavourite': isFavourite(emote)}" @mouseover="selected = emote.id" @click="inject(emote)">
|
||||
<div class="bd-autocompleteField">
|
||||
<img :src="emote.src" :alt="emote.name" />
|
||||
<div class="bd-flexGrow">{{emote.id}}</div>
|
||||
<div class="bd-emoteFavouriteButton" :class="{'bd-active': isFavourite(emote)}" @click.stop="toggleFavourite(emote)">
|
||||
<MiStar :size="16" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { EmoteModule } from 'builtin';
|
||||
import { Events, Settings } from 'modules';
|
||||
import { DOMManip as Manip } from 'ui';
|
||||
import { MiStar } from './MaterialIcon';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MiStar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
EmoteModule,
|
||||
emotes: [],
|
||||
title: '',
|
||||
selIndex: 0,
|
||||
selected: '',
|
||||
open: false,
|
||||
selectedIndex: 0,
|
||||
sterm: '',
|
||||
settingUpdatedHandler: null
|
||||
};
|
||||
},
|
||||
created() {
|
||||
const enabledSetting = Settings.getSetting('emotes', 'default', 'enable');
|
||||
enabledSetting.on('setting-updated', this.settingUpdatedHandler = event => {
|
||||
if (event.value) return this.addEventListeners();
|
||||
this.removeEventListeners();
|
||||
this.reset();
|
||||
});
|
||||
|
||||
if (enabledSetting.value) this.addEventListeners();
|
||||
},
|
||||
destroyed() {
|
||||
if (this.settingUpdatedHandler) {
|
||||
const enabledSetting = Settings.getSetting('emotes', 'default', 'enable');
|
||||
enabledSetting.removeListener('setting-updated', this.settingUpdatedHandler);
|
||||
}
|
||||
|
||||
this.removeEventListeners();
|
||||
},
|
||||
methods: {
|
||||
addEventListeners() {
|
||||
window.addEventListener('keydown', this.prevents);
|
||||
const ta = document.querySelector('.chat textarea');
|
||||
if (!ta) return;
|
||||
ta.addEventListener('keydown', this.setCaret);
|
||||
ta.addEventListener('keyup', this.searchEmotes);
|
||||
},
|
||||
removeEventListeners() {
|
||||
window.removeEventListener('keydown', this.prevents);
|
||||
const ta = document.querySelector('.chat textarea');
|
||||
if (!ta) return;
|
||||
ta.removeEventListener('keydown', this.setCaret);
|
||||
ta.removeEventListener('keyup', this.searchEmotes);
|
||||
},
|
||||
prevents(e) {
|
||||
if (!this.open) return;
|
||||
if (e.which === 27) this.reset();
|
||||
else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') this.traverse(e);
|
||||
else if (e.key !== 'Tab' && e.key !== 'Enter') return;
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
},
|
||||
setCaret(e) {
|
||||
this.caret = e.target.selectionEnd;
|
||||
},
|
||||
getEmoteSrc(emote) {
|
||||
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 uri.replace(':id', value);
|
||||
},
|
||||
isFavourite(emote) {
|
||||
return EmoteModule.isFavourite(emote);
|
||||
},
|
||||
toggleFavourite(emote) {
|
||||
return EmoteModule.setFavourite(emote, !this.isFavourite(emote));
|
||||
},
|
||||
searchEmotes(e) {
|
||||
if (e.which === 27 || e.key === 'ArrowDown' || e.key === 'ArrowUp') return;
|
||||
if (e.key === 'Tab' || e.key === 'Enter' && this.open) {
|
||||
const selected = this.emotes[this.selectedIndex];
|
||||
if (!selected) return;
|
||||
this.inject(selected);
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
const { selectionEnd, value } = e.target;
|
||||
this.sterm = value.substr(0, selectionEnd).split(/\s+/g).pop();
|
||||
|
||||
if (!this.sterm.startsWith(';')) {
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.sterm.length < 4) {
|
||||
this.reset();
|
||||
return;
|
||||
}
|
||||
this.title = this.sterm.substr(1);
|
||||
this.emotes = EmoteModule.filter(new RegExp(this.sterm.substr(1), 'i'), 10);
|
||||
this.open = this.emotes.length;
|
||||
},
|
||||
traverse(e) {
|
||||
if (!this.open) return;
|
||||
if (e.key === 'ArrowUp') {
|
||||
this.selectedIndex = (this.selectedIndex - 1) < 0 ? Math.min(this.emotes.length, 10) - 1 : this.selectedIndex - 1;
|
||||
return;
|
||||
}
|
||||
if (e.key === 'ArrowDown') {
|
||||
this.selectedIndex = (this.selectedIndex + 1) >= Math.min(this.emotes.length, 10) ? 0 : this.selectedIndex + 1;
|
||||
return;
|
||||
}
|
||||
return;
|
||||
},
|
||||
reset() {
|
||||
this.emotes = [];
|
||||
this.title = '';
|
||||
this.selIndex = 0;
|
||||
this.selected = '';
|
||||
this.open = false;
|
||||
this.selectedIndex = 0;
|
||||
this.sterm = '';
|
||||
},
|
||||
inject(emote) {
|
||||
const ta = document.querySelector('.chat textarea');
|
||||
if (!ta) return;
|
||||
const { selectionEnd, value } = ta;
|
||||
const en = `;${emote.id};`;
|
||||
let substr = value.substr(0, selectionEnd);
|
||||
substr = substr.replace(new RegExp(this.sterm + '$'), en);
|
||||
|
||||
Manip.setText(substr + value.substr(selectionEnd, value.length), false);
|
||||
ta.selectionEnd = ta.selectionStart = selectionEnd + en.length - this.sterm.length;
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
Loading…
Reference in New Issue