Merge pull request #312 from samuelthomas2774/component-patches
React component patches
This commit is contained in:
commit
5cb4bc15bd
|
@ -4,6 +4,7 @@ dist
|
|||
etc
|
||||
release
|
||||
|
||||
tests/tmp
|
||||
tests/log.txt
|
||||
|
||||
# User data
|
||||
|
|
|
@ -21,7 +21,7 @@ export default new class BlockedMessages extends BuiltinModule {
|
|||
async enabled(e) {
|
||||
const MessageListComponents = Reflection.module.byProps('BlockedMessageGroup');
|
||||
MessageListComponents.OriginalBlockedMessageGroup = MessageListComponents.BlockedMessageGroup;
|
||||
MessageListComponents.BlockedMessageGroup = () => { return null; };
|
||||
MessageListComponents.BlockedMessageGroup = () => null;
|
||||
this.cancelBlockedMessages = () => {
|
||||
MessageListComponents.BlockedMessageGroup = MessageListComponents.OriginalBlockedMessageGroup;
|
||||
delete MessageListComponents.OriginalBlockedMessageGroup;
|
||||
|
|
|
@ -22,10 +22,10 @@ export default class BuiltinModule {
|
|||
this.patch = this.patch.bind(this);
|
||||
}
|
||||
|
||||
init() {
|
||||
async init() {
|
||||
this.setting.on('setting-updated', this._settingUpdated);
|
||||
if (this.setting.value) {
|
||||
if (this.enabled) this.enabled();
|
||||
if (this.enabled) await this.enabled();
|
||||
if (this.applyPatches) this.applyPatches();
|
||||
}
|
||||
}
|
||||
|
@ -38,16 +38,15 @@ export default class BuiltinModule {
|
|||
return Patcher.getPatchesByCaller(`BD:${this.moduleName}`);
|
||||
}
|
||||
|
||||
_settingUpdated(e) {
|
||||
const { value } = e;
|
||||
if (value === true) {
|
||||
if (this.enabled) this.enabled(e);
|
||||
if (this.applyPatches) this.applyPatches();
|
||||
return;
|
||||
}
|
||||
if (value === false) {
|
||||
if (this.disabled) this.disabled(e);
|
||||
async _settingUpdated(e) {
|
||||
if (e.value) {
|
||||
if (this.enabled) await this.enabled(e);
|
||||
if (this.applyPatches) await this.applyPatches();
|
||||
if (this.rerenderPatchedComponents) this.rerenderPatchedComponents();
|
||||
} else {
|
||||
if (this.disabled) await this.disabled(e);
|
||||
this.unpatch();
|
||||
if (this.rerenderPatchedComponents) this.rerenderPatchedComponents();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,12 +74,14 @@ export default class BuiltinModule {
|
|||
*/
|
||||
patch(module, fnName, cb, when = 'after') {
|
||||
if (!['before', 'after', 'instead'].includes(when)) when = 'after';
|
||||
Patch(`BD:${this.moduleName}`, module)[when](fnName, cb.bind(this));
|
||||
return Patch(`BD:${this.moduleName}`, module)[when](fnName, cb.bind(this));
|
||||
}
|
||||
|
||||
childPatch(module, fnName, child, cb, when = 'after') {
|
||||
const last = child.pop();
|
||||
|
||||
this.patch(module, fnName, (component, args, retVal) => {
|
||||
this.patch(retVal[child[0]], child[1], cb, when);
|
||||
const unpatch = this.patch(child.reduce((obj, key) => obj[key], retVal), last, function(...args) {unpatch(); return cb.call(this, component, ...args);}, when);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -42,6 +42,10 @@ export default new class ColoredText extends BuiltinModule {
|
|||
this.intensitySetting.off('setting-updated', this._intensityUpdated);
|
||||
}
|
||||
|
||||
rerenderPatchedComponents() {
|
||||
if (this.MessageContent) this.MessageContent.forceUpdateAll();
|
||||
}
|
||||
|
||||
/* Methods */
|
||||
_intensityUpdated() {
|
||||
this.MessageContent.forceUpdateAll();
|
||||
|
@ -50,16 +54,16 @@ export default new class ColoredText extends BuiltinModule {
|
|||
/* Patches */
|
||||
async applyPatches() {
|
||||
if (this.patches.length) return;
|
||||
this.MessageContent = await ReactComponents.getComponent('MessageContent', { selector: Reflection.resolve('container', 'containerCozy', 'containerCompact', 'edited').selector }, m => m.defaultProps && m.defaultProps.hasOwnProperty('disableButtons'));
|
||||
this.MessageContent = await ReactComponents.getComponent('MessageContent');
|
||||
this.patch(this.MessageContent.component.prototype, 'render', this.injectColoredText);
|
||||
this.MessageContent.forceUpdateAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set markup text colour to match role colour
|
||||
*/
|
||||
injectColoredText(thisObject, args, originalReturn) {
|
||||
this.patch(originalReturn.props, 'children', function(obj, args, returnValue) {
|
||||
const unpatch = this.patch(originalReturn.props, 'children', (obj, args, returnValue) => {
|
||||
unpatch();
|
||||
const { TinyColor } = Reflection.modules;
|
||||
const markup = Utils.findInReactTree(returnValue, m => m && m.props && m.props.className && m.props.className.includes('da-markup'));
|
||||
const roleColor = thisObject.props.message.colorString;
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
*/
|
||||
|
||||
import { Settings, Cache, Events } from 'modules';
|
||||
import BuiltinModule from './BuiltinModule';
|
||||
import { Reflection, ReactComponents, MonkeyPatch, Patcher, DiscordApi, Security } from 'modules';
|
||||
import BuiltinModule from '../BuiltinModule';
|
||||
import { Reflection, ReactComponents, DiscordApi, Security } from 'modules';
|
||||
import { VueInjector, Modals, Toasts } from 'ui';
|
||||
import { ClientLogger as Logger, ClientIPC } from 'common';
|
||||
import { request } from 'vendor';
|
||||
|
@ -172,7 +172,7 @@ export default new class E2EE extends BuiltinModule {
|
|||
this.patch(Dispatcher, 'dispatch', this.dispatcherPatch, 'before');
|
||||
this.patchMessageContent();
|
||||
|
||||
const ChannelTextArea = await ReactComponents.getComponent('ChannelTextArea', { selector: Reflection.resolve('channelTextArea', 'emojiButton').selector });
|
||||
const ChannelTextArea = await ReactComponents.getComponent('ChannelTextArea');
|
||||
this.patchChannelTextArea(ChannelTextArea);
|
||||
this.patchChannelTextAreaSubmit(ChannelTextArea);
|
||||
ChannelTextArea.forceUpdateAll();
|
||||
|
@ -236,12 +236,14 @@ export default new class E2EE extends BuiltinModule {
|
|||
}
|
||||
|
||||
async patchMessageContent() {
|
||||
const MessageContent = await ReactComponents.getComponent('MessageContent', { selector: Reflection.resolve('container', 'containerCozy', 'containerCompact', 'edited').selector }, m => m.defaultProps && m.defaultProps.hasOwnProperty('disableButtons'));
|
||||
const MessageContent = await ReactComponents.getComponent('MessageContent');
|
||||
this.patch(MessageContent.component.prototype, 'render', this.beforeRenderMessageContent, 'before');
|
||||
this.patch(MessageContent.component.prototype, 'render', this.afterRenderMessageContent);
|
||||
this.childPatch(MessageContent.component.prototype, 'render', ['props', 'children'], this.afterRenderMessageContent);
|
||||
MessageContent.forceUpdateAll();
|
||||
|
||||
const ImageWrapper = await ReactComponents.getComponent('ImageWrapper', { selector: Reflection.resolve('imageWrapper').selector });
|
||||
const ImageWrapper = await ReactComponents.getComponent('ImageWrapper');
|
||||
this.patch(ImageWrapper.component.prototype, 'render', this.beforeRenderImageWrapper, 'before');
|
||||
ImageWrapper.forceUpdateAll();
|
||||
}
|
||||
|
||||
beforeRenderMessageContent(component) {
|
||||
|
@ -285,10 +287,16 @@ export default new class E2EE extends BuiltinModule {
|
|||
component.props.message.contentParsed = create.contentParsed;
|
||||
}
|
||||
|
||||
afterRenderMessageContent(component, args, retVal) {
|
||||
afterRenderMessageContent(component, _childrenObject, args, retVal) {
|
||||
if (!component.props.message.bd_encrypted) return;
|
||||
const buttons = Utils.findInReactTree(retVal, m => Array.isArray(m) && m[1].props && m[1].props.currentUserId);
|
||||
|
||||
const { className } = Reflection.resolve('buttonContainer', 'avatar', 'username');
|
||||
const buttonContainer = Utils.findInReactTree(retVal, m => m && m.className && m.className.indexOf(className) !== -1);
|
||||
if (!buttonContainer) return;
|
||||
|
||||
const buttons = buttonContainer.children.props.children;
|
||||
if (!buttons) return;
|
||||
|
||||
try {
|
||||
buttons.unshift(VueInjector.createReactElement(E2EEMessageButton));
|
||||
} catch (err) {
|
|
@ -45,7 +45,7 @@
|
|||
import { E2EE } from 'builtin';
|
||||
import { Settings, DiscordApi, Reflection } from 'modules';
|
||||
import { Toasts } from 'ui';
|
||||
import { MiLock, MiImagePlus, MiIcVpnKey } from '../ui/components/common/MaterialIcon';
|
||||
import { MiLock, MiImagePlus, MiIcVpnKey } from 'commoncomponents';
|
||||
|
||||
export default {
|
||||
components: {
|
|
@ -17,7 +17,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { MiLock } from '../ui/components/common/MaterialIcon';
|
||||
import { MiLock } from 'commoncomponents';
|
||||
|
||||
export default {
|
||||
components: {
|
|
@ -0,0 +1,3 @@
|
|||
export { default as default } from './E2EE';
|
||||
export { default as E2EEComponent } from './E2EEComponent.vue';
|
||||
export { default as E2EEMessageButton } from './E2EEMessageButton.vue';
|
|
@ -9,18 +9,11 @@
|
|||
*/
|
||||
|
||||
import { Settings } from 'modules';
|
||||
|
||||
import BuiltinModule from './BuiltinModule';
|
||||
import EmoteModule from './EmoteModule';
|
||||
import GlobalAc from '../ui/autocomplete';
|
||||
import BuiltinModule from '../BuiltinModule';
|
||||
import EmoteModule, { EMOTE_SOURCES } from './EmoteModule';
|
||||
import GlobalAc from 'autocomplete';
|
||||
import { BdContextMenu } from 'ui';
|
||||
|
||||
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 EmoteAc extends BuiltinModule {
|
||||
|
||||
/* Getters */
|
|
@ -8,15 +8,10 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import VrWrapper from '../ui/vrwrapper';
|
||||
import VrWrapper from '../../ui/vrwrapper';
|
||||
import { EMOTE_SOURCES } from '.';
|
||||
import EmoteComponent from './EmoteComponent.vue';
|
||||
|
||||
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 class Emote extends VrWrapper {
|
||||
|
||||
constructor(type, id, name) {
|
|
@ -5,7 +5,7 @@
|
|||
<script>
|
||||
import { ClientLogger as Logger } from 'common';
|
||||
import EmoteModule from './EmoteModule';
|
||||
import { MiStar } from '../ui/components/common';
|
||||
import { MiStar } from 'commoncomponents';
|
||||
|
||||
export default {
|
||||
components: {
|
|
@ -8,20 +8,17 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import BuiltinModule from './BuiltinModule';
|
||||
import BuiltinModule from '../BuiltinModule';
|
||||
import path from 'path';
|
||||
import { request } from 'vendor';
|
||||
|
||||
import { Utils, FileUtils } from 'common';
|
||||
import { Utils, FileUtils, ClientLogger as Logger } from 'common';
|
||||
import { DiscordApi, Settings, Globals, Reflection, ReactComponents, Database } from 'modules';
|
||||
import { DiscordContextMenu } from 'ui';
|
||||
|
||||
import Emote from './EmoteComponent.js';
|
||||
import Autocomplete from '../ui/components/common/Autocomplete.vue';
|
||||
|
||||
import GlobalAc from '../ui/autocomplete';
|
||||
|
||||
const EMOTE_SOURCES = [
|
||||
export 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'
|
||||
|
@ -131,6 +128,8 @@ export default new class EmoteModule extends BuiltinModule {
|
|||
|
||||
this.database.set(id, { id: emote.value.id || value, type });
|
||||
}
|
||||
|
||||
Logger.log('EmoteModule', ['Loaded emote database']);
|
||||
}
|
||||
|
||||
async loadUserData() {
|
||||
|
@ -218,15 +217,18 @@ export default new class EmoteModule extends BuiltinModule {
|
|||
async applyPatches() {
|
||||
this.patchMessageContent();
|
||||
this.patchSendAndEdit();
|
||||
const ImageWrapper = await ReactComponents.getComponent('ImageWrapper', { selector: Reflection.resolve('imageWrapper').selector });
|
||||
this.patch(ImageWrapper.component.prototype, 'render', this.beforeRenderImageWrapper, 'before');
|
||||
this.patchSpoiler();
|
||||
|
||||
const MessageAccessories = await ReactComponents.getComponent('MessageAccessories');
|
||||
this.patch(MessageAccessories.component.prototype, 'render', this.afterRenderMessageAccessories, 'after');
|
||||
MessageAccessories.forceUpdateAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Patches MessageContent render method
|
||||
*/
|
||||
async patchMessageContent() {
|
||||
const MessageContent = await ReactComponents.getComponent('MessageContent', { selector: Reflection.resolve('container', 'containerCozy', 'containerCompact', 'edited').selector }, m => m.defaultProps && m.defaultProps.hasOwnProperty('disableButtons'));
|
||||
const MessageContent = await ReactComponents.getComponent('MessageContent');
|
||||
this.childPatch(MessageContent.component.prototype, 'render', ['props', 'children'], this.afterRenderMessageContent);
|
||||
MessageContent.forceUpdateAll();
|
||||
}
|
||||
|
@ -240,10 +242,26 @@ export default new class EmoteModule extends BuiltinModule {
|
|||
this.patch(MessageActions, 'editMessage', this.handleEditMessage, 'instead');
|
||||
}
|
||||
|
||||
async patchSpoiler() {
|
||||
const Spoiler = await ReactComponents.getComponent('Spoiler');
|
||||
this.childPatch(Spoiler.component.prototype, 'render', ['props', 'children', 'props', 'children'], this.afterRenderSpoiler);
|
||||
Spoiler.forceUpdateAll();
|
||||
}
|
||||
|
||||
afterRenderSpoiler(component, _childrenObject, args, retVal) {
|
||||
const markup = Utils.findInReactTree(retVal, filter =>
|
||||
filter &&
|
||||
filter.className &&
|
||||
filter.className.includes('inlineContent'));
|
||||
if (!markup) return;
|
||||
|
||||
markup.children = this.processMarkup(markup.children);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle message render
|
||||
*/
|
||||
afterRenderMessageContent(component, args, retVal) {
|
||||
afterRenderMessageContent(component, _childrenObject, args, retVal) {
|
||||
const markup = Utils.findInReactTree(retVal, filter =>
|
||||
filter &&
|
||||
filter.className &&
|
||||
|
@ -256,11 +274,13 @@ export default new class EmoteModule extends BuiltinModule {
|
|||
/**
|
||||
* Handle send message
|
||||
*/
|
||||
async handleSendMessage(component, args, orig) {
|
||||
async handleSendMessage(MessageActions, args, orig) {
|
||||
if (!args.length) return orig(...args);
|
||||
const { content } = args[1];
|
||||
if (!content) return orig(...args);
|
||||
|
||||
Logger.log('EmoteModule', ['Sending message', MessageActions, args, orig]);
|
||||
|
||||
const emoteAsImage = Settings.getSetting('emotes', 'default', 'emoteasimage').value &&
|
||||
(DiscordApi.currentChannel.type === 'DM' || DiscordApi.currentChannel.checkPermissions(DiscordApi.modules.DiscordPermissions.ATTACH_FILES));
|
||||
|
||||
|
@ -271,7 +291,7 @@ export default new class EmoteModule extends BuiltinModule {
|
|||
const emote = this.findByName(isEmote[1], true);
|
||||
if (!emote) return word;
|
||||
this.addToMostUsed(emote);
|
||||
return emote ? `:${isEmote[1]}:` : word;
|
||||
return emote ? `;${isEmote[1]};` : word;
|
||||
}
|
||||
return word;
|
||||
}).join(' ');
|
||||
|
@ -305,23 +325,27 @@ export default new class EmoteModule extends BuiltinModule {
|
|||
if (!content) return orig(...args);
|
||||
args[2].content = args[2].content.split(' ').map(word => {
|
||||
const isEmote = /;(.*?);/g.exec(word);
|
||||
return isEmote ? `:${isEmote[1]}:` : word;
|
||||
return isEmote ? `;${isEmote[1]};` : word;
|
||||
}).join(' ');
|
||||
return orig(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle imagewrapper render
|
||||
* Handle MessageAccessories render
|
||||
*/
|
||||
beforeRenderImageWrapper(component, args, retVal) {
|
||||
if (!component.props || !component.props.src) return;
|
||||
afterRenderMessageAccessories(component, args, retVal) {
|
||||
if (!component.props || !component.props.message) return;
|
||||
if (!component.props.message.attachments || component.props.message.attachments.length !== 1) return;
|
||||
|
||||
const src = component.props.original || component.props.src.split('?')[0];
|
||||
if (!src || !src.includes('.bdemote.')) return;
|
||||
const emoteName = src.split('/').pop().split('.')[0];
|
||||
const emote = this.findByName(emoteName);
|
||||
const filename = component.props.message.attachments[0].filename;
|
||||
const match = filename.match(/([^/]*)\.bdemote\.(gif|png)$/i);
|
||||
if (!match) return;
|
||||
|
||||
const emote = this.findByName(match[1]);
|
||||
if (!emote) return;
|
||||
retVal.props.children = emote.render();
|
||||
|
||||
emote.jumboable = true;
|
||||
retVal.props.children[2] = emote.render();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -339,14 +363,14 @@ export default new class EmoteModule extends BuiltinModule {
|
|||
for (const child of markup) {
|
||||
if (typeof child !== 'string') {
|
||||
if (typeof child === 'object') {
|
||||
const isEmoji = Utils.findInReactTree(child, 'emojiName');
|
||||
if (isEmoji) child.props.children.props.jumboable = jumboable;
|
||||
const isEmoji = Utils.findInReactTree(child, filter => filter && filter.emojiName);
|
||||
if (isEmoji) isEmoji.jumboable = jumboable;
|
||||
}
|
||||
newMarkup.push(child);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!/:(\w+):/g.test(child)) {
|
||||
if (!/;(\w+);/g.test(child)) {
|
||||
newMarkup.push(child);
|
||||
continue;
|
||||
}
|
||||
|
@ -355,7 +379,7 @@ export default new class EmoteModule extends BuiltinModule {
|
|||
|
||||
let s = '';
|
||||
for (const word of words) {
|
||||
const isemote = /:(.*?):/g.exec(word);
|
||||
const isemote = /;(.*?);/g.exec(word);
|
||||
if (!isemote) {
|
||||
s += word;
|
||||
continue;
|
|
@ -0,0 +1,4 @@
|
|||
export { default as EmoteModule, EMOTE_SOURCES } from './EmoteModule';
|
||||
export { default as Emote } from './EmoteComponent';
|
||||
export { default as EmoteComponent } from './EmoteComponent.vue';
|
||||
export { default as EmoteAc } from './EmoteAc';
|
|
@ -1,16 +1,19 @@
|
|||
import { default as EmoteModule } from './EmoteModule';
|
||||
import { default as ReactDevtoolsModule } from './ReactDevtoolsModule';
|
||||
import { default as VueDevtoolsModule } from './VueDevToolsModule';
|
||||
import { default as TrackingProtection } from './TrackingProtection';
|
||||
import { default as E2EE } from './E2EE';
|
||||
import { default as ColoredText } from './ColoredText';
|
||||
import { default as TwentyFourHour } from './24Hour';
|
||||
import { default as KillClyde } from './KillClyde';
|
||||
import { default as BlockedMessages } from './BlockedMessages';
|
||||
import { default as VoiceDisconnect } from './VoiceDisconnect';
|
||||
import { default as EmoteAc } from './EmoteAc';
|
||||
import { EmoteModule, EmoteAc } from './Emotes';
|
||||
import ReactDevtoolsModule from './ReactDevtoolsModule';
|
||||
import VueDevtoolsModule from './VueDevToolsModule';
|
||||
import TrackingProtection from './TrackingProtection';
|
||||
import E2EE from './E2EE';
|
||||
import ColoredText from './ColoredText';
|
||||
import TwentyFourHour from './24Hour';
|
||||
import KillClyde from './KillClyde';
|
||||
import BlockedMessages from './BlockedMessages';
|
||||
import VoiceDisconnect from './VoiceDisconnect';
|
||||
|
||||
export default class {
|
||||
static get modules() {
|
||||
return require('./builtin');
|
||||
}
|
||||
|
||||
static initAll() {
|
||||
EmoteModule.init();
|
||||
ReactDevtoolsModule.init();
|
||||
|
|
|
@ -12,7 +12,7 @@ import BuiltinModule from './BuiltinModule';
|
|||
|
||||
import { Reflection } from 'modules';
|
||||
|
||||
export default new class E2EE extends BuiltinModule {
|
||||
export default new class TrackingProtection extends BuiltinModule {
|
||||
|
||||
/* Getters */
|
||||
get moduleName() { return 'TrackingProtection' }
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export { default as EmoteModule } from './EmoteModule';
|
||||
export { EmoteModule, EmoteAc } from './Emotes';
|
||||
export { default as ReactDevtoolsModule } from './ReactDevtoolsModule';
|
||||
export { default as VueDevtoolsModule } from './VueDevToolsModule';
|
||||
export { default as TrackingProtection } from './TrackingProtection';
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { DOM, BdUI, BdMenu, Modals, Toasts, Notifications, BdContextMenu, DiscordContextMenu } from 'ui';
|
||||
import { DOM, BdUI, BdMenu, Modals, Toasts, Notifications, BdContextMenu, DiscordContextMenu, Autocomplete } from 'ui';
|
||||
import BdCss from './styles/index.scss';
|
||||
import { Events, Globals, Settings, Database, Updater, ModuleManager, PluginManager, ThemeManager, ExtModuleManager, Vendor, Patcher, MonkeyPatch, ReactComponents, ReactHelpers, ReactAutoPatcher, DiscordApi, BdWebApi, Connectivity, Cache, Reflection, PackageInstaller } from 'modules';
|
||||
import { ClientLogger as Logger, ClientIPC, Utils, Axi } from 'common';
|
||||
|
@ -27,14 +27,14 @@ class BetterDiscord {
|
|||
Logger.log('main', 'BetterDiscord starting');
|
||||
|
||||
this._bd = {
|
||||
DOM, BdUI, BdMenu, Modals, Reflection, Toasts, Notifications, BdContextMenu, DiscordContextMenu,
|
||||
DOM, BdUI, BdMenu, Modals, Reflection, Toasts, Notifications, BdContextMenu, DiscordContextMenu, Autocomplete,
|
||||
|
||||
Events, Globals, Settings, Database, Updater,
|
||||
ModuleManager, PluginManager, ThemeManager, ExtModuleManager, PackageInstaller,
|
||||
Vendor,
|
||||
|
||||
Patcher, MonkeyPatch, ReactComponents, ReactHelpers, ReactAutoPatcher, DiscordApi,
|
||||
EmoteModule,
|
||||
BuiltinManager, EmoteModule,
|
||||
BdWebApi,
|
||||
Connectivity,
|
||||
Cache,
|
||||
|
|
|
@ -65,6 +65,10 @@ export default class Content extends AsyncEventEmitter {
|
|||
get config() { return this.settings.categories }
|
||||
get data() { return this.userConfig.data || (this.userConfig.data = {}) }
|
||||
|
||||
get packed() { return this.dirName.packed }
|
||||
get packagePath() { return this.dirName.packagePath }
|
||||
get packageName() { return this.dirName.pkg }
|
||||
|
||||
/**
|
||||
* Opens a settings modal for this content.
|
||||
* @return {Modal}
|
||||
|
|
|
@ -220,6 +220,7 @@ export default class {
|
|||
const unpackedPath = path.join(Globals.getPath('tmp'), packageName);
|
||||
|
||||
asar.extractAll(packagePath, unpackedPath);
|
||||
|
||||
return this.preloadContent({
|
||||
config,
|
||||
contentPath: unpackedPath,
|
||||
|
@ -228,8 +229,8 @@ export default class {
|
|||
packageName,
|
||||
packed: true
|
||||
}, reload, index);
|
||||
|
||||
} catch (err) {
|
||||
Logger.log('ContentManager', ['Error extracting packed content', err]);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
@ -322,12 +323,6 @@ export default class {
|
|||
return content;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
} finally {
|
||||
if (typeof dirName === 'object' && dirName.packed) {
|
||||
rimraf(dirName.contentPath, err => {
|
||||
if (err) Logger.err(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -353,6 +348,7 @@ export default class {
|
|||
await unload;
|
||||
|
||||
await FileUtils.recursiveDeleteDirectory(content.paths.contentPath);
|
||||
if (content.packed) await FileUtils.recursiveDeleteDirectory(content.packagePath);
|
||||
return true;
|
||||
} catch (err) {
|
||||
Logger.err(this.moduleName, err);
|
||||
|
@ -384,7 +380,7 @@ export default class {
|
|||
|
||||
if (this.unloadContentHook) this.unloadContentHook(content);
|
||||
|
||||
if (reload) return content.packed ? this.preloadPackedContent(content.packed.pkg, true, index) : this.preloadContent(content.dirName, true, index);
|
||||
if (reload) return content.packed ? this.preloadPackedContent(content.packagePath, true, index) : this.preloadContent(content.dirName, true, index);
|
||||
|
||||
this.localContent.splice(index, 1);
|
||||
} catch (err) {
|
||||
|
|
|
@ -36,10 +36,6 @@ export default new class extends Module {
|
|||
|
||||
async first() {
|
||||
const config = await ClientIPC.send('getConfig');
|
||||
config.paths.push({
|
||||
id: 'tmp',
|
||||
path: path.join(config.paths.find(p => p.id === 'base').path, 'tmp')
|
||||
});
|
||||
this.setState({ config });
|
||||
|
||||
// This is for Discord to stop error reporting :3
|
||||
|
|
|
@ -6,14 +6,14 @@ import rimraf from 'rimraf';
|
|||
|
||||
import { request } from 'vendor';
|
||||
import { Modals } from 'ui';
|
||||
import { Utils } from 'common';
|
||||
import { Utils, FileUtils } from 'common';
|
||||
import PluginManager from './pluginmanager';
|
||||
import Globals from './globals';
|
||||
import Security from './security';
|
||||
import { ReactComponents } from './reactcomponents';
|
||||
import Reflection from './reflection';
|
||||
import DiscordApi from './discordapi';
|
||||
import ThemeManager from './thememanager';
|
||||
import { MonkeyPatch } from './patcher';
|
||||
import { DOM } from 'ui';
|
||||
|
||||
export default class PackageInstaller {
|
||||
|
@ -84,15 +84,10 @@ export default class PackageInstaller {
|
|||
|
||||
await oldContent.unload(true);
|
||||
|
||||
if (oldContent.packed && oldContent.packed.packageName !== nameOrId) {
|
||||
rimraf(oldContent.packed.packagePath, err => {
|
||||
if (err) throw err;
|
||||
});
|
||||
} else {
|
||||
rimraf(oldContent.contentPath, err => {
|
||||
if (err) throw err;
|
||||
});
|
||||
if (oldContent.packed && oldContent.packageName !== nameOrId) {
|
||||
await FileUtils.deleteFile(oldContent.packagePath).catch(err => null);
|
||||
}
|
||||
await FileUtils.recursiveDeleteDirectory(oldContent.contentPath).catch(err => null);
|
||||
|
||||
return manager.preloadPackedContent(outputName);
|
||||
} catch (err) {
|
||||
|
@ -133,41 +128,51 @@ export default class PackageInstaller {
|
|||
}
|
||||
}
|
||||
|
||||
static async handleDrop(stateNode, e, original) {
|
||||
if (!e.dataTransfer.files.length || !e.dataTransfer.files[0].name.endsWith('.bd')) return original && original.call(stateNode, e);
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
if (stateNode) stateNode.clearDragging();
|
||||
|
||||
const currentChannel = DiscordApi.currentChannel;
|
||||
const canUpload = currentChannel ?
|
||||
currentChannel.checkPermissions(Reflection.modules.DiscordConstants.Permissions.SEND_MESSAGES) &&
|
||||
currentChannel.checkPermissions(Reflection.modules.DiscordConstants.Permissions.ATTACH_FILES) : false;
|
||||
|
||||
const files = Array.from(e.dataTransfer.files).slice(0);
|
||||
const actionCode = await this.dragAndDropHandler(e.dataTransfer.files[0].path, canUpload);
|
||||
|
||||
if (actionCode === 0 && stateNode) stateNode.promptToUpload(files, currentChannel.id, true, !e.shiftKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Patches Discord upload area for .bd files
|
||||
*/
|
||||
static async uploadAreaPatch() {
|
||||
const { selector } = Reflection.resolve('uploadArea');
|
||||
this.UploadArea = await ReactComponents.getComponent('UploadArea', { selector });
|
||||
|
||||
const reflect = Reflection.DOM(selector);
|
||||
const stateNode = reflect.getComponentStateNode(this.UploadArea);
|
||||
const callback = async function (e) {
|
||||
if (!e.dataTransfer.files.length || !e.dataTransfer.files[0].name.endsWith('.bd')) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
stateNode.clearDragging();
|
||||
const currentChannel = DiscordApi.currentChannel;
|
||||
const canUpload = currentChannel ? currentChannel.checkPermissions(Reflection.modules.DiscordConstants.Permissions.ATTACH_FILES) : false;
|
||||
const files = Array.from(e.dataTransfer.files).slice(0);
|
||||
const actionCode = await PackageInstaller.dragAndDropHandler(e.dataTransfer.files[0].path, canUpload);
|
||||
if (actionCode === 0) stateNode.promptToUpload(files, currentChannel.id, true, !e.shiftKey);
|
||||
};
|
||||
|
||||
static async uploadAreaPatch(UploadArea) {
|
||||
// Add a listener to root for when not in a channel
|
||||
const root = DOM.getElement('#app-mount');
|
||||
root.addEventListener('drop', callback);
|
||||
const rootHandleDrop = this.handleDrop.bind(this, undefined);
|
||||
root.addEventListener('drop', rootHandleDrop);
|
||||
|
||||
// Remove their handler, add ours, then read theirs to give ours priority to stop theirs when we get a .bd file.
|
||||
reflect.element.removeEventListener('drop', stateNode.handleDrop);
|
||||
reflect.element.addEventListener('drop', callback);
|
||||
reflect.element.addEventListener('drop', stateNode.handleDrop);
|
||||
const unpatchUploadAreaHandleDrop = MonkeyPatch('BD:ReactComponents', UploadArea.component.prototype).instead('handleDrop', (component, [e], original) => this.handleDrop(component, e, original));
|
||||
|
||||
this.unpatchUploadArea = function () {
|
||||
reflect.element.removeEventListener('drop', callback);
|
||||
root.removeEventListener('drop', callback);
|
||||
this.unpatchUploadArea = () => {
|
||||
unpatchUploadAreaHandleDrop();
|
||||
root.removeEventListener('drop', rootHandleDrop);
|
||||
this.unpatchUploadArea = undefined;
|
||||
};
|
||||
|
||||
for (const element of document.querySelectorAll(UploadArea.important.selector)) {
|
||||
const stateNode = Reflection.DOM(element).getComponentStateNode(UploadArea);
|
||||
|
||||
element.removeEventListener('drop', stateNode.handleDrop);
|
||||
stateNode.handleDrop = UploadArea.component.prototype.handleDrop.bind(stateNode);
|
||||
element.addEventListener('drop', stateNode.handleDrop);
|
||||
|
||||
stateNode.forceUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -109,21 +109,9 @@ export default class extends ContentManager {
|
|||
throw {message: `Plugin ${info.name} did not return a class that extends Plugin.`};
|
||||
|
||||
const instance = new plugin({
|
||||
configs, info, main,
|
||||
paths: {
|
||||
contentPath: paths.contentPath,
|
||||
dirName: packed ? packed.packageName : paths.dirName,
|
||||
mainPath: paths.mainPath
|
||||
}
|
||||
configs, info, main, paths
|
||||
});
|
||||
|
||||
if (packed) instance.packed = {
|
||||
pkg: packed.pkg,
|
||||
packageName: packed.packageName,
|
||||
packagePath: packed.packagePath,
|
||||
packed: true
|
||||
}; else instance.packed = false;
|
||||
|
||||
if (instance.enabled && this.loaded) {
|
||||
instance.userConfig.enabled = false;
|
||||
instance.start(false);
|
||||
|
|
|
@ -173,9 +173,18 @@ class ReactComponent {
|
|||
this.important = important;
|
||||
}
|
||||
|
||||
get elements() {
|
||||
if (!this.important || !this.important.selector) return [];
|
||||
|
||||
return document.querySelectorAll(this.important.selector);
|
||||
}
|
||||
|
||||
get stateNodes() {
|
||||
return [...this.elements].map(e => Reflection.DOM(e).getComponentStateNode(this));
|
||||
}
|
||||
|
||||
forceUpdateAll() {
|
||||
if (!this.important || !this.important.selector) return;
|
||||
for (const e of document.querySelectorAll(this.important.selector)) {
|
||||
for (const e of this.elements) {
|
||||
Reflection.DOM(e).forceUpdate(this);
|
||||
}
|
||||
}
|
||||
|
@ -186,6 +195,7 @@ export class ReactComponents {
|
|||
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 get componentAliases() { return this._componentAliases || (this._componentAliases = []) }
|
||||
|
||||
static get ReactComponent() { return ReactComponent }
|
||||
|
||||
|
@ -222,6 +232,8 @@ export class ReactComponents {
|
|||
* @return {Promise => ReactComponent}
|
||||
*/
|
||||
static async getComponent(name, important, filter) {
|
||||
name = this.getComponentName(name);
|
||||
|
||||
const have = this.components.find(c => c.id === name);
|
||||
if (have) return have;
|
||||
|
||||
|
@ -239,7 +251,13 @@ export class ReactComponents {
|
|||
let component, reflect;
|
||||
for (const element of elements) {
|
||||
reflect = Reflection.DOM(element);
|
||||
component = filter ? reflect.components.find(filter) : reflect.component;
|
||||
component = filter ? reflect.components.find(component => {
|
||||
try {
|
||||
return filter.call(undefined, component);
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}) : reflect.component;
|
||||
if (component) break;
|
||||
}
|
||||
|
||||
|
@ -276,6 +294,19 @@ export class ReactComponents {
|
|||
});
|
||||
}
|
||||
|
||||
static getComponentName(name) {
|
||||
const resolvedAliases = [];
|
||||
|
||||
while (this.componentAliases[name]) {
|
||||
resolvedAliases.push(name);
|
||||
name = this.componentAliases[name];
|
||||
|
||||
if (resolvedAliases.includes(name)) break;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
static setName(name, filter) {
|
||||
const have = this.components.find(c => c.id === name);
|
||||
if (have) return have;
|
||||
|
@ -351,6 +382,21 @@ export class ReactAutoPatcher {
|
|||
this.Message.forceUpdateAll();
|
||||
}
|
||||
|
||||
static async patchMessageContent() {
|
||||
const { selector } = Reflection.resolve('container', 'containerCozy', 'containerCompact', 'edited');
|
||||
this.MessageContent = await ReactComponents.getComponent('MessageContent', {selector}, c => c.defaultProps && c.defaultProps.hasOwnProperty('disableButtons'));
|
||||
}
|
||||
|
||||
static async patchSpoiler() {
|
||||
const { selector } = Reflection.resolve('spoilerText', 'spoilerContainer');
|
||||
this.Spoiler = await ReactComponents.getComponent('Spoiler', {selector}, c => c.prototype.renderSpoilerText);
|
||||
}
|
||||
|
||||
static async patchMessageAccessories() {
|
||||
const { selector } = Reflection.resolve('container', 'containerCozy', 'embedWrapper');
|
||||
this.MessageAccessories = await ReactComponents.getComponent('MessageAccessories', {selector});
|
||||
}
|
||||
|
||||
static async patchMessageGroup() {
|
||||
const { selector } = Reflection.resolve('container', 'message', 'messageCozy');
|
||||
this.MessageGroup = await ReactComponents.getComponent('MessageGroup', {selector});
|
||||
|
@ -369,7 +415,16 @@ export class ReactAutoPatcher {
|
|||
this.MessageGroup.forceUpdateAll();
|
||||
}
|
||||
|
||||
static async patchImageWrapper() {
|
||||
ReactComponents.componentAliases.ImageWrapper = 'Image';
|
||||
|
||||
const { selector } = Reflection.resolve('imageWrapper');
|
||||
this.ImageWrapper = await ReactComponents.getComponent('ImageWrapper', {selector}, c => typeof c.defaultProps.children === 'function');
|
||||
}
|
||||
|
||||
static async patchChannelMember() {
|
||||
ReactComponents.componentAliases.ChannelMember = 'MemberListItem';
|
||||
|
||||
const { selector } = Reflection.resolve('member', 'memberInner', 'activity');
|
||||
this.ChannelMember = await ReactComponents.getComponent('ChannelMember', {selector}, m => m.prototype.renderActivity);
|
||||
|
||||
|
@ -385,8 +440,13 @@ export class ReactAutoPatcher {
|
|||
this.ChannelMember.forceUpdateAll();
|
||||
}
|
||||
|
||||
static async patchNameTag() {
|
||||
const { selector } = Reflection.resolve('nameTag', 'username', 'discriminator', 'ownerIcon');
|
||||
this.NameTag = await ReactComponents.getComponent('NameTag', {selector});
|
||||
}
|
||||
|
||||
static async patchGuild() {
|
||||
const selector = `div.${Reflection.resolve('guild', 'guildsWrapper').className}:not(:first-child)`;
|
||||
const selector = `div.${Reflection.resolve('container', 'guildIcon', 'selected', 'unread').className}:not(:first-child)`;
|
||||
this.Guild = await ReactComponents.getComponent('Guild', {selector}, m => m.prototype.renderBadge);
|
||||
|
||||
this.unpatchGuild = MonkeyPatch('BD:ReactComponents', this.Guild.component.prototype).after('render', (component, args, retVal) => {
|
||||
|
@ -403,7 +463,7 @@ export class ReactAutoPatcher {
|
|||
* The Channel component contains the header, message scroller, message form and member list.
|
||||
*/
|
||||
static async patchChannel() {
|
||||
const selector = '.chat';
|
||||
const { selector } = Reflection.resolve('chat', 'title', 'channelName');
|
||||
this.Channel = await ReactComponents.getComponent('Channel', {selector});
|
||||
|
||||
this.unpatchChannel = MonkeyPatch('BD:ReactComponents', this.Channel.component.prototype).after('render', (component, args, retVal) => {
|
||||
|
@ -419,10 +479,17 @@ export class ReactAutoPatcher {
|
|||
this.Channel.forceUpdateAll();
|
||||
}
|
||||
|
||||
static async patchChannelTextArea() {
|
||||
const { selector } = Reflection.resolve('channelTextArea', 'autocomplete');
|
||||
this.ChannelTextArea = await ReactComponents.getComponent('ChannelTextArea', {selector});
|
||||
}
|
||||
|
||||
/**
|
||||
* The GuildTextChannel component represents a text channel in the guild channel list.
|
||||
*/
|
||||
static async patchGuildTextChannel() {
|
||||
ReactComponents.componentAliases.GuildTextChannel = 'TextChannel';
|
||||
|
||||
const { selector } = Reflection.resolve('containerDefault', 'actionIcon');
|
||||
this.GuildTextChannel = await ReactComponents.getComponent('GuildTextChannel', {selector}, c => c.prototype.renderMentionBadge);
|
||||
|
||||
|
@ -435,6 +502,8 @@ export class ReactAutoPatcher {
|
|||
* The GuildVoiceChannel component represents a voice channel in the guild channel list.
|
||||
*/
|
||||
static async patchGuildVoiceChannel() {
|
||||
ReactComponents.componentAliases.GuildVoiceChannel = 'VoiceChannel';
|
||||
|
||||
const { selector } = Reflection.resolve('containerDefault', 'actionIcon');
|
||||
this.GuildVoiceChannel = await ReactComponents.getComponent('GuildVoiceChannel', {selector}, c => c.prototype.handleVoiceConnect);
|
||||
|
||||
|
@ -447,7 +516,9 @@ export class ReactAutoPatcher {
|
|||
* The DirectMessage component represents a channel in the direct messages list.
|
||||
*/
|
||||
static async patchDirectMessage() {
|
||||
const selector = '.channel.private';
|
||||
ReactComponents.componentAliases.DirectMessage = 'PrivateChannel';
|
||||
|
||||
const { selector } = Reflection.resolve('channel', 'avatar', 'name');
|
||||
this.DirectMessage = await ReactComponents.getComponent('DirectMessage', {selector}, c => c.prototype.renderAvatar);
|
||||
|
||||
this.unpatchDirectMessage = MonkeyPatch('BD:ReactComponents', this.DirectMessage.component.prototype).after('render', this._afterChannelRender);
|
||||
|
@ -469,15 +540,18 @@ export class ReactAutoPatcher {
|
|||
}
|
||||
|
||||
static async patchUserProfileModal() {
|
||||
ReactComponents.componentAliases.UserProfileModal = 'UserProfileBody';
|
||||
|
||||
const { selector } = Reflection.resolve('root', 'topSectionNormal');
|
||||
this.UserProfileModal = await ReactComponents.getComponent('UserProfileModal', {selector}, Filters.byPrototypeFields(['renderHeader', 'renderBadges']));
|
||||
this.UserProfileModal = await ReactComponents.getComponent('UserProfileModal', {selector}, c => c.prototype.renderHeader && c.prototype.renderBadges);
|
||||
|
||||
this.unpatchUserProfileModal = MonkeyPatch('BD:ReactComponents', this.UserProfileModal.component.prototype).after('render', (component, args, retVal) => {
|
||||
const root = retVal.props.children[0] || retVal.props.children;
|
||||
const { user } = component.props;
|
||||
if (!user) return;
|
||||
retVal.props['data-user-id'] = user.id;
|
||||
if (user.bot) retVal.props.className += ' bd-isBot';
|
||||
if (user.id === DiscordApi.currentUser.id) retVal.props.className += ' bd-isCurrentUser';
|
||||
root.props['data-user-id'] = user.id;
|
||||
if (user.bot) root.props.className += ' bd-isBot';
|
||||
if (user.id === DiscordApi.currentUser.id) root.props.className += ' bd-isCurrentUser';
|
||||
});
|
||||
|
||||
this.UserProfileModal.forceUpdateAll();
|
||||
|
@ -485,24 +559,28 @@ export class ReactAutoPatcher {
|
|||
|
||||
static async patchUserPopout() {
|
||||
const { selector } = Reflection.resolve('userPopout', 'headerNormal');
|
||||
this.UserPopout = await ReactComponents.getComponent('UserPopout', {selector});
|
||||
this.UserPopout = await ReactComponents.getComponent('UserPopout', {selector}, c => c.prototype.renderHeader);
|
||||
|
||||
this.unpatchUserPopout = MonkeyPatch('BD:ReactComponents', this.UserPopout.component.prototype).after('render', (component, args, retVal) => {
|
||||
const root = retVal.props.children[0] || retVal.props.children;
|
||||
const { user, guild, guildMember } = component.props;
|
||||
if (!user) return;
|
||||
retVal.props['data-user-id'] = user.id;
|
||||
if (user.bot) retVal.props.className += ' bd-isBot';
|
||||
if (user.id === DiscordApi.currentUser.id) retVal.props.className += ' bd-isCurrentUser';
|
||||
if (guild) retVal.props['data-guild-id'] = guild.id;
|
||||
if (guild && user.id === guild.ownerId) retVal.props.className += ' bd-isGuildOwner';
|
||||
if (guild && guildMember) retVal.props.className += ' bd-isGuildMember';
|
||||
if (guildMember && guildMember.roles.length) retVal.props.className += ' bd-hasRoles';
|
||||
root.props['data-user-id'] = user.id;
|
||||
if (user.bot) root.props.className += ' bd-isBot';
|
||||
if (user.id === DiscordApi.currentUser.id) root.props.className += ' bd-isCurrentUser';
|
||||
if (guild) root.props['data-guild-id'] = guild.id;
|
||||
if (guild && user.id === guild.ownerId) root.props.className += ' bd-isGuildOwner';
|
||||
if (guild && guildMember) root.props.className += ' bd-isGuildMember';
|
||||
if (guildMember && guildMember.roles.length) root.props.className += ' bd-hasRoles';
|
||||
});
|
||||
|
||||
this.UserPopout.forceUpdateAll();
|
||||
}
|
||||
|
||||
static async patchUploadArea() {
|
||||
PackageInstaller.uploadAreaPatch();
|
||||
const { selector } = Reflection.resolve('uploadArea');
|
||||
this.UploadArea = await ReactComponents.getComponent('UploadArea', {selector});
|
||||
|
||||
PackageInstaller.uploadAreaPatch(this.UploadArea);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -147,6 +147,11 @@ export default class Theme extends Content {
|
|||
* @param {Array} files Files to watch
|
||||
*/
|
||||
set watchfiles(files) {
|
||||
if (this.packed) {
|
||||
// Don't watch files for packed themes
|
||||
return;
|
||||
}
|
||||
|
||||
if (!files) files = [];
|
||||
|
||||
for (const file of files) {
|
||||
|
|
|
@ -36,12 +36,7 @@ export default class ThemeManager extends ContentManager {
|
|||
static async loadTheme(paths, configs, info, main) {
|
||||
try {
|
||||
const instance = new Theme({
|
||||
configs, info, main,
|
||||
paths: {
|
||||
contentPath: paths.contentPath,
|
||||
dirName: paths.dirName,
|
||||
mainPath: paths.mainPath
|
||||
}
|
||||
configs, info, main, paths
|
||||
});
|
||||
if (instance.enabled) {
|
||||
instance.userConfig.enabled = false;
|
||||
|
|
|
@ -27,3 +27,9 @@ body:not(.bd-hideButton) {
|
|||
.bd-settingsWrapper.platform-linux {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
// Remove the margin on message attachments with an emote
|
||||
.da-containerCozy + .da-containerCozy > * > .bd-emote {
|
||||
margin-top: -8px;
|
||||
margin-bottom: -8px;
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ export default new class Autocomplete {
|
|||
}
|
||||
|
||||
async init() {
|
||||
this.cta = await ReactComponents.getComponent('ChannelTextArea', { selector: Reflection.resolve('channelTextArea', 'emojiButton').selector });
|
||||
this.cta = await ReactComponents.getComponent('ChannelTextArea');
|
||||
MonkeyPatch('BD:Autocomplete', this.cta.component.prototype).after('render', this.channelTextAreaAfterRender.bind(this));
|
||||
this.initialized = true;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
*/
|
||||
|
||||
import { Utils, ClientLogger as Logger } from 'common';
|
||||
import { ReactComponents, Reflection, MonkeyPatch } from 'modules';
|
||||
import { Reflection, MonkeyPatch } from 'modules';
|
||||
import { VueInjector, Toasts } from 'ui';
|
||||
import CMGroup from './components/contextmenu/Group.vue';
|
||||
|
||||
|
|
|
@ -87,8 +87,7 @@ export default class extends Module {
|
|||
async patchNameTag() {
|
||||
if (this.PatchedNameTag) return this.PatchedNameTag;
|
||||
|
||||
const selector = Reflection.resolve('nameTag', 'username', 'discriminator', 'ownerIcon').selector;
|
||||
const NameTag = await ReactComponents.getComponent('NameTag', {selector});
|
||||
const NameTag = await ReactComponents.getComponent('NameTag');
|
||||
|
||||
this.PatchedNameTag = class extends NameTag.component {
|
||||
render() {
|
||||
|
|
|
@ -9,6 +9,7 @@ export * from './contextmenus';
|
|||
export { default as VueInjector } from './vueinjector';
|
||||
export { default as Reflection } from './reflection';
|
||||
|
||||
export { default as Autocomplete } from './autocomplete';
|
||||
export { default as ProfileBadges } from './profilebadges';
|
||||
export { default as ClassNormaliser } from './classnormaliser';
|
||||
|
||||
|
|
|
@ -35,12 +35,16 @@ const TEST_ARGS = () => {
|
|||
'client': path.resolve(_basePath, 'client', 'dist'),
|
||||
'core': path.resolve(_basePath, 'core', 'dist'),
|
||||
'data': path.resolve(_baseDataPath, 'data'),
|
||||
'editor': path.resolve(_basePath, 'editor', 'dist')
|
||||
'editor': path.resolve(_basePath, 'editor', 'dist'),
|
||||
// tmp: path.join(_basePath, 'tmp')
|
||||
tmp: path.join(os.tmpdir(), 'betterdiscord', `${process.getuid()}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
const TEST_EDITOR = TESTS && true;
|
||||
|
||||
import process from 'process';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import sass from 'node-sass';
|
||||
import { BrowserWindow as OriginalBrowserWindow, dialog, session, shell } from 'electron';
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
|
||||
import Module from './modulebase';
|
||||
import { FileUtils } from './utils';
|
||||
import semver from 'semver';
|
||||
|
|
|
@ -10,16 +10,18 @@ span {
|
|||
opacity: $spanOpacity2 !important;
|
||||
}
|
||||
|
||||
.chat .messages-wrapper,
|
||||
#friends .friends-table {
|
||||
.da-chat .da-messagesWrapper,
|
||||
.da-friendsTable {
|
||||
background-image: url(map-get($relative-file-test, url));
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.avatar-large {
|
||||
background-image: url(map-get($avatar, url)) !important;
|
||||
border-radius: $avatarRadius !important;
|
||||
@if map-has-key($avatar, url) {
|
||||
.da-avatar > .da-image {
|
||||
background-image: url(map-get($avatar, url)) !important;
|
||||
border-radius: $avatarRadius !important;
|
||||
}
|
||||
}
|
||||
|
||||
// Can't use a for loop as then we don't get the index
|
||||
|
|
Loading…
Reference in New Issue