commit
da28828880
|
@ -25,30 +25,28 @@
|
|||
<div v-else class="bd-e2eeTaBtn bd-e2eeLock bd-ok">
|
||||
<MiLock v-tooltip="'Ready!'" />
|
||||
</div>
|
||||
|
||||
<template slot="popover">
|
||||
<div @click="toggleEncrypt" :class="{'bd-warn': !E2EE.encryptNewMessages, 'bd-ok': E2EE.encryptNewMessages}"><MiLock size="16" v-tooltip="'Toggle Encryption'" /></div>
|
||||
<div v-close-popover @click="showUploadDialog" v-if="!error"><MiImagePlus size="16" v-tooltip="'Upload Encrypted Image'" /></div>
|
||||
<!-- Using these icons for now -->
|
||||
<div v-close-popover @click="generatePublicKey" v-if="DiscordApi.currentChannel.type === 'DM'"><MiPencil size="16" v-tooltip="'Begin Key Exchange'" /></div>
|
||||
<div v-close-popover @click="generatePublicKey" v-if="DiscordApi.currentChannel.type === 'DM'"><MiIcVpnKey size="16" v-tooltip="'Begin Key Exchange'" /></div>
|
||||
</template>
|
||||
</v-popover>
|
||||
<div class="bd-taDivider"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
import fs from 'fs';
|
||||
import { Utils } from 'common';
|
||||
import { remote } from 'electron';
|
||||
import { Utils, FileUtils, ClientIPC } from 'common';
|
||||
import { E2EE } from 'builtin';
|
||||
import { DiscordApi, Security, WebpackModules } from 'modules';
|
||||
import { MiLock, MiPlus, MiImagePlus, MiPencil, MiRefresh } from '../ui/components/common/MaterialIcon';
|
||||
import { DiscordApi, WebpackModules } from 'modules';
|
||||
import { Toasts } from 'ui';
|
||||
import { MiLock, MiImagePlus, MiIcVpnKey } from '../ui/components/common/MaterialIcon';
|
||||
|
||||
export default {
|
||||
components: { MiLock, MiPlus, MiImagePlus, MiPencil, MiRefresh },
|
||||
components: {
|
||||
MiLock, MiImagePlus, MiIcVpnKey
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
E2EE,
|
||||
|
@ -59,12 +57,12 @@
|
|||
},
|
||||
methods: {
|
||||
async showUploadDialog() {
|
||||
const dialogResult = remote.dialog.showOpenDialog({ properties: ['openFile'] });
|
||||
if (!dialogResult) return;
|
||||
const dialogResult = await ClientIPC.send('bd-native-open', {properties: ['openFile']});
|
||||
if (!dialogResult || !dialogResult.length) return;
|
||||
|
||||
const readFile = fs.readFileSync(dialogResult[0]);
|
||||
const FileActions = WebpackModules.getModuleByProps(["makeFile"]);
|
||||
const Uploader = WebpackModules.getModuleByProps(["instantBatchUpload"]);
|
||||
const readFile = await FileUtils.readFileBuffer(dialogResult[0]);
|
||||
const FileActions = WebpackModules.getModuleByProps(['makeFile']);
|
||||
const Uploader = WebpackModules.getModuleByProps(['instantBatchUpload']);
|
||||
|
||||
const img = await Utils.getImageFromBuffer(readFile);
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ const EMOTE_SOURCES = [
|
|||
'https://static-cdn.jtvnw.net/emoticons/v1/:id/1.0',
|
||||
'https://cdn.frankerfacez.com/emoticon/:id/1',
|
||||
'https://cdn.betterttv.net/emote/:id/1x'
|
||||
]
|
||||
];
|
||||
|
||||
export default new class EmoteModule extends BuiltinModule {
|
||||
|
||||
|
@ -52,38 +52,30 @@ export default new class EmoteModule extends BuiltinModule {
|
|||
get settingPath() { return ['emotes', 'default', 'enable'] }
|
||||
|
||||
async enabled() {
|
||||
|
||||
// Add favourite button to context menu
|
||||
this.removeFavCm = DiscordContextMenu.add('BD:EmoteModule:FavCM', target => [
|
||||
this.favCm = DiscordContextMenu.add(target => [
|
||||
{
|
||||
text: 'Favourite',
|
||||
type: 'toggle',
|
||||
checked: target && target.alt ? this.favourites.find(e => e.name === target.alt.replace(/;/g, '')) : false,
|
||||
checked: target && target.alt && this.isFavourite(target.alt.replace(/;/g, '')),
|
||||
onChange: (checked, target) => {
|
||||
const { alt } = target;
|
||||
if (!alt) return false;
|
||||
|
||||
const name = alt.replace(/;/g, '');
|
||||
const emote = alt.replace(/;/g, '');
|
||||
|
||||
if (!checked) return this.removeFavourite(name);
|
||||
|
||||
const emote = this.findByName(name, true);
|
||||
if (!emote) return false;
|
||||
return this.addFavourite(emote);
|
||||
if (!checked) return this.removeFavourite(emote), false;
|
||||
return this.addFavourite(emote), true;
|
||||
}
|
||||
}
|
||||
], filter => filter.closest('.bd-emote'));
|
||||
], target => target.closest('.bd-emote'));
|
||||
|
||||
if (!this.database.size) {
|
||||
await this.loadLocalDb();
|
||||
}
|
||||
|
||||
// Read favourites and most used from database
|
||||
const userData = await Database.findOne({ 'id': 'EmoteModule' });
|
||||
if (userData) {
|
||||
if (userData.hasOwnProperty('favourites')) this._favourites = userData.favourites;
|
||||
if (userData.hasOwnProperty('mostused')) this._mostUsed = userData.mostused;
|
||||
}
|
||||
await this.loadUserData();
|
||||
|
||||
this.patchMessageContent();
|
||||
this.patchSendAndEdit();
|
||||
|
@ -91,24 +83,42 @@ export default new class EmoteModule extends BuiltinModule {
|
|||
MonkeyPatch('BD:EMOTEMODULE', ImageWrapper.component.prototype).after('render', this.beforeRenderImageWrapper.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an emote to favourites.
|
||||
* @param {Object|String} emote
|
||||
* @return {Promise}
|
||||
*/
|
||||
addFavourite(emote) {
|
||||
if (this.favourites.find(e => e.name === emote.name)) return true;
|
||||
if (this.isFavourite(emote)) return;
|
||||
if (typeof emote === 'string') emote = this.findByName(emote, true);
|
||||
this.favourites.push(emote);
|
||||
Database.insertOrUpdate({ 'id': 'EmoteModule' }, { 'id': 'EmoteModule', favourites: this.favourites, mostused: this.mostUsed })
|
||||
return true;
|
||||
return this.saveUserData();
|
||||
}
|
||||
|
||||
removeFavourite(name) {
|
||||
if (!this.favourites.find(e => e.name === name)) return false;
|
||||
this._favourites = this._favourites.filter(e => e.name !== name);
|
||||
Database.insertOrUpdate({ 'id': 'EmoteModule' }, { 'id': 'EmoteModule', favourites: this.favourites, mostused: this.mostUsed })
|
||||
return false;
|
||||
/**
|
||||
* Removes an emote from favourites.
|
||||
* @param {Object|String} emote
|
||||
* @return {Promise}
|
||||
*/
|
||||
removeFavourite(emote) {
|
||||
if (!this.isFavourite(emote)) return;
|
||||
Utils.removeFromArray(this.favourites, e => e.name === emote || e.name === emote.name, true);
|
||||
return this.saveUserData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an emote is in favourites.
|
||||
* @param {Object|String} emote
|
||||
* @return {Boolean}
|
||||
*/
|
||||
isFavourite(emote) {
|
||||
return !!this.favourites.find(e => e.name === emote || e.name === emote.name);
|
||||
}
|
||||
|
||||
async disabled() {
|
||||
// Unpatch all patches
|
||||
for (const patch of Patcher.getPatchesByCaller('BD:EMOTEMODULE')) patch.unpatch();
|
||||
if (this.removeFavCm) this.removeFavCm();
|
||||
DiscordContextMenu.remove(this.favCm);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -124,6 +134,23 @@ export default new class EmoteModule extends BuiltinModule {
|
|||
}
|
||||
}
|
||||
|
||||
async loadUserData() {
|
||||
const userData = await Database.findOne({ type: 'builtin', id: 'EmoteModule' });
|
||||
if (!userData) return;
|
||||
|
||||
if (userData.hasOwnProperty('favourites')) this._favourites = userData.favourites;
|
||||
if (userData.hasOwnProperty('mostused')) this._mostUsed = userData.mostused;
|
||||
}
|
||||
|
||||
async saveUserData() {
|
||||
await Database.insertOrUpdate({ type: 'builtin', id: 'EmoteModule' }, {
|
||||
type: 'builtin',
|
||||
id: 'EmoteModule',
|
||||
favourites: this.favourites,
|
||||
mostused: this.mostUsed
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Patches MessageContent render method
|
||||
*/
|
||||
|
@ -228,6 +255,7 @@ export default new class EmoteModule extends BuiltinModule {
|
|||
/**
|
||||
* Add/update emote to most used
|
||||
* @param {Object} emote emote to add/update
|
||||
* @return {Promise}
|
||||
*/
|
||||
addToMostUsed(emote) {
|
||||
const isMostUsed = this.mostUsed.find(mu => mu.key === emote.name);
|
||||
|
@ -243,7 +271,7 @@ export default new class EmoteModule extends BuiltinModule {
|
|||
}
|
||||
// Save most used to database
|
||||
// TODO only save first n
|
||||
Database.insertOrUpdate({ 'id': 'EmoteModule' }, { 'id': 'EmoteModule', favourites: this.favourites, mostused: this.mostUsed })
|
||||
return this.saveUserData();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -28,17 +28,18 @@ export default new class ReactDevtoolsModule extends BuiltinModule {
|
|||
}
|
||||
|
||||
enabled(e) {
|
||||
electron.remote.BrowserWindow.getAllWindows()[0].webContents.on('devtools-opened', this.devToolsOpened);
|
||||
electron.remote.getCurrentWindow().webContents.on('devtools-opened', this.devToolsOpened);
|
||||
if (electron.remote.getCurrentWindow().isDevToolsOpened) this.devToolsOpened();
|
||||
}
|
||||
|
||||
disabled(e) {
|
||||
electron.remote.BrowserWindow.removeDevToolsExtension('React Developer Tools');
|
||||
electron.remote.BrowserWindow.getAllWindows()[0].webContents.removeListener('devtools-opened', this.devToolsOpened);
|
||||
electron.remote.getCurrentWindow().webContents.removeListener('devtools-opened', this.devToolsOpened);
|
||||
}
|
||||
|
||||
devToolsOpened() {
|
||||
electron.remote.BrowserWindow.removeDevToolsExtension('React Developer Tools');
|
||||
electron.webFrame.registerURLSchemeAsPrivileged('chrome-extension');
|
||||
|
||||
try {
|
||||
const res = electron.remote.BrowserWindow.addDevToolsExtension(path.join(Globals.getPath('ext'), 'extensions', 'rdt'));
|
||||
if (res !== undefined) {
|
||||
|
|
|
@ -28,21 +28,22 @@ export default new class VueDevtoolsModule extends BuiltinModule {
|
|||
}
|
||||
|
||||
enabled(e) {
|
||||
electron.remote.BrowserWindow.getAllWindows()[0].webContents.on('devtools-opened', this.devToolsOpened);
|
||||
electron.remote.getCurrentWindow().webContents.on('devtools-opened', this.devToolsOpened);
|
||||
if (electron.remote.getCurrentWindow().isDevToolsOpened) this.devToolsOpened();
|
||||
}
|
||||
|
||||
disabled(e) {
|
||||
electron.remote.BrowserWindow.removeDevToolsExtension('Vue.js devtools');
|
||||
electron.remote.BrowserWindow.getAllWindows()[0].webContents.removeListener('devtools-opened', this.devToolsOpened);
|
||||
electron.remote.getCurrentWindow().webContents.removeListener('devtools-opened', this.devToolsOpened);
|
||||
}
|
||||
|
||||
devToolsOpened() {
|
||||
electron.remote.BrowserWindow.removeDevToolsExtension('Vue.js devtools');
|
||||
electron.webFrame.registerURLSchemeAsPrivileged('chrome-extension');
|
||||
|
||||
try {
|
||||
const res = electron.remote.BrowserWindow.addDevToolsExtension(path.join(Globals.getPath('ext'), 'extensions', 'vdt'));
|
||||
if (res !== undefined) {
|
||||
Toasts.success(`${res } Installed`);
|
||||
Toasts.success(`${res} Installed`);
|
||||
return;
|
||||
}
|
||||
Toasts.error('Vue.js devtools install failed');
|
||||
|
|
|
@ -61,5 +61,19 @@
|
|||
"developer": false,
|
||||
"webdev": false,
|
||||
"contributor": true
|
||||
},
|
||||
{
|
||||
"__user": "Lucario 🌌 V5.0.0#7902",
|
||||
"id": "438469378418409483",
|
||||
"developer": false,
|
||||
"webdev": false,
|
||||
"contributor": true
|
||||
},
|
||||
{
|
||||
"__user": "Maks#3712",
|
||||
"id": "340975736037048332",
|
||||
"developer": false,
|
||||
"webdev": false,
|
||||
"contributor": true
|
||||
}
|
||||
]
|
||||
|
|
|
@ -73,11 +73,20 @@ class BetterDiscord {
|
|||
Globals.initg();
|
||||
}
|
||||
|
||||
globalReady() {
|
||||
BdUI.initUiEvents();
|
||||
this.vueInstance = BdUI.injectUi();
|
||||
this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
try {
|
||||
await Database.init();
|
||||
await Settings.loadSettings();
|
||||
await ModuleManager.initModules();
|
||||
BuiltinManager.initAll();
|
||||
|
||||
if (tests) this.initTests();
|
||||
|
||||
if (!ignoreExternal) {
|
||||
await ExtModuleManager.loadAllModules(true);
|
||||
|
@ -85,45 +94,41 @@ class BetterDiscord {
|
|||
await ThemeManager.loadAllThemes(true);
|
||||
}
|
||||
|
||||
if (!Settings.get('core', 'advanced', 'ignore-content-manager-errors'))
|
||||
Modals.showContentManagerErrors();
|
||||
|
||||
Events.emit('ready');
|
||||
Events.emit('discord-ready');
|
||||
BuiltinManager.initAll();
|
||||
|
||||
function showDummyNotif() { // eslint-disable-line no-inner-declarations
|
||||
Notifications.add('Dummy Notification', [
|
||||
{
|
||||
text: 'Show Again', onClick: function () {
|
||||
setTimeout(showDummyNotif, 5000);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
{
|
||||
text: 'Ignore', onClick: function () {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
]);
|
||||
}
|
||||
showDummyNotif();
|
||||
|
||||
DiscordContextMenu.add('DummyThing', [
|
||||
{
|
||||
text: 'Hello',
|
||||
onClick: () => { Toasts.info('Hello!'); }
|
||||
}
|
||||
]);
|
||||
if (!Settings.get('core', 'advanced', 'ignore-content-manager-errors'))
|
||||
Modals.showContentManagerErrors();
|
||||
} catch (err) {
|
||||
Logger.err('main', ['FAILED TO LOAD!', err]);
|
||||
}
|
||||
}
|
||||
|
||||
globalReady() {
|
||||
BdUI.initUiEvents();
|
||||
this.vueInstance = BdUI.injectUi();
|
||||
this.init();
|
||||
initTests() {
|
||||
let notifications = 0;
|
||||
function showDummyNotif() { // eslint-disable-line no-inner-declarations
|
||||
Notifications.add(notifications++ ? `Notification ${notifications}` : undefined, 'Dummy Notification', [
|
||||
{
|
||||
text: 'Show Again', onClick: function () {
|
||||
setTimeout(showDummyNotif, 5000);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
{
|
||||
text: 'Ignore', onClick: function () {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
]);
|
||||
}
|
||||
showDummyNotif();
|
||||
|
||||
DiscordContextMenu.add([
|
||||
{
|
||||
text: 'Hello',
|
||||
onClick: () => { Toasts.info('Hello!'); }
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -266,7 +266,7 @@ export default class {
|
|||
if (!content) throw {message: `Could not find a ${this.contentType} from ${content}.`};
|
||||
|
||||
try {
|
||||
await Modals.confirm(`Delete ${this.contentType} ?`, `Are you sure you want to delete ${content.info.name} ?`, 'Delete').promise;
|
||||
await Modals.confirm(`Delete ${this.contentType}?`, `Are you sure you want to delete ${content.info.name} ?`, 'Delete').promise;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
|
@ -277,8 +277,7 @@ export default class {
|
|||
if (!force)
|
||||
await unload;
|
||||
|
||||
await FileUtils.directoryExists(content.paths.contentPath);
|
||||
FileUtils.deleteDirectory(content.paths.contentPath);
|
||||
await FileUtils.recursiveDeleteDirectory(content.paths.contentPath);
|
||||
return true;
|
||||
} catch (err) {
|
||||
Logger.err(this.moduleName, err);
|
||||
|
@ -308,7 +307,7 @@ export default class {
|
|||
|
||||
const index = this.getContentIndex(content);
|
||||
|
||||
delete Globals.require.cache[Globals.require.resolve(content.paths.mainPath)];
|
||||
if (this.unloadContentHook) this.unloadContentHook(content);
|
||||
|
||||
if (reload) {
|
||||
const newcontent = await this.preloadContent(content.dirName, true, index);
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
import { EmoteModule } from 'builtin';
|
||||
import { SettingsSet, SettingsCategory, Setting, SettingsScheme } from 'structs';
|
||||
import { BdMenu, Modals, DOM, DOMObserver, Reflection, VueInjector, Toasts } from 'ui';
|
||||
import { BdMenu, Modals, DOM, DOMObserver, Reflection, VueInjector, Toasts, Notifications, BdContextMenu, DiscordContextMenu } from 'ui';
|
||||
import * as CommonComponents from 'commoncomponents';
|
||||
import { Utils, Filters, ClientLogger as Logger, ClientIPC, AsyncEventEmitter } from 'common';
|
||||
import Settings from './settings';
|
||||
|
@ -23,6 +23,7 @@ import { WebpackModules } from './webpackmodules';
|
|||
import DiscordApi from './discordapi';
|
||||
import { ReactComponents, ReactHelpers } from './reactcomponents';
|
||||
import { Patcher, MonkeyPatch } from './patcher';
|
||||
import GlobalAc from '../ui/autocomplete';
|
||||
|
||||
export default class PluginApi {
|
||||
|
||||
|
@ -197,6 +198,25 @@ export default class PluginApi {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* BdContextMenu
|
||||
*/
|
||||
|
||||
showContextMenu(event, groups) {
|
||||
BdContextMenu.show(event, groups);
|
||||
this.activeMenu.menu = BdContextMenu.activeMenu.menu;
|
||||
}
|
||||
get activeMenu() {
|
||||
return this._activeMenu || (this._activeMenu = { menu: null });
|
||||
}
|
||||
get BdContextMenu() {
|
||||
return Object.defineProperty({
|
||||
show: this.showContextMenu.bind(this)
|
||||
}, 'activeMenu', {
|
||||
get: () => this.activeMenu
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* CssUtils
|
||||
*/
|
||||
|
@ -255,16 +275,9 @@ export default class PluginApi {
|
|||
get modalStack() {
|
||||
return this._modalStack || (this._modalStack = []);
|
||||
}
|
||||
get baseModalComponent() {
|
||||
return Modals.baseComponent;
|
||||
}
|
||||
addModal(_modal, component) {
|
||||
const modal = Modals.add(_modal, component);
|
||||
modal.on('close', () => {
|
||||
let index;
|
||||
while ((index = this.modalStack.findIndex(m => m === modal)) > -1)
|
||||
this.modalStack.splice(index, 1);
|
||||
});
|
||||
modal.on('close', () => Utils.removeFromArray(this.modalStack, modal));
|
||||
this.modalStack.push(modal);
|
||||
return modal;
|
||||
}
|
||||
|
@ -300,7 +313,7 @@ export default class PluginApi {
|
|||
get: () => this.modalStack
|
||||
},
|
||||
baseComponent: {
|
||||
get: () => this.baseModalComponent
|
||||
get: () => Modals.baseComponent
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -308,6 +321,7 @@ export default class PluginApi {
|
|||
/**
|
||||
* Toasts
|
||||
*/
|
||||
|
||||
showToast(message, options = {}) {
|
||||
return Toasts.push(message, options);
|
||||
}
|
||||
|
@ -334,33 +348,122 @@ export default class PluginApi {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifications
|
||||
*/
|
||||
|
||||
get notificationStack() {
|
||||
return this._notificationStack || (this._notificationStack = []);
|
||||
}
|
||||
addNotification(title, text, buttons = []) {
|
||||
if (arguments.length <= 1) text = title, title = undefined;
|
||||
if (arguments[1] instanceof Array) [text, buttons] = arguments, title = undefined;
|
||||
|
||||
const notification = Notifications.add(title, text, buttons, () => Utils.removeFromArray(this.notificationStack, notification));
|
||||
this.notificationStack.push(notification);
|
||||
return notification;
|
||||
}
|
||||
dismissNotification(index) {
|
||||
index = Notifications.stack.indexOf(this.notificationStack[index]);
|
||||
if (index) Notifications.dismiss(index);
|
||||
}
|
||||
dismissAllNotifications() {
|
||||
for (const index in this.notificationStack) {
|
||||
this.dismissNotification(index);
|
||||
}
|
||||
}
|
||||
get Notifications() {
|
||||
return Object.defineProperty({
|
||||
add: this.addNotification.bind(this),
|
||||
dismiss: this.dismissNotification.bind(this),
|
||||
dismissAll: this.dismissAllNotifications.bind(this)
|
||||
}, 'stack', {
|
||||
get: () => this.notificationStack
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Autocomplete
|
||||
*/
|
||||
|
||||
get autocompleteSets() {
|
||||
return this._autocompleteSets || (this._autocompleteSets = new Map());
|
||||
}
|
||||
addAutocompleteController(prefix, controller) {
|
||||
if (!controller) controller = this.plugin;
|
||||
if (GlobalAc.validPrefix(prefix)) return;
|
||||
GlobalAc.add(prefix, controller);
|
||||
this.autocompleteSets.set(prefix, controller);
|
||||
}
|
||||
removeAutocompleteController(prefix) {
|
||||
if (this.autocompleteSets.get(prefix) !== GlobalAc.sets.get(prefix)) return;
|
||||
GlobalAc.remove(prefix);
|
||||
this.autocompleteSets.delete(prefix);
|
||||
}
|
||||
removeAllAutocompleteControllers() {
|
||||
for (const [prefix] of this.autocompleteSets) {
|
||||
this.removeAutocompleteController(prefix);
|
||||
}
|
||||
}
|
||||
validAutocompletePrefix(prefix) {
|
||||
return GlobalAc.validPrefix(prefix);
|
||||
}
|
||||
toggleAutocompleteMode(prefix, sterm) {
|
||||
return GlobalAc.toggle(prefix, sterm);
|
||||
}
|
||||
searchAutocomplete(prefix, sterm) {
|
||||
return GlobalAc.items(prefix, sterm);
|
||||
}
|
||||
get Autocomplete() {
|
||||
return Object.defineProperty({
|
||||
add: this.addAutocompleteController.bind(this),
|
||||
remove: this.removeAutocompleteController.bind(this),
|
||||
removeAll: this.removeAllAutocompleteControllers.bind(this),
|
||||
validPrefix: this.validAutocompletePrefix.bind(this),
|
||||
toggle: this.toggleAutocompleteMode.bind(this),
|
||||
search: this.searchAutocomplete.bind(this)
|
||||
}, 'sets', {
|
||||
get: () => this.autocompleteSets
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Emotes
|
||||
*/
|
||||
|
||||
get emotes() {
|
||||
return EmoteModule.emotes;
|
||||
return EmoteModule.database;
|
||||
}
|
||||
get favourite_emotes() {
|
||||
return EmoteModule.favourite_emotes;
|
||||
get favouriteEmotes() {
|
||||
return EmoteModule.favourites;
|
||||
}
|
||||
get mostUsedEmotes() {
|
||||
return EmoteModule.mostUsed;
|
||||
}
|
||||
setFavouriteEmote(emote, favourite) {
|
||||
return EmoteModule.setFavourite(emote, favourite);
|
||||
return EmoteModule[favourite ? 'removeFavourite' : 'addFavourite'](emote);
|
||||
}
|
||||
addFavouriteEmote(emote) {
|
||||
return EmoteModule.addFavourite(emote);
|
||||
}
|
||||
removeFavouriteEmote(emote) {
|
||||
return EmoteModule.addFavourite(emote);
|
||||
return EmoteModule.removeFavourite(emote);
|
||||
}
|
||||
isFavouriteEmote(emote) {
|
||||
return EmoteModule.isFavourite(emote);
|
||||
}
|
||||
getEmote(emote) {
|
||||
return EmoteModule.getEmote(emote);
|
||||
return EmoteModule.findByName(emote, true);
|
||||
}
|
||||
filterEmotes(regex, limit, start = 0) {
|
||||
return EmoteModule.filterEmotes(regex, limit, start);
|
||||
getEmoteUseCount(emote) {
|
||||
const mostUsed = EmoteModule.mostUsed.find(mu => mu.key === emote.name);
|
||||
return mostUsed ? mostUsed.useCount : 0;
|
||||
}
|
||||
incrementEmoteUseCount(emote) {
|
||||
return EmoteModule.addToMostUsed(emote);
|
||||
}
|
||||
searchEmotes(regex, limit) {
|
||||
return EmoteModule.search(regex, limit);
|
||||
}
|
||||
get Emotes() {
|
||||
return Object.defineProperties({
|
||||
|
@ -369,13 +472,18 @@ export default class PluginApi {
|
|||
removeFavourite: this.removeFavouriteEmote.bind(this),
|
||||
isFavourite: this.isFavouriteEmote.bind(this),
|
||||
getEmote: this.getEmote.bind(this),
|
||||
filter: this.filterEmotes.bind(this)
|
||||
getUseCount: this.getEmoteUseCount.bind(this),
|
||||
incrementUseCount: this.incrementEmoteUseCount.bind(this),
|
||||
search: this.searchEmotes.bind(this)
|
||||
}, {
|
||||
emotes: {
|
||||
get: () => this.emotes
|
||||
},
|
||||
favourite_emotes: {
|
||||
get: () => this.favourite_emotes
|
||||
favourites: {
|
||||
get: () => this.favouriteEmotes
|
||||
},
|
||||
mostused: {
|
||||
get: () => this.mostUsedEmotes
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -552,6 +660,37 @@ export default class PluginApi {
|
|||
return m => MonkeyPatch(this.plugin.id, m);
|
||||
}
|
||||
|
||||
/**
|
||||
* DiscordContextMenu
|
||||
*/
|
||||
|
||||
get discordContextMenus() {
|
||||
return this._discordContextMenus || (this._discordContextMenus = []);
|
||||
}
|
||||
addDiscordContextMenu(items, filter) {
|
||||
const menu = DiscordContextMenu.add(items, filter);
|
||||
this.discordContextMenus.push(menu);
|
||||
return menu;
|
||||
}
|
||||
removeDiscordContextMenu(menu) {
|
||||
DiscordContextMenu.remove(menu);
|
||||
Utils.removeFromArray(this.discordContextMenus, menu);
|
||||
}
|
||||
removeAllDiscordContextMenus() {
|
||||
for (const menu of this.discordContextMenus) {
|
||||
this.removeDiscordContextMenu(menu);
|
||||
}
|
||||
}
|
||||
get DiscordContextMenu() {
|
||||
return Object.defineProperty({
|
||||
add: this.addDiscordContextMenu.bind(this),
|
||||
remove: this.removeDiscordContextMenu.bind(this),
|
||||
removeAll: this.removeAllDiscordContextMenus.bind(this)
|
||||
}, 'menus', {
|
||||
get: () => this.discordContextMenus
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Stop plugins from modifying the plugin API for all plugins
|
||||
|
|
|
@ -128,6 +128,10 @@ export default class extends ContentManager {
|
|||
static get unloadPlugin() { return this.unloadContent }
|
||||
static get reloadPlugin() { return this.reloadContent }
|
||||
|
||||
static unloadContentHook(content, reload) {
|
||||
delete Globals.require.cache[Globals.require.resolve(content.paths.mainPath)];
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops a plugin.
|
||||
* @param {Plugin|String} plugin
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
*/
|
||||
|
||||
import { Toasts } from 'ui';
|
||||
import { EmoteModule } from 'builtin';
|
||||
import { SettingsSet } from 'structs';
|
||||
import { FileUtils, ClientLogger as Logger } from 'common';
|
||||
import path from 'path';
|
||||
|
@ -63,7 +62,6 @@ export default new class Settings {
|
|||
|
||||
CssEditor.setState(scss, css, css_editor_files, scss_error);
|
||||
CssEditor.editor_bounds = css_editor_bounds || {};
|
||||
EmoteModule.favourite_emotes = favourite_emotes || [];
|
||||
} catch (err) {
|
||||
// There was an error loading settings
|
||||
// This probably means that the user doesn't have any settings yet
|
||||
|
@ -87,8 +85,7 @@ export default new class Settings {
|
|||
css: CssEditor.css,
|
||||
css_editor_files: CssEditor.files,
|
||||
scss_error: CssEditor.error,
|
||||
css_editor_bounds: CssEditor.editor_bounds,
|
||||
favourite_emotes: EmoteModule.favourite_emotes
|
||||
css_editor_bounds: CssEditor.editor_bounds
|
||||
});
|
||||
|
||||
for (const set of this.settings) {
|
||||
|
|
|
@ -367,7 +367,7 @@ export class ChannelCategory extends GuildChannel {
|
|||
* A list of channels in this category.
|
||||
*/
|
||||
get channels() {
|
||||
return List.from(this.guild.channels, c => c.parentId === this.id);
|
||||
return List.from(this.guild.channels.filter(c => c.parentId === this.id));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -32,8 +32,8 @@
|
|||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
cursor: pointer;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,11 +18,9 @@
|
|||
.bd-cm {
|
||||
left: 170px;
|
||||
max-height: 270px;
|
||||
overflow-y: auto;
|
||||
contain: layout;
|
||||
flex: 1;
|
||||
min-height: 1px;
|
||||
margin-left: 170px;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
height: 8px;
|
||||
|
|
|
@ -49,6 +49,13 @@
|
|||
.bd-notificationBody {
|
||||
padding: 10px 25px;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
|
||||
.bd-notificationTitle {
|
||||
margin-bottom: 10px;
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.bd-notificationText {
|
||||
color: #fff;
|
||||
|
|
|
@ -23,8 +23,8 @@
|
|||
> .bd-scroller {
|
||||
overflow-y: scroll;
|
||||
|
||||
.platform-darwin { // sass-lint:disable-line class-name-format
|
||||
.bd-settings & {
|
||||
.bd-settings & {
|
||||
.platform-darwin & { // sass-lint:disable-line class-name-format
|
||||
padding-top: 22px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,15 +4,15 @@ import { VueInjector } from 'ui';
|
|||
import AutocompleteComponent from './components/common/Autocomplete.vue';
|
||||
import { Utils } from 'common';
|
||||
|
||||
export default new class AutoComplete {
|
||||
export default new class Autocomplete {
|
||||
|
||||
get sets() {
|
||||
return this._sets || (this._sets = {});
|
||||
return this._sets || (this._sets = new Map());
|
||||
}
|
||||
|
||||
async init() {
|
||||
this.cta = await ReactComponents.getComponent('ChannelTextArea', { selector: WebpackModules.getSelector('channelTextArea', 'emojiButton') });
|
||||
MonkeyPatch('BD:EMOTEMODULE', this.cta.component.prototype).after('render', this.channelTextAreaAfterRender.bind(this));
|
||||
MonkeyPatch('BD:Autocomplete', this.cta.component.prototype).after('render', this.channelTextAreaAfterRender.bind(this));
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
|
@ -32,26 +32,26 @@ export default new class AutoComplete {
|
|||
|
||||
add(prefix, controller) {
|
||||
if (!this.initialized) this.init();
|
||||
if (this.sets.hasOwnProperty(prefix)) return;
|
||||
this.sets[prefix] = controller;
|
||||
if (this.validPrefix(prefix)) return;
|
||||
this.sets.set(prefix, controller);
|
||||
}
|
||||
|
||||
remove(prefix) {
|
||||
if (this.sets.hasOwnProperty(prefix)) delete this.sets[prefix];
|
||||
this.sets.delete(prefix);
|
||||
}
|
||||
|
||||
validPrefix(prefix) {
|
||||
return this.sets.hasOwnProperty(prefix);
|
||||
return this.sets.has(prefix);
|
||||
}
|
||||
|
||||
toggle(prefix, sterm) {
|
||||
if (!this.sets[prefix].toggle) return false;
|
||||
return this.sets[prefix].toggle(sterm);
|
||||
toggle(prefix, sterm, event) {
|
||||
const controller = this.sets.get(prefix);
|
||||
return controller && controller.toggle && controller.toggle(sterm, event);
|
||||
}
|
||||
|
||||
items(prefix, sterm) {
|
||||
if (!this.validPrefix(prefix)) return [];
|
||||
return this.sets[prefix].acsearch(sterm);
|
||||
return this.sets.get(prefix).acsearch(sterm);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,13 +25,19 @@ export default class {
|
|||
});
|
||||
if (hideButtonSetting.value) document.body.classList.add('bd-hideButton');
|
||||
|
||||
const currentWindow = remote.getCurrentWindow();
|
||||
const windowOptions = currentWindow.__bd_options;
|
||||
|
||||
if (!windowOptions.hasOwnProperty('frame') || windowOptions.frame) document.body.classList.add('bd-windowHasFrame');
|
||||
if (windowOptions.transparent) document.body.classList.add('bd-windowIsTransparent');
|
||||
|
||||
this.pathCache = {
|
||||
isDm: null,
|
||||
server: DiscordApi.currentGuild,
|
||||
channel: DiscordApi.currentChannel
|
||||
};
|
||||
|
||||
remote.getCurrentWindow().webContents.on('did-navigate-in-page', (e, url, isMainFrame) => {
|
||||
currentWindow.webContents.on('did-navigate-in-page', (e, url, isMainFrame) => {
|
||||
const { currentGuild, currentChannel } = DiscordApi;
|
||||
|
||||
if (!this.pathCache.server)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export { ReactComponent } from './vue';
|
||||
export { ReactComponent } from './vueinjector';
|
||||
|
||||
export * from './components/common';
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
*/
|
||||
|
||||
<template>
|
||||
<div ref="root" class="bd-cm" :class="{'bd-cmRenderLeft': renderLeft}" v-if="activeMenu && activeMenu.menu" :style="calculatePosition()">
|
||||
<CMGroup v-for="(group, index) in activeMenu.menu.groups" :items="group.items" :key="index" :closeMenu="hide" :left="left" :top="top"/>
|
||||
<div class="bd-cm" :class="{'bd-cmRenderLeft': renderLeft}" v-if="activeMenu && activeMenu.menu" :style="calculatePosition()">
|
||||
<CMGroup v-for="(group, index) in activeMenu.menu.groups" :items="group.items" :key="index" :left="left" :top="top" @close="hide" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -20,6 +20,9 @@
|
|||
import CMGroup from './contextmenu/Group.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CMGroup
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activeMenu: BdContextMenu.activeMenu,
|
||||
|
@ -29,27 +32,24 @@
|
|||
renderLeft: false
|
||||
};
|
||||
},
|
||||
components: { CMGroup },
|
||||
methods: {
|
||||
calculatePosition() {
|
||||
if (!this.activeMenu.menu.groups.length) return {};
|
||||
this.mouseX = this.activeMenu.menu.x;
|
||||
this.mouseY = this.activeMenu.menu.y;
|
||||
const mouseX = this.activeMenu.menu.x;
|
||||
const mouseY = this.activeMenu.menu.y;
|
||||
const height = this.activeMenu.menu.groups.reduce((total, group) => total + group.items.length, 0) * 28;
|
||||
this.top = window.innerHeight - this.mouseY - height < 0 ? this.mouseY - height : this.mouseY;
|
||||
this.left = window.innerWidth - this.mouseX - 170 < 0 ? this.mouseX - 170 : this.mouseX;
|
||||
this.top = window.innerHeight - mouseY - height < 0 ? mouseY - height : mouseY;
|
||||
this.left = window.innerWidth - mouseX - 170 < 0 ? mouseX - 170 : mouseX;
|
||||
this.renderLeft = (this.left + 170 * 2) > window.innerWidth;
|
||||
window.addEventListener('mouseup', this.clickHide);
|
||||
window.addEventListener('mousedown', this.clickHide);
|
||||
return { top: `${this.top}px`, left: `${this.left}px` };
|
||||
},
|
||||
hide() {
|
||||
window.removeEventListener('mouseup', this.clickHide);
|
||||
window.removeEventListener('mousedown', this.clickHide);
|
||||
this.activeMenu.menu = null;
|
||||
},
|
||||
clickHide(e) {
|
||||
if (!this.$refs.root) return;
|
||||
if (this.$refs.root.contains(e.target)) return;
|
||||
this.hide();
|
||||
if (!this.$el.contains(e.target)) this.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
<div @click="this.dismissFirst" class="bd-notificationDismissBtn"><MiArrowLeft size="20"/></div>
|
||||
</div>
|
||||
<div class="bd-notificationBody bd-flex">
|
||||
<div v-if="notifications[0].title" class="bd-notificationTitle">{{notifications[0].title}}</div>
|
||||
<div class="bd-notificationText">{{notifications[0].text}}</div>
|
||||
</div>
|
||||
<div class="bd-notificationFooter bd-flex">
|
||||
|
|
|
@ -10,10 +10,10 @@
|
|||
|
||||
<template>
|
||||
<div class="bd-settingsWrapper" :class="[{'bd-active': active}, 'platform-' + this.platform]">
|
||||
<div class="bd-settingsButton" :class="{'bd-active': active, 'bd-animating': animating, 'bd-hideButton': hideButton}" @click="active = true">
|
||||
<div class="bd-settingsButton" :class="{'bd-active': active, 'bd-animating': animating, 'bd-hideButton': hideButton}" @click="active = true" v-contextmenu="buttonContextMenu">
|
||||
<div v-if="updating === 0" v-tooltip.right="'Checking for updates'" class="bd-settingsButtonBtn bd-loading"></div>
|
||||
<div v-else-if="updating === 2" v-tooltip.right="'Updates available!'" class="bd-settingsButtonBtn bd-updates"></div>
|
||||
<div v-else class="bd-settingsButtonBtn" :class="[{'bd-loading': !loaded}]"></div>
|
||||
<div v-else class="bd-settingsButtonBtn" :class="{'bd-loading': !loaded}"></div>
|
||||
</div>
|
||||
<BdSettings ref="settings" :active="active" @close="active = false" />
|
||||
</div>
|
||||
|
@ -21,7 +21,7 @@
|
|||
|
||||
<script>
|
||||
// Imports
|
||||
import { Events, Settings } from 'modules';
|
||||
import { Events, Settings, Updater } from 'modules';
|
||||
import { Modals } from 'ui';
|
||||
import process from 'process';
|
||||
import BdSettings from './BdSettings.vue';
|
||||
|
@ -38,7 +38,21 @@
|
|||
eventHandlers: {},
|
||||
keybindHandler: null,
|
||||
hideButton: false,
|
||||
hideButtonToggleHandler: null
|
||||
hideButtonToggleHandler: null,
|
||||
buttonContextMenu: [
|
||||
{
|
||||
items: [
|
||||
{
|
||||
text: 'Check for updates',
|
||||
updating: false,
|
||||
onClick(event) {
|
||||
if (this.updating === 2) Updater.update();
|
||||
else if (this.updating !== 0) Updater.checkForUpdates();
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
components: {
|
||||
|
@ -67,6 +81,12 @@
|
|||
this.animating = false;
|
||||
this.timeout = null;
|
||||
}, 400);
|
||||
},
|
||||
updating(updating) {
|
||||
const updateItem = this.buttonContextMenu[0].items[0];
|
||||
|
||||
updateItem.updating = updating;
|
||||
updateItem.text = updating === 0 ? 'Checking for updates...' : updating === 2 ? 'Install updates' : 'Check for updates';
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
|
|
@ -59,8 +59,7 @@
|
|||
filePath() {
|
||||
try {
|
||||
return Globals.require.resolve(path.join(Globals.getPath('data'), 'window'));
|
||||
}
|
||||
catch (err) {
|
||||
} catch (err) {
|
||||
FileUtils.writeJsonToFile(this.defaultFilePath, {});
|
||||
return this.defaultFilePath;
|
||||
}
|
||||
|
@ -79,11 +78,13 @@
|
|||
|
||||
if (event.category.id === 'default' && event.setting.id === 'transparent') {
|
||||
newPreferences.transparent = event.value;
|
||||
if (event.value) delete newPreferences.backgroundColor;
|
||||
if (event.value) newPreferences.backgroundColor = null;
|
||||
else if (newPreferences.backgroundColor === null) delete newPreferences.backgroundColor;
|
||||
}
|
||||
|
||||
if (event.category.id === 'default' && event.setting.id === 'background-colour') {
|
||||
newPreferences.backgroundColor = event.value;
|
||||
if (event.value) newPreferences.transparent = false;
|
||||
}
|
||||
|
||||
if (event.category.id === 'default' && event.setting.id === 'frame') {
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
*/
|
||||
|
||||
<template>
|
||||
<Modal :class="['bd-modalBasic', {'bd-modal-out': modal.closing}]" :headerText="modal.title" @close="modal.close">
|
||||
<Modal class="bd-modalBasic" :headertext="modal.title" :closing="modal.closing" @close="modal.close">
|
||||
<div slot="body" class="bd-modalBasicBody">{{ modal.text }}</div>
|
||||
<div slot="footer" class="bd-modalControls">
|
||||
<div class="bd-flexGrow"></div>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
*/
|
||||
|
||||
<template>
|
||||
<Modal :class="['bd-modalBasic', {'bd-modal-out': modal.closing}]" :headerText="modal.title" @close="modal.close">
|
||||
<Modal class="bd-modalBasic" :headertext="modal.title" :closing="modal.closing" @close="modal.close">
|
||||
<div slot="body" class="bd-modalBasicBody">{{ modal.text }}</div>
|
||||
<div slot="footer" class="bd-modalControls">
|
||||
<div class="bd-flexGrow"></div>
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<template>
|
||||
<Modal :headerText="modal.event.header" @close="modal.close"
|
||||
:class="{'bd-err': modal.event.type && modal.event.type === 'err', 'bd-modalOut': modal.closing}">
|
||||
<Modal :headertext="modal.event.header" :closing="modal.closing" @close="modal.close"
|
||||
:class="{'bd-err': modal.event.type && modal.event.type === 'err'}">
|
||||
<MiError v-if="modal.event.type === 'err'" slot="icon" size="20"/>
|
||||
<div slot="body">
|
||||
<div v-for="(content, index) in modal.event.content">
|
||||
<div class="bd-modalError" :class="{'bd-open': content.showStack}">
|
||||
<div class="bd-modalErrorTitle bd-flex">
|
||||
<span class="bd-modalTitleText bd-flexGrow">{{content.message}}</span>
|
||||
<span class="bd-modalTitlelink" v-if="content.showStack" @click="() => { content.showStack = false; $forceUpdate(); }">Hide Stacktrace</span>
|
||||
<span class="bd-modalTitlelink" v-else @click="() => { content.showStack = true; $forceUpdate(); }">Show Stacktrace</span>
|
||||
<span class="bd-modalTitlelink" v-if="content.showStack" @click="content.showStack = false; $forceUpdate();">Hide Stacktrace</span>
|
||||
<span class="bd-modalTitlelink" v-else @click="content.showStack = true; $forceUpdate();">Show Stacktrace</span>
|
||||
</div>
|
||||
<div class="bd-scrollerWrap">
|
||||
<div class="bd-scroller">
|
||||
|
|
|
@ -9,15 +9,14 @@
|
|||
*/
|
||||
|
||||
<template>
|
||||
<Modal :class="['bd-modalBasic', {'bd-modalOut': modal.closing}]" :headerText="modal.title" @close="modal.close">
|
||||
<Modal class="bd-modalBasic" :headertext="modal.title" :closing="modal.closing" @close="modal.close">
|
||||
<div slot="body" class="bd-modalBasicBody bd-inputModalBody bd-formTextinput">
|
||||
{{ modal.text }}
|
||||
<input v-if="modal.password" ref="input" type="password" @keyup.stop="keyup" />
|
||||
<input v-else ref="input" type="text" @keyup.stop="keyup"/>
|
||||
<input ref="input" :type="modal.password ? 'password' : 'text'" @keyup.stop="keyup"/>
|
||||
</div>
|
||||
<div slot="footer" class="bd-modalControls">
|
||||
<div class="bd-flexGrow"></div>
|
||||
<div class="bd-button bd-ok" @click="() => { modal.confirm(value); modal.close(); }">OK</div>
|
||||
<div class="bd-button bd-ok" @click="modal.confirm(value); modal.close();">OK</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
*/
|
||||
|
||||
<template>
|
||||
<Modal :class="['bd-modalBasic', {'bd-modalOut': modal.closing}]" :headerText="modal.title" @close="modal.close">
|
||||
<Modal class="bd-modalBasic" :headertext="modal.title" :closing="modal.closing" @close="modal.close">
|
||||
<div slot="body" class="bd-modalBasicBody">
|
||||
<div v-for="(perm, i) in permissions" :key="`perm-${i}`" class="bd-permScope">
|
||||
<div class="bd-permAllow">
|
||||
|
@ -26,7 +26,7 @@
|
|||
<div slot="footer" class="bd-modalControls">
|
||||
<div class="bd-flexGrow"></div>
|
||||
<div class="bd-button" @click="modal.close">Cancel</div>
|
||||
<div class="bd-button bd-ok" @click="() => { modal.confirm(); modal.close(); }">Authorize</div>
|
||||
<div class="bd-button bd-ok" @click="modal.confirm(); modal.close();">Authorize</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
|
|
@ -10,8 +10,9 @@
|
|||
|
||||
<template>
|
||||
<div class="bd-settingsModal" :class="{'bd-edited': changed}">
|
||||
<Modal :class="{'bd-modalOut': modal.closing}" :headerText="modal.headertext" @close="modal.close">
|
||||
<Modal :headertext="modal.headertext" :closing="modal.closing" @close="modal.close">
|
||||
<SettingsPanel :settings="settings" :schemes="modal.schemes" slot="body" class="bd-settingsModalBody" />
|
||||
|
||||
<div slot="footer" class="bd-footerAlert" :class="{'bd-active': changed || saving, 'bd-warn': warnclose}" :style="{pointerEvents: changed ? 'all' : 'none'}">
|
||||
<div class="bd-footerAlertText">Unsaved changes</div>
|
||||
<div class="bd-button bd-resetButton bd-tp" :class="{'bd-disabled': saving}" @click="resetSettings">Reset</div>
|
||||
|
|
|
@ -132,7 +132,7 @@
|
|||
const { selectionEnd, value } = e.target;
|
||||
const sterm = value.slice(0, selectionEnd).split(/\s+/g).pop();
|
||||
const prefix = sterm.slice(0, 1);
|
||||
return this.controller.toggle(prefix, sterm);
|
||||
return this.controller.toggle(prefix, sterm, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,4 +20,5 @@ export { default as MiSuccess } from './materialicons/Success.vue';
|
|||
export { default as AccountCircle } from './materialicons/AccountCircle.vue';
|
||||
export { default as MiLock } from './materialicons/Lock.vue';
|
||||
export { default as MiImagePlus } from './materialicons/ImagePlus.vue';
|
||||
export { default as MiIcVpnKey } from './materialicons/IcVpnKey.vue';
|
||||
export { default as MiArrowLeft } from './materialicons/ArrowLeft.vue';
|
||||
|
|
|
@ -9,25 +9,27 @@
|
|||
*/
|
||||
|
||||
<template>
|
||||
<div :class="['bd-modal', {'bd-modalScrolled': scrolled}]">
|
||||
<div :class="['bd-modal', {'bd-modalOut': closing, 'bd-modalScrolled': scrolled}]">
|
||||
<div class="bd-modalInner">
|
||||
<div class="bd-modalHeader">
|
||||
<div class="bd-modalIcon">
|
||||
<slot name="icon" />
|
||||
</div>
|
||||
<span class="bd-modalHeadertext">{{ headerText }}</span>
|
||||
<slot name="header">
|
||||
<div v-if="$slots.icon" class="bd-modalIcon">
|
||||
<slot name="icon" />
|
||||
</div>
|
||||
<span class="bd-modalHeadertext">{{ headertext }}</span>
|
||||
</slot>
|
||||
<div class="bd-modalX" @click="$emit('close', $event.shiftKey, $event)">
|
||||
<MiClose size="18" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="bd-modalBody">
|
||||
<div class="bd-scrollerWrap">
|
||||
<div class="bd-scroller" @scroll="e => scrolled = e.target.scrollTop !== 0">
|
||||
<div class="bd-scroller" @scroll="e => scrolled = e.target.scrollTop > 0">
|
||||
<slot name="body"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bd-modalFooter">
|
||||
<div v-if="$slots.footer" class="bd-modalFooter">
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -39,7 +41,7 @@
|
|||
import { MiClose } from './MaterialIcon';
|
||||
|
||||
export default {
|
||||
props: ['headerText'],
|
||||
props: ['headertext', 'closing'],
|
||||
components: {
|
||||
MiClose
|
||||
},
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* BetterDiscord Material Design Icon
|
||||
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
|
||||
* All rights reserved.
|
||||
* https://betterdiscord.net
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* Material Design Icons
|
||||
* Copyright (c) 2014 Google
|
||||
* Apache 2.0 LICENSE
|
||||
* https://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
*/
|
||||
|
||||
<template>
|
||||
<span class="bd-materialDesignIcon">
|
||||
<svg :width="size || 24" :height="size || 24" viewBox="0 0 24 24">
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M12.65 10C11.83 7.67 9.61 6 7 6c-3.31 0-6 2.69-6 6s2.69 6 6 6c2.61 0 4.83-1.67 5.65-4H17v4h4v-4h2v-4H12.65zM7 14c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z"/>
|
||||
</svg>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['size']
|
||||
}
|
||||
</script>
|
|
@ -9,7 +9,7 @@
|
|||
*/
|
||||
|
||||
<template>
|
||||
<div class="bd-cmItem" :style="{color: item.color || ''}" @click="onClick">
|
||||
<div class="bd-cmItem" :style="{color: item.color || ''}" @click="$emit('click', $event)">
|
||||
<span>{{item.text}}</span>
|
||||
<div class="bd-cmHint" v-if="item.hint">{{item.hint}}</div>
|
||||
<img :src="item.icon" v-else-if="item.icon"/>
|
||||
|
@ -18,6 +18,6 @@
|
|||
|
||||
<script>
|
||||
export default {
|
||||
props: ['item', 'onClick']
|
||||
props: ['item']
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -9,20 +9,18 @@
|
|||
*/
|
||||
|
||||
<template>
|
||||
<div class="bd-cmGroup" ref="test">
|
||||
<div class="bd-cmGroup">
|
||||
<template v-for="(item, index) in items">
|
||||
<CMButton v-if="!item.type || item.type === 'button'" :item="item" :onClick="() => { item.onClick(); closeMenu(); }" />
|
||||
<CMToggle v-else-if="item.type === 'toggle'" :item="item" :checked="item.checked" :onClick="() => { item.checked = item.onChange(!item.checked, target) }" />
|
||||
<div v-else-if="item.type === 'sub'" class="bd-cmItem bd-cmSub" @mouseenter="e => subMenuMouseEnter(e, index, item)" @mouseleave="e => subMenuMouseLeave(e, index, item)">
|
||||
<div v-if="item.type === 'sub'" class="bd-cmItem bd-cmSub" @mouseenter="subMenuMouseEnter($event, index, item)" @mouseleave="subMenuMouseLeave($event, index, item)">
|
||||
{{item.text}}
|
||||
<MiChevronDown />
|
||||
<div class="bd-cm" v-if="index === visibleSub" :style="subStyle">
|
||||
<template v-for="(item, index) in item.items">
|
||||
<CMButton v-if="!item.type || item.type === 'button'" :item="item" :onClick="() => { item.onClick(); closeMenu(); }" />
|
||||
<CMToggle v-else-if="item.type === 'toggle'" :item="item" :checked="item.checked" :onClick="() => { item.checked = item.onChange(!item.checked, target) }" />
|
||||
</template>
|
||||
<div v-if="index === visibleSub" :class="['bd-cm', {'bd-cmRenderLeft': subRenderLeft}]" :style="subStyle">
|
||||
<CMGroup :items="item.items" :top="subTop" :left="subLeft" @close="$emit('close')" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CMToggle v-else-if="item.type === 'toggle'" :item="item" :checked="item.checked" @click="item.checked = item.onChange(!item.checked, target)" />
|
||||
<CMButton v-else :item="item" @click="item.onClick ? item.onClick($event) : undefined; item.type === 'button' ? $emit('close') : undefined" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -34,21 +32,29 @@
|
|||
import { MiChevronDown } from '../common';
|
||||
|
||||
export default {
|
||||
name: 'CMGroup',
|
||||
components: {
|
||||
CMButton, CMToggle, MiChevronDown
|
||||
},
|
||||
props: ['items', 'left', 'top', 'target'],
|
||||
data() {
|
||||
return {
|
||||
visibleSub: -1,
|
||||
subStyle: {}
|
||||
}
|
||||
subStyle: {},
|
||||
subTop: 0,
|
||||
subLeft: 0,
|
||||
subRenderLeft: false
|
||||
};
|
||||
},
|
||||
props: ['items', 'closeMenu', 'left', 'top', 'target'],
|
||||
components: { CMButton, CMToggle, MiChevronDown },
|
||||
methods: {
|
||||
subMenuMouseEnter(e, index, sub) {
|
||||
const subHeight = sub.items.length > 9 ? 270 : sub.items.length * e.target.offsetHeight;
|
||||
const top = this.top + subHeight + e.target.offsetTop > window.innerHeight ?
|
||||
this.subTop = this.top + subHeight + e.target.offsetTop > window.innerHeight ?
|
||||
this.top - subHeight + e.target.offsetTop + e.target.offsetHeight :
|
||||
this.top + e.target.offsetTop;
|
||||
this.subStyle = { top: `${top}px`, left: `${this.left}px` };
|
||||
this.subRenderLeft = (this.left + 170 * 2) > window.innerWidth;
|
||||
this.subLeft = this.left + (!this.subRenderLeft ? e.target.clientWidth : 0);
|
||||
this.subStyle = { top: `${this.subTop - 2}px`, left: `${this.subLeft}px` };
|
||||
this.visibleSub = index;
|
||||
},
|
||||
subMenuMouseLeave(e, index, sub) {
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
*/
|
||||
|
||||
<template>
|
||||
<div class="bd-cmItem bd-cmToggle" @click="onClick">
|
||||
<div class="bd-cmItem bd-cmToggle" @click="$emit('click', $event)">
|
||||
<div class="bd-cmLabel">{{item.text}}</div>
|
||||
<div class="bd-cmCheckbox" :checked="checked">
|
||||
<div class="bd-cmCheckboxInner">
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { Utils, ClientLogger as Logger } from 'common';
|
||||
import { ReactComponents, WebpackModules, MonkeyPatch } from 'modules';
|
||||
import { VueInjector, Toasts } from 'ui';
|
||||
import CMGroup from './components/contextmenu/Group.vue';
|
||||
|
@ -17,7 +18,7 @@ export class BdContextMenu {
|
|||
/**
|
||||
* Show a context menu
|
||||
* @param {MouseEvent|Object} e MouseEvent or Object { x: 0, y: 0 }
|
||||
* @param {Object[]} grops Groups of items to show in context menu
|
||||
* @param {Object[]} groups Groups of items to show in context menu
|
||||
*/
|
||||
static show(e, groups) {
|
||||
const x = e.x || e.clientX;
|
||||
|
@ -29,6 +30,17 @@ export class BdContextMenu {
|
|||
return this._activeMenu || (this._activeMenu = { menu: null });
|
||||
}
|
||||
|
||||
static install(Vue) {
|
||||
Vue.directive('contextmenu', {
|
||||
bind(el, binding) {
|
||||
el.addEventListener('contextmenu', event => {
|
||||
Logger.log('BdContextMenu', ['Showing context menu', event, el, binding]);
|
||||
BdContextMenu.show(event, binding.value);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class DiscordContextMenu {
|
||||
|
@ -39,14 +51,15 @@ export class DiscordContextMenu {
|
|||
* @param {any} items items to add
|
||||
* @param {Function} [filter] filter function for target filtering
|
||||
*/
|
||||
static add(id, items, filter) {
|
||||
static add(items, filter) {
|
||||
if (!this.patched) this.patch();
|
||||
this.menus.push({ id, items, filter });
|
||||
return () => this.remove(id);
|
||||
const menu = { items, filter };
|
||||
this.menus.push(menu);
|
||||
return menu;
|
||||
}
|
||||
|
||||
static remove(id) {
|
||||
this._menus = this._menus.filter(menu => menu.id !== id);
|
||||
static remove(menu) {
|
||||
Utils.removeFromArray(this.menus, menu);
|
||||
}
|
||||
|
||||
static get menus() {
|
||||
|
@ -58,8 +71,8 @@ export class DiscordContextMenu {
|
|||
this.patched = true;
|
||||
const self = this;
|
||||
MonkeyPatch('BD:DiscordCMOCM', WebpackModules.getModuleByProps(['openContextMenu'])).instead('openContextMenu', (_, [e, fn], originalFn) => {
|
||||
const overrideFn = function (...args) {
|
||||
const res = fn(...args);
|
||||
const overrideFn = function () {
|
||||
const res = fn.apply(this, arguments);
|
||||
if (!res.hasOwnProperty('type')) return res;
|
||||
if (!res.type.prototype || !res.type.prototype.render || res.type.prototype.render.__patched) return res;
|
||||
MonkeyPatch('BD:DiscordCMRender', res.type.prototype).after('render', (c, a, r) => self.renderCm(c, a, r, res));
|
||||
|
@ -78,7 +91,7 @@ export class DiscordContextMenu {
|
|||
if (!retVal.props.children) return;
|
||||
if (!(retVal.props.children instanceof Array)) retVal.props.children = [retVal.props.children];
|
||||
|
||||
for (const menu of this.menus.filter(menu => { if (!menu.filter) return true; return menu.filter(target)})) {
|
||||
for (const menu of this.menus.filter(menu => !menu.filter || menu.filter(target))) {
|
||||
retVal.props.children.push(VueInjector.createReactElement(CMGroup, {
|
||||
target,
|
||||
top,
|
||||
|
|
|
@ -15,11 +15,17 @@ export default class Notifications {
|
|||
/**
|
||||
* Add a new notification to the stack.
|
||||
* Notifications should only be used for important things.
|
||||
* @param {String} [title]
|
||||
* @param {String} text
|
||||
* @param {Object} [buttons] buttons to show { text: 'Text for the button', onClick: fn() { return true if notification should be dismissed } }
|
||||
* @param {Object[]} [buttons] buttons to show { text: 'Text for the button', onClick: fn() { return true if notification should be dismissed } }
|
||||
*/
|
||||
static add(text, buttons = []) {
|
||||
this.stack.push({ text, buttons });
|
||||
static add(title, text, buttons = [], ondismiss) {
|
||||
if (arguments.length <= 1) text = title, title = undefined;
|
||||
if (arguments[1] instanceof Array) [text, buttons, ondismiss] = arguments, title = undefined;
|
||||
|
||||
const notification = { title, text, buttons, ondismiss };
|
||||
this.stack.push(notification);
|
||||
return notification;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -35,6 +41,9 @@ export default class Notifications {
|
|||
* @param {Number} index Index of the notification
|
||||
*/
|
||||
static dismiss(index) {
|
||||
const notification = this.stack[index];
|
||||
if (!notification) return;
|
||||
if (notification.ondismiss) notification.ondismiss();
|
||||
this.stack.splice(index, 1);
|
||||
}
|
||||
|
||||
|
|
|
@ -8,10 +8,11 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { WebpackModules } from 'modules';
|
||||
import Vue from 'vue';
|
||||
import VTooltip from 'v-tooltip';
|
||||
import DOM from './dom';
|
||||
import VueInjector from './vueinjector';
|
||||
import { BdContextMenu } from './contextmenus';
|
||||
|
||||
Vue.use(VTooltip, {
|
||||
defaultContainer: 'bd-tooltips',
|
||||
|
@ -45,22 +46,7 @@ Vue.use(VTooltip, {
|
|||
}
|
||||
});
|
||||
|
||||
export const ReactComponent = {
|
||||
props: ['component', 'component-props', 'component-children', 'react-element'],
|
||||
render(createElement) {
|
||||
return createElement('div');
|
||||
},
|
||||
mounted() {
|
||||
const { React, ReactDOM } = WebpackModules;
|
||||
|
||||
ReactDOM.unmountComponentAtNode(this.$el);
|
||||
ReactDOM.render(this.reactElement || React.createElement(this.component, this.componentProps, ...(this.componentChildren || [])), this.$el);
|
||||
},
|
||||
beforeDestroy() {
|
||||
WebpackModules.ReactDOM.unmountComponentAtNode(this.$el);
|
||||
}
|
||||
};
|
||||
|
||||
Vue.component('ReactComponent', ReactComponent);
|
||||
Vue.use(VueInjector);
|
||||
Vue.use(BdContextMenu);
|
||||
|
||||
export default Vue;
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
*/
|
||||
|
||||
import { WebpackModules } from 'modules';
|
||||
import Vue from './vue';
|
||||
import Vue from 'vue';
|
||||
|
||||
export default class {
|
||||
|
||||
|
@ -93,4 +93,24 @@ export default class {
|
|||
}
|
||||
}
|
||||
|
||||
static install(Vue) {
|
||||
Vue.component('ReactComponent', ReactComponent);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const ReactComponent = {
|
||||
props: ['component', 'component-props', 'component-children', 'react-element'],
|
||||
render(createElement) {
|
||||
return createElement('div');
|
||||
},
|
||||
mounted() {
|
||||
const { React, ReactDOM } = WebpackModules;
|
||||
|
||||
ReactDOM.unmountComponentAtNode(this.$el);
|
||||
ReactDOM.render(this.reactElement || React.createElement(this.component, this.componentProps, ...(this.componentChildren || [])), this.$el);
|
||||
},
|
||||
beforeDestroy() {
|
||||
WebpackModules.ReactDOM.unmountComponentAtNode(this.$el);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -132,16 +132,21 @@ export class Utils {
|
|||
* @param {Function} exclude A function to filter objects that shouldn't be cloned
|
||||
* @return {Any} The cloned value
|
||||
*/
|
||||
static deepclone(value, exclude) {
|
||||
static deepclone(value, exclude, cloned) {
|
||||
if (exclude && exclude(value)) return value;
|
||||
|
||||
if (typeof value === 'object') {
|
||||
if (value instanceof Array) return value.map(i => this.deepclone(i, exclude));
|
||||
if (!cloned) cloned = new WeakMap();
|
||||
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (value instanceof Array) return value.map(i => this.deepclone(i, exclude, cloned));
|
||||
|
||||
if (cloned.has(value)) return cloned.get(value);
|
||||
|
||||
const clone = Object.assign({}, value);
|
||||
cloned.set(value, clone);
|
||||
|
||||
for (const key in clone) {
|
||||
clone[key] = this.deepclone(clone[key], exclude);
|
||||
clone[key] = this.deepclone(clone[key], exclude, cloned);
|
||||
}
|
||||
|
||||
return clone;
|
||||
|
@ -159,6 +164,8 @@ export class Utils {
|
|||
if (exclude && exclude(object)) return;
|
||||
|
||||
if (typeof object === 'object' && object !== null) {
|
||||
if (Object.isFrozen(object)) return object;
|
||||
|
||||
const properties = Object.getOwnPropertyNames(object);
|
||||
|
||||
for (const property of properties) {
|
||||
|
@ -177,9 +184,9 @@ export class Utils {
|
|||
* @param {Any} item The item to remove from the array
|
||||
* @return {Array}
|
||||
*/
|
||||
static removeFromArray(array, item) {
|
||||
static removeFromArray(array, item, filter) {
|
||||
let index;
|
||||
while ((index = array.indexOf(item)) > -1)
|
||||
while ((index = filter ? array.findIndex(item) : array.indexOf(item)) > -1)
|
||||
array.splice(index, 1);
|
||||
return array;
|
||||
}
|
||||
|
@ -496,30 +503,52 @@ export class FileUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Delete a directory
|
||||
* Deletes a file.
|
||||
* @param {String} path The file's path
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async deleteFile(path) {
|
||||
await this.fileExists(path);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.unlink(path, (err, files) => {
|
||||
if (err) reject(err);
|
||||
else resolve(files);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a directory.
|
||||
* @param {String} path The directory's path
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async deleteDirectory(pathToDir) {
|
||||
try {
|
||||
await this.directoryExists(pathToDir);
|
||||
const files = await this.listDirectory(pathToDir);
|
||||
static async deleteDirectory(path) {
|
||||
await this.directoryExists(path);
|
||||
|
||||
for (const file of files) {
|
||||
const pathToFile = path.join(pathToDir, file);
|
||||
try {
|
||||
await this.directoryExists(pathToFile);
|
||||
await this.deleteDirectory(pathToFile);
|
||||
} catch (err) {
|
||||
fs.unlinkSync(pathToFile);
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.rmdir(path, (err, files) => {
|
||||
if (err) reject(err);
|
||||
else resolve(files);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a directory and it's contents.
|
||||
* @param {String} path The directory's path
|
||||
* @return {Promise}
|
||||
*/
|
||||
static async recursiveDeleteDirectory(pathToDir) {
|
||||
for (const file of await this.listDirectory(pathToDir)) {
|
||||
const pathToFile = path.join(pathToDir, file);
|
||||
try {
|
||||
await this.recursiveDeleteDirectory(pathToFile);
|
||||
} catch (err) {
|
||||
await this.deleteFile(pathToFile);
|
||||
}
|
||||
|
||||
fs.rmdirSync(pathToDir);
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
await this.deleteDirectory(pathToDir);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import gulp from 'gulp';
|
||||
import pump from 'pump';
|
||||
import babel from 'gulp-babel';
|
||||
import watch from 'gulp-watch';
|
||||
|
||||
gulp.task('build', function () {
|
||||
return pump([
|
||||
|
@ -12,9 +11,5 @@ gulp.task('build', function () {
|
|||
});
|
||||
|
||||
gulp.task('watch', function () {
|
||||
return pump([
|
||||
watch('src/**/*.js'),
|
||||
babel(),
|
||||
gulp.dest('dist')
|
||||
]);
|
||||
return gulp.watch('src/**/*.js', gulp.series('build'));
|
||||
});
|
||||
|
|
|
@ -74,7 +74,7 @@ class PatchedBrowserWindow extends BrowserWindow {
|
|||
|
||||
super(options);
|
||||
|
||||
this.__bd_preload = [];
|
||||
Object.defineProperty(this, '__bd_preload', {value: []});
|
||||
|
||||
if (originalOptions.webPreferences && originalOptions.webPreferences.preload) {
|
||||
this.__bd_preload.push(originalOptions.webPreferences.preload);
|
||||
|
@ -82,6 +82,11 @@ class PatchedBrowserWindow extends BrowserWindow {
|
|||
if (userOptions.webPreferences && userOptions.webPreferences.preload) {
|
||||
this.__bd_preload.push(path.resolve(_dataPath, userOptions.webPreferences.preload));
|
||||
}
|
||||
|
||||
Object.defineProperty(this, '__bd_options', {value: options});
|
||||
Object.freeze(options);
|
||||
Object.freeze(options.webPreferences);
|
||||
Object.freeze(this.__bd_preload);
|
||||
}
|
||||
|
||||
static get userWindowPreferences() {
|
||||
|
|
|
@ -17,6 +17,8 @@ import electron, { ipcRenderer } from 'electron';
|
|||
|
||||
console.log('[BetterDiscord|Sparkplug]');
|
||||
|
||||
electron.webFrame.registerURLSchemeAsPrivileged('chrome-extension');
|
||||
|
||||
const currentWindow = electron.remote.getCurrentWindow();
|
||||
|
||||
if (currentWindow.__bd_preload) {
|
||||
|
|
|
@ -1,20 +1,29 @@
|
|||
exports.main = (Plugin, { Logger, Settings, Modals, BdMenu: { BdMenuItems }, CommonComponents, Api }) => class extends Plugin {
|
||||
exports.main = (Plugin, { Logger, Settings, Modals, BdMenu: { BdMenuItems }, CommonComponents, DiscordContextMenu, Autocomplete, Notifications, Api }) => class extends Plugin {
|
||||
async onstart() {
|
||||
this.keybindEvent = this.keybindEvent.bind(this);
|
||||
|
||||
// Some array event examples
|
||||
/**
|
||||
* Array setting events.
|
||||
*/
|
||||
|
||||
const arraySetting = this.settings.getSetting('default', 'array-1');
|
||||
Logger.log('Array setting', arraySetting);
|
||||
arraySetting.on('item-added', event => Logger.log('Item', event.item, 'was added to the array setting'));
|
||||
arraySetting.on('item-updated', event => Logger.log('Item', event.item, 'of the array setting was updated', event));
|
||||
arraySetting.on('item-removed', event => Logger.log('Item', event.item, 'removed from the array setting'));
|
||||
|
||||
// Keybind setting examples
|
||||
/**
|
||||
* Keybind setting events.
|
||||
*/
|
||||
|
||||
const keybindSetting = this.settings.getSetting('default', 'keybind-1');
|
||||
Logger.log('Keybind setting', keybindSetting);
|
||||
keybindSetting.on('keybind-activated', this.keybindEvent);
|
||||
|
||||
// Create a new settings set and add it to the menu
|
||||
/**
|
||||
* Settings.
|
||||
*/
|
||||
|
||||
const set = Settings.createSet({
|
||||
text: this.name
|
||||
});
|
||||
|
@ -41,7 +50,7 @@ exports.main = (Plugin, { Logger, Settings, Modals, BdMenu: { BdMenuItems }, Com
|
|||
Logger.log('Updated settings', updatedSettings);
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
set.setSaved();
|
||||
})
|
||||
});
|
||||
|
||||
const setting2 = await category.addSetting({
|
||||
id: 'setting-2',
|
||||
|
@ -52,6 +61,10 @@ exports.main = (Plugin, { Logger, Settings, Modals, BdMenu: { BdMenuItems }, Com
|
|||
|
||||
setting2.on('setting-updated', event => Logger.log('Setting 2 was changed to', event.value));
|
||||
|
||||
/**
|
||||
* Menu items.
|
||||
*/
|
||||
|
||||
this.menuItem = BdMenuItems.addSettingsSet('Plugins', set, 'Plugin 4');
|
||||
|
||||
this.menuItem2 = BdMenuItems.addVueComponent('Plugins', 'Also Plugin 4', {
|
||||
|
@ -65,6 +78,32 @@ exports.main = (Plugin, { Logger, Settings, Modals, BdMenu: { BdMenuItems }, Com
|
|||
Api, plugin: Api.plugin
|
||||
}; }
|
||||
});
|
||||
|
||||
/**
|
||||
* Discord context menus.
|
||||
*/
|
||||
|
||||
this.contextMenu = DiscordContextMenu.add([
|
||||
{
|
||||
text: 'Test',
|
||||
onClick: () => Modals.basic('Test', 'Hello from Plugin 4')
|
||||
}
|
||||
]);
|
||||
|
||||
/**
|
||||
* Autocomplete.
|
||||
* This calls `acsearch` on the controller (the plugin object). You can add multiple autocomplete sets by passing another controller.
|
||||
*/
|
||||
|
||||
Autocomplete.add('|');
|
||||
|
||||
/**
|
||||
* Notifications.
|
||||
*/
|
||||
|
||||
Notifications.add('Notification from Plugin 4', [
|
||||
{text: 'Dismiss', onClick: () => true}
|
||||
]);
|
||||
}
|
||||
|
||||
onstop() {
|
||||
|
@ -72,10 +111,34 @@ exports.main = (Plugin, { Logger, Settings, Modals, BdMenu: { BdMenuItems }, Com
|
|||
keybindSetting.off('keybind-activated', this.keybindEvent);
|
||||
|
||||
BdMenuItems.removeAll();
|
||||
DiscordContextMenu.removeAll();
|
||||
Autocomplete.removeAll();
|
||||
}
|
||||
|
||||
keybindEvent(event) {
|
||||
Logger.log('Keybind pressed', event);
|
||||
Modals.basic('Example Plugin 4', 'Test keybind activated.');
|
||||
}
|
||||
|
||||
acsearch(sterm) {
|
||||
// sterm is the text after the prefix
|
||||
Logger.log('Searching for', sterm);
|
||||
|
||||
return {
|
||||
title: ['Plugin 4 autocomplete'],
|
||||
items: [
|
||||
{key: 'Item 1', value: {replaceWith: 'Something to insert when selected'}},
|
||||
{key: 'Item 2', value: {replaceWith: 'Something to insert when selected'}},
|
||||
{key: 'Item 3', value: {replaceWith: 'Something to insert when selected'}},
|
||||
{key: 'Item 4', value: {replaceWith: 'Something to insert when selected'}}
|
||||
]
|
||||
|
||||
// `title` can also be an array - the second item will be white
|
||||
// You can also add `type: 'imagetext'` here and add an `src` property to each item's value to show an image
|
||||
};
|
||||
}
|
||||
|
||||
get api() {
|
||||
return Api;
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue