commit
30716b57e6
|
@ -12,6 +12,7 @@ import { EmoteModule } from 'builtin';
|
|||
import { SettingsSet, SettingsCategory, Setting, SettingsScheme } from 'structs';
|
||||
import { BdMenu, Modals, DOM, DOMObserver, VueInjector, Toasts, Notifications, BdContextMenu, DiscordContextMenu } from 'ui';
|
||||
import * as CommonComponents from 'commoncomponents';
|
||||
import { default as Components } from '../ui/components/generic';
|
||||
import { Utils, Filters, ClientLogger as Logger, ClientIPC, AsyncEventEmitter } from 'common';
|
||||
import Settings from './settings';
|
||||
import ExtModuleManager from './extmodulemanager';
|
||||
|
@ -24,6 +25,9 @@ import DiscordApi from './discordapi';
|
|||
import { ReactComponents, ReactHelpers } from './reactcomponents';
|
||||
import { Patcher, MonkeyPatch } from './patcher';
|
||||
import GlobalAc from '../ui/autocomplete';
|
||||
import Vue from 'vue';
|
||||
import path from 'path';
|
||||
import Globals from './globals';
|
||||
|
||||
export default class PluginApi {
|
||||
|
||||
|
@ -61,6 +65,7 @@ export default class PluginApi {
|
|||
get EventsWrapper() { return EventsWrapper }
|
||||
|
||||
get CommonComponents() { return CommonComponents }
|
||||
get Components() { return Components }
|
||||
get Filters() { return Filters }
|
||||
get Discord() { return DiscordApi }
|
||||
get DiscordApi() { return DiscordApi }
|
||||
|
@ -105,7 +110,9 @@ export default class PluginApi {
|
|||
removeFromArray: (...args) => Utils.removeFromArray.apply(Utils, args),
|
||||
defineSoftGetter: (...args) => Utils.defineSoftGetter.apply(Utils, args),
|
||||
wait: (...args) => Utils.wait.apply(Utils, args),
|
||||
until: (...args) => Utils.until.apply(Utils, args)
|
||||
until: (...args) => Utils.until.apply(Utils, args),
|
||||
findInTree: (...args) => Utils.findInTree.apply(Utils, args),
|
||||
findInReactTree: (...args) => Utils.findInReactTree.apply(Utils, args)
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -605,6 +612,10 @@ export default class PluginApi {
|
|||
});
|
||||
}
|
||||
|
||||
Vuewrap(id, component, props) {
|
||||
return VueInjector.createReactElement(Vue.component(id, component), props);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Stop plugins from modifying the plugin API for all plugins
|
||||
|
|
|
@ -130,6 +130,12 @@ export default class extends ContentManager {
|
|||
|
||||
static unloadContentHook(content, reload) {
|
||||
delete Globals.require.cache[Globals.require.resolve(content.paths.mainPath)];
|
||||
const uncache = [];
|
||||
for (const required in Globals.require.cache) {
|
||||
if (!required.includes(content.paths.contentPath)) continue;
|
||||
uncache.push(Globals.require.resolve(required));
|
||||
}
|
||||
for (const u of uncache) delete Globals.require.cache[u];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* BetterDiscord Generic Button Component
|
||||
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
|
||||
* All rights reserved.
|
||||
* https://betterdiscord.net
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
<template>
|
||||
<div class="bd-button" :class="classes" @click="onClick">
|
||||
{{text}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['classes', 'text', 'onClick']
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* BetterDiscord Generic Button Group Component
|
||||
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
|
||||
* All rights reserved.
|
||||
* https://betterdiscord.net
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
<template>
|
||||
<div class="bd-buttonGroup" :class="classes">
|
||||
<Button v-for="(button, index) in buttons" :text="button.text" :classes="button.classes" :onClick="button.onClick" :key="index"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Button from './Button.vue';
|
||||
export default {
|
||||
props: ['buttons', 'classes'],
|
||||
components: { Button }
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,29 @@
|
|||
import VrWrapper from '../../vrwrapper';
|
||||
|
||||
import ButtonGroupComponent from './ButtonGroup.vue';
|
||||
class ButtonGroupWrapper extends VrWrapper {
|
||||
get component() { return ButtonGroupComponent }
|
||||
constructor(props) {
|
||||
super();
|
||||
this.props = props;
|
||||
}
|
||||
}
|
||||
|
||||
import ButtonComponent from './Button.vue';
|
||||
class ButtonWrapper extends VrWrapper {
|
||||
get component() { return ButtonComponent }
|
||||
constructor(props) {
|
||||
super();
|
||||
this.props = props;
|
||||
}
|
||||
}
|
||||
|
||||
export default class {
|
||||
static Button(props) {
|
||||
return new ButtonWrapper(props);
|
||||
}
|
||||
|
||||
static ButtonGroup(props) {
|
||||
return new ButtonGroupWrapper(props);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
module.exports = (React, props) => {
|
||||
return React.createElement(
|
||||
'button',
|
||||
{ className: 'exampleCustomElement', onClick: props.onClick },
|
||||
'r'
|
||||
);
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
module.exports = (VueWrap, props) => {
|
||||
return VueWrap('somecomponent', {
|
||||
render: function (createElement) {
|
||||
return createElement('button', {
|
||||
class: 'exampleCustomElement',
|
||||
on: {
|
||||
click: this.onClick
|
||||
}
|
||||
}, 'v');
|
||||
},
|
||||
props: ['onClick']
|
||||
}, props);
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"info": {
|
||||
"id": "render-example",
|
||||
"name": "Render Example",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jiiks",
|
||||
"discord_id": "81388395867156480",
|
||||
"github_username": "Jiiks",
|
||||
"twitter_username": "Jiiksi"
|
||||
}
|
||||
],
|
||||
"version": 1.0,
|
||||
"description": "Example for rendering stuff"
|
||||
},
|
||||
"main": "index.js"
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
/**
|
||||
* This is an example of how you should add custom elements instead of manipulating the DOM directly
|
||||
*/
|
||||
|
||||
// Import custom components
|
||||
const customVueComponent = require('./components/vuecomponent');
|
||||
const customReactComponent = require('./components/reactcomponent');
|
||||
|
||||
module.exports = (Plugin, Api, Vendor) => {
|
||||
|
||||
// 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 });
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
Loading…
Reference in New Issue