Allow plugins to use the require function to access the plugin API instead of wrapping every module in a function
This commit is contained in:
parent
835cc3134c
commit
c05179da4b
|
@ -148,7 +148,7 @@ export default class {
|
|||
* @param {bool} suppressErrors Suppress any errors that occur during loading of content
|
||||
*/
|
||||
static async refreshContent(suppressErrors = false) {
|
||||
if (!this.localContent.length) return this.loadAllContent();
|
||||
if (!this.localContent.length) return this.loadAllContent(suppressErrors);
|
||||
|
||||
try {
|
||||
await FileUtils.ensureDirectory(this.contentPath);
|
||||
|
@ -289,16 +289,6 @@ export default class {
|
|||
userConfig.config = defaultConfig.clone({ settings: userConfig.config });
|
||||
userConfig.config.setSaved();
|
||||
|
||||
for (const setting of userConfig.config.findSettings(() => true)) {
|
||||
// This will load custom settings
|
||||
// Setting the content's path on only the live config (and not the default config) ensures that custom settings will not be loaded on the default settings
|
||||
setting.setContentPath(contentPath);
|
||||
}
|
||||
|
||||
for (const scheme of userConfig.config.schemes) {
|
||||
scheme.setContentPath(contentPath);
|
||||
}
|
||||
|
||||
Utils.deepfreeze(defaultConfig, object => object instanceof Combokeys);
|
||||
|
||||
const configs = {
|
||||
|
@ -318,6 +308,16 @@ export default class {
|
|||
if (!reload && this.getContentById(content.id))
|
||||
throw { message: `A ${this.contentType} with the ID ${content.id} already exists.` };
|
||||
|
||||
for (const setting of userConfig.config.findSettings(() => true)) {
|
||||
// This will load custom settings
|
||||
// Setting the content's path on only the live config (and not the default config) ensures that custom settings will not be loaded on the default settings
|
||||
setting.setContentPath(contentPath);
|
||||
}
|
||||
|
||||
for (const scheme of userConfig.config.schemes) {
|
||||
scheme.setContentPath(contentPath);
|
||||
}
|
||||
|
||||
if (reload) this.localContent.splice(index, 1, content);
|
||||
else this.localContent.push(content);
|
||||
return content;
|
||||
|
|
|
@ -84,7 +84,7 @@ export default class PluginApi {
|
|||
*/
|
||||
|
||||
get Logger() {
|
||||
return {
|
||||
return Object.defineProperty(this, 'Logger', {value: {
|
||||
log: (...message) => Logger.log(this.plugin.name, message),
|
||||
error: (...message) => Logger.err(this.plugin.name, message),
|
||||
err: (...message) => Logger.err(this.plugin.name, message),
|
||||
|
@ -92,7 +92,7 @@ export default class PluginApi {
|
|||
info: (...message) => Logger.info(this.plugin.name, message),
|
||||
debug: (...message) => Logger.dbg(this.plugin.name, message),
|
||||
dbg: (...message) => Logger.dbg(this.plugin.name, message)
|
||||
};
|
||||
}}).Logger;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -100,7 +100,7 @@ export default class PluginApi {
|
|||
*/
|
||||
|
||||
get Utils() {
|
||||
return {
|
||||
return Object.defineProperty(this, 'Utils', {value: {
|
||||
overload: (...args) => Utils.overload.apply(Utils, args),
|
||||
tryParseJson: (...args) => Utils.tryParseJson.apply(Utils, args),
|
||||
toCamelCase: (...args) => Utils.toCamelCase.apply(Utils, args),
|
||||
|
@ -113,7 +113,7 @@ export default class PluginApi {
|
|||
until: (...args) => Utils.until.apply(Utils, args),
|
||||
findInTree: (...args) => Utils.findInTree.apply(Utils, args),
|
||||
findInReactTree: (...args) => Utils.findInReactTree.apply(Utils, args)
|
||||
};
|
||||
}}).Utils;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -133,12 +133,12 @@ export default class PluginApi {
|
|||
return new SettingsScheme(args);
|
||||
}
|
||||
get Settings() {
|
||||
return {
|
||||
return Object.defineProperty(this, 'Settings', {value: {
|
||||
createSet: this.createSettingsSet.bind(this),
|
||||
createCategory: this.createSettingsCategory.bind(this),
|
||||
createSetting: this.createSetting.bind(this),
|
||||
createScheme: this.createSettingsScheme.bind(this)
|
||||
};
|
||||
}}).Settings;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -149,9 +149,9 @@ export default class PluginApi {
|
|||
return Settings.get(set, category, setting);
|
||||
}
|
||||
get InternalSettings() {
|
||||
return {
|
||||
return Object.defineProperty(this, 'InternalSettings', {value: {
|
||||
get: this.getInternalSetting.bind(this)
|
||||
};
|
||||
}}).InternalSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -159,12 +159,12 @@ export default class PluginApi {
|
|||
*/
|
||||
|
||||
get BdMenu() {
|
||||
return {
|
||||
return Object.defineProperty(this, 'BdMenu', {value: {
|
||||
open: BdMenu.open.bind(BdMenu),
|
||||
close: BdMenu.close.bind(BdMenu),
|
||||
items: this.BdMenuItems,
|
||||
BdMenuItems: this.BdMenuItems
|
||||
};
|
||||
}}).BdMenu;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -194,7 +194,7 @@ export default class PluginApi {
|
|||
BdMenu.items.remove(item);
|
||||
}
|
||||
get BdMenuItems() {
|
||||
return Object.defineProperty({
|
||||
return Object.defineProperty(this, 'BdMenuItems', {value: Object.defineProperty({
|
||||
add: this.addMenuItem.bind(this),
|
||||
addSettingsSet: this.addMenuSettingsSet.bind(this),
|
||||
addVueComponent: this.addMenuVueComponent.bind(this),
|
||||
|
@ -202,7 +202,7 @@ export default class PluginApi {
|
|||
removeAll: this.removeAllMenuItems.bind(this)
|
||||
}, 'items', {
|
||||
get: () => this.menuItems
|
||||
});
|
||||
})}).BdMenuItems;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -217,11 +217,11 @@ export default class PluginApi {
|
|||
return this._activeMenu || (this._activeMenu = { menu: null });
|
||||
}
|
||||
get BdContextMenu() {
|
||||
return Object.defineProperty({
|
||||
return Object.defineProperty(this, 'BdContextMenu', {value: Object.defineProperty({
|
||||
show: this.showContextMenu.bind(this)
|
||||
}, 'activeMenu', {
|
||||
get: () => this.activeMenu
|
||||
});
|
||||
})}).BdContextMenu;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -264,7 +264,7 @@ export default class PluginApi {
|
|||
}
|
||||
}
|
||||
get CssUtils() {
|
||||
return {
|
||||
return Object.defineProperty(this, 'CssUtils', {value: {
|
||||
compileSass: this.compileSass.bind(this),
|
||||
getConfigAsSCSS: this.getConfigAsSCSS.bind(this),
|
||||
getConfigAsSCSSMap: this.getConfigAsSCSSMap.bind(this),
|
||||
|
@ -272,7 +272,7 @@ export default class PluginApi {
|
|||
injectSass: this.injectSass.bind(this),
|
||||
deleteStyle: this.deleteStyle.bind(this),
|
||||
deleteAllStyles: this.deleteAllStyles.bind(this)
|
||||
};
|
||||
}}).CssUtils;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -308,7 +308,7 @@ export default class PluginApi {
|
|||
return this.addModal(Modals.createSettingsModal(settingsset, headertext, options));
|
||||
}
|
||||
get Modals() {
|
||||
return Object.defineProperties({
|
||||
return Object.defineProperty(this, 'Modals', {value: Object.defineProperties({
|
||||
add: this.addModal.bind(this),
|
||||
close: this.closeModal.bind(this),
|
||||
closeAll: this.closeAllModals.bind(this),
|
||||
|
@ -322,7 +322,7 @@ export default class PluginApi {
|
|||
baseComponent: {
|
||||
get: () => Modals.baseComponent
|
||||
}
|
||||
});
|
||||
})}).Modals;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -345,14 +345,14 @@ export default class PluginApi {
|
|||
return Toasts.warning(message, options);
|
||||
}
|
||||
get Toasts() {
|
||||
return {
|
||||
return Object.defineProperty(this, 'Toasts', {value: {
|
||||
push: this.showToast.bind(this),
|
||||
success: this.showSuccessToast.bind(this),
|
||||
error: this.showErrorToast.bind(this),
|
||||
info: this.showInfoToast.bind(this),
|
||||
warning: this.showWarningToast.bind(this),
|
||||
get enabled() { return Toasts.enabled }
|
||||
};
|
||||
}}).Toasts;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -380,13 +380,13 @@ export default class PluginApi {
|
|||
}
|
||||
}
|
||||
get Notifications() {
|
||||
return Object.defineProperty({
|
||||
return Object.defineProperty(this, 'Notifications', {value: Object.defineProperty({
|
||||
add: this.addNotification.bind(this),
|
||||
dismiss: this.dismissNotification.bind(this),
|
||||
dismissAll: this.dismissAllNotifications.bind(this)
|
||||
}, 'stack', {
|
||||
get: () => this.notificationStack
|
||||
});
|
||||
})}).Notifications;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -422,7 +422,7 @@ export default class PluginApi {
|
|||
return GlobalAc.items(prefix, sterm);
|
||||
}
|
||||
get Autocomplete() {
|
||||
return Object.defineProperty({
|
||||
return Object.defineProperty(this, 'Autocomplete', {value: Object.defineProperty({
|
||||
add: this.addAutocompleteController.bind(this),
|
||||
remove: this.removeAutocompleteController.bind(this),
|
||||
removeAll: this.removeAllAutocompleteControllers.bind(this),
|
||||
|
@ -431,7 +431,7 @@ export default class PluginApi {
|
|||
search: this.searchAutocomplete.bind(this)
|
||||
}, 'sets', {
|
||||
get: () => this.autocompleteSets
|
||||
});
|
||||
})}).Autocomplete;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -473,7 +473,7 @@ export default class PluginApi {
|
|||
return EmoteModule.search(regex, limit);
|
||||
}
|
||||
get Emotes() {
|
||||
return Object.defineProperties({
|
||||
return Object.defineProperty(this, 'Emotes', {value: Object.defineProperties({
|
||||
setFavourite: this.setFavouriteEmote.bind(this),
|
||||
addFavourite: this.addFavouriteEmote.bind(this),
|
||||
removeFavourite: this.removeFavouriteEmote.bind(this),
|
||||
|
@ -492,7 +492,7 @@ export default class PluginApi {
|
|||
mostused: {
|
||||
get: () => this.mostUsedEmotes
|
||||
}
|
||||
});
|
||||
})}).Emotes;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -507,10 +507,10 @@ export default class PluginApi {
|
|||
return PluginManager.localContent.map(plugin => plugin.id);
|
||||
}
|
||||
get Plugins() {
|
||||
return {
|
||||
return Object.defineProperty(this, 'Plugins', {value: {
|
||||
getPlugin: this.getPlugin.bind(this),
|
||||
listPlugins: this.listPlugins.bind(this)
|
||||
};
|
||||
}}).Plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -525,10 +525,10 @@ export default class PluginApi {
|
|||
return ThemeManager.localContent.map(theme => theme.id);
|
||||
}
|
||||
get Themes() {
|
||||
return {
|
||||
return Object.defineProperty(this, 'Themes', {value: {
|
||||
getTheme: this.getTheme.bind(this),
|
||||
listThemes: this.listThemes.bind(this)
|
||||
};
|
||||
}}).Themes;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -543,10 +543,10 @@ export default class PluginApi {
|
|||
return ExtModuleManager.localContent.map(module => module.id);
|
||||
}
|
||||
get ExtModules() {
|
||||
return {
|
||||
return Object.defineProperty(this, 'ExtModules', {value: {
|
||||
getModule: this.getModule.bind(this),
|
||||
listModules: this.listModules.bind(this)
|
||||
};
|
||||
}}).ExtModules;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -566,7 +566,7 @@ export default class PluginApi {
|
|||
return Patcher.unpatchAll(patches || this.plugin.id);
|
||||
}
|
||||
get Patcher() {
|
||||
return Object.defineProperty({
|
||||
return Object.defineProperty(this, 'Patcher', {value: Object.defineProperty({
|
||||
before: this.patchBefore.bind(this),
|
||||
after: this.patchAfter.bind(this),
|
||||
instead: this.patchInstead.bind(this),
|
||||
|
@ -575,10 +575,10 @@ export default class PluginApi {
|
|||
monkeyPatch: this.monkeyPatch.bind(this)
|
||||
}, 'patches', {
|
||||
get: () => this.patches
|
||||
});
|
||||
})}).Patcher;
|
||||
}
|
||||
get monkeyPatch() {
|
||||
return m => MonkeyPatch(this.plugin.id, m);
|
||||
return Object.defineProperty(this, 'monkeyPatch', {value: m => MonkeyPatch(this.plugin.id, m)}).monkeyPatch;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -603,17 +603,32 @@ export default class PluginApi {
|
|||
}
|
||||
}
|
||||
get DiscordContextMenu() {
|
||||
return Object.defineProperty({
|
||||
return Object.defineProperty(this, 'DiscordContextMenu', {value: Object.defineProperty({
|
||||
add: this.addDiscordContextMenu.bind(this),
|
||||
remove: this.removeDiscordContextMenu.bind(this),
|
||||
removeAll: this.removeAllDiscordContextMenus.bind(this)
|
||||
}, 'menus', {
|
||||
get: () => this.discordContextMenus
|
||||
});
|
||||
})}).DiscordContextMenu;
|
||||
}
|
||||
|
||||
Vuewrap(id, component, props) {
|
||||
return VueInjector.createReactElement(Vue.component(id, component), props);
|
||||
get Vuewrap() {
|
||||
return Object.defineProperty(this, 'Vuewrap', {value: (id, component, props) => {
|
||||
if (!component.name) component.name = id;
|
||||
return VueInjector.createReactElement(component, props);
|
||||
}}).Vuewrap;
|
||||
}
|
||||
|
||||
unloadAll(closeModals = true) {
|
||||
this.Events.unsubscribeAll();
|
||||
this.observer.unsubscribeAll();
|
||||
this.BdMenuItems.removeAll();
|
||||
this.CssUtils.deleteAllStyles();
|
||||
if (closeModals) this.Modals.closeAll();
|
||||
if (closeModals) this.Notifications.dismissAll();
|
||||
this.Autocomplete.removeAll();
|
||||
this.Patcher.unpatchAll();
|
||||
this.DiscordContextMenu.removeAll();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
import { Permissions } from 'modules';
|
||||
import { Modals } from 'ui';
|
||||
import { ErrorEvent } from 'structs';
|
||||
import { ErrorEvent, CustomSetting } from 'structs';
|
||||
import { ClientLogger as Logger } from 'common';
|
||||
import Globals from './globals';
|
||||
import ContentManager from './contentmanager';
|
||||
|
@ -18,8 +18,9 @@ import ExtModuleManager from './extmodulemanager';
|
|||
import Plugin from './plugin';
|
||||
import PluginApi from './pluginapi';
|
||||
import Vendor from './vendor';
|
||||
import path from 'path';
|
||||
|
||||
export default class extends ContentManager {
|
||||
export default class PluginManager extends ContentManager {
|
||||
|
||||
static get localPlugins() {
|
||||
return this.localContent;
|
||||
|
@ -37,9 +38,17 @@ export default class extends ContentManager {
|
|||
return 'plugins';
|
||||
}
|
||||
|
||||
static async loadAllPlugins(suppressErrors) {
|
||||
static get pluginApiInstances() {
|
||||
return this._pluginApiInstances || (this._pluginApiInstances = {});
|
||||
}
|
||||
|
||||
static get pluginDependencies() {
|
||||
return this._pluginDependencies || (this._pluginDependencies = {});
|
||||
}
|
||||
|
||||
static async loadAllContent(suppressErrors) {
|
||||
this.loaded = false;
|
||||
const loadAll = await this.loadAllContent(true);
|
||||
const loadAll = await super.loadAllContent(true);
|
||||
this.loaded = true;
|
||||
for (const plugin of this.localPlugins) {
|
||||
if (!plugin.enabled) continue;
|
||||
|
@ -71,6 +80,7 @@ export default class extends ContentManager {
|
|||
|
||||
return loadAll;
|
||||
}
|
||||
static get loadAllPlugins() { return this.loadAllContent }
|
||||
static get refreshPlugins() { return this.refreshContent }
|
||||
|
||||
static get loadContent() { return this.loadPlugin }
|
||||
|
@ -86,7 +96,9 @@ export default class extends ContentManager {
|
|||
}
|
||||
}
|
||||
|
||||
const deps = {};
|
||||
const pluginapi = this.pluginApiInstances[paths.contentPath] = new PluginApi(info, paths.contentPath);
|
||||
|
||||
const deps = this.pluginDependencies[paths.contentPath] = {};
|
||||
if (dependencies) {
|
||||
for (const [key, value] of Object.entries(dependencies)) {
|
||||
const extModule = ExtModuleManager.findModule(key);
|
||||
|
@ -99,14 +111,13 @@ export default class extends ContentManager {
|
|||
|
||||
const pluginExports = Globals.require(paths.mainPath);
|
||||
|
||||
const pluginFunction = mainExport ? pluginExports[mainExport]
|
||||
let plugin = mainExport ? pluginExports[mainExport]
|
||||
: pluginExports.__esModule ? pluginExports.default : pluginExports;
|
||||
if (typeof pluginFunction !== 'function')
|
||||
throw {message: `Plugin ${info.name} did not export a function.`};
|
||||
if (typeof plugin === 'function' && !(plugin.prototype instanceof Plugin))
|
||||
plugin = plugin.call(pluginExports, Plugin, pluginapi, Vendor, deps);
|
||||
|
||||
const plugin = pluginFunction.call(pluginExports, Plugin, new PluginApi(info, paths.contentPath), Vendor, deps);
|
||||
if (!plugin || !(plugin.prototype instanceof Plugin))
|
||||
throw {message: `Plugin ${info.name} did not return a class that extends Plugin.`};
|
||||
throw {message: `Plugin ${info.name} did not export a class that extends Plugin or a function that returns a class that extends Plugin.`};
|
||||
|
||||
const instance = new plugin({
|
||||
configs, info, main, paths
|
||||
|
@ -124,6 +135,12 @@ export default class extends ContentManager {
|
|||
static get reloadPlugin() { return this.reloadContent }
|
||||
|
||||
static unloadContentHook(content, reload) {
|
||||
const pluginapi = this.pluginApiInstances[content.contentPath];
|
||||
pluginapi.unloadAll();
|
||||
|
||||
delete this.pluginApiInstances[content.contentPath];
|
||||
delete this.pluginDependencies[content.contentPath];
|
||||
|
||||
delete Globals.require.cache[Globals.require.resolve(content.paths.mainPath)];
|
||||
const uncache = [];
|
||||
for (const required in Globals.require.cache) {
|
||||
|
@ -167,4 +184,111 @@ export default class extends ContentManager {
|
|||
|
||||
static get waitForPlugin() { return this.waitForContent }
|
||||
|
||||
static patchModuleLoad() {
|
||||
const Module = Globals.require('module');
|
||||
const load = Module._load;
|
||||
const resolveFilename = Module._resolveFilename;
|
||||
|
||||
Module._load = function (request, parent, isMain) {
|
||||
if (request === 'betterdiscord' || request.startsWith('betterdiscord/')) {
|
||||
const plugin = PluginManager.getPluginByModule(parent);
|
||||
const contentPath = plugin ? plugin.contentPath : PluginManager.getPluginPathByModule(parent);
|
||||
|
||||
if (contentPath) {
|
||||
const module = PluginManager.requireApi(request, plugin, contentPath, parent);
|
||||
|
||||
if (module) return module;
|
||||
}
|
||||
}
|
||||
|
||||
return load.apply(this, arguments);
|
||||
};
|
||||
|
||||
Module._resolveFilename = function (request, parent, isMain) {
|
||||
if (request === 'betterdiscord' || request.startsWith('betterdiscord/')) {
|
||||
const contentPath = PluginManager.getPluginPathByModule(parent);
|
||||
if (contentPath) return request;
|
||||
}
|
||||
|
||||
return resolveFilename.apply(this, arguments);
|
||||
};
|
||||
}
|
||||
|
||||
static getPluginByModule(module) {
|
||||
return this.localContent.find(plugin => module.filename === plugin.contentPath || module.filename.startsWith(plugin.contentPath + path.sep));
|
||||
}
|
||||
|
||||
static getPluginPathByModule(module) {
|
||||
return Object.keys(this.pluginApiInstances).find(contentPath => module.filename === contentPath || module.filename.startsWith(contentPath + path.sep));
|
||||
}
|
||||
|
||||
static requireApi(request, plugin, contentPath, parent) {
|
||||
if (request === 'betterdiscord/plugin') return Plugin;
|
||||
if (request === 'betterdiscord/plugin-api') return PluginManager.pluginApiInstances[contentPath];
|
||||
if (request === 'betterdiscord/vendor') return Vendor;
|
||||
if (request === 'betterdiscord/dependencies') return PluginManager.pluginDependencies[contentPath];
|
||||
|
||||
if (request.startsWith('betterdiscord/vendor/')) {
|
||||
return Vendor[request.substr(21)];
|
||||
}
|
||||
|
||||
if (request.startsWith('betterdiscord/dependencies/')) {
|
||||
return PluginManager.pluginDependencies[contentPath][request.substr(27)];
|
||||
}
|
||||
|
||||
if (request === 'betterdiscord/plugin-instance') return plugin;
|
||||
|
||||
if (request.startsWith('betterdiscord/bridge/')) {
|
||||
const plugin = PluginManager.getPluginById(request.substr(21));
|
||||
return plugin.bridge;
|
||||
}
|
||||
|
||||
if (request.startsWith('betterdiscord/extmodule/')) {
|
||||
const module = ExtModuleManager.findModule(request.substr(24));
|
||||
return module && module.__require ? module.__require : null;
|
||||
}
|
||||
|
||||
if (request.startsWith('betterdiscord/plugin-api/')) {
|
||||
const api = PluginManager.pluginApiInstances[contentPath];
|
||||
const apirequest = request.substr(25);
|
||||
|
||||
if (apirequest === 'async-eventemitter') return api.AsyncEventEmitter;
|
||||
if (apirequest === 'eventswrapper') return api.EventsWrapper;
|
||||
if (apirequest === 'commoncomponents') return api.CommonComponents;
|
||||
if (apirequest === 'components') return api.Components;
|
||||
if (apirequest === 'filters') return api.Filters;
|
||||
if (apirequest === 'discord-api') return api.DiscordApi;
|
||||
if (apirequest === 'react-components') return api.ReactComponents;
|
||||
if (apirequest === 'react-helpers') return api.ReactHelpers;
|
||||
if (apirequest === 'reflection') return api.Reflection;
|
||||
if (apirequest === 'dom') return api.DOM;
|
||||
if (apirequest === 'vueinjector') return api.VueInjector;
|
||||
|
||||
if (apirequest === 'reflection/modules') return api.Reflection.modules;
|
||||
|
||||
if (apirequest === 'observer') return api.observer;
|
||||
|
||||
if (apirequest === 'logger') return api.Logger;
|
||||
if (apirequest === 'utils') return api.Utils;
|
||||
if (apirequest === 'settings') return api.Settings;
|
||||
if (apirequest === 'internalsettings') return api.InternalSettings;
|
||||
if (apirequest === 'bdmenu') return api.BdMenu;
|
||||
if (apirequest === 'bdmenuitems') return api.BdMenuItems;
|
||||
if (apirequest === 'bdcontextmenu') return api.BdContextMenu;
|
||||
if (apirequest === 'cssutils') return api.CssUtils;
|
||||
if (apirequest === 'modals') return api.Modals;
|
||||
if (apirequest === 'toasts') return api.Toasts;
|
||||
if (apirequest === 'notifications') return api.Notifications;
|
||||
if (apirequest === 'autocomplete') return api.Autocomplete;
|
||||
if (apirequest === 'emotes') return api.Emotes;
|
||||
if (apirequest === 'patcher') return api.Patcher;
|
||||
if (apirequest === 'discordcontextmenu') return api.DiscordContextMenu;
|
||||
if (apirequest === 'vuewrap') return api.Vuewrap.bind(api);
|
||||
|
||||
if (apirequest === 'settings/custom') return CustomSetting;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PluginManager.patchModuleLoad();
|
||||
|
|
|
@ -28,6 +28,7 @@ export default class {
|
|||
* jQuery
|
||||
*/
|
||||
static get jQuery() { return jQuery }
|
||||
static get jquery() { return jQuery }
|
||||
static get $() { return this.jQuery }
|
||||
|
||||
/**
|
||||
|
@ -40,6 +41,7 @@ export default class {
|
|||
* Vue
|
||||
*/
|
||||
static get Vue() { return Vue }
|
||||
static get vue() { return Vue }
|
||||
|
||||
static get axios() { return Axi.axios }
|
||||
|
||||
|
@ -49,5 +51,8 @@ export default class {
|
|||
static get filetype() { return filetype }
|
||||
static get filewatcher() { return filewatcher }
|
||||
static get VTooltip() { return VTooltip }
|
||||
static get combokeys() { return Combokeys }
|
||||
static get 'file-type'() { return filetype }
|
||||
static get 'v-tooltip'() { return VTooltip }
|
||||
|
||||
}
|
||||
|
|
|
@ -67,11 +67,14 @@ export default class CustomSetting extends Setting {
|
|||
* @param {String} classExport The name of a property of the file's exports that will be used (optional)
|
||||
*/
|
||||
setClass(class_file, class_export) {
|
||||
const component = Globals.require(path.join(this.path, class_file));
|
||||
const setting_class = class_export ? component[class_export](CustomSetting) : component.default ? component.default(CustomSetting) : component(CustomSetting);
|
||||
const class_exports = Globals.require(path.resolve(this.path, class_file));
|
||||
let setting_class = class_export ? class_exports[class_export] : class_exports.default ? class_exports.default : class_exports;
|
||||
|
||||
if (typeof setting_class === 'function' && !(setting_class.prototype instanceof CustomSetting))
|
||||
setting_class = setting_class.call(class_exports, CustomSetting);
|
||||
|
||||
if (!(setting_class.prototype instanceof CustomSetting))
|
||||
throw {message: 'Custom setting class function returned a class that doesn\'t extend from CustomSetting.'};
|
||||
throw {message: 'Custom setting class doesn\'t extend from CustomSetting.'};
|
||||
|
||||
this.__proto__ = setting_class.prototype;
|
||||
}
|
||||
|
|
|
@ -1,27 +1,26 @@
|
|||
module.exports = (Plugin, Api, Vendor) => {
|
||||
const Plugin = require('betterdiscord/plugin');
|
||||
|
||||
return class extends Plugin {
|
||||
onStart() {
|
||||
document.addEventListener('dblclick', this.handler);
|
||||
return true;
|
||||
}
|
||||
module.exports = class extends Plugin {
|
||||
onStart() {
|
||||
document.addEventListener('dblclick', this.handler);
|
||||
return true;
|
||||
}
|
||||
|
||||
onStop() {
|
||||
document.removeEventListener('dblclick', this.handler);
|
||||
return true;
|
||||
}
|
||||
onStop() {
|
||||
document.removeEventListener('dblclick', this.handler);
|
||||
return true;
|
||||
}
|
||||
|
||||
handler(e) {
|
||||
const message = e.target.closest('[class^=messageCozy]') || e.target.closest('[class^=messageCompact]');
|
||||
if (!message) return;
|
||||
const btn = message.querySelector('[class^=buttonContainer] [class^=button-]');
|
||||
if (!btn) return;
|
||||
btn.click();
|
||||
const popup = document.querySelector('[class^=container][role=menu]');
|
||||
if (!popup) return;
|
||||
const rii = popup[Object.keys(popup).find(k => k.startsWith('__reactInternal'))];
|
||||
if (!rii || !rii.memoizedProps || !rii.memoizedProps.children || !rii.memoizedProps.children[1] || !rii.memoizedProps.children[1].props || !rii.memoizedProps.children[1].props.onClick) return;
|
||||
rii.memoizedProps.children[1].props.onClick();
|
||||
}
|
||||
handler(e) {
|
||||
const message = e.target.closest('[class^=messageCozy]') || e.target.closest('[class^=messageCompact]');
|
||||
if (!message) return;
|
||||
const btn = message.querySelector('[class^=buttonContainer] [class^=button-]');
|
||||
if (!btn) return;
|
||||
btn.click();
|
||||
const popup = document.querySelector('[class^=container][role=menu]');
|
||||
if (!popup) return;
|
||||
const rii = popup[Object.keys(popup).find(k => k.startsWith('__reactInternal'))];
|
||||
if (!rii || !rii.memoizedProps || !rii.memoizedProps.children || !rii.memoizedProps.children[1] || !rii.memoizedProps.children[1].props || !rii.memoizedProps.children[1].props.onClick) return;
|
||||
rii.memoizedProps.children[1].props.onClick();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
exports.main = (Plugin, { Logger, Settings, Modals, BdMenu: { BdMenuItems }, CommonComponents, DiscordContextMenu, Autocomplete, Notifications, Api }) => class extends Plugin {
|
||||
const Plugin = require('betterdiscord/plugin');
|
||||
const { Logger, Settings, Modals, BdMenuItems, CommonComponents, DiscordContextMenu, Autocomplete, Notifications, Api } = require('betterdiscord/plugin-api');
|
||||
|
||||
exports.main = class extends Plugin {
|
||||
async onstart() {
|
||||
this.keybindEvent = this.keybindEvent.bind(this);
|
||||
|
||||
|
@ -141,4 +144,4 @@ exports.main = (Plugin, { Logger, Settings, Modals, BdMenu: { BdMenuItems }, Com
|
|||
get api() {
|
||||
return Api;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,21 +1,28 @@
|
|||
module.exports.default = {
|
||||
template: "<div style=\"margin-bottom: 15px; background-color: rgba(0, 0, 0, 0.2); border: 1px dashed rgba(255, 255, 255, 0.2); padding: 10px; color: #f6f6f7; font-weight: 500; font-size: 15px;\">Test custom setting {{ setting.id }}. This is from component.js in the plugin/theme's directory. (It can use functions.)</div>",
|
||||
exports.default = {
|
||||
template: `<div style="margin-bottom: 15px; background-color: rgba(0, 0, 0, 0.2); border: 1px dashed rgba(255, 255, 255, 0.2); padding: 10px; color: #f6f6f7; font-weight: 500; font-size: 15px;">
|
||||
Test custom setting {{ setting.id }}. This is from component.js in the plugin/theme's directory. (It can use functions.)
|
||||
</div>`,
|
||||
props: ['setting', 'change']
|
||||
};
|
||||
|
||||
const CustomSetting = require('betterdiscord/plugin-api/settings/custom');
|
||||
|
||||
const component = {
|
||||
template: "<div style=\"margin-bottom: 15px; background-color: rgba(0, 0, 0, 0.2); border: 1px dashed rgba(255, 255, 255, 0.2); padding: 10px; color: #f6f6f7; font-weight: 500; font-size: 15px;\">Test custom setting {{ setting.id }}. Also in component.js. It extends the CustomSetting class. <button class=\"bd-button bd-buttonPrimary\" style=\"display: inline-block; margin-left: 10px;\" @click=\"change(1)\">Set value to 1</button> <button class=\"bd-button bd-buttonPrimary\" style=\"display: inline-block; margin-left: 10px;\" @click=\"change(2)\">Set value to 2</button></div>",
|
||||
template: `<div style="margin-bottom: 15px; background-color: rgba(0, 0, 0, 0.2); border: 1px dashed rgba(255, 255, 255, 0.2); padding: 10px; color: #f6f6f7; font-weight: 500; font-size: 15px;">
|
||||
Test custom setting {{ setting.id }}. Also in component.js. It extends the CustomSetting class.
|
||||
|
||||
<button class="bd-button bd-buttonPrimary" style="display: inline-block; margin-left: 10px;" @click="change(1)">Set value to 1</button>
|
||||
<button class="bd-button bd-buttonPrimary" style="display: inline-block; margin-left: 10px;" @click="change(2)">Set value to 2</button>
|
||||
</div>`,
|
||||
props: ['setting', 'change']
|
||||
};
|
||||
|
||||
module.exports.CustomSetting = function (CustomSetting) {
|
||||
return class extends CustomSetting {
|
||||
get component() {
|
||||
return component;
|
||||
}
|
||||
|
||||
get debug() {
|
||||
return true;
|
||||
}
|
||||
exports.CustomSetting = class extends CustomSetting {
|
||||
get component() {
|
||||
return component;
|
||||
}
|
||||
};
|
||||
|
||||
get debug() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
},
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"Example Module": "1.0"
|
||||
"example-module": "1.0"
|
||||
},
|
||||
"defaultConfig": [
|
||||
{
|
||||
|
|
|
@ -1,144 +1,152 @@
|
|||
module.exports = (Plugin, Api, Vendor, Dependencies) => {
|
||||
const Plugin = require('betterdiscord/plugin');
|
||||
const PluginApi = require('betterdiscord/plugin-api');
|
||||
const Vendor = require('betterdiscord/vendor');
|
||||
const Dependencies = require('betterdiscord/dependencies');
|
||||
|
||||
const { $, _ } = Vendor;
|
||||
const { Events, Logger, InternalSettings, CssUtils } = Api;
|
||||
const { Events, Logger, Utils, InternalSettings, CssUtils } = PluginApi;
|
||||
const { $, _ } = Vendor;
|
||||
|
||||
return class extends Plugin {
|
||||
get api() {
|
||||
return Api;
|
||||
}
|
||||
const ExampleModule = require('betterdiscord/dependencies/example-module');
|
||||
|
||||
async onStart() {
|
||||
await this.injectStyles();
|
||||
|
||||
Events.subscribe('TEST_EVENT', this.eventTest);
|
||||
Logger.log('onStart');
|
||||
|
||||
Logger.log(`Plugin setting "default-0" value: ${this.settings.get('default-0')}`);
|
||||
this.on('setting-updated', event => {
|
||||
console.log('Received plugin setting update:', event);
|
||||
});
|
||||
this.on('settings-updated', event => {
|
||||
console.log('Received plugin settings update:', event);
|
||||
});
|
||||
|
||||
this.settings.on('setting-updated', event => {
|
||||
Logger.log(`Setting ${event.category.id}/${event.setting.id} changed from ${event.old_value} to ${event.value}:`, event);
|
||||
});
|
||||
|
||||
// this.settings.categories.find(c => c.id === 'default').settings.find(s => s.id === 'default-5')
|
||||
this.settings.getSetting('default', 'default-0').on('setting-updated', async event => {
|
||||
Logger.log(`Some feature ${event.value ? 'enabled' : 'disabled'}`);
|
||||
});
|
||||
|
||||
this.settings.on('settings-updated', async event => {
|
||||
await this.injectStyles();
|
||||
|
||||
Logger.log('Settings updated:', event, 'Waiting before saving complete...');
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
Logger.log('Done');
|
||||
});
|
||||
|
||||
Logger.log(`Internal setting "core/default/test-setting" value: ${InternalSettings.get('core', 'default', 'test-setting')}`);
|
||||
Events.subscribe('setting-updated', event => {
|
||||
console.log('Received internal setting update:', event);
|
||||
});
|
||||
|
||||
const exampleModule = new Dependencies['Example Module'];
|
||||
Logger.log(`2+4=${exampleModule.add(2, 4)}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
async injectStyles() {
|
||||
const scss = await CssUtils.getConfigAsSCSS() + `.layer-kosS71 .guilds-wrapper + * {
|
||||
&::before {
|
||||
content: 'Example plugin stuff (test radio setting #{$default-5} selected)';
|
||||
display: block;
|
||||
padding: 10px 40px;
|
||||
color: #eee;
|
||||
background-color: #202225;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
}`;
|
||||
Logger.log('Plugin SCSS:', scss);
|
||||
await CssUtils.injectSass(scss);
|
||||
}
|
||||
|
||||
onStop() {
|
||||
CssUtils.deleteAllStyles();
|
||||
Events.unsubscribeAll();
|
||||
Logger.log('onStop');
|
||||
console.log(this.showSettingsModal());
|
||||
return true;
|
||||
}
|
||||
|
||||
onUnload(reload) {
|
||||
Logger.log('Unloading plugin');
|
||||
delete require.cache[require.resolve('./component')];
|
||||
}
|
||||
|
||||
eventTest(e) {
|
||||
Logger.log(e);
|
||||
}
|
||||
|
||||
get bridge() {
|
||||
return {
|
||||
test1: this.test1.bind(this),
|
||||
test2: this.test2.bind(this)
|
||||
};
|
||||
}
|
||||
|
||||
test1() { return 'It works!'; }
|
||||
test2() { return 'This works too!'; }
|
||||
|
||||
settingChanged(event) {
|
||||
if (!this.enabled) return;
|
||||
Logger.log(`${event.category_id}/${event.setting_id} changed to ${event.value}`);
|
||||
}
|
||||
|
||||
settingsChanged(event) {
|
||||
if (!this.enabled) return;
|
||||
Logger.log([ 'Settings updated', event.updatedSettings ]);
|
||||
}
|
||||
|
||||
get settingscomponent() {
|
||||
const plugin = this;
|
||||
return this._settingscomponent ? this._settingscomponent : this._settingscomponent = {
|
||||
template: "<div style=\"margin-bottom: 15px; background-color: rgba(0, 0, 0, 0.2); border: 1px dashed rgba(255, 255, 255, 0.2); padding: 10px; color: #f6f6f7; font-weight: 500; font-size: 15px;\">Test custom setting {{ setting.id }}. This is from Plugin.settingscomponent.<br />Plugin ID: {{ plugin.id }}</div>",
|
||||
props: ['setting', 'change'],
|
||||
data() { return { plugin }; }
|
||||
};
|
||||
}
|
||||
|
||||
getSettingsComponent(setting, change) {
|
||||
return this._settingscomponent2 ? this._settingscomponent2 : this.settingscomponent2 = {
|
||||
template: "<div style=\"margin-bottom: 15px; background-color: rgba(0, 0, 0, 0.2); border: 1px dashed rgba(255, 255, 255, 0.2); padding: 10px; color: #f6f6f7; font-weight: 500; font-size: 15px;\">Test custom setting {{ setting.id }}. This is from Plugin.getSettingsComponent().</div>",
|
||||
props: ['setting', 'change']
|
||||
};
|
||||
}
|
||||
|
||||
getSettingsComponentHTMLElement(setting, change) {
|
||||
const el = document.createElement('div');
|
||||
el.setAttribute('style', 'margin-bottom: 15px; background-color: rgba(0, 0, 0, 0.2); border: 1px dashed rgba(255, 255, 255, 0.2); padding: 10px; color: #f6f6f7; font-weight: 500; font-size: 15px;');
|
||||
el.textContent = `Test custom setting ${setting.id}. This is from Plugin.getSettingsComponentHTMLElement(). Current value: ${setting.value}.`;
|
||||
|
||||
const button1 = document.createElement('button');
|
||||
button1.setAttribute('class', 'bd-button bd-buttonPrimary');
|
||||
button1.setAttribute('style', 'display: inline-block; margin-left: 10px;');
|
||||
button1.addEventListener('click', () => change(1));
|
||||
button1.textContent = 'Set value to 1';
|
||||
el.appendChild(button1);
|
||||
|
||||
const button2 = document.createElement('button');
|
||||
button2.setAttribute('class', 'bd-button bd-buttonPrimary');
|
||||
button2.setAttribute('style', 'display: inline-block; margin-left: 10px;');
|
||||
button2.addEventListener('click', () => change(2));
|
||||
button2.textContent = 'Set value to 2';
|
||||
el.appendChild(button2);
|
||||
|
||||
return el;
|
||||
}
|
||||
module.exports = class extends Plugin {
|
||||
get require() {
|
||||
return require;
|
||||
}
|
||||
|
||||
get api() {
|
||||
return PluginApi;
|
||||
}
|
||||
|
||||
async onStart() {
|
||||
await this.injectStyles();
|
||||
|
||||
Events.subscribe('TEST_EVENT', this.eventTest);
|
||||
Logger.log('onStart');
|
||||
|
||||
Logger.log(`Plugin setting "default-0" value: ${this.settings.get('default-0')}`);
|
||||
this.on('setting-updated', event => {
|
||||
console.log('Received plugin setting update:', event);
|
||||
});
|
||||
this.on('settings-updated', event => {
|
||||
console.log('Received plugin settings update:', event);
|
||||
});
|
||||
|
||||
this.settings.on('setting-updated', event => {
|
||||
Logger.log(`Setting ${event.category.id}/${event.setting.id} changed from ${event.old_value} to ${event.value}:`, event);
|
||||
});
|
||||
|
||||
// this.settings.categories.find(c => c.id === 'default').settings.find(s => s.id === 'default-5')
|
||||
this.settings.getSetting('default', 'default-0').on('setting-updated', async event => {
|
||||
Logger.log(`Some feature ${event.value ? 'enabled' : 'disabled'}`);
|
||||
});
|
||||
|
||||
this.settings.on('settings-updated', async event => {
|
||||
await this.injectStyles();
|
||||
|
||||
Logger.log('Settings updated:', event, 'Waiting before saving complete...');
|
||||
await Utils.wait(5000);
|
||||
Logger.log('Done');
|
||||
});
|
||||
|
||||
Logger.log(`Internal setting "core/default/test-setting" value: ${InternalSettings.get('core', 'default', 'test-setting')}`);
|
||||
Events.subscribe('setting-updated', event => {
|
||||
Logger.log('Received internal setting update:', event);
|
||||
});
|
||||
|
||||
const exampleModule = new ExampleModule();
|
||||
Logger.log(`2+4=${exampleModule.add(2, 4)}`);
|
||||
}
|
||||
|
||||
async injectStyles() {
|
||||
const scss = await CssUtils.getConfigAsSCSS() + `.layer-kosS71 .guilds-wrapper + * {
|
||||
&::before {
|
||||
content: 'Example plugin stuff (test radio setting #{$default-5} selected)';
|
||||
display: block;
|
||||
padding: 10px 40px;
|
||||
color: #eee;
|
||||
background-color: #202225;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
}`;
|
||||
Logger.log('Plugin SCSS:', scss);
|
||||
await CssUtils.injectSass(scss);
|
||||
}
|
||||
|
||||
onStop() {
|
||||
PluginApi.unloadAll();
|
||||
Logger.log('onStop');
|
||||
console.log(this.showSettingsModal());
|
||||
}
|
||||
|
||||
onUnload(reload) {
|
||||
Logger.log('Unloading plugin');
|
||||
}
|
||||
|
||||
eventTest(e) {
|
||||
Logger.log(e);
|
||||
}
|
||||
|
||||
get bridge() {
|
||||
return {
|
||||
test1: this.test1.bind(this),
|
||||
test2: this.test2.bind(this)
|
||||
};
|
||||
}
|
||||
|
||||
test1() { return 'It works!'; }
|
||||
test2() { return 'This works too!'; }
|
||||
|
||||
settingChanged(event) {
|
||||
if (!this.enabled) return;
|
||||
Logger.log(`${event.category_id}/${event.setting_id} changed to ${event.value}`);
|
||||
}
|
||||
|
||||
settingsChanged(event) {
|
||||
if (!this.enabled) return;
|
||||
Logger.log('Settings updated', event.updatedSettings);
|
||||
}
|
||||
|
||||
get settingscomponent() {
|
||||
const plugin = this;
|
||||
return this._settingscomponent ? this._settingscomponent : this._settingscomponent = {
|
||||
template: `<div style="margin-bottom: 15px; background-color: rgba(0, 0, 0, 0.2); border: 1px dashed rgba(255, 255, 255, 0.2); padding: 10px; color: #f6f6f7; font-weight: 500; font-size: 15px;">
|
||||
Test custom setting {{ setting.id }}. This is from Plugin.settingscomponent.<br />
|
||||
Plugin ID: {{ plugin.id }}
|
||||
</div>`,
|
||||
props: ['setting', 'change'],
|
||||
data() { return { plugin }; }
|
||||
};
|
||||
}
|
||||
|
||||
getSettingsComponent(setting, change) {
|
||||
return this._settingscomponent2 ? this._settingscomponent2 : this.settingscomponent2 = {
|
||||
template: `<div style="margin-bottom: 15px; background-color: rgba(0, 0, 0, 0.2); border: 1px dashed rgba(255, 255, 255, 0.2); padding: 10px; color: #f6f6f7; font-weight: 500; font-size: 15px;">
|
||||
Test custom setting {{ setting.id }}. This is from Plugin.getSettingsComponent().
|
||||
</div>`,
|
||||
props: ['setting', 'change']
|
||||
};
|
||||
}
|
||||
|
||||
getSettingsComponentHTMLElement(setting, change) {
|
||||
const el = document.createElement('div');
|
||||
el.setAttribute('style', 'margin-bottom: 15px; background-color: rgba(0, 0, 0, 0.2); border: 1px dashed rgba(255, 255, 255, 0.2); padding: 10px; color: #f6f6f7; font-weight: 500; font-size: 15px;');
|
||||
el.textContent = `Test custom setting ${setting.id}. This is from Plugin.getSettingsComponentHTMLElement(). Current value: ${setting.value}.`;
|
||||
|
||||
const button1 = document.createElement('button');
|
||||
button1.setAttribute('class', 'bd-button bd-buttonPrimary');
|
||||
button1.setAttribute('style', 'display: inline-block; margin-left: 10px;');
|
||||
button1.addEventListener('click', () => change(1));
|
||||
button1.textContent = 'Set value to 1';
|
||||
el.appendChild(button1);
|
||||
|
||||
const button2 = document.createElement('button');
|
||||
button2.setAttribute('class', 'bd-button bd-buttonPrimary');
|
||||
button2.setAttribute('style', 'display: inline-block; margin-left: 10px;');
|
||||
button2.addEventListener('click', () => change(2));
|
||||
button2.textContent = 'Set value to 2';
|
||||
el.appendChild(button2);
|
||||
|
||||
return el;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,21 @@
|
|||
module.exports = (Plugin, Api, Vendor) => {
|
||||
|
||||
const { Logger, ReactComponents, Patcher, monkeyPatch } = Api;
|
||||
const Plugin = require('betterdiscord/plugin');
|
||||
const { Logger, ReactComponents, Patcher, monkeyPatch } = require('betterdiscord/plugin-api');
|
||||
|
||||
return class extends Plugin {
|
||||
onStart() {
|
||||
this.patchMessage();
|
||||
return true;
|
||||
}
|
||||
|
||||
async patchMessage() {
|
||||
const Message = await ReactComponents.getComponent('Message');
|
||||
monkeyPatch(Message.component.prototype).after('render', e => {
|
||||
Logger.log('MESSAGE RENDER!', e);
|
||||
});
|
||||
}
|
||||
|
||||
onStop() {
|
||||
// The automatic unpatcher is not there yet
|
||||
Patcher.unpatchAll();
|
||||
return true;
|
||||
}
|
||||
module.exports = class extends Plugin {
|
||||
onStart() {
|
||||
this.patchMessage();
|
||||
}
|
||||
|
||||
};
|
||||
async patchMessage() {
|
||||
const Message = await ReactComponents.getComponent('Message');
|
||||
monkeyPatch(Message.component.prototype).after('render', e => {
|
||||
Logger.log('MESSAGE RENDER!', e);
|
||||
});
|
||||
}
|
||||
|
||||
onStop() {
|
||||
// The automatic unpatcher is not there yet
|
||||
Patcher.unpatchAll();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
module.exports = (React, props) => {
|
||||
const { React } = require('betterdiscord/plugin-api/reflection/modules');
|
||||
|
||||
module.exports = props => {
|
||||
return React.createElement(
|
||||
'button',
|
||||
{ className: 'exampleCustomElement', onClick: props.onClick },
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
module.exports = (VueWrap, props) => {
|
||||
return VueWrap('somecomponent', {
|
||||
const { Vuewrap } = require('betterdiscord/plugin-api');
|
||||
|
||||
module.exports = props => {
|
||||
return Vuewrap('somecomponent', {
|
||||
render: function (createElement) {
|
||||
return createElement('button', {
|
||||
class: 'exampleCustomElement',
|
||||
|
|
|
@ -2,127 +2,126 @@
|
|||
* This is an example of how you should add custom elements instead of manipulating the DOM directly
|
||||
*/
|
||||
|
||||
const Plugin = require('betterdiscord/plugin');
|
||||
const PluginApi = require('betterdiscord/plugin-api');
|
||||
const Vendor = require('betterdiscord/vendor');
|
||||
|
||||
// Destructure some apis
|
||||
const { Logger, ReactComponents, Patcher, monkeyPatch, Components, Utils, CssUtils } = PluginApi;
|
||||
|
||||
// Be careful with this - some modules won't have been loaded yet
|
||||
const { React } = require('betterdiscord/plugin-api/reflection/modules');
|
||||
|
||||
// Import custom components
|
||||
const customVueComponent = require('./components/vuecomponent');
|
||||
const customReactComponent = require('./components/reactcomponent');
|
||||
|
||||
module.exports = (Plugin, Api, Vendor) => {
|
||||
module.exports = class extends Plugin {
|
||||
|
||||
// Destructure some apis
|
||||
const { Logger, ReactComponents, Patcher, monkeyPatch, Reflection, Utils, CssUtils, VueInjector, Vuewrap, requireUncached } = Api;
|
||||
const { Vue } = Vendor;
|
||||
const { React } = Reflection.modules; // This should be in vendor
|
||||
|
||||
return class extends Plugin {
|
||||
|
||||
async onStart() {
|
||||
this.injectStyle();
|
||||
this.patchGuildTextChannel();
|
||||
this.patchMessages();
|
||||
return true;
|
||||
}
|
||||
|
||||
async onStop() {
|
||||
// The automatic unpatcher is not there yet
|
||||
Patcher.unpatchAll();
|
||||
CssUtils.deleteAllStyles();
|
||||
|
||||
// Force update elements to remove our changes
|
||||
const GuildTextChannel = await ReactComponents.getComponent('GuildTextChannel');
|
||||
GuildTextChannel.forceUpdateAll();
|
||||
const MessageContent = await ReactComponents.getComponent('MessageContent', { selector: Reflection.resolve('container', 'containerCozy', 'containerCompact', 'edited').selector }, m => m.defaultProps && m.defaultProps.hasOwnProperty('disableButtons'));
|
||||
MessageContent.forceUpdateAll();
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Inject some style for our custom element */
|
||||
async injectStyle() {
|
||||
const css = `
|
||||
.exampleCustomElement {
|
||||
background: #7a7d82;
|
||||
color: #FFF;
|
||||
border-radius: 5px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
opacity: .5;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.exampleBtnGroup {
|
||||
.bd-button {
|
||||
font-size: 14px;
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
await CssUtils.injectSass(css);
|
||||
}
|
||||
|
||||
async patchGuildTextChannel() {
|
||||
// Get the GuildTextChannel component and patch it's render function
|
||||
const GuildTextChannel = await ReactComponents.getComponent('GuildTextChannel');
|
||||
monkeyPatch(GuildTextChannel.component.prototype).after('render', this.injectCustomElements.bind(this));
|
||||
// Force update to see our changes immediatly
|
||||
GuildTextChannel.forceUpdateAll();
|
||||
}
|
||||
|
||||
async patchMessages() {
|
||||
// Get Message component and patch it's render function
|
||||
const MessageContent = await ReactComponents.getComponent('MessageContent', { selector: Reflection.resolve('container', 'containerCozy', 'containerCompact', 'edited').selector });
|
||||
monkeyPatch(MessageContent.component.prototype).after('render', this.injectGenericComponents.bind(this));
|
||||
// Force update to see our changes immediatly
|
||||
MessageContent.forceUpdateAll();
|
||||
}
|
||||
|
||||
/*
|
||||
* Injecting a custom React element using React.createElement
|
||||
* https://reactjs.org/docs/react-api.html#createelement
|
||||
* Injecting a custom Vue element using Vue.component
|
||||
* https://vuejs.org/v2/guide/render-function.html
|
||||
**/
|
||||
injectCustomElements(that, args, returnValue) {
|
||||
// Get the child we want using a treewalker since we know the child we want has a channel property and children.
|
||||
const child = Utils.findInReactTree(returnValue, filter => filter.hasOwnProperty('channel') && filter.children);
|
||||
if (!child) return;
|
||||
// If children is not an array make it into one
|
||||
if (!child.children instanceof Array) child.children = [child.children];
|
||||
|
||||
// Add our custom components to children
|
||||
child.children.push(customReactComponent(React, { onClick: e => this.handleClick(e, child.channel) }));
|
||||
child.children.push(customVueComponent(Vuewrap, { onClick: e => this.handleClick(e, child.channel) }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject generic components provided by BD
|
||||
*/
|
||||
injectGenericComponents(that, args, returnValue) {
|
||||
// If children is not an array make it into one
|
||||
if (!returnValue.props.children instanceof Array) returnValue.props.children = [returnValue.props.children];
|
||||
// Add a generic Button component provided by BD
|
||||
returnValue.props.children.push(Api.Components.ButtonGroup({
|
||||
classes: [ 'exampleBtnGroup' ], // Additional classes for button group
|
||||
buttons: [
|
||||
{
|
||||
classes: ['exampleBtn'], // Additional classes for button
|
||||
text: 'Hello World!', // Text for button
|
||||
onClick: e => Logger.log('Hello World!') // Button click handler
|
||||
},
|
||||
{
|
||||
classes: ['exampleBtn'],
|
||||
text: 'Button',
|
||||
onClick: e => Logger.log('Button!')
|
||||
}
|
||||
]
|
||||
}).render()); // Render will return the wrapped component that can then be displayed
|
||||
}
|
||||
|
||||
/**
|
||||
* Will log the channel object
|
||||
*/
|
||||
handleClick(e, channel) {
|
||||
Logger.log('Clicked!', channel);
|
||||
}
|
||||
async onStart() {
|
||||
this.injectStyle();
|
||||
this.patchGuildTextChannel();
|
||||
this.patchMessages();
|
||||
}
|
||||
|
||||
async onStop() {
|
||||
// The automatic unpatcher is not there yet
|
||||
Patcher.unpatchAll();
|
||||
CssUtils.deleteAllStyles();
|
||||
|
||||
// Force update elements to remove our changes
|
||||
const GuildTextChannel = await ReactComponents.getComponent('GuildTextChannel');
|
||||
GuildTextChannel.forceUpdateAll();
|
||||
const MessageContent = await ReactComponents.getComponent('MessageContent');
|
||||
MessageContent.forceUpdateAll();
|
||||
}
|
||||
|
||||
/* Inject some style for our custom element */
|
||||
async injectStyle() {
|
||||
const css = `
|
||||
.exampleCustomElement {
|
||||
background: #7a7d82;
|
||||
color: #FFF;
|
||||
border-radius: 5px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
opacity: .5;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.exampleBtnGroup {
|
||||
.bd-button {
|
||||
font-size: 14px;
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
await CssUtils.injectSass(css);
|
||||
}
|
||||
|
||||
async patchGuildTextChannel() {
|
||||
// Get the GuildTextChannel component and patch it's render function
|
||||
const GuildTextChannel = await ReactComponents.getComponent('GuildTextChannel');
|
||||
monkeyPatch(GuildTextChannel.component.prototype).after('render', this.injectCustomElements.bind(this));
|
||||
// Force update to see our changes immediatly
|
||||
GuildTextChannel.forceUpdateAll();
|
||||
}
|
||||
|
||||
async patchMessages() {
|
||||
// Get Message component and patch it's render function
|
||||
const MessageContent = await ReactComponents.getComponent('MessageContent');
|
||||
monkeyPatch(MessageContent.component.prototype).after('render', this.injectGenericComponents.bind(this));
|
||||
// Force update to see our changes immediatly
|
||||
MessageContent.forceUpdateAll();
|
||||
}
|
||||
|
||||
/*
|
||||
* Injecting a custom React element using React.createElement
|
||||
* https://reactjs.org/docs/react-api.html#createelement
|
||||
* Injecting a custom Vue element using Vue.component
|
||||
* https://vuejs.org/v2/guide/render-function.html
|
||||
**/
|
||||
injectCustomElements(that, args, returnValue) {
|
||||
// Get the child we want using a treewalker since we know the child we want has a channel property and children.
|
||||
const child = Utils.findInReactTree(returnValue, filter => filter.hasOwnProperty('channel') && filter.children);
|
||||
if (!child) return;
|
||||
// If children is not an array make it into one
|
||||
if (!child.children instanceof Array) child.children = [child.children];
|
||||
|
||||
// Add our custom components to children
|
||||
child.children.push(customReactComponent({ onClick: e => this.handleClick(e, child.channel) }));
|
||||
child.children.push(customVueComponent({ onClick: e => this.handleClick(e, child.channel) }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject generic components provided by BD
|
||||
*/
|
||||
injectGenericComponents(that, args, returnValue) {
|
||||
// If children is not an array make it into one
|
||||
if (!returnValue.props.children instanceof Array) returnValue.props.children = [returnValue.props.children];
|
||||
// Add a generic Button component provided by BD
|
||||
returnValue.props.children.push(Components.ButtonGroup({
|
||||
classes: [ 'exampleBtnGroup' ], // Additional classes for button group
|
||||
buttons: [
|
||||
{
|
||||
classes: ['exampleBtn'], // Additional classes for button
|
||||
text: 'Hello World!', // Text for button
|
||||
onClick: e => Logger.log('Hello World!') // Button click handler
|
||||
},
|
||||
{
|
||||
classes: ['exampleBtn'],
|
||||
text: 'Button',
|
||||
onClick: e => Logger.log('Button!')
|
||||
}
|
||||
]
|
||||
}).render()); // Render will return the wrapped component that can then be displayed
|
||||
}
|
||||
|
||||
/**
|
||||
* Will log the channel object
|
||||
*/
|
||||
handleClick(e, channel) {
|
||||
Logger.log('Clicked!', channel);
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue