Merge pull request #184 from samuelthomas2774/refactor
Refactor and comment
This commit is contained in:
commit
a4ceb8bd2c
|
@ -2,6 +2,7 @@ root = true
|
|||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
indent_style = space
|
||||
|
|
|
@ -1,27 +1,11 @@
|
|||
.idea/*
|
||||
*.name
|
||||
devjs/.idea/devjs.iml
|
||||
*.bak
|
||||
*.bak.*
|
||||
*.xpi
|
||||
Firefox/data/js/jquery-2.1.4.min.js
|
||||
*.dev.*
|
||||
/nbproject/private/
|
||||
# Generated files
|
||||
node_modules
|
||||
.sass-cache
|
||||
/*.jiiks
|
||||
Installers/dotNet/bin/
|
||||
Installers/dotNet/packages/
|
||||
Installers/dotNet/dlls/
|
||||
v2/dist/vendor/
|
||||
v2/lib/static.js
|
||||
**/*.suo
|
||||
Installers/**/*/bin
|
||||
Installers/**/*/obj
|
||||
Installers/**/*/packages
|
||||
.vs
|
||||
dist/
|
||||
user.config.json
|
||||
tests/data
|
||||
/tests/themes/SimplerFlat
|
||||
dist
|
||||
etc
|
||||
release
|
||||
|
||||
tests/log.txt
|
||||
|
||||
# User data
|
||||
tests/data
|
||||
user.config.json
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# BetterDiscordApp [![Travis][build-badge]][build]
|
||||
|
||||
[build-badge]: https://img.shields.io/travis/JsSucks/BetterDiscordApp/master.svg
|
||||
[build]: https://travis-ci.org/JsSucks/BetterDiscordApp
|
|
@ -5,21 +5,19 @@
|
|||
"version": "2.0.0b",
|
||||
"homepage": "https://betterdiscord.net",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"main": "dist/betterdiscord.client.js",
|
||||
"contributors": [
|
||||
"Jiiks",
|
||||
"Pohky"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Jiiks/BetterDiscordApp.git"
|
||||
"url": "https://github.com/JsSucks/BetterDiscordApp.git"
|
||||
},
|
||||
"private": false,
|
||||
"devDependencies": {
|
||||
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack --progress --colors",
|
||||
"watch": "webpack --progress --colors --watch"
|
||||
"watch": "webpack --progress --colors --watch",
|
||||
"release": "webpack --progress --colors --config=webpack.production.config.js"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,11 +9,10 @@
|
|||
data() {
|
||||
return {
|
||||
favourite: false
|
||||
}
|
||||
};
|
||||
},
|
||||
props: ['src', 'name'],
|
||||
methods: {
|
||||
},
|
||||
methods: {},
|
||||
beforeMount() {
|
||||
// Check favourite state
|
||||
}
|
||||
|
|
|
@ -7,26 +7,34 @@
|
|||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
import { FileUtils, ClientLogger as Logger } from 'common';
|
||||
|
||||
import { Events, Globals, WebpackModules, ReactComponents, MonkeyPatch } from 'modules';
|
||||
import { DOM, VueInjector, Reflection } from 'ui';
|
||||
import { FileUtils, ClientLogger as Logger } from 'common';
|
||||
import path from 'path';
|
||||
import EmoteComponent from './EmoteComponent.vue';
|
||||
|
||||
let emotes = null;
|
||||
const emotesEnabled = true;
|
||||
|
||||
export default class {
|
||||
|
||||
static get searchCache() {
|
||||
return this._searchCache || (this._searchCache = {});
|
||||
}
|
||||
|
||||
static get emoteDb() {
|
||||
return emotes;
|
||||
}
|
||||
|
||||
static get React() {
|
||||
return WebpackModules.getModuleByName('React');
|
||||
}
|
||||
|
||||
static get ReactDOM() {
|
||||
return WebpackModules.getModuleByName('ReactDOM');
|
||||
}
|
||||
|
||||
static processMarkup(markup) {
|
||||
if (!emotesEnabled) return markup; // TODO Get it from setttings
|
||||
const newMarkup = [];
|
||||
|
@ -92,13 +100,20 @@ export default class {
|
|||
}
|
||||
|
||||
static async observe() {
|
||||
const dataPath = Globals.getObject('paths').find(path => path.id === 'data').path;
|
||||
const dataPath = Globals.getPath('data');
|
||||
try {
|
||||
emotes = await FileUtils.readJsonFromFile(path.join(dataPath, 'emotes.json'));
|
||||
} catch (err) {
|
||||
Logger.err('EmoteModule', [`Failed to load emote data. Make sure you've downloaded the emote data and placed it in ${dataPath}:`, err]);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
emotes = await FileUtils.readJsonFromFile(dataPath + '/emotes.json');
|
||||
const Message = await ReactComponents.getComponent('Message');
|
||||
this.unpatchRender = MonkeyPatch('BD:EmoteModule', Message.component.prototype).after('render', (component, args, retVal) => {
|
||||
try {
|
||||
const markup = this.findByProp(retVal, 'className', 'markup'); // First child has all the actual text content, second is the edited timestamp
|
||||
// First child has all the actual text content, second is the edited timestamp
|
||||
const markup = this.findByProp(retVal, 'className', 'markup');
|
||||
if (!markup) return;
|
||||
markup.children[0] = this.processMarkup(markup.children[0]);
|
||||
} catch (err) {
|
||||
|
@ -131,12 +146,11 @@ export default class {
|
|||
}
|
||||
const { bdemoteName, bdemoteSrc } = root.dataset;
|
||||
if (!bdemoteName || !bdemoteSrc) return;
|
||||
VueInjector.inject(
|
||||
root,
|
||||
DOM.createElement('span'),
|
||||
{ EmoteComponent },
|
||||
`<EmoteComponent src="${bdemoteSrc}" name="${bdemoteName}"/>`
|
||||
);
|
||||
VueInjector.inject(root, {
|
||||
components: { EmoteComponent },
|
||||
data: { src: bdemoteSrc, name: bdemoteName },
|
||||
template: '<EmoteComponent :src="src" :name="name" />'
|
||||
}, DOM.createElement('span'));
|
||||
root.classList.add('bd-is-emote');
|
||||
}
|
||||
|
||||
|
@ -179,4 +193,5 @@ export default class {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
[
|
||||
{
|
||||
"__user": "Jiiks#5000",
|
||||
"id": "81388395867156480",
|
||||
"developer": true,
|
||||
"webdev": true,
|
||||
"contributor": true
|
||||
},
|
||||
{
|
||||
"__user": "Pohky#0156",
|
||||
"id": "98003542823944192",
|
||||
"developer": true,
|
||||
"webdev": false,
|
||||
"contributor": true
|
||||
},
|
||||
{
|
||||
"__user": "Hammock#3110",
|
||||
"id": "138850472541814784",
|
||||
"developer": false,
|
||||
"webdev": true,
|
||||
"contributor": true
|
||||
},
|
||||
{
|
||||
"__user": "Zerebos#7790",
|
||||
"id": "249746236008169473",
|
||||
"developer": true,
|
||||
"webdev": false,
|
||||
"contributor": true
|
||||
},
|
||||
{
|
||||
"__user": "Pierce#1337",
|
||||
"id": "125367412370440192",
|
||||
"developer": true,
|
||||
"webdev": false,
|
||||
"contributor": true
|
||||
},
|
||||
{
|
||||
"__user": "Samuel Elliott#2764",
|
||||
"id": "284056145272766465",
|
||||
"developer": true,
|
||||
"webdev": false,
|
||||
"contributor": true
|
||||
},
|
||||
{
|
||||
"__user": "Lilian Tedone#6223",
|
||||
"id": "184021060562321419",
|
||||
"developer": false,
|
||||
"webdev": false,
|
||||
"contributor": true
|
||||
},
|
||||
{
|
||||
"__user": "samfun123#8972",
|
||||
"id": "76052829285916672",
|
||||
"developer": false,
|
||||
"webdev": false,
|
||||
"contributor": true
|
||||
},
|
||||
{
|
||||
"__user": "samogot#4379",
|
||||
"id": "171005991272316937",
|
||||
"developer": false,
|
||||
"webdev": false,
|
||||
"contributor": true
|
||||
}
|
||||
]
|
|
@ -51,6 +51,12 @@
|
|||
"hint": "Adds some of BetterDiscord's internal modules to `global._bd`.",
|
||||
"value": false
|
||||
},
|
||||
{
|
||||
"id": "debugger-keybind",
|
||||
"type": "keybind",
|
||||
"text": "Debugger keybind",
|
||||
"hint": "When this keybind is activated the developer tools will be opened and Discord will be paused."
|
||||
},
|
||||
{
|
||||
"id": "ignore-content-manager-errors",
|
||||
"type": "bool",
|
||||
|
@ -91,7 +97,6 @@
|
|||
{
|
||||
"id": "css",
|
||||
"text": "CSS Editor",
|
||||
"hidden": true,
|
||||
"settings": [
|
||||
{
|
||||
"category": "default",
|
||||
|
|
|
@ -10,39 +10,32 @@
|
|||
|
||||
import { DOM, BdUI, Modals, Reflection } from 'ui';
|
||||
import BdCss from './styles/index.scss';
|
||||
import { Patcher, MonkeyPatch, Vendor, Events, CssEditor, Globals, ExtModuleManager, PluginManager, ThemeManager, ModuleManager, WebpackModules, Settings, Database, ReactComponents, ReactAutoPatcher, DiscordApi } from 'modules';
|
||||
import { Events, CssEditor, Globals, Settings, Database, Updater, ModuleManager, PluginManager, ThemeManager, ExtModuleManager, Vendor, WebpackModules, Patcher, MonkeyPatch, ReactComponents, ReactAutoPatcher, DiscordApi } from 'modules';
|
||||
import { ClientLogger as Logger, ClientIPC, Utils } from 'common';
|
||||
import { EmoteModule } from 'builtin';
|
||||
import electron from 'electron';
|
||||
import path from 'path';
|
||||
|
||||
const tests = typeof PRODUCTION === 'undefined';
|
||||
const ignoreExternal = false;
|
||||
const DEV = true;
|
||||
|
||||
class BetterDiscord {
|
||||
|
||||
constructor() {
|
||||
Logger.file = tests ? path.resolve(__dirname, '..', '..', 'tests', 'log.txt') : path.join(__dirname, 'log.txt');
|
||||
Logger.log('main', 'BetterDiscord starting');
|
||||
|
||||
this._bd = {
|
||||
DOM,
|
||||
BdUI,
|
||||
Modals,
|
||||
Reflection,
|
||||
Patcher,
|
||||
MonkeyPatch,
|
||||
DOM, BdUI, Modals, Reflection,
|
||||
|
||||
Events, CssEditor, Globals, Settings, Database, Updater,
|
||||
ModuleManager, PluginManager, ThemeManager, ExtModuleManager,
|
||||
Vendor,
|
||||
Events,
|
||||
CssEditor,
|
||||
Globals,
|
||||
ExtModuleManager,
|
||||
PluginManager,
|
||||
ThemeManager,
|
||||
ModuleManager,
|
||||
WebpackModules,
|
||||
Settings,
|
||||
Database,
|
||||
ReactComponents,
|
||||
DiscordApi,
|
||||
Logger,
|
||||
ClientIPC,
|
||||
Utils,
|
||||
EmoteModule
|
||||
|
||||
WebpackModules, Patcher, MonkeyPatch, ReactComponents, DiscordApi,
|
||||
EmoteModule,
|
||||
|
||||
Logger, ClientIPC, Utils
|
||||
};
|
||||
|
||||
const developermode = Settings.getSetting('core', 'advanced', 'developer-mode');
|
||||
|
@ -52,6 +45,14 @@ class BetterDiscord {
|
|||
else if (window._bd) delete window._bd;
|
||||
});
|
||||
|
||||
const debuggerkeybind = Settings.getSetting('core', 'advanced', 'debugger-keybind');
|
||||
debuggerkeybind.on('keybind-activated', () => {
|
||||
const currentWindow = electron.remote.getCurrentWindow();
|
||||
if (currentWindow.isDevToolsOpened()) return eval('debugger;');
|
||||
currentWindow.openDevTools();
|
||||
setTimeout(() => eval('debugger;'), 1000);
|
||||
});
|
||||
|
||||
DOM.injectStyle(BdCss, 'bdmain');
|
||||
this.globalReady = this.globalReady.bind(this);
|
||||
Events.on('global-ready', this.globalReady);
|
||||
|
@ -63,14 +64,16 @@ class BetterDiscord {
|
|||
await Database.init();
|
||||
await Settings.loadSettings();
|
||||
await ModuleManager.initModules();
|
||||
Modals.showContentManagerErrors();
|
||||
|
||||
if (!ignoreExternal) {
|
||||
await ExtModuleManager.loadAllModules(true);
|
||||
await PluginManager.loadAllPlugins(true);
|
||||
await ThemeManager.loadAllThemes(true);
|
||||
}
|
||||
|
||||
if (!Settings.get('core', 'advanced', 'ignore-content-manager-errors'))
|
||||
Modals.showContentManagerErrors();
|
||||
|
||||
Events.emit('ready');
|
||||
Events.emit('discord-ready');
|
||||
EmoteModule.observe();
|
||||
|
@ -90,7 +93,7 @@ class BetterDiscord {
|
|||
if (window.BetterDiscord) {
|
||||
Logger.log('main', 'Attempting to inject again?');
|
||||
} else {
|
||||
let instance = null;
|
||||
let instance;
|
||||
Events.on('autopatcher', () => instance = new BetterDiscord());
|
||||
ReactAutoPatcher.autoPatch().then(() => Events.emit('autopatcher'));
|
||||
}
|
||||
|
|
|
@ -59,6 +59,7 @@ export default class Content {
|
|||
|
||||
/**
|
||||
* Opens a settings modal for this content.
|
||||
* @return {Modal}
|
||||
*/
|
||||
showSettingsModal() {
|
||||
return Modals.contentSettings(this);
|
||||
|
@ -73,20 +74,13 @@ export default class Content {
|
|||
|
||||
/**
|
||||
* Saves the content's current configuration.
|
||||
* @return {Promise}
|
||||
*/
|
||||
async saveConfiguration() {
|
||||
try {
|
||||
/*
|
||||
await FileUtils.writeFile(`${this.contentPath}/user.config.json`, JSON.stringify({
|
||||
enabled: this.enabled,
|
||||
config: this.settings.strip().settings,
|
||||
data: this.data
|
||||
}));
|
||||
*/
|
||||
Database.insertOrUpdate({ type: 'contentconfig', $or: [{ id: this.id }, { name: this.name }] }, {
|
||||
type: 'contentconfig',
|
||||
Database.insertOrUpdate({ type: `${this.type}-config`, id: this.id }, {
|
||||
type: `${this.type}-config`,
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
enabled: this.enabled,
|
||||
config: this.settings.strip().settings,
|
||||
data: this.data
|
||||
|
@ -143,15 +137,6 @@ export default class Content {
|
|||
return this.events.on(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an event listener.
|
||||
* @param {String} event The event to remove the listener from
|
||||
* @param {Function} callback The bound callback (optional)
|
||||
*/
|
||||
off(...args) {
|
||||
return this.events.removeListener(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an event listener that removes itself when called, therefore only being called once.
|
||||
* @param {String} event The event to add the listener to
|
||||
|
@ -162,6 +147,15 @@ export default class Content {
|
|||
return this.events.once(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an event listener.
|
||||
* @param {String} event The event to remove the listener from
|
||||
* @param {Function} callback The bound callback (optional)
|
||||
*/
|
||||
off(...args) {
|
||||
return this.events.removeListener(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event.
|
||||
* @param {String} event The event to emit
|
||||
|
|
|
@ -24,31 +24,52 @@ import Combokeys from 'combokeys';
|
|||
export default class {
|
||||
|
||||
/**
|
||||
* Any errors that happened
|
||||
* returns {Array}
|
||||
* Any errors that happened.
|
||||
* @return {Array}
|
||||
*/
|
||||
static get errors() {
|
||||
return this._errors || (this._errors = []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Locallly stored content
|
||||
* returns {Array}
|
||||
* Locally stored content.
|
||||
* @return {Array}
|
||||
*/
|
||||
static get localContent() {
|
||||
return this._localContent ? this._localContent : (this._localContent = []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Local path for content
|
||||
* returns {String}
|
||||
* The type of content this content manager manages.
|
||||
*/
|
||||
static get contentType() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of this content manager.
|
||||
*/
|
||||
static get moduleName() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* The path used to store this content manager's content.
|
||||
*/
|
||||
static get pathId() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Local path for content.
|
||||
* @return {String}
|
||||
*/
|
||||
static get contentPath() {
|
||||
return Globals.getPath(this.pathId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all locally stored content
|
||||
* Load all locally stored content.
|
||||
* @param {bool} suppressErrors Suppress any errors that occur during loading of content
|
||||
*/
|
||||
static async loadAllContent(suppressErrors = false) {
|
||||
|
@ -83,8 +104,6 @@ export default class {
|
|||
});
|
||||
this._errors = [];
|
||||
}
|
||||
|
||||
return this.localContent;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
|
@ -102,7 +121,7 @@ export default class {
|
|||
const directories = await FileUtils.listDirectory(this.contentPath);
|
||||
|
||||
for (let dir of directories) {
|
||||
// If content is already loaded this should resolve.
|
||||
// If content is already loaded this should resolve
|
||||
if (this.getContentByDirName(dir)) continue;
|
||||
|
||||
try {
|
||||
|
@ -150,8 +169,6 @@ export default class {
|
|||
});
|
||||
this._errors = [];
|
||||
}
|
||||
|
||||
return this.localContent;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
|
@ -169,15 +186,12 @@ export default class {
|
|||
|
||||
await FileUtils.directoryExists(contentPath);
|
||||
|
||||
if (!reload) {
|
||||
const loaded = this.localContent.find(content => content.contentPath === contentPath);
|
||||
if (loaded) {
|
||||
if (!reload && this.getContentByPath(contentPath))
|
||||
throw { 'message': `Attempted to load already loaded user content: ${path}` };
|
||||
}
|
||||
}
|
||||
|
||||
const readConfig = await this.readConfig(contentPath);
|
||||
const mainPath = path.join(contentPath, readConfig.main);
|
||||
const configPath = path.resolve(contentPath, 'config.json');
|
||||
const readConfig = await FileUtils.readJsonFromFile(configPath);
|
||||
const mainPath = path.join(contentPath, readConfig.main || 'index.js');
|
||||
|
||||
const defaultConfig = new SettingsSet({
|
||||
settings: readConfig.defaultConfig,
|
||||
|
@ -191,19 +205,16 @@ export default class {
|
|||
};
|
||||
|
||||
try {
|
||||
//const readUserConfig = await this.readUserConfig(contentPath);
|
||||
const readUserConfig = await Database.find({ type: 'contentconfig', name: readConfig.info.name });
|
||||
const id = readConfig.info.id || readConfig.info.name.toLowerCase().replace(/[^a-zA-Z0-9-]/g, '-').replace(/--/g, '-');
|
||||
const readUserConfig = await Database.find({ type: `${this.contentType}-config`, id });
|
||||
if (readUserConfig.length) {
|
||||
userConfig.enabled = readUserConfig[0].enabled || false;
|
||||
// await userConfig.config.merge({ settings: readUserConfig.config });
|
||||
// userConfig.config.setSaved();
|
||||
// userConfig.config = userConfig.config.clone({ settings: readUserConfig.config });
|
||||
userConfig.config = readUserConfig[0].config;
|
||||
userConfig.data = readUserConfig[0].data || {};
|
||||
}
|
||||
} catch (err) { /*We don't care if this fails it either means that user config doesn't exist or there's something wrong with it so we revert to default config*/
|
||||
Logger.info(this.moduleName, `Failed reading config for ${this.contentType} ${readConfig.info.name} in ${dirName}`);
|
||||
Logger.err(this.moduleName, err);
|
||||
} catch (err) {
|
||||
// We don't care if this fails it either means that user config doesn't exist or there's something wrong with it so we revert to default config
|
||||
Logger.warn(this.moduleName, [`Failed reading config for ${this.contentType} ${readConfig.info.name} in ${dirName}`, err]);
|
||||
}
|
||||
|
||||
userConfig.config = defaultConfig.clone({ settings: userConfig.config });
|
||||
|
@ -244,9 +255,10 @@ export default class {
|
|||
}
|
||||
|
||||
/**
|
||||
* Unload content
|
||||
* @param {any} content Content to unload
|
||||
* @param {bool} reload Whether to reload the content after
|
||||
* Unload content.
|
||||
* @param {Content|String} content Content to unload
|
||||
* @param {Boolean} reload Whether to reload the content after
|
||||
* @return {Content}
|
||||
*/
|
||||
static async unloadContent(content, reload) {
|
||||
content = this.findContent(content);
|
||||
|
@ -275,34 +287,18 @@ export default class {
|
|||
}
|
||||
|
||||
/**
|
||||
* Reload content
|
||||
* @param {any} content Content to reload
|
||||
* Reload content.
|
||||
* @param {Content|String} content Content to reload
|
||||
* @return {Content}
|
||||
*/
|
||||
static reloadContent(content) {
|
||||
return this.unloadContent(content, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read content config file
|
||||
* @param {any} configPath Config file path
|
||||
*/
|
||||
static async readConfig(configPath) {
|
||||
configPath = path.resolve(configPath, 'config.json');
|
||||
return FileUtils.readJsonFromFile(configPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read content user config file
|
||||
* @param {any} configPath User config file path
|
||||
*/
|
||||
static async readUserConfig(configPath) {
|
||||
configPath = path.resolve(configPath, 'user.config.json');
|
||||
return FileUtils.readJsonFromFile(configPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the passed object is an instance of this content type.
|
||||
* @param {any} content Object to check
|
||||
* @param {Any} content Object to check
|
||||
* @return {Boolean}
|
||||
*/
|
||||
static isThisContent(content) {
|
||||
return content instanceof Content;
|
||||
|
@ -318,8 +314,9 @@ export default class {
|
|||
|
||||
/**
|
||||
* Wildcard content finder
|
||||
* @param {any} wild Content ID / directory name / path / name
|
||||
* @param {bool} nonunique Allow searching attributes that may not be unique
|
||||
* @param {String} wild Content ID / directory name / path / name
|
||||
* @param {Boolean} nonunique Allow searching attributes that may not be unique
|
||||
* @return {Content}
|
||||
*/
|
||||
static findContent(wild, nonunique) {
|
||||
if (this.isThisContent(wild)) return wild;
|
||||
|
@ -338,7 +335,8 @@ export default class {
|
|||
|
||||
/**
|
||||
* Wait for content to load
|
||||
* @param {any} content_id
|
||||
* @param {String} content_id
|
||||
* @return {Promise}
|
||||
*/
|
||||
static waitForContent(content_id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
|
|
@ -32,24 +32,24 @@ export default new class {
|
|||
}
|
||||
|
||||
/**
|
||||
* Init css editor
|
||||
* Init css editor.
|
||||
*/
|
||||
init() {
|
||||
ClientIPC.on('bd-get-scss', () => this.sendToEditor('set-scss', { scss: this.scss }));
|
||||
ClientIPC.on('bd-get-scss', () => this.scss, true);
|
||||
ClientIPC.on('bd-update-scss', (e, scss) => this.updateScss(scss));
|
||||
ClientIPC.on('bd-save-csseditor-bounds', (e, bounds) => this.saveEditorBounds(bounds));
|
||||
|
||||
ClientIPC.on('bd-save-scss', async (e, scss) => {
|
||||
await this.updateScss(scss);
|
||||
await this.save();
|
||||
});
|
||||
}, true);
|
||||
|
||||
this.liveupdate = Settings.getSetting('css', 'default', 'live-update');
|
||||
this.liveupdate.on('setting-updated', event => {
|
||||
this.sendToEditor('set-liveupdate', event.value);
|
||||
});
|
||||
|
||||
ClientIPC.on('bd-get-liveupdate', () => this.sendToEditor('set-liveupdate', this.liveupdate.value));
|
||||
ClientIPC.on('bd-get-liveupdate', () => this.liveupdate.value, true);
|
||||
ClientIPC.on('bd-set-liveupdate', (e, value) => this.liveupdate.value = value);
|
||||
|
||||
this.watchfilessetting = Settings.getSetting('css', 'default', 'watch-files');
|
||||
|
@ -60,20 +60,20 @@ export default new class {
|
|||
}
|
||||
|
||||
/**
|
||||
* Show css editor, flashes if already visible
|
||||
* Show css editor, flashes if already visible.
|
||||
*/
|
||||
async show() {
|
||||
await ClientIPC.send('openCssEditor', this.editor_bounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update css in client
|
||||
* @param {String} scss scss to compile
|
||||
* @param {bool} sendSource send to css editor instance
|
||||
* Update css in client.
|
||||
* @param {String} scss SCSS to compile
|
||||
* @param {bool} sendSource Whether to send to css editor instance
|
||||
*/
|
||||
async updateScss(scss, sendSource) {
|
||||
if (sendSource)
|
||||
this.sendToEditor('set-scss', { scss });
|
||||
this.sendToEditor('set-scss', scss);
|
||||
|
||||
if (!scss) {
|
||||
this._scss = this.css = '';
|
||||
|
@ -97,24 +97,26 @@ export default new class {
|
|||
}
|
||||
|
||||
/**
|
||||
* Save css to file
|
||||
* Save css to file.
|
||||
* @return {Promise}
|
||||
*/
|
||||
async save() {
|
||||
Settings.saveSettings();
|
||||
save() {
|
||||
return Settings.saveSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save current editor bounds
|
||||
* @param {Rectangle} bounds editor bounds
|
||||
* Save current editor bounds.
|
||||
* @param {Rectangle} bounds Editor bounds
|
||||
* @return {Promise}
|
||||
*/
|
||||
saveEditorBounds(bounds) {
|
||||
this.editor_bounds = bounds;
|
||||
Settings.saveSettings();
|
||||
return Settings.saveSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send scss to core for compilation
|
||||
* @param {String} scss scss string
|
||||
* Send SCSS to core for compilation.
|
||||
* @param {String} scss SCSS string
|
||||
*/
|
||||
async compile(scss) {
|
||||
return await ClientIPC.send('bd-compileSass', {
|
||||
|
@ -124,7 +126,7 @@ export default new class {
|
|||
}
|
||||
|
||||
/**
|
||||
* Recompile the current SCSS
|
||||
* Recompile the current SCSS.
|
||||
* @return {Promise}
|
||||
*/
|
||||
async recompile() {
|
||||
|
@ -132,16 +134,18 @@ export default new class {
|
|||
}
|
||||
|
||||
/**
|
||||
* Send data to open editor
|
||||
* @param {any} channel
|
||||
* @param {any} data
|
||||
* Send data to open editor.
|
||||
* @param {String} channel
|
||||
* @param {Any} data
|
||||
* @return {Promise}
|
||||
*/
|
||||
async sendToEditor(channel, data) {
|
||||
return await ClientIPC.send('sendToCssEditor', { channel, data });
|
||||
return ClientIPC.sendToCssEditor(channel, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens an SCSS file in a system editor
|
||||
* Opens an SCSS file in a system editor.
|
||||
* @return {Promise}
|
||||
*/
|
||||
async openSystemEditor() {
|
||||
try {
|
||||
|
@ -160,7 +164,8 @@ export default new class {
|
|||
throw {message: 'Failed to open system editor.'};
|
||||
}
|
||||
|
||||
/** Set current state
|
||||
/**
|
||||
* Set current state
|
||||
* @param {String} scss Current uncompiled SCSS
|
||||
* @param {String} css Current compiled CSS
|
||||
* @param {String} files Files imported in the SCSS
|
||||
|
@ -168,36 +173,35 @@ export default new class {
|
|||
*/
|
||||
setState(scss, css, files, err) {
|
||||
this._scss = scss;
|
||||
this.sendToEditor('set-scss', { scss });
|
||||
this.sendToEditor('set-scss', scss);
|
||||
this.css = css;
|
||||
this.files = files;
|
||||
this.error = err;
|
||||
}
|
||||
|
||||
/**
|
||||
* Current uncompiled scss
|
||||
* Current uncompiled scss.
|
||||
*/
|
||||
get scss() {
|
||||
return this._scss || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current scss
|
||||
* Set current scss.
|
||||
*/
|
||||
set scss(scss) {
|
||||
this.updateScss(scss, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Current compiled css
|
||||
* Current compiled css.
|
||||
*/
|
||||
get css() {
|
||||
return this._css || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject compiled css to head
|
||||
* {DOM}
|
||||
* Inject compiled css to head.
|
||||
*/
|
||||
set css(css) {
|
||||
this._css = css;
|
||||
|
@ -205,15 +209,14 @@ export default new class {
|
|||
}
|
||||
|
||||
/**
|
||||
* Current error
|
||||
* Current error.
|
||||
*/
|
||||
get error() {
|
||||
return this._error || undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set current error
|
||||
* {DOM}
|
||||
* Set current error.
|
||||
*/
|
||||
set error(err) {
|
||||
this._error = err;
|
||||
|
@ -293,7 +296,7 @@ export default new class {
|
|||
|
||||
/**
|
||||
* Checks if the system editor's file exists.
|
||||
* @return {Boolean}
|
||||
* @return {Promise}
|
||||
*/
|
||||
async fileExists() {
|
||||
try {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { ClientIPC } from 'bdipc';
|
||||
import { ClientIPC } from 'common';
|
||||
|
||||
export default class {
|
||||
|
||||
|
@ -16,6 +16,12 @@ export default class {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts or updates data in the database.
|
||||
* @param {Object} args The record to find
|
||||
* @param {Object} data The new record
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async insertOrUpdate(args, data) {
|
||||
try {
|
||||
return ClientIPC.send('bd-dba', { action: 'update', args, data });
|
||||
|
@ -24,6 +30,11 @@ export default class {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds data in the database.
|
||||
* @param {Object} args The record to find
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async find(args) {
|
||||
try {
|
||||
return ClientIPC.send('bd-dba', { action: 'find', args });
|
||||
|
@ -31,4 +42,5 @@ export default class {
|
|||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -402,7 +402,7 @@ export default class DiscordApi {
|
|||
|
||||
static get currentChannel() {
|
||||
const channel = Modules.ChannelStore.getChannel(Modules.SelectedChannelStore.getChannelId());
|
||||
return channel.isPrivate ? new PrivateChannel(channel) : new GuildChannel(channel);
|
||||
if (channel) return channel.isPrivate() ? new PrivateChannel(channel) : new GuildChannel(channel);
|
||||
}
|
||||
|
||||
static get currentUser() {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* BetterDiscord WebpackModules Module
|
||||
* BetterDiscord Event Hook
|
||||
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
|
||||
* All rights reserved.
|
||||
* https://betterdiscord.net
|
||||
|
@ -8,14 +8,13 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import EventListener from './eventlistener';
|
||||
import { Utils } from 'common';
|
||||
import Events from './events';
|
||||
import { Utils, ClientLogger as Logger } from 'common';
|
||||
import { WebpackModules } from './webpackmodules';
|
||||
import Events from './events';
|
||||
import EventListener from './eventlistener';
|
||||
|
||||
import * as SocketStructs from '../structs/socketstructs';
|
||||
|
||||
|
||||
/**
|
||||
* Discord socket event hook
|
||||
* @extends {EventListener}
|
||||
|
@ -23,7 +22,7 @@ import * as SocketStructs from '../structs/socketstructs';
|
|||
export default class extends EventListener {
|
||||
|
||||
init() {
|
||||
console.log(SocketStructs);
|
||||
Logger.log('EventHook', SocketStructs);
|
||||
this.hook();
|
||||
}
|
||||
|
||||
|
@ -44,16 +43,16 @@ export default class extends EventListener {
|
|||
orig.call(this, ...args);
|
||||
self.wsc = this;
|
||||
self.emit(...args);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
get eventsModule() {
|
||||
return WebpackModules.getModuleByPrototypes(['setMaxListeners', 'emit']);
|
||||
return WebpackModules.getModuleByName('Events');
|
||||
}
|
||||
|
||||
/**
|
||||
* Discord emit overload
|
||||
* @param {any} e
|
||||
* @param {any} event
|
||||
* @param {any} action
|
||||
* @param {any} data
|
||||
*/
|
||||
|
@ -66,8 +65,8 @@ export default class extends EventListener {
|
|||
|
||||
/**
|
||||
* Emit callback
|
||||
* @param {any} e Event Action
|
||||
* @param {any} d Event Args
|
||||
* @param {any} event Event
|
||||
* @param {any} data Event data
|
||||
*/
|
||||
dispatch(e, d) {
|
||||
Events.emit('raw-event', { type: e, data: d });
|
||||
|
@ -143,7 +142,7 @@ export default class extends EventListener {
|
|||
LFG_LISTING_CREATE: 'LFG_LISTING_CREATE', // No groups here
|
||||
LFG_LISTING_DELETE: 'LFG_LISTING_DELETE', // Thank you
|
||||
BRAINTREE_POPUP_BRIDGE_CALLBACK: 'BRAINTREE_POPUP_BRIDGE_CALLBACK' // What
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,14 +12,39 @@ import { EventEmitter } from 'events';
|
|||
const emitter = new EventEmitter();
|
||||
|
||||
export default class {
|
||||
static on(eventName, callBack) {
|
||||
emitter.on(eventName, callBack);
|
||||
|
||||
/**
|
||||
* Adds an event listener.
|
||||
* @param {String} event The event to listen for
|
||||
* @param {Function} callback The function to call when the event is emitted
|
||||
*/
|
||||
static on(event, callback) {
|
||||
emitter.on(event, callback);
|
||||
}
|
||||
|
||||
static off(eventName, callBack) {
|
||||
emitter.removeListener(eventName, callBack);
|
||||
/**
|
||||
* Adds an event listener that is only called once.
|
||||
* @param {String} event The event to listen for
|
||||
* @param {Function} callback The function to call when the event is emitted
|
||||
*/
|
||||
static once(event, callback) {
|
||||
emitter.once(event, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an event listener.
|
||||
* @param {String} event The event to remove
|
||||
* @param {Function} callback The listener to remove
|
||||
*/
|
||||
static off(event, callback) {
|
||||
emitter.removeListener(event, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event
|
||||
* @param {String} event The event to emit
|
||||
* @param {Any} ...data Data to pass to the event listeners
|
||||
*/
|
||||
static emit(...args) {
|
||||
emitter.emit(...args);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
const eventemitters = new WeakMap();
|
||||
|
||||
export default class EventsWrapper {
|
||||
constructor(eventemitter) {
|
||||
|
||||
constructor(eventemitter, bind) {
|
||||
eventemitters.set(this, eventemitter);
|
||||
}
|
||||
|
||||
|
@ -19,26 +20,33 @@ export default class EventsWrapper {
|
|||
return this._eventSubs || (this._eventSubs = []);
|
||||
}
|
||||
|
||||
get on() { return this.subscribe }
|
||||
subscribe(event, callback) {
|
||||
if (this.eventSubs.find(e => e.event === event && e.callback === callback)) return;
|
||||
this.eventSubs.push({
|
||||
event,
|
||||
callback
|
||||
});
|
||||
eventemitters.get(this).on(event, callback);
|
||||
const boundCallback = () => callback.apply(this.bind, arguments);
|
||||
this.eventSubs.push({ event, callback, boundCallback });
|
||||
eventemitters.get(this).on(event, boundCallback);
|
||||
}
|
||||
|
||||
once(event, callback) {
|
||||
if (this.eventSubs.find(e => e.event === event && e.callback === callback)) return;
|
||||
const boundCallback = () => this.off(event, callback) && callback.apply(this.bind, arguments);
|
||||
this.eventSubs.push({ event, callback, boundCallback });
|
||||
eventemitters.get(this).on(event, boundCallback);
|
||||
}
|
||||
|
||||
get off() { return this.unsubscribe }
|
||||
unsubscribe(event, callback) {
|
||||
for (let index of this.eventSubs) {
|
||||
if (this.eventSubs[index].event !== event || (callback && this.eventSubs[index].callback === callback)) return;
|
||||
eventemitters.get(this).off(event, this.eventSubs[index].callback);
|
||||
if (this.eventSubs[index].event !== event || (callback && this.eventSubs[index].callback === callback)) continue;
|
||||
eventemitters.get(this).off(event, this.eventSubs[index].boundCallback);
|
||||
this.eventSubs.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
unsubscribeAll() {
|
||||
for (let event of this.eventSubs) {
|
||||
eventemitters.get(this).off(event.event, event.callback);
|
||||
eventemitters.get(this).off(event.event, event.boundCallback);
|
||||
}
|
||||
this.eventSubs.splice(0, this.eventSubs.length);
|
||||
}
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import sparkplug from 'sparkplug';
|
||||
import { ClientIPC } from 'common';
|
||||
import Module from './module';
|
||||
import Events from './events';
|
||||
import { ClientIPC } from 'bdipc';
|
||||
|
||||
export default new class extends Module {
|
||||
|
||||
|
@ -28,28 +29,24 @@ export default new class extends Module {
|
|||
this.getObject = this.getObject.bind(this);
|
||||
}
|
||||
|
||||
first() {
|
||||
(async() => {
|
||||
async first() {
|
||||
const config = await ClientIPC.send('getConfig');
|
||||
this.setState(config);
|
||||
this.setState({ config });
|
||||
|
||||
// This is for Discord to stop error reporting :3
|
||||
window.BetterDiscord = {
|
||||
'version': config.version,
|
||||
'v': config.version
|
||||
version: config.version,
|
||||
v: config.version
|
||||
};
|
||||
window.jQuery = {};
|
||||
|
||||
if (window.__bd) {
|
||||
this.setState(window.__bd);
|
||||
window.__bd = {
|
||||
setWS: this.setWS
|
||||
}
|
||||
if (sparkplug.bd) {
|
||||
this.setState({ bd: sparkplug.bd });
|
||||
sparkplug.bd.setWS = this.setWS;
|
||||
}
|
||||
|
||||
Events.emit('global-ready');
|
||||
Events.emit('socket-created', this.state.wsHook);
|
||||
})();
|
||||
}
|
||||
|
||||
setWS(wSocket) {
|
||||
|
@ -60,19 +57,43 @@ export default new class extends Module {
|
|||
}
|
||||
|
||||
getObject(name) {
|
||||
return this.state[name];
|
||||
return this.config[name] || this.bd[name];
|
||||
}
|
||||
|
||||
get bd() {
|
||||
return this.state.bd;
|
||||
}
|
||||
|
||||
get localStorage() {
|
||||
return this.bd.localStorage;
|
||||
}
|
||||
|
||||
get webSocket() {
|
||||
return this.bd.wsHook;
|
||||
}
|
||||
|
||||
get WebSocket() {
|
||||
return this.bd.wsOrig;
|
||||
}
|
||||
|
||||
get ignited() {
|
||||
return this.bd.ignited;
|
||||
}
|
||||
|
||||
get config() {
|
||||
return this.state.config;
|
||||
}
|
||||
|
||||
get paths() {
|
||||
return this.config.paths;
|
||||
}
|
||||
|
||||
getPath(id) {
|
||||
return this.state.paths.find(path => path.id === id).path;
|
||||
return this.paths.find(path => path.id === id).path;
|
||||
}
|
||||
|
||||
static get paths() {
|
||||
return this.state.paths;
|
||||
}
|
||||
|
||||
static get version() {
|
||||
return this.state.version;
|
||||
get version() {
|
||||
return this.config.version;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,17 +8,16 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/*
|
||||
Base Module that every non-static module should extend
|
||||
/**
|
||||
* Base Module that every non-static module should extend
|
||||
*/
|
||||
|
||||
export default class Module {
|
||||
|
||||
constructor(args) {
|
||||
this.__ = {
|
||||
state: args || {},
|
||||
args
|
||||
}
|
||||
};
|
||||
this.setState = this.setState.bind(this);
|
||||
this.initialize();
|
||||
}
|
||||
|
@ -38,7 +37,6 @@ export default class Module {
|
|||
set args(t) { }
|
||||
get args() { return this.__.args; }
|
||||
|
||||
|
||||
set state(state) { return this.__.state = state; }
|
||||
get state() { return this.__.state; }
|
||||
|
||||
|
|
|
@ -8,30 +8,39 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/*Module Manager initializes all modules when everything is ready*/
|
||||
|
||||
import { ClientLogger as Logger } from 'common';
|
||||
import { Events, SocketProxy, EventHook, CssEditor } from 'modules';
|
||||
import { ProfileBadges } from 'ui';
|
||||
import Updater from './updater';
|
||||
|
||||
/**
|
||||
* Module Manager initializes all modules when everything is ready
|
||||
*/
|
||||
export default class {
|
||||
|
||||
/**
|
||||
* An array of modules.
|
||||
*/
|
||||
static get modules() {
|
||||
return this._modules ? this._modules : (this._modules = [
|
||||
new ProfileBadges(),
|
||||
new SocketProxy(),
|
||||
new EventHook(),
|
||||
CssEditor,
|
||||
new Updater()
|
||||
Updater
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all modules.
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async initModules() {
|
||||
for (let module of this.modules) {
|
||||
try {
|
||||
if (module.init && module.init instanceof Function) module.init();
|
||||
} catch (err) {
|
||||
console.log(`Failed to initialize module: ${err}`);
|
||||
Logger.err('Module Manager', ['Failed to initialize module:', err]);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -1,19 +1,23 @@
|
|||
export { default as Events } from './events';
|
||||
export { default as Settings } from './settings';
|
||||
export { default as CssEditor } from './csseditor';
|
||||
export { default as ExtModuleManager } from './extmodulemanager';
|
||||
export { default as Globals } from './globals';
|
||||
export { default as Settings } from './settings';
|
||||
export { default as Database } from './database';
|
||||
export { default as Updater } from './updater';
|
||||
|
||||
export { default as ModuleManager } from './modulemanager';
|
||||
export { default as PluginManager } from './pluginmanager';
|
||||
export { default as ThemeManager } from './thememanager';
|
||||
export { default as Globals } from './globals';
|
||||
export { default as ExtModuleManager } from './extmodulemanager';
|
||||
export { default as Permissions } from './permissionmanager';
|
||||
|
||||
export { default as EventsWrapper } from './eventswrapper';
|
||||
export { default as Vendor } from './vendor';
|
||||
|
||||
export * from './webpackmodules';
|
||||
export { default as ModuleManager } from './modulemanager';
|
||||
export * from './patcher';
|
||||
export * from './reactcomponents';
|
||||
export { default as EventListener } from './eventlistener';
|
||||
export { default as SocketProxy } from './socketproxy';
|
||||
export { default as EventHook } from './eventhook';
|
||||
export { default as Permissions } from './permissionmanager';
|
||||
export { default as Database } from './database';
|
||||
export { default as EventsWrapper } from './eventswrapper';
|
||||
export { default as DiscordApi } from './discordapi';
|
||||
export * from './patcher';
|
||||
export * from './reactcomponents';
|
||||
|
|
|
@ -12,7 +12,9 @@ import { WebpackModules } from './webpackmodules';
|
|||
import { ClientLogger as Logger, Utils } from 'common';
|
||||
|
||||
export class Patcher {
|
||||
|
||||
static get patches() { return this._patches || (this._patches = {}) }
|
||||
|
||||
static getPatchesByCaller(id) {
|
||||
const patches = [];
|
||||
for (const patch in this.patches) {
|
||||
|
@ -22,16 +24,21 @@ export class Patcher {
|
|||
}
|
||||
return patches;
|
||||
}
|
||||
|
||||
static unpatchAll(patches) {
|
||||
if (typeof patches === 'string')
|
||||
patches = this.getPatchesByCaller(patches);
|
||||
|
||||
for (const patch of patches) {
|
||||
for (const child of patch.children) {
|
||||
child.unpatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static resolveModule(module) {
|
||||
if (module instanceof Function || (module instanceof Object && !(module instanceof Array))) return module;
|
||||
if ('string' === typeof module) return WebpackModules.getModuleByName(module);
|
||||
if (typeof module === 'string') return WebpackModules.getModuleByName(module);
|
||||
if (module instanceof Array) return WebpackModules.getModuleByProps(module);
|
||||
return null;
|
||||
}
|
||||
|
@ -99,10 +106,12 @@ export class Patcher {
|
|||
static before() { return this.pushChildPatch(...arguments, 'before') }
|
||||
static after() { return this.pushChildPatch(...arguments, 'after') }
|
||||
static instead() { return this.pushChildPatch(...arguments, 'instead') }
|
||||
|
||||
static pushChildPatch(caller, unresolvedModule, functionName, callback, displayName, type = 'after') {
|
||||
const module = this.resolveModule(unresolvedModule);
|
||||
if (!module || !module[functionName] || !(module[functionName] instanceof Function)) return null;
|
||||
displayName = 'string' === typeof unresolvedModule ? unresolvedModule : displayName || module.displayName || module.name || module.constructor.displayName || module.constructor.name;
|
||||
displayName = typeof unresolvedModule === 'string' ? unresolvedModule :
|
||||
displayName || module.displayName || module.name || module.constructor.displayName || module.constructor.name;
|
||||
const patchId = `${displayName}:${functionName}:${caller}`;
|
||||
|
||||
const patch = this.patches[patchId] || this.pushPatch(caller, patchId, module, functionName);
|
||||
|
|
|
@ -15,15 +15,15 @@ export default class Plugin extends Content {
|
|||
|
||||
get type() { return 'plugin' }
|
||||
|
||||
// Don't use - these will eventually be removed!
|
||||
get pluginPath() { return this.contentPath }
|
||||
get pluginConfig() { return this.config }
|
||||
|
||||
get start() { return this.enable }
|
||||
get stop() { return this.disable }
|
||||
|
||||
reload() {
|
||||
return PluginManager.reloadPlugin(this);
|
||||
}
|
||||
|
||||
unload() {
|
||||
PluginManager.unloadPlugin(this);
|
||||
return PluginManager.unloadPlugin(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* BetterDiscord Plugin Api
|
||||
* BetterDiscord Plugin API
|
||||
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
|
||||
* All rights reserved.
|
||||
* https://betterdiscord.net
|
||||
|
@ -8,7 +8,7 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { Utils, ClientLogger as Logger, ClientIPC } from 'common';
|
||||
import { Utils, ClientLogger as Logger, ClientIPC, AsyncEventEmitter } from 'common';
|
||||
import Settings from './settings';
|
||||
import ExtModuleManager from './extmodulemanager';
|
||||
import PluginManager from './pluginmanager';
|
||||
|
@ -20,31 +20,24 @@ import { SettingsSet, SettingsCategory, Setting, SettingsScheme } from 'structs'
|
|||
import { BdMenuItems, Modals, DOM, Reflection } from 'ui';
|
||||
import DiscordApi from './discordapi';
|
||||
import { ReactComponents } from './reactcomponents';
|
||||
import { MonkeyPatch } from './patcher';
|
||||
import { Patcher, MonkeyPatch } from './patcher';
|
||||
|
||||
export default class PluginApi {
|
||||
|
||||
constructor(pluginInfo) {
|
||||
constructor(pluginInfo, pluginPath) {
|
||||
this.pluginInfo = pluginInfo;
|
||||
this.pluginPath = pluginPath;
|
||||
|
||||
this.Events = new EventsWrapper(Events);
|
||||
Utils.defineSoftGetter(this.Events, 'bind', () => this.plugin);
|
||||
|
||||
this._menuItems = undefined;
|
||||
this._injectedStyles = undefined;
|
||||
this._modalStack = undefined;
|
||||
}
|
||||
get Discord() {
|
||||
return DiscordApi;
|
||||
}
|
||||
get ReactComponents() {
|
||||
return ReactComponents;
|
||||
}
|
||||
get Reflection() {
|
||||
return Reflection;
|
||||
}
|
||||
get MonkeyPatch() {
|
||||
return module => MonkeyPatch(this.pluginInfo.id, module);
|
||||
}
|
||||
|
||||
get plugin() {
|
||||
return PluginManager.getPluginById(this.pluginInfo.id || this.pluginInfo.name.toLowerCase().replace(/[^a-zA-Z0-9-]/g, '-').replace(/--/g, '-'));
|
||||
return PluginManager.getPluginByPath(this.pluginPath);
|
||||
}
|
||||
|
||||
async bridge(plugin_id) {
|
||||
|
@ -61,15 +54,18 @@ export default class PluginApi {
|
|||
|
||||
get Api() { return this }
|
||||
|
||||
get AsyncEventEmitter() { return AsyncEventEmitter }
|
||||
get EventsWrapper() { return EventsWrapper }
|
||||
|
||||
/**
|
||||
* Logger
|
||||
*/
|
||||
|
||||
loggerLog(...message) { Logger.log(this.pluginInfo.name, message) }
|
||||
loggerErr(...message) { Logger.err(this.pluginInfo.name, message) }
|
||||
loggerWarn(...message) { Logger.warn(this.pluginInfo.name, message) }
|
||||
loggerInfo(...message) { Logger.info(this.pluginInfo.name, message) }
|
||||
loggerDbg(...message) { Logger.dbg(this.pluginInfo.name, message) }
|
||||
loggerLog(...message) { Logger.log(this.plugin.name, message) }
|
||||
loggerErr(...message) { Logger.err(this.plugin.name, message) }
|
||||
loggerWarn(...message) { Logger.warn(this.plugin.name, message) }
|
||||
loggerInfo(...message) { Logger.info(this.plugin.name, message) }
|
||||
loggerDbg(...message) { Logger.dbg(this.plugin.name, message) }
|
||||
get Logger() {
|
||||
return {
|
||||
log: this.loggerLog.bind(this),
|
||||
|
@ -381,6 +377,53 @@ export default class PluginApi {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* DiscordApi
|
||||
*/
|
||||
|
||||
get Discord() {
|
||||
return DiscordApi;
|
||||
}
|
||||
|
||||
get ReactComponents() {
|
||||
return ReactComponents;
|
||||
}
|
||||
|
||||
get Reflection() {
|
||||
return Reflection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Patcher
|
||||
*/
|
||||
|
||||
get patches() {
|
||||
return Patcher.getPatchesByCaller(this.plugin.id);
|
||||
}
|
||||
patchBefore(...args) { return this.pushChildPatch(...args, 'before') }
|
||||
patchAfter(...args) { return this.pushChildPatch(...args, 'after') }
|
||||
patchInstead(...args) { return this.pushChildPatch(...args, 'instead') }
|
||||
pushChildPatch(...args) {
|
||||
return Patcher.pushChildPatch(this.plugin.id, ...args);
|
||||
}
|
||||
unpatchAll(patches) {
|
||||
return Patcher.unpatchAll(patches || this.plugin.id);
|
||||
}
|
||||
get Patcher() {
|
||||
return Object.defineProperty({
|
||||
before: this.patchBefore.bind(this),
|
||||
after: this.patchAfter.bind(this),
|
||||
instead: this.patchInstead.bind(this),
|
||||
pushChildPatch: this.pushChildPatch.bind(this),
|
||||
unpatchAll: this.unpatchAll.bind(this),
|
||||
}, 'patches', {
|
||||
get: () => this.patches
|
||||
});
|
||||
}
|
||||
get monkeyPatch() {
|
||||
return module => MonkeyPatch(this.plugin.id, module);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Stop plugins from modifying the plugin API for all plugins
|
||||
|
|
|
@ -76,12 +76,12 @@ export default class extends ContentManager {
|
|||
static async loadPlugin(paths, configs, info, main, dependencies, permissions) {
|
||||
if (permissions && permissions.length > 0) {
|
||||
for (let perm of permissions) {
|
||||
console.log(`Permission: ${Permissions.permissionText(perm).HEADER} - ${Permissions.permissionText(perm).BODY}`);
|
||||
Logger.log(this.moduleName, `Permission: ${Permissions.permissionText(perm).HEADER} - ${Permissions.permissionText(perm).BODY}`);
|
||||
}
|
||||
try {
|
||||
const allowed = await Modals.permissions(`${info.name} wants to:`, info.name, permissions).promise;
|
||||
} catch (err) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,15 +90,13 @@ export default class extends ContentManager {
|
|||
for (const [key, value] of Object.entries(dependencies)) {
|
||||
const extModule = ExtModuleManager.findModule(key);
|
||||
if (!extModule) {
|
||||
throw {
|
||||
'message': `Dependency: ${key}:${value} is not loaded`
|
||||
};
|
||||
throw {message: `Dependency ${key}:${value} is not loaded.`};
|
||||
}
|
||||
deps[key] = extModule.__require;
|
||||
}
|
||||
}
|
||||
|
||||
const plugin = window.require(paths.mainPath)(Plugin, new PluginApi(info), Vendor, deps);
|
||||
const plugin = window.require(paths.mainPath)(Plugin, new PluginApi(info, paths.contentPath), Vendor, deps);
|
||||
if (!(plugin.prototype instanceof Plugin))
|
||||
throw {message: `Plugin ${info.name} did not return a class that extends Plugin.`};
|
||||
|
||||
|
@ -121,24 +119,24 @@ export default class extends ContentManager {
|
|||
static get unloadPlugin() { return this.unloadContent }
|
||||
static get reloadPlugin() { return this.reloadContent }
|
||||
|
||||
static stopPlugin(name) {
|
||||
const plugin = name instanceof Plugin ? name : this.getPluginByName(name);
|
||||
try {
|
||||
if (plugin) return plugin.stop();
|
||||
} catch (err) {
|
||||
// Logger.err('PluginManager', err);
|
||||
}
|
||||
return true; //Return true anyways since plugin doesn't exist
|
||||
/**
|
||||
* Stops a plugin.
|
||||
* @param {Plugin|String} plugin
|
||||
* @return {Promise}
|
||||
*/
|
||||
static stopPlugin(plugin) {
|
||||
plugin = this.isPlugin(plugin) ? plugin : this.getPluginById(plugin);
|
||||
return plugin.stop();
|
||||
}
|
||||
|
||||
static startPlugin(name) {
|
||||
const plugin = name instanceof Plugin ? name : this.getPluginByName(name);
|
||||
try {
|
||||
if (plugin) return plugin.start();
|
||||
} catch (err) {
|
||||
// Logger.err('PluginManager', err);
|
||||
}
|
||||
return true; //Return true anyways since plugin doesn't exist
|
||||
/**
|
||||
* Starts a plugin.
|
||||
* @param {Plugin|String} plugin
|
||||
* @return {Promise}
|
||||
*/
|
||||
static startPlugin(plugin) {
|
||||
plugin = this.isPlugin(plugin) ? plugin : this.getPluginById(plugin);
|
||||
return plugin.start();
|
||||
}
|
||||
|
||||
static get isPlugin() { return this.isThisContent }
|
||||
|
|
|
@ -19,6 +19,7 @@ class Helpers {
|
|||
static get plannedActions() {
|
||||
return this._plannedActions || (this._plannedActions = new Map());
|
||||
}
|
||||
|
||||
static recursiveArray(parent, key, count = 1) {
|
||||
let index = 0;
|
||||
function* innerCall(parent, key) {
|
||||
|
@ -34,6 +35,7 @@ class Helpers {
|
|||
|
||||
return innerCall(parent, key);
|
||||
}
|
||||
|
||||
static recursiveArrayCount(parent, key) {
|
||||
let count = 0;
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
|
@ -41,6 +43,7 @@ class Helpers {
|
|||
++count;
|
||||
return this.recursiveArray(parent, key, count);
|
||||
}
|
||||
|
||||
static get recursiveChildren() {
|
||||
return function* (parent, key, index = 0, count = 1) {
|
||||
const item = parent[key];
|
||||
|
@ -52,12 +55,14 @@ class Helpers {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
static returnFirst(iterator, process) {
|
||||
for (let child of iterator) {
|
||||
const retVal = process(child);
|
||||
if (retVal !== undefined) return retVal;
|
||||
}
|
||||
}
|
||||
|
||||
static getFirstChild(rootParent, rootKey, selector) {
|
||||
const getDirectChild = (item, selector) => {
|
||||
if (item && item.props && item.props.children) {
|
||||
|
@ -116,11 +121,13 @@ class Helpers {
|
|||
};
|
||||
return this.returnFirst(this.recursiveChildren(rootParent, rootKey), checkFilter.bind(null, selector)) || {};
|
||||
}
|
||||
|
||||
static parseSelector(selector) {
|
||||
if (selector.startsWith('.')) return { className: selector.substr(1) }
|
||||
if (selector.startsWith('#')) return { id: selector.substr(1) }
|
||||
return {}
|
||||
}
|
||||
|
||||
static findByProp(obj, what, value) {
|
||||
if (obj.hasOwnProperty(what) && obj[what] === value) return obj;
|
||||
if (obj.props && !obj.children) return this.findByProp(obj.props, what, value);
|
||||
|
@ -132,6 +139,7 @@ class Helpers {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static findProp(obj, what) {
|
||||
if (obj.hasOwnProperty(what)) return obj[what];
|
||||
if (obj.props && !obj.children) return this.findProp(obj.props, what);
|
||||
|
@ -144,6 +152,7 @@ class Helpers {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static get ReactDOM() {
|
||||
return WebpackModules.getModuleByName('ReactDOM');
|
||||
}
|
||||
|
@ -155,12 +164,15 @@ class ReactComponent {
|
|||
this._component = component;
|
||||
this._retVal = retVal;
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
get component() {
|
||||
return this._component;
|
||||
}
|
||||
|
||||
get retVal() {
|
||||
return this._retVal;
|
||||
}
|
||||
|
|
|
@ -8,22 +8,23 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import defaultSettings from '../data/user.settings.default';
|
||||
import Globals from './globals';
|
||||
import CssEditor from './csseditor';
|
||||
import Events from './events';
|
||||
import { Utils, FileUtils, ClientLogger as Logger } from 'common';
|
||||
import { SettingsSet, SettingUpdatedEvent } from 'structs';
|
||||
import path from 'path';
|
||||
import Globals from './globals';
|
||||
import CssEditor from './csseditor';
|
||||
import Events from './events';
|
||||
import defaultSettings from '../data/user.settings.default';
|
||||
|
||||
export default new class Settings {
|
||||
|
||||
constructor() {
|
||||
this.settings = defaultSettings.map(_set => {
|
||||
const set = new SettingsSet(_set);
|
||||
|
||||
set.on('setting-updated', event => {
|
||||
const { category, setting, value, old_value } = event;
|
||||
Logger.log('Settings', `${set.id}/${category.id}/${setting.id} was changed from ${old_value} to ${value}`);
|
||||
Logger.log('Settings', [`${set.id}/${category.id}/${setting.id} was changed from`, old_value, 'to', value]);
|
||||
Events.emit('setting-updated', event);
|
||||
Events.emit(`setting-updated-${set.id}_${category.id}_${setting.id}`, event);
|
||||
});
|
||||
|
@ -37,6 +38,9 @@ export default new class Settings {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads BetterDiscord's settings.
|
||||
*/
|
||||
async loadSettings() {
|
||||
try {
|
||||
await FileUtils.ensureDirectory(this.dataPath);
|
||||
|
@ -48,7 +52,7 @@ export default new class Settings {
|
|||
for (let set of this.settings) {
|
||||
const newSet = settings.find(s => s.id === set.id);
|
||||
if (!newSet) continue;
|
||||
set.merge(newSet);
|
||||
await set.merge(newSet);
|
||||
set.setSaved();
|
||||
}
|
||||
|
||||
|
@ -57,10 +61,13 @@ export default new class Settings {
|
|||
} catch (err) {
|
||||
// There was an error loading settings
|
||||
// This probably means that the user doesn't have any settings yet
|
||||
Logger.err('Settings', err);
|
||||
Logger.warn('Settings', ['Failed to load internal settings', err]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves BetterDiscord's settings including CSS editor data.
|
||||
*/
|
||||
async saveSettings() {
|
||||
try {
|
||||
await FileUtils.ensureDirectory(this.dataPath);
|
||||
|
@ -72,15 +79,10 @@ export default new class Settings {
|
|||
css: CssEditor.css,
|
||||
css_editor_files: CssEditor.files,
|
||||
scss_error: CssEditor.error,
|
||||
css_editor_bounds: {
|
||||
width: CssEditor.editor_bounds.width,
|
||||
height: CssEditor.editor_bounds.height,
|
||||
x: CssEditor.editor_bounds.x,
|
||||
y: CssEditor.editor_bounds.y
|
||||
}
|
||||
css_editor_bounds: CssEditor.editor_bounds
|
||||
});
|
||||
|
||||
for (let set of this.getSettings) {
|
||||
for (let set of this.settings) {
|
||||
set.setSaved();
|
||||
}
|
||||
} catch (err) {
|
||||
|
@ -90,8 +92,13 @@ export default new class Settings {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds one of BetterDiscord's settings sets.
|
||||
* @param {String} set_id The ID of the set to find
|
||||
* @return {SettingsSet}
|
||||
*/
|
||||
getSet(set_id) {
|
||||
return this.getSettings.find(s => s.id === set_id);
|
||||
return this.settings.find(s => s.id === set_id);
|
||||
}
|
||||
|
||||
get core() { return this.getSet('core') }
|
||||
|
@ -100,39 +107,46 @@ export default new class Settings {
|
|||
get css() { return this.getSet('css') }
|
||||
get security() { return this.getSet('security') }
|
||||
|
||||
/**
|
||||
* Finds a category in one of BetterDiscord's settings sets.
|
||||
* @param {String} set_id The ID of the set to look in
|
||||
* @param {String} category_id The ID of the category to find
|
||||
* @return {SettingsCategory}
|
||||
*/
|
||||
getCategory(set_id, category_id) {
|
||||
const set = this.getSet(set_id);
|
||||
return set ? set.getCategory(category_id) : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a setting in one of BetterDiscord's settings sets.
|
||||
* @param {String} set_id The ID of the set to look in
|
||||
* @param {String} category_id The ID of the category to look in
|
||||
* @param {String} setting_id The ID of the setting to find
|
||||
* @return {Setting}
|
||||
*/
|
||||
getSetting(set_id, category_id, setting_id) {
|
||||
const set = this.getSet(set_id);
|
||||
return set ? set.getSetting(category_id, setting_id) : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a setting's value in one of BetterDiscord's settings sets.
|
||||
* @param {String} set_id The ID of the set to look in
|
||||
* @param {String} category_id The ID of the category to look in
|
||||
* @param {String} setting_id The ID of the setting to find
|
||||
* @return {Any}
|
||||
*/
|
||||
get(set_id, category_id, setting_id) {
|
||||
const set = this.getSet(set_id);
|
||||
return set ? set.get(category_id, setting_id) : undefined;
|
||||
}
|
||||
|
||||
async mergeSettings(set_id, newSettings) {
|
||||
const set = this.getSet(set_id);
|
||||
if (!set) return;
|
||||
|
||||
return await set.merge(newSettings);
|
||||
}
|
||||
|
||||
setSetting(set_id, category_id, setting_id, value) {
|
||||
const setting = this.getSetting(set_id, category_id, setting_id);
|
||||
if (!setting) throw {message: `Tried to set ${set_id}/${category_id}/${setting_id}, which doesn't exist`};
|
||||
setting.value = value;
|
||||
}
|
||||
|
||||
get getSettings() {
|
||||
return this.settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* The path to store user data in.
|
||||
*/
|
||||
get dataPath() {
|
||||
return Globals.getPath('data');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { ClientLogger as Logger } from 'common';
|
||||
import EventListener from './eventlistener';
|
||||
|
||||
export default class SocketProxy extends EventListener {
|
||||
|
@ -19,7 +20,7 @@ export default class SocketProxy extends EventListener {
|
|||
|
||||
get eventBindings() {
|
||||
return [
|
||||
{ id: 'socket-created', 'callback': this.socketCreated }
|
||||
{ id: 'socket-created', callback: this.socketCreated }
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -33,7 +34,7 @@ export default class SocketProxy extends EventListener {
|
|||
}
|
||||
|
||||
onMessage(e) {
|
||||
console.info(e);
|
||||
Logger.info('SocketProxy', e);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -31,10 +31,6 @@ export default class Theme extends Content {
|
|||
get type() { return 'theme' }
|
||||
get css() { return this.data.css }
|
||||
|
||||
// Don't use - these will eventually be removed!
|
||||
get themePath() { return this.contentPath }
|
||||
get themeConfig() { return this.config }
|
||||
|
||||
/**
|
||||
* Called when settings are updated.
|
||||
* This can be overridden by other content types.
|
||||
|
@ -63,7 +59,7 @@ export default class Theme extends Content {
|
|||
* @return {Promise}
|
||||
*/
|
||||
async compile() {
|
||||
console.log('Compiling CSS');
|
||||
Logger.log(this.name, 'Compiling CSS');
|
||||
|
||||
if (this.info.type === 'sass') {
|
||||
const config = await ThemeManager.getConfigAsSCSS(this.settings);
|
||||
|
@ -121,6 +117,7 @@ export default class Theme extends Content {
|
|||
*/
|
||||
set files(files) {
|
||||
this.data.files = files;
|
||||
|
||||
if (Settings.get('css', 'default', 'watch-files'))
|
||||
this.watchfiles = files;
|
||||
}
|
||||
|
|
|
@ -62,11 +62,11 @@ export default class ThemeManager extends ContentManager {
|
|||
}
|
||||
|
||||
static enableTheme(theme) {
|
||||
theme.enable();
|
||||
return theme.enable();
|
||||
}
|
||||
|
||||
static disableTheme(theme) {
|
||||
theme.disable();
|
||||
return theme.disable();
|
||||
}
|
||||
|
||||
static get isTheme() { return this.isThisContent }
|
||||
|
@ -74,6 +74,11 @@ export default class ThemeManager extends ContentManager {
|
|||
return theme instanceof Theme;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a representation of a settings set's values in SCSS.
|
||||
* @param {SettingsSet} settingsset The set to convert to SCSS
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async getConfigAsSCSS(settingsset) {
|
||||
const variables = [];
|
||||
|
||||
|
@ -87,6 +92,11 @@ export default class ThemeManager extends ContentManager {
|
|||
return variables.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a representation of a settings set's values as an SCSS map.
|
||||
* @param {SettingsSet} settingsset The set to convert to an SCSS map
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async getConfigAsSCSSMap(settingsset) {
|
||||
const variables = [];
|
||||
|
||||
|
@ -100,6 +110,11 @@ export default class ThemeManager extends ContentManager {
|
|||
return '(' + variables.join(', ') + ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a setting's name and value as a string that can be included in SCSS.
|
||||
* @param {Setting} setting The setting to convert to SCSS
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async parseSetting(setting) {
|
||||
const { type, id, value } = setting;
|
||||
const name = id.replace(/[^a-zA-Z0-9-]/g, '-').replace(/--/g, '-');
|
||||
|
@ -108,6 +123,11 @@ export default class ThemeManager extends ContentManager {
|
|||
if (scss) return [name, scss];
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes a string so it can be included in SCSS.
|
||||
* @param {String} value The string to escape
|
||||
* @return {String}
|
||||
*/
|
||||
static toSCSSString(value) {
|
||||
if (typeof value !== 'string' && value.toString) value = value.toString();
|
||||
return `'${typeof value === 'string' ? value.replace(/\\/g, '\\\\').replace(/'/g, '\\\'') : ''}'`;
|
||||
|
|
|
@ -13,15 +13,20 @@ import Globals from './globals';
|
|||
import { $ } from 'vendor';
|
||||
import { ClientLogger as Logger } from 'common';
|
||||
|
||||
export default class {
|
||||
export default new class {
|
||||
|
||||
constructor() {
|
||||
window.updater = this;
|
||||
this.updatesAvailable = false;
|
||||
this.latestVersion = undefined;
|
||||
this.error = undefined;
|
||||
|
||||
this.init = this.init.bind(this);
|
||||
this.checkForUpdates = this.checkForUpdates.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* The interval to wait before checking for updates.
|
||||
*/
|
||||
get interval() {
|
||||
return 60 * 1000 * 30;
|
||||
}
|
||||
|
@ -30,34 +35,61 @@ export default class {
|
|||
this.updateInterval = setInterval(this.checkForUpdates, this.interval);
|
||||
}
|
||||
|
||||
update() {
|
||||
// TODO
|
||||
/**
|
||||
* Installs an update.
|
||||
* TODO
|
||||
*/
|
||||
async update() {
|
||||
try {
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
|
||||
this.updatesAvailable = false;
|
||||
this.latestVersion = Globals.version;
|
||||
Events.emit('update-check-end');
|
||||
} catch (err) {
|
||||
this.error = err;
|
||||
this.checkForUpdates();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for updates.
|
||||
* @return {Promise}
|
||||
*/
|
||||
checkForUpdates() {
|
||||
if (this.updatesAvailable) return;
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.updatesAvailable) return resolve(true);
|
||||
Events.emit('update-check-start');
|
||||
Logger.info('Updater', 'Checking for updates');
|
||||
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: 'https://rawgit.com/JsSucks/BetterDiscordApp/master/package.json',
|
||||
cache: false,
|
||||
success: e => {
|
||||
try {
|
||||
this.latestVersion = e.version;
|
||||
Events.emit('update-check-end');
|
||||
Logger.info('Updater',
|
||||
`Latest Version: ${e.version} - Current Version: ${Globals.version}`);
|
||||
if (e.version !== Globals.version) {
|
||||
Logger.info('Updater', `Latest Version: ${e.version} - Current Version: ${Globals.version}`);
|
||||
|
||||
if (this.latestVersion !== Globals.version) {
|
||||
this.updatesAvailable = true;
|
||||
Events.emit('updates-available');
|
||||
resolve(true);
|
||||
}
|
||||
|
||||
resolve(false);
|
||||
} catch (err) {
|
||||
Events.emit('update-check-fail', err);
|
||||
reject(err);
|
||||
}
|
||||
},
|
||||
fail: e => Events.emit('update-check-fail', e)
|
||||
fail: err => {
|
||||
Events.emit('update-check-fail', err);
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -11,29 +11,36 @@
|
|||
import { WebpackModules } from './webpackmodules';
|
||||
import jQuery from 'jquery';
|
||||
import lodash from 'lodash';
|
||||
import Vue from 'vue';
|
||||
|
||||
export { jQuery as $ };
|
||||
|
||||
export default class {
|
||||
|
||||
static get jQuery() {
|
||||
return jQuery;
|
||||
}
|
||||
/**
|
||||
* jQuery
|
||||
*/
|
||||
static get jQuery() { return jQuery }
|
||||
static get $() { return this.jQuery }
|
||||
|
||||
static get $() {
|
||||
return this.jQuery;
|
||||
}
|
||||
|
||||
static get lodash() {
|
||||
return lodash;
|
||||
}
|
||||
|
||||
static get _() {
|
||||
return this.lodash;
|
||||
}
|
||||
/**
|
||||
* Lodash
|
||||
*/
|
||||
static get lodash() { return lodash }
|
||||
static get _() { return this.lodash }
|
||||
|
||||
/**
|
||||
* Moment
|
||||
*/
|
||||
static get moment() {
|
||||
return WebpackModules.getModuleByName('Moment');
|
||||
}
|
||||
|
||||
/**
|
||||
* Vue
|
||||
*/
|
||||
static get Vue() {
|
||||
return Vue;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -51,6 +51,8 @@ const KnownModules = {
|
|||
React: Filters.byProperties(['createElement', 'cloneElement']),
|
||||
ReactDOM: Filters.byProperties(['render', 'findDOMNode']),
|
||||
|
||||
Events: Filters.byPrototypeFields(['setMaxListeners', 'emit']),
|
||||
|
||||
/* Guild Info, Stores, and Utilities */
|
||||
GuildStore: Filters.byProperties(['getGuild']),
|
||||
SortedGuildStore: Filters.byProperties(['getSortedGuilds']),
|
||||
|
@ -89,7 +91,6 @@ const KnownModules = {
|
|||
UserActivityStore: Filters.byProperties(['getActivity']),
|
||||
UserNameResolver: Filters.byProperties(['getName']),
|
||||
|
||||
|
||||
/* Emoji Store and Utils */
|
||||
EmojiInfo: Filters.byProperties(['isEmojiDisabled']),
|
||||
EmojiUtils: Filters.byProperties(['getGuildEmoji']),
|
||||
|
@ -100,7 +101,6 @@ const KnownModules = {
|
|||
InviteResolver: Filters.byProperties(['findInvite']),
|
||||
InviteActions: Filters.byProperties(['acceptInvite']),
|
||||
|
||||
|
||||
/* Discord Objects & Utils */
|
||||
DiscordConstants: Filters.byProperties(["Permissions", "ActivityTypes", "StatusTypes"]),
|
||||
Permissions: Filters.byProperties(['getHighestRole']),
|
||||
|
@ -126,7 +126,6 @@ const KnownModules = {
|
|||
ExperimentsManager: Filters.byProperties(['isDeveloper']),
|
||||
CurrentExperiment: Filters.byProperties(['getExperimentId']),
|
||||
|
||||
|
||||
/* Images, Avatars and Utils */
|
||||
ImageResolver: Filters.byProperties(["getUserAvatarURL"]),
|
||||
ImageUtils: Filters.byProperties(['getSizedImageSrc']),
|
||||
|
@ -180,7 +179,6 @@ const KnownModules = {
|
|||
URLParser: Filters.byProperties(['Url', 'parse']),
|
||||
ExtraURLs: Filters.byProperties(['getArticleURL']),
|
||||
|
||||
|
||||
/* DOM/React Components */
|
||||
/* ==================== */
|
||||
UserSettingsWindow: Filters.byProperties(['open', 'updateAccount']),
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
export default class {
|
||||
|
||||
}
|
|
@ -17,22 +17,37 @@ export default class ErrorEvent extends Event {
|
|||
this.showStack = false; // For error modal
|
||||
}
|
||||
|
||||
/**
|
||||
* The module the error occured in.
|
||||
*/
|
||||
get module() {
|
||||
return this.args.module;
|
||||
}
|
||||
|
||||
/**
|
||||
* A message describing the error.
|
||||
*/
|
||||
get message() {
|
||||
return this.args.message;
|
||||
}
|
||||
|
||||
/**
|
||||
* The original error object.
|
||||
*/
|
||||
get err() {
|
||||
return this.args.err;
|
||||
}
|
||||
|
||||
/**
|
||||
* A trace showing which functions were called when the error occured.
|
||||
*/
|
||||
get stackTrace() {
|
||||
return this.err.stack;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of event.
|
||||
*/
|
||||
get __eventType() {
|
||||
return 'error';
|
||||
}
|
||||
|
|
|
@ -17,14 +17,23 @@ export default class Event {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* An object containing information about the event.
|
||||
*/
|
||||
get event() {
|
||||
return this.__eventInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* The first argument that was passed to the constructor, which contains information about the event.
|
||||
*/
|
||||
get args() {
|
||||
return this.event.args[0];
|
||||
}
|
||||
|
||||
get __eventType() { return null; }
|
||||
/**
|
||||
* The type of event.
|
||||
*/
|
||||
get __eventType() { return undefined; }
|
||||
|
||||
}
|
||||
|
|
|
@ -12,10 +12,16 @@ import Event from './event';
|
|||
|
||||
export default class SettingsUpdatedEvent extends Event {
|
||||
|
||||
/**
|
||||
* An array of SettingUpdated events.
|
||||
*/
|
||||
get updatedSettings() {
|
||||
return this.args.updatedSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of event.
|
||||
*/
|
||||
get __eventType() {
|
||||
return 'settings-updated';
|
||||
}
|
||||
|
|
|
@ -12,38 +12,65 @@ import Event from './event';
|
|||
|
||||
export default class SettingUpdatedEvent extends Event {
|
||||
|
||||
/**
|
||||
* The set containing the setting that was updated.
|
||||
*/
|
||||
get set() {
|
||||
return this.args.set;
|
||||
}
|
||||
|
||||
/**
|
||||
* The ID of the set containing the setting that was updated.
|
||||
*/
|
||||
get set_id() {
|
||||
return this.args.set.id;
|
||||
return this.set.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The category containing the setting that was updated.
|
||||
*/
|
||||
get category() {
|
||||
return this.args.category;
|
||||
}
|
||||
|
||||
/**
|
||||
* The ID of the category containing the setting that was updated.
|
||||
*/
|
||||
get category_id() {
|
||||
return this.args.category.id;
|
||||
return this.category.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The setting that was updated.
|
||||
*/
|
||||
get setting() {
|
||||
return this.args.setting;
|
||||
}
|
||||
|
||||
/**
|
||||
* The ID of the setting that was updated.
|
||||
*/
|
||||
get setting_id() {
|
||||
return this.args.setting.id;
|
||||
return this.setting.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The setting's new value.
|
||||
*/
|
||||
get value() {
|
||||
return this.args.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The setting's old value.
|
||||
*/
|
||||
get old_value() {
|
||||
return this.args.old_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of event.
|
||||
*/
|
||||
get __eventType() {
|
||||
return 'setting-updated';
|
||||
}
|
||||
|
|
|
@ -2,3 +2,4 @@ export { default as SettingsSet } from './settingsset';
|
|||
export { default as SettingsCategory } from './settingscategory';
|
||||
export { default as Setting } from './setting';
|
||||
export { default as SettingsScheme } from './settingsscheme';
|
||||
export * from './types';
|
||||
|
|
|
@ -14,22 +14,29 @@ export default class MultipleChoiceOption {
|
|||
|
||||
constructor(args) {
|
||||
this.args = args.args || args;
|
||||
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* This option's ID.
|
||||
*/
|
||||
get id() {
|
||||
return this.args.id || this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* A string describing this option.
|
||||
*/
|
||||
get text() {
|
||||
return this.args.text;
|
||||
}
|
||||
|
||||
/**
|
||||
* The value to return when this option is active.
|
||||
*/
|
||||
get value() {
|
||||
return this.args.value;
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new MultipleChoiceOption(Utils.deepclone(this.args));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,35 +15,49 @@ export default class SettingsScheme {
|
|||
constructor(args) {
|
||||
this.args = args.args || args;
|
||||
|
||||
this.args.settings = this.settings.map(({ category, settings }) => ({
|
||||
category, settings: settings.map(({ id, value }) => ({
|
||||
id, value
|
||||
}))
|
||||
}));
|
||||
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* The scheme's ID.
|
||||
*/
|
||||
get id() {
|
||||
return this.args.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The URL of the scheme's icon. This should be a base64 encoded data URI.
|
||||
*/
|
||||
get icon_url() {
|
||||
return this.args.icon_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* The scheme's name.
|
||||
*/
|
||||
get name() {
|
||||
return this.args.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* A string to be displayed under the scheme.
|
||||
*/
|
||||
get hint() {
|
||||
return this.args.hint;
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of stripped settings categories this scheme manages.
|
||||
*/
|
||||
get settings() {
|
||||
return this.args.settings || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this scheme's values are currently applied to a set.
|
||||
* @param {SettingsSet} set The set to check
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isActive(set) {
|
||||
for (let schemeCategory of this.settings) {
|
||||
const category = set.categories.find(c => c.category === schemeCategory.category);
|
||||
|
@ -66,12 +80,13 @@ export default class SettingsScheme {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies this scheme's values to a set.
|
||||
* @param {SettingsSet} set The set to merge this scheme's values into
|
||||
* @return {Promise}
|
||||
*/
|
||||
applyTo(set) {
|
||||
return set.merge({ settings: this.settings });
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new SettingsScheme(Utils.deepclone(this.args));
|
||||
return set.merge(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -446,7 +446,7 @@ export default class SettingsSet {
|
|||
text: this.text,
|
||||
headertext: this.headertext,
|
||||
settings: this.categories.map(category => category.clone()),
|
||||
schemes: this.schemes.map(scheme => scheme.clone())
|
||||
schemes: this.schemes
|
||||
}, ...merge);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
export { default as BoolSetting } from './bool';
|
||||
export { default as StringSetting } from './text';
|
||||
export { default as NumberSetting } from './number';
|
||||
export { default as DropdownSetting } from './dropdown';
|
||||
export { default as RadioSetting } from './radio';
|
||||
export { default as SliderSetting } from './slider';
|
||||
export { default as ColourSetting } from './colour';
|
||||
export { default as KeybindSetting } from './keybind';
|
||||
export { default as FileSetting } from './file';
|
||||
export { default as GuildSetting } from './guild';
|
||||
export { default as ArraySetting } from './array';
|
||||
export { default as CustomSetting } from './custom';
|
|
@ -11,18 +11,42 @@
|
|||
import Setting from './basesetting';
|
||||
import Combokeys from 'combokeys';
|
||||
|
||||
let keybindsPaused = false;
|
||||
|
||||
export default class KeybindSetting extends Setting {
|
||||
|
||||
constructor(args, ...merge) {
|
||||
super(args, ...merge);
|
||||
|
||||
this.__keybind_activated = this.__keybind_activated.bind(this);
|
||||
|
||||
this.combokeys = new Combokeys(document);
|
||||
this.combokeys.bind(this.value, event => this.emit('keybind-activated', event));
|
||||
this.combokeys.bind(this.value, this.__keybind_activated);
|
||||
}
|
||||
|
||||
/**
|
||||
* The value to use when the setting doesn't have a value.
|
||||
*/
|
||||
get defaultValue() {
|
||||
return '';
|
||||
}
|
||||
|
||||
setValueHook() {
|
||||
this.combokeys.reset();
|
||||
this.combokeys.bind(this.value, event => this.emit('keybind-activated', event));
|
||||
this.combokeys.bind(this.value, this.__keybind_activated);
|
||||
}
|
||||
|
||||
__keybind_activated(event) {
|
||||
if (KeybindSetting.paused) return;
|
||||
this.emit('keybind-activated', event);
|
||||
}
|
||||
|
||||
static get paused() {
|
||||
return keybindsPaused;
|
||||
}
|
||||
|
||||
static set paused(paused) {
|
||||
keybindsPaused = paused;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,16 +25,21 @@
|
|||
width: 16px;
|
||||
filter: brightness(10);
|
||||
cursor: pointer;
|
||||
.theme-light [class*="topSectionNormal-"] & {
|
||||
}
|
||||
|
||||
.theme-light [class*="topSectionNormal-"] .bd-profile-badge-developer,
|
||||
.theme-light [class*="topSectionNormal-"] .bd-profile-badge-contributor,
|
||||
.theme-light .bd-message-badge-developer,
|
||||
.theme-light .bd-message-badge-contributor {
|
||||
background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iQ2FscXVlXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjAwMCAyMDAwIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAyMDAwIDIwMDAiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxnPjxwYXRoIGZpbGw9IiMzRTgyRTUiIGQ9Ik0xNDAyLjIsNjMxLjdjLTkuNy0zNTMuNC0yODYuMi00OTYtNjQyLjYtNDk2SDY4LjR2NzE0LjFsNDQyLDM5OFY0OTAuN2gyNTdjMjc0LjUsMCwyNzQuNSwzNDQuOSwwLDM0NC45SDU5Ny42djMyOS41aDE2OS44YzI3NC41LDAsMjc0LjUsMzQ0LjgsMCwzNDQuOGgtNjk5djM1NC45aDY5MS4yYzM1Ni4zLDAsNjMyLjgtMTQyLjYsNjQyLjYtNDk2YzAtMTYyLjYtNDQuNS0yODQuMS0xMjIuOS0zNjguNkMxMzU3LjcsOTE1LjgsMTQwMi4yLDc5NC4zLDE0MDIuMiw2MzEuN3oiLz48cGF0aCBmaWxsPSIjYmJiYmJiIiBkPSJNMTI2Mi41LDEzNS4yTDEyNjIuNSwxMzUuMmwtNzYuOCwwYzI2LjYsMTMuMyw1MS43LDI4LjEsNzUsNDQuM2M3MC43LDQ5LjEsMTI2LjEsMTExLjUsMTY0LjYsMTg1LjNjMzkuOSw3Ni42LDYxLjUsMTY1LjYsNjQuMywyNjQuNmwwLDEuMnYxLjJjMCwxNDEuMSwwLDU5Ni4xLDAsNzM3LjF2MS4ybDAsMS4yYy0yLjcsOTktMjQuMywxODgtNjQuMywyNjQuNmMtMzguNSw3My44LTkzLjgsMTM2LjItMTY0LjYsMTg1LjNjLTIyLjYsMTUuNy00Ni45LDMwLjEtNzIuNiw0My4xaDcyLjVjMzQ2LjIsMS45LDY3MS0xNzEuMiw2NzEtNTY3LjlWNzE2LjdDMTkzMy41LDMxMi4yLDE2MDguNywxMzUuMiwxMjYyLjUsMTM1LjJ6Ii8+PC9nPjwvc3ZnPg==');
|
||||
filter: none;
|
||||
}
|
||||
}
|
||||
|
||||
.bd-message-badges-wrap {
|
||||
display: inline-block;
|
||||
margin-left: 6px;
|
||||
height: 11px;
|
||||
|
||||
.bd-message-badge-developer,
|
||||
.bd-message-badge-contributor {
|
||||
width: 12px;
|
||||
|
|
|
@ -52,12 +52,12 @@
|
|||
|
||||
.bd-settings-button-btn {
|
||||
background-image: $logoBigBw;
|
||||
background-size: 100% 100%;
|
||||
filter: none;
|
||||
opacity: 1;
|
||||
width: 130px;
|
||||
height: 80px;
|
||||
background-size: 100% 100%;
|
||||
margin-left: 20px;
|
||||
height: 43px;
|
||||
margin: 18px 0 17px 20px;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,3 +4,4 @@
|
|||
@import './card.scss';
|
||||
@import './tooltips.scss';
|
||||
@import './settings-schemes.scss';
|
||||
@import './updater.scss';
|
||||
|
|
|
@ -19,18 +19,53 @@ bd-tooltips {
|
|||
position: absolute;
|
||||
word-wrap: break-word;
|
||||
z-index: 9001;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.bd-tooltip:after {
|
||||
.bd-tooltip-arrow {
|
||||
border: 5px solid transparent;
|
||||
content: " ";
|
||||
height: 0;
|
||||
pointer-events: none;
|
||||
width: 0;
|
||||
border-top-color: #000;
|
||||
left: 50%;
|
||||
margin-left: -5px;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
}
|
||||
|
||||
&[x-placement^="top"] {
|
||||
margin-bottom: 10px;
|
||||
|
||||
.bd-tooltip-arrow {
|
||||
margin-left: -5px;
|
||||
border-top-color: #000;
|
||||
bottom: -10px;
|
||||
}
|
||||
}
|
||||
|
||||
&[x-placement^="bottom"] {
|
||||
margin-top: 10px;
|
||||
|
||||
.bd-tooltip-arrow {
|
||||
border-width: 0 5px 5px 5px;
|
||||
top: -5px;
|
||||
border-bottom-color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
&[x-placement^="right"] {
|
||||
margin-left: 10px;
|
||||
|
||||
.bd-tooltip-arrow {
|
||||
border-width: 5px 5px 5px 0;
|
||||
left: -5px;
|
||||
border-right-color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
&[x-placement^="left"] {
|
||||
margin-right: 10px;
|
||||
|
||||
.bd-tooltip-arrow {
|
||||
border-width: 5px 0 5px 5px;
|
||||
right: -5px;
|
||||
border-left-color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
.bd-updaterview {
|
||||
p {
|
||||
margin: 0 0 10px;
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
|
@ -8,37 +8,14 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { Events, WebpackModules, EventListener, ReactComponents, Renderer } from 'modules';
|
||||
import { Events, WebpackModules, EventListener, DiscordApi, ReactComponents, Renderer } from 'modules';
|
||||
import { ClientLogger as Logger } from 'common';
|
||||
import Reflection from './reflection';
|
||||
import DOM from './dom';
|
||||
import VueInjector from './vueinjector';
|
||||
import EditedTimeStamp from './components/common/EditedTimestamp.vue';
|
||||
import Autocomplete from './components/common/Autocomplete.vue';
|
||||
|
||||
class TempApi {
|
||||
static get currentGuildId() {
|
||||
try {
|
||||
return WebpackModules.getModuleByName('SelectedGuildStore').getGuildId();
|
||||
} catch (err) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
static get currentChannelId() {
|
||||
try {
|
||||
return WebpackModules.getModuleByName('SelectedChannelStore').getChannelId();
|
||||
} catch (err) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
static get currentUserId() {
|
||||
try {
|
||||
return WebpackModules.getModuleByName('UserStore').getCurrentUser().id;
|
||||
} catch (err) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default class extends EventListener {
|
||||
|
||||
constructor(args) {
|
||||
|
@ -54,22 +31,19 @@ export default class extends EventListener {
|
|||
}
|
||||
|
||||
get eventBindings() {
|
||||
return [{ id: 'gkh:keyup', callback: this.injectAutocomplete }];
|
||||
/*
|
||||
return [
|
||||
{ id: 'server-switch', callback: this.manipAll },
|
||||
{ id: 'channel-switch', callback: this.manipAll },
|
||||
{ id: 'discord:MESSAGE_CREATE', callback: this.markupInjector },
|
||||
{ id: 'discord:MESSAGE_UPDATE', callback: this.markupInjector },
|
||||
// { id: 'server-switch', callback: this.manipAll },
|
||||
// { id: 'channel-switch', callback: this.manipAll },
|
||||
// { id: 'discord:MESSAGE_CREATE', callback: this.markupInjector },
|
||||
// { id: 'discord:MESSAGE_UPDATE', callback: this.markupInjector },
|
||||
{ id: 'gkh:keyup', callback: this.injectAutocomplete }
|
||||
];
|
||||
*/
|
||||
}
|
||||
|
||||
manipAll() {
|
||||
try {
|
||||
this.appMount.setAttribute('guild-id', TempApi.currentGuildId);
|
||||
this.appMount.setAttribute('channel-id', TempApi.currentChannelId);
|
||||
this.appMount.setAttribute('guild-id', DiscordApi.currentGuild.id);
|
||||
this.appMount.setAttribute('channel-id', DiscordApi.currentChannel.id);
|
||||
this.setIds();
|
||||
this.makeMutable();
|
||||
} catch (err) {
|
||||
|
@ -128,13 +102,11 @@ export default class extends EventListener {
|
|||
if (markup.ets) {
|
||||
const etsRoot = document.createElement('span');
|
||||
markup.clone.appendChild(etsRoot);
|
||||
VueInjector.inject(
|
||||
etsRoot,
|
||||
DOM.createElement('span', null, 'test'),
|
||||
{ EditedTimeStamp },
|
||||
`<EditedTimeStamp ets="${markup.ets}"/>`,
|
||||
true
|
||||
);
|
||||
VueInjector.inject(etsRoot, {
|
||||
components: { EditedTimeStamp },
|
||||
data: { ets: markup.ets },
|
||||
template: '<EditedTimeStamp :ets="ets" />'
|
||||
});
|
||||
}
|
||||
|
||||
Events.emit('ui:mutable:.markup', markup.clone);
|
||||
|
@ -174,14 +146,14 @@ export default class extends EventListener {
|
|||
const userTest = Reflection(msgGroup).prop('user');
|
||||
if (!userTest) return;
|
||||
msgGroup.setAttribute('data-author-id', userTest.id);
|
||||
if (userTest.id === TempApi.currentUserId) msgGroup.setAttribute('data-currentuser', true);
|
||||
if (userTest.id === DiscordApi.currentUserId) msgGroup.setAttribute('data-currentuser', true);
|
||||
return;
|
||||
}
|
||||
msg.setAttribute('data-message-id', messageid);
|
||||
const msgGroup = msg.closest('.message-group');
|
||||
if (!msgGroup) return;
|
||||
msgGroup.setAttribute('data-author-id', authorid);
|
||||
if (authorid === TempApi.currentUserId) msgGroup.setAttribute('data-currentuser', true);
|
||||
if (authorid === DiscordApi.currentUser.id) msgGroup.setAttribute('data-currentuser', true);
|
||||
}
|
||||
|
||||
setUserId(user) {
|
||||
|
@ -189,7 +161,7 @@ export default class extends EventListener {
|
|||
const userid = Reflection(user).prop('user.id');
|
||||
if (!userid) return;
|
||||
user.setAttribute('data-user-id', userid);
|
||||
const currentUser = userid === TempApi.currentUserId;
|
||||
const currentUser = userid === DiscordApi.currentUser.id;
|
||||
if (currentUser) user.setAttribute('data-currentuser', true);
|
||||
Events.emit('ui:useridset', user);
|
||||
}
|
||||
|
@ -214,12 +186,11 @@ export default class extends EventListener {
|
|||
const parent = document.querySelector('[class*="channelTextArea"] > [class*="inner"]');
|
||||
if (!parent) return;
|
||||
parent.append(root);
|
||||
VueInjector.inject(
|
||||
root,
|
||||
DOM.createElement('span'),
|
||||
{ Autocomplete },
|
||||
`<Autocomplete initial="${e.target.value}"/>`,
|
||||
true
|
||||
);
|
||||
VueInjector.inject(root, {
|
||||
components: { Autocomplete },
|
||||
data: { initial: e.target.value },
|
||||
template: '<Autocomplete :initial="initial" />'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,17 +8,22 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { Events } from 'modules';
|
||||
import { Utils } from 'common';
|
||||
|
||||
let items = 0;
|
||||
|
||||
const BdMenuItems = new class {
|
||||
export const BdMenuItems = new class {
|
||||
|
||||
constructor() {
|
||||
window.bdmenu = this;
|
||||
|
||||
this.items = [];
|
||||
|
||||
const updater = this.add({category: 'Updates', contentid: 'updater', text: 'Updates available!', hidden: true});
|
||||
Events.on('update-check-end', () => updater.hidden = true);
|
||||
Events.on('updates-available', () => updater.hidden = false);
|
||||
|
||||
this.addSettingsSet('Internal', 'core', 'Core');
|
||||
this.addSettingsSet('Internal', 'ui', 'UI');
|
||||
this.addSettingsSet('Internal', 'emotes', 'Emotes');
|
||||
|
@ -28,7 +33,14 @@ const BdMenuItems = new class {
|
|||
this.add({category: 'External', contentid: 'themes', text: 'Themes'});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an item to the menu.
|
||||
* @param {Object} item The item to add to the menu
|
||||
* @return {Object}
|
||||
*/
|
||||
add(item) {
|
||||
if (this.items.includes(item)) return item;
|
||||
|
||||
item.id = items++;
|
||||
item.contentid = item.contentid || (items++ + '');
|
||||
item.active = false;
|
||||
|
@ -39,6 +51,13 @@ const BdMenuItems = new class {
|
|||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a settings set to the menu.
|
||||
* @param {String} category The category to display this item under
|
||||
* @param {SettingsSet} set The settings set to display when this item is active
|
||||
* @param {String} text The text to display in the menu (optional)
|
||||
* @return {Object} The item that was added
|
||||
*/
|
||||
addSettingsSet(category, set, text) {
|
||||
return this.add({
|
||||
category, set,
|
||||
|
@ -46,16 +65,25 @@ const BdMenuItems = new class {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a Vue component to the menu.
|
||||
* @param {String} category The category to display this item under
|
||||
* @param {String} text The text to display in the menu
|
||||
* @param {Object} component The Vue component to display when this item is active
|
||||
* @return {Object} The item that was added
|
||||
*/
|
||||
addVueComponent(category, text, component) {
|
||||
return this.add({
|
||||
category, text, component
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item from the menu.
|
||||
* @param {Object} item The item to remove from the menu
|
||||
*/
|
||||
remove(item) {
|
||||
Utils.removeFromArray(this.items, item);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export { BdMenuItems };
|
||||
|
|
|
@ -8,48 +8,21 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { Events, WebpackModules, DiscordApi } from 'modules';
|
||||
import { Utils } from 'common';
|
||||
import { remote } from 'electron';
|
||||
import DOM from './dom';
|
||||
import Vue from './vue';
|
||||
import { BdSettingsWrapper } from './components';
|
||||
import BdModals from './components/bd/BdModals.vue';
|
||||
import { Events, WebpackModules } from 'modules';
|
||||
import { Utils } from 'common';
|
||||
import AutoManip from './automanip';
|
||||
import { remote } from 'electron';
|
||||
|
||||
class TempApi {
|
||||
static get currentGuild() {
|
||||
try {
|
||||
const currentGuildId = WebpackModules.getModuleByName('SelectedGuildStore').getGuildId();
|
||||
return WebpackModules.getModuleByName('GuildStore').getGuild(currentGuildId);
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
static get currentChannel() {
|
||||
try {
|
||||
const currentChannelId = WebpackModules.getModuleByName('SelectedChannelStore').getChannelId();
|
||||
return WebpackModules.getModuleByName('ChannelStore').getChannel(currentChannelId);
|
||||
} catch (err) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
static get currentUserId() {
|
||||
try {
|
||||
return WebpackModules.getModuleByName('UserStore').getCurrentUser().id;
|
||||
} catch (err) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
import { BdSettingsWrapper, BdModals } from './components';
|
||||
|
||||
export default class {
|
||||
|
||||
static initUiEvents() {
|
||||
this.pathCache = {
|
||||
isDm: null,
|
||||
server: TempApi.currentGuild,
|
||||
channel: TempApi.currentChannel
|
||||
server: DiscordApi.currentGuild,
|
||||
channel: DiscordApi.currentChannel
|
||||
};
|
||||
window.addEventListener('keyup', e => Events.emit('gkh:keyup', e));
|
||||
this.autoManip = new AutoManip();
|
||||
|
@ -67,9 +40,9 @@ export default class {
|
|||
if (!remote.BrowserWindow.getFocusedWindow()) return;
|
||||
clearInterval(ehookInterval);
|
||||
remote.BrowserWindow.getFocusedWindow().webContents.on('did-navigate-in-page', (e, url, isMainFrame) => {
|
||||
const { currentGuild, currentChannel } = TempApi;
|
||||
const { currentGuild, currentChannel } = DiscordApi;
|
||||
if (!this.pathCache.server) {
|
||||
Events.emit('server-switch', { 'server': currentGuild, 'channel': currentChannel });
|
||||
Events.emit('server-switch', { server: currentGuild, channel: currentChannel });
|
||||
this.pathCache.server = currentGuild;
|
||||
this.pathCache.channel = currentChannel;
|
||||
return;
|
||||
|
@ -84,7 +57,7 @@ export default class {
|
|||
currentGuild.id &&
|
||||
this.pathCache.server &&
|
||||
this.pathCache.server.id !== currentGuild.id) {
|
||||
Events.emit('server-switch', { 'server': currentGuild, 'channel': currentChannel });
|
||||
Events.emit('server-switch', { server: currentGuild, channel: currentChannel });
|
||||
this.pathCache.server = currentGuild;
|
||||
this.pathCache.channel = currentChannel;
|
||||
return;
|
||||
|
@ -110,19 +83,19 @@ export default class {
|
|||
DOM.createElement('div', null, 'bd-modals').appendTo(DOM.bdModals);
|
||||
DOM.createElement('bd-tooltips').appendTo(DOM.bdBody);
|
||||
|
||||
const modals = new Vue({
|
||||
this.modals = new Vue({
|
||||
el: '#bd-modals',
|
||||
components: { BdModals },
|
||||
template: '<BdModals />'
|
||||
});
|
||||
|
||||
const vueInstance = new Vue({
|
||||
this.vueInstance = new Vue({
|
||||
el: '#bd-settings',
|
||||
components: { BdSettingsWrapper },
|
||||
template: '<BdSettingsWrapper />'
|
||||
});
|
||||
|
||||
return vueInstance;
|
||||
return this.vueInstance;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,13 +19,14 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// Imports
|
||||
import { Events } from 'modules';
|
||||
import { Modals } from 'ui';
|
||||
import { Modal } from '../common';
|
||||
import { MiError } from '../common/MaterialIcon';
|
||||
import ErrorModal from './modals/ErrorModal.vue';
|
||||
import { Modal } from './common';
|
||||
import { MiError } from './common/MaterialIcon';
|
||||
import ErrorModal from './bd/modals/ErrorModal.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
|
@ -49,6 +49,7 @@
|
|||
<CssEditorView v-if="item.contentid === 'css'" />
|
||||
<PluginsView v-if="item.contentid === 'plugins'" />
|
||||
<ThemesView v-if="item.contentid === 'themes'" />
|
||||
<UpdaterView v-if="item.contentid === 'updater'" />
|
||||
</div>
|
||||
</ContentColumn>
|
||||
</SidebarView>
|
||||
|
@ -60,7 +61,7 @@
|
|||
import { Settings } from 'modules';
|
||||
import { BdMenuItems } from 'ui';
|
||||
import { SidebarView, Sidebar, SidebarItem, ContentColumn } from './sidebar';
|
||||
import { SettingsWrapper, SettingsPanel, CssEditorView, PluginsView, ThemesView } from './bd';
|
||||
import { SettingsWrapper, SettingsPanel, CssEditorView, PluginsView, ThemesView, UpdaterView } from './bd';
|
||||
import { SvgX, MiGithubCircle, MiWeb, MiClose, MiTwitterCircle } from './common';
|
||||
|
||||
export default {
|
||||
|
@ -79,7 +80,7 @@
|
|||
props: ['active', 'close'],
|
||||
components: {
|
||||
SidebarView, Sidebar, SidebarItem, ContentColumn,
|
||||
SettingsWrapper, SettingsPanel, CssEditorView, PluginsView, ThemesView,
|
||||
SettingsWrapper, SettingsPanel, CssEditorView, PluginsView, ThemesView, UpdaterView,
|
||||
MiGithubCircle, MiWeb, MiClose, MiTwitterCircle
|
||||
},
|
||||
computed: {
|
||||
|
|
|
@ -9,11 +9,11 @@
|
|||
*/
|
||||
|
||||
<template>
|
||||
<div :class="{'bd-profile-badges-wrap': hasBadges == 'false'}">
|
||||
<div :class="{'bd-profile-badges-wrap': !hasBadges}">
|
||||
<div class="bd-profile-badges">
|
||||
<div v-if="developer == 'true'" v-tooltip="'BetterDiscord Developer'" class="bd-profile-badge bd-profile-badge-developer" @click="onClick"></div>
|
||||
<div v-else-if="webdev == 'true'" v-tooltip="'BetterDiscord Web Developer'" class="bd-profile-badge bd-profile-badge-developer" @click="onClick"></div>
|
||||
<div v-else-if="contributor == 'true'" v-tooltip="'BetterDiscord Contributor'" class="bd-profile-badge bd-profile-badge-contributor" @click="onClick"></div>
|
||||
<div v-if="developer" v-tooltip="'BetterDiscord Developer'" class="bd-profile-badge bd-profile-badge-developer" @click="onClick"></div>
|
||||
<div v-else-if="webdev" v-tooltip="'BetterDiscord Web Developer'" class="bd-profile-badge bd-profile-badge-developer" @click="onClick"></div>
|
||||
<div v-else-if="contributor" v-tooltip="'BetterDiscord Contributor'" class="bd-profile-badge bd-profile-badge-contributor" @click="onClick"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
|
||||
<template>
|
||||
<div class="bd-message-badges-wrap">
|
||||
<div v-if="developer == 'true'" v-tooltip="'BetterDiscord Developer'" class="bd-message-badge bd-message-badge-developer" @click="onClick"></div>
|
||||
<div v-else-if="webdev == 'true'" v-tooltip="'BetterDiscord Web Developer'" class="bd-message-badge bd-message-badge-developer" @click="onClick"></div>
|
||||
<div v-else-if="contributor == 'true'" v-tooltip="'BetterDiscord Contributor'" class="bd-message-badge bd-message-badge-contributor" @click="onClick"></div>
|
||||
<div v-if="developer" v-tooltip="'BetterDiscord Developer'" class="bd-message-badge bd-message-badge-developer" @click="onClick"></div>
|
||||
<div v-else-if="webdev" v-tooltip="'BetterDiscord Web Developer'" class="bd-message-badge bd-message-badge-developer" @click="onClick"></div>
|
||||
<div v-else-if="contributor" v-tooltip="'BetterDiscord Contributor'" class="bd-message-badge bd-message-badge-contributor" @click="onClick"></div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
<template>
|
||||
<SettingsWrapper headertext="CSS Editor">
|
||||
<div class="bd-css-editor">
|
||||
<div v-if="CssEditor.error" class="bd-form-item">
|
||||
<div v-if="error" class="bd-form-item">
|
||||
<h5 style="margin-bottom: 10px;">Compiler error</h5>
|
||||
<div class="bd-err bd-pre-wrap"><div class="bd-pre">{{ CssEditor.error.formatted }}</div></div>
|
||||
<div class="bd-err bd-pre-wrap"><div class="bd-pre">{{ error.formatted }}</div></div>
|
||||
<div class="bd-form-divider"></div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -21,16 +21,12 @@
|
|||
</template>
|
||||
<script>
|
||||
// Imports
|
||||
import { ClientLogger as Logger } from 'common';
|
||||
import { shell } from 'electron';
|
||||
import Card from './Card.vue';
|
||||
import { Button, ButtonGroup, SettingSwitch, MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension } from '../common';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
settingsOpen: false
|
||||
}
|
||||
},
|
||||
props: ['plugin', 'togglePlugin', 'reloadPlugin', 'deletePlugin', 'showSettings'],
|
||||
components: {
|
||||
Card, Button, ButtonGroup, SettingSwitch, MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension
|
||||
|
@ -38,9 +34,9 @@
|
|||
methods: {
|
||||
editPlugin() {
|
||||
try {
|
||||
shell.openItem(this.plugin.pluginPath);
|
||||
shell.openItem(this.plugin.contentPath);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
Logger.err('PluginCard', [`Error opening plugin directory ${this.plugin.contentPath}:`, err]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,17 +37,19 @@
|
|||
// Imports
|
||||
import { PluginManager } from 'modules';
|
||||
import { Modals } from 'ui';
|
||||
import { SettingsWrapper } from './';
|
||||
import PluginCard from './PluginCard.vue';
|
||||
import { ClientLogger as Logger } from 'common';
|
||||
import { MiRefresh } from '../common';
|
||||
import SettingsWrapper from './SettingsWrapper.vue';
|
||||
import PluginCard from './PluginCard.vue';
|
||||
import RefreshBtn from '../common/RefreshBtn.vue';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
PluginManager,
|
||||
local: true,
|
||||
localPlugins: PluginManager.localPlugins
|
||||
}
|
||||
};
|
||||
},
|
||||
components: {
|
||||
SettingsWrapper, PluginCard,
|
||||
|
@ -62,32 +64,32 @@
|
|||
this.local = false;
|
||||
},
|
||||
async refreshLocal() {
|
||||
await PluginManager.refreshPlugins();
|
||||
await this.PluginManager.refreshPlugins();
|
||||
},
|
||||
async refreshOnline() {
|
||||
|
||||
// TODO
|
||||
},
|
||||
async togglePlugin(plugin) {
|
||||
// TODO Display error if plugin fails to start/stop
|
||||
// TODO: display error if plugin fails to start/stop
|
||||
const enabled = plugin.enabled;
|
||||
try {
|
||||
await plugin.enabled ? PluginManager.stopPlugin(plugin) : PluginManager.startPlugin(plugin);
|
||||
await enabled ? this.PluginManager.stopPlugin(plugin) : this.PluginManager.startPlugin(plugin);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
Logger.err('PluginsView', [`Error ${enabled ? 'stopp' : 'start'}ing plugin ${plugin.name}:`, err]);
|
||||
}
|
||||
},
|
||||
async reloadPlugin(plugin) {
|
||||
try {
|
||||
await PluginManager.reloadPlugin(plugin);
|
||||
await this.PluginManager.reloadPlugin(plugin);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
Logger.err('PluginsView', [`Error reloading plugin ${plugin.name}:`, err]);
|
||||
}
|
||||
},
|
||||
async deletePlugin(plugin, unload) {
|
||||
try {
|
||||
if (unload) await PluginManager.unloadPlugin(plugin);
|
||||
else await PluginManager.deletePlugin(plugin);
|
||||
await unload ? this.PluginManager.unloadPlugin(plugin) : this.PluginManager.deletePlugin(plugin);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
Logger.err('PluginsView', [`Error ${unload ? 'unload' : 'delet'}ing plugin ${plugin.name}:`, err]);
|
||||
}
|
||||
},
|
||||
showSettings(plugin, dont_clone) {
|
||||
|
|
|
@ -26,11 +26,6 @@
|
|||
import { Button, ButtonGroup, SettingSwitch, MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension } from '../common';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
settingsOpen: false
|
||||
}
|
||||
},
|
||||
props: ['theme', 'toggleTheme', 'reloadTheme', 'deleteTheme', 'showSettings'],
|
||||
components: {
|
||||
Card, Button, ButtonGroup, SettingSwitch, MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension
|
||||
|
@ -40,7 +35,7 @@
|
|||
try {
|
||||
shell.openItem(this.theme.themePath);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
Logger.err('ThemeCard', [`Error opening theme directory ${this.theme.contentPath}:`, err]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,17 +37,19 @@
|
|||
// Imports
|
||||
import { ThemeManager } from 'modules';
|
||||
import { Modals } from 'ui';
|
||||
import { SettingsWrapper } from './';
|
||||
import { ClientLogger as Logger } from 'common';
|
||||
import { MiRefresh } from '../common';
|
||||
import SettingsWrapper from './SettingsWrapper.vue';
|
||||
import ThemeCard from './ThemeCard.vue';
|
||||
import RefreshBtn from '../common/RefreshBtn.vue';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
ThemeManager,
|
||||
local: true,
|
||||
localThemes: ThemeManager.localThemes
|
||||
}
|
||||
};
|
||||
},
|
||||
components: {
|
||||
SettingsWrapper, ThemeCard,
|
||||
|
@ -62,33 +64,31 @@
|
|||
this.local = false;
|
||||
},
|
||||
async refreshLocal() {
|
||||
await ThemeManager.refreshThemes();
|
||||
await this.ThemeManager.refreshThemes();
|
||||
},
|
||||
async refreshOnline() {
|
||||
|
||||
// TODO
|
||||
},
|
||||
async toggleTheme(theme) {
|
||||
// TODO Display error if theme fails to enable/disable
|
||||
// TODO: display error if theme fails to enable/disable
|
||||
try {
|
||||
await theme.enabled ? ThemeManager.disableTheme(theme) : ThemeManager.enableTheme(theme);
|
||||
await theme.enabled ? this.ThemeManager.disableTheme(theme) : this.ThemeManager.enableTheme(theme);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
Logger.err('ThemesView', [`Error ${enabled ? 'stopp' : 'start'}ing theme ${theme.name}:`, err]);
|
||||
}
|
||||
},
|
||||
async reloadTheme(theme, reload) {
|
||||
try {
|
||||
if (reload) await ThemeManager.reloadTheme(theme);
|
||||
else await theme.recompile();
|
||||
await reload ? this.ThemeManager.reloadTheme(theme) : theme.recompile();
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
Logger.err('ThemesView', [`Error ${reload ? 'reload' : 'recompil'}ing theme ${theme.name}:`, err]);
|
||||
}
|
||||
},
|
||||
async deleteTheme(theme, unload) {
|
||||
try {
|
||||
if (unload) await ThemeManager.unloadTheme(theme);
|
||||
else await ThemeManager.deleteTheme(theme);
|
||||
await unload ? this.ThemeManager.unloadTheme(theme) : this.ThemeManager.deleteTheme(theme);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
Logger.err('ThemesView', [`Error ${unload ? 'unload' : 'delet'}ing theme ${theme.name}:`, err]);
|
||||
}
|
||||
},
|
||||
showSettings(theme, dont_clone) {
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
/**
|
||||
* BetterDiscord Updater View Component
|
||||
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
|
||||
* All rights reserved.
|
||||
* https://betterdiscord.net
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
<template>
|
||||
<SettingsWrapper headertext="Updates">
|
||||
<div class="bd-flex bd-flex-col bd-updaterview">
|
||||
<div v-if="error" class="bd-form-item">
|
||||
<h5 style="margin-bottom: 10px;">Error installing updates</h5>
|
||||
<div class="bd-err bd-pre-wrap"><div class="bd-pre">{{ error.formatted }}</div></div>
|
||||
<div class="bd-form-divider"></div>
|
||||
</div>
|
||||
|
||||
<template v-if="updatesAvailable">
|
||||
<p>Version {{ newVersion }} is available. You are currently running version {{ currentVersion }}.</p>
|
||||
<FormButton :onClick="install" :loading="updating">Install</FormButton>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p>You're all up to date!</p>
|
||||
</template>
|
||||
</div>
|
||||
</SettingsWrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Globals, Updater } from 'modules';
|
||||
import { ClientLogger as Logger } from 'common';
|
||||
import SettingsWrapper from './SettingsWrapper.vue';
|
||||
import { FormButton } from '../common';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
currentVersion: Globals.version,
|
||||
updating: false,
|
||||
updater: Updater
|
||||
};
|
||||
},
|
||||
components: {
|
||||
SettingsWrapper,
|
||||
FormButton
|
||||
},
|
||||
computed: {
|
||||
updatesAvailable() {
|
||||
return this.updater.updatesAvailable;
|
||||
},
|
||||
newVersion() {
|
||||
return this.updater.latestVersion;
|
||||
},
|
||||
error() {
|
||||
return this.updater.error;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async install() {
|
||||
this.updating = true;
|
||||
try {
|
||||
await this.updater.update();
|
||||
} catch (err) {}
|
||||
this.updating = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -3,5 +3,6 @@ export { default as SettingsPanel } from './SettingsPanel.vue';
|
|||
export { default as CssEditorView } from './CssEditor.vue';
|
||||
export { default as PluginsView } from './PluginsView.vue';
|
||||
export { default as ThemesView } from './ThemesView.vue';
|
||||
export { default as UpdaterView } from './UpdaterView.vue';
|
||||
export { default as BdBadge } from './BdBadge.vue';
|
||||
export { default as BdMessageBadge } from './BdMessageBadge.vue';
|
||||
|
|
|
@ -24,8 +24,9 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { KeybindSetting } from 'structs';
|
||||
import { ClientIPC, ClientLogger as Logger } from 'common';
|
||||
import { shell } from 'electron';
|
||||
import { ClientIPC } from 'common';
|
||||
import Combokeys from 'combokeys';
|
||||
import CombokeysRecord from 'combokeys/plugins/record';
|
||||
|
||||
|
@ -49,6 +50,7 @@
|
|||
},
|
||||
watch: {
|
||||
active(active) {
|
||||
KeybindSetting.paused = active;
|
||||
if (active) combokeys.record(this.recorded);
|
||||
}
|
||||
},
|
||||
|
@ -65,7 +67,7 @@
|
|||
this.active = false;
|
||||
this.recordingValue = undefined;
|
||||
this.setting.value = sequence.join(' ');
|
||||
console.log('keypress', sequence);
|
||||
Logger.log('Keybind', ['Recorded sequence', sequence]);
|
||||
},
|
||||
getDisplayString(value) {
|
||||
if (!value) return;
|
||||
|
|
|
@ -34,6 +34,7 @@
|
|||
import { EmoteModule } from 'builtin';
|
||||
import { Events } from 'modules';
|
||||
import { DOM } from 'ui';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
|
@ -44,7 +45,7 @@
|
|||
open: false,
|
||||
selectedIndex: 0,
|
||||
sterm: ''
|
||||
}
|
||||
};
|
||||
},
|
||||
props: ['initial'],
|
||||
beforeMount() {
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export { default as BdSettingsWrapper } from './BdSettingsWrapper.vue';
|
||||
export { default as BdSettings } from './BdSettings.vue';
|
||||
export { default as BdModals } from './BdModals.vue';
|
||||
|
|
|
@ -186,4 +186,5 @@ export default class DOM {
|
|||
node.setAttribute(attribute.name, attribute.value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import { EventListener } from 'modules';
|
|||
import DOM from './dom';
|
||||
import { BdBadge, BdMessageBadge } from './components/bd';
|
||||
import VueInjector from './vueinjector';
|
||||
import contributors from '../data/contributors';
|
||||
|
||||
export default class extends EventListener {
|
||||
|
||||
|
@ -55,41 +56,37 @@ export default class extends EventListener {
|
|||
if (msgGroup.dataset.hasBadges) return;
|
||||
msgGroup.setAttribute('data-has-badges', true);
|
||||
if (!msgGroup.dataset.authorId) return;
|
||||
const c = this.contributors.find(c => c.id === msgGroup.dataset.authorId);
|
||||
const c = contributors.find(c => c.id === msgGroup.dataset.authorId);
|
||||
if (!c) return;
|
||||
const root = document.createElement('span');
|
||||
const wrapperParent = msgGroup.querySelector('.username-wrapper').parentElement;
|
||||
const usernameWrapper = msgGroup.querySelector('.username-wrapper');
|
||||
if (!usernameWrapper) return;
|
||||
const wrapperParent = usernameWrapper.parentElement;
|
||||
if (!wrapperParent || wrapperParent.children.length < 2) return;
|
||||
wrapperParent.insertBefore(root, wrapperParent.children[1]);
|
||||
const { developer, contributor, webdev } = c;
|
||||
VueInjector.inject(
|
||||
root,
|
||||
DOM.createElement('div', null, 'bdmessagebadges'),
|
||||
{ BdMessageBadge },
|
||||
`<BdMessageBadge developer="${developer}" webdev="${webdev}" contributor="${contributor}"/>`,
|
||||
true
|
||||
);
|
||||
VueInjector.inject(root, {
|
||||
components: { BdMessageBadge },
|
||||
data: { c },
|
||||
template: '<BdMessageBadge :developer="c.developer" :webdev="c.webdev" :contributor="c.contributor" />'
|
||||
});
|
||||
}
|
||||
|
||||
userlistBadge(e) {
|
||||
const c = this.contributors.find(c => c.id === e.dataset.userId);
|
||||
const c = contributors.find(c => c.id === e.dataset.userId);
|
||||
if (!c) return;
|
||||
const memberUsername = e.querySelector('.member-username');
|
||||
if (!memberUsername) return;
|
||||
const root = document.createElement('span');
|
||||
memberUsername.append(root);
|
||||
const { developer, contributor, webdev } = c;
|
||||
VueInjector.inject(
|
||||
root,
|
||||
DOM.createElement('div', null, 'bdmessagebadges'),
|
||||
{ BdMessageBadge },
|
||||
`<BdMessageBadge developer="${developer}" webdev="${webdev}" contributor="${contributor}"/>`,
|
||||
true
|
||||
);
|
||||
VueInjector.inject(root, {
|
||||
components: { BdMessageBadge },
|
||||
data: { c },
|
||||
template: '<BdMessageBadge :developer="c.developer" :webdev="c.webdev" :contributor="c.contributor" />'
|
||||
});
|
||||
}
|
||||
|
||||
inject(userid) {
|
||||
const c = this.contributors.find(c => c.id === userid);
|
||||
const c = contributors.find(c => c.id === userid);
|
||||
if (!c) return;
|
||||
|
||||
setTimeout(() => {
|
||||
|
@ -101,29 +98,16 @@ export default class extends EventListener {
|
|||
root = document.querySelector('[class*="headerInfo"]');
|
||||
}
|
||||
|
||||
const { developer, contributor, webdev } = c;
|
||||
|
||||
VueInjector.inject(
|
||||
root,
|
||||
DOM.createElement('div', null, 'bdprofilebadges'),
|
||||
{ BdBadge },
|
||||
`<BdBadge hasBadges="${hasBadges}" developer="${developer}" webdev="${webdev}" contributor="${contributor}"/>`
|
||||
);
|
||||
VueInjector.inject(root, {
|
||||
components: { BdBadge },
|
||||
data: { hasBadges, c },
|
||||
template: '<BdBadge :hasBadges="hasBadges" :developer="c.developer" :webdev="c.webdev" :contributor="c.contributor" />',
|
||||
}, DOM.createElement('div', null, 'bdprofilebadges'));
|
||||
}, 400);
|
||||
}
|
||||
|
||||
get contributors() {
|
||||
return [
|
||||
{ 'id': '81388395867156480', 'webdev': true, 'developer': true, 'contributor': true }, // Jiiks
|
||||
{ 'id': '98003542823944192', 'webdev': false, 'developer': true, 'contributor': true }, // Pohky
|
||||
{ 'id': '138850472541814784', 'webdev': true, 'developer': false, 'contributor': true }, // Hammock
|
||||
{ 'id': '249746236008169473', 'webdev': false, 'developer': true, 'contributor': true }, // Zerebos
|
||||
{ 'id': '125367412370440192', 'webdev': false, 'developer': true, 'contributor': true }, // Pierce
|
||||
{ 'id': '284056145272766465', 'webdev': false, 'developer': false, 'contributor': true }, // Samuel Elliott
|
||||
{ 'id': '184021060562321419', 'webdev': false, 'developer': false, 'contributor': true }, // Lilian Tedone
|
||||
{ 'id': '76052829285916672', 'webdev': false, 'developer': false, 'contributor': true }, // samfun123
|
||||
{ 'id': '171005991272316937', 'webdev': false, 'developer': false, 'contributor': true }, // samogot
|
||||
];
|
||||
return contributors;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,8 +16,9 @@ Vue.use(VTooltip, {
|
|||
defaultContainer: 'bd-tooltips',
|
||||
defaultClass: 'bd-tooltip',
|
||||
defaultTargetClass: 'bd-has-tooltip',
|
||||
defaultArrowSelector: '.bd-tooltip-arrow',
|
||||
defaultInnerSelector: '.bd-tooltip-inner',
|
||||
defaultTemplate: '<div class="bd-tooltip"><span class="bd-tooltip-inner"></span></div>',
|
||||
defaultTemplate: '<div class="bd-tooltip"><div class="bd-tooltip-arrow"></div><span class="bd-tooltip-inner"></span></div>',
|
||||
defaultBoundariesElement: DOM.getElement('#app-mount')
|
||||
});
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* BetterDiscord VUE Injector Module
|
||||
* BetterDiscord Vue Injector Module
|
||||
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
|
||||
* All rights reserved.
|
||||
* https://betterdiscord.net
|
||||
|
@ -12,14 +12,20 @@ import Vue from './vue';
|
|||
|
||||
export default class {
|
||||
|
||||
static inject(root, bdnode, components, template, replaceRoot) {
|
||||
if(!replaceRoot) bdnode.appendTo(root);
|
||||
/**
|
||||
* 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 {BdNode} bdnode The element to append
|
||||
* @return {Vue}
|
||||
*/
|
||||
static inject(root, options, bdnode) {
|
||||
if(bdnode) bdnode.appendTo(root);
|
||||
|
||||
return new Vue({
|
||||
el: replaceRoot ? root : bdnode.element,
|
||||
components,
|
||||
template
|
||||
});
|
||||
const vue = new Vue(options);
|
||||
|
||||
vue.$mount(bdnode ? bdnode.element : root);
|
||||
return vue;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
const
|
||||
path = require('path'),
|
||||
webpack = require('webpack');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
|
||||
const jsLoader = {
|
||||
test: /\.(js|jsx)$/,
|
||||
|
@ -9,18 +8,18 @@ const jsLoader = {
|
|||
query: {
|
||||
presets: ['react']
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const vueLoader = {
|
||||
test: /\.(vue)$/,
|
||||
loader: 'vue-loader'
|
||||
}
|
||||
};
|
||||
|
||||
const scssLoader = {
|
||||
test: /\.scss$/,
|
||||
exclude: /node_modules/,
|
||||
loader: ['css-loader', 'sass-loader']
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
entry: './src/index.js',
|
||||
|
@ -32,9 +31,11 @@ module.exports = {
|
|||
loaders: [jsLoader, vueLoader, scssLoader]
|
||||
},
|
||||
externals: {
|
||||
'electron': 'window.require("electron")',
|
||||
'fs': 'window.require("fs")',
|
||||
'path': 'window.require("path")'
|
||||
electron: 'window.require("electron")',
|
||||
fs: 'window.require("fs")',
|
||||
path: 'window.require("path")',
|
||||
node_utils: 'window.require("util")',
|
||||
sparkplug: 'require("../../core/dist/sparkplug")'
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
|
@ -49,15 +50,10 @@ module.exports = {
|
|||
path.resolve('src', 'structs'),
|
||||
path.resolve('src', 'builtin')
|
||||
]
|
||||
}
|
||||
/* resolve: {
|
||||
alias: {
|
||||
'momentjs': 'vendor/moment.min.js'
|
||||
},
|
||||
modules: [
|
||||
path.resolve('./node_modules'),
|
||||
path.resolve(__dirname, '..'),
|
||||
path.resolve(__dirname, '..', 'node_modules')
|
||||
]
|
||||
}*/
|
||||
node: {
|
||||
process: false,
|
||||
__filename: false,
|
||||
__dirname: false
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
||||
|
||||
const jsLoader = {
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
query: {
|
||||
presets: ['react']
|
||||
}
|
||||
};
|
||||
|
||||
const vueLoader = {
|
||||
test: /\.(vue)$/,
|
||||
loader: 'vue-loader'
|
||||
};
|
||||
|
||||
const scssLoader = {
|
||||
test: /\.scss$/,
|
||||
exclude: /node_modules/,
|
||||
loader: ['css-loader', 'sass-loader']
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
entry: './src/index.js',
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'betterdiscord.client-release.js'
|
||||
},
|
||||
module: {
|
||||
loaders: [jsLoader, vueLoader, scssLoader]
|
||||
},
|
||||
externals: {
|
||||
electron: 'window.require("electron")',
|
||||
fs: 'window.require("fs")',
|
||||
path: 'window.require("path")',
|
||||
node_utils: 'window.require("util")',
|
||||
sparkplug: 'require("./sparkplug")'
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
vue$: path.resolve('..', 'node_modules', 'vue', 'dist', 'vue.esm.js')
|
||||
},
|
||||
modules: [
|
||||
path.resolve('..', 'node_modules'),
|
||||
path.resolve('..', 'common', 'modules'),
|
||||
path.resolve('src', 'modules'),
|
||||
path.resolve('src', 'ui'),
|
||||
path.resolve('src', 'plugins'),
|
||||
path.resolve('src', 'structs'),
|
||||
path.resolve('src', 'builtin')
|
||||
]
|
||||
},
|
||||
node: {
|
||||
process: false,
|
||||
__filename: false,
|
||||
__dirname: false
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
PRODUCTION: JSON.stringify(true)
|
||||
}),
|
||||
new UglifyJsPlugin()
|
||||
]
|
||||
};
|
|
@ -8,51 +8,155 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const { ipcRenderer, ipcMain } = require('electron');
|
||||
import { ipcRenderer } from 'electron';
|
||||
|
||||
export class ClientIPC {
|
||||
static on(channel, cb) {
|
||||
ipcRenderer.on(channel, (event, message) => cb(event, message));
|
||||
const callbacks = new WeakMap();
|
||||
|
||||
const ClientIPC = new class ClientIPC {
|
||||
|
||||
constructor() {
|
||||
this.on('ping', () => 'pong', true);
|
||||
}
|
||||
|
||||
static async send(channel, message) {
|
||||
/**
|
||||
* Adds an IPC event listener.
|
||||
* @param {String} channel The channel to listen on
|
||||
* @param {Function} callback A function that will be called when a message is received
|
||||
* @param {Boolean} reply Whether to automatically reply to the message with the callback's return value
|
||||
* @return {Promise}
|
||||
*/
|
||||
on(channel, callback, reply) {
|
||||
channel = channel.startsWith('bd-') ? channel : `bd-${channel}`;
|
||||
const __eid = Date.now().toString();
|
||||
ipcRenderer.send(channel, Object.assign(message ? message : {}, { __eid }));
|
||||
return new Promise((resolve, reject) => {
|
||||
ipcRenderer.once(__eid, (event, arg) => {
|
||||
if (arg.err) {
|
||||
return reject(arg.err);
|
||||
}
|
||||
resolve(arg);
|
||||
});
|
||||
});
|
||||
|
||||
const boundCallback = async (event, args) => {
|
||||
const ipcevent = new BDIpcEvent(event, args);
|
||||
try {
|
||||
const r = callback(ipcevent, ipcevent.message);
|
||||
if (reply) ipcevent.reply(await r);
|
||||
} catch (err) {
|
||||
console.error('Error in IPC callback:', err);
|
||||
if (reply) ipcevent.reject(err);
|
||||
}
|
||||
};
|
||||
|
||||
callbacks.set(callback, boundCallback);
|
||||
ipcRenderer.on(channel, boundCallback);
|
||||
}
|
||||
|
||||
off(channel, callback) {
|
||||
ipcRenderer.removeListener(channel, callbacks.get(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the main process and returns a promise that is resolved when the main process replies.
|
||||
* @param {String} channel The channel to send a message to
|
||||
* @param {Any} message Data to send to the main process
|
||||
* @param {Boolean} error Whether to mark the message as an error
|
||||
* @return {Promise}
|
||||
*/
|
||||
async send(channel, message, error) {
|
||||
channel = channel.startsWith('bd-') ? channel : `bd-${channel}`;
|
||||
|
||||
const eid = 'bd-' + Date.now().toString();
|
||||
ipcRenderer.send(channel, { eid, message, error });
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
ipcRenderer.once(eid, (event, arg) => {
|
||||
if (arg.error) reject(arg.message);
|
||||
else resolve(arg.message);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the Discord window and returns a promise that is resolved when it replies.
|
||||
* @param {String} channel The channel to send a message to
|
||||
* @param {Any} message Data to send to the renderer process
|
||||
* @return {Promise}
|
||||
*/
|
||||
sendToDiscord(channel, message) {
|
||||
return this.send('bd-sendToDiscord', {
|
||||
channel, message
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the CSS editor window and returns a promise that is resolved when it replies.
|
||||
* @param {String} channel The channel to send a message to
|
||||
* @param {Any} message Data to send to the CSS editor window
|
||||
* @return {Promise}
|
||||
*/
|
||||
sendToCssEditor(channel, message) {
|
||||
return this.send('bd-sendToCssEditor', {
|
||||
channel, message
|
||||
});
|
||||
}
|
||||
|
||||
ping() {
|
||||
return this.send('ping');
|
||||
}
|
||||
|
||||
getConfig() {
|
||||
return this.send('getConfig');
|
||||
}
|
||||
|
||||
showOpenDialog(options) {
|
||||
return this.send('native-open', options);
|
||||
}
|
||||
|
||||
compileSass(options) {
|
||||
return this.send('compileSass', options);
|
||||
}
|
||||
|
||||
dba(command) {
|
||||
return this.send('dba', command);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ClientIPC;
|
||||
|
||||
/**
|
||||
* An IPC event.
|
||||
*/
|
||||
class BDIpcEvent {
|
||||
|
||||
constructor(event, args) {
|
||||
this.args = args;
|
||||
this.ipcEvent = event;
|
||||
this.replied = false;
|
||||
}
|
||||
|
||||
bindings() {
|
||||
this.send = this.send.bind(this);
|
||||
this.reply = this.reply.bind(this);
|
||||
}
|
||||
|
||||
send(message) {
|
||||
this.ipcEvent.sender.send(this.args.__eid, message);
|
||||
/**
|
||||
* Sends a message back to the message's sender.
|
||||
* @param {Any} message Data to send to this message's sender
|
||||
*/
|
||||
get send() { return this.reply }
|
||||
reply(message, error) {
|
||||
if (this.replied)
|
||||
throw {message: 'This message has already been replied to.'};
|
||||
|
||||
this.replied = true;
|
||||
return ClientIPC.send(this.eid, message, error);
|
||||
}
|
||||
|
||||
reply(message) {
|
||||
this.send(message);
|
||||
}
|
||||
reject(err) {
|
||||
return this.reply(err, true);
|
||||
}
|
||||
|
||||
export class CoreIPC {
|
||||
static on(channel, cb) {
|
||||
ipcMain.on(channel, (event, args) => cb(new BDIpcEvent(event, args)));
|
||||
get message() {
|
||||
return this.args.message;
|
||||
}
|
||||
|
||||
get error() {
|
||||
return this.args.error;
|
||||
}
|
||||
|
||||
get eid() {
|
||||
return this.args.eid;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export { ClientIPC } from './bdipc';
|
||||
export { default as ClientIPC } from './bdipc';
|
||||
export * from './utils';
|
||||
export { ClientLogger } from './logger';
|
||||
export { default as AsyncEventEmitter } from './async-eventemitter';
|
||||
|
|
|
@ -9,47 +9,10 @@
|
|||
*/
|
||||
|
||||
import { Vendor } from 'modules';
|
||||
import { FileUtils } from './utils';
|
||||
import node_utils from 'node_utils';
|
||||
|
||||
const logs = [];
|
||||
|
||||
export class ClientLogger {
|
||||
|
||||
static err(module, message) { this.log(module, message, 'err'); }
|
||||
static warn(module, message) { this.log(module, message, 'warn'); }
|
||||
static info(module, message) { this.log(module, message, 'info'); }
|
||||
static dbg(module, message) { this.log(module, message, 'dbg'); }
|
||||
|
||||
static log(module, message, level = 'log') {
|
||||
level = this.parseLevel(level);
|
||||
if (typeof message === 'object' && !(message instanceof Array)) {
|
||||
console[level]('[%cBetter%cDiscord:%s]', 'color: #3E82E5', '', `${module}${level === 'debug' ? '|DBG' : ''}`, message);
|
||||
let message_string = message.toString();
|
||||
if (message_string === '[object Object]')
|
||||
message_string += ' ' + JSON.stringify(message, null, 4);
|
||||
|
||||
logs.push(`${level.toUpperCase()} : [${Vendor.moment().format('DD/MM/YY hh:mm:ss')}|${module}] ${message_string}${message_string === '[object Object]' ? ' ' + JSON.stringify(message, null, 4) : ''}`);
|
||||
return;
|
||||
}
|
||||
|
||||
message = typeof message === 'object' && message instanceof Array ? message : [message];
|
||||
console[level]('[%cBetter%cDiscord:%s]', 'color: #3E82E5', '', `${module}${level === 'debug' ? '|DBG' : ''}`, ...message);
|
||||
logs.push(`${level.toUpperCase()} : [${Vendor.moment().format('DD/MM/YY hh:mm:ss')}|${module}] ${message.join(' ')}`);
|
||||
}
|
||||
|
||||
static logError(err) {
|
||||
if (!err.module && !err.message) {
|
||||
console.log(err);
|
||||
return;
|
||||
}
|
||||
this.err(err.module, err.message);
|
||||
}
|
||||
|
||||
static get logs() {
|
||||
return logs;
|
||||
}
|
||||
|
||||
static get levels() {
|
||||
return {
|
||||
export const logLevels = {
|
||||
'log': 'log',
|
||||
'warn': 'warn',
|
||||
'err': 'error',
|
||||
|
@ -58,10 +21,48 @@ export class ClientLogger {
|
|||
'dbg': 'debug',
|
||||
'info': 'info'
|
||||
};
|
||||
|
||||
export default class Logger {
|
||||
|
||||
constructor(file) {
|
||||
this.logs = [];
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
err(module, message) { this.log(module, message, 'err'); }
|
||||
warn(module, message) { this.log(module, message, 'warn'); }
|
||||
info(module, message) { this.log(module, message, 'info'); }
|
||||
dbg(module, message) { this.log(module, message, 'dbg'); }
|
||||
|
||||
log(module, message, level = 'log') {
|
||||
level = Logger.parseLevel(level);
|
||||
|
||||
message = typeof message === 'object' && message instanceof Array ? message : [message];
|
||||
console[level]('[%cBetter%cDiscord:%s]', 'color: #3E82E5', '', `${module}${level === 'debug' ? '|DBG' : ''}`, ...message);
|
||||
|
||||
const message_string = message.map(m => typeof m === 'string' ? m : node_utils.inspect(m)).join(' ');
|
||||
this.logs.push(`${level.toUpperCase()} : [${Logger.timestamp}|${module}] ${message_string}`);
|
||||
|
||||
if (this.file)
|
||||
FileUtils.appendToFile(this.file, `${level.toUpperCase()} : [${Logger.timestamp}|${module}] ${message_string}\n`);
|
||||
}
|
||||
|
||||
logError(err) {
|
||||
if (!err.module && !err.message) {
|
||||
console.log(err);
|
||||
return;
|
||||
}
|
||||
this.err(err.module, err.message);
|
||||
}
|
||||
|
||||
static parseLevel(level) {
|
||||
return this.levels.hasOwnProperty(level) ? this.levels[level] : 'log';
|
||||
return logLevels.hasOwnProperty(level) ? logLevels[level] : 'log';
|
||||
}
|
||||
|
||||
static get timestamp() {
|
||||
return Vendor.moment().format('DD/MM/YY hh:mm:ss');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const ClientLogger = new Logger();
|
||||
|
|
|
@ -8,19 +8,13 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
const
|
||||
path = require('path'),
|
||||
fs = require('fs'),
|
||||
_ = require('lodash');
|
||||
|
||||
import { PatchedFunction, Patch } from './monkeypatch';
|
||||
import { Vendor } from 'modules';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import _ from 'lodash';
|
||||
import filetype from 'file-type';
|
||||
|
||||
export class Utils {
|
||||
static isArrowFunction(fn) {
|
||||
return !fn.toString().startsWith('function');
|
||||
}
|
||||
static overload(fn, cb) {
|
||||
const orig = fn;
|
||||
return function (...args) {
|
||||
|
@ -31,6 +25,10 @@ export class Utils {
|
|||
|
||||
/**
|
||||
* Monkey-patches an object's method.
|
||||
* @param {Object} object The object containing the function to monkey patch
|
||||
* @param {String} methodName The name of the method to monkey patch
|
||||
* @param {Object|String|Function} options Options to pass to the Patch constructor
|
||||
* @param {Function} function If {options} is either "before" or "after", this function will be used as that hook
|
||||
*/
|
||||
static monkeyPatch(object, methodName, options, f) {
|
||||
const patchedFunction = new PatchedFunction(object, methodName);
|
||||
|
@ -41,12 +39,31 @@ export class Utils {
|
|||
|
||||
/**
|
||||
* Monkey-patches an object's method and returns a promise that will be resolved with the data object when the method is called.
|
||||
* You will have to call data.callOriginalMethod() if it wants the original method to be called.
|
||||
* This can only be used to get the arguments and return data. If you want to change anything, call Utils.monkeyPatch with the once option set to true.
|
||||
*/
|
||||
static monkeyPatchOnce(object, methodName) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.monkeyPatch(object, methodName, 'after', data => {
|
||||
data.patch.cancel();
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Monkey-patches an object's method and returns a promise that will be resolved with the data object when the method is called.
|
||||
* You will have to call data.callOriginalMethod() if you wants the original method to be called.
|
||||
*/
|
||||
static monkeyPatchAsync(object, methodName, callback) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.monkeyPatch(object, methodName, data => {
|
||||
data.patch.cancel();
|
||||
|
||||
data.promise = data.return = callback ? Promise.all(callback.call(global, data, ...data.arguments)) : new Promise((resolve, reject) => {
|
||||
data.resolve = resolve;
|
||||
data.reject = reject;
|
||||
});
|
||||
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
|
@ -81,15 +98,20 @@ export class Utils {
|
|||
};
|
||||
|
||||
const patch = this.monkeyPatch(what, methodName, {
|
||||
before: before ? compatible_function(before) : undefined,
|
||||
before: !instead && before ? compatible_function(before) : undefined,
|
||||
instead: instead ? compatible_function(instead) : undefined,
|
||||
after: after ? compatible_function(after) : undefined,
|
||||
after: !instead && after ? compatible_function(after) : undefined,
|
||||
once
|
||||
});
|
||||
|
||||
return cancelPatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to parse a string as JSON.
|
||||
* @param {String} json The string to parse
|
||||
* @return {Any}
|
||||
*/
|
||||
static async tryParseJson(jsonString) {
|
||||
try {
|
||||
return JSON.parse(jsonString);
|
||||
|
@ -101,6 +123,11 @@ export class Utils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new object with normalised keys.
|
||||
* @param {Object} object
|
||||
* @return {Object}
|
||||
*/
|
||||
static toCamelCase(o) {
|
||||
const camelCased = {};
|
||||
_.forEach(o, (value, key) => {
|
||||
|
@ -112,17 +139,20 @@ export class Utils {
|
|||
return camelCased;
|
||||
}
|
||||
|
||||
static compare(value1, value2) {
|
||||
/**
|
||||
* Checks if two or more values contain the same data.
|
||||
* @param {Any} ...value The value to compare
|
||||
* @return {Boolean}
|
||||
*/
|
||||
static compare(value1, value2, ...values) {
|
||||
// Check to see if value1 and value2 contain the same data
|
||||
if (typeof value1 !== typeof value2) return false;
|
||||
if (value1 === null && value2 === null) return true;
|
||||
if (value1 === null || value2 === null) return false;
|
||||
|
||||
if (typeof value1 === 'object' || typeof value1 === 'array') {
|
||||
if (typeof value1 === 'object') {
|
||||
// Loop through the object and check if everything's the same
|
||||
let value1array = typeof value1 === 'array' ? value1 : Object.keys(value1);
|
||||
let value2array = typeof value2 === 'array' ? value2 : Object.keys(value2);
|
||||
if (value1array.length !== value2array.length) return false;
|
||||
if (Object.keys(value1).length !== Object.keys(value2).length) return false;
|
||||
|
||||
for (let key in value1) {
|
||||
if (!this.compare(value1[key], value2[key])) return false;
|
||||
|
@ -130,9 +160,20 @@ export class Utils {
|
|||
} else if (value1 !== value2) return false;
|
||||
|
||||
// value1 and value2 contain the same data
|
||||
// Check any more values
|
||||
for (let value3 of values) {
|
||||
if (!this.compare(value1, value3))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clones an object and all it's properties.
|
||||
* @param {Any} value The value to clone
|
||||
* @return {Any} The cloned value
|
||||
*/
|
||||
static deepclone(value) {
|
||||
if (typeof value === 'object') {
|
||||
if (value instanceof Array) return value.map(i => this.deepclone(i));
|
||||
|
@ -149,6 +190,11 @@ export class Utils {
|
|||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Freezes an object and all it's properties.
|
||||
* @param {Any} object The object to freeze
|
||||
* @param {Function} exclude A function to filter object that shouldn't be frozen
|
||||
*/
|
||||
static deepfreeze(object, exclude) {
|
||||
if (exclude && exclude(object)) return;
|
||||
|
||||
|
@ -165,38 +211,57 @@ export class Utils {
|
|||
return object;
|
||||
}
|
||||
|
||||
static filterArray(array, filter) {
|
||||
const indexes = [];
|
||||
for (let index in array) {
|
||||
if (!filter(array[index], index))
|
||||
indexes.push(index);
|
||||
}
|
||||
|
||||
for (let i in indexes)
|
||||
array.splice(indexes[i] - i, 1);
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item from an array. This differs from Array.prototype.filter as it mutates the original array instead of creating a new one.
|
||||
* @param {Array} array The array to filter
|
||||
* @param {Any} item The item to remove from the array
|
||||
* @return {Array}
|
||||
*/
|
||||
static removeFromArray(array, item) {
|
||||
let index;
|
||||
while ((index = array.indexOf(item)) > -1)
|
||||
array.splice(index, 1);
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a property with a getter that can be changed like a normal property.
|
||||
* @param {Object} object The object to define a property on
|
||||
* @param {String} property The property to define
|
||||
* @param {Function} getter The property's getter
|
||||
* @return {Object}
|
||||
*/
|
||||
static defineSoftGetter(object, property, get) {
|
||||
return Object.defineProperty(object, property, {
|
||||
get,
|
||||
set: value => Object.defineProperty(object, property, {
|
||||
value,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
}),
|
||||
configurable: true,
|
||||
enumerable: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class FileUtils {
|
||||
/**
|
||||
* Checks if a file exists and is a file.
|
||||
* @param {String} path The file's path
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async fileExists(path) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.stat(path, (err, stats) => {
|
||||
if (err) return reject({
|
||||
'message': `No such file or directory: ${err.path}`,
|
||||
message: `No such file or directory: ${err.path}`,
|
||||
err
|
||||
});
|
||||
|
||||
if (!stats.isFile()) return reject({
|
||||
'message': `Not a file: ${path}`,
|
||||
message: `Not a file: ${path}`,
|
||||
stats
|
||||
});
|
||||
|
||||
|
@ -205,16 +270,21 @@ export class FileUtils {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a directory exists and is a directory.
|
||||
* @param {String} path The directory's path
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async directoryExists(path) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.stat(path, (err, stats) => {
|
||||
if (err) return reject({
|
||||
'message': `Directory does not exist: ${path}`,
|
||||
message: `Directory does not exist: ${path}`,
|
||||
err
|
||||
});
|
||||
|
||||
if (!stats.isDirectory()) return reject({
|
||||
'message': `Not a directory: ${path}`,
|
||||
message: `Not a directory: ${path}`,
|
||||
stats
|
||||
});
|
||||
|
||||
|
@ -223,18 +293,25 @@ export class FileUtils {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a directory.
|
||||
* @param {String} path The directory's path
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async createDirectory(path) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.mkdir(path, err => {
|
||||
if (err) {
|
||||
if (err.code === 'EEXIST') return resolve();
|
||||
else return reject(err);
|
||||
}
|
||||
resolve();
|
||||
if (err) reject(err);
|
||||
else resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a directory exists and creates it if it doesn't.
|
||||
* @param {String} path The directory's path
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async ensureDirectory(path) {
|
||||
try {
|
||||
await this.directoryExists(path);
|
||||
|
@ -249,17 +326,22 @@ export class FileUtils {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the contents of a file.
|
||||
* @param {String} path The file's path
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async readFile(path) {
|
||||
try {
|
||||
await this.fileExists(path);
|
||||
} catch (err) {
|
||||
throw (err);
|
||||
throw err;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(path, 'utf-8', (err, data) => {
|
||||
if (err) reject({
|
||||
'message': `Could not read file: ${path}`,
|
||||
if (err) return reject({
|
||||
message: `Could not read file: ${path}`,
|
||||
err
|
||||
});
|
||||
|
||||
|
@ -268,24 +350,62 @@ export class FileUtils {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the contents of a file.
|
||||
* @param {String} path The file's path
|
||||
* @param {Object} options Additional options to pass to fs.readFile
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async readFileBuffer(path, options) {
|
||||
try {
|
||||
await this.fileExists(path);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(path, options || {}, (err, data) => {
|
||||
if (err) return reject(err);
|
||||
resolve(data);
|
||||
if (err) reject(err);
|
||||
else resolve(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes to a file.
|
||||
* @param {String} path The file's path
|
||||
* @param {String} data The file's new contents
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async writeFile(path, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.writeFile(path, data, err => {
|
||||
if (err) return reject(err);
|
||||
resolve();
|
||||
if (err) reject(err);
|
||||
else resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes to the end of a file.
|
||||
* @param {String} path The file's path
|
||||
* @param {String} data The data to append to the file
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async appendToFile(path, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.appendFile(path, data, err => {
|
||||
if (err) reject(err);
|
||||
else resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the contents of a file parsed as JSON.
|
||||
* @param {String} path The file's path
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async readJsonFromFile(path) {
|
||||
let readFile;
|
||||
try {
|
||||
|
@ -295,41 +415,57 @@ export class FileUtils {
|
|||
}
|
||||
|
||||
try {
|
||||
const parsed = await Utils.tryParseJson(readFile);
|
||||
return parsed;
|
||||
return await Utils.tryParseJson(readFile);
|
||||
} catch (err) {
|
||||
throw (Object.assign(err, { path }));
|
||||
throw Object.assign(err, { path });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes to a file as JSON.
|
||||
* @param {String} path The file's path
|
||||
* @param {Any} data The file's new contents
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async writeJsonToFile(path, json) {
|
||||
return this.writeFile(path, JSON.stringify(json));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of items in a directory.
|
||||
* @param {String} path The directory's path
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async listDirectory(path) {
|
||||
try {
|
||||
await this.directoryExists(path);
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readdir(path, (err, files) => {
|
||||
if (err) return reject(err);
|
||||
resolve(files);
|
||||
if (err) reject(err);
|
||||
else resolve(files);
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
static async readDir(path) {
|
||||
return this.listDirectory(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a file or buffer's MIME type and typical file extension.
|
||||
* @param {String|Buffer} buffer A buffer or the path of a file
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async getFileType(buffer) {
|
||||
if (typeof buffer === 'string') buffer = await this.readFileBuffer(buffer);
|
||||
|
||||
return filetype(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a file's contents as a data URI.
|
||||
* @param {String} path The directory's path
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async toDataURI(buffer, type) {
|
||||
if (typeof buffer === 'string') buffer = await this.readFileBuffer(buffer);
|
||||
if (!type) type = this.getFileType(buffer).mime;
|
||||
|
|
|
@ -5,7 +5,7 @@ const
|
|||
plumber = require('gulp-plumber'),
|
||||
watch = require('gulp-watch');
|
||||
|
||||
const task_babel = function () {
|
||||
const task_build = function () {
|
||||
return pump([
|
||||
gulp.src('src/**/*js'),
|
||||
plumber(),
|
||||
|
@ -14,7 +14,7 @@ const task_babel = function () {
|
|||
]);
|
||||
}
|
||||
|
||||
const watch_babel = function () {
|
||||
const task_watch = function () {
|
||||
return pump([
|
||||
watch('src/**/*js'),
|
||||
plumber(),
|
||||
|
@ -23,5 +23,5 @@ const watch_babel = function () {
|
|||
]);
|
||||
}
|
||||
|
||||
gulp.task('build', task_babel);
|
||||
gulp.task('watch', watch_babel);
|
||||
gulp.task('build', task_build);
|
||||
gulp.task('watch', task_watch);
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
module.exports = require('./main.js');
|
|
@ -5,19 +5,16 @@
|
|||
"version": "2.0.0b",
|
||||
"homepage": "https://betterdiscord.net",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"main": "dist/main.js",
|
||||
"contributors": [
|
||||
"Jiiks",
|
||||
"Pohky"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Jiiks/BetterDiscordApp.git"
|
||||
"url": "https://github.com/JsSucks/BetterDiscordApp.git"
|
||||
},
|
||||
"private": false,
|
||||
"devDependencies": {
|
||||
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp build",
|
||||
"watch": "gulp watch"
|
||||
|
|
196
core/src/main.js
196
core/src/main.js
|
@ -10,48 +10,47 @@
|
|||
|
||||
const path = require('path');
|
||||
const sass = require('node-sass');
|
||||
|
||||
const { FileUtils, BDIpc, Config, WindowUtils, CSSEditor, Database } = require('./modules');
|
||||
const { BrowserWindow, dialog } = require('electron');
|
||||
|
||||
const tests = true;
|
||||
const _basePath = __dirname;
|
||||
const { FileUtils, BDIpc, Config, WindowUtils, CSSEditor, Database } = require('./modules');
|
||||
|
||||
const tests = typeof PRODUCTION === 'undefined';
|
||||
|
||||
const _basePath = tests ? path.resolve(__dirname, '..', '..') : __dirname;
|
||||
const _baseDataPath = tests ? path.resolve(_basePath, 'tests') : _basePath;
|
||||
|
||||
const sparkplug = path.resolve(__dirname, 'sparkplug.js');
|
||||
|
||||
const _clientScript = tests
|
||||
? path.resolve(__dirname, '..', '..', 'client', 'dist', 'betterdiscord.client.js')
|
||||
: path.resolve(__dirname, 'betterdiscord.client.js');
|
||||
const _dataPath = tests
|
||||
? path.resolve(__dirname, '..', '..', 'tests', 'data')
|
||||
: path.resolve(__dirname, 'data');
|
||||
const _extPath = tests
|
||||
? path.resolve(__dirname, '..', '..', 'tests', 'ext')
|
||||
: path.resolve(__dirname, 'ext');
|
||||
const _pluginPath = path.resolve(_extPath, 'plugins');
|
||||
const _themePath = path.resolve(_extPath, 'themes');
|
||||
const _modulePath = path.resolve(_extPath, 'modules');
|
||||
? path.resolve(_basePath, 'client', 'dist', 'betterdiscord.client.js')
|
||||
: path.resolve(_basePath, 'betterdiscord.client.js');
|
||||
const _cssEditorPath = tests
|
||||
? path.resolve(__dirname, '..', '..', 'csseditor', 'dist')
|
||||
: path.resolve(__dirname, 'csseditor');
|
||||
|
||||
const _dataPath = path.resolve(_baseDataPath, 'data');
|
||||
const _extPath = path.resolve(_baseDataPath, 'ext');
|
||||
const _pluginPath = path.resolve(_extPath, 'plugins');
|
||||
const _themePath = path.resolve(_extPath, 'themes');
|
||||
const _modulePath = path.resolve(_extPath, 'modules');
|
||||
|
||||
const version = require(path.resolve(_basePath, 'package.json')).version;
|
||||
|
||||
const paths = [
|
||||
{ id: 'base', path: _basePath.replace(/\\/g, '/') },
|
||||
{ id: 'cs', path: _clientScript.replace(/\\/g, '/') },
|
||||
{ id: 'data', path: _dataPath.replace(/\\/g, '/') },
|
||||
{ id: 'ext', path: _extPath.replace(/\\/g, '/') },
|
||||
{ id: 'plugins', path: _pluginPath.replace(/\\/g, '/') },
|
||||
{ id: 'themes', path: _themePath.replace(/\\/g, '/') },
|
||||
{ id: 'modules', path: _modulePath.replace(/\\/g, '/') },
|
||||
{ id: 'csseditor', path: _cssEditorPath.replace(/\\/g, '/') }
|
||||
{ id: 'base', path: _basePath },
|
||||
{ id: 'cs', path: _clientScript },
|
||||
{ id: 'data', path: _dataPath },
|
||||
{ id: 'ext', path: _extPath },
|
||||
{ id: 'plugins', path: _pluginPath },
|
||||
{ id: 'themes', path: _themePath },
|
||||
{ id: 'modules', path: _modulePath },
|
||||
{ id: 'csseditor', path: _cssEditorPath }
|
||||
];
|
||||
|
||||
const sparkplug = path.resolve(__dirname, 'sparkplug.js').replace(/\\/g, '/');
|
||||
|
||||
const Common = {};
|
||||
const globals = {
|
||||
version: '2.0.0a',
|
||||
version,
|
||||
paths
|
||||
}
|
||||
|
||||
const dbInstance = new Database(paths.find(path => path.id === 'data').path);
|
||||
};
|
||||
|
||||
class Comms {
|
||||
|
||||
|
@ -61,67 +60,48 @@ class Comms {
|
|||
}
|
||||
|
||||
initListeners() {
|
||||
BDIpc.on('bd-getConfig', o => {
|
||||
o.reply(Common.Config.config);
|
||||
});
|
||||
BDIpc.on('ping', () => 'pong', true);
|
||||
|
||||
BDIpc.on('bd-sendToDiscord', event => this.bd.windowUtils.send(event.args.channel, event.args.message));
|
||||
BDIpc.on('bd-getConfig', () => this.bd.config.config, true);
|
||||
|
||||
BDIpc.on('bd-openCssEditor', o => this.bd.csseditor.openEditor(o));
|
||||
// BDIpc.on('bd-setScss', o => this.bd.csseditor.setSCSS(o.args.scss));
|
||||
BDIpc.on('bd-sendToCssEditor', o => this.bd.csseditor.send(o.args.channel, o.args.data));
|
||||
BDIpc.on('bd-sendToDiscord', (event, m) => this.sendToDiscord(m.channel, m.message), true);
|
||||
|
||||
BDIpc.on('bd-readFile', this.readFile);
|
||||
BDIpc.on('bd-readJson', o => this.readFile(o, true));
|
||||
BDIpc.on('bd-openCssEditor', (event, options) => this.bd.csseditor.openEditor(options), true);
|
||||
BDIpc.on('bd-sendToCssEditor', (event, m) => this.sendToCssEditor(m.channel, m.message), true);
|
||||
|
||||
BDIpc.on('bd-native-open', o => {
|
||||
dialog.showOpenDialog(BrowserWindow.fromWebContents(o.ipcEvent.sender), o.args, filenames => {
|
||||
o.reply(filenames);
|
||||
BDIpc.on('bd-native-open', (event, options) => {
|
||||
dialog.showOpenDialog(BrowserWindow.fromWebContents(event.ipcEvent.sender), options, filenames => {
|
||||
event.reply(filenames);
|
||||
});
|
||||
});
|
||||
|
||||
BDIpc.on('bd-compileSass', o => {
|
||||
if (!o.args.path && !o.args.data) return o.reply('');
|
||||
if (typeof o.args.path === 'string' && typeof o.args.data === 'string') {
|
||||
o.args.data = `${o.args.data} @import '${o.args.path.replace(/\\/g, '\\\\').replace(/'/g, '\\\'')}';`;
|
||||
o.args.path = undefined;
|
||||
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, '\\\'')}';`;
|
||||
options.path = undefined;
|
||||
}
|
||||
|
||||
sass.render(o.args, (err, result) => {
|
||||
if (err) {
|
||||
o.reply({ err });
|
||||
return;
|
||||
}
|
||||
o.reply(result);
|
||||
sass.render(options, (err, result) => {
|
||||
if (err) event.reject(err);
|
||||
else event.reply(result);
|
||||
});
|
||||
});
|
||||
|
||||
BDIpc.on('bd-dba', o => {
|
||||
(async () => {
|
||||
try {
|
||||
const ret = await dbInstance.exec(o.args);
|
||||
o.reply(ret);
|
||||
} catch (err) {
|
||||
o.reply({err});
|
||||
}
|
||||
})();
|
||||
});
|
||||
}
|
||||
|
||||
async readFile(o, json) {
|
||||
const { path } = o.args;
|
||||
try {
|
||||
const readFile = json ? await FileUtils.readJsonFromFile(path) : await FileUtils.readFile(path);
|
||||
o.reply(readFile);
|
||||
} catch (err) {
|
||||
o.reply(err);
|
||||
}
|
||||
BDIpc.on('bd-dba', (event, options) => this.bd.dbInstance.exec(options), true);
|
||||
}
|
||||
|
||||
async send(channel, message) {
|
||||
BDIpc.send(channel, message);
|
||||
}
|
||||
|
||||
async sendToDiscord(channel, message) {
|
||||
return this.bd.windowUtils.send(channel, message);
|
||||
}
|
||||
|
||||
async sendToCssEditor(channel, message) {
|
||||
return this.bd.csseditor.send(channel, message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class BetterDiscord {
|
||||
|
@ -135,23 +115,32 @@ class BetterDiscord {
|
|||
|
||||
this.injectScripts = this.injectScripts.bind(this);
|
||||
this.ignite = this.ignite.bind(this);
|
||||
Common.Config = new Config(globals);
|
||||
|
||||
this.config = new Config(args || globals);
|
||||
this.dbInstance = new Database(this.config.getPath('data'));
|
||||
this.comms = new Comms(this);
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
const window = await this.waitForWindow();
|
||||
this.windowUtils = new WindowUtils({ window });
|
||||
await this.waitForWindowUtils();
|
||||
|
||||
await FileUtils.ensureDirectory(paths.find(path => path.id === 'ext').path);
|
||||
if (!tests) {
|
||||
const basePath = this.config.getPath('base');
|
||||
const files = await FileUtils.listDirectory(basePath);
|
||||
const latestCs = FileUtils.resolveLatest(files, file => file.endsWith('.js') && file.startsWith('client.'), file => file.replace('client.', '').replace('.js', ''), 'client.', '.js');
|
||||
this.config.getPath('cs', true).path = path.resolve(basePath, latestCs);
|
||||
}
|
||||
|
||||
this.csseditor = new CSSEditor(this, paths.find(path => path.id === 'csseditor').path);
|
||||
await FileUtils.ensureDirectory(this.config.getPath('ext'));
|
||||
|
||||
this.windowUtils.events('did-get-response-details', () => this.ignite(this.windowUtils.window));
|
||||
this.windowUtils.events('did-finish-load', e => this.injectScripts(true));
|
||||
this.csseditor = new CSSEditor(this, this.config.getPath('csseditor'));
|
||||
|
||||
this.windowUtils.events('did-navigate-in-page', (event, url, isMainFrame) => {
|
||||
this.windowUtils.on('did-get-response-details', () => this.ignite());
|
||||
this.windowUtils.on('did-finish-load', () => this.injectScripts(true));
|
||||
|
||||
this.windowUtils.on('did-navigate-in-page', (event, url, isMainFrame) => {
|
||||
this.windowUtils.send('did-navigate-in-page', { event, url, isMainFrame });
|
||||
});
|
||||
|
||||
|
@ -166,10 +155,8 @@ class BetterDiscord {
|
|||
const defer = setInterval(() => {
|
||||
const windows = BrowserWindow.getAllWindows();
|
||||
|
||||
if (windows.length > 0) {
|
||||
windows.forEach(window => {
|
||||
self.ignite(window);
|
||||
});
|
||||
for (let window of windows) {
|
||||
if (window) BetterDiscord.ignite(window);
|
||||
}
|
||||
|
||||
if (windows.length === 1 && windows[0].webContents.getURL().includes('discordapp.com')) {
|
||||
|
@ -180,22 +167,39 @@ class BetterDiscord {
|
|||
});
|
||||
}
|
||||
|
||||
ignite(window) {
|
||||
//Hook things that Discord removes from global. These will be removed again in the client script
|
||||
window.webContents.executeJavaScript(`require("${sparkplug}");`);
|
||||
async waitForWindowUtils() {
|
||||
if (this.windowUtils) return this.windowUtils;
|
||||
const window = await this.waitForWindow();
|
||||
return this.windowUtils = new WindowUtils({ window });
|
||||
}
|
||||
|
||||
get window() {
|
||||
return this.windowUtils ? this.windowUtils.window : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks things that Discord removes from global. These will be removed again in the client script.
|
||||
*/
|
||||
ignite() {
|
||||
return BetterDiscord.ignite(this.window);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks things that Discord removes from global. These will be removed again in the client script.
|
||||
* @param {BrowserWindow} window The window to inject the sparkplug script into
|
||||
*/
|
||||
static ignite(window) {
|
||||
return WindowUtils.injectScript(window, sparkplug);
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects the client script into the main window.
|
||||
* @param {Boolean} reload Whether the main window was reloaded
|
||||
*/
|
||||
async injectScripts(reload = false) {
|
||||
console.log(`RELOAD? ${reload}`);
|
||||
if (!tests) {
|
||||
const files = await FileUtils.listDirectory(paths.find(path => path.id === 'base').path);
|
||||
const latestCs = FileUtils.resolveLatest(files, file => file.endsWith('.js') && file.startsWith('client.'), file => file.replace('client.', '').replace('.js', ''), 'client.', '.js');
|
||||
paths.find(path => path.id === 'cs').path = path.resolve(paths.find(path => path.id === 'base').path, latestCs).replace(/\\/g, '/');
|
||||
return this.windowUtils.injectScript(this.config.getPath('cs'));
|
||||
}
|
||||
this.windowUtils.injectScript(paths.find(path => path.id === 'cs').path);
|
||||
}
|
||||
|
||||
get fileUtils() { return FileUtils; }
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -7,37 +7,115 @@
|
|||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
const { Module } = require('./modulebase');
|
||||
|
||||
const { ipcMain } = require('electron');
|
||||
|
||||
const { Module } = require('./modulebase');
|
||||
|
||||
const callbacks = new WeakMap();
|
||||
|
||||
/**
|
||||
* The IPC module used in the main process.
|
||||
*/
|
||||
class BDIpc {
|
||||
|
||||
/**
|
||||
* Adds an IPC event listener.
|
||||
* @param {String} channel The channel to listen on
|
||||
* @param {Function} callback A function that will be called when a message is received
|
||||
* @param {Boolean} reply Whether to automatically reply to the message with the callback's return value
|
||||
* @return {Promise}
|
||||
*/
|
||||
static on(channel, callback, reply) {
|
||||
channel = channel.startsWith('bd-') ? channel : `bd-${channel}`;
|
||||
|
||||
const boundCallback = async (event, args) => {
|
||||
const ipcevent = new BDIpcEvent(event, args);
|
||||
try {
|
||||
const r = callback(ipcevent, ipcevent.message);
|
||||
if (reply) ipcevent.reply(await r);
|
||||
} catch (err) {
|
||||
console.error('Error in IPC callback:', err);
|
||||
if (reply) ipcevent.reject(err);
|
||||
}
|
||||
};
|
||||
|
||||
callbacks.set(callback, boundCallback);
|
||||
ipcMain.on(channel, boundCallback);
|
||||
}
|
||||
|
||||
static off(channel, callback) {
|
||||
ipcMain.removeListener(channel, callbacks.get(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the main process and returns a promise that is resolved when the main process replies.
|
||||
* @param {BrowserWindow} window The window to send a message to
|
||||
* @param {String} channel The channel to send a message to
|
||||
* @param {Any} message Data to send to the main process
|
||||
* @param {Boolean} error Whether to mark the message as an error
|
||||
* @return {Promise}
|
||||
*/
|
||||
static send(window, channel, message, error) {
|
||||
channel = channel.startsWith('bd-') ? channel : `bd-${channel}`;
|
||||
|
||||
const eid = 'bd-' + Date.now().toString();
|
||||
window.send(channel, { eid, message, error });
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
ipcMain.once(eid, (event, arg) => {
|
||||
if (arg.error) reject(arg.message);
|
||||
else resolve(arg.message);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static ping(window) {
|
||||
return this.send(window, 'ping');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class BDIpcEvent extends Module {
|
||||
|
||||
constructor(event, args) {
|
||||
super(args);
|
||||
this.ipcEvent = event;
|
||||
this.replied = false;
|
||||
}
|
||||
|
||||
bindings() {
|
||||
this.send = this.send.bind(this);
|
||||
this.reply = this.reply.bind(this);
|
||||
}
|
||||
|
||||
send(message) {
|
||||
this.ipcEvent.sender.send(this.args.__eid, message);
|
||||
/**
|
||||
* Sends a message back to the message's sender.
|
||||
* @param {Any} message Data to send to this message's sender
|
||||
*/
|
||||
reply(message, error) {
|
||||
if (this.replied)
|
||||
throw {message: 'This message has already been replied to.'};
|
||||
|
||||
this.replied = true;
|
||||
return BDIpc.send(this.ipcEvent.sender, this.eid, message, error);
|
||||
}
|
||||
|
||||
reply(message) {
|
||||
this.send(message);
|
||||
reject(err) {
|
||||
return this.reply(err, true);
|
||||
}
|
||||
|
||||
get message() {
|
||||
return this.args.message;
|
||||
}
|
||||
|
||||
class BDIpc {
|
||||
|
||||
static on(channel, cb) {
|
||||
ipcMain.on(channel, (event, args) => cb(new BDIpcEvent(event, args)));
|
||||
get error() {
|
||||
return this.args.error;
|
||||
}
|
||||
|
||||
get eid() {
|
||||
return this.args.eid;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = { BDIpc };
|
|
@ -20,10 +20,15 @@ class Config extends Module {
|
|||
return this.args.paths;
|
||||
}
|
||||
|
||||
getPath(id, full) {
|
||||
const path = this.paths.find(path => path.id === id);
|
||||
return full ? path : path.path;
|
||||
}
|
||||
|
||||
get config() {
|
||||
return {
|
||||
'version': this.version,
|
||||
'paths': this.paths
|
||||
version: this.version,
|
||||
paths: this.paths
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ const { BrowserWindow } = require('electron');
|
|||
|
||||
const { Module } = require('./modulebase');
|
||||
const { WindowUtils } = require('./utils');
|
||||
const { BDIpc } = require('./bdipc');
|
||||
|
||||
class CSSEditor extends Module {
|
||||
|
||||
|
@ -22,22 +23,21 @@ class CSSEditor extends Module {
|
|||
this.bd = bd;
|
||||
}
|
||||
|
||||
openEditor(o) {
|
||||
/**
|
||||
* Opens an editor.
|
||||
* @return {Promise}
|
||||
*/
|
||||
openEditor(options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.editor) {
|
||||
if (this.editor.isFocused()) return;
|
||||
|
||||
this.editor.focus();
|
||||
this.editor.flashFrame(true);
|
||||
o.reply(true);
|
||||
return;
|
||||
return resolve(true);
|
||||
}
|
||||
|
||||
const options = this.options;
|
||||
for (let option in o.args) {
|
||||
if (o.args.hasOwnProperty(option)) {
|
||||
options[option] = o.args[option];
|
||||
}
|
||||
}
|
||||
options = Object.assign({}, this.options, options);
|
||||
|
||||
this.editor = new BrowserWindow(options);
|
||||
this.editor.loadURL('about:blank');
|
||||
|
@ -55,24 +55,32 @@ class CSSEditor extends Module {
|
|||
|
||||
this.editor.webContents.on('did-finish-load', () => {
|
||||
this.editorUtils.injectScript(path.join(this.editorPath, 'csseditor.js'));
|
||||
o.reply(true);
|
||||
resolve(true);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
setSCSS(scss) {
|
||||
this.send('set-scss', scss);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends data to the editor.
|
||||
* @param {String} channel
|
||||
* @param {Any} data
|
||||
*/
|
||||
send(channel, data) {
|
||||
if (!this.editor) return;
|
||||
this.editor.webContents.send(channel, data);
|
||||
if (!this.editor) throw {message: 'The CSS editor is not open.'};
|
||||
return BDIpc.send(this.editor, channel, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the CSS editor's always on top flag.
|
||||
*/
|
||||
set alwaysOnTop(state) {
|
||||
if (!this.editor) return;
|
||||
this.editor.setAlwaysOnTop(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default options to pass to BrowserWindow.
|
||||
*/
|
||||
get options() {
|
||||
return {
|
||||
width: 800,
|
||||
|
|
|
@ -8,17 +8,16 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/*
|
||||
Base Module that every non-static module should extend
|
||||
/**
|
||||
* Base Module that every non-static module should extend.
|
||||
*/
|
||||
|
||||
class Module {
|
||||
|
||||
constructor(args) {
|
||||
this.__ = {
|
||||
state: args,
|
||||
args
|
||||
}
|
||||
};
|
||||
this.init();
|
||||
}
|
||||
|
||||
|
|
|
@ -10,43 +10,36 @@
|
|||
|
||||
// TODO Use common
|
||||
|
||||
const
|
||||
path = require('path'),
|
||||
fs = require('fs');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const { Module } = require('./modulebase');
|
||||
const { BDIpc } = require('./bdipc');
|
||||
|
||||
class Utils {
|
||||
|
||||
static async tryParseJson(jsonString) {
|
||||
try {
|
||||
return JSON.parse(jsonString);
|
||||
} catch (err) {
|
||||
throw ({
|
||||
'message': 'Failed to parse json',
|
||||
message: 'Failed to parse json',
|
||||
err
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static get timestamp() {
|
||||
return 'Timestamp';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FileUtils {
|
||||
|
||||
static async fileExists(path) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.stat(path, (err, stats) => {
|
||||
if(err) return reject({
|
||||
'message': `No such file or directory: ${err.path}`,
|
||||
message: `No such file or directory: ${err.path}`,
|
||||
err
|
||||
});
|
||||
|
||||
if(!stats.isFile()) return reject({
|
||||
'message': `Not a file: ${path}`,
|
||||
message: `Not a file: ${path}`,
|
||||
stats
|
||||
});
|
||||
|
||||
|
@ -59,12 +52,12 @@ class FileUtils {
|
|||
return new Promise((resolve, reject) => {
|
||||
fs.stat(path, (err, stats) => {
|
||||
if(err) return reject({
|
||||
'message': `Directory does not exist: ${path}`,
|
||||
message: `Directory does not exist: ${path}`,
|
||||
err
|
||||
});
|
||||
|
||||
if(!stats.isDirectory()) return reject({
|
||||
'message': `Not a directory: ${path}`,
|
||||
message: `Not a directory: ${path}`,
|
||||
stats
|
||||
});
|
||||
|
||||
|
@ -77,13 +70,13 @@ class FileUtils {
|
|||
try {
|
||||
await this.fileExists(path);
|
||||
} catch (err) {
|
||||
throw(err);
|
||||
throw err;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(path, 'utf-8', (err, data) => {
|
||||
if(err) reject({
|
||||
'message': `Could not read file: ${path}`,
|
||||
if(err) return reject({
|
||||
message: `Could not read file: ${path}`,
|
||||
err
|
||||
});
|
||||
|
||||
|
@ -97,14 +90,13 @@ class FileUtils {
|
|||
try {
|
||||
readFile = await this.readFile(path);
|
||||
} catch(err) {
|
||||
throw(err);
|
||||
throw err;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = await Utils.tryParseJson(readFile);
|
||||
return parsed;
|
||||
return await Utils.tryParseJson(readFile);
|
||||
} catch (err) {
|
||||
throw(Object.assign(err, { path }));
|
||||
throw Object.assign(err, { path });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,8 +105,8 @@ class FileUtils {
|
|||
await this.directoryExists(path);
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readdir(path, (err, files) => {
|
||||
if (err) return reject(err);
|
||||
resolve(files);
|
||||
if (err) reject(err);
|
||||
else resolve(files);
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
|
@ -145,11 +137,8 @@ class FileUtils {
|
|||
static async createDirectory(path) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.mkdir(path, err => {
|
||||
if (err) {
|
||||
if (err.code === 'EEXIST') return resolve();
|
||||
else return reject(err);
|
||||
}
|
||||
resolve();
|
||||
if (err) reject(err);
|
||||
else resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -170,7 +159,6 @@ class FileUtils {
|
|||
}
|
||||
|
||||
class WindowUtils extends Module {
|
||||
|
||||
bindings() {
|
||||
this.openDevTools = this.openDevTools.bind(this);
|
||||
this.executeJavascript = this.executeJavascript.bind(this);
|
||||
|
@ -190,28 +178,32 @@ class WindowUtils extends Module {
|
|||
}
|
||||
|
||||
executeJavascript(script) {
|
||||
this.webContents.executeJavaScript(script);
|
||||
return this.webContents.executeJavaScript(script);
|
||||
}
|
||||
|
||||
injectScript(fpath, variable) {
|
||||
console.log(`Injecting: ${fpath}`);
|
||||
return WindowUtils.injectScript(this.window, fpath, variable);
|
||||
}
|
||||
|
||||
static injectScript(window, fpath, variable) {
|
||||
window = window.webContents || window;
|
||||
if (!window) return;
|
||||
// console.log(`Injecting: ${fpath} to`, window);
|
||||
|
||||
const escaped_path = fpath.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
||||
const escaped_variable = variable ? variable.replace(/\\/g, '\\\\').replace(/"/g, '\\"') : null;
|
||||
|
||||
if (variable) this.executeJavascript(`window["${escaped_variable}"] = require("${escaped_path}");`);
|
||||
else this.executeJavascript(`require("${escaped_path}");`);
|
||||
if (variable) return window.executeJavaScript(`window["${escaped_variable}"] = require("${escaped_path}");`);
|
||||
else return window.executeJavaScript(`require("${escaped_path}");`);
|
||||
}
|
||||
|
||||
events(event, callback) {
|
||||
on(event, callback) {
|
||||
this.webContents.on(event, callback);
|
||||
}
|
||||
|
||||
send(channel, message) {
|
||||
channel = channel.startsWith('bd-') ? channel : `bd-${channel}`;
|
||||
this.webContents.send(channel, message);
|
||||
return BDIpc.send(this.window, channel, message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
|
|
@ -1,5 +1,17 @@
|
|||
/**
|
||||
* BetterDiscord Sparkplug
|
||||
* Copyright (c) 2015-present JsSucks - https://github.com/JsSucks
|
||||
* All rights reserved.
|
||||
* https://github.com/JsSucks - https://betterdiscord.net
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* This file is evaluated in the renderer process!
|
||||
*/
|
||||
|
||||
(() => {
|
||||
if (window.__bd && window.__bd.ignited) return;
|
||||
if (module.exports.bd) return;
|
||||
|
||||
console.log('[BetterDiscord|Sparkplug]');
|
||||
|
||||
|
@ -7,12 +19,12 @@
|
|||
if (!ls) console.warn('[BetterDiscord|Sparkplug] Failed to hook localStorage :(');
|
||||
const wsOrig = window.WebSocket;
|
||||
|
||||
window.__bd = {
|
||||
const bd = module.exports.bd = {
|
||||
localStorage: ls,
|
||||
wsHook: null,
|
||||
wsOrig,
|
||||
ignited: true
|
||||
}
|
||||
};
|
||||
|
||||
class WSHook extends window.WebSocket {
|
||||
|
||||
|
@ -25,14 +37,14 @@
|
|||
console.info(`[BetterDiscord|WebSocket Proxy] new WebSocket detected, url: ${url}`);
|
||||
if (!url.includes('gateway.discord.gg')) return;
|
||||
|
||||
if (window.__bd.setWS) {
|
||||
window.__bd.setWS(this);
|
||||
if (bd.setWS) {
|
||||
bd.setWS(this);
|
||||
console.info(`[BetterDiscord|WebSocket Proxy] WebSocket sent to instance`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.info(`[BetterDiscord|WebSocket Proxy] WebSocket stored to __bd['wsHook']`);
|
||||
window.__bd.wsHook = this;
|
||||
console.info(`[BetterDiscord|WebSocket Proxy] WebSocket stored to bd.wsHook`);
|
||||
bd.wsHook = this;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,21 +5,19 @@
|
|||
"version": "0.4.0",
|
||||
"homepage": "https://betterdiscord.net",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"main": "dist/csseditor.js",
|
||||
"contributors": [
|
||||
"Jiiks",
|
||||
"Pohky"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Jiiks/BetterDiscordApp.git"
|
||||
"url": "https://github.com/JsSucks/BetterDiscordApp.git"
|
||||
},
|
||||
"private": false,
|
||||
"devDependencies": {
|
||||
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack --progress --colors",
|
||||
"watch": "webpack --progress --colors --watch"
|
||||
"watch": "webpack --progress --colors --watch",
|
||||
"release": "webpack --progress --colors --config=webpack.production.config.js"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
const { ipcRenderer } = window.require('electron');
|
||||
|
||||
export default class {
|
||||
|
||||
static on(channel, cb) {
|
||||
ipcRenderer.on(channel, (event, args) => cb(event, args));
|
||||
}
|
||||
|
||||
static async send(channel, message) {
|
||||
const __eid = Date.now().toString();
|
||||
ipcRenderer.send(
|
||||
channel.startsWith('bd-') ? channel: `bd-${channel}`,
|
||||
message === undefined ? { __eid } : Object.assign(message, { __eid })
|
||||
);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
ipcRenderer.once(__eid, (event, arg) => {
|
||||
if (arg.err) return reject(arg);
|
||||
resolve(arg);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static sendToDiscord(channel, message) {
|
||||
this.send('bd-sendToDiscord', { channel, message });
|
||||
}
|
||||
|
||||
}
|
|
@ -33,18 +33,18 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import '../../node_modules/codemirror/addon/scroll/simplescrollbars.js';
|
||||
import '../../node_modules/codemirror/mode/css/css.js';
|
||||
import '../../node_modules/codemirror/addon/hint/css-hint.js';
|
||||
import '../../node_modules/codemirror/addon/search/search.js';
|
||||
import '../../node_modules/codemirror/addon/search/searchcursor.js';
|
||||
import '../../node_modules/codemirror/addon/search/jump-to-line.js';
|
||||
import '../../node_modules/codemirror/addon/dialog/dialog.js';
|
||||
import '../../node_modules/codemirror/addon/hint/show-hint.js';
|
||||
import ClientIPC from 'bdipc';
|
||||
|
||||
import BDIpc from './BDIpc';
|
||||
import { remote } from 'electron';
|
||||
|
||||
const { remote } = window.require('electron');
|
||||
import 'codemirror/addon/scroll/simplescrollbars.js';
|
||||
import 'codemirror/mode/css/css.js';
|
||||
import 'codemirror/addon/hint/css-hint.js';
|
||||
import 'codemirror/addon/search/search.js';
|
||||
import 'codemirror/addon/search/searchcursor.js';
|
||||
import 'codemirror/addon/search/jump-to-line.js';
|
||||
import 'codemirror/addon/dialog/dialog.js';
|
||||
import 'codemirror/addon/hint/show-hint.js';
|
||||
|
||||
const ExcludedIntelliSenseTriggerKeys = {
|
||||
'8': 'backspace',
|
||||
|
@ -131,42 +131,38 @@
|
|||
}
|
||||
},
|
||||
created() {
|
||||
BDIpc.on('set-scss', (_, data) => {
|
||||
if (data.error) {
|
||||
console.log(data.error);
|
||||
return;
|
||||
}
|
||||
console.log(data);
|
||||
this.setScss(data.scss);
|
||||
});
|
||||
ClientIPC.on('set-scss', (_, scss) => this.setScss(scss));
|
||||
|
||||
BDIpc.on('scss-error', (_, err) => {
|
||||
ClientIPC.on('scss-error', (_, err) => {
|
||||
this.error = err;
|
||||
this.$forceUpdate();
|
||||
if (err)
|
||||
console.error('SCSS parse error:', err);
|
||||
});
|
||||
|
||||
BDIpc.on('set-liveupdate', (e, liveUpdate) => this.liveUpdate = liveUpdate);
|
||||
ClientIPC.on('set-liveupdate', (e, liveUpdate) => this.liveUpdate = liveUpdate);
|
||||
},
|
||||
mounted() {
|
||||
this.codemirror.on('keyup', this.cmOnKeyUp);
|
||||
BDIpc.sendToDiscord('get-scss');
|
||||
BDIpc.sendToDiscord('get-liveupdate');
|
||||
|
||||
(async () => {
|
||||
this.setScss(await ClientIPC.sendToDiscord('get-scss'));
|
||||
this.liveUpdate = await ClientIPC.sendToDiscord('get-liveupdate');
|
||||
})();
|
||||
},
|
||||
watch: {
|
||||
liveUpdate(liveUpdate) {
|
||||
BDIpc.sendToDiscord('set-liveupdate', liveUpdate);
|
||||
ClientIPC.sendToDiscord('set-liveupdate', liveUpdate);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
save() {
|
||||
const scss = this.codemirror.getValue();
|
||||
BDIpc.sendToDiscord('save-scss', scss);
|
||||
ClientIPC.sendToDiscord('save-scss', scss);
|
||||
},
|
||||
update() {
|
||||
const scss = this.codemirror.getValue();
|
||||
BDIpc.sendToDiscord('update-scss', scss);
|
||||
ClientIPC.sendToDiscord('update-scss', scss);
|
||||
},
|
||||
toggleaot() {
|
||||
this.alwaysOnTop = !this.alwaysOnTop;
|
||||
|
@ -180,7 +176,7 @@
|
|||
this.codemirror.setValue(scss || '');
|
||||
},
|
||||
cmOnChange(value) {
|
||||
if(this.liveUpdate) BDIpc.sendToDiscord('update-scss', value);
|
||||
if(this.liveUpdate) ClientIPC.sendToDiscord('update-scss', value);
|
||||
},
|
||||
cmOnKeyUp(editor, event) {
|
||||
if (event.ctrlKey) return;
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
const
|
||||
path = require('path'),
|
||||
webpack = require('webpack');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
|
||||
const vueLoader = {
|
||||
test: /\.(vue)$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'vue-loader'
|
||||
}
|
||||
};
|
||||
|
||||
const scssLoader = {
|
||||
test: /\.(css|scss)$/,
|
||||
loader: ['css-loader', 'sass-loader']
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
entry: './src/index.js',
|
||||
|
@ -21,9 +21,17 @@ module.exports = {
|
|||
module: {
|
||||
loaders: [vueLoader, scssLoader]
|
||||
},
|
||||
externals: {
|
||||
electron: 'window.require("electron")',
|
||||
fs: 'window.require("fs")'
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
vue$: path.resolve('..', 'node_modules', 'vue', 'dist', 'vue.esm.js')
|
||||
}
|
||||
},
|
||||
modules: [
|
||||
path.resolve('..', 'node_modules'),
|
||||
path.resolve('..', 'common', 'modules')
|
||||
]
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
||||
|
||||
const vueLoader = {
|
||||
test: /\.(vue)$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'vue-loader'
|
||||
};
|
||||
|
||||
const scssLoader = {
|
||||
test: /\.(css|scss)$/,
|
||||
loader: ['css-loader', 'sass-loader']
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
entry: './src/index.js',
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'csseditor-release.js'
|
||||
},
|
||||
module: {
|
||||
loaders: [vueLoader, scssLoader]
|
||||
},
|
||||
externals: {
|
||||
electron: 'window.require("electron")',
|
||||
fs: 'window.require("fs")'
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
vue$: path.resolve('..', 'node_modules', 'vue', 'dist', 'vue.esm.js')
|
||||
},
|
||||
modules: [
|
||||
path.resolve('..', 'node_modules'),
|
||||
path.resolve('..', 'common', 'modules')
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
PRODUCTION: JSON.stringify(true)
|
||||
}),
|
||||
new UglifyJsPlugin()
|
||||
]
|
||||
};
|
53
gulpfile.js
53
gulpfile.js
|
@ -6,64 +6,75 @@ const
|
|||
merge = require('gulp-merge'),
|
||||
copy = require('gulp-copy'),
|
||||
rename = require('gulp-rename'),
|
||||
inject = require('gulp-inject-string'),
|
||||
copydeps = require('gulp-npm-copy-deps');
|
||||
|
||||
const mainpkg = require('./package.json');
|
||||
const corepkg = require('./core/package.json');
|
||||
const clientpkg = require('./client/package.json');
|
||||
const editorpkg = require('./csseditor/package.json');
|
||||
|
||||
const releasepkg = function() {
|
||||
delete mainpkg.main;
|
||||
delete mainpkg.devDependencies;
|
||||
delete mainpkg.scripts;
|
||||
return fs.writeFileSync('./release/package.json', JSON.stringify(mainpkg, null, 2));
|
||||
};
|
||||
|
||||
const client = function() {
|
||||
return pump([
|
||||
gulp.src('./client/dist/*.client.js'),
|
||||
gulp.src('./client/dist/*.client-release.js'),
|
||||
rename(`client.${clientpkg.version}.js`),
|
||||
gulp.dest('./release')
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
const core = function() {
|
||||
return pump([
|
||||
gulp.src('./core/dist/modules/**/*'),
|
||||
copy('release/', { prefix: 2 })
|
||||
]);
|
||||
}
|
||||
|
||||
const core2 = function() {
|
||||
return pump([
|
||||
gulp.src('./core/dist/main.js'),
|
||||
inject.after("'use strict';\n", 'const PRODUCTION = true;\n'),
|
||||
rename(`core.${corepkg.version}.js`),
|
||||
gulp.dest('./release')
|
||||
]);
|
||||
}
|
||||
|
||||
const core3 = function() {
|
||||
return fs.writeFileSync('./release/index.js', `module.exports = require('./core.${corepkg.version}.js');`);
|
||||
}
|
||||
};
|
||||
|
||||
const sparkplug = function() {
|
||||
return pump([
|
||||
gulp.src('./core/dist/sparkplug.js'),
|
||||
gulp.dest('./release')
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
const core_modules = function() {
|
||||
return pump([
|
||||
gulp.src('./core/dist/modules/**/*'),
|
||||
copy('release/', { prefix: 2 })
|
||||
]);
|
||||
};
|
||||
|
||||
const index = function() {
|
||||
return fs.writeFileSync('./release/index.js', `module.exports = require('./core.${corepkg.version}.js');`);
|
||||
};
|
||||
|
||||
const cssEditor = function() {
|
||||
return pump([
|
||||
gulp.src('./csseditor/dist/**/*'),
|
||||
gulp.src('./csseditor/dist/csseditor-release.js'),
|
||||
rename('csseditor.js'),
|
||||
copy('release/csseditor', { prefix: 2 })
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
const deps = function() {
|
||||
return copydeps('./', './release');
|
||||
}
|
||||
};
|
||||
|
||||
const bindings = function() {
|
||||
const node_sass_bindings = function() {
|
||||
return pump([
|
||||
gulp.src('./other/node_sass_bindings/**/*'),
|
||||
copy('release/node_modules/node-sass/vendor', { prefix: 2 })
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
gulp.task('release', function () {
|
||||
del(['./release/**/*']).then(() => merge(client(), core(), core2(), core3(), sparkplug(), cssEditor(), deps(), bindings()));
|
||||
del(['./release/**/*']).then(() => merge(releasepkg(), client(), core(), sparkplug(), core_modules(), index(), cssEditor(), deps(), node_sass_bindings()));
|
||||
});
|
||||
|
|
|
@ -5,19 +5,16 @@
|
|||
"version": "2.0.0",
|
||||
"homepage": "https://betterdiscord.net",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"main": "dist/installer.js",
|
||||
"contributors": [
|
||||
"Jiiks",
|
||||
"Pohky"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Jiiks/BetterDiscordApp.git"
|
||||
"url": "https://github.com/JsSucks/BetterDiscordApp.git"
|
||||
},
|
||||
"private": false,
|
||||
"devDependencies": {
|
||||
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack --progress --colors",
|
||||
"watch": "webpack --progress --colors --watch"
|
||||
|
|
|
@ -2,16 +2,17 @@ const
|
|||
path = require('path'),
|
||||
webpack = require('webpack'),
|
||||
HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
|
||||
const vueLoader = {
|
||||
test: /\.(vue)$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'vue-loader'
|
||||
}
|
||||
};
|
||||
|
||||
const scssLoader = {
|
||||
test: /\.(css|scss)$/,
|
||||
loader: ['css-loader', 'sass-loader']
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
entry: './src/index.js',
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -12,7 +12,7 @@
|
|||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Jiiks/BetterDiscordApp.git"
|
||||
"url": "https://github.com/JsSucks/BetterDiscordApp.git"
|
||||
},
|
||||
"private": false,
|
||||
"dependencies": {
|
||||
|
@ -38,6 +38,7 @@
|
|||
"gulp": "^3.9.1",
|
||||
"gulp-babel": "^7.0.0",
|
||||
"gulp-copy": "^1.1.0",
|
||||
"gulp-inject-string": "^1.1.1",
|
||||
"gulp-merge": "^0.1.1",
|
||||
"gulp-npm-copy-deps": "^1.0.2",
|
||||
"gulp-plumber": "^1.2.0",
|
||||
|
@ -46,11 +47,10 @@
|
|||
"html-webpack-plugin": "^3.0.6",
|
||||
"jquery": "^3.2.1",
|
||||
"lodash": "^4.17.4",
|
||||
"nedb": "^1.8.0",
|
||||
"node-gyp": "^3.6.2",
|
||||
"node-sass": "^4.7.2",
|
||||
"pump": "^2.0.0",
|
||||
"sass-loader": "^6.0.6",
|
||||
"uglifyjs-webpack-plugin": "^1.2.4",
|
||||
"v-tooltip": "^2.0.0-rc.30",
|
||||
"vue": "^2.5.13",
|
||||
"vue-codemirror": "^4.0.3",
|
||||
|
@ -73,6 +73,7 @@
|
|||
"lint": "eslint -f unix client/src core/src csseditor/src",
|
||||
"test": "npm run build && npm run lint",
|
||||
"build_node-sass": "node scripts/build-node-sass.js",
|
||||
"release": "npm run lint && npm run build && gulp release"
|
||||
"build_release": "npm run release --prefix client && npm run build --prefix core && npm run release --prefix csseditor && npm run build --prefix installer",
|
||||
"release": "npm run lint && npm run build_release && gulp release"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,11 +4,11 @@ set "ELECTRON=1.6.15"
|
|||
set "PLATFORM=win32"
|
||||
set "ARCH=ia32"
|
||||
set "VER=53"
|
||||
set "VENDOR_PATH=..\node_modules\node-sass\vendor"
|
||||
set "BUILD_PATH=..\node_modules\node-sass\build\Release\binding.node"
|
||||
set "VENDOR_PATH=.\node_modules\node-sass\vendor"
|
||||
set "BUILD_PATH=.\node_modules\node-sass\build\Release\binding.node"
|
||||
|
||||
echo Building %PLATFORM%-%ARCH% bindings
|
||||
call ../node_modules/.bin/electron-rebuild -v=%ELECTRON% -a=%ARCH% -m ../node_modules/node-sass
|
||||
call ./node_modules/.bin/electron-rebuild -v=%ELECTRON% -a=%ARCH% -m ./node_modules/node-sass
|
||||
|
||||
if exist %VENDOR_PATH%\%PLATFORM%-%ARCH%-%VER%\binding.node (
|
||||
echo Deleting old %VENDOR_PATH%\%PLATFORM%-%ARCH%-%VER%\binding.node
|
||||
|
@ -30,7 +30,7 @@ if not exist %BUILD_PATH% (
|
|||
set "ARCH=x64"
|
||||
|
||||
echo Building %PLATFORM%-%ARCH% bindings
|
||||
call ../node_modules/.bin/electron-rebuild -v=%ELECTRON% -a=%ARCH% -m ../node_modules/node-sass
|
||||
call ./node_modules/.bin/electron-rebuild -v=%ELECTRON% -a=%ARCH% -m ./node_modules/node-sass
|
||||
|
||||
if exist %VENDOR_PATH%\%PLATFORM%-%ARCH%-%VER%\binding.node (
|
||||
echo Deleting old %VENDOR_PATH%\%PLATFORM%-%ARCH%-%VER%\binding.node
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"version": "0.3.2",
|
||||
"paths": []
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>CSS Editor</title>
|
||||
<link rel="stylesheet" href="../../node_modules/codemirror/lib/codemirror.css" />
|
||||
<link rel="stylesheet" href="../../node_modules/codemirror/theme/material.css" />
|
||||
<link rel="stylesheet" href="../../node_modules/codemirror/addon/scroll/simplescrollbars.css" />
|
||||
<link rel="stylesheet" href="../../node_modules/codemirror/addon/dialog/dialog.css" />
|
||||
<link rel="stylesheet" href="../../node_modules/codemirror/addon/hint/show-hint.css" />
|
||||
<link rel="stylesheet" href="./main.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="titlebar">
|
||||
<div class="draggable"></div>
|
||||
<div class="icon">
|
||||
<div class="inner"></div>
|
||||
</div>
|
||||
<div class="title">CSS Editor</div>
|
||||
<div class="flex-spacer"></div>
|
||||
<div class="controls">
|
||||
<button title="Toggle always on top" id="toggleaot">P</button>
|
||||
<button title="Close CSS Editor" id="closeeditor">X</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="spinner"><div class="valign">Loading Please Wait...</div></div>
|
||||
<div class="editor" id="editor">
|
||||
</div>
|
||||
<div class="tools">
|
||||
<div class="flex-row">
|
||||
<button id="btnSave">Save</button>
|
||||
<button id="btnUpdate">Update</button>
|
||||
<div class="flex-spacer"></div>
|
||||
<div id="chkboxLiveUpdate"><input type="checkbox"><span>Live Update</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>const $ = require('../../node_modules/jquery/dist/jquery.min.js');</script>
|
||||
<script src="../../node_modules/codemirror/lib/codemirror.js"></script>
|
||||
<script src="../../node_modules/codemirror/mode/css/css.js"></script>
|
||||
<script src="../../node_modules/codemirror/addon/scroll/simplescrollbars.js"></script>
|
||||
<script src="../../node_modules/codemirror/addon/search/search.js"></script>
|
||||
<script src="../../node_modules/codemirror/addon/search/searchcursor.js"></script>
|
||||
<script src="../../node_modules/codemirror/addon/search/jump-to-line.js"></script>
|
||||
<script src="../../node_modules/codemirror/addon/dialog/dialog.js"></script>
|
||||
<script src="../../node_modules/codemirror/addon/hint/show-hint.js"></script>
|
||||
<script src="../../node_modules/codemirror/addon/hint/css-hint.js"></script>
|
||||
<script src="./main.js"></script>
|
||||
</body>
|
||||
</html>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue