Merge pull request #168 from JsSucks/optimize
Optimize - Requires Core Rebuild(?)
This commit is contained in:
commit
d1dc8140e4
|
@ -19,12 +19,3 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style>
|
|
||||||
.bd-emotewrapper {
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
.bd-emotewrapper img {
|
|
||||||
max-height: 32px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -8,84 +8,98 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
import { FileUtils } from 'common';
|
import { FileUtils } from 'common';
|
||||||
import { Events, Globals } from 'modules';
|
import { Events, Globals, WebpackModules, ReactComponents } from 'modules';
|
||||||
import { DOM, VueInjector } from 'ui';
|
import { DOM, VueInjector } from 'ui';
|
||||||
import EmoteComponent from './EmoteComponent.vue';
|
import EmoteComponent from './EmoteComponent.vue';
|
||||||
let emotes = null;
|
let emotes = null;
|
||||||
|
const emotesEnabled = true;
|
||||||
|
|
||||||
export default class {
|
export default class {
|
||||||
|
static get React() {
|
||||||
|
return WebpackModules.getModuleByName('React');
|
||||||
|
}
|
||||||
|
static processMarkup(markup) {
|
||||||
|
if (!emotesEnabled) return markup; // TODO Get it from setttings
|
||||||
|
const newMarkup = [];
|
||||||
|
for (const [ti, t] of markup.entries()) {
|
||||||
|
if ('string' !== typeof t) {
|
||||||
|
newMarkup.push(t);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const words = t.split(/([^\s]+)([\s]|$)/g);
|
||||||
|
if (!words) continue;
|
||||||
|
let text = null;
|
||||||
|
for (const [wi, word] of words.entries()) {
|
||||||
|
let isEmote = false;
|
||||||
|
if (this.testWord(word)) {
|
||||||
|
isEmote = true;
|
||||||
|
}
|
||||||
|
if (isEmote) {
|
||||||
|
if (text !== null) {
|
||||||
|
newMarkup.push(text);
|
||||||
|
text = null;
|
||||||
|
}
|
||||||
|
newMarkup.push(this.React.createElement('span', { className: 'bd-emote-outer' }, word));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (text === null) {
|
||||||
|
text = `${word}`;
|
||||||
|
} else {
|
||||||
|
text += `${word}`;
|
||||||
|
}
|
||||||
|
if (wi === words.length - 1) {
|
||||||
|
newMarkup.push(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newMarkup;
|
||||||
|
}
|
||||||
|
|
||||||
|
static testWord(word) {
|
||||||
|
if (!/:[\w]+:/gmi.test(word)) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static injectAll() {
|
||||||
|
if (!emotesEnabled) return;
|
||||||
|
const all = document.getElementsByClassName('bd-emote-outer');
|
||||||
|
for (const ec of all) {
|
||||||
|
if (ec.children.length) continue;
|
||||||
|
this.injectEmote(ec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static async observe() {
|
static async observe() {
|
||||||
const dataPath = Globals.getObject('paths').find(path => path.id === 'data').path;
|
const dataPath = Globals.getObject('paths').find(path => path.id === 'data').path;
|
||||||
try {
|
try {
|
||||||
emotes = await FileUtils.readJsonFromFile(dataPath + '/emotes.json');
|
emotes = await FileUtils.readJsonFromFile(dataPath + '/emotes.json');
|
||||||
Events.on('ui:mutable:.markup',
|
const Message = await ReactComponents.getComponent('Message');
|
||||||
markup => {
|
Message.on('componentDidMount', ({ element }) => this.injectEmotes(element));
|
||||||
if (!emotes) return;
|
Message.on('componentDidUpdate', ({ state, element }) => {
|
||||||
this.injectEmotes(markup);
|
if (!state.isEditing) this.injectEmotes(element);
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static injectEmotes(node) {
|
static injectEmote(e) {
|
||||||
if (!/:[\w]+:/gmi.test(node.textContent)) return node;
|
if (!emotesEnabled) return;
|
||||||
const childNodes = [...node.childNodes];
|
const isEmote = this.isEmote(e.textContent);
|
||||||
const newNode = document.createElement('div');
|
if (!isEmote) return;
|
||||||
newNode.className = node.className;
|
|
||||||
newNode.classList.add('hasEmotes');
|
|
||||||
|
|
||||||
for (const [cni, cn] of childNodes.entries()) {
|
|
||||||
if (cn.nodeType !== Node.TEXT_NODE) {
|
|
||||||
newNode.appendChild(cn);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { nodeValue } = cn;
|
|
||||||
const words = nodeValue.split(/([^\s]+)([\s]|$)/g);
|
|
||||||
|
|
||||||
if (!words.some(word => word.startsWith(':') && word.endsWith(':'))) {
|
|
||||||
newNode.appendChild(cn);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let text = null;
|
|
||||||
for (const [wi, word] of words.entries()) {
|
|
||||||
let isEmote = null;
|
|
||||||
if (word.startsWith(':') && word.endsWith(':')) {
|
|
||||||
isEmote = this.isEmote(word);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isEmote) {
|
|
||||||
if (text !== null) {
|
|
||||||
newNode.appendChild(document.createTextNode(text));
|
|
||||||
text = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const emoteRoot = document.createElement('span');
|
|
||||||
newNode.appendChild(emoteRoot);
|
|
||||||
VueInjector.inject(
|
VueInjector.inject(
|
||||||
emoteRoot,
|
e,
|
||||||
DOM.createElement('span'),
|
DOM.createElement('span'),
|
||||||
{ EmoteComponent },
|
{ EmoteComponent },
|
||||||
`<EmoteComponent src="${isEmote.src}" name="${isEmote.name}"/>`,
|
`<EmoteComponent src="${isEmote.src}" name="${isEmote.name}"/>`
|
||||||
true
|
|
||||||
);
|
);
|
||||||
continue;
|
e.classList.add('bd-is-emote');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (text === null) {
|
static injectEmotes(element) {
|
||||||
text = word;
|
if (!emotesEnabled || !element) return;
|
||||||
} else {
|
for (const beo of element.getElementsByClassName('bd-emote-outer')) this.injectEmote(beo);
|
||||||
text += word;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wi === words.length - 1) {
|
|
||||||
newNode.appendChild(document.createTextNode(text));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
node.replaceWith(newNode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static isEmote(word) {
|
static isEmote(word) {
|
||||||
|
|
|
@ -8,33 +8,48 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DOM, BdUI, Modals } from 'ui';
|
import { DOM, BdUI, Modals, Reflection } from 'ui';
|
||||||
import BdCss from './styles/index.scss';
|
import BdCss from './styles/index.scss';
|
||||||
import { Events, CssEditor, Globals, ExtModuleManager, PluginManager, ThemeManager, ModuleManager, WebpackModules, Settings, Database, DiscordApi } from 'modules';
|
import { Patcher, Events, CssEditor, Globals, ExtModuleManager, PluginManager, ThemeManager, ModuleManager, WebpackModules, Settings, Database, ReactComponents, ReactAutoPatcher, DiscordApi } from 'modules';
|
||||||
import { ClientLogger as Logger, ClientIPC } from 'common';
|
import { ClientLogger as Logger, ClientIPC, Utils } from 'common';
|
||||||
import { EmoteModule } from 'builtin';
|
import { EmoteModule } from 'builtin';
|
||||||
const ignoreExternal = false;
|
const ignoreExternal = false;
|
||||||
|
const DEV = true;
|
||||||
|
|
||||||
class BetterDiscord {
|
class BetterDiscord {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
window.discordApi = DiscordApi;
|
window.BDDEVMODE = function () {
|
||||||
window.bddb = Database;
|
if (!DEV) return;
|
||||||
window.bdglobals = Globals;
|
window._bd = {
|
||||||
window.ClientIPC = ClientIPC;
|
DOM,
|
||||||
window.css = CssEditor;
|
BdUI,
|
||||||
window.pm = PluginManager;
|
Modals,
|
||||||
window.tm = ThemeManager;
|
Reflection,
|
||||||
window.events = Events;
|
Patcher,
|
||||||
window.wpm = WebpackModules;
|
Events,
|
||||||
window.bdsettings = Settings;
|
CssEditor,
|
||||||
window.bdmodals = Modals;
|
Globals,
|
||||||
window.bdlogs = Logger;
|
ExtModuleManager,
|
||||||
window.emotes = EmoteModule;
|
PluginManager,
|
||||||
window.dom = DOM;
|
ThemeManager,
|
||||||
|
ModuleManager,
|
||||||
|
WebpackModules,
|
||||||
|
Settings,
|
||||||
|
Database,
|
||||||
|
ReactComponents,
|
||||||
|
DiscordApi,
|
||||||
|
Logger,
|
||||||
|
ClientIPC,
|
||||||
|
Utils,
|
||||||
|
EmoteModule
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DOM.injectStyle(BdCss, 'bdmain');
|
DOM.injectStyle(BdCss, 'bdmain');
|
||||||
Events.on('global-ready', this.globalReady.bind(this));
|
this.globalReady = this.globalReady.bind(this);
|
||||||
|
Events.on('global-ready', this.globalReady);
|
||||||
|
Globals.initg();
|
||||||
}
|
}
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
|
@ -68,5 +83,11 @@ class BetterDiscord {
|
||||||
if (window.BetterDiscord) {
|
if (window.BetterDiscord) {
|
||||||
Logger.log('main', 'Attempting to inject again?');
|
Logger.log('main', 'Attempting to inject again?');
|
||||||
} else {
|
} else {
|
||||||
let bdInstance = new BetterDiscord();
|
let instance = null;
|
||||||
|
// eslint-disable-next-line no-inner-declarations
|
||||||
|
function init() {
|
||||||
|
instance = new BetterDiscord();
|
||||||
|
}
|
||||||
|
Events.on('autopatcher', init);
|
||||||
|
ReactAutoPatcher.autoPatch().then(() => Events.emit('autopatcher'));
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,9 @@ export default new class extends Module {
|
||||||
|
|
||||||
constructor(args) {
|
constructor(args) {
|
||||||
super(args);
|
super(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
initg() {
|
||||||
this.first();
|
this.first();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,3 +15,5 @@ export { default as Permissions } from './permissionmanager';
|
||||||
export { default as Database } from './database';
|
export { default as Database } from './database';
|
||||||
export { default as EventsWrapper } from './eventswrapper';
|
export { default as EventsWrapper } from './eventswrapper';
|
||||||
export { default as DiscordApi } from './discordapi';
|
export { default as DiscordApi } from './discordapi';
|
||||||
|
export { default as Patcher } from './patcher';
|
||||||
|
export * from './reactcomponents';
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
/**
|
||||||
|
* BetterDiscord Component Patcher
|
||||||
|
* Copyright (c) 2015-present JsSucks - https://github.com/JsSucks
|
||||||
|
* All rights reserved.
|
||||||
|
* https://github.com/JsSucks - 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import WebpackModules from './webpackmodules';
|
||||||
|
|
||||||
|
export default class Patcher {
|
||||||
|
static get patches() { return this._patches || (this._patches = {}) }
|
||||||
|
static resolveModule(mn) {
|
||||||
|
if (mn instanceof Function || (mn instanceof Object && !(mn instanceof Array))) return mn;
|
||||||
|
if ('string' === typeof mn) return WebpackModules.getModuleByName(mn);
|
||||||
|
if (mn instanceof Array) return WebpackModules.getModuleByProps(mn);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
static overrideFn(patch) {
|
||||||
|
return function () {
|
||||||
|
for (const s of patch.supers) {
|
||||||
|
try {
|
||||||
|
s.fn.apply(this, arguments);
|
||||||
|
} catch (err) { }
|
||||||
|
}
|
||||||
|
const retVal = patch.ofn.apply(this, arguments);
|
||||||
|
for (const s of patch.slaves) {
|
||||||
|
try {
|
||||||
|
s.fn.apply(this, [arguments, { patch, retVal }]);
|
||||||
|
} catch (err) { }
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static rePatch(po) {
|
||||||
|
po.patch = po.module[po.fnn] = this.overrideFn(po);
|
||||||
|
}
|
||||||
|
|
||||||
|
static pushPatch(id, module, fnn) {
|
||||||
|
const patch = {
|
||||||
|
module,
|
||||||
|
fnn,
|
||||||
|
ofn: module[fnn],
|
||||||
|
revert: () => {
|
||||||
|
patch.module[patch.fnn] = patch.ofn;
|
||||||
|
patch.patch = null;
|
||||||
|
patch.slaves = patch.supers = [];
|
||||||
|
},
|
||||||
|
supers: [],
|
||||||
|
slaves: [],
|
||||||
|
patch: null
|
||||||
|
};
|
||||||
|
patch.patch = module[fnn] = this.overrideFn(patch);
|
||||||
|
return this.patches[id] = patch;
|
||||||
|
}
|
||||||
|
|
||||||
|
static superpatch(mn, fnn, cb, dn) {
|
||||||
|
const module = this.resolveModule(mn);
|
||||||
|
if (!module || !module[fnn] || !(module[fnn] instanceof Function)) return null;
|
||||||
|
const displayName = 'string' === typeof mn ? mn : dn || module.displayName || module.name || module.constructor.displayName || module.constructor.name;
|
||||||
|
const patchId = `${displayName}:${fnn}`;
|
||||||
|
const patchObject = this.patches[patchId] || this.pushPatch(patchId, module, fnn);
|
||||||
|
if (!patchObject.patch) this.rePatch(patchObject);
|
||||||
|
const id = patchObject.supers.length + 1;
|
||||||
|
const patch = {
|
||||||
|
id,
|
||||||
|
fn: cb,
|
||||||
|
unpatch: () => patchObject.supers.splice(patchObject.supers.findIndex(slave => slave.id === id), 1)
|
||||||
|
};
|
||||||
|
patchObject.supers.push(patch);
|
||||||
|
return patch;
|
||||||
|
}
|
||||||
|
|
||||||
|
static slavepatch(mn, fnn, cb, dn) {
|
||||||
|
const module = this.resolveModule(mn);
|
||||||
|
if (!module || !module[fnn] || !(module[fnn] instanceof Function)) return null;
|
||||||
|
const displayName = 'string' === typeof mn ? mn : dn || module.displayName || module.name || module.constructor.displayName || module.constructor.name;
|
||||||
|
const patchId = `${displayName}:${fnn}`;
|
||||||
|
const patchObject = this.patches[patchId] || this.pushPatch(patchId, module, fnn);
|
||||||
|
if (!patchObject.patch) this.rePatch(patchObject);
|
||||||
|
const id = patchObject.slaves.length + 1;
|
||||||
|
const patch = {
|
||||||
|
id,
|
||||||
|
fn: cb,
|
||||||
|
unpatch: () => patchObject.slaves.splice(patchObject.slaves.findIndex(slave => slave.id === id), 1)
|
||||||
|
};
|
||||||
|
patchObject.slaves.push(patch);
|
||||||
|
return patch;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,426 @@
|
||||||
|
/**
|
||||||
|
* BetterDiscord React Component Manipulations
|
||||||
|
* original concept and some code by samogot - https://github.com/samogot / https://github.com/samogot/betterdiscord-plugins/tree/master/v2/1Lib%20Discord%20Internals
|
||||||
|
* Copyright (c) 2015-present JsSucks - https://github.com/JsSucks
|
||||||
|
* All rights reserved.
|
||||||
|
* https://github.com/JsSucks - 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Patcher from './patcher';
|
||||||
|
import WebpackModules from './webpackmodules';
|
||||||
|
import DiscordApi from './discordapi';
|
||||||
|
import { EmoteModule } from 'builtin';
|
||||||
|
import { Reflection } from 'ui';
|
||||||
|
|
||||||
|
class Filters {
|
||||||
|
static get byPrototypeFields() {
|
||||||
|
return (fields, selector = x => x) => (module) => {
|
||||||
|
const component = selector(module);
|
||||||
|
if (!component) return false;
|
||||||
|
if (!component.prototype) return false;
|
||||||
|
for (const field of fields) {
|
||||||
|
if (!component.prototype[field]) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static get byCode() {
|
||||||
|
return (search, selector = x => x) => (module) => {
|
||||||
|
const method = selector(module);
|
||||||
|
if (!method) return false;
|
||||||
|
return method.toString().search(search) !== -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static get and() {
|
||||||
|
return (...filters) => (module) => {
|
||||||
|
for (const filter of filters) {
|
||||||
|
if (!filter(module)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Helpers {
|
||||||
|
static get plannedActions() {
|
||||||
|
return this._plannedActions || (this._plannedActions = new Map());
|
||||||
|
}
|
||||||
|
static recursiveArray(parent, key, count = 1) {
|
||||||
|
let index = 0;
|
||||||
|
function* innerCall(parent, key) {
|
||||||
|
const item = parent[key];
|
||||||
|
if (item instanceof Array) {
|
||||||
|
for (const subKey of item.keys()) {
|
||||||
|
yield* innerCall(item, subKey)
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
yield { item, parent, key, index: index++, count };
|
||||||
|
}
|
||||||
|
|
||||||
|
return innerCall(parent, key);
|
||||||
|
}
|
||||||
|
static recursiveArrayCount(parent, key) {
|
||||||
|
let count = 0;
|
||||||
|
// eslint-disable-next-line no-empty-pattern
|
||||||
|
for (let { } of this.recursiveArray(parent, key))
|
||||||
|
++count;
|
||||||
|
return this.recursiveArray(parent, key, count);
|
||||||
|
}
|
||||||
|
static get recursiveChildren() {
|
||||||
|
return function*(parent, key, index = 0, count = 1) {
|
||||||
|
const item = parent[key];
|
||||||
|
yield { item, parent, key, index, count };
|
||||||
|
if (item && item.props && item.props.children) {
|
||||||
|
for (let { parent, key, index, count } of this.recursiveArrayCount(item.props, 'children')) {
|
||||||
|
yield* this.recursiveChildren(parent, key, index, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static returnFirst(iterator, process) {
|
||||||
|
for (let child of iterator) {
|
||||||
|
const retVal = process(child);
|
||||||
|
if (retVal !== undefined) return retVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static getFirstChild(rootParent, rootKey, selector) {
|
||||||
|
const getDirectChild = (item, selector) => {
|
||||||
|
if (item && item.props && item.props.children) {
|
||||||
|
return this.returnFirst(this.recursiveArrayCount(item.props, 'children'), checkFilter.bind(null, selector));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const checkFilter = (selector, { item, parent, key, count, index }) => {
|
||||||
|
let match = true;
|
||||||
|
if (match && selector.type)
|
||||||
|
match = item && selector.type === item.type;
|
||||||
|
if (match && selector.tag)
|
||||||
|
match = item && typeof item.type === 'string' && selector.tag === item.type;
|
||||||
|
if (match && selector.className) {
|
||||||
|
match = item && item.props && typeof item.props.className === 'string';
|
||||||
|
if (match) {
|
||||||
|
const classes = item.props.className.split(' ');
|
||||||
|
if (selector.className === true)
|
||||||
|
match = !!classes[0];
|
||||||
|
else if (typeof selector.className === 'string')
|
||||||
|
match = classes.includes(selector.className);
|
||||||
|
else if (selector.className instanceof RegExp)
|
||||||
|
match = !!classes.find(cls => selector.className.test(cls));
|
||||||
|
else match = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (match && selector.text) {
|
||||||
|
if (selector.text === true)
|
||||||
|
match = typeof item === 'string';
|
||||||
|
else if (typeof selector.text === 'string')
|
||||||
|
match = item === selector.text;
|
||||||
|
else if (selector.text instanceof RegExp)
|
||||||
|
match = typeof item === 'string' && selector.text.test(item);
|
||||||
|
else match = false;
|
||||||
|
}
|
||||||
|
if (match && selector.nthChild)
|
||||||
|
match = index === (selector.nthChild < 0 ? count + selector.nthChild : selector.nthChild);
|
||||||
|
if (match && selector.hasChild)
|
||||||
|
match = getDirectChild(item, selector.hasChild);
|
||||||
|
if (match && selector.hasSuccessor)
|
||||||
|
match = item && !!this.getFirstChild(parent, key, selector.hasSuccessor).item;
|
||||||
|
if (match && selector.eq) {
|
||||||
|
--selector.eq;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (match) {
|
||||||
|
if (selector.child) {
|
||||||
|
return getDirectChild(item, selector.child);
|
||||||
|
}
|
||||||
|
else if (selector.successor) {
|
||||||
|
return this.getFirstChild(parent, key, selector.successor);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return { item, parent, key };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return this.returnFirst(this.recursiveChildren(rootParent, rootKey), checkFilter.bind(null, selector)) || {};
|
||||||
|
}
|
||||||
|
static parseSelector(selector) {
|
||||||
|
if (selector.startsWith('.')) return { className: selector.substr(1) }
|
||||||
|
if (selector.startsWith('#')) return { id: selector.substr(1) }
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
static findByProp(obj, what, value) {
|
||||||
|
if (obj.hasOwnProperty(what) && obj[what] === value) return obj;
|
||||||
|
if (obj.props && !obj.children) return this.findByProp(obj.props, what, value);
|
||||||
|
if (!obj.children || !obj.children.length) return null;
|
||||||
|
for (const child of obj.children) {
|
||||||
|
if (!child) continue;
|
||||||
|
const findInChild = this.findByProp(child, what, value);
|
||||||
|
if (findInChild) return findInChild;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
static get ReactDOM() {
|
||||||
|
return WebpackModules.getModuleByName('ReactDOM');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReactComponent {
|
||||||
|
constructor(id, component, retVal) {
|
||||||
|
this._id = id;
|
||||||
|
this._component = component;
|
||||||
|
this._retVal = retVal;
|
||||||
|
const self = this;
|
||||||
|
Patcher.slavepatch(this.component.prototype, 'componentDidMount', function (a, parv) {
|
||||||
|
self.eventCallback('componentDidMount', {
|
||||||
|
props: this.props,
|
||||||
|
state: this.state,
|
||||||
|
element: Helpers.ReactDOM.findDOMNode(this),
|
||||||
|
retVal: parv.retVal
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Patcher.slavepatch(this.component.prototype, 'componentDidUpdate', function(a, parv) {
|
||||||
|
self.eventCallback('componentDidUpdate', {
|
||||||
|
prevProps: a[0],
|
||||||
|
prevState: a[1],
|
||||||
|
props: this.props,
|
||||||
|
state: this.state,
|
||||||
|
element: Helpers.ReactDOM.findDOMNode(this),
|
||||||
|
retVal: parv.retVal
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Patcher.slavepatch(this.component.prototype, 'render', function (a, parv) {
|
||||||
|
self.eventCallback('render', {
|
||||||
|
component: this,
|
||||||
|
retVal: parv.retVal,
|
||||||
|
p: parv
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
eventCallback(event, eventData) {
|
||||||
|
for (const listener of this.events.find(e => e.id === event).listeners) {
|
||||||
|
listener(eventData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get events() {
|
||||||
|
return this._events || (this._events = [
|
||||||
|
{ id: 'componentDidMount', listeners: [] },
|
||||||
|
{ id: 'componentDidUpdate', listeners: [] },
|
||||||
|
{ id: 'render', listeners: [] }
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
on(event, callback) {
|
||||||
|
const have = this.events.find(e => e.id === event);
|
||||||
|
if (!have) return;
|
||||||
|
have.listeners.push(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
get id() {
|
||||||
|
return this._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
get component() {
|
||||||
|
return this._component;
|
||||||
|
}
|
||||||
|
|
||||||
|
get retVal() {
|
||||||
|
return this._retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
unpatchRender() {
|
||||||
|
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
patchRender(actions, updateOthers) {
|
||||||
|
const self = this;
|
||||||
|
if (!(actions instanceof Array)) actions = [actions];
|
||||||
|
Patcher.slavepatch(this.component.prototype, 'render', function (args, obj) {
|
||||||
|
console.log('obj', obj);
|
||||||
|
for (const action of actions) {
|
||||||
|
let { selector, method, fn } = action;
|
||||||
|
if ('string' === typeof selector) selector = Helpers.parseSelector(selector);
|
||||||
|
const { item, parent, key } = Helpers.getFirstChild(obj, 'retVal', selector);
|
||||||
|
console.log('item2', item);
|
||||||
|
if (!item) continue;
|
||||||
|
const content = fn.apply(this, [item]);
|
||||||
|
switch (method) {
|
||||||
|
case 'replace':
|
||||||
|
parent[key] = content;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (updateOthers) self.forceUpdateOthers();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
forceUpdateOthers() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ReactAutoPatcher {
|
||||||
|
static async autoPatch() {
|
||||||
|
await this.ensureReact();
|
||||||
|
Patcher.superpatch('React', 'createElement', (component, retVal) => ReactComponents.push(component, retVal));
|
||||||
|
this.patchem();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
static async ensureReact() {
|
||||||
|
while (!window.webpackJsonp || !WebpackModules.getModuleByName('React')) await new Promise(resolve => setTimeout(resolve, 10));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
static patchem() {
|
||||||
|
this.patchMessage();
|
||||||
|
this.patchMessageGroup();
|
||||||
|
this.patchChannelMember();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async patchMessage() {
|
||||||
|
this.Message.component = await ReactComponents.getComponent('Message', true, { selector: '.message' });
|
||||||
|
this.Message.component.on('render', ({ component, retVal, p }) => {
|
||||||
|
const { message } = component.props;
|
||||||
|
const { id, colorString, bot, author, attachments, embeds } = message;
|
||||||
|
retVal.props['data-message-id'] = id;
|
||||||
|
retVal.props['data-colourstring'] = colorString;
|
||||||
|
if (author && author.id) retVal.props['data-user-id'] = author.id;
|
||||||
|
if (bot || (author && author.bot)) retVal.props.className += ' bd-isBot';
|
||||||
|
if (attachments && attachments.length) retVal.props.className += ' bd-hasAttachments';
|
||||||
|
if (embeds && embeds.length) retVal.props.className += ' bd-hasEmbeds';
|
||||||
|
if (author && author.id === DiscordApi.currentUser.id) retVal.props.className += ' bd-isCurrentUser';
|
||||||
|
try {
|
||||||
|
const markup = Helpers.findByProp(retVal, 'className', 'markup').children; // First child has all the actual text content, second is the edited timestamp
|
||||||
|
markup[0] = EmoteModule.processMarkup(markup[0]);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('MARKUP PARSER ERROR', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async patchMessageGroup() {
|
||||||
|
ReactComponents.setName('MessageGroup', this.MessageGroup.filter);
|
||||||
|
this.MessageGroup.component = await ReactComponents.getComponent('MessageGroup', true, { selector: '.message-group' });
|
||||||
|
this.MessageGroup.component.on('render', ({ component, retVal, p }) => {
|
||||||
|
const authorid = component.props.messages[0].author.id;
|
||||||
|
retVal.props['data-author-id'] = authorid;
|
||||||
|
if (authorid === DiscordApi.currentUser.id) retVal.props.className += ' bd-isCurrentUser';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async patchChannelMember() {
|
||||||
|
this.ChannelMember.component = await ReactComponents.getComponent('ChannelMember');
|
||||||
|
this.ChannelMember.component.on('render', ({ component, retVal, p }) => {
|
||||||
|
const { user, isOwner } = component.props;
|
||||||
|
retVal.props.children.props['data-member-id'] = user.id;
|
||||||
|
if (user.id === DiscordApi.currentUser.id) retVal.props.children.props.className += ' bd-isCurrentUser';
|
||||||
|
if (isOwner) retVal.props.children.props.className += ' bd-isOwner';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static get MessageGroup() {
|
||||||
|
return this._messageGroup || (
|
||||||
|
this._messageGroup = {
|
||||||
|
filter: Filters.byCode(/"message-group"[\s\S]*"has-divider"[\s\S]*"hide-overflow"[\s\S]*"is-local-bot-message"/, c => c.prototype && c.prototype.render)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static get Message() {
|
||||||
|
return this._message || (this._message = {});
|
||||||
|
}
|
||||||
|
|
||||||
|
static get ChannelMember() {
|
||||||
|
return this._channelMember || (
|
||||||
|
this._channelMember = {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ReactComponents {
|
||||||
|
static get components() { return this._components || (this._components = []) }
|
||||||
|
static get unknownComponents() { return this._unknownComponents || (this._unknownComponents = [])}
|
||||||
|
static get listeners() { return this._listeners || (this._listeners = []) }
|
||||||
|
static get nameSetters() { return this._nameSetters || (this._nameSetters =[])}
|
||||||
|
|
||||||
|
static push(component, retVal) {
|
||||||
|
if (!(component instanceof Function)) return null;
|
||||||
|
const { displayName } = component;
|
||||||
|
if (!displayName) {
|
||||||
|
return this.processUnknown(component, retVal);
|
||||||
|
}
|
||||||
|
const have = this.components.find(comp => comp.id === displayName);
|
||||||
|
if (have) return component;
|
||||||
|
const c = new ReactComponent(displayName, component, retVal);
|
||||||
|
this.components.push(c);
|
||||||
|
const listener = this.listeners.find(listener => listener.id === displayName);
|
||||||
|
if (!listener) return c;
|
||||||
|
for (const l of listener.listeners) {
|
||||||
|
l(c);
|
||||||
|
}
|
||||||
|
this.listeners.splice(this.listeners.findIndex(listener => listener.id === displayName), 1);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getComponent(name, important, importantArgs) {
|
||||||
|
const have = this.components.find(c => c.id === name);
|
||||||
|
if (have) return have;
|
||||||
|
if (important) {
|
||||||
|
const importantInterval = setInterval(() => {
|
||||||
|
if (this.components.find(c => c.id === name)) {
|
||||||
|
console.info(`Important component ${name} already found`);
|
||||||
|
clearInterval(importantInterval);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const select = document.querySelector(importantArgs.selector);
|
||||||
|
if (!select) return;
|
||||||
|
const reflect = Reflection(select);
|
||||||
|
if (!reflect.component) {
|
||||||
|
clearInterval(important);
|
||||||
|
console.error(`FAILED TO GET IMPORTANT COMPONENT ${name} WITH REFLECTION FROM`, select);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!reflect.component.displayName) reflect.component.displayName = name;
|
||||||
|
console.info(`Found important component ${name} with reflection.`);
|
||||||
|
this.push(reflect.component);
|
||||||
|
clearInterval(importantInterval);
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
const listener = this.listeners.find(l => l.id === name);
|
||||||
|
if (!listener) this.listeners.push({
|
||||||
|
id: name,
|
||||||
|
listeners: []
|
||||||
|
});
|
||||||
|
return new Promise(resolve => {
|
||||||
|
this.listeners.find(l => l.id === name).listeners.push(c => resolve(c));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static setName(name, filter, callback) {
|
||||||
|
const have = this.components.find(c => c.id === name);
|
||||||
|
if (have) return have;
|
||||||
|
|
||||||
|
for (const [rci, rc] of this.unknownComponents.entries()) {
|
||||||
|
if (filter(rc.component)) {
|
||||||
|
rc.component.displayName = name;
|
||||||
|
this.unknownComponents.splice(rci, 1);
|
||||||
|
return this.push(rc.component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.nameSetters.push({ name, filter });
|
||||||
|
}
|
||||||
|
|
||||||
|
static processUnknown(component, retVal) {
|
||||||
|
const have = this.unknownComponents.find(c => c.component === component);
|
||||||
|
for (const [fi, filter] of this.nameSetters.entries()) {
|
||||||
|
if (filter.filter(component)) {
|
||||||
|
component.displayName = filter.name;
|
||||||
|
this.nameSetters.splice(fi, 1);
|
||||||
|
return this.push(component, retVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (have) return have;
|
||||||
|
this.unknownComponents.push(component);
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
.bd-emote-outer.bd-is-emote {
|
||||||
|
font-size: 0;
|
||||||
|
}
|
||||||
|
.bd-emotewrapper {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-height: 32px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,3 +12,4 @@
|
||||||
@import './discordoverrides.scss';
|
@import './discordoverrides.scss';
|
||||||
@import './helpers.scss';
|
@import './helpers.scss';
|
||||||
@import './misc.scss';
|
@import './misc.scss';
|
||||||
|
@import './emote.scss';
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Events, WebpackModules, EventListener } from 'modules';
|
import { Events, WebpackModules, EventListener, ReactComponents, Renderer } from 'modules';
|
||||||
import Reflection from './reflection';
|
import Reflection from './reflection';
|
||||||
import DOM from './dom';
|
import DOM from './dom';
|
||||||
import VueInjector from './vueinjector';
|
import VueInjector from './vueinjector';
|
||||||
|
@ -41,72 +41,8 @@ class TempApi {
|
||||||
|
|
||||||
export default class extends EventListener {
|
export default class extends EventListener {
|
||||||
|
|
||||||
constructor() {
|
constructor(args) {
|
||||||
super();
|
super(args);
|
||||||
const messageFilter = function (m) {
|
|
||||||
return m.addedNodes && m.addedNodes.length && m.addedNodes[0].classList && m.addedNodes[0].classList.contains('message-group');
|
|
||||||
}
|
|
||||||
|
|
||||||
DOM.observer.subscribe('loading-more-manip', messageFilter, mutations => {
|
|
||||||
this.setIds();
|
|
||||||
this.makeMutable();
|
|
||||||
Events.emit('ui:laodedmore', mutations.map(m => m.addedNodes[0]));
|
|
||||||
}, 'filter');
|
|
||||||
|
|
||||||
const userFilter = function (m) {
|
|
||||||
return m.addedNodes && m.addedNodes.length && m.addedNodes[0].classList && m.addedNodes[0].classList.contains('member');
|
|
||||||
}
|
|
||||||
|
|
||||||
DOM.observer.subscribe('loading-more-users-manip', userFilter, mutations => {
|
|
||||||
this.setUserIds();
|
|
||||||
Events.emit('ui:loadedmoreusers', mutations.map(m => m.addedNodes[0]));
|
|
||||||
}, 'filter');
|
|
||||||
|
|
||||||
const channelFilter = function(m) {
|
|
||||||
return m.addedNodes &&
|
|
||||||
m.addedNodes.length &&
|
|
||||||
m.addedNodes[0].className &&
|
|
||||||
m.addedNodes[0].className.includes('container');
|
|
||||||
}
|
|
||||||
|
|
||||||
DOM.observer.subscribe('loading-more-channels-manip', channelFilter, mutations => {
|
|
||||||
this.setChannelIds();
|
|
||||||
Events.emit('ui:loadedmorechannels', mutations.map(m => m.addedNodes[0]));
|
|
||||||
}, 'filter');
|
|
||||||
|
|
||||||
const popoutFilter = function(m) {
|
|
||||||
return m.addedNodes &&
|
|
||||||
m.addedNodes.length &&
|
|
||||||
m.addedNodes[0].className &&
|
|
||||||
m.addedNodes[0].className.includes('popout');
|
|
||||||
}
|
|
||||||
|
|
||||||
DOM.observer.subscribe('userpopout-manip', popoutFilter, mutations => {
|
|
||||||
const userPopout = document.querySelector('[class*=userPopout]');
|
|
||||||
if (!userPopout) return;
|
|
||||||
const user = Reflection(userPopout).prop('user');
|
|
||||||
if (!user) return;
|
|
||||||
userPopout.setAttribute('data-user-id', user.id);
|
|
||||||
if (user.id === TempApi.currentUserId) userPopout.setAttribute('data-currentuser', true);
|
|
||||||
}, 'filter');
|
|
||||||
|
|
||||||
const modalFilter = function(m) {
|
|
||||||
return m.addedNodes &&
|
|
||||||
m.addedNodes.length &&
|
|
||||||
m.addedNodes[0].className &&
|
|
||||||
m.addedNodes[0].className.includes('modal');
|
|
||||||
}
|
|
||||||
|
|
||||||
DOM.observer.subscribe('modal-manip', modalFilter, mutations => {
|
|
||||||
const userModal = document.querySelector('[class*=modal] > [class*=inner]');
|
|
||||||
if (!userModal) return;
|
|
||||||
const user = Reflection(userModal).prop('user');
|
|
||||||
if (!user) return;
|
|
||||||
const modal = userModal.closest('[class*=modal]');
|
|
||||||
if (!modal) return;
|
|
||||||
modal.setAttribute('data-user-id', user.id);
|
|
||||||
if (user.id === TempApi.currentUserId) modal.setAttribute('data-currentuser', true);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bindings() {
|
bindings() {
|
||||||
|
@ -118,6 +54,8 @@ export default class extends EventListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
get eventBindings() {
|
get eventBindings() {
|
||||||
|
return [{ id: 'gkh:keyup', callback: this.injectAutocomplete }];
|
||||||
|
/*
|
||||||
return [
|
return [
|
||||||
{ id: 'server-switch', callback: this.manipAll },
|
{ id: 'server-switch', callback: this.manipAll },
|
||||||
{ id: 'channel-switch', callback: this.manipAll },
|
{ id: 'channel-switch', callback: this.manipAll },
|
||||||
|
@ -125,6 +63,7 @@ export default class extends EventListener {
|
||||||
{ id: 'discord:MESSAGE_UPDATE', callback: this.markupInjector },
|
{ id: 'discord:MESSAGE_UPDATE', callback: this.markupInjector },
|
||||||
{ id: 'gkh:keyup', callback: this.injectAutocomplete }
|
{ id: 'gkh:keyup', callback: this.injectAutocomplete }
|
||||||
];
|
];
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
manipAll() {
|
manipAll() {
|
||||||
|
|
|
@ -57,10 +57,10 @@ export default class {
|
||||||
if (!this.profilePopupModule) return;
|
if (!this.profilePopupModule) return;
|
||||||
clearInterval(defer);
|
clearInterval(defer);
|
||||||
|
|
||||||
Utils.monkeyPatch(this.profilePopupModule, 'open', 'after', (data, userid) => Events.emit('ui-event', {
|
/*Utils.monkeyPatch(this.profilePopupModule, 'open', 'after', (data, userid) => Events.emit('ui-event', {
|
||||||
event: 'profile-popup-open',
|
event: 'profile-popup-open',
|
||||||
data: { userid }
|
data: { userid }
|
||||||
}));
|
}));*/
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
const ehookInterval = setInterval(() => {
|
const ehookInterval = setInterval(() => {
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-for="(emote, index) in emotes" class="bd-autocompleteRow" :key="emote.id">
|
<div v-for="(emote, index) in emotes" class="bd-autocompleteRow" :key="index">
|
||||||
<div class="bd-autocompleteSelector bd-selectable" :class="{'bd-selected': index === selectedIndex}" @mouseover="() => { selected = emote.id }" @click="() => inject(emote)">
|
<div class="bd-autocompleteSelector bd-selectable" :class="{'bd-selected': index === selectedIndex}" @mouseover="() => { selected = emote.id }" @click="() => inject(emote)">
|
||||||
<div class="bd-autocompleteField">
|
<div class="bd-autocompleteField">
|
||||||
<img :src="getEmoteSrc(emote)"/>
|
<img :src="getEmoteSrc(emote)"/>
|
||||||
|
@ -54,12 +54,14 @@
|
||||||
created() {
|
created() {
|
||||||
window.addEventListener('keydown', this.prevents);
|
window.addEventListener('keydown', this.prevents);
|
||||||
const ta = document.querySelector('.chat textarea');
|
const ta = document.querySelector('.chat textarea');
|
||||||
|
if(!ta) return;
|
||||||
ta.addEventListener('keydown', this.setCaret);
|
ta.addEventListener('keydown', this.setCaret);
|
||||||
ta.addEventListener('keyup', this.searchEmotes);
|
ta.addEventListener('keyup', this.searchEmotes);
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
window.removeEventListener('keydown', this.prevents);
|
window.removeEventListener('keydown', this.prevents);
|
||||||
const ta = document.querySelector('.chat textarea');
|
const ta = document.querySelector('.chat textarea');
|
||||||
|
if (!ta) return;
|
||||||
ta.removeEventListener('keydown', this.setCaret);
|
ta.removeEventListener('keydown', this.setCaret);
|
||||||
ta.removeEventListener('keyup', this.searchEmotes);
|
ta.removeEventListener('keyup', this.searchEmotes);
|
||||||
},
|
},
|
||||||
|
@ -80,48 +82,59 @@
|
||||||
return uri.replace(':id', value);
|
return uri.replace(':id', value);
|
||||||
},
|
},
|
||||||
searchEmotes(e) {
|
searchEmotes(e) {
|
||||||
if (e.key === 'ArrowDown' && this.open && this.caret) {
|
if (this.traverse(e)) return;
|
||||||
this.selectedIndex = (this.selectedIndex + 1) >= 10 ? 0 : this.selectedIndex + 1;
|
if (e.key === 'Tab' && this.open) {
|
||||||
return;
|
|
||||||
} else if (e.key === 'ArrowUp' && this.open && this.caret) {
|
|
||||||
this.selectedIndex = (this.selectedIndex - 1) < 0 ? 9 : this.selectedIndex - 1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (e.key === 'Tab' && this.open && this.caret) {
|
|
||||||
const selected = this.emotes[this.selectedIndex];
|
const selected = this.emotes[this.selectedIndex];
|
||||||
if (!selected) return;
|
if (!selected) return;
|
||||||
this.inject(selected);
|
this.inject(selected);
|
||||||
|
this.reset();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const se = e.target.selectionEnd;
|
if (e.key === 'Tab' && !this.open) this.open = true;
|
||||||
this.sterm = e.target.value.substr(0, se).split(' ').slice(-1).pop();
|
if (!this.open) return;
|
||||||
|
const { selectionEnd, value } = e.target;
|
||||||
|
this.sterm = value.substr(0, selectionEnd).split(/\s+/g).pop();
|
||||||
|
|
||||||
if (this.sterm.length < 3) {
|
if (this.sterm.length < 3) {
|
||||||
this.emotes = [];
|
this.reset();
|
||||||
this.selected = '';
|
|
||||||
this.selectedIndex = 0;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.title = this.sterm;
|
this.title = this.sterm;
|
||||||
this.emotes = EmoteModule.filter(new RegExp(this.sterm, ''), 10);
|
this.emotes = EmoteModule.filter(new RegExp(this.sterm, ''), 10);
|
||||||
this.open = this.emotes.length;
|
this.open = this.emotes.length;
|
||||||
},
|
},
|
||||||
|
traverse(e) {
|
||||||
|
if (!this.open) return false;
|
||||||
|
if (e.key === 'ArrowUp') {
|
||||||
|
this.selectedIndex = (this.selectedIndex - 1) < 0 ? 9 : this.selectedIndex - 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (e.key === 'ArrowDown') {
|
||||||
|
this.selectedIndex = (this.selectedIndex + 1) >= 10 ? 0 : this.selectedIndex + 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
this.emotes = [];
|
||||||
|
this.title = '';
|
||||||
|
this.selIndex = 0;
|
||||||
|
this.selected = '';
|
||||||
|
this.open = false;
|
||||||
|
this.selectedIndex = 0;
|
||||||
|
this.sterm = '';
|
||||||
|
},
|
||||||
inject(emote) {
|
inject(emote) {
|
||||||
const ta = document.querySelector('.chat textarea');
|
const ta = document.querySelector('.chat textarea');
|
||||||
if (!ta) return;
|
if (!ta) return;
|
||||||
const currentText = document.querySelector('.chat textarea').value;
|
const { selectionEnd, value } = ta;
|
||||||
const se = ta.selectionEnd;
|
const en = `:${emote.id}:`;
|
||||||
const split = currentText.substr(0, se).split(' ');
|
let substr = value.substr(0, selectionEnd);
|
||||||
split.pop();
|
substr = substr.replace(new RegExp(this.sterm + '$'), en);
|
||||||
split.push(`:${emote.id}:`);
|
|
||||||
const join = split.join(' ');
|
DOM.manip.setText(substr + value.substr(selectionEnd, value.length), false);
|
||||||
const rest = currentText.substr(se, currentText.length);
|
ta.selectionEnd = ta.selectionStart = selectionEnd + en.length - this.sterm.length;
|
||||||
DOM.manip.setText(join + ' ' + rest, false);
|
this.reset();
|
||||||
this.emotes = [];
|
|
||||||
this.open = false;
|
|
||||||
this.selectedIndex = 0;
|
|
||||||
this.selected = '';
|
|
||||||
ta.selectionEnd = ta.selectionStart = se + `:${emote.id}:`.length - this.title.length;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,6 +80,23 @@ class Reflection {
|
||||||
}
|
}
|
||||||
return this.propIterator(curProp, propNames);
|
return this.propIterator(curProp, propNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getState(node) {
|
||||||
|
try {
|
||||||
|
return this.reactInternalInstance(node).return.stateNode.state;
|
||||||
|
} catch (err) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static getComponent(node) {
|
||||||
|
// IMPORTANT TODO Currently only checks the first found component. For example channel-member will not return the correct component
|
||||||
|
try {
|
||||||
|
return this.reactInternalInstance(node).return.type;
|
||||||
|
} catch (err) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function (node) {
|
export default function (node) {
|
||||||
|
@ -91,9 +108,15 @@ export default function (node) {
|
||||||
get props() {
|
get props() {
|
||||||
return 'not yet implemented';
|
return 'not yet implemented';
|
||||||
}
|
}
|
||||||
|
get state() {
|
||||||
|
return Reflection.getState(this.node);
|
||||||
|
}
|
||||||
get reactInternalInstance() {
|
get reactInternalInstance() {
|
||||||
return Reflection.reactInternalInstance(this.node);
|
return Reflection.reactInternalInstance(this.node);
|
||||||
}
|
}
|
||||||
|
get component() {
|
||||||
|
return Reflection.getComponent(this.node);
|
||||||
|
}
|
||||||
prop(propName) {
|
prop(propName) {
|
||||||
const split = propName.split('.');
|
const split = propName.split('.');
|
||||||
const first = Reflection.findProp(this.node, split[0]);
|
const first = Reflection.findProp(this.node, split[0]);
|
||||||
|
|
Loading…
Reference in New Issue