Merge master into csseditor
This commit is contained in:
commit
0e70eea85c
|
@ -0,0 +1,14 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[package.json]
|
||||
indent_size = 2
|
|
@ -21,11 +21,16 @@
|
|||
"babel-preset-es2015": "^6.24.1",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"babel-loader": "^7.1.2",
|
||||
"moment": "^2.20.1",
|
||||
"jquery": "^3.2.1"
|
||||
"jquery": "^3.2.1",
|
||||
"vue": "^2.5.13",
|
||||
"vue-loader": "^13.7.0",
|
||||
"vue-template-compiler": "^2.5.13",
|
||||
"css-loader": "^0.28.9",
|
||||
"sass-loader": "^6.0.6",
|
||||
"node-sass": "^4.7.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack --progress --colors",
|
||||
"watch": "webpack --progress --colors --watch"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,14 +10,26 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
const { Global, Logger, Utils, PluginManager, BDIpc, WebpackModules, SocketProxy, CssEditor } = require('./modules');
|
||||
const styles = require('./styles/index.scss');
|
||||
const { Global, Logger, Utils, PluginManager, BDIpc, WebpackModules, SocketProxy, Events } = require('./modules');
|
||||
//const { UI } = require('./modules/ui/index.jsx');
|
||||
|
||||
class BetterDiscord {
|
||||
|
||||
constructor() {
|
||||
window.bdUtils = Utils;
|
||||
window.wpm = WebpackModules;
|
||||
window.cssEditor = CssEditor;
|
||||
Events.on('global-ready', e => {
|
||||
const { UI } = require('./modules/ui/vueui.js');
|
||||
this.ui = new UI();
|
||||
});
|
||||
|
||||
//Inject styles to head for now
|
||||
const style = document.createElement('style');
|
||||
style.id = 'bd-main';
|
||||
style.type = 'text/css';
|
||||
style.appendChild(document.createTextNode(styles));
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -30,7 +42,7 @@ if (window.BetterDiscord) {
|
|||
'vendor': {
|
||||
jQuery: require('jquery'),
|
||||
$: require('jquery'),
|
||||
moment: require('moment')
|
||||
moment: window.wpm.getModuleByNameSync('Moment')
|
||||
}
|
||||
};
|
||||
}
|
|
@ -11,12 +11,14 @@
|
|||
const { Module } = require('./modulebase');
|
||||
const { Events } = require('./events');
|
||||
const { BDIpc } = require('./bdipc');
|
||||
const { WebpackModules } = require('./webpackmodules');
|
||||
|
||||
class Global extends Module {
|
||||
|
||||
constructor(args) {
|
||||
super(args);
|
||||
this.first();
|
||||
window.gl = this;
|
||||
}
|
||||
|
||||
bindings() {
|
||||
|
@ -29,6 +31,13 @@ class Global extends Module {
|
|||
(async () => {
|
||||
const config = await BDIpc.send('getConfig');
|
||||
this.setState(config);
|
||||
/* const getReact = await WebpackModules.getModuleByProps(('createElement', 'cloneElement'));
|
||||
this.React = getReact[0].exports;
|
||||
window.React = this.React;
|
||||
const getReactDom = await WebpackModules.getModuleByProps(('render', 'findDOMNode'));
|
||||
this.reactDOM = getReactDom[0].exports;*/
|
||||
// this.setState(Object.assign(config, { React, reactDOM }));
|
||||
Events.emit('global-ready');
|
||||
})();
|
||||
|
||||
if (window.__bd) {
|
||||
|
@ -51,6 +60,10 @@ class Global extends Module {
|
|||
return this.state[name];
|
||||
}
|
||||
|
||||
getLoadedModule(name) {
|
||||
return this[name];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const _instance = new Global();
|
|
@ -0,0 +1,245 @@
|
|||
/**
|
||||
* BetterDiscord Plugin Manager
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const { Module } = require('./modulebase');
|
||||
const { FileUtils, Logger } = require('./utils');
|
||||
const { Global } = require('./global');
|
||||
const path = window.require('path');
|
||||
|
||||
class Plugin {
|
||||
|
||||
constructor(pluginInternals) {
|
||||
this.__pluginInternals = pluginInternals;
|
||||
}
|
||||
|
||||
get configs() { return this.__pluginInternals.configs }
|
||||
get info() { return this.__pluginInternals.info }
|
||||
get paths() { return this.__pluginInternals.paths }
|
||||
get main() { return this.__pluginInternals.main }
|
||||
get defaultConfig() { return this.configs.defaultConfig }
|
||||
get userConfig() { return this.configs.userConfig }
|
||||
get name() { return this.info.name }
|
||||
get authors() { return this.info.authors }
|
||||
get version() { return this.info.version }
|
||||
get pluginPath() { return this.paths.pluginPath }
|
||||
get dirName() { return this.paths.dirName }
|
||||
get enabled() { return this.userConfig.enabled }
|
||||
|
||||
start() {
|
||||
if (this.onStart) return this.onStart();
|
||||
return true; //Assume plugin started since it doesn't have onStart
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.onStop) return this.onStop();
|
||||
return true; //Assume plugin stopped since it doesn't have onStop
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class PluginManager extends Module {
|
||||
|
||||
setInitialState() {
|
||||
window.pm = this;
|
||||
this.setState({
|
||||
plugins: []
|
||||
});
|
||||
}
|
||||
|
||||
get plugins() {
|
||||
return this.state.plugins;
|
||||
}
|
||||
|
||||
get pluginsPath() {
|
||||
return Global.getObject('paths').find(path => path.id === 'plugins').path;
|
||||
}
|
||||
|
||||
async loadAllPlugins() {
|
||||
try {
|
||||
const directories = await FileUtils.readDir(this.pluginsPath);
|
||||
|
||||
for (let dir of directories) {
|
||||
try {
|
||||
await this.loadPlugin(dir);
|
||||
} catch (err) {
|
||||
//We don't want every plugin to fail loading when one does
|
||||
Logger.err('PluginManager', err);
|
||||
}
|
||||
}
|
||||
|
||||
return this.plugins;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async refreshPlugins() {
|
||||
if (this.plugins.length <= 0) return this.loadAllPlugins();
|
||||
try {
|
||||
const directories = await FileUtils.readDir(this.pluginsPath);
|
||||
for (let dir of directories) {
|
||||
//If a plugin is already loaded this should resolve.
|
||||
if (this.getPluginByDirName(dir)) continue;
|
||||
|
||||
try {
|
||||
//Load the plugin if not
|
||||
await this.loadPlugin(dir);
|
||||
} catch (err) {
|
||||
//We don't want every plugin to fail loading when one does
|
||||
Logger.err('PluginManager', err);
|
||||
}
|
||||
}
|
||||
|
||||
for (let plugin of this.plugins) {
|
||||
if (directories.includes(plugin.dirName)) continue;
|
||||
//Plugin was deleted manually, stop it and remove any reference
|
||||
try {
|
||||
if (plugin.enabled) plugin.stop();
|
||||
const { pluginPath } = plugin;
|
||||
const index = this.getPluginIndex(plugin);
|
||||
|
||||
delete window.require.cache[window.require.resolve(pluginPath)];
|
||||
this.plugins.splice(index, 1);
|
||||
} catch (err) {
|
||||
//This might fail but we don't have any other option at this point
|
||||
Logger.err('PluginManager', err);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async loadPlugin(pluginPath) {
|
||||
const { plugins } = this.state;
|
||||
const dirName = pluginPath;
|
||||
|
||||
try {
|
||||
pluginPath = path.join(this.pluginsPath, pluginPath);
|
||||
|
||||
const loaded = plugins.find(plugin => plugin.pluginPath === pluginPath);
|
||||
if (loaded) {
|
||||
throw { 'message': 'Attempted to load an already loaded plugin' };
|
||||
}
|
||||
|
||||
const readConfig = await this.readConfig(pluginPath);
|
||||
const mainPath = path.join(pluginPath, readConfig.main);
|
||||
const userConfigPath = path.join(pluginPath, 'user.config.json');
|
||||
|
||||
let userConfig = readConfig.defaultConfig;
|
||||
try {
|
||||
const readUserConfig = await FileUtils.readJsonFromFile(userConfigPath);
|
||||
userConfig = Object.assign({}, userConfig, readUserConfig);
|
||||
} 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*/}
|
||||
|
||||
const configs = {
|
||||
defaultConfig: readConfig.defaultConfig,
|
||||
userConfig
|
||||
};
|
||||
|
||||
const plugin = window.require(mainPath)(Plugin, {}, {});
|
||||
const instance = new plugin({configs, info: readConfig.info, main: readConfig.main, paths: { pluginPath, dirName }});
|
||||
|
||||
if (instance.enabled) instance.start();
|
||||
|
||||
plugins.push(instance);
|
||||
|
||||
this.setState(plugins);
|
||||
|
||||
return instance;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async reloadPlugin(plugin) {
|
||||
const _plugin = this.findPlugin(plugin);
|
||||
if (!_plugin) throw { 'message': 'Attempted to reload a plugin that is not loaded?' };
|
||||
if (!_plugin.stop()) throw { 'message': 'Plugin failed to stop!' };
|
||||
const index = this.getPluginIndex(_plugin);
|
||||
const { pluginPath, dirName } = _plugin;
|
||||
delete window.require.cache[window.require.resolve(pluginPath)];
|
||||
|
||||
this.plugins.splice(index, 1);
|
||||
|
||||
return this.loadPlugin(dirName);
|
||||
}
|
||||
|
||||
//TODO make this nicer
|
||||
findPlugin(wild) {
|
||||
let plugin = this.getPluginByName(wild);
|
||||
if (plugin) return plugin;
|
||||
plugin = this.getPluginById(wild);
|
||||
if (plugin) return plugin;
|
||||
plugin = this.getPluginByPath(wild);
|
||||
if (plugin) return plugin;
|
||||
return this.getPluginByDirName(wild);
|
||||
}
|
||||
|
||||
getPluginIndex(plugin) { return this.plugins.findIndex(p => p === plugin) }
|
||||
getPluginByName(name) { return this.plugins.find(p => p.name === name) }
|
||||
getPluginById(id) { return this.plugins.find(p => p.id === id) }
|
||||
getPluginByPath(path) { return this.plugins.find(p => p.pluginPath === path) }
|
||||
getPluginByDirName(dirName) { return this.plugins.find(p => p.dirName === dirName) }
|
||||
|
||||
stopPlugin(name) {
|
||||
const plugin = this.getPluginByName(name);
|
||||
try {
|
||||
if (plugin && plugin.instance) return plugin.instance.stop();
|
||||
} catch (err) {
|
||||
Logger.err('PluginManager', err);
|
||||
}
|
||||
return true; //Return true anyways since plugin doesn't exist
|
||||
}
|
||||
|
||||
startPlugin(name) {
|
||||
const plugin = this.getPluginByName(name);
|
||||
try {
|
||||
if (plugin && plugin.instance) return plugin.instance.start();
|
||||
} catch (err) {
|
||||
Logger.err('PluginManager', err);
|
||||
}
|
||||
return true; //Return true anyways since plugin doesn't exist
|
||||
}
|
||||
|
||||
async readConfig(path) {
|
||||
path = `${path}/config.json`;
|
||||
return FileUtils.readJsonFromFile(path);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const _instance = new PluginManager();
|
||||
|
||||
async function pluginManager(pluginName) {
|
||||
|
||||
try {
|
||||
//Load test plugin
|
||||
const plugin = await _instance.loadPlugin(pluginName);
|
||||
//Attempt to load the same plugin again
|
||||
const plugin2 = await _instance.loadPlugin(pluginName);
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.log(`Failed to load plugin! ${err.message}`);
|
||||
}
|
||||
|
||||
try {
|
||||
//Reload test plugin
|
||||
const reloadedPlugin = await _instance.reloadPlugin('Example Plugin');
|
||||
} catch (err) {
|
||||
console.log(`Failed to reload plugin! ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (window.bdTests) window.bdTests.pluginManager = pluginManager;
|
||||
else window.bdTests = { pluginManager };
|
||||
|
||||
module.exports = { PluginManager: _instance }
|
|
@ -9,7 +9,6 @@
|
|||
*/
|
||||
|
||||
const { Module } = require('./modulebase');
|
||||
const moment = require('moment');
|
||||
const fs = window.require('fs');
|
||||
const path = window.require('path');
|
||||
|
||||
|
@ -17,13 +16,31 @@ const logs = [];
|
|||
|
||||
class Logger {
|
||||
|
||||
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') {
|
||||
message = message.message || message;
|
||||
if (typeof message === 'object') {
|
||||
//TODO object handler for logs
|
||||
console.log(message);
|
||||
return;
|
||||
}
|
||||
level = this.parseLevel(level);
|
||||
console[level]('[%cBetter%cDiscord:%s] %s', 'color: #3E82E5', '', `${module}${level === 'debug' ? '|DBG' : ''}`, message);
|
||||
logs.push(`[${moment().format('DD/MM/YY hh:mm:ss')}|${module}|${level}] ${message}`);
|
||||
logs.push(`[${BetterDiscord.vendor.moment().format('DD/MM/YY hh:mm:ss')}|${module}|${level}] ${message}`);
|
||||
window.bdlogs = logs;
|
||||
}
|
||||
|
||||
static logError(err) {
|
||||
if (!err.module && !err.message) {
|
||||
console.log(err);
|
||||
return;
|
||||
}
|
||||
this.err(err.module, err.message);
|
||||
}
|
||||
|
||||
static get levels() {
|
||||
return {
|
||||
'log': 'log',
|
||||
|
@ -121,6 +138,15 @@ class FileUtils {
|
|||
});
|
||||
}
|
||||
|
||||
static async writeFile(path, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.writeFile(path, data, err => {
|
||||
if (err) return reject(err);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static async readJsonFromFile(path) {
|
||||
let readFile;
|
||||
try {
|
||||
|
@ -136,7 +162,69 @@ class FileUtils {
|
|||
throw (Object.assign(err, { path }));
|
||||
}
|
||||
}
|
||||
|
||||
static async writeJsonToFile(path, json) {
|
||||
return this.writeFile(path, JSON.stringify(json));
|
||||
}
|
||||
|
||||
static async readDir(path) {
|
||||
try {
|
||||
await this.directoryExists(path);
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readdir(path, (err, files) => {
|
||||
if (err) return reject(err);
|
||||
resolve(files);
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Filters {
|
||||
static byProperties(props, selector = m => m) {
|
||||
return module => {
|
||||
const component = selector(module);
|
||||
if (!component) return false;
|
||||
return props.every(property => component[property] !== undefined);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { Logger, Utils, FileUtils }
|
||||
static byPrototypeFields(fields, selector = m => m) {
|
||||
return module => {
|
||||
const component = selector(module);
|
||||
if (!component) return false;
|
||||
if (!component.prototype) return false;
|
||||
for (const field of fields) {
|
||||
if (!component.prototype[field]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static byCode(search, selector = m => m) {
|
||||
return module => {
|
||||
const method = selector(module);
|
||||
if (!method) return false;
|
||||
return method.toString().search(search) !== -1;
|
||||
}
|
||||
}
|
||||
|
||||
static byDisplayName(name) {
|
||||
return module => {
|
||||
return module && module.displayName === name;
|
||||
}
|
||||
}
|
||||
|
||||
static combine(...filters) {
|
||||
return module => {
|
||||
for (const filter of filters) {
|
||||
if (!filter(module)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { Logger, Utils, FileUtils, Filters }
|
|
@ -0,0 +1,289 @@
|
|||
/**
|
||||
* BetterDiscord Client WebpackModules Module
|
||||
* 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.
|
||||
*/
|
||||
const { Filters } = require('./utils');
|
||||
|
||||
const KnownModules = {
|
||||
React: Filters.byProperties(['createElement', 'cloneElement']),
|
||||
ReactDOM: Filters.byProperties(['render', 'findDOMNode']),
|
||||
|
||||
/* Guild Info, Stores, and Utilities */
|
||||
GuildStore: Filters.byProperties(['getGuild']),
|
||||
SortedGuildStore: Filters.byProperties(['getSortedGuilds']),
|
||||
SelectedGuildStore: Filters.byProperties(['getLastSelectedGuildId']),
|
||||
GuildSync: Filters.byProperties(["getSyncedGuilds"]),
|
||||
GuildInfo: Filters.byProperties(["getAcronym"]),
|
||||
GuildChannelsStore: Filters.byProperties(['getChannels', 'getDefaultChannel']),
|
||||
GuildMemberStore: Filters.byProperties(['getMember']),
|
||||
MemberCountStore: Filters.byProperties(["getMemberCounts"]),
|
||||
GuildEmojiStore: Filters.byProperties(['getEmojis']),
|
||||
GuildActions: Filters.byProperties(['markGuildAsRead']),
|
||||
GuildPermissions: Filters.byProperties(['getGuildPermissions']),
|
||||
|
||||
/* Channel Store & Actions */
|
||||
ChannelStore: Filters.byProperties(['getChannels', 'getDMFromUserId']),
|
||||
SelectedChannelStore: Filters.byProperties(['getLastSelectedChannelId']),
|
||||
ChannelActions: Filters.byProperties(["selectChannel"]),
|
||||
|
||||
/* Current User Info, State and Settings */
|
||||
CurrentUserInfo: Filters.byProperties(["getToken"]),
|
||||
CurrentUserState: Filters.byProperties(["guildPositions"]),
|
||||
AccountManager: Filters.byProperties(['register', 'login']),
|
||||
UserSettingsUpdater: Filters.byProperties(['updateRemoteSettings']),
|
||||
OnlineWatcher: Filters.byProperties(['isOnline']),
|
||||
CurrentUserIdle: Filters.byProperties(['getIdleTime']),
|
||||
RelationshipStore: Filters.byProperties(['isBlocked']),
|
||||
MentionStore: Filters.byProperties(["getMentions"]),
|
||||
|
||||
/* User Stores and Utils */
|
||||
UserStore: Filters.byProperties(['getCurrentUser']),
|
||||
UserStatusStore: Filters.byProperties(['getStatuses']),
|
||||
UserTypingStore: Filters.byProperties(['isTyping']),
|
||||
UserActivityStore: Filters.byProperties(['getActivity']),
|
||||
UserNameResolver: Filters.byProperties(['getName']),
|
||||
|
||||
|
||||
/* Emoji Store and Utils */
|
||||
EmojiInfo: Filters.byProperties(['isEmojiDisabled']),
|
||||
EmojiUtils: Filters.byProperties(['diversitySurrogate']),
|
||||
EmojiStore: Filters.byProperties(['getByCategory', 'EMOJI_NAME_RE']),
|
||||
|
||||
/* Invite Store and Utils */
|
||||
InviteStore: Filters.byProperties(["getInvites"]),
|
||||
InviteResolver: Filters.byProperties(['findInvite']),
|
||||
InviteActions: Filters.byProperties(['acceptInvite']),
|
||||
|
||||
|
||||
/* Discord Objects & Utils */
|
||||
DiscordConstants: Filters.byProperties(["Permissions", "ActivityTypes", "StatusTypes"]),
|
||||
Permissions: Filters.byProperties(['getHighestRole']),
|
||||
ColorConverter: Filters.byProperties(['hex2int']),
|
||||
ColorShader: Filters.byProperties(['darken']),
|
||||
ClassResolver: Filters.byProperties(["getClass"]),
|
||||
ButtonData: Filters.byProperties(["ButtonSizes"]),
|
||||
IconNames: Filters.byProperties(["IconNames"]),
|
||||
|
||||
/* Discord Messages */
|
||||
HistoryUtils: Filters.byProperties(['transitionTo', 'replaceWith', 'getHistory']),
|
||||
MessageActions: Filters.byProperties(['jumpToMessage', '_sendMessage']),
|
||||
MessageQueue: Filters.byProperties(['enqueue']),
|
||||
MessageParser: Filters.byProperties(['createMessage', 'parse', 'unparse']),
|
||||
|
||||
/* In-Game Overlay */
|
||||
OverlayUserPopoutSettings: Filters.byProperties(['openUserPopout']),
|
||||
OverlayUserPopoutInfo: Filters.byProperties(['getOpenedUserPopout']),
|
||||
|
||||
/* Experiments */
|
||||
ExperimentStore: Filters.byProperties(['getExperimentOverrides']),
|
||||
ExperimentsManager: Filters.byProperties(['isDeveloper']),
|
||||
CurrentExperiment: Filters.byProperties(['getExperimentId']),
|
||||
|
||||
|
||||
/* Images, Avatars and Utils */
|
||||
ImageResolver: Filters.byProperties(["getUserAvatarURL"]),
|
||||
ImageUtils: Filters.byProperties(['getSizedImageSrc']),
|
||||
AvatarDefaults: Filters.byProperties(["getUserAvatarURL", "DEFAULT_AVATARS"]),
|
||||
|
||||
/* Drag & Drop */
|
||||
DNDActions: Filters.byProperties(["beginDrag"]),
|
||||
DNDSources: Filters.byProperties(["addTarget"]),
|
||||
DNDObjects: Filters.byProperties(["DragSource"]),
|
||||
|
||||
/* Electron & Other Internals with Utils*/
|
||||
ElectronModule: Filters.byProperties(["_getMainWindow"]),
|
||||
Dispatcher: Filters.byProperties(['dirtyDispatch']),
|
||||
PathUtils: Filters.byProperties(["hasBasename"]),
|
||||
NotificationModule: Filters.byProperties(["showNotification"]),
|
||||
RouterModule: Filters.byProperties(["Router"]),
|
||||
APIModule: Filters.byProperties(["getAPIBaseURL"]),
|
||||
AnalyticEvents: Filters.byProperties(["AnalyticEventConfigs"]),
|
||||
KeyGenerator: Filters.byCode(/"binary"/),
|
||||
Buffers: Filters.byProperties(['Buffer', 'kMaxLength']),
|
||||
DeviceStore: Filters.byProperties(['getDevices']),
|
||||
SoftwareInfo: Filters.byProperties(["os"]),
|
||||
CurrentContext: Filters.byProperties(["setTagsContext"]),
|
||||
|
||||
/* Media Stuff (Audio/Video) */
|
||||
MediaDeviceInfo: Filters.byProperties(["Codecs", "SUPPORTED_BROWSERS"]),
|
||||
MediaInfo: Filters.byProperties(["getOutputVolume"]),
|
||||
MediaEngineInfo: Filters.byProperties(['MediaEngineFeatures']),
|
||||
VoiceInfo: Filters.byProperties(["EchoCancellation"]),
|
||||
VideoStream: Filters.byProperties(["getVideoStream"]),
|
||||
SoundModule: Filters.byProperties(["playSound"]),
|
||||
|
||||
/* Window, DOM, HTML */
|
||||
WindowInfo: Filters.byProperties(['isFocused', 'windowSize']),
|
||||
TagInfo: Filters.byProperties(['VALID_TAG_NAMES']),
|
||||
DOMInfo: Filters.byProperties(['canUseDOM']),
|
||||
HTMLUtils: Filters.byProperties(['htmlFor', 'sanitizeUrl']),
|
||||
|
||||
/* Locale/Location and Time */
|
||||
LocaleManager: Filters.byProperties(['setLocale']),
|
||||
Moment: Filters.byProperties(['parseZone']),
|
||||
LocationManager: Filters.byProperties(["createLocation"]),
|
||||
Timestamps: Filters.byProperties(["fromTimestamp"]),
|
||||
|
||||
/* Strings and Utils */
|
||||
Strings: Filters.byProperties(["TEXT", "TEXTAREA_PLACEHOLDER"]),
|
||||
StringFormats: Filters.byProperties(['a', 'z']),
|
||||
StringUtils: Filters.byProperties(["toASCII"]),
|
||||
|
||||
/* URLs and Utils */
|
||||
URLParser: Filters.byProperties(['Url', 'parse']),
|
||||
ExtraURLs: Filters.byProperties(['getArticleURL']),
|
||||
|
||||
|
||||
/* DOM/React Components */
|
||||
/* ==================== */
|
||||
UserSettingsWindow: Filters.byProperties(['open', 'updateAccount']),
|
||||
LayerManager: Filters.byProperties(['popLayer', 'pushLayer']),
|
||||
|
||||
/* Modals */
|
||||
ModalStack: Filters.byProperties(['push', 'update', 'pop', 'popWithKey']),
|
||||
UserProfileModals: Filters.byProperties(['fetchMutualFriends', 'setSection']),
|
||||
ConfirmModal: Filters.byPrototypeFields(['handleCancel', 'handleSubmit', 'handleMinorConfirm']),
|
||||
|
||||
/* Popouts */
|
||||
PopoutStack: Filters.byProperties(['open', 'close', 'closeAll']),
|
||||
PopoutOpener: Filters.byProperties(['openPopout']),
|
||||
EmojiPicker: Filters.byPrototypeFields(['onHoverEmoji', 'selectEmoji']),
|
||||
|
||||
/* Context Menus */
|
||||
ContextMenuActions: Filters.byCode(/CONTEXT_MENU_CLOSE/, c => c.close),
|
||||
ContextMenuItemsGroup: Filters.byCode(/itemGroup/),
|
||||
ContextMenuItem: Filters.byCode(/\.label\b.*\.hint\b.*\.action\b/),
|
||||
|
||||
/* In-Message Links */
|
||||
ExternalLink: Filters.byCode(/\.trusted\b/)
|
||||
};
|
||||
|
||||
const Cache = {};
|
||||
|
||||
|
||||
class WebpackModules {
|
||||
|
||||
/* Synchronous */
|
||||
static getModuleByNameSync(name, fallback) {
|
||||
if (Cache.hasOwnProperty(name)) return Cache[name];
|
||||
if (KnownModules.hasOwnProperty(name)) fallback = KnownModules[name];
|
||||
if (!fallback) return null;
|
||||
return Cache[name] = this.getModuleSync(fallback, true);
|
||||
}
|
||||
|
||||
static getModuleByDisplayNameSync(name) {
|
||||
return this.getModuleSync(Filters.byDisplayName(name), true);
|
||||
}
|
||||
|
||||
static getModuleByRegexSync(regex, first = true) {
|
||||
return this.getModuleSync(Filters.byCode(regex), first);
|
||||
}
|
||||
|
||||
static getModuleByPrototypesSync(prototypes, first = true) {
|
||||
return this.getModuleSync(Filters.byPrototypeFields(prototypes), first);
|
||||
}
|
||||
|
||||
static getModuleByPropsSync(props, first = true) {
|
||||
return this.getModuleSync(Filters.byProperties(props), first);
|
||||
}
|
||||
|
||||
static getModuleSync(filter, first = true) {
|
||||
const modules = this.getAllModulesSync();
|
||||
const rm = [];
|
||||
for (let index in modules) {
|
||||
if (!modules.hasOwnProperty(index)) continue;
|
||||
const module = modules[index];
|
||||
const { exports } = module;
|
||||
let foundModule = null;
|
||||
|
||||
if (!exports) continue;
|
||||
if (exports.__esModule && exports.default && filter(exports.default)) foundModule = exports.default;
|
||||
if (filter(exports)) foundModule = exports;
|
||||
if (!foundModule) continue;
|
||||
if (first) return foundModule;
|
||||
rm.push(foundModule);
|
||||
}
|
||||
return rm;
|
||||
}
|
||||
|
||||
static getAllModulesSync() {
|
||||
const id = 'bd-webpackmodulessync';
|
||||
const __webpack_require__ = window['webpackJsonp'](
|
||||
[],
|
||||
{
|
||||
[id]: (module, exports, __webpack_require__) => exports.default = __webpack_require__
|
||||
},
|
||||
[id]).default;
|
||||
delete __webpack_require__.m[id];
|
||||
delete __webpack_require__.c[id];
|
||||
return __webpack_require__.c;
|
||||
}
|
||||
|
||||
/* Asynchronous */
|
||||
static async getModuleByName(name, first = true, fallback) {
|
||||
if (Cache.hasOwnProperty(name)) return Cache[name];
|
||||
if (KnownModules.hasOwnProperty(name)) fallback = KnownModules[name];
|
||||
if (!fallback) return null;
|
||||
return Cache[name] = await this.getModule(fallback, first);
|
||||
}
|
||||
|
||||
static async getModuleByDisplayNameSync(name) {
|
||||
return await this.getModule(Filters.byDisplayName(name), true);
|
||||
}
|
||||
|
||||
static async getModuleByRegexSync(regex, first = true) {
|
||||
return await this.getModule(Filters.byCode(regex), first);
|
||||
}
|
||||
|
||||
static async getModuleByPrototypes(prototypes, first = true) {
|
||||
return await this.getModule(Filters.byPrototypeFields(prototypes), first);
|
||||
}
|
||||
|
||||
static async getModuleByProps(props, first = true) {
|
||||
return await this.getModule(Filters.byProperties(props), first);
|
||||
}
|
||||
|
||||
static async getModule(filter, first = true) {
|
||||
const modules = await this.getAllModules();
|
||||
const rm = [];
|
||||
for (let index in modules) {
|
||||
if (!modules.hasOwnProperty(index)) continue;
|
||||
const module = modules[index];
|
||||
const { exports } = module;
|
||||
let foundModule = null;
|
||||
|
||||
if (!exports) continue;
|
||||
if (exports.__esModule && exports.default && filter(exports.default)) foundModule = exports.default;
|
||||
if (filter(exports)) foundModule = exports;
|
||||
if (!foundModule) continue;
|
||||
if (first) return foundModule;
|
||||
rm.push(foundModule);
|
||||
}
|
||||
return rm;
|
||||
}
|
||||
|
||||
static async getAllModules() {
|
||||
return new Promise(resolve => {
|
||||
const id = 'bd-webpackmodules';
|
||||
window['webpackJsonp'](
|
||||
[],
|
||||
{
|
||||
[id]: (module, exports, __webpack_require__) => {
|
||||
delete __webpack_require__.c[id];
|
||||
delete __webpack_require__.m[id];
|
||||
resolve(__webpack_require__.c);
|
||||
}
|
||||
},
|
||||
[id]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = { WebpackModules };
|
|
@ -1,9 +1,8 @@
|
|||
export { Global } from './global';
|
||||
export { Logger, Utils, FileUtils } from './utils';
|
||||
export { PluginManager } from './pluginmanager';
|
||||
export { Pluging } from './plugin';
|
||||
export { BDIpc } from './bdipc';
|
||||
export { WebpackModules } from './webpackmodules';
|
||||
export { Events } from './events';
|
||||
export { SocketProxy } from './discordsocket';
|
||||
export { CssEditor } from './csseditor';
|
||||
export { Global } from './core/global';
|
||||
export { Logger, Utils, FileUtils } from './core/utils';
|
||||
export { PluginManager } from './core/pluginmanager';
|
||||
export { Pluging } from './core/plugin';
|
||||
export { BDIpc } from './core/bdipc';
|
||||
export { WebpackModules } from './core/webpackmodules';
|
||||
export { Events } from './core/events';
|
||||
export { SocketProxy } from './core/discordsocket';
|
|
@ -1,156 +0,0 @@
|
|||
/**
|
||||
* BetterDiscord Plugin Manager
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const { Module } = require('./modulebase');
|
||||
const { FileUtils } = require('./utils');
|
||||
const { Global } = require('./global');
|
||||
const path = window.require('path');
|
||||
|
||||
class Plugin {
|
||||
|
||||
constructor(pluginInternals) {
|
||||
this.__pluginInternals = pluginInternals;
|
||||
}
|
||||
|
||||
get configs() { return this.__pluginInternals.configs }
|
||||
get info() { return this.__pluginInternals.info }
|
||||
get paths() { return this.__pluginInternals.paths }
|
||||
get main() { return this.__pluginInternals.main }
|
||||
get defaultConfig() { return this.configs.defaultConfig }
|
||||
get userConfig() { return this.configs.userConfig }
|
||||
get name() { return this.info.name }
|
||||
get authors() { return this.info.authors }
|
||||
get version() { return this.info.version }
|
||||
get pluginPath() { return this.paths.pluginPath }
|
||||
get enabled() { return this.userConfig.enabled }
|
||||
|
||||
start() {
|
||||
if (this.onStart) return this.onStart();
|
||||
return true; //Assume plugin started since it doesn't have onStart
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.onStop) return this.onStop();
|
||||
return true; //Assume plugin stopped since it doesn't have onStop
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class PluginManager extends Module {
|
||||
|
||||
setInitialState() {
|
||||
window.pm = this;
|
||||
this.setState({
|
||||
plugins: []
|
||||
});
|
||||
}
|
||||
|
||||
get plugins() {
|
||||
return this.state.plugins;
|
||||
}
|
||||
|
||||
pluginsPath() {
|
||||
return Global.getObject('paths').find(path => path.id === 'plugins').path;
|
||||
}
|
||||
|
||||
async loadPlugin(pluginPath) {
|
||||
const { plugins } = this.state;
|
||||
|
||||
try {
|
||||
const pluginsPath = this.pluginsPath();
|
||||
pluginPath = path.join(pluginsPath, pluginPath);
|
||||
|
||||
const loaded = plugins.find(plugin => plugin.pluginPath === pluginPath);
|
||||
if (loaded) {
|
||||
throw { 'message': 'Attempted to load an already loaded plugin' };
|
||||
}
|
||||
|
||||
const readConfig = await this.readConfig(pluginPath);
|
||||
const mainPath = path.join(pluginPath, readConfig.main);
|
||||
|
||||
//TODO Read plugin user config and call onStart if enabled
|
||||
const userConfigPath = path.join(pluginPath, 'user.config.json');
|
||||
|
||||
let userConfig = readConfig.defaultConfig;
|
||||
try {
|
||||
const readUserConfig = await FileUtils.readJsonFromFile(userConfigPath);
|
||||
userConfig = Object.assign(userConfig, readUserConfig);
|
||||
} 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*/}
|
||||
|
||||
const configs = {
|
||||
defaultConfig: readConfig.defaultConfig,
|
||||
userConfig
|
||||
};
|
||||
|
||||
const plugin = window.require(mainPath)(Plugin, {}, {});
|
||||
const instance = new plugin({configs, info: readConfig.info, main: readConfig.main, paths: { pluginPath }});
|
||||
|
||||
if (instance.enabled) instance.start();
|
||||
|
||||
plugins.push(instance);
|
||||
|
||||
this.setState(plugins);
|
||||
|
||||
return instance;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async reloadPlugin(pluginPath) {
|
||||
//TODO Cleanup loaded plugin
|
||||
return await this.loadPlugin(pluginPath);
|
||||
}
|
||||
|
||||
getPluginByName(name) { return this.plugins.find(plugin => plugin.name === name); }
|
||||
getPluginById(id) { return this.plugins.find(plugin => plugin.id === id); }
|
||||
|
||||
stopPlugin(name) {
|
||||
const plugin = this.getPluginByName(name);
|
||||
if (plugin && plugin.instance) return plugin.instance.stop();
|
||||
return true; //Return true anyways since plugin doesn't exist
|
||||
}
|
||||
|
||||
startPlugin(name) {
|
||||
const plugin = this.getPluginByName(name);
|
||||
if (plugin && plugin.instance) return plugin.instance.start();
|
||||
return true; //Return true anyways since plugin doesn't exist
|
||||
}
|
||||
|
||||
async readConfig(path) {
|
||||
path = `${path}/config.json`;
|
||||
return FileUtils.readJsonFromFile(path);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const _instance = new PluginManager();
|
||||
|
||||
async function pluginManager() {
|
||||
|
||||
const pluginName = 'Example';
|
||||
|
||||
try {
|
||||
//Load test plugin
|
||||
const plugin = await _instance.loadPlugin(pluginName);
|
||||
//Attempt to load the same plugin again
|
||||
const plugin2 = await _instance.loadPlugin(pluginName);
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.log(`Failed to load plugin! ${err.message}`);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
if (window.bdTests) window.bdTests.pluginManager = pluginManager;
|
||||
else window.bdTests = { pluginManager };
|
||||
|
||||
module.exports = { PluginManager: _instance }
|
|
@ -0,0 +1,23 @@
|
|||
<template>
|
||||
<h2 class="bd-content-title">{{text}}</h2>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
text: {}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.bd-content-title {
|
||||
color: #4D7DEC;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 100%;
|
||||
outline: 0;
|
||||
padding: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,56 @@
|
|||
<template>
|
||||
<div class="bd-settingsWrap">
|
||||
<div class="bd-scroller">
|
||||
<ContentHeader text="Core Settings" />
|
||||
<div>
|
||||
<SettingSwitch v-for="setting in settings" :setting="setting" :key="setting.id" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import ContentHeader from './ContentHeader.vue';
|
||||
import SettingSwitch from './SettingSwitch.vue';
|
||||
|
||||
const settings = [
|
||||
{id: 0, title: "Public Servers", hint: "Display public servers button"},
|
||||
{id: 1, title: "Minimal Mode", hint: "Hide elements and reduce the size of elements"},
|
||||
{id: 2, title: "Voice Mode", hint: "Only show voice chat"},
|
||||
{id: 0, title: "Public Servers", hint: "Display public servers button"},
|
||||
{id: 1, title: "Minimal Mode", hint: "Hide elements and reduce the size of elements"},
|
||||
{id: 2, title: "Voice Mode", hint: "Only show voice chat"},
|
||||
{id: 0, title: "Public Servers", hint: "Display public servers button"},
|
||||
{id: 1, title: "Minimal Mode", hint: "Hide elements and reduce the size of elements"},
|
||||
{id: 2, title: "Voice Mode", hint: "Only show voice chat"},
|
||||
{id: 0, title: "Public Servers", hint: "Display public servers button"},
|
||||
{id: 1, title: "Minimal Mode", hint: "Hide elements and reduce the size of elements"},
|
||||
{id: 2, title: "Voice Mode", hint: "Only show voice chat"},
|
||||
{id: 0, title: "Public Servers", hint: "Display public servers button"},
|
||||
{id: 1, title: "Minimal Mode", hint: "Hide elements and reduce the size of elements"},
|
||||
{id: 2, title: "Voice Mode", hint: "Only show voice chat"},
|
||||
{id: 0, title: "Public Servers", hint: "Display public servers button"},
|
||||
{id: 1, title: "Minimal Mode", hint: "Hide elements and reduce the size of elements"},
|
||||
{id: 2, title: "Voice Mode", hint: "Only show voice chat"},
|
||||
{id: 0, title: "Public Servers", hint: "Display public servers button"},
|
||||
{id: 1, title: "Minimal Mode", hint: "Hide elements and reduce the size of elements"},
|
||||
{id: 2, title: "Voice Mode", hint: "Only show voice chat"},
|
||||
{id: 0, title: "Public Servers", hint: "Display public servers button"},
|
||||
{id: 1, title: "Minimal Mode", hint: "Hide elements and reduce the size of elements"},
|
||||
{id: 2, title: "Voice Mode", hint: "Only show voice chat"}
|
||||
];
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ContentHeader,
|
||||
SettingSwitch
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
settings
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,107 @@
|
|||
<template>
|
||||
<div class="bd-setting-switch">
|
||||
<div class="bd-title">
|
||||
<h3>{{setting.title}}</h3>
|
||||
<label class="bd-switch-wrapper">
|
||||
<input type="checkbox" class="bd-switch-checkbox" />
|
||||
<div class="bd-switch" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="bd-hint">{{setting.hint}}</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
setting: {}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.bd-setting-switch {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
-webkit-box-pack: start;
|
||||
justify-content: flex-start;
|
||||
-webkit-box-align: stretch;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.bd-setting-switch .bd-title {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
-webkit-box-align: stretch;
|
||||
align-items: stretch;
|
||||
-webkit-box-direction: normal;
|
||||
-webkit-box-orient: horizontal;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.bd-setting-switch .bd-switch-wrapper {
|
||||
flex: 0 0 auto;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
width: 44px;
|
||||
height: 24px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.bd-setting-switch .bd-switch-wrapper input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.bd-setting-switch .bd-switch-wrapper .bd-switch {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: #72767d;
|
||||
border-radius: 14px;
|
||||
transition: background .15s ease-in-out,box-shadow .15s ease-in-out,border .15s ease-in-out;
|
||||
}
|
||||
|
||||
.bd-setting-switch .bd-title h3 {
|
||||
font-weight: 500;
|
||||
color: #f6f6f7;
|
||||
flex: 1;
|
||||
line-height: 24px;
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.bd-setting-switch .bd-hint {
|
||||
flex: 1 1 auto;
|
||||
color: #72767d;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 15px;
|
||||
line-height: 30px;
|
||||
border-bottom: 0px solid hsla(218,5%,47%,.1);
|
||||
}
|
||||
|
||||
.bd-setting-switch .bd-switch-wrapper .bd-switch:before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
bottom: 3px;
|
||||
background: #f6f6f7;
|
||||
border-radius: 10px;
|
||||
transition: all .15s ease;
|
||||
box-shadow: 0 3px 1px 0 rgba(0,0,0,.05), 0 2px 2px 0 rgba(0,0,0,.1), 0 3px 3px 0 rgba(0,0,0,.05);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,46 @@
|
|||
<template>
|
||||
<div class="bd-settingsWrap">
|
||||
<div class="bd-scroller">
|
||||
<ContentHeader text="UI Settings" />
|
||||
<div>
|
||||
<SettingSwitch v-for="setting in settings" :setting="setting" :key="setting.id" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import ContentHeader from './ContentHeader.vue';
|
||||
import SettingSwitch from './SettingSwitch.vue';
|
||||
|
||||
const settings = [
|
||||
{id: 0, title: "Dummy Setting", hint: "Dummy Setting Hint"},
|
||||
{id: 0, title: "Dummy Setting", hint: "Dummy Setting Hint"},
|
||||
{id: 0, title: "Dummy Setting", hint: "Dummy Setting Hint"},
|
||||
{id: 0, title: "Dummy Setting", hint: "Dummy Setting Hint"},
|
||||
{id: 0, title: "Dummy Setting", hint: "Dummy Setting Hint"},
|
||||
{id: 0, title: "Dummy Setting", hint: "Dummy Setting Hint"},
|
||||
{id: 0, title: "Dummy Setting", hint: "Dummy Setting Hint"},
|
||||
{id: 0, title: "Dummy Setting", hint: "Dummy Setting Hint"},
|
||||
{id: 0, title: "Dummy Setting", hint: "Dummy Setting Hint"},
|
||||
{id: 0, title: "Dummy Setting", hint: "Dummy Setting Hint"},
|
||||
{id: 0, title: "Dummy Setting", hint: "Dummy Setting Hint"},
|
||||
{id: 0, title: "Dummy Setting", hint: "Dummy Setting Hint"},
|
||||
{id: 0, title: "Dummy Setting", hint: "Dummy Setting Hint"},
|
||||
{id: 0, title: "Dummy Setting", hint: "Dummy Setting Hint"}
|
||||
];
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ContentHeader,
|
||||
SettingSwitch
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
settings
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<div :class="['bd-settings-wrapper', 'platform-' + this.platform]">
|
||||
<div class="bd-settings-button" :class="{active: this.isActive}" @click="showSettings">
|
||||
<div class="bd-settings-button-btn"></div>
|
||||
</div>
|
||||
<BdSettings :isActive="this.isActive" :close="hideSettings" />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import BdSettings from './bdsettings.vue';
|
||||
export default {
|
||||
components: {
|
||||
BdSettings
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isActive: false,
|
||||
platform: global.process.platform
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleSettings() {
|
||||
this.isActive = !this.isActive;
|
||||
},
|
||||
hideSettings() {
|
||||
this.isActive = false;
|
||||
},
|
||||
showSettings() {
|
||||
this.isActive = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,103 @@
|
|||
<template>
|
||||
<div class="bd-settings" :class="{active: isActive}">
|
||||
<SidebarView :contentVisible="this.activeIndex >= 0" :animating="this.animating">
|
||||
<Sidebar slot="sidebar">
|
||||
<div class="bd-settings-x" @click="close">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 12 12"><g fill="none" fill-rule="evenodd"><path d="M0 0h12v12H0"></path><path class="fill" fill="#dcddde" d="M9.5 3.205L8.795 2.5 6 5.295 3.205 2.5l-.705.705L5.295 6 2.5 8.795l.705.705L6 6.705 8.795 9.5l.705-.705L6.705 6"></path></g></svg>
|
||||
</div>
|
||||
<SidebarItem v-for="item in sidebarItems" :item="item" :key="item.id" :onClick="itemOnClick" />
|
||||
</Sidebar>
|
||||
<SidebarViewContent slot="content">
|
||||
<div :class="{active: activeContent('core'), animating: animatingContent('core')}">
|
||||
<CoreSettings />
|
||||
</div>
|
||||
<div :class="{active: activeContent('ui'), animating: animatingContent('ui')}">
|
||||
<UISettings />
|
||||
</div>
|
||||
</SidebarViewContent>
|
||||
</SidebarView>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
const sidebarItems = [
|
||||
{ text: 'Internal', t: 'header' },
|
||||
{ id: 0, contentid: "core", text: 'Core', active: false, t: 'button' },
|
||||
{ id: 1, contentid: "ui", text: 'UI', active: false, t: 'button' },
|
||||
{ id: 2, contentid: "emotes", text: 'Emotes', active: false, t: 'button' },
|
||||
{ id: 3, contentid: "css", text: 'CSS Editor', active: false, t: 'button' },
|
||||
{ text: 'External', t: 'header' },
|
||||
{ id: 4, contentid: "plugins", text: 'Plugins', active: false, t: 'button' },
|
||||
{ id: 5, contentid: "themes", text: 'Themes', active: false, t: 'button' }
|
||||
];
|
||||
|
||||
function itemOnClick(id) {
|
||||
if (this.animating || id === this.activeIndex) return;
|
||||
if (this.activeIndex >= 0) this.sidebarItems.find(item => item.id === this.activeIndex).active = false;
|
||||
this.sidebarItems.find(item => item.id === id).active = true;
|
||||
this.animating = true;
|
||||
this.lastActiveIndex = this.activeIndex;
|
||||
this.activeIndex = id;
|
||||
|
||||
if (this.first) {
|
||||
this.first = false;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.animating = false;
|
||||
this.lastActiveIndex = -1;
|
||||
}, 400);
|
||||
}
|
||||
|
||||
function animatingContent(s) {
|
||||
const item = this.sidebarItems.find(item => item.contentid === s);
|
||||
if (!item) return false;
|
||||
return item.id === this.lastActiveIndex;
|
||||
}
|
||||
|
||||
function activeContent(s) {
|
||||
const item = this.sidebarItems.find(item => item.contentid === s);
|
||||
if (!item) return false;
|
||||
return item.id === this.activeIndex;
|
||||
}
|
||||
|
||||
|
||||
import { SidebarItem, SidebarView, Sidebar, SidebarViewContent } from './sidebar/index.js';
|
||||
import CoreSettings from './CoreSettings.vue';
|
||||
import UISettings from './UISettings.vue';
|
||||
export default {
|
||||
props: ['isActive', 'close'],
|
||||
components: {
|
||||
SidebarItem,
|
||||
SidebarView,
|
||||
Sidebar,
|
||||
SidebarViewContent,
|
||||
CoreSettings,
|
||||
UISettings
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
sidebarItems,
|
||||
activeIndex: -1,
|
||||
lastActiveIndex: -1,
|
||||
animating: false,
|
||||
first: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
itemOnClick,
|
||||
activeContent,
|
||||
animatingContent
|
||||
},
|
||||
updated: function () {
|
||||
if (!this.isActive) {
|
||||
this.activeIndex = this.lastActiveIndex = -1;
|
||||
this.sidebarItems.forEach(sidebarItem => { sidebarItem.active = false; });
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,8 @@
|
|||
<template>
|
||||
<div class="bd-item" :class="{active: item.active}" @click="onClick(item.id)">{{item.text}}</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['item', 'onClick']
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,48 @@
|
|||
<template>
|
||||
<div class='bd-content bd-content-column'>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.bd-content-column {
|
||||
//position: relative;
|
||||
height: 100%;
|
||||
-webkit-box-flex: 1;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.bd-content-column > div {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bd-content-column .bd-settingsWrap {
|
||||
flex-direction: column;
|
||||
}
|
||||
.bd-content-column .bd-settingsWrap .bd-scroller {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
contain: layout;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
z-index: 200;
|
||||
width: auto;
|
||||
margin-right: 15px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
::-webkit-scrollbar {
|
||||
-moz-appearance: scrollbartrack-vertical;
|
||||
background-color: rgba(0,0,0,0.1);
|
||||
width: 8px;
|
||||
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(0,0,0,0.6);
|
||||
border-radius: 16px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,8 @@
|
|||
<template>
|
||||
<div class='bd-header'>{{item.text}}</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['item']
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,17 @@
|
|||
<template>
|
||||
<SidebarButton v-if="item.t == 'button'" :item="item" :onClick="onClick" />
|
||||
<SidebarHeader v-else-if="item.t == 'header'" :item="item" />
|
||||
</template>
|
||||
<script>
|
||||
|
||||
import { SidebarHeader, SidebarButton } from './';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SidebarHeader,
|
||||
SidebarButton
|
||||
},
|
||||
props: ['item', 'onClick']
|
||||
}
|
||||
|
||||
</script>
|
|
@ -0,0 +1,11 @@
|
|||
<template>
|
||||
<div class='bd-sidebar'>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,27 @@
|
|||
<template>
|
||||
<div class='bd-sidebar-view' :class="{active: contentVisible, animating: animating}">
|
||||
<div class='bd-sidebar-region'>
|
||||
<div class='bd-settingsWrap'>
|
||||
<div class='bd-scroller'>
|
||||
<slot name="sidebar" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='bd-content-region'>
|
||||
<slot name="content" />
|
||||
<div class='bd-content-tools' />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
contentVisible: {
|
||||
default: false
|
||||
},
|
||||
animating: {
|
||||
default: false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,6 @@
|
|||
export { default as SidebarHeader } from './Header.vue';
|
||||
export { default as SidebarButton } from './Button.vue';
|
||||
export { default as SidebarItem } from './Item.vue';
|
||||
export { default as SidebarView } from './View.vue';
|
||||
export { default as Sidebar } from './Sidebar.vue';
|
||||
export { default as SidebarViewContent } from './Content.vue';
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* BetterDiscord Client Renderer
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const { WebpackModules } = require('../');
|
||||
|
||||
class Renderer {
|
||||
|
||||
static async render(component, root) {
|
||||
if (!this.React) this.React = await this.getReact();
|
||||
if (!this.reactDom) this.reactDom = await this.getReactDom();
|
||||
const React = this.React;
|
||||
window.React = React;
|
||||
this.reactDom.render(component, root);
|
||||
}
|
||||
|
||||
static async getReact() {
|
||||
const getReact = await WebpackModules.getModuleByProps(('createElement', 'cloneElement'));
|
||||
return getReact[0].exports;
|
||||
}
|
||||
|
||||
static async getReactDom() {
|
||||
const getReactDom = await WebpackModules.getModuleByProps(('render', 'findDOMNode'));
|
||||
return getReactDom[0].exports;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = { Renderer };
|
|
@ -0,0 +1,73 @@
|
|||
<template src="./templates/BdSettings.html"></template>
|
||||
|
||||
<script>
|
||||
/*Imports*/
|
||||
import { SidebarView, Sidebar, SidebarItem, ContentColumn } from './sidebar';
|
||||
import { CoreSettings, UISettings, EmoteSettings, PluginsView } from './bd';
|
||||
const components = { SidebarView, Sidebar, SidebarItem, ContentColumn, CoreSettings, UISettings, EmoteSettings, PluginsView };
|
||||
|
||||
/*Constants*/
|
||||
const sidebarItems = [
|
||||
{ text: 'Internal', _type: 'header' },
|
||||
{ id: 0, contentid: "core", text: 'Core', active: false, _type: 'button' },
|
||||
{ id: 1, contentid: "ui", text: 'UI', active: false, _type: 'button' },
|
||||
{ id: 2, contentid: "emotes", text: 'Emotes', active: false, _type: 'button' },
|
||||
{ id: 3, contentid: "css", text: 'CSS Editor', active: false, _type: 'button' },
|
||||
{ text: 'External', _type: 'header' },
|
||||
{ id: 4, contentid: "plugins", text: 'Plugins', active: false, _type: 'button' },
|
||||
{ id: 5, contentid: "themes", text: 'Themes', active: false, _type: 'button' }
|
||||
];
|
||||
|
||||
/*Methods*/
|
||||
function itemOnClick(id) {
|
||||
if (this.animating || id === this.activeIndex) return;
|
||||
if (this.activeIndex >= 0) this.sidebarItems.find(item => item.id === this.activeIndex).active = false;
|
||||
this.sidebarItems.find(item => item.id === id).active = true;
|
||||
this.animating = true;
|
||||
this.lastActiveIndex = this.activeIndex;
|
||||
this.activeIndex = id;
|
||||
|
||||
if (this.first) {
|
||||
this.first = false;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.animating = false;
|
||||
this.lastActiveIndex = -1;
|
||||
}, 400);
|
||||
}
|
||||
|
||||
function animatingContent(s) {
|
||||
const item = this.sidebarItems.find(item => item.contentid === s);
|
||||
if (!item) return false;
|
||||
return item.id === this.lastActiveIndex;
|
||||
}
|
||||
|
||||
function activeContent(s) {
|
||||
const item = this.sidebarItems.find(item => item.contentid === s);
|
||||
if (!item) return false;
|
||||
return item.id === this.activeIndex;
|
||||
}
|
||||
|
||||
const methods = { itemOnClick, animatingContent, activeContent };
|
||||
|
||||
export default {
|
||||
components,
|
||||
props: ['active', 'close'],
|
||||
methods,
|
||||
data() {
|
||||
return {
|
||||
sidebarItems,
|
||||
activeIndex: -1,
|
||||
lastActiveIndex: -1,
|
||||
animating: false,
|
||||
first: true
|
||||
}
|
||||
},
|
||||
updated: function () {
|
||||
if (this.active) return;
|
||||
this.activeIndex = this.lastActiveIndex = -1;
|
||||
this.sidebarItems.forEach(item => item.active = false);
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,41 @@
|
|||
<template src="./templates/BdSettingsWrapper.html"></template>
|
||||
<script>
|
||||
/*Imports*/
|
||||
import BdSettings from './BdSettings.vue';
|
||||
const components = { BdSettings };
|
||||
|
||||
/*Methods*/
|
||||
function showSettings() { this.active = true; }
|
||||
function hideSettings() { this.active = false; }
|
||||
|
||||
const methods = { showSettings, hideSettings };
|
||||
|
||||
let globalKeyListener;
|
||||
|
||||
export default {
|
||||
components,
|
||||
methods,
|
||||
data() {
|
||||
return {
|
||||
active: false,
|
||||
platform: global.process.platform
|
||||
}
|
||||
},
|
||||
created: function () {
|
||||
window.addEventListener('keyup', globalKeyListener = e => {
|
||||
if (this.active && e.which === 27) {
|
||||
this.hideSettings();
|
||||
return;
|
||||
}
|
||||
if (!e.metaKey && !e.ctrlKey || e.key !== 'b') return;
|
||||
|
||||
!this.active ? this.showSettings() : this.hideSettings();
|
||||
|
||||
e.stopImmediatePropagation();
|
||||
});
|
||||
},
|
||||
destroyed: function () {
|
||||
if (globalKeyListener) window.removeEventListener('keyup', globalKeyListener);
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,14 @@
|
|||
<template>
|
||||
<SettingsWrapper headertext="Core Settings">
|
||||
</SettingsWrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/*Imports*/
|
||||
import { SettingsWrapper } from './';
|
||||
const components = { SettingsWrapper };
|
||||
|
||||
export default {
|
||||
components
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,14 @@
|
|||
<template>
|
||||
<SettingsWrapper headertext="Emote Settings">
|
||||
</SettingsWrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/*Imports*/
|
||||
import { SettingsWrapper } from './';
|
||||
const components = { SettingsWrapper };
|
||||
|
||||
export default {
|
||||
components
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,9 @@
|
|||
<template src="./templates/PluginCard.html"></template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: ['plugin'],
|
||||
name: "PluginCard"
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,45 @@
|
|||
<template src="./templates/PluginsView.html"></template>
|
||||
<script>
|
||||
const { PluginManager } = require('../../../../'); //#1 require of 2018~ :3
|
||||
|
||||
/*Imports*/
|
||||
import { SettingsWrapper } from './';
|
||||
import PluginCard from './PluginCard.vue';
|
||||
const components = { SettingsWrapper, PluginCard };
|
||||
|
||||
/*Variables*/
|
||||
|
||||
/*Methods*/
|
||||
async function refreshLocalPlugins() {
|
||||
try {
|
||||
await PluginManager.refreshPlugins();
|
||||
} catch (err) {
|
||||
|
||||
}
|
||||
this.localPlugins = PluginManager.plugins;
|
||||
}
|
||||
|
||||
function showLocal() {
|
||||
this.local = true;
|
||||
}
|
||||
|
||||
function showOnline() {
|
||||
this.local = false;
|
||||
}
|
||||
|
||||
const methods = { showLocal, showOnline, refreshLocalPlugins };
|
||||
|
||||
export default {
|
||||
components,
|
||||
data() {
|
||||
return {
|
||||
localPlugins: [],
|
||||
local: true
|
||||
}
|
||||
},
|
||||
methods,
|
||||
created: function () {
|
||||
this.refreshLocalPlugins();
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,11 @@
|
|||
<template src="./templates/SettingsWrapper.html"></template>
|
||||
<script>
|
||||
/*Imports*/
|
||||
import { ScrollerWrap } from '../generic';
|
||||
const components = { ScrollerWrap };
|
||||
|
||||
export default {
|
||||
components,
|
||||
props: ['headertext']
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,14 @@
|
|||
<template>
|
||||
<SettingsWrapper headertext="UI Settings">
|
||||
</SettingsWrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/*Imports*/
|
||||
import { SettingsWrapper } from './';
|
||||
const components = { SettingsWrapper };
|
||||
|
||||
export default {
|
||||
components
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,6 @@
|
|||
export { default as SettingsWrapper } from './SettingsWrapper.vue';
|
||||
export { default as CoreSettings } from './CoreSettings.vue';
|
||||
export { default as UISettings } from './UISettings.vue';
|
||||
export { default as EmoteSettings } from './EmoteSettings.vue';
|
||||
export { default as PluginsView } from './PluginsView.vue';
|
||||
export { default as PluginCard } from './PluginCard.vue';
|
|
@ -0,0 +1,17 @@
|
|||
<div class="bd-plugin-card">
|
||||
<div class="bd-plugin-header">
|
||||
<span>{{plugin.name}}</span>
|
||||
<div class="bd-flex-spacer"/>
|
||||
<label class="bd-switch-wrapper">
|
||||
<input type="checkbox" class="bd-switch-checkbox" />
|
||||
<div class="bd-switch"/>
|
||||
</label>
|
||||
</div>
|
||||
<div class="bd-plugin-body">
|
||||
<div class="bd-plugin-description">{{plugin.description}}</div>
|
||||
<div class="bd-plugin-footer">
|
||||
<div class="bd-plugin-extra">v{{plugin.version}} by {{plugin.authors.join(', ').replace(/,(?!.*,)/gmi, ' and')}}</div>
|
||||
<div class="bd-controls"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,16 @@
|
|||
<SettingsWrapper headertext="Plugins">
|
||||
<div class="bd-flex bd-flex-col bd-pluginsView">
|
||||
<div class="bd-flex">
|
||||
<div class="bd-flex-grow bd-button" :class="{'bd-active': local}" @click="showLocal">
|
||||
<h3>Local</h3>
|
||||
<div @click="refreshLocalPlugins()">Refresh</div>
|
||||
</div>
|
||||
<div class="bd-flex-grow bd-button" :class="{'bd-active': !local}" @click="showOnline">
|
||||
<h3>Online</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="local" class="bd-flex bd-flex-grow bd-flex-col bd-plugins-container bd-local-plugins">
|
||||
<PluginCard v-for="plugin in localPlugins" :plugin="plugin" :key="plugin.id"/>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsWrapper>
|
|
@ -0,0 +1,8 @@
|
|||
<div class="bd-settingsWrap">
|
||||
<div class="bd-settingsWrap-header">{{headertext}}</div>
|
||||
<ScrollerWrap>
|
||||
<div class="bd-scroller">
|
||||
<slot/>
|
||||
</div>
|
||||
</ScrollerWrap>
|
||||
</div>
|
|
@ -0,0 +1,2 @@
|
|||
<template src="./templates/ScrollerWrap.html"></template>
|
||||
<script>export default { props: ['dark'] }</script>
|
|
@ -0,0 +1 @@
|
|||
export { default as ScrollerWrap } from './ScrollerWrap.vue';
|
|
@ -0,0 +1,3 @@
|
|||
<div class="bd-scroller-wrap" :class="{'bd-dark': dark}">
|
||||
<slot/>
|
||||
</div>
|
|
@ -0,0 +1,6 @@
|
|||
<template src="./templates/Button.html"></template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['item', 'onClick']
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,2 @@
|
|||
<template src="./templates/ContentColumn.html"></template>
|
||||
<script> export default { }</script>
|
|
@ -0,0 +1,6 @@
|
|||
<template src="./templates/Header.html"></template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['item']
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,11 @@
|
|||
<template src="./templates/Item.html"></template>
|
||||
<script>
|
||||
/*Imports*/
|
||||
import { SidebarHeader, SidebarButton } from './';
|
||||
const components = { SidebarHeader, SidebarButton };
|
||||
|
||||
export default {
|
||||
components,
|
||||
props: ['item', 'onClick']
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,2 @@
|
|||
<template src="./templates/Sidebar.html"></template>
|
||||
<script> export default { }</script>
|
|
@ -0,0 +1,11 @@
|
|||
<template src="./templates/View.html"></template>
|
||||
<script>
|
||||
/*Imports*/
|
||||
import { ScrollerWrap } from '../generic';
|
||||
const components = { ScrollerWrap };
|
||||
|
||||
export default {
|
||||
components,
|
||||
props: ['contentVisible', 'animating']
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,6 @@
|
|||
export { default as SidebarView } from './View.vue';
|
||||
export { default as Sidebar } from './Sidebar.vue';
|
||||
export { default as SidebarHeader } from './Header.vue';
|
||||
export { default as SidebarButton } from './Button.vue';
|
||||
export { default as SidebarItem } from './Item.vue';
|
||||
export { default as ContentColumn } from './ContentColumn.vue';
|
|
@ -0,0 +1 @@
|
|||
<div class="bd-item" :class="{active: item.active}" @click="onClick(item.id)">{{item.text}}</div>
|
|
@ -0,0 +1,3 @@
|
|||
<div class="bd-content bd-content-column">
|
||||
<slot />
|
||||
</div>
|
|
@ -0,0 +1 @@
|
|||
<div class='bd-header'>{{item.text}}</div>
|
|
@ -0,0 +1,2 @@
|
|||
<SidebarButton v-if="item._type == 'button'" :item="item" :onClick="onClick" />
|
||||
<SidebarHeader v-else-if="item._type == 'header'" :item="item" />
|
|
@ -0,0 +1,3 @@
|
|||
<div class="bd-sidebar bd-scroller">
|
||||
<slot/>
|
||||
</div>
|
|
@ -0,0 +1,12 @@
|
|||
<div class="bd-sidebar-view" :class="{active: contentVisible, animating: animating}">
|
||||
<div class="bd-sidebar-region">
|
||||
<div class="bd-settingsWrap">
|
||||
<ScrollerWrap dark="true">
|
||||
<slot name="sidebar"/>
|
||||
</ScrollerWrap>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bd-content-region">
|
||||
<slot name="content"/>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,26 @@
|
|||
<div class="bd-settings" :class="{active: active}" @keyup="close">
|
||||
<SidebarView :contentVisible="this.activeIndex >= 0" :animating="this.animating">
|
||||
<Sidebar slot="sidebar">
|
||||
<div class="bd-settings-x" @click="close">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 12 12"><g fill="none" fill-rule="evenodd"><path d="M0 0h12v12H0"></path><path class="fill" fill="#dcddde" d="M9.5 3.205L8.795 2.5 6 5.295 3.205 2.5l-.705.705L5.295 6 2.5 8.795l.705.705L6 6.705 8.795 9.5l.705-.705L6.705 6"></path></g></svg>
|
||||
<span>ESC</span>
|
||||
</div>
|
||||
<SidebarItem v-for="item in sidebarItems" :item="item" :key="item.id" :onClick="itemOnClick" />
|
||||
</Sidebar>
|
||||
<ContentColumn slot="content">
|
||||
<div :class="{active: activeContent('core'), animating: animatingContent('core')}">
|
||||
<CoreSettings />
|
||||
</div>
|
||||
<div :class="{active: activeContent('ui'), animating: animatingContent('ui')}">
|
||||
<UISettings />
|
||||
</div>
|
||||
<div :class="{active: activeContent('emotes'), animating: animatingContent('emotes')}">
|
||||
<EmoteSettings />
|
||||
</div>
|
||||
<div :class="{active: activeContent('plugins'), animating: animatingContent('plugins')}">
|
||||
<PluginsView />
|
||||
</div>
|
||||
</ContentColumn>
|
||||
|
||||
</SidebarView>
|
||||
</div>
|
|
@ -0,0 +1,6 @@
|
|||
<div class="bd-settings-wrapper" :class="[{active: active}, 'platform-' + this.platform]">
|
||||
<div class="bd-settings-button" :class="{active: active}" @click="showSettings">
|
||||
<div class="bd-settings-button-btn"></div>
|
||||
</div>
|
||||
<BdSettings :active="active" :close="hideSettings"/>
|
||||
</div>
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* BetterDiscord Client UI Module
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const $ = require('jquery');
|
||||
const Vue = require('vue');
|
||||
|
||||
const BdSettingsWrapper = (require('./vue/components/BdSettingsWrapper.vue')).default;
|
||||
class UI {
|
||||
|
||||
constructor() {
|
||||
$('body').append($('<bdbody/>').append($('<div/>', {
|
||||
id: 'bd-settings'
|
||||
})));
|
||||
|
||||
this.vueInstance = new Vue.default({
|
||||
el: '#bd-settings',
|
||||
template: '<BdSettingsWrapper/>',
|
||||
components: { BdSettingsWrapper }
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
module.exports = { UI }
|
|
@ -1,75 +0,0 @@
|
|||
/**
|
||||
* BetterDiscord Client WebpackModules Module
|
||||
* 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.
|
||||
*/
|
||||
|
||||
class WebpackModules {
|
||||
|
||||
static async getModuleByProps(props) {
|
||||
const modules = await this.getAllModules();
|
||||
return new Promise((resolve, reject) => {
|
||||
const rm = [];
|
||||
for (let index in modules) {
|
||||
if (!modules.hasOwnProperty(index)) continue;
|
||||
const module = modules[index];
|
||||
const { exports } = module;
|
||||
|
||||
if (!exports || typeof exports !== 'object') continue;
|
||||
if (!(props in exports)) continue;
|
||||
rm.push(module);
|
||||
// resolve(module);
|
||||
// break;
|
||||
}
|
||||
resolve(rm);
|
||||
reject(null);
|
||||
});
|
||||
}
|
||||
|
||||
/*This will most likely not work for most modules*/
|
||||
static async getModuleByName(name) {
|
||||
const modules = await this.getAllModules();
|
||||
return new Promise((resolve, reject) => {
|
||||
for (let index in modules) {
|
||||
if (!modules.hasOwnProperty(index)) continue;
|
||||
const module = modules[index];
|
||||
const { exports } = module;
|
||||
if (!exports) continue;
|
||||
|
||||
if (typeof exports === 'object' && (name in exports || exports.name === name)) {
|
||||
resolve(module.exports);
|
||||
break;
|
||||
} else if (typeof exports === 'function' && exports.name === name) {
|
||||
resolve(module.exports);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
reject(null);
|
||||
});
|
||||
}
|
||||
|
||||
static async getAllModules() {
|
||||
return new Promise(resolve => {
|
||||
const id = 'bd-webpackmodules';
|
||||
window['webpackJsonp'](
|
||||
[],
|
||||
{
|
||||
[id]: (module, exports, __webpack_require__) => {
|
||||
delete __webpack_require__.c[id];
|
||||
delete __webpack_require__.m[id];
|
||||
resolve(__webpack_require__.c);
|
||||
}
|
||||
},
|
||||
[id]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = { WebpackModules };
|
|
@ -0,0 +1 @@
|
|||
@import './partials/index.scss';
|
|
@ -0,0 +1,70 @@
|
|||
@keyframes bd-slidein {
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
opacity: .2;
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: translateX(0%);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateX(0%);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bd-slideout {
|
||||
0% {
|
||||
transform: translateX(0%);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateX(-100%);
|
||||
opacity: .2;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bd-slideoutin {
|
||||
0% {
|
||||
transform: translateX(-10%);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
20% {
|
||||
transform: translateX(-100%);
|
||||
opacity: .2;
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: translateX(0%);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateX(-10%);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bd-fade-out {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bd-fade-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
@import './plugincard.scss';
|
||||
|
||||
.bd-pluginsView {
|
||||
.bd-button {
|
||||
text-align: center;
|
||||
|
||||
|
||||
h3 {
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
display: block;
|
||||
font-size: 1.17em;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.bd-active {
|
||||
color: #fff;
|
||||
background: rgb(62, 130, 229);
|
||||
}
|
||||
|
||||
&:first-of-type {
|
||||
border-radius: 8px 0 0 8px;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bd-settings-button {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 22px;
|
||||
width: 70px;
|
||||
height: 48px;
|
||||
left: 0;
|
||||
background: #202225;
|
||||
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2), 0 2px 0 rgba(0, 0, 0, 0.06);
|
||||
opacity: 1;
|
||||
|
||||
.bd-settings-button-btn {
|
||||
background-image: $logoSmallBw;
|
||||
background-size: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 3000;
|
||||
cursor: pointer;
|
||||
filter: grayscale(100%);
|
||||
opacity: 0.5;
|
||||
transition: all 0.4s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
filter: none;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: transparent;
|
||||
opacity: 1;
|
||||
box-shadow: none;
|
||||
z-index: 90000;
|
||||
|
||||
.bd-settings-button-btn {
|
||||
background-image: $logoBigBw;
|
||||
filter: none;
|
||||
opacity: 1;
|
||||
width: 130px;
|
||||
height: 80px;
|
||||
background-size: 100% 100%;
|
||||
margin-left: 20px;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bd-settings {
|
||||
position: absolute;
|
||||
top: 22px;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 3000;
|
||||
width: 310px;
|
||||
transform: translateX(-100%) translateY(-100%);
|
||||
opacity: 0;
|
||||
transition: all .4s ease-in-out;
|
||||
|
||||
&.active {
|
||||
width: 900px;
|
||||
transform: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.bd-settings-x {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
left: 250px;
|
||||
border: 2px solid #6e6e6e;
|
||||
border-radius: 50%;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
span {
|
||||
color: #72767d;
|
||||
position: absolute;
|
||||
top: 32px;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: hsla(218,5%,47%,.3);
|
||||
}
|
||||
}
|
||||
|
||||
.platform-darwin & {
|
||||
top: 0px;
|
||||
|
||||
.bd-sidebar-view .bd-sidebar-region,
|
||||
.bd-sidebar-view .bd-content-region {
|
||||
padding-top: 22px;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
$colbdblue: #3e82e5;
|
||||
$colerr: #d84040;
|
||||
$colwarn: #faa61a;
|
||||
$colok: #43b581;
|
||||
$coldimwhite: #b9bbbe;
|
|
@ -0,0 +1,11 @@
|
|||
.guilds-wrapper {
|
||||
padding-top: 50px !important;
|
||||
}
|
||||
|
||||
[class*="guilds-wrapper"] + [class*="flex"] {
|
||||
border-radius: 0 0 0 5px;
|
||||
}
|
||||
|
||||
.unread-mentions-indicator-top {
|
||||
top: 45px;
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
.bd-scroller-wrap {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
.bd-scroller {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: #1e2124;
|
||||
border-color: #36393e;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb,
|
||||
&::-webkit-scrollbar-track-piece {
|
||||
background-clip: padding-box;
|
||||
border-width: 3px;
|
||||
border-style: solid;
|
||||
border-radius: 7px;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track-piece {
|
||||
background-color: #2f3136;
|
||||
border-color: #36393e;
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&.bd-dark {
|
||||
.bd-scroller {
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: #36393e;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track-piece {
|
||||
background-color: #2b2e31;
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bd-button {
|
||||
cursor: pointer;
|
||||
color: #b9bbbe;
|
||||
background: #202225;
|
||||
text-align: center;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
$logoSmallBw: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iQ2FscXVlXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMjAwMCAyMDAwIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCAyMDAwIDIwMDAiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxnPjxwYXRoIGZpbGw9IiMzRTgyRTUiIGQ9Ik0xNDAyLjIsNjMxLjdjLTkuNy0zNTMuNC0yODYuMi00OTYtNjQyLjYtNDk2SDY4LjR2NzE0LjFsNDQyLDM5OFY0OTAuN2gyNTdjMjc0LjUsMCwyNzQuNSwzNDQuOSwwLDM0NC45SDU5Ny42djMyOS41aDE2OS44YzI3NC41LDAsMjc0LjUsMzQ0LjgsMCwzNDQuOGgtNjk5djM1NC45aDY5MS4yYzM1Ni4zLDAsNjMyLjgtMTQyLjYsNjQyLjYtNDk2YzAtMTYyLjYtNDQuNS0yODQuMS0xMjIuOS0zNjguNkMxMzU3LjcsOTE1LjgsMTQwMi4yLDc5NC4zLDE0MDIuMiw2MzEuN3oiLz48cGF0aCBmaWxsPSIjRkZGRkZGIiBkPSJNMTI2Mi41LDEzNS4yTDEyNjIuNSwxMzUuMmwtNzYuOCwwYzI2LjYsMTMuMyw1MS43LDI4LjEsNzUsNDQuM2M3MC43LDQ5LjEsMTI2LjEsMTExLjUsMTY0LjYsMTg1LjNjMzkuOSw3Ni42LDYxLjUsMTY1LjYsNjQuMywyNjQuNmwwLDEuMnYxLjJjMCwxNDEuMSwwLDU5Ni4xLDAsNzM3LjF2MS4ybDAsMS4yYy0yLjcsOTktMjQuMywxODgtNjQuMywyNjQuNmMtMzguNSw3My44LTkzLjgsMTM2LjItMTY0LjYsMTg1LjNjLTIyLjYsMTUuNy00Ni45LDMwLjEtNzIuNiw0My4xaDcyLjVjMzQ2LjIsMS45LDY3MS0xNzEuMiw2NzEtNTY3LjlWNzE2LjdDMTkzMy41LDMxMi4yLDE2MDguNywxMzUuMiwxMjYyLjUsMTM1LjJ6Ii8+PC9nPjwvc3ZnPg==);
|
||||
$logoBigBw: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iQ2FscXVlXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMTk0MCA2NDAiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDE5NDAgNjQwIiB4bWw6c3BhY2U9InByZXNlcnZlIj48Zz48cGF0aCBmaWxsPSIjRkZGRkZGIiBkPSJNMTU2LjEsMjczLjlIMjl2MTQyLjdsODQuNiw3Ni4yVjM1NC4yaDQ1LjJjMjguNywwLDQyLjksMTMuOCw0Mi45LDM2djEwNmMwLDIyLjItMTMuNCwzNy4xLTQyLjksMzcuMUgyOC42djgwLjdoMTI3YzY4LjEsMC40LDEzMi0zMy43LDEzMi0xMTEuN3YtMTE0QzI4OC4xLDMwOC43LDIyNC4yLDI3My45LDE1Ni4xLDI3My45TDE1Ni4xLDI3My45eiIvPjxwYXRoIGZpbGw9IiNGRkZGRkYiIGQ9Ik04MjEuOSw1MDIuM1YzODUuMmMwLTQyLjEsNzUuOC01MS43LDk4LjctOS42bDcwLTI4LjNjLTI3LjYtNjAuNS03Ny43LTc4LjEtMTE5LjQtNzguMWMtNjguMSwwLTEzNS41LDM5LjQtMTM1LjUsMTE1Ljl2MTE3LjFjMCw3Ny4zLDY3LjMsMTE1LjksMTMzLjksMTE1LjljNDIuOSwwLDk0LjEtMjEsMTIyLjUtNzYuMmwtNzUtMzQuNEM4OTguOCw1NTQuOCw4MjEuOSw1NDMuMyw4MjEuOSw1MDIuM0w4MjEuOSw1MDIuM3oiLz48cGF0aCBmaWxsPSIjRkZGRkZGIiBkPSJNNTkwLjQsNDAxLjNjLTI2LjQtNS43LTQ0LTE1LjMtNDUuMi0zMS44YzEuNS0zOS40LDYyLjQtNDAuOSw5OC0zLjFsNTYuMy00My4yYy0zNS4yLTQyLjktNzUtNTQuMy0xMTUuOS01NC4zYy02Mi40LDAtMTIyLjgsMzUuMi0xMjIuOCwxMDEuOGMwLDY0LjcsNDkuNyw5OS41LDEwNC41LDEwNy45YzI3LjksMy44LDU4LjksMTQuOSw1OC4yLDM0LjFjLTIuMywzNi40LTc3LjMsMzQuNC0xMTEuNC02LjlsLTU0LjMsNTAuOWMzMS44LDQwLjksNzUsNjEuNiwxMTUuNiw2MS42YzYyLjQsMCwxMzEuNi0zNiwxMzQuMy0xMDEuOEM3MTEuMyw0MzMuNSw2NTAuOSw0MTIuNCw1OTAuNCw0MDEuM0w1OTAuNCw0MDEuM3oiLz48cmVjdCB4PSIzMzQiIHk9IjI3My45IiBmaWxsPSIjRkZGRkZGIiB3aWR0aD0iODUuNyIgaGVpZ2h0PSIzMzkuOCIvPjxwYXRoIGZpbGw9IiNGRkZGRkYiIGQ9Ik0xNzc5LjMsMjczLjloLTEyN3YxNDIuN2w4NC42LDc2LjJWMzU0LjJoNDUuMmMyOC43LDAsNDIuOSwxMy44LDQyLjksMzZ2MTA2YzAsMjIuMi0xMy40LDM3LjEtNDIuOSwzNy4xaC0xMzAuMXY4MC43aDEyNy40YzY4LjEsMC40LDEzMi0zMy43LDEzMi0xMTEuN3YtMTE0QzE5MTEuNCwzMDguNywxODQ3LjUsMjczLjksMTc3OS4zLDI3My45TDE3NzkuMywyNzMuOXoiLz48cGF0aCBmaWxsPSIjRkZGRkZGIiBkPSJNMTE1NiwyNjkuM2MtNzAuNCwwLTE0MC40LDM4LjMtMTQwLjQsMTE2LjdWNTAyYzAsNzcuNyw3MC40LDExNi43LDE0MS4yLDExNi43YzcwLjQsMCwxNDAuNC0zOSwxNDAuNC0xMTYuN1YzODZDMTI5Ny4yLDMwNy45LDEyMjYuNCwyNjkuMywxMTU2LDI2OS4zeiBNMTIxMS4xLDUwMmMwLDI0LjUtMjcuNiwzNy4xLTU0LjcsMzcuMWMtMjcuNiwwLTU1LjEtMTEuOS01NS4xLTM3LjFWMzg2YzAtMjQuOSwyNi44LTM4LjMsNTMuNi0zOC4zYzI3LjksMCw1Ni4zLDExLjksNTYuMywzOC4zVjUwMnoiLz48cGF0aCBmaWxsPSIjRkZGRkZGIiBkPSJNMTUzOC4zLDQ5Ny40YzQwLjktMTMsNjYuNi00OC42LDY2LjYtMTExLjRjLTEuOS03OS42LTU2LjMtMTExLjctMTI2LjMtMTExLjdoLTEzNS44djMzOS44aDg2LjlWNTA2LjJoMTUuM2w3OC44LDEwNy45aDEwNy4xTDE1MzguMyw0OTcuNHogTTE0ODAuMSw0MzEuOWgtNTAuNXYtNzcuN2g1MC41QzE1MzQuMSwzNTQuMiwxNTM0LjEsNDMxLjksMTQ4MC4xLDQzMS45eiIvPjwvZz48Zz48cGF0aCBmaWxsPSIjM0U4MkU1IiBkPSJNMTAzLjMsNDMuM0gyOC42djc3LjFsNDcuNyw0M1Y4MS43aDI3LjhjMjkuNiwwLDI5LjYsMzcuMiwwLDM3LjJIODUuOHYzNS42aDE4LjNjMjkuNiwwLDI5LjYsMzcuMiwwLDM3LjJIMjguNlYyMzBoNzQuNmMzOC41LDAsNjguMy0xNS40LDY5LjQtNTMuNmMwLTE3LjYtNC44LTMwLjctMTMuMy0zOS44YzguNS05LjEsMTMuMy0yMi4yLDEzLjMtMzkuOEMxNzEuNiw1OC43LDE0MS44LDQzLjMsMTAzLjMsNDMuM3oiLz48cG9seWdvbiBmaWxsPSIjM0U4MkU1IiBwb2ludHM9IjM1OC43LDQzLjMgMzU4LjcsODEuNyA0MDYuOCw4MS43IDQwNi44LDIzMCA0NTQuNSwyMzAgNDU0LjUsODEuNyA1MjkuOCw4MS43IDUyOS44LDIzMCA1NzcuNiwyMzAgNTc3LjYsODEuNyA2MjUuNyw4MS43IDYyNS43LDQzLjMgIi8+PHBvbHlnb24gZmlsbD0iIzNFODJFNSIgcG9pbnRzPSIxOTcuNiw0My4zIDE5Ny42LDIzMCAzNDEuNywyMzAgMzQxLjcsMTkxLjcgMjQ1LjQsMTkxLjcgMjQ1LjQsMTYwLjUgMjk2LjUsMTYwLjUgMjk2LjUsMTEyLjggMjQ1LjQsMTEyLjggMjQ1LjQsODEuNyAzNDEuNyw4MS43IDM0MS43LDQzLjMgIi8+PHBvbHlnb24gZmlsbD0iIzNFODJFNSIgcG9pbnRzPSI2NDIuNCw0My4zIDY0Mi40LDIzMCA3ODYuNCwyMzAgNzg2LjQsMTkxLjcgNjkwLjEsMTkxLjcgNjkwLjEsMTYwLjUgNzQxLjMsMTYwLjUgNzQxLjMsMTEyLjggNjkwLjEsMTEyLjggNjkwLjEsODEuNyA3ODYuNCw4MS43IDc4Ni40LDQzLjMgIi8+PHBhdGggZmlsbD0iIzNFODJFNSIgZD0iTTkxOC42LDE2NmMyMi41LTcuMSwzNi41LTI2LjYsMzYuNS02MS4xYy0xLTQzLjYtMzAuOC02MS43LTY5LjItNjEuN2gtNzQuNVYyMzBIODU5di01OS4yaDguNGw0My4yLDU5LjJoNTguN0w5MTguNiwxNjZ6IE04ODYuNywxMzAuMkg4NTlWODEuN2gyNy43QzkxNi4zLDgxLjcsOTE2LjMsMTMwLjIsODg2LjcsMTMwLjJ6Ii8+PC9nPjwvc3ZnPg==);
|
|
@ -0,0 +1,9 @@
|
|||
@import './images.scss';
|
||||
@import './colours.scss';
|
||||
@import './animations.scss';
|
||||
@import './layouts.scss';
|
||||
@import './bdsettings.scss';
|
||||
@import './sidebarview.scss';
|
||||
@import './generic.scss';
|
||||
|
||||
@import './discordoverrides.scss';
|
|
@ -0,0 +1,19 @@
|
|||
.bd-flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.bd-flex-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.bd-flex-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.bd-flex-grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.bd-flex-spacer {
|
||||
flex-grow: 1;
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
.bd-plugin-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
background: rgba(32, 34, 37, 0.6);
|
||||
border: 1px solid #202225;
|
||||
padding: 5px 10px;
|
||||
min-height: 150px;
|
||||
color: #b9bbbe;
|
||||
border-radius: 8px;
|
||||
margin-top: 10px;
|
||||
|
||||
.bd-plugin-header {
|
||||
padding-bottom: 5px;
|
||||
display: flex;
|
||||
flex-grow: 0;
|
||||
font-weight: 700;
|
||||
box-shadow: 0 1px 0px darken(#2f3136, 8%);
|
||||
}
|
||||
|
||||
.bd-plugin-body {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
|
||||
.bd-plugin-description {
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
max-height: 60px;
|
||||
min-height: 60px;
|
||||
color: #8a8c90;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
background: rgba(32, 34, 37, 0.6);
|
||||
padding: 5px;
|
||||
border-radius: 8px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.bd-plugin-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
flex-grow: 1;
|
||||
align-items: flex-end;
|
||||
|
||||
.bd-plugin-extra {
|
||||
color: rgba(255, 255, 255, 0.15);
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bd-switch-wrapper {
|
||||
flex: 0 0 auto;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
display: block;
|
||||
|
||||
input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.bd-switch {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: #72767d;
|
||||
border-radius: 14px;
|
||||
transition: background .15s ease-in-out,box-shadow .15s ease-in-out,border .15s ease-in-out;
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
bottom: 3px;
|
||||
background: #f6f6f7;
|
||||
border-radius: 10px;
|
||||
transition: all .15s ease;
|
||||
box-shadow: 0 3px 1px 0 rgba(0,0,0,.05), 0 2px 2px 0 rgba(0,0,0,.1), 0 3px 3px 0 rgba(0,0,0,.05);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
.bd-sidebar-view {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
|
||||
.bd-sidebar-region {
|
||||
background: #202225;
|
||||
-webkit-box-flex: 1;
|
||||
-webkit-box-pack: end;
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
flex: 1 0 30%;
|
||||
z-index: 5;
|
||||
max-width: 310px;
|
||||
min-width: 310px;
|
||||
|
||||
.bd-settingsWrap {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
-webkit-box-flex: 1;
|
||||
flex: 1;
|
||||
min-height: 1px;
|
||||
box-sizing: border-box;
|
||||
padding: 80px 15px 15px 15px;
|
||||
|
||||
.bd-scroller.bd-sidebar {
|
||||
width: 100%;
|
||||
padding-right: 20px;
|
||||
padding: 0;
|
||||
|
||||
.bd-header {
|
||||
padding: 6px 0;
|
||||
margin-left: 10px;
|
||||
margin-top: 15px;
|
||||
color: rgba(255, 255, 255, 0.15);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 16px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-family: Whitney, Helvetica Neue, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.bd-item {
|
||||
border-radius: 3px;
|
||||
margin-bottom: 2px;
|
||||
padding-bottom: 6px;
|
||||
padding-top: 6px;
|
||||
padding: 6px 10px;
|
||||
color: $coldimwhite;
|
||||
cursor: pointer;
|
||||
font-size: 17px;
|
||||
line-height: 20px;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-family: Whitney, Helvetica Neue, Helvetica, Arial, sans-serif;
|
||||
|
||||
&:hover,
|
||||
&.active {
|
||||
background: $colbdblue;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: #FFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bd-content-region {
|
||||
flex-grow: 1;
|
||||
transform: translateX(-100%);
|
||||
background: #36393e;
|
||||
box-shadow: 0 0 4px #202225;
|
||||
|
||||
.bd-content-column {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.bd-settingsWrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
|
||||
.bd-scroller-wrap {
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.bd-settingsWrap-header {
|
||||
color: $colbdblue;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 100%;
|
||||
outline: 0;
|
||||
padding: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bd-content-column > div:not(.active) {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.bd-content {
|
||||
animation: bd-fade-in .4s forwards;
|
||||
|
||||
.animating {
|
||||
animation: bd-fade-out .4s forwards;
|
||||
}
|
||||
|
||||
.bd-settingsWrap {
|
||||
padding: 20px 15px 15px 15px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
&.active {
|
||||
.bd-content-region {
|
||||
animation: bd-slidein .6s forwards;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,10 +7,22 @@ const jsLoader = {
|
|||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
query: {
|
||||
// presets: ['es2015', 'react']
|
||||
presets: ['react']
|
||||
}
|
||||
}
|
||||
|
||||
const vueLoader = {
|
||||
test: /\.(vue)$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'vue-loader'
|
||||
}
|
||||
|
||||
const scssLoader = {
|
||||
test: /\.scss$/,
|
||||
exclude: /node_modules/,
|
||||
loader: ['css-loader', 'sass-loader']
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
entry: './src/index.js',
|
||||
output: {
|
||||
|
@ -18,10 +30,15 @@ module.exports = {
|
|||
filename: 'betterdiscord.client.js'
|
||||
},
|
||||
module: {
|
||||
loaders: [jsLoader]
|
||||
loaders: [jsLoader, vueLoader, scssLoader]
|
||||
},
|
||||
externals: {
|
||||
'electron': 'window.require("electron")'
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
vue$: path.resolve('node_modules', 'vue', 'dist', 'vue.esm.js')
|
||||
}
|
||||
}
|
||||
/* resolve: {
|
||||
alias: {
|
||||
|
|
|
@ -1,14 +1,27 @@
|
|||
const
|
||||
const
|
||||
gulp = require('gulp'),
|
||||
pump = require('pump'),
|
||||
babel = require('gulp-babel');
|
||||
babel = require('gulp-babel'),
|
||||
plumber = require('gulp-plumber'),
|
||||
watch = require('gulp-watch');
|
||||
|
||||
const task_babel = function () {
|
||||
return pump([
|
||||
gulp.src('src/**/*js'),
|
||||
babel(),
|
||||
gulp.dest('dist')
|
||||
]);
|
||||
gulp.src('src/**/*js'),
|
||||
plumber(),
|
||||
babel(),
|
||||
gulp.dest('dist')
|
||||
]);
|
||||
}
|
||||
|
||||
gulp.task('babel', task_babel);
|
||||
const watch_babel = function () {
|
||||
return pump([
|
||||
watch('src/**/*js'),
|
||||
plumber(),
|
||||
babel(),
|
||||
gulp.dest('dist')
|
||||
]);
|
||||
}
|
||||
|
||||
gulp.task('build', task_babel);
|
||||
gulp.task('watch', watch_babel);
|
|
@ -21,6 +21,12 @@
|
|||
"electron": "^1.7.10",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-babel": "^7.0.0",
|
||||
"pump": "^2.0.0"
|
||||
"pump": "^2.0.0",
|
||||
"gulp-plumber": "^1.2.0",
|
||||
"gulp-watch": "^5.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp build",
|
||||
"watch": "gulp watch"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"name": "betterdiscord",
|
||||
"description": "BetterDiscord",
|
||||
"author": "Jiiks",
|
||||
"version": "0.4.0",
|
||||
"homepage": "https://betterdiscord.net",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"contributors": [
|
||||
"Jiiks",
|
||||
"Pohky"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Jiiks/BetterDiscordApp.git"
|
||||
},
|
||||
"private": false,
|
||||
"devDependencies": {},
|
||||
"scripts": {
|
||||
"build": "cd ./client && npm run build && cd ../core && npm run build",
|
||||
"watch_client": "cd ./client && npm run watch",
|
||||
"watch_core": "cd ./core && npm run watch"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"info": {
|
||||
"name": "Example Plugin 2",
|
||||
"authors": ["Jiiks"],
|
||||
"version": 1.0,
|
||||
"description": "Example Plugin 2 Description"
|
||||
},
|
||||
"main": "index.js",
|
||||
"defaultConfig": {
|
||||
"foo": "bar"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
module.exports = (Plugin, Api, Vendor) => {
|
||||
|
||||
const { $, moment } = Vendor;
|
||||
const { Events } = Api;
|
||||
|
||||
const test = 'Testing';
|
||||
|
||||
return class extends Plugin {
|
||||
test() {
|
||||
return test;
|
||||
}
|
||||
|
||||
onStart() {
|
||||
console.log('On Start!');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -2,7 +2,8 @@
|
|||
"info": {
|
||||
"name": "Example Plugin",
|
||||
"authors": ["Jiiks"],
|
||||
"version": 1.0
|
||||
"version": 1.0,
|
||||
"description": "Example Plugin Description"
|
||||
},
|
||||
"main": "index.js",
|
||||
"defaultConfig": {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
npm run watch_client
|
|
@ -0,0 +1 @@
|
|||
npm run watch_core
|
Loading…
Reference in New Issue