Merge e49df8952f
into 835cc3134c
This commit is contained in:
commit
e1d0b138cf
|
@ -279,8 +279,6 @@ export default new class EmoteModule extends BuiltinModule {
|
|||
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.type === 'GROUP_DM' || DiscordApi.currentChannel.checkPermissions(DiscordApi.modules.DiscordPermissions.ATTACH_FILES));
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
"id": "developer-mode",
|
||||
"type": "bool",
|
||||
"text": "Developer mode",
|
||||
"hint": "Adds some of BetterDiscord's internal modules to `global._bd`.",
|
||||
"hint": "Adds some of BetterDiscord's internal modules to `global._bd` and enable additional options in plugin and theme settings.",
|
||||
"value": false
|
||||
},
|
||||
{
|
||||
|
|
|
@ -22,7 +22,7 @@ const ignoreExternal = tests && true;
|
|||
class BetterDiscord {
|
||||
|
||||
constructor() {
|
||||
Logger.file = tests ? path.resolve(__dirname, '..', '..', 'tests', 'log.txt') : path.join(__dirname, 'log.txt');
|
||||
Logger.file = tests ? path.resolve(__dirname, '..', '..', 'tests', 'log.txt') : `${__dirname}-log.txt`;
|
||||
Logger.trimLogFile();
|
||||
Logger.log('main', 'BetterDiscord starting');
|
||||
|
||||
|
|
|
@ -118,7 +118,7 @@ export default class Content extends AsyncEventEmitter {
|
|||
* @return {Promise}
|
||||
*/
|
||||
async enable(save = true) {
|
||||
if (this.enabled) return;
|
||||
if (this.enabled || this.unloaded) return;
|
||||
await this.emit('enable');
|
||||
await this.emit('start');
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ import { remote } from 'electron';
|
|||
import Content from './content';
|
||||
import Globals from './globals';
|
||||
import Database from './database';
|
||||
import { Utils, FileUtils, ClientLogger as Logger } from 'common';
|
||||
import { Utils, FileUtils, ClientLogger as Logger, ClientIPC } from 'common';
|
||||
import { SettingsSet, ErrorEvent } from 'structs';
|
||||
import { Modals } from 'ui';
|
||||
import Combokeys from 'combokeys';
|
||||
|
@ -73,23 +73,23 @@ export default class {
|
|||
}
|
||||
|
||||
static async packContent(path, contentPath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
remote.dialog.showSaveDialog({
|
||||
title: 'Save Package',
|
||||
defaultPath: path,
|
||||
filters: [
|
||||
{
|
||||
name: 'BetterDiscord Package',
|
||||
extensions: ['bd']
|
||||
}
|
||||
]
|
||||
}, filepath => {
|
||||
if (!filepath) return;
|
||||
const filepath = await ClientIPC.send('bd-native-save', {
|
||||
title: 'Save Package',
|
||||
defaultPath: path,
|
||||
filters: [
|
||||
{
|
||||
name: 'BetterDiscord Package',
|
||||
extensions: ['bd']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
asar.uncache(filepath);
|
||||
asar.createPackage(contentPath, filepath, () => {
|
||||
resolve(filepath);
|
||||
});
|
||||
if (!filepath) return;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
asar.uncache(filepath);
|
||||
asar.createPackage(contentPath, filepath, () => {
|
||||
resolve(filepath);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -104,13 +104,8 @@ export default class {
|
|||
const directories = await FileUtils.listDirectory(this.contentPath);
|
||||
|
||||
for (const dir of directories) {
|
||||
const packed = dir.endsWith('.bd');
|
||||
|
||||
if (!packed) {
|
||||
try {
|
||||
await FileUtils.directoryExists(path.join(this.contentPath, dir));
|
||||
} catch (err) { continue; }
|
||||
}
|
||||
const stat = await FileUtils.stat(path.join(this.contentPath, dir));
|
||||
const packed = stat.isFile();
|
||||
|
||||
try {
|
||||
if (packed) {
|
||||
|
@ -148,6 +143,8 @@ export default class {
|
|||
* @param {bool} suppressErrors Suppress any errors that occur during loading of content
|
||||
*/
|
||||
static async refreshContent(suppressErrors = false) {
|
||||
this.loaded = true;
|
||||
|
||||
if (!this.localContent.length) return this.loadAllContent();
|
||||
|
||||
try {
|
||||
|
@ -155,18 +152,18 @@ export default class {
|
|||
const directories = await FileUtils.listDirectory(this.contentPath);
|
||||
|
||||
for (const dir of directories) {
|
||||
const packed = dir.endsWith('.bd');
|
||||
|
||||
// If content is already loaded this should resolve
|
||||
if (this.getContentByDirName(dir)) continue;
|
||||
|
||||
try {
|
||||
await FileUtils.directoryExists(path.join(this.contentPath, dir));
|
||||
} catch (err) { continue; }
|
||||
const stat = await FileUtils.stat(path.join(this.contentPath, dir));
|
||||
const packed = stat.isFile();
|
||||
|
||||
try {
|
||||
// Load if not
|
||||
await this.preloadContent(dir);
|
||||
if (packed) {
|
||||
await this.preloadPackedContent(dir);
|
||||
} else {
|
||||
await this.preloadContent(dir);
|
||||
}
|
||||
} catch (err) {
|
||||
// We don't want every plugin/theme to fail loading when one does
|
||||
this.errors.push(new ErrorEvent({
|
||||
|
@ -217,7 +214,8 @@ export default class {
|
|||
await FileUtils.fileExists(packagePath);
|
||||
|
||||
const config = JSON.parse(asar.extractFile(packagePath, 'config.json').toString());
|
||||
const unpackedPath = path.join(Globals.getPath('tmp'), packageName);
|
||||
const id = config.info.id || config.info.name.toLowerCase().replace(/[^a-zA-Z0-9-]/g, '-').replace(/--/g, '-');
|
||||
const unpackedPath = path.join(Globals.getPath('tmp'), this.pathId, id);
|
||||
|
||||
asar.extractAll(packagePath, unpackedPath);
|
||||
|
||||
|
@ -348,7 +346,7 @@ export default class {
|
|||
await unload;
|
||||
|
||||
await FileUtils.recursiveDeleteDirectory(content.paths.contentPath);
|
||||
if (content.packed) await FileUtils.recursiveDeleteDirectory(content.packagePath);
|
||||
if (content.packed) await FileUtils.deleteFile(content.packagePath);
|
||||
return true;
|
||||
} catch (err) {
|
||||
Logger.err(this.moduleName, err);
|
||||
|
@ -368,6 +366,8 @@ export default class {
|
|||
if (!content) throw {message: `Could not find a ${this.contentType} from ${content}.`};
|
||||
|
||||
try {
|
||||
Object.defineProperty(content, 'unloaded', {configurable: true, value: true});
|
||||
|
||||
const disablePromise = content.disable(false);
|
||||
const unloadPromise = content.emit('unload', reload);
|
||||
|
||||
|
@ -380,8 +380,14 @@ export default class {
|
|||
|
||||
if (this.unloadContentHook) this.unloadContentHook(content);
|
||||
|
||||
if (reload) return content.packed ? this.preloadPackedContent(content.packagePath, true, index) : this.preloadContent(content.dirName, true, index);
|
||||
if (reload) {
|
||||
const newcontent = content.packed ? this.preloadPackedContent(content.dirName.pkg, true, index) :
|
||||
this.preloadContent(content.dirName, true, index);
|
||||
Object.defineProperty(content, 'unloaded', {value: newcontent});
|
||||
return newcontent;
|
||||
}
|
||||
|
||||
Object.defineProperty(content, 'unloaded', {value: true});
|
||||
this.localContent.splice(index, 1);
|
||||
} catch (err) {
|
||||
Logger.err(this.moduleName, err);
|
||||
|
@ -433,7 +439,7 @@ export default class {
|
|||
|
||||
static getContentIndex(content) { return this.localContent.findIndex(c => c === content) }
|
||||
static getContentById(id) { return this.localContent.find(c => c.id === id) }
|
||||
static getContentByDirName(dirName) { return this.localContent.find(c => c.dirName === dirName) }
|
||||
static getContentByDirName(dirName) { return this.localContent.find(c => !c.packed ? c.dirName === dirName : c.dirName.pkg === dirName) }
|
||||
static getContentByPath(path) { return this.localContent.find(c => c.contentPath === path) }
|
||||
static getContentByName(name) { return this.localContent.find(c => c.name === name) }
|
||||
|
||||
|
|
|
@ -190,15 +190,34 @@ class ReactComponent {
|
|||
}
|
||||
}
|
||||
|
||||
ReactComponent.important = Symbol('BD.ReactComponent.important');
|
||||
|
||||
export class ReactComponents {
|
||||
/** @type {ReactComponent[]} */
|
||||
static get components() { return this._components || (this._components = []) }
|
||||
|
||||
/** @type {Reflection.modules.React.Component[]} */
|
||||
static get unknownComponents() { return this._unknownComponents || (this._unknownComponents = []) }
|
||||
|
||||
/** @type {{id: string, listeners: function[]}[]} */
|
||||
static get listeners() { return this._listeners || (this._listeners = []) }
|
||||
|
||||
/** @type {<{name: string, filter: function}[]>} */
|
||||
static get nameSetters() { return this._nameSetters || (this._nameSetters = []) }
|
||||
static get componentAliases() { return this._componentAliases || (this._componentAliases = []) }
|
||||
|
||||
/** @type {Object.<string, string>} */
|
||||
static get componentAliases() { return this._componentAliases || (this._componentAliases = {}) }
|
||||
|
||||
static get ReactComponent() { return ReactComponent }
|
||||
|
||||
/**
|
||||
* Processes a React component.
|
||||
* @param {Reflection.modules.React.Component} component The React component class
|
||||
* @param {object} retVal
|
||||
* @param {object} important
|
||||
* @param {string} important.selector A query selector the component will render elements matching (used to select all component instances to force them to rerender)
|
||||
* @return {ReactComponent}
|
||||
*/
|
||||
static push(component, retVal, important) {
|
||||
if (!(component instanceof Function)) return null;
|
||||
const { displayName } = component;
|
||||
|
@ -212,6 +231,8 @@ export class ReactComponents {
|
|||
return component;
|
||||
}
|
||||
|
||||
if (!important) important = component[ReactComponent.important];
|
||||
|
||||
const c = new ReactComponent(displayName, component, retVal, important);
|
||||
this.components.push(c);
|
||||
|
||||
|
@ -226,16 +247,19 @@ export class ReactComponents {
|
|||
|
||||
/**
|
||||
* Finds a component from the components array or by waiting for it to be mounted.
|
||||
* @param {String} name The component's name
|
||||
* @param {Object} important An object containing a selector to look for
|
||||
* @param {Function} filter A function to filter components if a single element is rendered by multiple components
|
||||
* @return {Promise => ReactComponent}
|
||||
* @param {string} name The component's name
|
||||
* @param {object} important An object containing a selector to look for
|
||||
* @param {function} filter A function to filter components if a single element is rendered by multiple components
|
||||
* @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;
|
||||
if (have) {
|
||||
if (!have.important) have.important = important;
|
||||
return have;
|
||||
}
|
||||
|
||||
if (important) {
|
||||
const callback = () => {
|
||||
|
@ -262,7 +286,7 @@ export class ReactComponents {
|
|||
}
|
||||
|
||||
if (!component && filter) {
|
||||
Logger.log('ReactComponents', ['Found elements matching the query selector but no components passed the filter']);
|
||||
Logger.log('ReactComponents', ['Found elements matching the query selector but no components passed the filter', name, important, filter]);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -321,8 +345,12 @@ export class ReactComponents {
|
|||
return this.nameSetters.push({ name, filter });
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a React component that isn't known.
|
||||
* @param {Reflection.modules.React.Component} component
|
||||
* @param {} retVal
|
||||
*/
|
||||
static processUnknown(component, retVal) {
|
||||
const have = this.unknownComponents.find(c => c.component === component);
|
||||
for (const [fi, filter] of this.nameSetters.entries()) {
|
||||
if (filter.filter.filter(component)) {
|
||||
Logger.log('ReactComponents', 'Filter match!');
|
||||
|
@ -331,9 +359,8 @@ export class ReactComponents {
|
|||
return this.push(component, retVal);
|
||||
}
|
||||
}
|
||||
if (have) return have;
|
||||
this.unknownComponents.push(component);
|
||||
return component;
|
||||
|
||||
if (!this.unknownComponents.includes(component)) this.unknownComponents.push(component);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -441,13 +468,13 @@ export class ReactAutoPatcher {
|
|||
}
|
||||
|
||||
static async patchNameTag() {
|
||||
const { selector } = Reflection.resolve('nameTag', 'username', 'discriminator', 'ownerIcon');
|
||||
const { selector } = Reflection.resolve('nameTag', 'username', 'discriminator', 'bot');
|
||||
this.NameTag = await ReactComponents.getComponent('NameTag', {selector});
|
||||
}
|
||||
|
||||
static async patchGuild() {
|
||||
const selector = `div.${Reflection.resolve('container', 'guildIcon', 'selected', 'unread').className}:not(:first-child)`;
|
||||
this.Guild = await ReactComponents.getComponent('Guild', {selector}, m => m.prototype.renderBadge);
|
||||
const { selector } = Reflection.resolve('listItem', 'guildSeparator', 'selected', 'friendsOnline');
|
||||
this.Guild = await ReactComponents.getComponent('Guild', {selector}, m => m.displayName === 'Guild');
|
||||
|
||||
this.unpatchGuild = MonkeyPatch('BD:ReactComponents', this.Guild.component.prototype).after('render', (component, args, retVal) => {
|
||||
const { guild } = component.props;
|
||||
|
@ -505,7 +532,7 @@ export class ReactAutoPatcher {
|
|||
ReactComponents.componentAliases.GuildVoiceChannel = 'VoiceChannel';
|
||||
|
||||
const { selector } = Reflection.resolve('containerDefault', 'actionIcon');
|
||||
this.GuildVoiceChannel = await ReactComponents.getComponent('GuildVoiceChannel', {selector}, c => c.prototype.handleVoiceConnect);
|
||||
this.GuildVoiceChannel = await ReactComponents.getComponent('GuildVoiceChannel', {selector}, c => c.prototype.renderVoiceUsers);
|
||||
|
||||
this.unpatchGuildVoiceChannel = MonkeyPatch('BD:ReactComponents', this.GuildVoiceChannel.component.prototype).after('render', this._afterChannelRender);
|
||||
|
||||
|
|
|
@ -22,10 +22,20 @@ export default class Theme extends Content {
|
|||
|
||||
const watchfiles = Settings.getSetting('css', 'default', 'watch-files');
|
||||
if (watchfiles.value) this.watchfiles = this.files;
|
||||
watchfiles.on('setting-updated', event => {
|
||||
|
||||
watchfiles.on('setting-updated', this.__watchFilesSettingUpdated = event => {
|
||||
if (event.value) this.watchfiles = this.files;
|
||||
else this.watchfiles = [];
|
||||
});
|
||||
|
||||
this.on('unload', () => {
|
||||
watchfiles.off('setting-updated', this.__watchFilesSettingUpdated);
|
||||
|
||||
if (this._filewatcher) {
|
||||
this._filewatcher.removeAll();
|
||||
delete this._filewatcher;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get type() { return 'theme' }
|
||||
|
@ -61,7 +71,7 @@ export default class Theme extends Content {
|
|||
async compile() {
|
||||
Logger.log(this.name, 'Compiling CSS');
|
||||
|
||||
if (this.info.type === 'sass') {
|
||||
if (this.info.type === 'sass' || this.info.type === 'scss') {
|
||||
const config = await ThemeManager.getConfigAsSCSS(this.settings);
|
||||
|
||||
const result = await ClientIPC.send('bd-compileSass', {
|
||||
|
@ -147,7 +157,7 @@ export default class Theme extends Content {
|
|||
* @param {Array} files Files to watch
|
||||
*/
|
||||
set watchfiles(files) {
|
||||
if (this.packed) {
|
||||
if (this.unloaded || this.packed) {
|
||||
// Don't watch files for packed themes
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ export default class ThemeManager extends ContentManager {
|
|||
});
|
||||
if (instance.enabled) {
|
||||
instance.userConfig.enabled = false;
|
||||
instance.enable();
|
||||
instance.enable(false);
|
||||
}
|
||||
return instance;
|
||||
} catch (err) {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
.bd-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
background: transparent;
|
||||
border-bottom: 1px solid rgba(114, 118, 126, .3);
|
||||
min-height: 150px;
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
.bd-formCollection {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
div {
|
||||
&:first-child {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.bd-collectionItem {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
margin-top: 5px;
|
||||
|
||||
.bd-removeCollectionItem {
|
||||
width: 20px;
|
||||
flex: 0 1 auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
margin-bottom: 30px;
|
||||
|
||||
&:hover {
|
||||
svg {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
fill: #ccc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bd-newCollectionItem {
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
align-self: flex-end;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 2px;
|
||||
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
fill: #ccc;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
svg {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +1,17 @@
|
|||
.bd-pluginsview,
|
||||
.bd-themesview {
|
||||
.bd-localPh {
|
||||
.bd-scroller {
|
||||
padding: 0 20px 0 0;
|
||||
}
|
||||
}
|
||||
flex-grow: 1;
|
||||
|
||||
.bd-onlinePh,
|
||||
.bd-localPh {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 10px 0;
|
||||
flex-grow: 1;
|
||||
|
||||
> .bd-scrollerWrap > .bd-scroller {
|
||||
padding: 0 20px 0 0;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.bd-spinnerContainer {
|
||||
display: flex;
|
||||
|
@ -19,8 +20,9 @@
|
|||
|
||||
.bd-onlinePhHeader {
|
||||
display: flex;
|
||||
padding: 0 20px 0 10px;
|
||||
padding: 0 20px 0 0;
|
||||
min-height: 80px;
|
||||
margin-top: 20px;
|
||||
|
||||
.bd-flexRow {
|
||||
min-height: 40px;
|
||||
|
@ -81,16 +83,10 @@
|
|||
}
|
||||
|
||||
.bd-onlinePhBody {
|
||||
margin-top: 10px;
|
||||
|
||||
.bd-spinnerContainer {
|
||||
min-height: 40px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.bd-scroller {
|
||||
padding: 0 20px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
|
|
|
@ -8,6 +8,5 @@
|
|||
@import './updater';
|
||||
@import './window-preferences';
|
||||
@import './kvp';
|
||||
@import './collection';
|
||||
@import './e2ee';
|
||||
@import './devview';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.bd-remoteCard {
|
||||
flex-direction: column;
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
padding: 10px 0;
|
||||
border-radius: 0;
|
||||
border-bottom: 1px solid rgba(114, 118, 126, .3);
|
||||
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
// sass-lint:disable-all
|
||||
body:not(.bd-hideButton) {
|
||||
[class*='layer-'] > * > [class*='wrapper-'] {
|
||||
[class*='layers-'] > [class*='layer-'] > * > [class*='wrapper-'] {
|
||||
padding-top: 49px !important;
|
||||
}
|
||||
.platform-osx [class*='layer-'] > * > [class*='wrapper-'] {
|
||||
.platform-osx [class*='layers-'] > [class*='layer-'] > * > [class*='wrapper-'] {
|
||||
margin-top: 26px;
|
||||
}
|
||||
|
||||
[class*='layer-'] > * > [class*='wrapper-'] + [class*='flex'] {
|
||||
.platform-osx [class*='layers-'] > [class*='layer-'] > * > [class*='wrapper-'] [class*='listItem-']:first-child {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
[class*='layers-'] > [class*='layer-'] > * > [class*='wrapper-'] + [class*='flex'] {
|
||||
border-radius: 0 0 0 5px;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
.bd-scrollerWrap {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
|
||||
.bd-scroller {
|
||||
@include scrollbar;
|
||||
|
|
|
@ -17,18 +17,13 @@
|
|||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
|
||||
> .bd-scrollerWrap {
|
||||
flex-grow: 1;
|
||||
|
||||
> .bd-scroller {
|
||||
overflow-y: scroll;
|
||||
}
|
||||
> .bd-scrollerWrap > .bd-scroller {
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.bd-settings & {
|
||||
.platform-darwin & { // sass-lint:disable-line class-name-format
|
||||
padding-top: 22px;
|
||||
}
|
||||
.platform-darwin .bd-settings &.bd-settingswrapNoscroller, // sass-lint:disable-line class-name-format
|
||||
.platform-darwin .bd-settings & > .bd-scrollerWrap > .bd-scroller { // sass-lint:disable-line class-name-format
|
||||
padding-top: 22px;
|
||||
}
|
||||
|
||||
.bd-settingswrapHeader {
|
||||
|
|
|
@ -12,12 +12,14 @@ import { Module, Reflection } from 'modules';
|
|||
|
||||
const normalizedPrefix = 'da';
|
||||
const randClass = new RegExp(`^(?!${normalizedPrefix}-)((?:[A-Za-z]|[0-9]|-)+)-(?:[A-Za-z]|[0-9]|-|_){6}$`);
|
||||
const normalisedClass = /^(([a-zA-Z0-9]+)-[^\s]{6}) da-([a-zA-Z0-9]+)$/;
|
||||
|
||||
export default class ClassNormaliser extends Module {
|
||||
|
||||
init() {
|
||||
this.patchClassModules(Reflection.module.getModule(this.moduleFilter.bind(this), false));
|
||||
this.normalizeElement(document.querySelector('#app-mount'));
|
||||
this.patchDOMMethods();
|
||||
}
|
||||
|
||||
patchClassModules(modules) {
|
||||
|
@ -26,6 +28,36 @@ export default class ClassNormaliser extends Module {
|
|||
}
|
||||
}
|
||||
|
||||
patchDOMMethods() {
|
||||
const add = DOMTokenList.prototype.add;
|
||||
|
||||
DOMTokenList.prototype.add = function(...tokens) {
|
||||
for (const token of tokens) {
|
||||
let match;
|
||||
if (typeof token === 'string' && (match = token.match(normalisedClass)) && match[2] === match[3]) return add.call(this, match[1]);
|
||||
return add.call(this, token);
|
||||
}
|
||||
};
|
||||
|
||||
const remove = DOMTokenList.prototype.remove;
|
||||
|
||||
DOMTokenList.prototype.remove = function(...tokens) {
|
||||
for (const token of tokens) {
|
||||
let match;
|
||||
if (typeof token === 'string' && (match = token.match(normalisedClass)) && match[2] === match[3]) return remove.call(this, match[1]);
|
||||
return remove.call(this, token);
|
||||
}
|
||||
};
|
||||
|
||||
const contains = DOMTokenList.prototype.contains;
|
||||
|
||||
DOMTokenList.prototype.contains = function(token) {
|
||||
let match;
|
||||
if (typeof token === 'string' && (match = token.match(normalisedClass)) && match[2] === match[3]) return contains.call(this, match[1]);
|
||||
return contains.call(this, token);
|
||||
};
|
||||
}
|
||||
|
||||
shouldIgnore(value) {
|
||||
if (!isNaN(value)) return true;
|
||||
if (value.endsWith('px') || value.endsWith('ch') || value.endsWith('em') || value.endsWith('ms')) return true;
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<Button v-if="devmode && !plugin.packed" v-tooltip="'Package Plugin'" @click="package"><MiBoxDownload size="18"/></Button>
|
||||
<Button v-tooltip="'Settings (shift + click to open settings without cloning the set)'" v-if="plugin.hasSettings" @click="$emit('show-settings', $event.shiftKey)"><MiSettings size="18" /></Button>
|
||||
<Button v-tooltip="'Reload'" @click="$emit('reload-plugin')"><MiRefresh size="18" /></Button>
|
||||
<Button v-tooltip="'Edit'" @click="editPlugin"><MiPencil size="18" /></Button>
|
||||
<Button v-if="devmode && !plugin.packed" v-tooltip="'Edit'" @click="editPlugin"><MiPencil size="18" /></Button>
|
||||
<Button v-tooltip="'Uninstall (shift + click to unload)'" @click="$emit('delete-plugin', $event.shiftKey)" type="err"><MiDelete size="18" /></Button>
|
||||
</ButtonGroup>
|
||||
</Card>
|
||||
|
@ -33,14 +33,19 @@
|
|||
export default {
|
||||
data() {
|
||||
return {
|
||||
devmode: Settings.getSetting('core', 'advanced', 'developer-mode').value
|
||||
}
|
||||
devmodeSetting: Settings.getSetting('core', 'advanced', 'developer-mode')
|
||||
};
|
||||
},
|
||||
props: ['plugin'],
|
||||
components: {
|
||||
Card, Button, ButtonGroup, SettingSwitch,
|
||||
MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension, MiBoxDownload
|
||||
},
|
||||
computed: {
|
||||
devmode() {
|
||||
return this.devmodeSetting.value;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async package() {
|
||||
try {
|
||||
|
|
|
@ -34,9 +34,11 @@
|
|||
<div class="bd-spinner7" />
|
||||
</div>
|
||||
<div class="bd-searchHint">{{searchHint}}</div>
|
||||
<div class="bd-fancySearch" :class="{'bd-disabled': loadingOnline, 'bd-active': loadingOnline || (onlinePlugins && onlinePlugins.docs)}">
|
||||
<input type="text" class="bd-textInput" placeholder="Search" @keydown.enter="searchInput" @keyup.stop :value="onlinePlugins.filters.sterm" />
|
||||
</div>
|
||||
<form @submit.prevent="refreshOnline">
|
||||
<div class="bd-fancySearch" :class="{'bd-disabled': loadingOnline, 'bd-active': loadingOnline || (onlinePlugins && onlinePlugins.docs)}">
|
||||
<input type="text" class="bd-textInput" placeholder="Search" v-model="onlinePlugins.filters.sterm" :disabled="loadingOnline" @input="search" @keyup.stop/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="bd-flex bd-flexRow" v-if="onlinePlugins && onlinePlugins.docs && onlinePlugins.docs.length">
|
||||
<div class="bd-searchSort bd-flex bd-flexGrow">
|
||||
|
@ -49,11 +51,13 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ScrollerWrap class="bd-onlinePhBody" v-if="!loadingOnline && onlinePlugins" :scrollend="scrollend">
|
||||
<RemoteCard v-if="onlinePlugins && onlinePlugins.docs" v-for="plugin in onlinePlugins.docs" :key="onlinePlugins.id" :item="plugin" :tagClicked="searchByTag" />
|
||||
<div class="bd-spinnerContainer">
|
||||
<div v-if="loadingMore" class="bd-spinner7" />
|
||||
</div>
|
||||
<ScrollerWrap class="bd-onlinePhBody" @scrollend="scrollend">
|
||||
<template v-if="!loadingOnline && onlinePlugins">
|
||||
<RemoteCard v-if="onlinePlugins && onlinePlugins.docs" v-for="plugin in onlinePlugins.docs" :key="onlinePlugins.id" :item="plugin" @tagclicked="searchByTag" />
|
||||
<div class="bd-spinnerContainer">
|
||||
<div v-if="loadingMore" class="bd-spinner7" />
|
||||
</div>
|
||||
</template>
|
||||
</ScrollerWrap>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -94,7 +98,8 @@
|
|||
},
|
||||
loadingOnline: false,
|
||||
loadingMore: false,
|
||||
searchHint: ''
|
||||
searchHint: '',
|
||||
searchTimeout: null
|
||||
};
|
||||
},
|
||||
components: {
|
||||
|
@ -107,8 +112,9 @@
|
|||
showLocal() {
|
||||
this.local = true;
|
||||
},
|
||||
showOnline() {
|
||||
async showOnline() {
|
||||
this.local = false;
|
||||
if (!this.onlinePlugins.pagination.total) await this.refreshOnline();
|
||||
},
|
||||
async refreshLocal() {
|
||||
await this.PluginManager.refreshPlugins();
|
||||
|
@ -156,11 +162,6 @@
|
|||
dont_clone
|
||||
});
|
||||
},
|
||||
searchInput(e) {
|
||||
if (this.loadingOnline || this.loadingMore) return;
|
||||
this.onlinePlugins.filters.sterm = e.target.value;
|
||||
this.refreshOnline();
|
||||
},
|
||||
async scrollend(e) {
|
||||
if (this.onlinePlugins.pagination.page >= this.onlinePlugins.pagination.pages) return;
|
||||
if (this.loadingOnline || this.loadingMore) return;
|
||||
|
@ -193,6 +194,10 @@
|
|||
}
|
||||
this.refreshOnline();
|
||||
},
|
||||
async search() {
|
||||
clearTimeout(this.searchTimeout);
|
||||
this.searchTimeout = setTimeout(this.refreshOnline, 1000);
|
||||
},
|
||||
async searchByTag(tag) {
|
||||
if (this.loadingOnline || this.loadingMore) return;
|
||||
this.onlinePlugins.filters.sterm = tag;
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<div class="bd-flexRow bd-flex bd-flexGrow">
|
||||
<div class="bd-flexGrow bd-remoteCardTags">
|
||||
<div v-for="(tag, index) in item.tags" class="bd-remoteCardTag">
|
||||
<div @click="tagClicked(tag)">{{tag}}</div><span v-if="index + 1 < item.tags.length">, </span>
|
||||
<div @click="$emit('tagclicked', tag)">{{tag}}</div><span v-if="index + 1 < item.tags.length">, </span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bd-buttonGroup">
|
||||
|
@ -44,7 +44,7 @@
|
|||
import { shell } from 'electron';
|
||||
|
||||
export default {
|
||||
props: ['item', 'tagClicked'],
|
||||
props: ['item'],
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
*/
|
||||
|
||||
<template>
|
||||
<div class="bd-settingswrap">
|
||||
<div v-if="noscroller" class="bd-flex bd-flexCol">
|
||||
<div class="bd-settingswrap" :class="{'bd-settingswrapNoscroller': noscroller}">
|
||||
<div v-if="noscroller" class="bd-flex bd-flexCol bd-flexGrow">
|
||||
<div class="bd-settingswrapHeader">
|
||||
<span class="bd-settingswrapHeaderText">{{ headertext }}</span>
|
||||
<slot name="header" />
|
||||
|
@ -19,7 +19,7 @@
|
|||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
<ScrollerWrap v-else :scrollend="scrollend">
|
||||
<ScrollerWrap v-else @scrollend="$emit('scrollend', $event)">
|
||||
<div class="bd-settingswrapHeader">
|
||||
<span class="bd-settingswrapHeaderText">{{ headertext }}</span>
|
||||
<slot name="header" />
|
||||
|
@ -36,7 +36,7 @@
|
|||
import { ScrollerWrap } from '../common';
|
||||
|
||||
export default {
|
||||
props: ['headertext', 'scrollend', 'noscroller'],
|
||||
props: ['headertext', 'noscroller'],
|
||||
components: {
|
||||
ScrollerWrap
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<Button v-if="devmode && !theme.packed" v-tooltip="'Package Theme'" @click="package"><MiBoxDownload size="18" /></Button>
|
||||
<Button v-tooltip="'Settings (shift + click to open settings without cloning the set)'" v-if="theme.hasSettings" @click="$emit('show-settings', $event.shiftKey)"><MiSettings size="18" /></Button>
|
||||
<Button v-tooltip="'Recompile (shift + click to reload)'" @click="$emit('reload-theme', $event.shiftKey)"><MiRefresh size="18" /></Button>
|
||||
<Button v-tooltip="'Edit'" @click="editTheme"><MiPencil size="18" /></Button>
|
||||
<Button v-if="devmode && !theme.packed" v-tooltip="'Edit'" @click="editTheme"><MiPencil size="18" /></Button>
|
||||
<Button v-tooltip="'Uninstall (shift + click to unload)'" @click="$emit('delete-theme', $event.shiftKey)" type="err"><MiDelete size="18" /></Button>
|
||||
</ButtonGroup>
|
||||
</Card>
|
||||
|
@ -33,14 +33,19 @@
|
|||
export default {
|
||||
data() {
|
||||
return {
|
||||
devmode: Settings.getSetting('core', 'advanced', 'developer-mode').value
|
||||
}
|
||||
devmodeSetting: Settings.getSetting('core', 'advanced', 'developer-mode')
|
||||
};
|
||||
},
|
||||
props: ['theme', 'online'],
|
||||
components: {
|
||||
Card, Button, ButtonGroup, SettingSwitch,
|
||||
MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension, MiBoxDownload
|
||||
},
|
||||
computed: {
|
||||
devmode() {
|
||||
return this.devmodeSetting.value;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async package() {
|
||||
try {
|
||||
|
|
|
@ -34,9 +34,11 @@
|
|||
<div class="bd-spinner7" />
|
||||
</div>
|
||||
<div class="bd-searchHint">{{searchHint}}</div>
|
||||
<div class="bd-fancySearch" :class="{'bd-disabled': loadingOnline, 'bd-active': loadingOnline || (onlineThemes && onlineThemes.docs)}">
|
||||
<input type="text" class="bd-textInput" placeholder="Search" @keydown.enter="searchInput" @keyup.stop :value="onlineThemes.filters.sterm"/>
|
||||
</div>
|
||||
<form @submit.prevent="refreshOnline">
|
||||
<div class="bd-fancySearch" :class="{'bd-disabled': loadingOnline, 'bd-active': loadingOnline || (onlineThemes && onlineThemes.docs)}">
|
||||
<input type="text" class="bd-textInput" placeholder="Search" v-model="onlineThemes.filters.sterm" :disabled="loadingOnline" @input="search" @keyup.stop/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="bd-flex bd-flexRow" v-if="onlineThemes && onlineThemes.docs && onlineThemes.docs.length">
|
||||
<div class="bd-searchSort bd-flex bd-flexGrow">
|
||||
|
@ -48,11 +50,13 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ScrollerWrap class="bd-onlinePhBody" v-if="!loadingOnline && onlineThemes" :scrollend="scrollend">
|
||||
<RemoteCard v-if="onlineThemes && onlineThemes.docs" v-for="theme in onlineThemes.docs" :key="theme.id" :item="theme" :tagClicked="searchByTag"/>
|
||||
<div class="bd-spinnerContainer">
|
||||
<div v-if="loadingMore" class="bd-spinner7"/>
|
||||
</div>
|
||||
<ScrollerWrap class="bd-onlinePhBody" @scrollend="scrollend">
|
||||
<template v-if="!loadingOnline && onlineThemes">
|
||||
<RemoteCard v-if="onlineThemes && onlineThemes.docs" v-for="theme in onlineThemes.docs" :key="theme.id" :item="theme" @tagclicked="searchByTag"/>
|
||||
<div class="bd-spinnerContainer">
|
||||
<div v-if="loadingMore" class="bd-spinner7"/>
|
||||
</div>
|
||||
</template>
|
||||
</ScrollerWrap>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -93,7 +97,8 @@
|
|||
},
|
||||
loadingOnline: false,
|
||||
loadingMore: false,
|
||||
searchHint: ''
|
||||
searchHint: '',
|
||||
searchTimeout: null
|
||||
};
|
||||
},
|
||||
components: {
|
||||
|
@ -108,6 +113,7 @@
|
|||
},
|
||||
async showOnline() {
|
||||
this.local = false;
|
||||
if (!this.onlineThemes.pagination.total) await this.refreshOnline();
|
||||
},
|
||||
async refreshLocal() {
|
||||
await this.ThemeManager.refreshThemes();
|
||||
|
@ -154,11 +160,6 @@
|
|||
dont_clone
|
||||
});
|
||||
},
|
||||
searchInput(e) {
|
||||
if (this.loadingOnline || this.loadingMore) return;
|
||||
this.onlineThemes.filters.sterm = e.target.value;
|
||||
this.refreshOnline();
|
||||
},
|
||||
async scrollend(e) {
|
||||
if (this.onlineThemes.pagination.page >= this.onlineThemes.pagination.pages) return;
|
||||
if (this.loadingOnline || this.loadingMore) return;
|
||||
|
@ -191,6 +192,10 @@
|
|||
}
|
||||
this.refreshOnline();
|
||||
},
|
||||
async search() {
|
||||
clearTimeout(this.searchTimeout);
|
||||
this.searchTimeout = setTimeout(this.refreshOnline, 1000);
|
||||
},
|
||||
async searchByTag(tag) {
|
||||
if (this.loadingOnline || this.loadingMore) return;
|
||||
this.onlineThemes.filters.sterm = tag;
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<div class="bd-settingSwitch">
|
||||
<div class="bd-title">
|
||||
<h3>{{item.text}}</h3>
|
||||
<div class="bd-switchWrapper" @click="() => toggle(item)">
|
||||
<div class="bd-switchWrapper" @click="$emit('toggle', item)">
|
||||
<input type="checkbox" class="bd-switchCheckbox" />
|
||||
<div class="bd-switch" :class="{'bd-checked': item.status.update}" />
|
||||
</div>
|
||||
|
@ -23,6 +23,6 @@
|
|||
|
||||
<script>
|
||||
export default {
|
||||
props: ['item', 'toggle']
|
||||
props: ['item']
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<div class="bd-formDivider"></div>
|
||||
<div v-for="update in bdUpdates">
|
||||
<UpdaterStatus :item="update" v-if="update.status.updating" />
|
||||
<UpdaterToggle :item="update" :toggle="() => updater.toggleUpdate(update)" v-else />
|
||||
<UpdaterToggle :item="update" @toggle="updater.toggleUpdate(update)" v-else />
|
||||
<div class="bd-formDivider"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
},
|
||||
removeItem(file_path) {
|
||||
if (this.setting.disabled) return;
|
||||
this.setting.value = Utils.removeFromArray(this.setting.value, file_path);
|
||||
this.setting.value = this.setting.value.filter(f => f !== file_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,13 +10,13 @@
|
|||
|
||||
<template>
|
||||
<div class="bd-dropdown" :class="{'bd-active': active, 'bd-disabled': disabled}">
|
||||
<div class="bd-dropdownCurrent" @click="() => active = !active && !disabled">
|
||||
<span class="bd-dropdownText">{{ getSelectedText() }}</span>
|
||||
<div class="bd-dropdownCurrent" @click.stop="() => active = !active && !disabled">
|
||||
<span class="bd-dropdownText">{{ selectedText }}</span>
|
||||
<span class="bd-dropdownArrowWrap">
|
||||
<span class="bd-dropdownArrow"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="bd-dropdownOptions bd-flex bd-flexCol" ref="options" v-if="active">
|
||||
<div class="bd-dropdownOptions bd-flex bd-flexCol" ref="options" v-if="active && !disabled">
|
||||
<div class="bd-dropdownOption" v-for="option in options" :class="{'bd-dropdownOptionSelected': value === option.value}" @click="select(option)">{{ option.text }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -31,11 +31,15 @@
|
|||
clickHandler: null
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getSelectedText() {
|
||||
const selected_option = this.options.find(option => option.value === this.value);
|
||||
return selected_option ? selected_option.text : this.value;
|
||||
computed: {
|
||||
selectedOption() {
|
||||
return this.options.find(option => option.value === this.value);
|
||||
},
|
||||
selectedText() {
|
||||
return this.selectedOption ? this.selectedOption.text : this.value;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
select(option) {
|
||||
this.$emit('input', option.value);
|
||||
this.active = false;
|
||||
|
|
|
@ -18,12 +18,11 @@
|
|||
|
||||
<script>
|
||||
export default {
|
||||
props: ['dark', 'scrollend'],
|
||||
props: ['dark'],
|
||||
methods: {
|
||||
onscroll(e) {
|
||||
if (!this.scrollend) return;
|
||||
const { offsetHeight, scrollTop, scrollHeight } = e.target;
|
||||
if (offsetHeight + scrollTop >= scrollHeight) this.scrollend(e);
|
||||
if (offsetHeight + scrollTop >= scrollHeight) this.$emit('scrollend', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,6 @@
|
|||
|
||||
<script>
|
||||
export default {
|
||||
props: ['item', 'onClick', 'checked']
|
||||
props: ['item', 'checked']
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -9,13 +9,13 @@
|
|||
*/
|
||||
|
||||
<template>
|
||||
<div class="bd-button" :class="classes" @click="onClick">
|
||||
<div class="bd-button" @click="$emit('click')">
|
||||
{{text}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['classes', 'text', 'onClick']
|
||||
props: ['text']
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -9,15 +9,15 @@
|
|||
*/
|
||||
|
||||
<template>
|
||||
<div class="bd-buttonGroup" :class="classes">
|
||||
<Button v-for="(button, index) in buttons" :text="button.text" :classes="button.classes" :onClick="button.onClick" :key="index"/>
|
||||
<div class="bd-buttonGroup">
|
||||
<Button v-for="(button, index) in buttons" :text="button.text" :classes="button.class" @click="button.onClick" :key="index"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Button from './Button.vue';
|
||||
export default {
|
||||
props: ['buttons', 'classes'],
|
||||
props: ['buttons'],
|
||||
components: { Button }
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -84,7 +84,7 @@ export class DiscordContextMenu {
|
|||
}
|
||||
|
||||
static renderCm(component, args, retVal, res) {
|
||||
if (!retVal.props || !res.props) return;
|
||||
if (!retVal.props || !retVal.props.style || !res.props) return;
|
||||
const { target } = component.props;
|
||||
const { top, left } = retVal.props.style;
|
||||
if (!target || !top || !left) return;
|
||||
|
|
|
@ -71,7 +71,7 @@ export default class extends Module {
|
|||
const c = contributors.find(c => c.id === user.id);
|
||||
if (!c) return;
|
||||
|
||||
const nameTag = retVal.props.children.props.children[1].props.children[0];
|
||||
const nameTag = retVal.props.children[1].props.children[0].props.children[0];
|
||||
nameTag.type = this.PatchedNameTag || nameTag.type;
|
||||
});
|
||||
|
||||
|
@ -89,26 +89,24 @@ export default class extends Module {
|
|||
|
||||
const NameTag = await ReactComponents.getComponent('NameTag');
|
||||
|
||||
this.PatchedNameTag = class extends NameTag.component {
|
||||
render() {
|
||||
const retVal = NameTag.component.prototype.render.call(this, arguments);
|
||||
try {
|
||||
if (!retVal.props || !retVal.props.children) return;
|
||||
this.PatchedNameTag = function (props) {
|
||||
const retVal = NameTag.component.apply(this, arguments);
|
||||
try {
|
||||
if (!retVal.props || !retVal.props.children) return retVal;
|
||||
|
||||
const user = ReactHelpers.findProp(this, 'user');
|
||||
if (!user) return;
|
||||
const contributor = contributors.find(c => c.id === user.id);
|
||||
if (!contributor) return;
|
||||
const user = ReactHelpers.findProp(props, 'user');
|
||||
if (!user) return retVal;
|
||||
const contributor = contributors.find(c => c.id === user.id);
|
||||
if (!contributor) return retVal;
|
||||
|
||||
retVal.props.children.splice(1, 0, VueInjector.createReactElement(BdBadge, {
|
||||
contributor,
|
||||
type: 'nametag'
|
||||
}));
|
||||
} catch (err) {
|
||||
Logger.err('ProfileBadges', ['Error thrown while rendering a NameTag', err]);
|
||||
}
|
||||
return retVal;
|
||||
retVal.props.children.splice(1, 0, VueInjector.createReactElement(BdBadge, {
|
||||
contributor,
|
||||
type: 'nametag'
|
||||
}));
|
||||
} catch (err) {
|
||||
Logger.err('ProfileBadges', ['Error thrown while rendering a NameTag', err]);
|
||||
}
|
||||
return retVal;
|
||||
};
|
||||
|
||||
// Rerender all channel members
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { Reflection } from 'modules';
|
||||
import { Reflection, ReactComponents } from 'modules';
|
||||
import Vue from 'vue';
|
||||
|
||||
export default class {
|
||||
|
@ -16,12 +16,12 @@ export default class {
|
|||
/**
|
||||
* Creates a new Vue object and mounts it in the passed element.
|
||||
* @param {HTMLElement} root The element to mount the new Vue object at
|
||||
* @param {Object} options Options to pass to Vue
|
||||
* @param {Object} options Options to pass to Vue (see https://vuejs.org/v2/api/#Options-Data)
|
||||
* @param {BdNode} bdnode The element to append
|
||||
* @return {Vue}
|
||||
*/
|
||||
static inject(root, options, bdnode) {
|
||||
if(bdnode) bdnode.appendTo(root);
|
||||
if (bdnode) bdnode.appendTo(root);
|
||||
|
||||
const vue = new Vue(options);
|
||||
|
||||
|
@ -37,18 +37,18 @@ export default class {
|
|||
* @return {React.Element}
|
||||
*/
|
||||
static createReactElement(component, props, mountAtTop) {
|
||||
const { React } = Reflection.modules;
|
||||
return React.createElement(this.ReactCompatibility, {component, mountAtTop, props});
|
||||
return Reflection.modules.React.createElement(this.ReactCompatibility, {component, mountAtTop, props});
|
||||
}
|
||||
|
||||
static get ReactCompatibility() {
|
||||
if (this._ReactCompatibility) return this._ReactCompatibility;
|
||||
const { React, ReactDOM } = Reflection.modules;
|
||||
|
||||
const { React, ReactDOM} = Reflection.modules;
|
||||
|
||||
return this._ReactCompatibility = class VueComponent extends React.Component {
|
||||
/**
|
||||
* A React component that renders a Vue component.
|
||||
*/
|
||||
const ReactCompatibility = class VueComponent extends React.Component {
|
||||
render() {
|
||||
return React.createElement('span');
|
||||
return React.createElement('span', {className: 'bd-reactVueComponent'});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -89,7 +89,13 @@ export default class {
|
|||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Add a name for ReactComponents
|
||||
ReactCompatibility.displayName = 'BD.VueComponent';
|
||||
ReactCompatibility[ReactComponents.ReactComponent.important] = {selector: '.bd-reactVueComponent'};
|
||||
|
||||
return Object.defineProperty(this, 'ReactCompatibility', {value: ReactCompatibility}).ReactCompatibility;
|
||||
}
|
||||
|
||||
static install(Vue) {
|
||||
|
@ -98,6 +104,9 @@ export default class {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* A Vue component that renders a React component.
|
||||
*/
|
||||
export const ReactComponent = {
|
||||
props: ['component', 'component-props', 'component-children', 'react-element'],
|
||||
render(createElement) {
|
||||
|
|
|
@ -17,7 +17,7 @@ const config = {
|
|||
})
|
||||
],
|
||||
externals: {
|
||||
sparkplug: 'require("./sparkplug")'
|
||||
sparkplug: 'require("../core/sparkplug")'
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -37,7 +37,6 @@ const TEST_ARGS = () => {
|
|||
'data': path.resolve(_baseDataPath, 'data'),
|
||||
'editor': path.resolve(_basePath, 'editor', 'dist'),
|
||||
// tmp: path.join(_basePath, 'tmp')
|
||||
tmp: path.join(os.tmpdir(), 'betterdiscord', `${process.getuid()}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -83,6 +82,12 @@ class Comms {
|
|||
});
|
||||
});
|
||||
|
||||
BDIpc.on('bd-native-save', (event, options) => {
|
||||
dialog.showSaveDialog(OriginalBrowserWindow.fromWebContents(event.ipcEvent.sender), options, filename => {
|
||||
event.reply(filename);
|
||||
});
|
||||
});
|
||||
|
||||
BDIpc.on('bd-compileSass', (event, options) => {
|
||||
if (typeof options.path === 'string' && typeof options.data === 'string') {
|
||||
options.data = `${options.data} @import '${options.path.replace(/\\/g, '\\\\').replace(/'/g, '\\\'')}';`;
|
||||
|
@ -103,7 +108,7 @@ class Comms {
|
|||
BDIpc.on('bd-keytar-find-credentials', (event, { service }) => keytar.findCredentials(service), true);
|
||||
|
||||
BDIpc.on('bd-readDataFile', async (event, fileName) => {
|
||||
const rf = await FileUtils.readFile(path.resolve(configProxy().getPath('data'), fileName));
|
||||
const rf = await FileUtils.readFile(path.resolve(this.bd.config.getPath('data'), fileName));
|
||||
event.reply(rf);
|
||||
});
|
||||
|
||||
|
@ -197,6 +202,11 @@ class BrowserWindow extends OriginalBrowserWindow {
|
|||
}
|
||||
}
|
||||
|
||||
// Some Electron APIs depend on browserWindow.constructor being BrowserWindow
|
||||
Object.defineProperty(BrowserWindow.prototype, 'constructor', {
|
||||
value: OriginalBrowserWindow
|
||||
});
|
||||
|
||||
export class BetterDiscord {
|
||||
|
||||
get comms() { return this._comms ? this._comms : (this._commas = new Comms(this)); }
|
||||
|
@ -207,9 +217,13 @@ export class BetterDiscord {
|
|||
get updater() { return this._updater ? this._updater : (this._updater = new Updater(this)); }
|
||||
get sendToDiscord() { return this.windowUtils.send; }
|
||||
|
||||
constructor(args) {
|
||||
if (TESTS) args = TEST_ARGS();
|
||||
constructor(...args) {
|
||||
if (TESTS) args.unshift(TEST_ARGS());
|
||||
|
||||
args = deepmerge.all(args);
|
||||
|
||||
console.log('[BetterDiscord|args] ', JSON.stringify(args, null, 4));
|
||||
|
||||
if (BetterDiscord.loaded) {
|
||||
console.log('[BetterDiscord] Creating two BetterDiscord objects???');
|
||||
return null;
|
||||
|
@ -337,6 +351,10 @@ export class BetterDiscord {
|
|||
this.config.addPath('userfiles', userfiles);
|
||||
this.config.addPath('snippets', snippets);
|
||||
if (!this.config.getPath('editor')) this.config.addPath('editor', path.resolve(base, 'editor'));
|
||||
|
||||
if (!this.config.getPath('tmp')) this.config.addPath('tmp', process.platform !== 'win32' ?
|
||||
path.join(os.tmpdir(), 'betterdiscord', `${process.getuid()}`) :
|
||||
path.join(os.tmpdir(), 'betterdiscord'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
*/
|
||||
|
||||
import Module from './modulebase';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
|
||||
export default class Config extends Module {
|
||||
|
||||
|
@ -72,6 +74,9 @@ export default class Config extends Module {
|
|||
|
||||
// Compatibility with old client code and new installer args
|
||||
compatibility() {
|
||||
this.args.paths = Object.entries(this.args.paths).map(([id, path]) => ({ id, path }));
|
||||
this.args.paths = Object.entries(this.args.paths).map(([id, _path]) => ({
|
||||
id, path: path.resolve(os.homedir(), _path)
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import semver from 'semver';
|
|||
import Axi from './axi';
|
||||
import zlib from 'zlib';
|
||||
import tarfs from 'tar-fs';
|
||||
import path from 'path';
|
||||
|
||||
const TEST_UPDATE = [
|
||||
{
|
||||
|
@ -91,7 +92,8 @@ export default class Updater extends Module {
|
|||
async updateBd(update) {
|
||||
try {
|
||||
console.log('[BetterDiscord:Updater] Updating', update.id);
|
||||
await this.downloadTarGz(`https://github.com/JsSucks/BetterDiscordApp${update.remote}`, this.bd.config.getPath('base'));
|
||||
await this.downloadTarGz(`https://github.com/JsSucks/BetterDiscordApp${update.remote}`, this.bd.config.getPath('tmp'));
|
||||
await FileUtils.rn(path.join(this.bd.config.getPath('tmp'), update.id), this.bd.config.getPath(update.id));
|
||||
this.updateFinished(update);
|
||||
// Cleanup
|
||||
await FileUtils.rm(`${this.bd.config.getPath(update.id)}_old`);
|
||||
|
|
|
@ -84,14 +84,7 @@ gulp.task('client-pkg', function() {
|
|||
]);
|
||||
});
|
||||
|
||||
gulp.task('client-sparkplug', function() {
|
||||
return pump([
|
||||
gulp.src('core/dist/sparkplug.js'),
|
||||
gulp.dest('release/client')
|
||||
]);
|
||||
});
|
||||
|
||||
gulp.task('client-release', gulp.parallel('client-main', 'client-pkg', 'client-sparkplug'));
|
||||
gulp.task('client-release', gulp.parallel('client-main', 'client-pkg'));
|
||||
|
||||
// Editor
|
||||
|
||||
|
|
|
@ -1,16 +1,21 @@
|
|||
const bdinfo = require('./bd.json');
|
||||
const bdinfo = require('./bd');
|
||||
const { app } = require('electron');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const Module = require('module');
|
||||
|
||||
const packagePath = path.join(__dirname, '..', 'app.asar');
|
||||
const packagePath = path.resolve(__dirname, '..', 'app.asar');
|
||||
app.getAppPath = () => packagePath;
|
||||
|
||||
function loadBd() {
|
||||
const { paths } = bdinfo;
|
||||
const { BetterDiscord } = require(paths.core);
|
||||
const instance = new BetterDiscord(bdinfo);
|
||||
const userconfig = (() => {
|
||||
try {
|
||||
return require(path.resolve(os.homedir(), bdinfo.paths.userconfig));
|
||||
} catch (err) {}
|
||||
})() || {};
|
||||
|
||||
const { BetterDiscord } = require(path.resolve(os.homedir(), (userconfig.paths || {}).core || bdinfo.paths.core));
|
||||
const instance = new BetterDiscord(bdinfo, userconfig);
|
||||
}
|
||||
|
||||
app.on('ready', loadBd);
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
const args = process.argv;
|
||||
const process = require('process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const args = process.argv;
|
||||
|
||||
const useBdRelease = args[2] && args[2].toLowerCase() === 'release';
|
||||
const releaseInput = useBdRelease ? args[3] && args[3].toLowerCase() : args[2] && args[2].toLowerCase();
|
||||
const release = releaseInput === 'canary' ? 'Discord Canary' : releaseInput === 'ptb' ? 'Discord PTB' : 'Discord';
|
||||
|
@ -16,7 +18,7 @@ const discordPath = (function() {
|
|||
} else if (process.platform === 'darwin') {
|
||||
const appPath = releaseInput === 'canary' ? path.join('/Applications', 'Discord Canary.app')
|
||||
: releaseInput === 'ptb' ? path.join('/Applications', 'Discord PTB.app')
|
||||
: useBdRelease && args[3] ? args[3] ? args[2] : args[2]
|
||||
: useBdRelease && args[3] ? args[3] : !useBdRelease && args[2] ? args[2]
|
||||
: path.join('/Applications', 'Discord.app');
|
||||
|
||||
return path.join(appPath, 'Contents', 'Resources');
|
||||
|
@ -31,30 +33,53 @@ console.log(`Found ${release} in ${discordPath}`);
|
|||
const appPath = path.join(discordPath, 'app');
|
||||
const packageJson = path.join(appPath, 'package.json');
|
||||
const indexJs = path.join(appPath, 'index.js');
|
||||
const bdJson = path.join(appPath, 'bd.json');
|
||||
|
||||
if (!fs.existsSync(appPath)) fs.mkdirSync(appPath);
|
||||
if (fs.existsSync(packageJson)) fs.unlinkSync(packageJson);
|
||||
if (fs.existsSync(indexJs)) fs.unlinkSync(indexJs);
|
||||
|
||||
const bdPath = useBdRelease ? path.resolve(__dirname, '..', 'release') : path.resolve(__dirname, '..');
|
||||
if (fs.existsSync(bdJson)) fs.unlinkSync(bdJson);
|
||||
|
||||
console.log(`Writing package.json`);
|
||||
fs.writeFileSync(packageJson, JSON.stringify({
|
||||
fs.writeFileSync(path.join(appPath, 'package.json'), JSON.stringify({
|
||||
name: 'betterdiscord',
|
||||
description: 'BetterDiscord',
|
||||
main: 'index.js',
|
||||
private: true
|
||||
}, null, 4));
|
||||
|
||||
console.log(`Writing index.js`);
|
||||
fs.writeFileSync(indexJs, `const path = require('path');
|
||||
const fs = require('fs');
|
||||
const Module = require('module');
|
||||
const electron = require('electron');
|
||||
const basePath = path.join(__dirname, '..', 'app.asar');
|
||||
electron.app.getAppPath = () => basePath;
|
||||
Module._load(basePath, null, true);
|
||||
electron.app.on('ready', () => new (require('${bdPath.replace(/\\/g, '\\\\').replace(/'/g, '\\\'')}').BetterDiscord)());
|
||||
`);
|
||||
if (useBdRelease) {
|
||||
console.log(`Writing index.js`);
|
||||
fs.writeFileSync(path.join(appPath, 'index.js'), fs.readFileSync(path.resolve(__dirname, '..', 'installer', 'stub.js')));
|
||||
|
||||
console.log(`Writing bd.json`);
|
||||
fs.writeFileSync(path.join(appPath, 'bd.json'), JSON.stringify({
|
||||
options: {
|
||||
autoInject: true,
|
||||
commonCore: true,
|
||||
commonData: true
|
||||
},
|
||||
paths: {
|
||||
core: path.resolve(__dirname, '..', 'release', 'core'),
|
||||
client: path.resolve(__dirname, '..', 'release', 'client'),
|
||||
editor: path.resolve(__dirname, '..', 'release', 'editor'),
|
||||
data: path.resolve(__dirname, '..', 'release', 'data'),
|
||||
// tmp: path.resolve(os.tmpdir(), 'betterdiscord', `${process.getuid()}`)
|
||||
}
|
||||
}, null, 4));
|
||||
} else {
|
||||
const bdPath = path.resolve(__dirname, '..');
|
||||
|
||||
console.log(`Writing index.js`);
|
||||
fs.writeFileSync(path.join(appPath, 'index.js'), `const path = require('path');
|
||||
const fs = require('fs');
|
||||
const Module = require('module');
|
||||
const electron = require('electron');
|
||||
const basePath = path.join(__dirname, '..', 'app.asar');
|
||||
electron.app.getAppPath = () => basePath;
|
||||
Module._load(basePath, null, true);
|
||||
electron.app.on('ready', () => new (require('${bdPath.replace(/\\/g, '\\\\').replace(/'/g, '\\\'')}').BetterDiscord)());
|
||||
`);
|
||||
}
|
||||
|
||||
console.log(`Injection successful, please restart ${release}.`);
|
||||
|
|
|
@ -101,15 +101,15 @@ module.exports = (Plugin, Api, Vendor) => {
|
|||
if (!returnValue.props.children instanceof Array) returnValue.props.children = [returnValue.props.children];
|
||||
// Add a generic Button component provided by BD
|
||||
returnValue.props.children.push(Api.Components.ButtonGroup({
|
||||
classes: [ 'exampleBtnGroup' ], // Additional classes for button group
|
||||
class: [ 'exampleBtnGroup' ], // Additional classes for button group
|
||||
buttons: [
|
||||
{
|
||||
classes: ['exampleBtn'], // Additional classes for button
|
||||
class: ['exampleBtn'], // Additional classes for button
|
||||
text: 'Hello World!', // Text for button
|
||||
onClick: e => Logger.log('Hello World!') // Button click handler
|
||||
},
|
||||
{
|
||||
classes: ['exampleBtn'],
|
||||
class: ['exampleBtn'],
|
||||
text: 'Button',
|
||||
onClick: e => Logger.log('Button!')
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue