Merge pull request #164 from JsSucks/dom-attributes
UI Events, badges, data attributes, etc
This commit is contained in:
commit
27aa21a47a
|
@ -19,89 +19,17 @@ import TwitchEmotes from '../data/twitch_emotes.json';
|
|||
export default class {
|
||||
|
||||
static observe() {
|
||||
Events.on('server-switch', this.injectAll.bind(this));
|
||||
Events.on('channel-switch', this.injectAll.bind(this));
|
||||
Events.on('discord:MESSAGE_CREATE', e => {
|
||||
// Assume that it's the last one for now since the event doesn't give the element
|
||||
const query = document.querySelectorAll('.markup:not(.mutable)');
|
||||
if (!query) return;
|
||||
this.injectMarkup(query[query.length - 1], true);
|
||||
}); // TODO
|
||||
}
|
||||
|
||||
static injectAll() {
|
||||
for (const el of document.querySelectorAll('.markup:not(.mutable)')) {
|
||||
this.injectMarkup(el, this.cloneMarkup(el), false);
|
||||
}
|
||||
}
|
||||
|
||||
static cloneMarkup(node) {
|
||||
const childNodes = [...node.childNodes];
|
||||
const clone = document.createElement('div');
|
||||
clone.className = 'markup mutable';
|
||||
const ets = this.getEts(node);
|
||||
for (const [cni, cn] of childNodes.entries()) {
|
||||
if (cn.nodeType !== Node.TEXT_NODE) {
|
||||
if (cn.className.includes('edited')) continue;
|
||||
}
|
||||
clone.appendChild(cn.cloneNode(true));
|
||||
}
|
||||
return { clone, ets }
|
||||
}
|
||||
|
||||
static getEts(node) {
|
||||
try {
|
||||
const reh = Object.keys(node).find(k => k.startsWith('__reactInternalInstance'));
|
||||
return node[reh].memoizedProps.children[node[reh].memoizedProps.children.length - 1].props.text;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static injectMarkup(sibling, markup, reinject) {
|
||||
if (sibling.className && sibling.className.includes('mutable')) return; // Ignore trying to make mutable mutable again
|
||||
let cc = null;
|
||||
for (const cn of sibling.parentElement.childNodes) {
|
||||
if (cn.className && cn.className.includes('mutable')) cc = cn;
|
||||
}
|
||||
if (cc) sibling.parentElement.removeChild(cc);
|
||||
if (markup === true) markup = this.cloneMarkup(sibling);
|
||||
markup.clone = this.injectEmotes(markup.clone);
|
||||
sibling.parentElement.insertBefore(markup.clone, sibling);
|
||||
sibling.classList.add('shadow');
|
||||
sibling.style.display = 'none';
|
||||
if (markup.ets) {
|
||||
const etsRoot = document.createElement('span');
|
||||
markup.clone.appendChild(etsRoot);
|
||||
VueInjector.inject(
|
||||
etsRoot,
|
||||
DOM.createElement('span', null, 'test'),
|
||||
{ EditedTimeStamp },
|
||||
`<EditedTimeStamp ets="${markup.ets}"/>`,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if (reinject) return;
|
||||
new MutationObserver(() => {
|
||||
this.injectMarkup(sibling, this.cloneMarkup(sibling), true);
|
||||
}).observe(sibling, { characterData: false, attributes: false, childList: true, subtree: false });
|
||||
|
||||
new MutationObserver(() => {
|
||||
this.injectMarkup(sibling, this.cloneMarkup(sibling), true);
|
||||
}).observe(sibling, { characterData: true, attributes: false, childList: false, subtree: true });
|
||||
}
|
||||
|
||||
static makeMutable(node) {
|
||||
if (node.classList && node.classList.contains('shadow')) return;
|
||||
this.injectMarkup(node, this.cloneMarkup(node));
|
||||
Events.on('ui:mutable:.markup', markup => {
|
||||
this.injectEmotes(markup);
|
||||
});
|
||||
}
|
||||
|
||||
static injectEmotes(node) {
|
||||
if (!/:[\w]+:/gmi.test(node.textContent)) return node;
|
||||
const childNodes = [...node.childNodes];
|
||||
const newNode = document.createElement('div');
|
||||
newNode.className = 'markup mutable hasEmotes';
|
||||
newNode.className = node.className;
|
||||
newNode.classList.add('hasEmotes');
|
||||
|
||||
for (const [cni, cn] of childNodes.entries()) {
|
||||
if (cn.nodeType !== Node.TEXT_NODE) {
|
||||
|
@ -133,7 +61,7 @@ export default class {
|
|||
newNode.appendChild(emoteRoot);
|
||||
VueInjector.inject(
|
||||
emoteRoot,
|
||||
DOM.createElement('span', null, 'emotetest'),
|
||||
DOM.createElement('span'),
|
||||
{ EmoteComponent },
|
||||
`<EmoteComponent src="${isEmote.src}" name="${isEmote.name}"/>`,
|
||||
true
|
||||
|
@ -152,7 +80,7 @@ export default class {
|
|||
}
|
||||
}
|
||||
}
|
||||
return newNode;
|
||||
node.replaceWith(newNode);
|
||||
}
|
||||
|
||||
static isEmote(word) {
|
||||
|
|
|
@ -13,6 +13,7 @@ import BdCss from './styles/index.scss';
|
|||
import { Events, CssEditor, Globals, ExtModuleManager, PluginManager, ThemeManager, ModuleManager, WebpackModules, Settings, Database } from 'modules';
|
||||
import { ClientLogger as Logger, ClientIPC } from 'common';
|
||||
import { EmoteModule } from 'builtin';
|
||||
const ignoreExternal = false;
|
||||
|
||||
class BetterDiscord {
|
||||
|
||||
|
@ -29,7 +30,7 @@ class BetterDiscord {
|
|||
window.bdmodals = Modals;
|
||||
window.bdlogs = Logger;
|
||||
window.emotes = EmoteModule;
|
||||
|
||||
window.dom = DOM;
|
||||
EmoteModule.observe();
|
||||
|
||||
DOM.injectStyle(BdCss, 'bdmain');
|
||||
|
@ -41,13 +42,14 @@ class BetterDiscord {
|
|||
await Database.init();
|
||||
await Settings.loadSettings();
|
||||
await ModuleManager.initModules();
|
||||
await ExtModuleManager.loadAllModules(true);
|
||||
await PluginManager.loadAllPlugins(true);
|
||||
await ThemeManager.loadAllThemes(true);
|
||||
|
||||
Modals.showContentManagerErrors();
|
||||
if (!ignoreExternal) {
|
||||
await ExtModuleManager.loadAllModules(true);
|
||||
await PluginManager.loadAllPlugins(true);
|
||||
await ThemeManager.loadAllThemes(true);
|
||||
}
|
||||
if (!Settings.get('core', 'advanced', 'ignore-content-manager-errors'))
|
||||
Modals.showContentManagerErrors();
|
||||
|
||||
Events.emit('ready');
|
||||
Events.emit('discord-ready');
|
||||
} catch (err) {
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
*/
|
||||
|
||||
import DiscordEvent from './discordevent';
|
||||
import { Reflection } from 'ui';
|
||||
|
||||
export class MESSAGE_CREATE extends DiscordEvent {
|
||||
get author() { return this.args.author }
|
||||
|
@ -26,6 +27,14 @@ export class MESSAGE_CREATE extends DiscordEvent {
|
|||
get timestamp() { return this.args.timestamp }
|
||||
get tts() { return this.args.tts }
|
||||
get type() { return this.args.type }
|
||||
get element() {
|
||||
const find = document.querySelector(`[message-id="${this.id}"]`);
|
||||
if (find) return find;
|
||||
const messages = document.querySelectorAll('.message');
|
||||
const lastMessage = messages[messages.length - 1];
|
||||
if (Reflection(lastMessage).prop('message.id') === this.id) return lastMessage;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
export class MESSAGE_UPDATE extends MESSAGE_CREATE { }
|
||||
|
||||
|
|
|
@ -18,13 +18,41 @@
|
|||
}
|
||||
|
||||
.bd-profile-badge-developer,
|
||||
.bd-profile-badge-contributor {
|
||||
.bd-profile-badge-contributor,
|
||||
.bd-message-badge-developer,
|
||||
.bd-message-badge-contributor {
|
||||
background-image: $logoSmallBw;
|
||||
width: 16px;
|
||||
filter: brightness(10);
|
||||
|
||||
cursor: pointer;
|
||||
.theme-light [class*="topSectionNormal-"] & {
|
||||
background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iQ2FscXVlXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjAwMCAyMDAwIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAyMDAwIDIwMDAiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxnPjxwYXRoIGZpbGw9IiMzRTgyRTUiIGQ9Ik0xNDAyLjIsNjMxLjdjLTkuNy0zNTMuNC0yODYuMi00OTYtNjQyLjYtNDk2SDY4LjR2NzE0LjFsNDQyLDM5OFY0OTAuN2gyNTdjMjc0LjUsMCwyNzQuNSwzNDQuOSwwLDM0NC45SDU5Ny42djMyOS41aDE2OS44YzI3NC41LDAsMjc0LjUsMzQ0LjgsMCwzNDQuOGgtNjk5djM1NC45aDY5MS4yYzM1Ni4zLDAsNjMyLjgtMTQyLjYsNjQyLjYtNDk2YzAtMTYyLjYtNDQuNS0yODQuMS0xMjIuOS0zNjguNkMxMzU3LjcsOTE1LjgsMTQwMi4yLDc5NC4zLDE0MDIuMiw2MzEuN3oiLz48cGF0aCBmaWxsPSIjYmJiYmJiIiBkPSJNMTI2Mi41LDEzNS4yTDEyNjIuNSwxMzUuMmwtNzYuOCwwYzI2LjYsMTMuMyw1MS43LDI4LjEsNzUsNDQuM2M3MC43LDQ5LjEsMTI2LjEsMTExLjUsMTY0LjYsMTg1LjNjMzkuOSw3Ni42LDYxLjUsMTY1LjYsNjQuMywyNjQuNmwwLDEuMnYxLjJjMCwxNDEuMSwwLDU5Ni4xLDAsNzM3LjF2MS4ybDAsMS4yYy0yLjcsOTktMjQuMywxODgtNjQuMywyNjQuNmMtMzguNSw3My44LTkzLjgsMTM2LjItMTY0LjYsMTg1LjNjLTIyLjYsMTUuNy00Ni45LDMwLjEtNzIuNiw0My4xaDcyLjVjMzQ2LjIsMS45LDY3MS0xNzEuMiw2NzEtNTY3LjlWNzE2LjdDMTkzMy41LDMxMi4yLDE2MDguNywxMzUuMiwxMjYyLjUsMTM1LjJ6Ii8+PC9nPjwvc3ZnPg==');
|
||||
filter: none;
|
||||
}
|
||||
}
|
||||
|
||||
.bd-message-badges-wrap {
|
||||
display: inline-block;
|
||||
margin-left: 6px;
|
||||
height: 11px;
|
||||
.bd-message-badge-developer,
|
||||
.bd-message-badge-contributor {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.member-username .bd-message-badges-wrap {
|
||||
display: inline-block;
|
||||
height: 17px;
|
||||
width: 14px;
|
||||
|
||||
.bd-message-badge-developer,
|
||||
.bd-message-badge-contributor {
|
||||
width: 14px;
|
||||
height: 16px;
|
||||
background-position: center;
|
||||
background-size: 12px 12px;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
}
|
|
@ -7,7 +7,8 @@
|
|||
@import './bdsettings/index.scss';
|
||||
@import './generic/index.scss';
|
||||
@import './modals/index.scss';
|
||||
@import './profilebadges.scss';
|
||||
@import './badges.scss';
|
||||
|
||||
@import './discordoverrides.scss';
|
||||
@import './helpers.scss';
|
||||
@import './misc.scss';
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
.edit-message .markup.mutable {
|
||||
display: none;
|
||||
}
|
|
@ -0,0 +1,198 @@
|
|||
/**
|
||||
* BetterDiscord Automated DOM Manipulations
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { Events, WebpackModules, EventListener } from 'modules';
|
||||
import Reflection from './reflection';
|
||||
import DOM from './dom';
|
||||
import VueInjector from './vueinjector';
|
||||
import EditedTimeStamp from './components/common/EditedTimestamp.vue';
|
||||
|
||||
class TempApi {
|
||||
static get currentGuildId() {
|
||||
try {
|
||||
return WebpackModules.getModuleByName('SelectedGuildStore').getGuildId();
|
||||
} catch (err) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
static get currentChannelId() {
|
||||
try {
|
||||
return WebpackModules.getModuleByName('SelectedChannelStore').getChannelId();
|
||||
} catch (err) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
static get currentUserId() {
|
||||
try {
|
||||
return WebpackModules.getModuleByName('UserStore').getCurrentUser().id;
|
||||
} catch (err) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default class extends EventListener {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
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');
|
||||
}
|
||||
|
||||
bindings() {
|
||||
this.manipAll = this.manipAll.bind(this);
|
||||
this.markupInjector = this.markupInjector.bind(this);
|
||||
this.setIds = this.setIds.bind(this);
|
||||
this.setMessageIds = this.setMessageIds.bind(this);
|
||||
this.setUserIds = this.setUserIds.bind(this);
|
||||
}
|
||||
|
||||
get eventBindings() {
|
||||
return [
|
||||
{ id: 'server-switch', callback: this.manipAll },
|
||||
{ id: 'channel-switch', callback: this.manipAll },
|
||||
{ id: 'discord:MESSAGE_CREATE', callback: this.markupInjector },
|
||||
{ id: 'discord:MESSAGE_UPDATE', callback: this.markupInjector }
|
||||
];
|
||||
}
|
||||
|
||||
manipAll() {
|
||||
try {
|
||||
this.appMount.setAttribute('guild-id', TempApi.currentGuildId);
|
||||
this.appMount.setAttribute('channel-id', TempApi.currentChannelId);
|
||||
this.setIds();
|
||||
this.makeMutable();
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
markupInjector(e) {
|
||||
if (!e.element) return;
|
||||
this.setId(e.element);
|
||||
const markup = e.element.querySelector('.markup:not(.mutable)');
|
||||
if (markup) this.injectMarkup(markup, this.cloneMarkup(markup), false);
|
||||
}
|
||||
|
||||
getEts(node) {
|
||||
try {
|
||||
const reh = Object.keys(node).find(k => k.startsWith('__reactInternalInstance'));
|
||||
return node[reh].memoizedProps.children[node[reh].memoizedProps.children.length - 1].props.text;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
makeMutable() {
|
||||
for (const el of document.querySelectorAll('.markup:not(.mutable)')) {
|
||||
this.injectMarkup(el, this.cloneMarkup(el), false);
|
||||
}
|
||||
}
|
||||
|
||||
cloneMarkup(node) {
|
||||
const childNodes = [...node.childNodes];
|
||||
const clone = document.createElement('div');
|
||||
clone.className = 'markup mutable';
|
||||
const ets = this.getEts(node);
|
||||
for (const [cni, cn] of childNodes.entries()) {
|
||||
if (cn.nodeType !== Node.TEXT_NODE) {
|
||||
if (cn.className.includes('edited')) continue;
|
||||
}
|
||||
clone.appendChild(cn.cloneNode(true));
|
||||
}
|
||||
return { clone, ets }
|
||||
}
|
||||
|
||||
injectMarkup(sibling, markup, reinject) {
|
||||
if (sibling.className && sibling.className.includes('mutable')) return; // Ignore trying to make mutable again
|
||||
let cc = null;
|
||||
for (const cn of sibling.parentElement.childNodes) {
|
||||
if (cn.className && cn.className.includes('mutable')) cc = cn;
|
||||
}
|
||||
if (cc) sibling.parentElement.removeChild(cc);
|
||||
if (markup === true) markup = this.cloneMarkup(sibling);
|
||||
|
||||
sibling.parentElement.insertBefore(markup.clone, sibling);
|
||||
sibling.classList.add('shadow');
|
||||
sibling.style.display = 'none';
|
||||
if (markup.ets) {
|
||||
const etsRoot = document.createElement('span');
|
||||
markup.clone.appendChild(etsRoot);
|
||||
VueInjector.inject(
|
||||
etsRoot,
|
||||
DOM.createElement('span', null, 'test'),
|
||||
{ EditedTimeStamp },
|
||||
`<EditedTimeStamp ets="${markup.ets}"/>`,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
Events.emit('ui:mutable:.markup', markup.clone);
|
||||
}
|
||||
|
||||
setIds() {
|
||||
this.setMessageIds();
|
||||
this.setUserIds();
|
||||
}
|
||||
|
||||
setMessageIds() {
|
||||
for (let msg of document.querySelectorAll('.message')) {
|
||||
this.setId(msg);
|
||||
}
|
||||
}
|
||||
|
||||
setUserIds() {
|
||||
for (let user of document.querySelectorAll('.channel-members-wrap .member')) {
|
||||
this.setUserId(user);
|
||||
}
|
||||
}
|
||||
|
||||
setId(msg) {
|
||||
if (msg.hasAttribute('message-id')) return;
|
||||
const messageid = Reflection(msg).prop('message.id');
|
||||
const authorid = Reflection(msg).prop('message.author.id');
|
||||
if (!messageid || !authorid) return;
|
||||
msg.setAttribute('data-message-id', messageid);
|
||||
const msgGroup = msg.closest('.message-group');
|
||||
if (!msgGroup) return;
|
||||
msgGroup.setAttribute('data-author-id', authorid);
|
||||
if (authorid === TempApi.currentUserId) msgGroup.setAttribute('data-currentuser', true);
|
||||
}
|
||||
|
||||
setUserId(user) {
|
||||
if (user.hasAttribute('data-user-id')) return;
|
||||
const userid = Reflection(user).prop('user.id');
|
||||
if (!userid) return;
|
||||
user.setAttribute('data-user-id', userid);
|
||||
const currentUser = userid === TempApi.currentUserId;
|
||||
if (currentUser) user.setAttribute('data-currentuser', true);
|
||||
Events.emit('ui:useridset', user);
|
||||
}
|
||||
|
||||
get appMount() {
|
||||
return document.getElementById('app-mount');
|
||||
}
|
||||
}
|
|
@ -14,10 +14,44 @@ import { BdSettingsWrapper } from './components';
|
|||
import BdModals from './components/bd/BdModals.vue';
|
||||
import { Events, WebpackModules } from 'modules';
|
||||
import { Utils } from 'common';
|
||||
import AutoManip from './automanip';
|
||||
import { remote } from 'electron';
|
||||
|
||||
class TempApi {
|
||||
static get currentGuild() {
|
||||
try {
|
||||
const currentGuildId = WebpackModules.getModuleByName('SelectedGuildStore').getGuildId();
|
||||
return WebpackModules.getModuleByName('GuildStore').getGuild(currentGuildId);
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
static get currentChannel() {
|
||||
try {
|
||||
const currentChannelId = WebpackModules.getModuleByName('SelectedChannelStore').getChannelId();
|
||||
return WebpackModules.getModuleByName('ChannelStore').getChannel(currentChannelId);
|
||||
} catch (err) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
static get currentUserId() {
|
||||
try {
|
||||
return WebpackModules.getModuleByName('UserStore').getCurrentUser().id;
|
||||
} catch (err) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default class {
|
||||
|
||||
static initUiEvents() {
|
||||
this.pathCache = {
|
||||
isDm: null,
|
||||
server: TempApi.currentGuild,
|
||||
channel: TempApi.currentChannel
|
||||
};
|
||||
this.autoManip = new AutoManip();
|
||||
const defer = setInterval(() => {
|
||||
if (!this.profilePopupModule) return;
|
||||
clearInterval(defer);
|
||||
|
@ -27,39 +61,42 @@ export default class {
|
|||
data: { userid }
|
||||
}));
|
||||
}, 100);
|
||||
// This is temporary
|
||||
const locationInterval = setInterval(() => {
|
||||
try {
|
||||
const path = window.location.pathname.match(/\d+/g);
|
||||
if (!path) {
|
||||
this.prevLocation = null;
|
||||
Events.emit('server-switch');
|
||||
|
||||
const ehookInterval = setInterval(() => {
|
||||
if (!remote.BrowserWindow.getFocusedWindow()) return;
|
||||
clearInterval(ehookInterval);
|
||||
remote.BrowserWindow.getFocusedWindow().webContents.on('did-navigate-in-page', (e, url, isMainFrame) => {
|
||||
const { currentGuild, currentChannel } = TempApi;
|
||||
if (!this.pathCache.server) {
|
||||
Events.emit('server-switch', { 'server': currentGuild, 'channel': currentChannel });
|
||||
this.pathCache.server = currentGuild;
|
||||
this.pathCache.channel = currentChannel;
|
||||
return;
|
||||
}
|
||||
const ids = path.reduce((obj, el, index) => {
|
||||
obj[index === 0 ? 'guildId' : 'channelId'] = el;
|
||||
return obj;
|
||||
},
|
||||
{});
|
||||
|
||||
if (Object.keys(ids).length === 1) {
|
||||
ids.isDm = true;
|
||||
ids.dmId = ids.guildId
|
||||
if (!this.pathCache.channel) {
|
||||
Events.emit('channel-switch', currentChannel);
|
||||
this.pathCache.server = currentGuild;
|
||||
this.pathCache.channel = currentChannel;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.prevLocation) {
|
||||
Events.emit('server-switch');
|
||||
} else {
|
||||
if (this.prevLocation.channelId !== ids.channelId) {
|
||||
Events.emit('channel-switch');
|
||||
} else if (this.prevLocation.guildId !== ids.guildId) {
|
||||
Events.emit('server-switch');
|
||||
}
|
||||
if (currentGuild &&
|
||||
currentGuild.id &&
|
||||
this.pathCache.server &&
|
||||
this.pathCache.server.id !== currentGuild.id) {
|
||||
Events.emit('server-switch', { 'server': currentGuild, 'channel': currentChannel });
|
||||
this.pathCache.server = currentGuild;
|
||||
this.pathCache.channel = currentChannel;
|
||||
return;
|
||||
}
|
||||
this.prevLocation = ids;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
if (currentChannel &&
|
||||
currentChannel.id &&
|
||||
this.pathCache.channel &&
|
||||
this.pathCache.channel.id !== currentChannel.id) Events.emit('channel-switch', currentChannel);
|
||||
|
||||
|
||||
this.pathCache.server = currentGuild;
|
||||
this.pathCache.channel = currentChannel;
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* BetterDiscord BD Message Badge 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-message-badges-wrap">
|
||||
<div v-if="developer == 'true'" v-tooltip="'BetterDiscord Developer'" class="bd-message-badge bd-message-badge-developer" @click="onClick"></div>
|
||||
<div v-else-if="webdev == 'true'" v-tooltip="'BetterDiscord Web Developer'" class="bd-message-badge bd-message-badge-developer" @click="onClick"></div>
|
||||
<div v-else-if="contributor == 'true'" v-tooltip="'BetterDiscord Contributor'" class="bd-message-badge bd-message-badge-contributor" @click="onClick"></div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
// Imports
|
||||
import { shell } from 'electron';
|
||||
|
||||
export default {
|
||||
props: ['webdev', 'developer', 'contributor', 'hasBadges'],
|
||||
methods: {
|
||||
onClick() {
|
||||
if (this.developer) return shell.openExternal('https://github.com/JsSucks/BetterDiscordApp');
|
||||
if (this.webdev) return shell.openExternal('https://betterdiscord.net');
|
||||
if (this.contributor) return shell.openExternal('https://github.com/JsSucks/BetterDiscordApp/graphs/contributors');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -4,3 +4,4 @@ export { default as CssEditorView } from './CssEditor.vue';
|
|||
export { default as PluginsView } from './PluginsView.vue';
|
||||
export { default as ThemesView } from './ThemesView.vue';
|
||||
export { default as BdBadge } from './BdBadge.vue';
|
||||
export { default as BdMessageBadge } from './BdMessageBadge.vue';
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<template>
|
||||
<span class="edited" v-tooltip="ets">(edited)</span>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['ets']
|
||||
}
|
||||
</script>
|
|
@ -37,16 +37,16 @@ class DOMObserver {
|
|||
this.subscribe = this.subscribe.bind(this);
|
||||
this.observerCallback = this.observerCallback.bind(this);
|
||||
this.observer = new MutationObserver(this.observerCallback);
|
||||
this.observe();
|
||||
}
|
||||
|
||||
observerCallback(mutations) {
|
||||
for (let sub of this.subscriptions) {
|
||||
try {
|
||||
const f = mutations.find(sub.filter);
|
||||
if (f) {
|
||||
sub.callback(f);
|
||||
continue;
|
||||
}
|
||||
const f = sub.type && sub.type === 'filter' ? mutations.filter(sub.filter) : mutations.find(sub.filter);
|
||||
if (!f) continue;
|
||||
if (sub.type && sub.type === 'filter' && !f.length) continue;
|
||||
sub.callback(f);
|
||||
} catch(err) {}
|
||||
}
|
||||
}
|
||||
|
@ -67,12 +67,13 @@ class DOMObserver {
|
|||
return this._subscriptions || (this._subscriptions = []);
|
||||
}
|
||||
|
||||
subscribe(id, filter, callback) {
|
||||
subscribe(id, filter, callback, type) {
|
||||
if (this.subscriptions.find(sub => sub.id === id)) return;
|
||||
this.subscriptions.push({
|
||||
id,
|
||||
filter,
|
||||
callback
|
||||
callback,
|
||||
type
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -158,4 +159,10 @@ export default class DOM {
|
|||
style.appendChild(document.createTextNode(css));
|
||||
return style;
|
||||
}
|
||||
|
||||
static setAttributes(node, attributes) {
|
||||
for (let attribute of attributes) {
|
||||
node.setAttribute(attribute.name, attribute.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,18 +10,27 @@
|
|||
|
||||
import { EventListener } from 'modules';
|
||||
import DOM from './dom';
|
||||
import { BdBadge } from './components/bd';
|
||||
import { BdBadge, BdMessageBadge } from './components/bd';
|
||||
import VueInjector from './vueinjector';
|
||||
|
||||
export default class extends EventListener {
|
||||
|
||||
bindings() {
|
||||
this.uiEvent = this.uiEvent.bind(this);
|
||||
this.messageBadge = this.messageBadge.bind(this);
|
||||
this.badges = this.badges.bind(this);
|
||||
this.userlistBadge = this.userlistBadge.bind(this);
|
||||
}
|
||||
|
||||
get eventBindings() {
|
||||
return [
|
||||
{ id: 'ui-event', callback: this.uiEvent }
|
||||
{ id: 'discord:MESSAGE_CREATE', callback: this.messageBadge },
|
||||
{ id: 'discord:MESSAGE_UPDATE', callback: this.messageBadge },
|
||||
{ id: 'server-switch', callback: this.badges },
|
||||
{ id: 'channel-switch', callback: this.badges },
|
||||
{ id: 'ui:loadedmore', callback: this.badges },
|
||||
{ id: 'ui:useridset', callback: this.userlistBadge },
|
||||
{ id: 'ui-event', callback: this.uiEvent }
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -34,6 +43,51 @@ export default class extends EventListener {
|
|||
this.inject(userid);
|
||||
}
|
||||
|
||||
badges() {
|
||||
for (const messageGroup of document.querySelectorAll('.message-group')) {
|
||||
this.messageBadge({ element: messageGroup });
|
||||
}
|
||||
}
|
||||
|
||||
messageBadge(e) {
|
||||
if (!e.element) return;
|
||||
const msgGroup = e.element.closest('.message-group');
|
||||
if (msgGroup.dataset.hasBadges) return;
|
||||
msgGroup.setAttribute('data-has-badges', true);
|
||||
if (!msgGroup.dataset.authorId) return;
|
||||
const c = this.contributors.find(c => c.id === msgGroup.dataset.authorId);
|
||||
if (!c) return;
|
||||
const root = document.createElement('span');
|
||||
const wrapperParent = msgGroup.querySelector('.username-wrapper').parentElement;
|
||||
if (!wrapperParent || wrapperParent.children.length < 2) return;
|
||||
wrapperParent.insertBefore(root, wrapperParent.children[1]);
|
||||
const { developer, contributor, webdev } = c;
|
||||
VueInjector.inject(
|
||||
root,
|
||||
DOM.createElement('div', null, 'bdmessagebadges'),
|
||||
{ BdMessageBadge },
|
||||
`<BdMessageBadge developer="${developer}" webdev="${webdev}" contributor="${contributor}"/>`,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
userlistBadge(e) {
|
||||
const c = this.contributors.find(c => c.id === e.dataset.userId);
|
||||
if (!c) return;
|
||||
const memberUsername = e.querySelector('.member-username');
|
||||
if (!memberUsername) return;
|
||||
const root = document.createElement('span');
|
||||
memberUsername.append(root);
|
||||
const { developer, contributor, webdev } = c;
|
||||
VueInjector.inject(
|
||||
root,
|
||||
DOM.createElement('div', null, 'bdmessagebadges'),
|
||||
{ BdMessageBadge },
|
||||
`<BdMessageBadge developer="${developer}" webdev="${webdev}" contributor="${contributor}"/>`,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
inject(userid) {
|
||||
const c = this.contributors.find(c => c.id === userid);
|
||||
if (!c) return;
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
/**
|
||||
* BetterDiscord Reflection Module
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class Reflection {
|
||||
static reactInternalInstance(node) {
|
||||
if (!node) return null;
|
||||
if (!Object.keys(node) || !Object.keys(node).length) return null;
|
||||
const riiKey = Object.keys(node).find(k => k.startsWith('__reactInternalInstance'));
|
||||
return riiKey ? node[riiKey] : null;
|
||||
}
|
||||
|
||||
static findProp(node, prop) {
|
||||
const ii = this.reactInternalInstance(node);
|
||||
if (!ii) return null;
|
||||
const fir = this.findInReturn(ii, prop);
|
||||
if (fir) return fir;
|
||||
return null;
|
||||
}
|
||||
|
||||
static findInReturn(internalInstance, prop) {
|
||||
const r = internalInstance.return;
|
||||
if (!r) return null;
|
||||
let find = this.findMemoizedProp(r, prop);
|
||||
if (find) return find;
|
||||
find = this.findMemoizedState(r, prop);
|
||||
if (find) return find;
|
||||
return this.findInReturn(r, prop);
|
||||
}
|
||||
|
||||
static findMemoizedProp(obj, prop) {
|
||||
if (!obj.hasOwnProperty('memoizedProps')) return null;
|
||||
obj = obj.memoizedProps;
|
||||
return this.findPropIn(obj, prop);
|
||||
}
|
||||
|
||||
static findMemoizedState(obj, prop) {
|
||||
if (!obj.hasOwnProperty('memoizedState')) return null;
|
||||
obj = obj.memoizedState;
|
||||
return this.findPropIn(obj, prop);
|
||||
}
|
||||
|
||||
static findPropIn(obj, prop) {
|
||||
if (obj && !(obj instanceof Array) && obj instanceof Object && obj.hasOwnProperty(prop)) return obj[prop];
|
||||
if (obj && obj instanceof Array) {
|
||||
const found = obj.find(mp => {
|
||||
if (mp.props && mp.props.hasOwnProperty(prop)) return true;
|
||||
});
|
||||
if (found) return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static propIterator(obj, propNames) {
|
||||
if (obj === null || obj === undefined) return null;
|
||||
const curPropName = propNames.shift(1);
|
||||
if (!obj.hasOwnProperty(curPropName)) return null;
|
||||
const curProp = obj[curPropName];
|
||||
if (propNames.length === 0) {
|
||||
return curProp;
|
||||
}
|
||||
return this.propIterator(curProp, propNames);
|
||||
}
|
||||
}
|
||||
|
||||
export default function (node) {
|
||||
return new class {
|
||||
constructor(node) {
|
||||
if ('string' === typeof node) node = document.querySelector(node);
|
||||
this.node = this.el = this.element = node;
|
||||
}
|
||||
get props() {
|
||||
return 'not yet implemented';
|
||||
}
|
||||
get reactInternalInstance() {
|
||||
return Reflection.reactInternalInstance(this.node);
|
||||
}
|
||||
prop(propName) {
|
||||
const split = propName.split('.');
|
||||
const first = Reflection.findProp(this.node, split[0]);
|
||||
if (split.length === 1) return first;
|
||||
return Reflection.propIterator(first, split.slice(1));
|
||||
}
|
||||
}(node);
|
||||
}
|
|
@ -4,3 +4,4 @@ export { default as VueInjector } from './vueinjector';
|
|||
export * from './bdmenu';
|
||||
export { default as Modals } from './modals';
|
||||
export { default as ProfileBadges } from './profilebadges';
|
||||
export { default as Reflection } from './reflection';
|
||||
|
|
Loading…
Reference in New Issue