Compare commits
301 Commits
Author | SHA1 | Date |
---|---|---|
Alexei Stukov | 835cc3134c | |
Jiiks | 02b30153ee | |
Alexei Stukov | d09dc5a2a5 | |
Mega-Mewthree | 98d9a30027 | |
Alexei Stukov | 5cb4bc15bd | |
Alexei Stukov | bb2aba04d5 | |
Samuel Elliott | 82e9b0bd6a | |
Samuel Elliott | 47575d3449 | |
Samuel Elliott | 32e2582ded | |
Samuel Elliott | d58dda6f50 | |
Samuel Elliott | 648954d533 | |
Samuel Elliott | 4aa38f4582 | |
Samuel Elliott | b3ba1aef13 | |
Samuel Elliott | 2a6cbd39b7 | |
Samuel Elliott | ce5bcb9b85 | |
Samuel Elliott | 5a3821ad3e | |
Samuel Elliott | fd0032b24c | |
Samuel Elliott | 226719b36e | |
Samuel Elliott | fcfee53928 | |
Samuel Elliott | 285ae34b50 | |
Alexei Stukov | a170a97688 | |
Samuel Elliott | a770f57b28 | |
Samuel Elliott | 5757fc20c9 | |
Samuel Elliott | 421289f63b | |
Samuel Elliott | ac85316578 | |
Samuel Elliott | 08af9be061 | |
Samuel Elliott | ead0fbbd1e | |
Samuel Elliott | dc85a808f8 | |
Samuel Elliott | aecfa814f9 | |
Samuel Elliott | 436f3d3c36 | |
Alexei Stukov | 68a8187964 | |
Alexei Stukov | f30e4c12fe | |
Alexei Stukov | 686514ed1d | |
Alexei Stukov | d795da1750 | |
Alexei Stukov | 3219ff7c6e | |
Alexei Stukov | 33567a2cfd | |
Jiiks | 85310bfbff | |
Jiiks | d1d79a37b7 | |
Jiiks | d95592acc9 | |
Jiiks | f6a3fb65da | |
Alexei Stukov | 6788cca363 | |
Lars van der Zande | 1fb442e096 | |
Jiiks | 6e64ff61c5 | |
Jiiks | dd8fe68a11 | |
Jiiks | a3829089f9 | |
Jiiks | 174c1ee791 | |
Jiiks | b8793fd2b6 | |
Jiiks | 31986ca3a0 | |
Jiiks | 10ff740f75 | |
Jiiks | 83fbab63c0 | |
Jiiks | c4670946e6 | |
Jiiks | e5239d952e | |
Jiiks | a57783a9d8 | |
Jiiks | 001a6e4fda | |
Jiiks | dd621038f9 | |
Jiiks | 288c233447 | |
Jiiks | 399c6e792b | |
Jiiks | 0be6facba4 | |
Jiiks | dc7247a12d | |
Jiiks | 377c4fd104 | |
Jiiks | 252d496dc2 | |
Jiiks | 83e334c3f8 | |
Jiiks | e72ad10dfc | |
Jiiks | 9ef392c575 | |
Jiiks | 76057efbb7 | |
Jiiks | dcb121750a | |
Jiiks | 13fa769e9e | |
Jiiks | 3143991239 | |
Jiiks | 1ae0c5aa4d | |
Jiiks | 5ea39f86f7 | |
Jiiks | d6a946e096 | |
Jiiks | b68c1fbd04 | |
Jiiks | 07d3629622 | |
Jiiks | 15daa9acef | |
Jiiks | 817a4a03b6 | |
Jiiks | e63386e9eb | |
Jiiks | 402acdfea9 | |
Jiiks | b440206d07 | |
Jiiks | c7bea4a743 | |
Jiiks | 2528d87b8f | |
Alexei Stukov | 405d74fada | |
Alexei Stukov | b311220132 | |
Alexei Stukov | 66b47457b1 | |
Alexei Stukov | 6e9f9f8bf8 | |
Alexei Stukov | 88a113dc8f | |
Alexei Stukov | a61f860466 | |
Jiiks | 729a4607bd | |
Jiiks | d81dcc9aa2 | |
Jiiks | 1e4f3fa82b | |
Alexei Stukov | 150a1d63c4 | |
Jiiks | 6b481733b9 | |
Jiiks | e07b9b1550 | |
Jiiks | 99c2b53ec6 | |
Jiiks | 2a93e5d2a3 | |
Jiiks | 3661207602 | |
Jiiks | 92845728cc | |
Jiiks | c99753fc8c | |
Jiiks | 688e6022a0 | |
Jiiks | 5408d994be | |
Zack Rauen | 003c9766bc | |
Zack Rauen | 82e9c257ce | |
Jiiks | 6167cc7c4b | |
Jiiks | 1bacecf8d4 | |
Jiiks | d102686379 | |
Jiiks | 1160955629 | |
Jiiks | e60f765a50 | |
Alexei Stukov | d3db696616 | |
Jiiks | b8d16c6e4d | |
Jiiks | b22923d12f | |
Jiiks | 1ea307efdd | |
Jiiks | d98cff878f | |
Jiiks | dd11708f9f | |
Jiiks | 665a7818c9 | |
Jiiks | d75b907ae4 | |
Alexei Stukov | f051bc4812 | |
Jiiks | 9773f78506 | |
Alexei Stukov | f4b7c99c31 | |
Jiiks | 97519b2307 | |
Jiiks | 806ca5028a | |
Jiiks | db89e3a1a0 | |
Jiiks | 6030a78b91 | |
Jiiks | 99ec82795c | |
Jiiks | b1e8b591ba | |
Jiiks | ac9e16632d | |
Jiiks | d8f977b57a | |
Jiiks | c3283c47f6 | |
Jiiks | 2b86f2d741 | |
Jiiks | 9f6389845a | |
Jiiks | 7489fd0aaf | |
Jiiks | 7e1c379fd3 | |
Jiiks | c0765120e8 | |
Jiiks | 7e10de32cb | |
Jiiks | 145d61fe5e | |
Jiiks | 72b278de6e | |
Jiiks | 5e977e8dc4 | |
Jiiks | 109eb31aa5 | |
Jiiks | 1b6f3005f5 | |
Jiiks | d02f894d4a | |
Jiiks | 20561e2938 | |
Jiiks | a3724d739b | |
Jiiks | cf1da34e16 | |
Jiiks | 5454950838 | |
Jiiks | ed7abe3571 | |
Jiiks | b14bf93ef9 | |
Jiiks | 3436bbe52b | |
Jiiks | 1a26e77dd9 | |
Jiiks | da1fc0a2f0 | |
Jiiks | d1fd5ae881 | |
Jiiks | 8c04e7d2d3 | |
Jiiks | 3fc1adc503 | |
Jiiks | 691c9f378a | |
Jiiks | a769385219 | |
Jiiks | f1f23fa220 | |
Jiiks | 99ef0d9f81 | |
Jiiks | 5597c485d1 | |
Jiiks | 7f08ba27e6 | |
Jiiks | 99614aecdc | |
Alexei Stukov | 726db7f0c9 | |
Zack Rauen | 67c39fd9d9 | |
Jiiks | 23d4eb77b2 | |
Jiiks | 124ec078aa | |
Jiiks | e6c9bc447e | |
Jiiks | 7bc4b652fa | |
Alexei Stukov | 6ab1f5c844 | |
Alexei Stukov | 816ff2ed08 | |
Jiiks | 6ca36ebbb5 | |
Jiiks | f511e4556e | |
Alexei Stukov | 36515432c7 | |
Alexei Stukov | 47782c0168 | |
Jiiks | 3a370f2827 | |
Jiiks | 90f5596cb2 | |
Jiiks | 75439a600e | |
Alexei Stukov | f46960fdd0 | |
Alexei Stukov | f684780842 | |
Jiiks | 6c8631f1cd | |
Jiiks | 0311602539 | |
Jiiks | b5e8098d20 | |
Jiiks | 111be57b59 | |
Jiiks | 76e19c8469 | |
Jiiks | ccff016820 | |
Jiiks | 021e282b1c | |
Jiiks | 6699fcc1e7 | |
Jiiks | f3ea192974 | |
Jiiks | 9c1a93f4c1 | |
Jiiks | 2c23f18e89 | |
Jiiks | c88d2cdae9 | |
Alexei Stukov | 8235e55357 | |
Alexei Stukov | 754e6bbd72 | |
Alexei Stukov | e27d8874ef | |
Alexei Stukov | c310cc52d5 | |
Jiiks | 073cce825d | |
Alexei Stukov | a04fb59db7 | |
Alexei Stukov | f8ed871f8e | |
Alexei Stukov | fae97613d1 | |
Jiiks | 27df24f369 | |
Jiiks | 5bb95f59c0 | |
Alexei Stukov | bcabaab14c | |
Alexei Stukov | 033f3472cb | |
Alexei Stukov | 8fbd75ce94 | |
Alexei Stukov | 3a38b59a4d | |
Alexei Stukov | c29b659afb | |
Alexei Stukov | ece161fac0 | |
Lilian Tedone | b57ab92269 | |
Alexei Stukov | 5a3aa553cd | |
Jiiks | a2f48dd007 | |
Jiiks | 4f10f38581 | |
Jiiks | ea0dc0c6f1 | |
Jiiks | 93f536bf21 | |
Jiiks | 497ab62ee6 | |
Jiiks | 784a14f853 | |
Jiiks | 95447dd3f0 | |
Jiiks | 2cff8edb59 | |
Jiiks | e4c0796e5e | |
Jiiks | e3010fcd43 | |
Jiiks | 08b66d5e5a | |
Jiiks | c618956639 | |
Jiiks | c6ddb75630 | |
Jiiks | 1fd7b97ce6 | |
Jiiks | fa5be193ad | |
Jiiks | 63874c8b4e | |
Jiiks | b4dbfcd808 | |
Jiiks | 7a9b72a33a | |
Jiiks | 6db34b9cee | |
Alexei Stukov | ead51ae676 | |
Jiiks | 9a77aae56b | |
Jiiks | 3182f28359 | |
Jiiks | a274b0c839 | |
Jiiks | 199b9249e4 | |
Jiiks | 08ba649a91 | |
Jiiks | a0eb43188f | |
Jiiks | b1bd5190c1 | |
Jiiks | 7fb5c8a378 | |
Jiiks | 26b1fad5a4 | |
Jiiks | f028aefab8 | |
Jiiks | f0d66300c4 | |
Jiiks | 12b291cf0b | |
Jiiks | a8f686eb61 | |
Jiiks | 98bf4d21ac | |
Jiiks | d5a82ab2b3 | |
Jiiks | d5d7709c9f | |
Jiiks | cfc197cdc0 | |
Jiiks | e8cfb840fe | |
Jiiks | f01f4926eb | |
Jiiks | 83a4e0a114 | |
Jiiks | 4b8a905d2a | |
Alexei Stukov | 08ae8dceb5 | |
Jiiks | ad28e7c638 | |
Jiiks | 9ba8047066 | |
Alexei Stukov | 9ba9ec1556 | |
Jiiks | ce2658b6e2 | |
Jiiks | f3cc033182 | |
Jiiks | 7b44d163ca | |
Alexei Stukov | 72d24a1cb0 | |
Alexei Stukov | c2c520fe27 | |
Samuel Elliott | ad55459649 | |
Samuel Elliott | dfac3f1825 | |
Samuel Elliott | adecbf8945 | |
Samuel Elliott | a371fabc70 | |
Samuel Elliott | c04ffdfc24 | |
Samuel Elliott | 572721c57f | |
Samuel Elliott | 4af09224d7 | |
Samuel Elliott | 7f1ac9bea0 | |
Samuel Elliott | ce93b2b4c6 | |
Samuel Elliott | d98ed6649e | |
Zack Rauen | 468422084a | |
Alexei Stukov | 395167aafa | |
Alexei Stukov | 803cf59f11 | |
Alexei Stukov | 133511aeec | |
Zack Rauen | be024c1bbe | |
Zack Rauen | 6f10c71623 | |
Zack Rauen | adb2430d71 | |
Alexei Stukov | 990453cdad | |
Jiiks | 7cf69f6993 | |
Jiiks | 5822110c87 | |
Jiiks | 669a88c1ff | |
Jiiks | 6a8c77b578 | |
Jiiks | a93d12df03 | |
Jiiks | 80096c5e71 | |
Jiiks | 307b084214 | |
Jiiks | 1e0f3b69fb | |
Jiiks | 550d0d3c40 | |
Jiiks | 96f8fe680f | |
Jiiks | b48adc7a62 | |
Jiiks | 6db016060b | |
Jiiks | ecb0b3e9d0 | |
Jiiks | 8c060314da | |
Jiiks | a040a605d4 | |
Jiiks | 09fb76d148 | |
Jiiks | ddda5b3cc1 | |
Jiiks | cff425f916 | |
Jiiks | 76d54f0a95 | |
Jiiks | 2093c472b5 | |
Jiiks | 1009c62ea0 | |
Jiiks | 2cd94fcd5f | |
Jiiks | cb35b14311 | |
Jiiks | 6b3cb712c7 | |
Jiiks | cfbafffe46 | |
Jiiks | 2913a85368 | |
Jiiks | 5c160d75a6 | |
Jiiks | f14b1b71e7 | |
Jiiks | 4e3a56e466 |
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[Bug]"
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
- [ ] Used appropriate template for the issue type
|
||||
- [ ] Searched both open and closed issues for duplicates of this issue
|
||||
- [ ] Title adequately and _concisely_ reflects the feature or the bug
|
||||
|
||||
**Describe the bug**
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
**To Reproduce**
|
||||
<!-- Steps to reproduce the behavior -->
|
||||
|
||||
**Expected behavior**
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
**Screenshots**
|
||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||
|
||||
**System information**
|
||||
<!-- os, discord branch etc -->
|
||||
|
||||
**Additional context**
|
||||
<!-- Add any other context about the problem here. -->
|
||||
|
||||
**Are you willing and able to fix this?**
|
||||
<!-- "Yes" or, if "no", what can current contributors do to help you create a PR? -->
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
name: Custom issue template
|
||||
about: Describe this issue template's purpose here.
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
- [ ] Used appropriate template for the issue type
|
||||
- [ ] Searched both open and closed issues for duplicates of this issue
|
||||
- [ ] Title adequately and _concisely_ reflects the feature or the bug
|
|
@ -0,0 +1,27 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[Feature]"
|
||||
labels: feature
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
- [ ] Used appropriate template for the issue type
|
||||
- [ ] Searched both open and closed issues for duplicates of this issue
|
||||
- [ ] Title adequately and _concisely_ reflects the feature or the bug
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||
|
||||
**Describe the solution you'd like**
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
**Additional context**
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
|
||||
**Are you willing and able to implement this?**
|
||||
<!-- "Yes" or, if "no", what can current contributors do to help you create a PR? -->
|
|
@ -4,6 +4,7 @@ dist
|
|||
etc
|
||||
release
|
||||
|
||||
tests/tmp
|
||||
tests/log.txt
|
||||
|
||||
# User data
|
||||
|
@ -12,3 +13,4 @@ user.config.json
|
|||
/.vs
|
||||
/npm-debug.log
|
||||
/tests/ext/extensions
|
||||
/tests/userdata
|
||||
|
|
10
README.md
10
README.md
|
@ -1,4 +1,12 @@
|
|||
# BetterDiscordApp [![Travis][build-badge]][build]
|
||||
# BetterDiscordApp [![Travis][build-badge]][build] [![Snyk][snyk-badge]][snyk-url] [![deps][deps-badge]][deps-url] [![devdeps][devdeps-badge]][devdeps-url]
|
||||
|
||||
[build-badge]: https://img.shields.io/travis/JsSucks/BetterDiscordApp/master.svg
|
||||
[build]: https://travis-ci.org/JsSucks/BetterDiscordApp
|
||||
|
||||
[snyk-badge]: https://snyk.io/test/github/JsSucks/BetterDiscordApp/badge.svg
|
||||
[snyk-url]: https://snyk.io/test/github/JsSucks/BetterDiscordApp
|
||||
|
||||
[deps-badge]: https://david-dm.org/JsSucks/BetterDiscordApp.svg
|
||||
[deps-url]: https://david-dm.org/JsSucks/BetterDiscordApp
|
||||
[devdeps-badge]: https://david-dm.org/JsSucks/BetterDiscordApp/dev-status.svg
|
||||
[devdeps-url]: https://david-dm.org/JsSucks/BetterDiscordApp?type=dev
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
module.exports = function(api) {
|
||||
|
||||
api.cache(true);
|
||||
|
||||
const presets = [['@babel/env', {
|
||||
targets: {
|
||||
'node': '8.6.0'
|
||||
}
|
||||
}]];
|
||||
|
||||
const plugins = [];
|
||||
|
||||
return {
|
||||
presets,
|
||||
plugins
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
module.exports = function(api) {
|
||||
|
||||
api.cache(true);
|
||||
|
||||
const presets = [['@babel/env', {
|
||||
targets: {
|
||||
'node': '8.6.0',
|
||||
'chrome': '60'
|
||||
}
|
||||
}], '@babel/react'];
|
||||
|
||||
const plugins = [];
|
||||
|
||||
return {
|
||||
presets,
|
||||
plugins
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
"name": "bdclient",
|
||||
"description": "BetterDiscord client package",
|
||||
"author": "Jiiks",
|
||||
"version": "2.0.0b",
|
||||
"version": "2.0.0-beta.6",
|
||||
"homepage": "https://betterdiscord.net",
|
||||
"license": "MIT",
|
||||
"main": "dist/betterdiscord.client.js",
|
||||
|
|
|
@ -36,5 +36,5 @@ export default new class TwentyFourHour extends BuiltinModule {
|
|||
if (matched[3] === 'AM') return returnValue.replace(matched[0], `${matched[1] === '12' ? '00' : matched[1].padStart(2, '0')}:${matched[2]}`)
|
||||
return returnValue.replace(matched[0], `${parseInt(matched[1]) + 12}:${matched[2]}`)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ export default new class BlockedMessages extends BuiltinModule {
|
|||
async enabled(e) {
|
||||
const MessageListComponents = Reflection.module.byProps('BlockedMessageGroup');
|
||||
MessageListComponents.OriginalBlockedMessageGroup = MessageListComponents.BlockedMessageGroup;
|
||||
MessageListComponents.BlockedMessageGroup = () => { return null; };
|
||||
MessageListComponents.BlockedMessageGroup = () => null;
|
||||
this.cancelBlockedMessages = () => {
|
||||
MessageListComponents.BlockedMessageGroup = MessageListComponents.OriginalBlockedMessageGroup;
|
||||
delete MessageListComponents.OriginalBlockedMessageGroup;
|
||||
|
|
|
@ -22,10 +22,10 @@ export default class BuiltinModule {
|
|||
this.patch = this.patch.bind(this);
|
||||
}
|
||||
|
||||
init() {
|
||||
async init() {
|
||||
this.setting.on('setting-updated', this._settingUpdated);
|
||||
if (this.setting.value) {
|
||||
if (this.enabled) this.enabled();
|
||||
if (this.enabled) await this.enabled();
|
||||
if (this.applyPatches) this.applyPatches();
|
||||
}
|
||||
}
|
||||
|
@ -38,16 +38,15 @@ export default class BuiltinModule {
|
|||
return Patcher.getPatchesByCaller(`BD:${this.moduleName}`);
|
||||
}
|
||||
|
||||
_settingUpdated(e) {
|
||||
const { value } = e;
|
||||
if (value === true) {
|
||||
if (this.enabled) this.enabled(e);
|
||||
if (this.applyPatches) this.applyPatches();
|
||||
return;
|
||||
}
|
||||
if (value === false) {
|
||||
if (this.disabled) this.disabled(e);
|
||||
async _settingUpdated(e) {
|
||||
if (e.value) {
|
||||
if (this.enabled) await this.enabled(e);
|
||||
if (this.applyPatches) await this.applyPatches();
|
||||
if (this.rerenderPatchedComponents) this.rerenderPatchedComponents();
|
||||
} else {
|
||||
if (this.disabled) await this.disabled(e);
|
||||
this.unpatch();
|
||||
if (this.rerenderPatchedComponents) this.rerenderPatchedComponents();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,7 +74,15 @@ export default class BuiltinModule {
|
|||
*/
|
||||
patch(module, fnName, cb, when = 'after') {
|
||||
if (!['before', 'after', 'instead'].includes(when)) when = 'after';
|
||||
Patch(`BD:${this.moduleName}`, module)[when](fnName, cb.bind(this));
|
||||
return Patch(`BD:${this.moduleName}`, module)[when](fnName, cb.bind(this));
|
||||
}
|
||||
|
||||
childPatch(module, fnName, child, cb, when = 'after') {
|
||||
const last = child.pop();
|
||||
|
||||
this.patch(module, fnName, (component, args, retVal) => {
|
||||
const unpatch = this.patch(child.reduce((obj, key) => obj[key], retVal), last, function(...args) {unpatch(); return cb.call(this, component, ...args);}, when);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -42,6 +42,10 @@ export default new class ColoredText extends BuiltinModule {
|
|||
this.intensitySetting.off('setting-updated', this._intensityUpdated);
|
||||
}
|
||||
|
||||
rerenderPatchedComponents() {
|
||||
if (this.MessageContent) this.MessageContent.forceUpdateAll();
|
||||
}
|
||||
|
||||
/* Methods */
|
||||
_intensityUpdated() {
|
||||
this.MessageContent.forceUpdateAll();
|
||||
|
@ -50,18 +54,20 @@ export default new class ColoredText extends BuiltinModule {
|
|||
/* Patches */
|
||||
async applyPatches() {
|
||||
if (this.patches.length) return;
|
||||
this.MessageContent = await ReactComponents.getComponent('MessageContent', { selector: Reflection.resolve('container', 'containerCozy', 'containerCompact', 'edited').selector });
|
||||
this.MessageContent = await ReactComponents.getComponent('MessageContent');
|
||||
this.patch(this.MessageContent.component.prototype, 'render', this.injectColoredText);
|
||||
this.MessageContent.forceUpdateAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set markup text colour to match role colour
|
||||
*/
|
||||
injectColoredText(thisObject, args, returnValue) {
|
||||
const { TinyColor } = Reflection.modules;
|
||||
const markup = Utils.findInReactTree(returnValue, m => m && m.props && m.props.className && m.props.className.includes('da-markup'));
|
||||
const roleColor = thisObject.props.message.colorString;
|
||||
if (markup && roleColor) markup.props.style = {color: TinyColor.mix(roleColor, this.defaultColor, this.intensity)};
|
||||
injectColoredText(thisObject, args, originalReturn) {
|
||||
const unpatch = this.patch(originalReturn.props, 'children', (obj, args, returnValue) => {
|
||||
unpatch();
|
||||
const { TinyColor } = Reflection.modules;
|
||||
const markup = Utils.findInReactTree(returnValue, m => m && m.props && m.props.className && m.props.className.includes('da-markup'));
|
||||
const roleColor = thisObject.props.message.colorString;
|
||||
if (markup && roleColor) markup.props.style = {color: TinyColor.mix(roleColor, this.defaultColor, this.intensity)};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
*/
|
||||
|
||||
import { Settings, Cache, Events } from 'modules';
|
||||
import BuiltinModule from './BuiltinModule';
|
||||
import { Reflection, ReactComponents, MonkeyPatch, Patcher, DiscordApi, Security } from 'modules';
|
||||
import BuiltinModule from '../BuiltinModule';
|
||||
import { Reflection, ReactComponents, DiscordApi, Security } from 'modules';
|
||||
import { VueInjector, Modals, Toasts } from 'ui';
|
||||
import { ClientLogger as Logger, ClientIPC } from 'common';
|
||||
import { request } from 'vendor';
|
||||
|
@ -172,7 +172,7 @@ export default new class E2EE extends BuiltinModule {
|
|||
this.patch(Dispatcher, 'dispatch', this.dispatcherPatch, 'before');
|
||||
this.patchMessageContent();
|
||||
|
||||
const ChannelTextArea = await ReactComponents.getComponent('ChannelTextArea', { selector: Reflection.resolve('channelTextArea', 'emojiButton').selector });
|
||||
const ChannelTextArea = await ReactComponents.getComponent('ChannelTextArea');
|
||||
this.patchChannelTextArea(ChannelTextArea);
|
||||
this.patchChannelTextAreaSubmit(ChannelTextArea);
|
||||
ChannelTextArea.forceUpdateAll();
|
||||
|
@ -236,12 +236,14 @@ export default new class E2EE extends BuiltinModule {
|
|||
}
|
||||
|
||||
async patchMessageContent() {
|
||||
const MessageContent = await ReactComponents.getComponent('MessageContent', { selector: Reflection.resolve('container', 'containerCozy', 'containerCompact', 'edited').selector });
|
||||
const MessageContent = await ReactComponents.getComponent('MessageContent');
|
||||
this.patch(MessageContent.component.prototype, 'render', this.beforeRenderMessageContent, 'before');
|
||||
this.patch(MessageContent.component.prototype, 'render', this.afterRenderMessageContent);
|
||||
this.childPatch(MessageContent.component.prototype, 'render', ['props', 'children'], this.afterRenderMessageContent);
|
||||
MessageContent.forceUpdateAll();
|
||||
|
||||
const ImageWrapper = await ReactComponents.getComponent('ImageWrapper', { selector: Reflection.resolve('imageWrapper').selector });
|
||||
const ImageWrapper = await ReactComponents.getComponent('ImageWrapper');
|
||||
this.patch(ImageWrapper.component.prototype, 'render', this.beforeRenderImageWrapper, 'before');
|
||||
ImageWrapper.forceUpdateAll();
|
||||
}
|
||||
|
||||
beforeRenderMessageContent(component) {
|
||||
|
@ -285,10 +287,16 @@ export default new class E2EE extends BuiltinModule {
|
|||
component.props.message.contentParsed = create.contentParsed;
|
||||
}
|
||||
|
||||
afterRenderMessageContent(component, args, retVal) {
|
||||
afterRenderMessageContent(component, _childrenObject, args, retVal) {
|
||||
if (!component.props.message.bd_encrypted) return;
|
||||
const buttons = Utils.findInReactTree(retVal, m => Array.isArray(m) && m[1].props && m[1].props.currentUserId);
|
||||
|
||||
const { className } = Reflection.resolve('buttonContainer', 'avatar', 'username');
|
||||
const buttonContainer = Utils.findInReactTree(retVal, m => m && m.className && m.className.indexOf(className) !== -1);
|
||||
if (!buttonContainer) return;
|
||||
|
||||
const buttons = buttonContainer.children.props.children;
|
||||
if (!buttons) return;
|
||||
|
||||
try {
|
||||
buttons.unshift(VueInjector.createReactElement(E2EEMessageButton));
|
||||
} catch (err) {
|
|
@ -45,7 +45,7 @@
|
|||
import { E2EE } from 'builtin';
|
||||
import { Settings, DiscordApi, Reflection } from 'modules';
|
||||
import { Toasts } from 'ui';
|
||||
import { MiLock, MiImagePlus, MiIcVpnKey } from '../ui/components/common/MaterialIcon';
|
||||
import { MiLock, MiImagePlus, MiIcVpnKey } from 'commoncomponents';
|
||||
|
||||
export default {
|
||||
components: {
|
|
@ -17,7 +17,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { MiLock } from '../ui/components/common/MaterialIcon';
|
||||
import { MiLock } from 'commoncomponents';
|
||||
|
||||
export default {
|
||||
components: {
|
|
@ -0,0 +1,3 @@
|
|||
export { default as default } from './E2EE';
|
||||
export { default as E2EEComponent } from './E2EEComponent.vue';
|
||||
export { default as E2EEMessageButton } from './E2EEMessageButton.vue';
|
|
@ -9,18 +9,11 @@
|
|||
*/
|
||||
|
||||
import { Settings } from 'modules';
|
||||
|
||||
import BuiltinModule from './BuiltinModule';
|
||||
import EmoteModule from './EmoteModule';
|
||||
import GlobalAc from '../ui/autocomplete';
|
||||
import BuiltinModule from '../BuiltinModule';
|
||||
import EmoteModule, { EMOTE_SOURCES } from './EmoteModule';
|
||||
import GlobalAc from 'autocomplete';
|
||||
import { BdContextMenu } from 'ui';
|
||||
|
||||
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 EmoteAc extends BuiltinModule {
|
||||
|
||||
/* Getters */
|
|
@ -8,15 +8,10 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import VrWrapper from '../ui/vrwrapper';
|
||||
import VrWrapper from '../../ui/vrwrapper';
|
||||
import { EMOTE_SOURCES } from '.';
|
||||
import EmoteComponent from './EmoteComponent.vue';
|
||||
|
||||
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 class Emote extends VrWrapper {
|
||||
|
||||
constructor(type, id, name) {
|
|
@ -5,7 +5,7 @@
|
|||
<script>
|
||||
import { ClientLogger as Logger } from 'common';
|
||||
import EmoteModule from './EmoteModule';
|
||||
import { MiStar } from '../ui/components/common';
|
||||
import { MiStar } from 'commoncomponents';
|
||||
|
||||
export default {
|
||||
components: {
|
|
@ -8,20 +8,17 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import BuiltinModule from './BuiltinModule';
|
||||
import BuiltinModule from '../BuiltinModule';
|
||||
import path from 'path';
|
||||
import { request } from 'vendor';
|
||||
|
||||
import { Utils, FileUtils } from 'common';
|
||||
import { Utils, FileUtils, ClientLogger as Logger } from 'common';
|
||||
import { DiscordApi, Settings, Globals, Reflection, ReactComponents, Database } from 'modules';
|
||||
import { DiscordContextMenu } from 'ui';
|
||||
|
||||
import Emote from './EmoteComponent.js';
|
||||
import Autocomplete from '../ui/components/common/Autocomplete.vue';
|
||||
|
||||
import GlobalAc from '../ui/autocomplete';
|
||||
|
||||
const EMOTE_SOURCES = [
|
||||
export 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'
|
||||
|
@ -131,6 +128,8 @@ export default new class EmoteModule extends BuiltinModule {
|
|||
|
||||
this.database.set(id, { id: emote.value.id || value, type });
|
||||
}
|
||||
|
||||
Logger.log('EmoteModule', ['Loaded emote database']);
|
||||
}
|
||||
|
||||
async loadUserData() {
|
||||
|
@ -218,16 +217,19 @@ export default new class EmoteModule extends BuiltinModule {
|
|||
async applyPatches() {
|
||||
this.patchMessageContent();
|
||||
this.patchSendAndEdit();
|
||||
const ImageWrapper = await ReactComponents.getComponent('ImageWrapper', { selector: Reflection.resolve('imageWrapper').selector });
|
||||
this.patch(ImageWrapper.component.prototype, 'render', this.beforeRenderImageWrapper, 'before');
|
||||
this.patchSpoiler();
|
||||
|
||||
const MessageAccessories = await ReactComponents.getComponent('MessageAccessories');
|
||||
this.patch(MessageAccessories.component.prototype, 'render', this.afterRenderMessageAccessories, 'after');
|
||||
MessageAccessories.forceUpdateAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Patches MessageContent render method
|
||||
*/
|
||||
async patchMessageContent() {
|
||||
const MessageContent = await ReactComponents.getComponent('MessageContent', { selector: Reflection.resolve('container', 'containerCozy', 'containerCompact', 'edited').selector });
|
||||
this.patch(MessageContent.component.prototype, 'render', this.afterRenderMessageContent);
|
||||
const MessageContent = await ReactComponents.getComponent('MessageContent');
|
||||
this.childPatch(MessageContent.component.prototype, 'render', ['props', 'children'], this.afterRenderMessageContent);
|
||||
MessageContent.forceUpdateAll();
|
||||
}
|
||||
|
||||
|
@ -240,10 +242,26 @@ export default new class EmoteModule extends BuiltinModule {
|
|||
this.patch(MessageActions, 'editMessage', this.handleEditMessage, 'instead');
|
||||
}
|
||||
|
||||
async patchSpoiler() {
|
||||
const Spoiler = await ReactComponents.getComponent('Spoiler');
|
||||
this.childPatch(Spoiler.component.prototype, 'render', ['props', 'children', 'props', 'children'], this.afterRenderSpoiler);
|
||||
Spoiler.forceUpdateAll();
|
||||
}
|
||||
|
||||
afterRenderSpoiler(component, _childrenObject, args, retVal) {
|
||||
const markup = Utils.findInReactTree(retVal, filter =>
|
||||
filter &&
|
||||
filter.className &&
|
||||
filter.className.includes('inlineContent'));
|
||||
if (!markup) return;
|
||||
|
||||
markup.children = this.processMarkup(markup.children);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle message render
|
||||
*/
|
||||
afterRenderMessageContent(component, args, retVal) {
|
||||
afterRenderMessageContent(component, _childrenObject, args, retVal) {
|
||||
const markup = Utils.findInReactTree(retVal, filter =>
|
||||
filter &&
|
||||
filter.className &&
|
||||
|
@ -256,13 +274,15 @@ export default new class EmoteModule extends BuiltinModule {
|
|||
/**
|
||||
* Handle send message
|
||||
*/
|
||||
async handleSendMessage(component, args, orig) {
|
||||
async handleSendMessage(MessageActions, args, orig) {
|
||||
if (!args.length) return orig(...args);
|
||||
const { content } = args[1];
|
||||
if (!content) return orig(...args);
|
||||
|
||||
Logger.log('EmoteModule', ['Sending message', MessageActions, args, orig]);
|
||||
|
||||
const emoteAsImage = Settings.getSetting('emotes', 'default', 'emoteasimage').value &&
|
||||
(DiscordApi.currentChannel.type === 'DM' || DiscordApi.currentChannel.checkPermissions(DiscordApi.modules.DiscordPermissions.ATTACH_FILES));
|
||||
(DiscordApi.currentChannel.type === 'DM' || DiscordApi.currentChannel.type === 'GROUP_DM' || DiscordApi.currentChannel.checkPermissions(DiscordApi.modules.DiscordPermissions.ATTACH_FILES));
|
||||
|
||||
if (!emoteAsImage || content.split(' ').length > 1) {
|
||||
args[1].content = args[1].content.split(' ').map(word => {
|
||||
|
@ -271,7 +291,7 @@ export default new class EmoteModule extends BuiltinModule {
|
|||
const emote = this.findByName(isEmote[1], true);
|
||||
if (!emote) return word;
|
||||
this.addToMostUsed(emote);
|
||||
return emote ? `:${isEmote[1]}:` : word;
|
||||
return emote ? `;${isEmote[1]};` : word;
|
||||
}
|
||||
return word;
|
||||
}).join(' ');
|
||||
|
@ -305,23 +325,27 @@ export default new class EmoteModule extends BuiltinModule {
|
|||
if (!content) return orig(...args);
|
||||
args[2].content = args[2].content.split(' ').map(word => {
|
||||
const isEmote = /;(.*?);/g.exec(word);
|
||||
return isEmote ? `:${isEmote[1]}:` : word;
|
||||
return isEmote ? `;${isEmote[1]};` : word;
|
||||
}).join(' ');
|
||||
return orig(...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle imagewrapper render
|
||||
* Handle MessageAccessories render
|
||||
*/
|
||||
beforeRenderImageWrapper(component, args, retVal) {
|
||||
if (!component.props || !component.props.src) return;
|
||||
afterRenderMessageAccessories(component, args, retVal) {
|
||||
if (!component.props || !component.props.message) return;
|
||||
if (!component.props.message.attachments || component.props.message.attachments.length !== 1) return;
|
||||
|
||||
const src = component.props.original || component.props.src.split('?')[0];
|
||||
if (!src || !src.includes('.bdemote.')) return;
|
||||
const emoteName = src.split('/').pop().split('.')[0];
|
||||
const emote = this.findByName(emoteName);
|
||||
const filename = component.props.message.attachments[0].filename;
|
||||
const match = filename.match(/([^/]*)\.bdemote\.(gif|png)$/i);
|
||||
if (!match) return;
|
||||
|
||||
const emote = this.findByName(match[1]);
|
||||
if (!emote) return;
|
||||
retVal.props.children = emote.render();
|
||||
|
||||
emote.jumboable = true;
|
||||
retVal.props.children[2] = emote.render();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -339,14 +363,14 @@ export default new class EmoteModule extends BuiltinModule {
|
|||
for (const child of markup) {
|
||||
if (typeof child !== 'string') {
|
||||
if (typeof child === 'object') {
|
||||
const isEmoji = Utils.findInReactTree(child, 'emojiName');
|
||||
if (isEmoji) child.props.children.props.jumboable = jumboable;
|
||||
const isEmoji = Utils.findInReactTree(child, filter => filter && filter.emojiName);
|
||||
if (isEmoji) isEmoji.jumboable = jumboable;
|
||||
}
|
||||
newMarkup.push(child);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!/:(\w+):/g.test(child)) {
|
||||
if (!/;(\w+);/g.test(child)) {
|
||||
newMarkup.push(child);
|
||||
continue;
|
||||
}
|
||||
|
@ -355,7 +379,7 @@ export default new class EmoteModule extends BuiltinModule {
|
|||
|
||||
let s = '';
|
||||
for (const word of words) {
|
||||
const isemote = /:(.*?):/g.exec(word);
|
||||
const isemote = /;(.*?);/g.exec(word);
|
||||
if (!isemote) {
|
||||
s += word;
|
||||
continue;
|
|
@ -0,0 +1,4 @@
|
|||
export { default as EmoteModule, EMOTE_SOURCES } from './EmoteModule';
|
||||
export { default as Emote } from './EmoteComponent';
|
||||
export { default as EmoteComponent } from './EmoteComponent.vue';
|
||||
export { default as EmoteAc } from './EmoteAc';
|
|
@ -1,16 +1,19 @@
|
|||
import { default as EmoteModule } from './EmoteModule';
|
||||
import { default as ReactDevtoolsModule } from './ReactDevtoolsModule';
|
||||
import { default as VueDevtoolsModule } from './VueDevToolsModule';
|
||||
import { default as TrackingProtection } from './TrackingProtection';
|
||||
import { default as E2EE } from './E2EE';
|
||||
import { default as ColoredText } from './ColoredText';
|
||||
import { default as TwentyFourHour } from './24Hour';
|
||||
import { default as KillClyde } from './KillClyde';
|
||||
import { default as BlockedMessages } from './BlockedMessages';
|
||||
import { default as VoiceDisconnect } from './VoiceDisconnect';
|
||||
import { default as EmoteAc } from './EmoteAc';
|
||||
import { EmoteModule, EmoteAc } from './Emotes';
|
||||
import ReactDevtoolsModule from './ReactDevtoolsModule';
|
||||
import VueDevtoolsModule from './VueDevToolsModule';
|
||||
import TrackingProtection from './TrackingProtection';
|
||||
import E2EE from './E2EE';
|
||||
import ColoredText from './ColoredText';
|
||||
import TwentyFourHour from './24Hour';
|
||||
import KillClyde from './KillClyde';
|
||||
import BlockedMessages from './BlockedMessages';
|
||||
import VoiceDisconnect from './VoiceDisconnect';
|
||||
|
||||
export default class {
|
||||
static get modules() {
|
||||
return require('./builtin');
|
||||
}
|
||||
|
||||
static initAll() {
|
||||
EmoteModule.init();
|
||||
ReactDevtoolsModule.init();
|
||||
|
|
|
@ -12,7 +12,7 @@ import BuiltinModule from './BuiltinModule';
|
|||
|
||||
import { Reflection } from 'modules';
|
||||
|
||||
export default new class E2EE extends BuiltinModule {
|
||||
export default new class TrackingProtection extends BuiltinModule {
|
||||
|
||||
/* Getters */
|
||||
get moduleName() { return 'TrackingProtection' }
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export { default as EmoteModule } from './EmoteModule';
|
||||
export { EmoteModule, EmoteAc } from './Emotes';
|
||||
export { default as ReactDevtoolsModule } from './ReactDevtoolsModule';
|
||||
export { default as VueDevtoolsModule } from './VueDevToolsModule';
|
||||
export { default as TrackingProtection } from './TrackingProtection';
|
||||
|
|
|
@ -222,6 +222,13 @@
|
|||
{
|
||||
"id": "default",
|
||||
"settings": [
|
||||
{
|
||||
"id": "unsafe-content",
|
||||
"type": "bool",
|
||||
"text": "Allow unverified content",
|
||||
"hint": "Allow loading unverified plugins/themes",
|
||||
"value": "false"
|
||||
},
|
||||
{
|
||||
"id": "tracking-protection",
|
||||
"type": "bool",
|
||||
|
|
|
@ -0,0 +1,232 @@
|
|||
const dummyTags = ['dark', 'light', 'simple', 'minimal', 'extra', 'something', 'tag', 'whatever', 'another', 'transparent'];
|
||||
|
||||
export default class ServerEmu {
|
||||
|
||||
static async themes(args) {
|
||||
if (!this._themes) this._themes = this.generateThemes();
|
||||
|
||||
await new Promise(r => setTimeout(r, Math.random() * 3000));
|
||||
|
||||
let docs = [];
|
||||
|
||||
if (args && args.sterm) {
|
||||
const { sterm } = args;
|
||||
const reg = new RegExp(sterm, 'gi');
|
||||
docs = this._themes.filter(doc => doc.tags.includes(sterm) || reg.exec(doc.name) || reg.exec(doc.description));
|
||||
} else {
|
||||
docs = this._themes;
|
||||
}
|
||||
|
||||
if (args.sort) {
|
||||
switch (args.sort) {
|
||||
case 'updated':
|
||||
if (args.ascending) docs = docs.sort((docA, docB) => new Date(docA.updated).getTime() - new Date(docB.updated).getTime());
|
||||
else docs = docs.sort((docA, docB) => new Date(docB.updated).getTime() - new Date(docA.updated).getTime());
|
||||
break;
|
||||
case 'installs':
|
||||
if (args.ascending) docs = docs.sort((docA, docB) => docA.installs - docB.installs);
|
||||
else docs = docs.sort((docA, docB) => docB.installs - docA.installs);
|
||||
break;
|
||||
case 'users':
|
||||
if (args.ascending) docs = docs.sort((docA, docB) => docA.activeUsers - docB.activeUsers);
|
||||
else docs = docs.sort((docA, docB) => docB.activeUsers - docA.activeUsers);
|
||||
break;
|
||||
case 'rating':
|
||||
if (args.ascending) docs = docs.sort((docA, docB) => docA.rating - docB.rating);
|
||||
else docs = docs.sort((docA, docB) => docB.rating - docA.rating);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const total = docs.length;
|
||||
const pages = Math.ceil(total / 9);
|
||||
|
||||
let page = 1;
|
||||
if (args && args.page) {
|
||||
page = args.page;
|
||||
docs = docs.slice((page - 1) * 9, page * 9);
|
||||
} else {
|
||||
docs = docs.slice(0, 9);
|
||||
}
|
||||
|
||||
return {
|
||||
docs,
|
||||
filters: {
|
||||
sterm: args.sterm || '',
|
||||
ascending: args.ascending || false,
|
||||
sort: args.sort || 'name'
|
||||
},
|
||||
pagination: {
|
||||
total,
|
||||
pages,
|
||||
limit: 9,
|
||||
page
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static async plugins(args) {
|
||||
if (!this._plugins) this._plugins = this.generatePlugins();
|
||||
|
||||
await new Promise(r => setTimeout(r, Math.random() * 3000));
|
||||
|
||||
let docs = [];
|
||||
|
||||
if (args && args.sterm) {
|
||||
const { sterm } = args;
|
||||
const reg = new RegExp(sterm, 'gi');
|
||||
docs = this._plugins.filter(doc => doc.tags.includes(sterm) || reg.exec(doc.name) || reg.exec(doc.description));
|
||||
} else {
|
||||
docs = this._plugins;
|
||||
}
|
||||
|
||||
if (args.sort) {
|
||||
switch (args.sort) {
|
||||
case 'updated':
|
||||
if (args.ascending) docs = docs.sort((docA, docB) => new Date(docA.updated).getTime() - new Date(docB.updated).getTime());
|
||||
else docs = docs.sort((docA, docB) => new Date(docB.updated).getTime() - new Date(docA.updated).getTime());
|
||||
break;
|
||||
case 'installs':
|
||||
if (args.ascending) docs = docs.sort((docA, docB) => docA.installs - docB.installs);
|
||||
else docs = docs.sort((docA, docB) => docB.installs - docA.installs);
|
||||
break;
|
||||
case 'users':
|
||||
if (args.ascending) docs = docs.sort((docA, docB) => docA.activeUsers - docB.activeUsers);
|
||||
else docs = docs.sort((docA, docB) => docB.activeUsers - docA.activeUsers);
|
||||
break;
|
||||
case 'rating':
|
||||
if (args.ascending) docs = docs.sort((docA, docB) => docA.rating - docB.rating);
|
||||
else docs = docs.sort((docA, docB) => docB.rating - docA.rating);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const total = docs.length;
|
||||
const pages = Math.ceil(total / 9);
|
||||
|
||||
let page = 1;
|
||||
if (args && args.page) {
|
||||
page = args.page;
|
||||
docs = docs.slice((page - 1) * 9, page * 9);
|
||||
} else {
|
||||
docs = docs.slice(0, 9);
|
||||
}
|
||||
|
||||
return {
|
||||
docs,
|
||||
filters: {
|
||||
sterm: args.sterm || '',
|
||||
ascending: args.ascending || false,
|
||||
sort: args.sort || 'name'
|
||||
},
|
||||
pagination: {
|
||||
total,
|
||||
pages,
|
||||
limit: 9,
|
||||
page
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static generateThemes() {
|
||||
const docs = [];
|
||||
const count = Math.floor(Math.random() * 50 + 30);
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const id = `theme${i}-${this.randomId()}`;
|
||||
const name = `Dummy Theme ${i}`;
|
||||
const tags = dummyTags.sort(() => .5 - Math.random()).slice(0, 3);
|
||||
|
||||
docs.push({
|
||||
id,
|
||||
name,
|
||||
tags,
|
||||
installs: Math.floor(Math.random() * 5000) + 5000,
|
||||
updated: this.randomTimestamp(),
|
||||
rating: Math.floor(Math.random() * 500) + 500,
|
||||
activeUsers: Math.floor(Math.random() * 1000) + 1000,
|
||||
rated: Math.random() > .5,
|
||||
version: this.randomVersion(),
|
||||
repository: this.dummyThemeRepo,
|
||||
files: this.dummyFiles,
|
||||
author: this.dummyAuthor,
|
||||
description: ''
|
||||
});
|
||||
}
|
||||
|
||||
return docs;
|
||||
}
|
||||
|
||||
static generatePlugins() {
|
||||
const docs = [];
|
||||
const count = Math.floor(Math.random() * 50 + 30);
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const id = `plugin${i}-${this.randomId()}`;
|
||||
const name = `Dummy Plugin ${i}`;
|
||||
const tags = dummyTags.sort(() => .5 - Math.random()).slice(0, 3);
|
||||
|
||||
docs.push({
|
||||
id,
|
||||
name,
|
||||
tags,
|
||||
installs: Math.floor(Math.random() * 5000) + 5000,
|
||||
updated: this.randomTimestamp(),
|
||||
rating: Math.floor(Math.random() * 500) + 500,
|
||||
activeUsers: Math.floor(Math.random() * 1000) + 1000,
|
||||
rated: Math.random() > .5,
|
||||
version: this.randomVersion(),
|
||||
repository: this.dummyPluginRepo,
|
||||
files: this.dummyFiles,
|
||||
author: this.dummyAuthor,
|
||||
description: ''
|
||||
});
|
||||
}
|
||||
|
||||
return docs;
|
||||
}
|
||||
|
||||
static get dummyThemeRepo() {
|
||||
return {
|
||||
name: 'ExampleRepository',
|
||||
baseUri: 'https://github.com/Jiiks/ExampleRepository',
|
||||
rawUri: 'https://github.com/Jiiks/ExampleRepository/raw/master',
|
||||
assetUri: 'https://api.github.com/repos/Jiiks/ExampleRepository/releases/assets/10023264'
|
||||
}
|
||||
}
|
||||
|
||||
static get dummyPluginRepo() {
|
||||
return {
|
||||
name: 'ExampleRepository',
|
||||
baseUri: 'https://github.com/Jiiks/ExampleRepository',
|
||||
rawUri: 'https://github.com/Jiiks/ExampleRepository/raw/master',
|
||||
assetUri: 'https://api.github.com/repos/Jiiks/ExampleRepository/releases/assets/10023265'
|
||||
}
|
||||
}
|
||||
|
||||
static get dummyFiles() {
|
||||
return {
|
||||
readme: 'Example/readme.md',
|
||||
previews: [{
|
||||
large: 'Example/preview1-big.png',
|
||||
thumb: 'Example/preview1-small.png'
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
static get dummyAuthor() {
|
||||
return 'Someone';
|
||||
}
|
||||
|
||||
static randomId() {
|
||||
return btoa(Math.random()).substring(3, 9);
|
||||
}
|
||||
|
||||
static randomTimestamp() {
|
||||
return `2018-${Math.floor((Math.random() * 12) + 1).toString().padStart(2, '0')}-${Math.floor((Math.random() * 30) + 1).toString().padStart(2, '0')}T14:51:32.057Z`;
|
||||
}
|
||||
|
||||
static randomVersion() {
|
||||
return `${Math.round(Math.random() * 3)}.${Math.round(Math.random() * 10)}.${Math.round(Math.random() * 10)}`;
|
||||
}
|
||||
}
|
|
@ -8,17 +8,16 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { DOM, BdUI, BdMenu, Modals, Toasts, Notifications, BdContextMenu, DiscordContextMenu } from 'ui';
|
||||
import { DOM, BdUI, BdMenu, Modals, Toasts, Notifications, BdContextMenu, DiscordContextMenu, Autocomplete } from 'ui';
|
||||
import BdCss from './styles/index.scss';
|
||||
import { Events, CssEditor, Globals, Settings, Database, Updater, ModuleManager, PluginManager, ThemeManager, ExtModuleManager, Vendor, Patcher, MonkeyPatch, ReactComponents, ReactHelpers, ReactAutoPatcher, DiscordApi, BdWebApi, Connectivity, Cache, Reflection } from 'modules';
|
||||
import { ClientLogger as Logger, ClientIPC, Utils } from 'common';
|
||||
import { Events, Globals, Settings, Database, Updater, ModuleManager, PluginManager, ThemeManager, ExtModuleManager, Vendor, Patcher, MonkeyPatch, ReactComponents, ReactHelpers, ReactAutoPatcher, DiscordApi, BdWebApi, Connectivity, Cache, Reflection, PackageInstaller } from 'modules';
|
||||
import { ClientLogger as Logger, ClientIPC, Utils, Axi } from 'common';
|
||||
import { BuiltinManager, EmoteModule, ReactDevtoolsModule, VueDevtoolsModule, TrackingProtection, E2EE } from 'builtin';
|
||||
import electron from 'electron';
|
||||
import path from 'path';
|
||||
import { setTimeout } from 'timers';
|
||||
|
||||
const tests = typeof PRODUCTION === 'undefined';
|
||||
const ignoreExternal = false;
|
||||
const ignoreExternal = tests && true;
|
||||
|
||||
class BetterDiscord {
|
||||
|
||||
|
@ -28,18 +27,18 @@ class BetterDiscord {
|
|||
Logger.log('main', 'BetterDiscord starting');
|
||||
|
||||
this._bd = {
|
||||
DOM, BdUI, BdMenu, Modals, Reflection, Toasts, Notifications, BdContextMenu, DiscordContextMenu,
|
||||
DOM, BdUI, BdMenu, Modals, Reflection, Toasts, Notifications, BdContextMenu, DiscordContextMenu, Autocomplete,
|
||||
|
||||
Events, CssEditor, Globals, Settings, Database, Updater,
|
||||
ModuleManager, PluginManager, ThemeManager, ExtModuleManager,
|
||||
Events, Globals, Settings, Database, Updater,
|
||||
ModuleManager, PluginManager, ThemeManager, ExtModuleManager, PackageInstaller,
|
||||
Vendor,
|
||||
|
||||
Patcher, MonkeyPatch, ReactComponents, ReactHelpers, ReactAutoPatcher, DiscordApi,
|
||||
EmoteModule,
|
||||
BuiltinManager, EmoteModule,
|
||||
BdWebApi,
|
||||
Connectivity,
|
||||
Cache,
|
||||
Logger, ClientIPC, Utils,
|
||||
Logger, ClientIPC, Utils, Axi,
|
||||
|
||||
plugins: PluginManager.localContent,
|
||||
themes: ThemeManager.localContent,
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import ServerEmu from '../dev/serveremu';
|
||||
|
||||
import { request } from 'vendor';
|
||||
|
||||
const APIBASE = 'ifyouareinwebtestthenyouknowwhatthisshouldbe'; // Do not push
|
||||
|
@ -27,6 +29,12 @@ export default class BdWebApi {
|
|||
};
|
||||
}
|
||||
|
||||
static get plugins() {
|
||||
return {
|
||||
get: this.getPlugins
|
||||
};
|
||||
}
|
||||
|
||||
static get users() {
|
||||
return {
|
||||
get: this.getUsers
|
||||
|
@ -41,11 +49,19 @@ export default class BdWebApi {
|
|||
}
|
||||
|
||||
static getThemes(args) {
|
||||
return ServerEmu.themes(args);
|
||||
// return dummyThemes();
|
||||
/*
|
||||
if (!args) return request.get(ENDPOINTS.themes);
|
||||
const { id } = args;
|
||||
if (id) return request.get(ENDPOINTS.theme(id));
|
||||
|
||||
return request.get(ENDPOINTS.themes);
|
||||
*/
|
||||
}
|
||||
|
||||
static getPlugins(args) {
|
||||
return ServerEmu.plugins(args);
|
||||
}
|
||||
|
||||
static getUsers(args) {
|
||||
|
|
|
@ -65,6 +65,10 @@ export default class Content extends AsyncEventEmitter {
|
|||
get config() { return this.settings.categories }
|
||||
get data() { return this.userConfig.data || (this.userConfig.data = {}) }
|
||||
|
||||
get packed() { return this.dirName.packed }
|
||||
get packagePath() { return this.dirName.packagePath }
|
||||
get packageName() { return this.dirName.pkg }
|
||||
|
||||
/**
|
||||
* Opens a settings modal for this content.
|
||||
* @return {Modal}
|
||||
|
|
|
@ -8,14 +8,19 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import asar from 'asar';
|
||||
import path, { dirname } from 'path';
|
||||
import rimraf from 'rimraf';
|
||||
|
||||
import { remote } from 'electron';
|
||||
import Content from './content';
|
||||
import Globals from './globals';
|
||||
import Database from './database';
|
||||
import { Utils, FileUtils, ClientLogger as Logger } from 'common';
|
||||
import { SettingsSet, ErrorEvent } from 'structs';
|
||||
import { Modals } from 'ui';
|
||||
import path from 'path';
|
||||
import Combokeys from 'combokeys';
|
||||
import Settings from './settings';
|
||||
|
||||
/**
|
||||
* Base class for managing external content
|
||||
|
@ -67,6 +72,28 @@ export default class {
|
|||
return Globals.getPath(this.pathId);
|
||||
}
|
||||
|
||||
static async packContent(path, contentPath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
remote.dialog.showSaveDialog({
|
||||
title: 'Save Package',
|
||||
defaultPath: path,
|
||||
filters: [
|
||||
{
|
||||
name: 'BetterDiscord Package',
|
||||
extensions: ['bd']
|
||||
}
|
||||
]
|
||||
}, filepath => {
|
||||
if (!filepath) return;
|
||||
|
||||
asar.uncache(filepath);
|
||||
asar.createPackage(contentPath, filepath, () => {
|
||||
resolve(filepath);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all locally stored content.
|
||||
* @param {bool} suppressErrors Suppress any errors that occur during loading of content
|
||||
|
@ -77,12 +104,20 @@ export default class {
|
|||
const directories = await FileUtils.listDirectory(this.contentPath);
|
||||
|
||||
for (const dir of directories) {
|
||||
try {
|
||||
await FileUtils.directoryExists(path.join(this.contentPath, dir));
|
||||
} catch (err) { continue; }
|
||||
const packed = dir.endsWith('.bd');
|
||||
|
||||
if (!packed) {
|
||||
try {
|
||||
await FileUtils.directoryExists(path.join(this.contentPath, dir));
|
||||
} catch (err) { continue; }
|
||||
}
|
||||
|
||||
try {
|
||||
await this.preloadContent(dir);
|
||||
if (packed) {
|
||||
await this.preloadPackedContent(dir);
|
||||
} else {
|
||||
await this.preloadContent(dir);
|
||||
}
|
||||
} catch (err) {
|
||||
this.errors.push(new ErrorEvent({
|
||||
module: this.moduleName,
|
||||
|
@ -120,6 +155,8 @@ export default class {
|
|||
const directories = await FileUtils.listDirectory(this.contentPath);
|
||||
|
||||
for (const dir of directories) {
|
||||
const packed = dir.endsWith('.bd');
|
||||
|
||||
// If content is already loaded this should resolve
|
||||
if (this.getContentByDirName(dir)) continue;
|
||||
|
||||
|
@ -173,6 +210,31 @@ export default class {
|
|||
}
|
||||
}
|
||||
|
||||
static async preloadPackedContent(pkg, reload = false, index) {
|
||||
try {
|
||||
const packagePath = path.join(this.contentPath, pkg);
|
||||
const packageName = pkg.replace('.bd', '');
|
||||
await FileUtils.fileExists(packagePath);
|
||||
|
||||
const config = JSON.parse(asar.extractFile(packagePath, 'config.json').toString());
|
||||
const unpackedPath = path.join(Globals.getPath('tmp'), packageName);
|
||||
|
||||
asar.extractAll(packagePath, unpackedPath);
|
||||
|
||||
return this.preloadContent({
|
||||
config,
|
||||
contentPath: unpackedPath,
|
||||
packagePath: packagePath,
|
||||
pkg,
|
||||
packageName,
|
||||
packed: true
|
||||
}, reload, index);
|
||||
} catch (err) {
|
||||
Logger.log('ContentManager', ['Error extracting packed content', err]);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common loading procedure for loading content before passing it to the actual loader
|
||||
* @param {any} dirName Base directory for content
|
||||
|
@ -181,7 +243,15 @@ export default class {
|
|||
*/
|
||||
static async preloadContent(dirName, reload = false, index) {
|
||||
try {
|
||||
const contentPath = path.join(this.contentPath, dirName);
|
||||
const unsafeAllowed = Settings.getSetting('security', 'default', 'unsafe-content').value;
|
||||
const packed = typeof dirName === 'object' && dirName.packed;
|
||||
|
||||
// Block any unpacked content as they can't be verified
|
||||
if (!packed && !unsafeAllowed) {
|
||||
throw 'Blocked unsafe content';
|
||||
}
|
||||
|
||||
const contentPath = packed ? dirName.contentPath : path.join(this.contentPath, dirName);
|
||||
|
||||
await FileUtils.directoryExists(contentPath);
|
||||
|
||||
|
@ -189,7 +259,7 @@ export default class {
|
|||
throw { 'message': `Attempted to load already loaded user content: ${path}` };
|
||||
|
||||
const configPath = path.resolve(contentPath, 'config.json');
|
||||
const readConfig = await FileUtils.readJsonFromFile(configPath);
|
||||
const readConfig = packed ? dirName.config : await FileUtils.readJsonFromFile(configPath);
|
||||
const mainPath = path.join(contentPath, readConfig.main || 'index.js');
|
||||
|
||||
const defaultConfig = new SettingsSet({
|
||||
|
@ -213,7 +283,7 @@ export default class {
|
|||
}
|
||||
} catch (err) {
|
||||
// We don't care if this fails it either means that user config doesn't exist or there's something wrong with it so we revert to default config
|
||||
Logger.warn(this.moduleName, [`Failed reading config for ${this.contentType} ${readConfig.info.name} in ${dirName}`, err]);
|
||||
Logger.warn(this.moduleName, [`Failed reading config for ${this.contentType} ${readConfig.info.name} in ${packed ? dirName.pkg : dirName}`, err]);
|
||||
}
|
||||
|
||||
userConfig.config = defaultConfig.clone({ settings: userConfig.config });
|
||||
|
@ -243,10 +313,10 @@ export default class {
|
|||
mainPath
|
||||
};
|
||||
|
||||
const content = await this.loadContent(paths, configs, readConfig.info, readConfig.main, readConfig.dependencies, readConfig.permissions, readConfig.mainExport);
|
||||
const content = await this.loadContent(paths, configs, readConfig.info, readConfig.main, readConfig.dependencies, readConfig.permissions, readConfig.mainExport, packed ? dirName : false);
|
||||
if (!content) return undefined;
|
||||
if (!reload && this.getContentById(content.id))
|
||||
throw {message: `A ${this.contentType} with the ID ${content.id} already exists.`};
|
||||
throw { message: `A ${this.contentType} with the ID ${content.id} already exists.` };
|
||||
|
||||
if (reload) this.localContent.splice(index, 1, content);
|
||||
else this.localContent.push(content);
|
||||
|
@ -278,6 +348,7 @@ export default class {
|
|||
await unload;
|
||||
|
||||
await FileUtils.recursiveDeleteDirectory(content.paths.contentPath);
|
||||
if (content.packed) await FileUtils.recursiveDeleteDirectory(content.packagePath);
|
||||
return true;
|
||||
} catch (err) {
|
||||
Logger.err(this.moduleName, err);
|
||||
|
@ -309,14 +380,7 @@ export default class {
|
|||
|
||||
if (this.unloadContentHook) this.unloadContentHook(content);
|
||||
|
||||
if (reload) {
|
||||
const newcontent = await this.preloadContent(content.dirName, true, index);
|
||||
if (newcontent.enabled) {
|
||||
newcontent.userConfig.enabled = false;
|
||||
newcontent.start(false);
|
||||
}
|
||||
return newcontent;
|
||||
}
|
||||
if (reload) return content.packed ? this.preloadPackedContent(content.packagePath, true, index) : this.preloadContent(content.dirName, true, index);
|
||||
|
||||
this.localContent.splice(index, 1);
|
||||
} catch (err) {
|
||||
|
|
|
@ -38,6 +38,14 @@ export default new class {
|
|||
ClientIPC.on('bd-get-scss', () => this.scss, true);
|
||||
ClientIPC.on('bd-update-scss', (e, scss) => this.updateScss(scss));
|
||||
ClientIPC.on('bd-save-csseditor-bounds', (e, bounds) => this.saveEditorBounds(bounds));
|
||||
ClientIPC.on('bd-editor-runScript', (e, script) => {
|
||||
try {
|
||||
new Function(script)();
|
||||
e.reply('ok');
|
||||
} catch (err) {
|
||||
e.reply({ err: err.stack || err });
|
||||
}
|
||||
});
|
||||
|
||||
ClientIPC.on('bd-save-scss', async (e, scss) => {
|
||||
await this.updateScss(scss);
|
||||
|
|
|
@ -126,6 +126,7 @@ export default class DiscordApi {
|
|||
static get currentGuild() {
|
||||
const guild = Modules.GuildStore.getGuild(Modules.SelectedGuildStore.getGuildId());
|
||||
if (guild) return Guild.from(guild);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -135,6 +136,7 @@ export default class DiscordApi {
|
|||
static get currentChannel() {
|
||||
const channel = Modules.ChannelStore.getChannel(Modules.SelectedChannelStore.getChannelId());
|
||||
if (channel) return Channel.from(channel);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -144,6 +146,7 @@ export default class DiscordApi {
|
|||
static get currentUser() {
|
||||
const user = Modules.UserStore.getCurrentUser();
|
||||
if (user) return User.from(user);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
* BetterDiscord Editor Module
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import Module from './imodule';
|
||||
import { DOM } from 'ui';
|
||||
|
||||
export default new class extends Module {
|
||||
|
||||
get name() { return 'Editor' }
|
||||
get delay() { return false; }
|
||||
|
||||
setInitialState(state) {
|
||||
return {
|
||||
editorBounds: undefined
|
||||
};
|
||||
}
|
||||
|
||||
initialize() {
|
||||
super.initialize();
|
||||
(async () => {
|
||||
try {
|
||||
// TODO this is temporary
|
||||
const userScss = await this.send('readDataFile', 'user.scss');
|
||||
const compiled = await this.send('compileSass', { data: userScss });
|
||||
this.injectStyle('customcss', compiled.css.toString());
|
||||
} catch (err) {
|
||||
console.warn('SCSS Compilation error', err);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
events(ipc) {
|
||||
ipc.on('editor-runScript', (e, script) => {
|
||||
try {
|
||||
new Function(script)();
|
||||
e.reply('ok');
|
||||
} catch (err) {
|
||||
e.reply({ err: err.stack || err });
|
||||
}
|
||||
});
|
||||
|
||||
ipc.on('editor-injectStyle', (e, { id, style }) => {
|
||||
this.injectStyle(id, style);
|
||||
e.reply('ok');
|
||||
});
|
||||
}
|
||||
|
||||
injectStyle(id, style) {
|
||||
return DOM.injectStyle(style, `userstyle-${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show editor, flashes if already visible.
|
||||
*/
|
||||
async show() {
|
||||
await this.send('editor-open', this.state.editorBounds);
|
||||
}
|
||||
|
||||
}
|
|
@ -8,6 +8,7 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import sparkplug from 'sparkplug';
|
||||
import { ClientIPC } from 'common';
|
||||
import Module from './module';
|
||||
|
@ -97,7 +98,7 @@ export default new class extends Module {
|
|||
}
|
||||
|
||||
get version() {
|
||||
return this.config.version;
|
||||
return this.config.versions.core;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* BetterDiscord Module Base
|
||||
* Copyright (c) 2015-present JsSucks - https://github.com/JsSucks
|
||||
* All rights reserved.
|
||||
* https://github.com/JsSucks - https://betterdiscord.net
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base Module that every non-static module should extend
|
||||
*/
|
||||
|
||||
import { ClientLogger as Logger, ClientIPC } from 'common';
|
||||
|
||||
export default class Module {
|
||||
|
||||
constructor(args) {
|
||||
this.__ = {
|
||||
state: args || {},
|
||||
args
|
||||
};
|
||||
this.setState = this.setState.bind(this);
|
||||
|
||||
if (this.delay) { // If delay is set then module is set to load delayed from modulemanager
|
||||
this.initialize = this.initialize.bind(this);
|
||||
this.init = this.initialize;
|
||||
} else {
|
||||
this.initialize();
|
||||
this.init = () => { };
|
||||
}
|
||||
}
|
||||
|
||||
initialize() {
|
||||
if (this.bindings) this.bindings();
|
||||
if (this.setInitialState) this.setState(this.setInitialState(this.state));
|
||||
if (this.events) this.events(ClientIPC);
|
||||
}
|
||||
|
||||
setState(newState) {
|
||||
const oldState = Object.assign({}, this.state);
|
||||
Object.assign(this.state, newState);
|
||||
if (this.stateChanged) this.stateChanged(oldState, newState);
|
||||
}
|
||||
|
||||
set args(t) { }
|
||||
get args() { return this.__.args; }
|
||||
|
||||
set state(state) { return this.__.state = state; }
|
||||
get state() { return this.__.state; }
|
||||
|
||||
async send(channel, message) {
|
||||
return ClientIPC.send(channel, message);
|
||||
}
|
||||
|
||||
log(msg) {
|
||||
Logger.log(this.name, msg);
|
||||
}
|
||||
|
||||
warn(msg) {
|
||||
Logger.log(this.name, msg);
|
||||
}
|
||||
|
||||
err(msg) {
|
||||
Logger.log(this.name, msg);
|
||||
}
|
||||
|
||||
info(msg) {
|
||||
Logger.log(this.name, msg);
|
||||
}
|
||||
|
||||
}
|
|
@ -9,7 +9,7 @@
|
|||
*/
|
||||
|
||||
import { ClientLogger as Logger } from 'common';
|
||||
import { SocketProxy, EventHook, CssEditor } from 'modules';
|
||||
import { SocketProxy, EventHook } from 'modules';
|
||||
import { ProfileBadges, ClassNormaliser } from 'ui';
|
||||
import Updater from './updater';
|
||||
|
||||
|
@ -27,7 +27,6 @@ export default class {
|
|||
new ClassNormaliser(),
|
||||
new SocketProxy(),
|
||||
new EventHook(),
|
||||
CssEditor,
|
||||
Updater
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
export { default as Events } from './events';
|
||||
export { default as CssEditor } from './csseditor';
|
||||
export { default as Editor } from './editor';
|
||||
export { default as Globals } from './globals';
|
||||
export { default as Settings } from './settings';
|
||||
export { default as Database } from './database';
|
||||
|
@ -27,3 +28,4 @@ export { default as Connectivity } from './connectivity';
|
|||
export { default as Security } from './security';
|
||||
export { default as Cache } from './cache';
|
||||
export { default as Reflection } from './reflection/index';
|
||||
export { default as PackageInstaller } from './packageinstaller';
|
||||
|
|
|
@ -0,0 +1,178 @@
|
|||
import EventListener from './eventlistener';
|
||||
import asar from 'asar';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import rimraf from 'rimraf';
|
||||
|
||||
import { request } from 'vendor';
|
||||
import { Modals } from 'ui';
|
||||
import { Utils, FileUtils } from 'common';
|
||||
import PluginManager from './pluginmanager';
|
||||
import Globals from './globals';
|
||||
import Security from './security';
|
||||
import Reflection from './reflection';
|
||||
import DiscordApi from './discordapi';
|
||||
import ThemeManager from './thememanager';
|
||||
import { MonkeyPatch } from './patcher';
|
||||
import { DOM } from 'ui';
|
||||
|
||||
export default class PackageInstaller {
|
||||
|
||||
/**
|
||||
* Handler for drag and drop package install
|
||||
* @param {String} filePath Path to local file
|
||||
* @param {Boolean} canUpload If the user can upload files in current window
|
||||
* @returns {Number} returns action code from modal
|
||||
*/
|
||||
static async dragAndDropHandler(filePath, canUpload) {
|
||||
try {
|
||||
const config = JSON.parse(asar.extractFile(filePath, 'config.json').toString());
|
||||
const { info, main } = config;
|
||||
|
||||
let icon = null;
|
||||
if (info.icon && info.icon_type) {
|
||||
const extractIcon = asar.extractFile(filePath, info.icon);
|
||||
icon = `data:${info.icon_type};base64,${Utils.arrayBufferToBase64(extractIcon)}`;
|
||||
}
|
||||
|
||||
const isPlugin = info.type && info.type === 'plugin' || main.endsWith('.js');
|
||||
|
||||
// Show install modal
|
||||
const modalResult = await Modals.installModal(isPlugin ? 'plugin' : 'theme', config, filePath, icon, canUpload).promise;
|
||||
return modalResult;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash and verify a package
|
||||
* @param {Byte[]|String} bytesOrPath byte array of binary or path to local file
|
||||
* @param {String} id Package id
|
||||
*/
|
||||
static async verifyPackage(bytesOrPath, id) {
|
||||
const bytes = typeof bytesOrPath === 'string' ? fs.readFileSync(bytesOrPath) : bytesOrPath;
|
||||
// Temporary hash to simulate response from server
|
||||
const tempVerified = ['2e3532ee366816adc37b0f478bfef35e03f96e7aeee9b115f5918ef6a4e94de8', '06a2eb4e37b926354ab80cd83207db67e544c932e9beddce545967a21f8db5aa'];
|
||||
const hashBytes = Security.hash('sha256', bytes, 'hex');
|
||||
|
||||
return tempVerified.includes(hashBytes);
|
||||
}
|
||||
|
||||
// TODO lots of stuff
|
||||
/**
|
||||
* Installs or updates defined package
|
||||
* @param {Byte[]|String} bytesOrPath byte array of binary or path to local file
|
||||
* @param {String} nameOrId Package name
|
||||
* @param {Boolean} update Does an older version already exist
|
||||
*/
|
||||
static async installPackage(bytesOrPath, nameOrId, contentType, update = false) {
|
||||
let outputPath = null;
|
||||
try {
|
||||
|
||||
const bytes = typeof bytesOrPath === 'string' ? fs.readFileSync(bytesOrPath) : bytesOrPath;
|
||||
const outputName = `${nameOrId}.bd`;
|
||||
|
||||
outputPath = path.join(Globals.getPath(`${contentType}s`), outputName);
|
||||
fs.writeFileSync(outputPath, bytes);
|
||||
|
||||
const manager = contentType === 'plugin' ? PluginManager : ThemeManager;
|
||||
|
||||
if (!update) return manager.preloadPackedContent(outputName);
|
||||
|
||||
const oldContent = manager.findContent(nameOrId);
|
||||
|
||||
await oldContent.unload(true);
|
||||
|
||||
if (oldContent.packed && oldContent.packageName !== nameOrId) {
|
||||
await FileUtils.deleteFile(oldContent.packagePath).catch(err => null);
|
||||
}
|
||||
await FileUtils.recursiveDeleteDirectory(oldContent.contentPath).catch(err => null);
|
||||
|
||||
return manager.preloadPackedContent(outputName);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Install package from remote location. Only github/bdapi is supported.
|
||||
* @param {String} remoteLocation Remote resource location
|
||||
*/
|
||||
static async installRemotePackage(remoteLocation) {
|
||||
try {
|
||||
const { hostname } = Object.assign(document.createElement('a'), { href: remoteLocation });
|
||||
if (hostname !== 'api.github.com' && hostname !== 'secretbdapi') throw 'Invalid host!';
|
||||
|
||||
const options = {
|
||||
uri: remoteLocation,
|
||||
encoding: null,
|
||||
headers: {
|
||||
'User-Agent': 'BetterDiscordClient',
|
||||
'Accept': 'application/octet-stream'
|
||||
}
|
||||
};
|
||||
|
||||
const response = await request.get(options);
|
||||
const outputPath = path.join(Globals.getPath('tmp'), Security.hash('sha256', response, 'hex'));
|
||||
fs.writeFileSync(outputPath, response);
|
||||
console.log('response', response);
|
||||
console.log('output', outputPath);
|
||||
|
||||
await this.dragAndDropHandler(outputPath);
|
||||
rimraf(outputPath, err => {
|
||||
if (err) console.log(err);
|
||||
});
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
static async handleDrop(stateNode, e, original) {
|
||||
if (!e.dataTransfer.files.length || !e.dataTransfer.files[0].name.endsWith('.bd')) return original && original.call(stateNode, e);
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
if (stateNode) stateNode.clearDragging();
|
||||
|
||||
const currentChannel = DiscordApi.currentChannel;
|
||||
const canUpload = currentChannel ?
|
||||
currentChannel.checkPermissions(Reflection.modules.DiscordConstants.Permissions.SEND_MESSAGES) &&
|
||||
currentChannel.checkPermissions(Reflection.modules.DiscordConstants.Permissions.ATTACH_FILES) : false;
|
||||
|
||||
const files = Array.from(e.dataTransfer.files).slice(0);
|
||||
const actionCode = await this.dragAndDropHandler(e.dataTransfer.files[0].path, canUpload);
|
||||
|
||||
if (actionCode === 0 && stateNode) stateNode.promptToUpload(files, currentChannel.id, true, !e.shiftKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Patches Discord upload area for .bd files
|
||||
*/
|
||||
static async uploadAreaPatch(UploadArea) {
|
||||
// Add a listener to root for when not in a channel
|
||||
const root = DOM.getElement('#app-mount');
|
||||
const rootHandleDrop = this.handleDrop.bind(this, undefined);
|
||||
root.addEventListener('drop', rootHandleDrop);
|
||||
|
||||
const unpatchUploadAreaHandleDrop = MonkeyPatch('BD:ReactComponents', UploadArea.component.prototype).instead('handleDrop', (component, [e], original) => this.handleDrop(component, e, original));
|
||||
|
||||
this.unpatchUploadArea = () => {
|
||||
unpatchUploadAreaHandleDrop();
|
||||
root.removeEventListener('drop', rootHandleDrop);
|
||||
this.unpatchUploadArea = undefined;
|
||||
};
|
||||
|
||||
for (const element of document.querySelectorAll(UploadArea.important.selector)) {
|
||||
const stateNode = Reflection.DOM(element).getComponentStateNode(UploadArea);
|
||||
|
||||
element.removeEventListener('drop', stateNode.handleDrop);
|
||||
stateNode.handleDrop = UploadArea.component.prototype.handleDrop.bind(stateNode);
|
||||
element.addEventListener('drop', stateNode.handleDrop);
|
||||
|
||||
stateNode.forceUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -74,7 +74,7 @@ export default class extends ContentManager {
|
|||
static get refreshPlugins() { return this.refreshContent }
|
||||
|
||||
static get loadContent() { return this.loadPlugin }
|
||||
static async loadPlugin(paths, configs, info, main, dependencies, permissions, mainExport) {
|
||||
static async loadPlugin(paths, configs, info, main, dependencies, permissions, mainExport, packed = false) {
|
||||
if (permissions && permissions.length > 0) {
|
||||
for (const perm of permissions) {
|
||||
Logger.log(this.moduleName, `Permission: ${Permissions.permissionText(perm).HEADER} - ${Permissions.permissionText(perm).BODY}`);
|
||||
|
@ -109,12 +109,7 @@ export default class extends ContentManager {
|
|||
throw {message: `Plugin ${info.name} did not return a class that extends Plugin.`};
|
||||
|
||||
const instance = new plugin({
|
||||
configs, info, main,
|
||||
paths: {
|
||||
contentPath: paths.contentPath,
|
||||
dirName: paths.dirName,
|
||||
mainPath: paths.mainPath
|
||||
}
|
||||
configs, info, main, paths
|
||||
});
|
||||
|
||||
if (instance.enabled && this.loaded) {
|
||||
|
|
|
@ -15,6 +15,7 @@ import { Utils, Filters, ClientLogger as Logger } from 'common';
|
|||
import { MonkeyPatch } from './patcher';
|
||||
import Reflection from './reflection/index';
|
||||
import DiscordApi from './discordapi';
|
||||
import PackageInstaller from './packageinstaller';
|
||||
|
||||
class Helpers {
|
||||
static get plannedActions() {
|
||||
|
@ -130,7 +131,8 @@ class Helpers {
|
|||
static findByProp(obj, what, value) {
|
||||
if (obj.hasOwnProperty(what) && obj[what] === value) return obj;
|
||||
if (obj.props && !obj.children) return this.findByProp(obj.props, what, value);
|
||||
if (!obj.children || !obj.children.length) return null;
|
||||
if (!obj.children) return null;
|
||||
if (!(obj.children instanceof Array)) return this.findByProp(obj.children, what, value);
|
||||
for (const child of obj.children) {
|
||||
if (!child) continue;
|
||||
const findInChild = this.findByProp(child, what, value);
|
||||
|
@ -171,9 +173,18 @@ class ReactComponent {
|
|||
this.important = important;
|
||||
}
|
||||
|
||||
get elements() {
|
||||
if (!this.important || !this.important.selector) return [];
|
||||
|
||||
return document.querySelectorAll(this.important.selector);
|
||||
}
|
||||
|
||||
get stateNodes() {
|
||||
return [...this.elements].map(e => Reflection.DOM(e).getComponentStateNode(this));
|
||||
}
|
||||
|
||||
forceUpdateAll() {
|
||||
if (!this.important || !this.important.selector) return;
|
||||
for (const e of document.querySelectorAll(this.important.selector)) {
|
||||
for (const e of this.elements) {
|
||||
Reflection.DOM(e).forceUpdate(this);
|
||||
}
|
||||
}
|
||||
|
@ -184,6 +195,7 @@ export class ReactComponents {
|
|||
static get unknownComponents() { return this._unknownComponents || (this._unknownComponents = []) }
|
||||
static get listeners() { return this._listeners || (this._listeners = []) }
|
||||
static get nameSetters() { return this._nameSetters || (this._nameSetters = []) }
|
||||
static get componentAliases() { return this._componentAliases || (this._componentAliases = []) }
|
||||
|
||||
static get ReactComponent() { return ReactComponent }
|
||||
|
||||
|
@ -220,6 +232,8 @@ export class ReactComponents {
|
|||
* @return {Promise => ReactComponent}
|
||||
*/
|
||||
static async getComponent(name, important, filter) {
|
||||
name = this.getComponentName(name);
|
||||
|
||||
const have = this.components.find(c => c.id === name);
|
||||
if (have) return have;
|
||||
|
||||
|
@ -237,7 +251,13 @@ export class ReactComponents {
|
|||
let component, reflect;
|
||||
for (const element of elements) {
|
||||
reflect = Reflection.DOM(element);
|
||||
component = filter ? reflect.components.find(filter) : reflect.component;
|
||||
component = filter ? reflect.components.find(component => {
|
||||
try {
|
||||
return filter.call(undefined, component);
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}) : reflect.component;
|
||||
if (component) break;
|
||||
}
|
||||
|
||||
|
@ -274,6 +294,19 @@ export class ReactComponents {
|
|||
});
|
||||
}
|
||||
|
||||
static getComponentName(name) {
|
||||
const resolvedAliases = [];
|
||||
|
||||
while (this.componentAliases[name]) {
|
||||
resolvedAliases.push(name);
|
||||
name = this.componentAliases[name];
|
||||
|
||||
if (resolvedAliases.includes(name)) break;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
static setName(name, filter) {
|
||||
const have = this.components.find(c => c.id === name);
|
||||
if (have) return have;
|
||||
|
@ -349,6 +382,21 @@ export class ReactAutoPatcher {
|
|||
this.Message.forceUpdateAll();
|
||||
}
|
||||
|
||||
static async patchMessageContent() {
|
||||
const { selector } = Reflection.resolve('container', 'containerCozy', 'containerCompact', 'edited');
|
||||
this.MessageContent = await ReactComponents.getComponent('MessageContent', {selector}, c => c.defaultProps && c.defaultProps.hasOwnProperty('disableButtons'));
|
||||
}
|
||||
|
||||
static async patchSpoiler() {
|
||||
const { selector } = Reflection.resolve('spoilerText', 'spoilerContainer');
|
||||
this.Spoiler = await ReactComponents.getComponent('Spoiler', {selector}, c => c.prototype.renderSpoilerText);
|
||||
}
|
||||
|
||||
static async patchMessageAccessories() {
|
||||
const { selector } = Reflection.resolve('container', 'containerCozy', 'embedWrapper');
|
||||
this.MessageAccessories = await ReactComponents.getComponent('MessageAccessories', {selector});
|
||||
}
|
||||
|
||||
static async patchMessageGroup() {
|
||||
const { selector } = Reflection.resolve('container', 'message', 'messageCozy');
|
||||
this.MessageGroup = await ReactComponents.getComponent('MessageGroup', {selector});
|
||||
|
@ -367,7 +415,16 @@ export class ReactAutoPatcher {
|
|||
this.MessageGroup.forceUpdateAll();
|
||||
}
|
||||
|
||||
static async patchImageWrapper() {
|
||||
ReactComponents.componentAliases.ImageWrapper = 'Image';
|
||||
|
||||
const { selector } = Reflection.resolve('imageWrapper');
|
||||
this.ImageWrapper = await ReactComponents.getComponent('ImageWrapper', {selector}, c => typeof c.defaultProps.children === 'function');
|
||||
}
|
||||
|
||||
static async patchChannelMember() {
|
||||
ReactComponents.componentAliases.ChannelMember = 'MemberListItem';
|
||||
|
||||
const { selector } = Reflection.resolve('member', 'memberInner', 'activity');
|
||||
this.ChannelMember = await ReactComponents.getComponent('ChannelMember', {selector}, m => m.prototype.renderActivity);
|
||||
|
||||
|
@ -383,8 +440,13 @@ export class ReactAutoPatcher {
|
|||
this.ChannelMember.forceUpdateAll();
|
||||
}
|
||||
|
||||
static async patchNameTag() {
|
||||
const { selector } = Reflection.resolve('nameTag', 'username', 'discriminator', 'ownerIcon');
|
||||
this.NameTag = await ReactComponents.getComponent('NameTag', {selector});
|
||||
}
|
||||
|
||||
static async patchGuild() {
|
||||
const selector = `div.${Reflection.resolve('guild', 'guildsWrapper').className}:not(:first-child)`;
|
||||
const selector = `div.${Reflection.resolve('container', 'guildIcon', 'selected', 'unread').className}:not(:first-child)`;
|
||||
this.Guild = await ReactComponents.getComponent('Guild', {selector}, m => m.prototype.renderBadge);
|
||||
|
||||
this.unpatchGuild = MonkeyPatch('BD:ReactComponents', this.Guild.component.prototype).after('render', (component, args, retVal) => {
|
||||
|
@ -401,7 +463,7 @@ export class ReactAutoPatcher {
|
|||
* The Channel component contains the header, message scroller, message form and member list.
|
||||
*/
|
||||
static async patchChannel() {
|
||||
const selector = '.chat';
|
||||
const { selector } = Reflection.resolve('chat', 'title', 'channelName');
|
||||
this.Channel = await ReactComponents.getComponent('Channel', {selector});
|
||||
|
||||
this.unpatchChannel = MonkeyPatch('BD:ReactComponents', this.Channel.component.prototype).after('render', (component, args, retVal) => {
|
||||
|
@ -417,10 +479,17 @@ export class ReactAutoPatcher {
|
|||
this.Channel.forceUpdateAll();
|
||||
}
|
||||
|
||||
static async patchChannelTextArea() {
|
||||
const { selector } = Reflection.resolve('channelTextArea', 'autocomplete');
|
||||
this.ChannelTextArea = await ReactComponents.getComponent('ChannelTextArea', {selector});
|
||||
}
|
||||
|
||||
/**
|
||||
* The GuildTextChannel component represents a text channel in the guild channel list.
|
||||
*/
|
||||
static async patchGuildTextChannel() {
|
||||
ReactComponents.componentAliases.GuildTextChannel = 'TextChannel';
|
||||
|
||||
const { selector } = Reflection.resolve('containerDefault', 'actionIcon');
|
||||
this.GuildTextChannel = await ReactComponents.getComponent('GuildTextChannel', {selector}, c => c.prototype.renderMentionBadge);
|
||||
|
||||
|
@ -433,6 +502,8 @@ export class ReactAutoPatcher {
|
|||
* The GuildVoiceChannel component represents a voice channel in the guild channel list.
|
||||
*/
|
||||
static async patchGuildVoiceChannel() {
|
||||
ReactComponents.componentAliases.GuildVoiceChannel = 'VoiceChannel';
|
||||
|
||||
const { selector } = Reflection.resolve('containerDefault', 'actionIcon');
|
||||
this.GuildVoiceChannel = await ReactComponents.getComponent('GuildVoiceChannel', {selector}, c => c.prototype.handleVoiceConnect);
|
||||
|
||||
|
@ -445,7 +516,9 @@ export class ReactAutoPatcher {
|
|||
* The DirectMessage component represents a channel in the direct messages list.
|
||||
*/
|
||||
static async patchDirectMessage() {
|
||||
const selector = '.channel.private';
|
||||
ReactComponents.componentAliases.DirectMessage = 'PrivateChannel';
|
||||
|
||||
const { selector } = Reflection.resolve('channel', 'avatar', 'name');
|
||||
this.DirectMessage = await ReactComponents.getComponent('DirectMessage', {selector}, c => c.prototype.renderAvatar);
|
||||
|
||||
this.unpatchDirectMessage = MonkeyPatch('BD:ReactComponents', this.DirectMessage.component.prototype).after('render', this._afterChannelRender);
|
||||
|
@ -467,15 +540,18 @@ export class ReactAutoPatcher {
|
|||
}
|
||||
|
||||
static async patchUserProfileModal() {
|
||||
ReactComponents.componentAliases.UserProfileModal = 'UserProfileBody';
|
||||
|
||||
const { selector } = Reflection.resolve('root', 'topSectionNormal');
|
||||
this.UserProfileModal = await ReactComponents.getComponent('UserProfileModal', {selector}, Filters.byPrototypeFields(['renderHeader', 'renderBadges']));
|
||||
this.UserProfileModal = await ReactComponents.getComponent('UserProfileModal', {selector}, c => c.prototype.renderHeader && c.prototype.renderBadges);
|
||||
|
||||
this.unpatchUserProfileModal = MonkeyPatch('BD:ReactComponents', this.UserProfileModal.component.prototype).after('render', (component, args, retVal) => {
|
||||
const root = retVal.props.children[0] || retVal.props.children;
|
||||
const { user } = component.props;
|
||||
if (!user) return;
|
||||
retVal.props['data-user-id'] = user.id;
|
||||
if (user.bot) retVal.props.className += ' bd-isBot';
|
||||
if (user.id === DiscordApi.currentUser.id) retVal.props.className += ' bd-isCurrentUser';
|
||||
root.props['data-user-id'] = user.id;
|
||||
if (user.bot) root.props.className += ' bd-isBot';
|
||||
if (user.id === DiscordApi.currentUser.id) root.props.className += ' bd-isCurrentUser';
|
||||
});
|
||||
|
||||
this.UserProfileModal.forceUpdateAll();
|
||||
|
@ -483,18 +559,19 @@ export class ReactAutoPatcher {
|
|||
|
||||
static async patchUserPopout() {
|
||||
const { selector } = Reflection.resolve('userPopout', 'headerNormal');
|
||||
this.UserPopout = await ReactComponents.getComponent('UserPopout', {selector});
|
||||
this.UserPopout = await ReactComponents.getComponent('UserPopout', {selector}, c => c.prototype.renderHeader);
|
||||
|
||||
this.unpatchUserPopout = MonkeyPatch('BD:ReactComponents', this.UserPopout.component.prototype).after('render', (component, args, retVal) => {
|
||||
const root = retVal.props.children[0] || retVal.props.children;
|
||||
const { user, guild, guildMember } = component.props;
|
||||
if (!user) return;
|
||||
retVal.props['data-user-id'] = user.id;
|
||||
if (user.bot) retVal.props.className += ' bd-isBot';
|
||||
if (user.id === DiscordApi.currentUser.id) retVal.props.className += ' bd-isCurrentUser';
|
||||
if (guild) retVal.props['data-guild-id'] = guild.id;
|
||||
if (guild && user.id === guild.ownerId) retVal.props.className += ' bd-isGuildOwner';
|
||||
if (guild && guildMember) retVal.props.className += ' bd-isGuildMember';
|
||||
if (guildMember && guildMember.roles.length) retVal.props.className += ' bd-hasRoles';
|
||||
root.props['data-user-id'] = user.id;
|
||||
if (user.bot) root.props.className += ' bd-isBot';
|
||||
if (user.id === DiscordApi.currentUser.id) root.props.className += ' bd-isCurrentUser';
|
||||
if (guild) root.props['data-guild-id'] = guild.id;
|
||||
if (guild && user.id === guild.ownerId) root.props.className += ' bd-isGuildOwner';
|
||||
if (guild && guildMember) root.props.className += ' bd-isGuildMember';
|
||||
if (guildMember && guildMember.roles.length) root.props.className += ' bd-hasRoles';
|
||||
});
|
||||
|
||||
this.UserPopout.forceUpdateAll();
|
||||
|
@ -504,25 +581,6 @@ export class ReactAutoPatcher {
|
|||
const { selector } = Reflection.resolve('uploadArea');
|
||||
this.UploadArea = await ReactComponents.getComponent('UploadArea', {selector});
|
||||
|
||||
const reflect = Reflection.DOM(selector);
|
||||
const stateNode = reflect.getComponentStateNode(this.UploadArea);
|
||||
const callback = function(e) {
|
||||
if (!e.dataTransfer.files.length || !e.dataTransfer.files[0].name.endsWith('.bd')) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
e.stopImmediatePropagation();
|
||||
stateNode.clearDragging();
|
||||
Modals.confirm('Function not ready', `You tried to install "${e.dataTransfer.files[0].path}", but installing .bd files isn't ready yet.`)
|
||||
// Possibly something like Events.emit('install-file', e.dataTransfer.files[0]);
|
||||
};
|
||||
|
||||
// Remove their handler, add ours, then readd theirs to give ours priority to stop theirs when we get a .bd file.
|
||||
reflect.element.removeEventListener('drop', stateNode.handleDrop);
|
||||
reflect.element.addEventListener('drop', callback);
|
||||
reflect.element.addEventListener('drop', stateNode.handleDrop);
|
||||
|
||||
this.unpatchUploadArea = function() {
|
||||
reflect.element.removeEventListener('drop', callback);
|
||||
};
|
||||
PackageInstaller.uploadAreaPatch(this.UploadArea);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -288,7 +288,7 @@ class Module {
|
|||
if (this._require) return this._require;
|
||||
|
||||
const __webpack_require__ = this.getWebpackRequire();
|
||||
if (!__webpack_require__) return;
|
||||
if (!__webpack_require__) return null;
|
||||
|
||||
this.hookWebpackRequireCache(__webpack_require__);
|
||||
return this._require = __webpack_require__;
|
||||
|
|
|
@ -272,7 +272,7 @@ class WebpackModules {
|
|||
if (this._require) return this._require;
|
||||
|
||||
const __webpack_require__ = this.getWebpackRequire();
|
||||
if (!__webpack_require__) return;
|
||||
if (!__webpack_require__) return null;
|
||||
|
||||
this.hookWebpackRequireCache(__webpack_require__);
|
||||
return this._require = __webpack_require__;
|
||||
|
|
|
@ -69,10 +69,11 @@ export default class Theme extends Content {
|
|||
path: this.paths.mainPath.replace(/\\/g, '/')
|
||||
});
|
||||
|
||||
// Why are these getters?
|
||||
Logger.log(this.name, ['Finished compiling theme', new class Info {
|
||||
get SCSS_variables() { console.log(config); }
|
||||
get Compiled_SCSS() { console.log(result.css.toString()); }
|
||||
get Result() { console.log(result); }
|
||||
get SCSS_variables() { console.log(config); return ''; }
|
||||
get Compiled_SCSS() { console.log(result.css.toString()); return ''; }
|
||||
get Result() { console.log(result); return ''; }
|
||||
}]);
|
||||
|
||||
return {
|
||||
|
@ -146,6 +147,11 @@ export default class Theme extends Content {
|
|||
* @param {Array} files Files to watch
|
||||
*/
|
||||
set watchfiles(files) {
|
||||
if (this.packed) {
|
||||
// Don't watch files for packed themes
|
||||
return;
|
||||
}
|
||||
|
||||
if (!files) files = [];
|
||||
|
||||
for (const file of files) {
|
||||
|
|
|
@ -36,12 +36,7 @@ export default class ThemeManager extends ContentManager {
|
|||
static async loadTheme(paths, configs, info, main) {
|
||||
try {
|
||||
const instance = new Theme({
|
||||
configs, info, main,
|
||||
paths: {
|
||||
contentPath: paths.contentPath,
|
||||
dirName: paths.dirName,
|
||||
mainPath: paths.mainPath
|
||||
}
|
||||
configs, info, main, paths
|
||||
});
|
||||
if (instance.enabled) {
|
||||
instance.userConfig.enabled = false;
|
||||
|
|
|
@ -8,81 +8,183 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import Events from './events';
|
||||
import Globals from './globals';
|
||||
import { ClientLogger as Logger } from 'common';
|
||||
import request from 'request-promise-native';
|
||||
import { Notifications } from 'ui';
|
||||
import { Reflection, Globals } from 'modules';
|
||||
|
||||
export default new class {
|
||||
import Events from './events';
|
||||
import Module from './imodule';
|
||||
|
||||
export default new class extends Module {
|
||||
|
||||
get updates() { return this.state.updates }
|
||||
get bdUpdates() { return this.state.updates.bd }
|
||||
get error() { return null; }
|
||||
get updatesAvailable() { return this.state.updatesAvailable; }
|
||||
|
||||
constructor() {
|
||||
this.updatesAvailable = false;
|
||||
this.latestVersion = undefined;
|
||||
this.error = undefined;
|
||||
|
||||
this.init = this.init.bind(this);
|
||||
this.checkForUpdates = this.checkForUpdates.bind(this);
|
||||
super({
|
||||
updatesAvailable: false,
|
||||
error: null,
|
||||
updates: { bd: [] },
|
||||
updating: false
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The interval to wait before checking for updates.
|
||||
*/
|
||||
get interval() {
|
||||
return 60 * 1000 * 30;
|
||||
bindings() {
|
||||
this.restartNotif = this.restartNotif.bind(this);
|
||||
this.reloadNotif = this.reloadNotif.bind(this);
|
||||
this.startUpdate = this.startUpdate.bind(this);
|
||||
this.setUpdateStatus = this.setUpdateStatus.bind(this);
|
||||
this.testUi = this.testUi.bind(this);
|
||||
}
|
||||
|
||||
init() {
|
||||
this.updateInterval = setInterval(this.checkForUpdates, this.interval);
|
||||
restartNotif() {
|
||||
Notifications.add('Updates Finished!', 'Restart required.', [
|
||||
{
|
||||
text: 'Restart Later',
|
||||
onClick: () => { setTimeout(this.restartNotif, 5 * 60000); return true; }
|
||||
},
|
||||
{
|
||||
text: 'Restart Now',
|
||||
onClick: () => {
|
||||
try {
|
||||
const { remote } = Globals.require('electron');
|
||||
window.close();
|
||||
Reflection.module.byProps('showToken', 'hideToken').showToken();
|
||||
remote.app.relaunch();
|
||||
remote.app.exit(0);
|
||||
} catch (err) {
|
||||
console.err(err);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs an update.
|
||||
* TODO
|
||||
*/
|
||||
async update() {
|
||||
try {
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
|
||||
this.updatesAvailable = false;
|
||||
this.latestVersion = Globals.version;
|
||||
Events.emit('update-check-end');
|
||||
} catch (err) {
|
||||
this.error = err;
|
||||
this.checkForUpdates();
|
||||
throw err;
|
||||
}
|
||||
reloadNotif() {
|
||||
Notifications.add('Updates Finished!', 'Reload required.', [
|
||||
{
|
||||
text: 'Reload Later',
|
||||
onClick: () => { setTimeout(this.reloadNotif, 5 * 60000); return true; }
|
||||
},
|
||||
{
|
||||
text: 'Reload Now',
|
||||
onClick: () => {
|
||||
document.location.reload();
|
||||
}
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for updates.
|
||||
* @return {Promise}
|
||||
*/
|
||||
async checkForUpdates() {
|
||||
if (this.updatesAvailable) return true;
|
||||
Events.emit('update-check-start');
|
||||
Logger.info('Updater', 'Checking for updates');
|
||||
events(ipc) {
|
||||
ipc.on('updater-checkForUpdates', () => {
|
||||
if (this.state.updating) return; // We're already updating. Updater should be paused anyways at this point.
|
||||
Events.emit('update-check-start');
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await request({
|
||||
uri: 'https://rawgit.com/JsSucks/BetterDiscordApp/master/package.json',
|
||||
json: true
|
||||
ipc.on('updater-noUpdates', () => {
|
||||
if (this.state.updatesAvailable) return; // If for some reason we get this even though we have updates already.
|
||||
this.setState({
|
||||
updatesAvailable: false,
|
||||
updates: {}
|
||||
});
|
||||
});
|
||||
|
||||
this.latestVersion = response.version;
|
||||
Events.emit('update-check-end');
|
||||
Logger.info('Updater', `Latest Version: ${response.version} - Current Version: ${Globals.version}`);
|
||||
ipc.on('updater-updatesAvailable', (_, updates) => {
|
||||
console.log(updates);
|
||||
if (this.state.updating) return; // If for some reason we get more updates when we're already updating
|
||||
updates.bd = updates.bd.map(update => {
|
||||
update.text = `${update.id.charAt(0).toUpperCase()}${update.id.slice(1)}`;
|
||||
update.hint = `Current: ${update.currentVersion} | Latest: ${update.version}`;
|
||||
update.status = {
|
||||
update: true,
|
||||
updating: false,
|
||||
updated: false,
|
||||
error: null
|
||||
};
|
||||
|
||||
if (this.latestVersion !== Globals.version) {
|
||||
this.updatesAvailable = true;
|
||||
Events.emit('updates-available');
|
||||
return true;
|
||||
return update;
|
||||
});
|
||||
this.setState({
|
||||
updates,
|
||||
updatesAvailable: true
|
||||
});
|
||||
});
|
||||
|
||||
ipc.on('updater-updated', (_, info) => {
|
||||
const { reloadRequired, restartRequired } = info;
|
||||
if (restartRequired) {
|
||||
this.restartNotif();
|
||||
return;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (err) {
|
||||
Events.emit('update-check-fail', err);
|
||||
throw err;
|
||||
if (reloadRequired) {
|
||||
this.reloadNotif();
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
ipc.on('updater-updateFinished', (_, update) => {
|
||||
this.setUpdateStatus(update.id, 'updated', true);
|
||||
});
|
||||
|
||||
ipc.on('updater-updateError', (_, update) => {
|
||||
this.setUpdateStatus(update.id, 'error', update.error);
|
||||
});
|
||||
}
|
||||
|
||||
stateChanged(oldState, newState) {
|
||||
if (!newState.updatesAvailable) return Events.emit('update-check-end');
|
||||
if (!oldState.updatesAvailable && newState.updatesAvailable) {
|
||||
Events.emit('updates-available');
|
||||
Notifications.add('', 'Updates Available!', [
|
||||
{
|
||||
text: 'Ignore',
|
||||
onClick: () => { return true; }
|
||||
},
|
||||
{
|
||||
text: 'Show Updates',
|
||||
onClick: () => {
|
||||
Events.emit('bd-open-menu', 'updater');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
setUpdateStatus(updateId, statusChild, statusValue) {
|
||||
for (const u of this.bdUpdates) {
|
||||
if (u.id === updateId) {
|
||||
u.status[statusChild] = statusValue;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toggleUpdate(update) {
|
||||
update.status.update = !update.status.update;
|
||||
}
|
||||
|
||||
async startUpdate() {
|
||||
console.log('start update');
|
||||
const updates = { bd: [] };
|
||||
for (const update of this.bdUpdates) {
|
||||
if (update.status.update) {
|
||||
update.status.updating = true;
|
||||
updates.bd.push(update);
|
||||
}
|
||||
}
|
||||
console.log(updates);
|
||||
this.send('updater-startUpdate', updates);
|
||||
}
|
||||
|
||||
testUi(updates) {
|
||||
this.setState({
|
||||
updates,
|
||||
updatesAvailable: true
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
import jQuery from 'jquery';
|
||||
import lodash from 'lodash';
|
||||
import Vue from 'vue';
|
||||
import { Axi } from 'common';
|
||||
|
||||
import request from 'request-promise-native';
|
||||
|
||||
|
@ -40,6 +41,8 @@ export default class {
|
|||
*/
|
||||
static get Vue() { return Vue }
|
||||
|
||||
static get axios() { return Axi.axios }
|
||||
|
||||
static get request() { return request }
|
||||
|
||||
static get Combokeys() { return Combokeys }
|
||||
|
|
|
@ -179,7 +179,7 @@ export class PermissionOverwrite {
|
|||
}
|
||||
|
||||
get guild() {
|
||||
if (this.channel) return this.channel.guild;
|
||||
return this.channel ? this.channel.guild : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -187,7 +187,7 @@ export class RolePermissionOverwrite extends PermissionOverwrite {
|
|||
get roleId() { return this.discordObject.id }
|
||||
|
||||
get role() {
|
||||
if (this.guild) return this.guild.roles.find(r => r.id === this.roleId);
|
||||
return this.guild ? this.guild.roles.find(r => r.id === this.roleId) : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -166,14 +166,14 @@ export class Guild {
|
|||
* The guild's AFK channel.
|
||||
*/
|
||||
get afkChannel() {
|
||||
if (this.afkChannelId) return Channel.fromId(this.afkChannelId);
|
||||
return this.afkChannelId ? Channel.fromId(this.afkChannelId) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The channel system messages are sent to.
|
||||
*/
|
||||
get systemChannel() {
|
||||
if (this.systemChannelId) return Channel.fromId(this.systemChannelId);
|
||||
return this.systemChannelId ? Channel.fromId(this.systemChannelId) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -42,11 +42,11 @@ export class Reaction {
|
|||
}
|
||||
|
||||
get message() {
|
||||
if (this.channel) return this.channel.messages.find(m => m.id === this.messageId);
|
||||
return this.channel ? this.channel.messages.find(m => m.id === this.messageId) : null;
|
||||
}
|
||||
|
||||
get guild() {
|
||||
if (this.channel) return this.channel.guild;
|
||||
return this.channel ? this.channel.guild : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,11 +84,11 @@ export class Embed {
|
|||
}
|
||||
|
||||
get message() {
|
||||
if (this.channel) return this.channel.messages.find(m => m.id === this.messageId);
|
||||
return this.channel ? this.channel.messages.find(m => m.id === this.messageId) : null;
|
||||
}
|
||||
|
||||
get guild() {
|
||||
if (this.channel) return this.channel.guild;
|
||||
return this.channel ? this.channel.guild : null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,6 +143,7 @@ export class Message {
|
|||
|
||||
get author() {
|
||||
if (this.discordObject.author && !this.webhookId) return User.from(this.discordObject.author);
|
||||
return null;
|
||||
}
|
||||
|
||||
get channel() {
|
||||
|
@ -150,7 +151,7 @@ export class Message {
|
|||
}
|
||||
|
||||
get guild() {
|
||||
if (this.channel) return this.channel.guild;
|
||||
return this.channel ? this.channel.guild : null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -202,7 +203,7 @@ export class DefaultMessage extends Message {
|
|||
get application() { return this.discordObject.application }
|
||||
|
||||
get webhook() {
|
||||
if (this.webhookId) return this.discordObject.author;
|
||||
return this.webhookId ? this.discordObject.author : null;
|
||||
}
|
||||
|
||||
get mentions() {
|
||||
|
|
|
@ -77,7 +77,7 @@ export class User {
|
|||
|
||||
get note() {
|
||||
const note = Modules.UserNoteStore.getNote(this.id);
|
||||
if (note) return note;
|
||||
return note ? note : null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,16 +17,45 @@
|
|||
}
|
||||
|
||||
.bd-settingsButtonBtn {
|
||||
background-image: $logoSmallBw;
|
||||
background-size: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
backface-visibility: hidden;
|
||||
-webkit-backface-visibility: hidden;
|
||||
width: 70px;
|
||||
height: 48px;
|
||||
cursor: pointer;
|
||||
filter: grayscale(100%);
|
||||
opacity: .5;
|
||||
transition: all .4s ease-in-out;
|
||||
position: relative;
|
||||
transition: all .3s cubic-bezier(.4, 0, 0, 1);
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
&::before {
|
||||
width: 70px;
|
||||
height: 48px;
|
||||
background-image: $logoSmallBw;
|
||||
background-size: 50% 50%;
|
||||
opacity: 1;
|
||||
transition: all .3s cubic-bezier(.4, 0, 0, 1), opacity .01s;
|
||||
}
|
||||
|
||||
&::after {
|
||||
width: 130px;
|
||||
height: 43px;
|
||||
background-image: $logoBigBw;
|
||||
background-size: 100% 100%;
|
||||
transform: translate(-7px, 2px) scale(.5);
|
||||
opacity: 0;
|
||||
transition: all .3s cubic-bezier(.4, 0, 0, 1);
|
||||
}
|
||||
|
||||
&:not(.bd-loading) {
|
||||
&:hover {
|
||||
|
@ -69,14 +98,23 @@
|
|||
box-shadow: none;
|
||||
|
||||
.bd-settingsButtonBtn {
|
||||
background-image: $logoBigBw;
|
||||
background-size: 100% 100%;
|
||||
filter: none;
|
||||
opacity: 1;
|
||||
width: 130px;
|
||||
height: 43px;
|
||||
margin: 18px 0 17px 25px;
|
||||
transform: translate(25px, 18px);
|
||||
cursor: default;
|
||||
|
||||
&::before {
|
||||
opacity: 0;
|
||||
transform: translate(-16px, -3px) scale(1.9);
|
||||
transition: all .3s cubic-bezier(.4, 0, 0, 1), opacity .1s .3s;
|
||||
}
|
||||
|
||||
&::after {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,97 @@
|
|||
.bd-pluginsview,
|
||||
.bd-themesview {
|
||||
.bd-onlinePh {
|
||||
.bd-localPh {
|
||||
.bd-scroller {
|
||||
padding: 0 20px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.bd-onlinePh,
|
||||
.bd-localPh {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 10% 0;
|
||||
margin: 10px 0;
|
||||
|
||||
.bd-spinnerContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.bd-onlinePhHeader {
|
||||
display: flex;
|
||||
padding: 0 20px 0 10px;
|
||||
min-height: 80px;
|
||||
|
||||
.bd-flexRow {
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.bd-searchHint {
|
||||
flex-grow: 1;
|
||||
line-height: 40px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.bd-searchSort {
|
||||
margin-top: 10px;
|
||||
justify-content: flex-end;
|
||||
|
||||
> span {
|
||||
color: #fff;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
font-size: 12px;
|
||||
height: 14px;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.bd-sort {
|
||||
display: flex;
|
||||
color: $coldimwhite;
|
||||
font-size: 12px;
|
||||
padding: 3px;
|
||||
height: 14px;
|
||||
cursor: pointer;
|
||||
transition: color .2s ease-in-out;
|
||||
font-weight: 700;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.bd-active {
|
||||
color: $colbdgreen;
|
||||
}
|
||||
|
||||
.bd-materialDesignIcon {
|
||||
fill: $colbdgreen;
|
||||
}
|
||||
|
||||
svg {
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
&.bd-flipY {
|
||||
.bd-materialDesignIcon {
|
||||
transform: scaleY(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bd-onlinePhBody {
|
||||
margin-top: 10px;
|
||||
|
||||
.bd-spinnerContainer {
|
||||
min-height: 40px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.bd-scroller {
|
||||
padding: 0 20px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: #fff;
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
.bd-contentColumn .bd-devview {
|
||||
display: grid;
|
||||
grid-template-columns: 33% 33% 33%;
|
||||
|
||||
.bd-button {
|
||||
font-size: 10px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
|
@ -10,3 +10,4 @@
|
|||
@import './kvp';
|
||||
@import './collection';
|
||||
@import './e2ee';
|
||||
@import './devview';
|
||||
|
|
|
@ -1,22 +1,17 @@
|
|||
.bd-remoteCard {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 10px;
|
||||
padding: 10px 0;
|
||||
padding: 10px;
|
||||
border-radius: 0;
|
||||
border-bottom: 1px solid rgba(114, 118, 126, .3);
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.005);
|
||||
}
|
||||
|
||||
.bd-remoteCardTitle {
|
||||
color: #b9bbbe;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.bd-remoteCardLikes {
|
||||
color: #f00;
|
||||
color: $colerr;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
@ -52,17 +47,30 @@
|
|||
.bd-remoteCardTags {
|
||||
color: #828a97;
|
||||
font-size: 10px;
|
||||
line-height: 20px;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
|
||||
.bd-remoteCardTag {
|
||||
> div {
|
||||
display: inline;
|
||||
cursor: pointer;
|
||||
margin-left: 2px;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bd-buttonGroup {
|
||||
align-self: flex-end;
|
||||
justify-content: flex-end;
|
||||
flex-grow: 1;
|
||||
max-height: 20px;
|
||||
max-height: 30px;
|
||||
|
||||
.bd-button {
|
||||
font-size: 12px;
|
||||
font-size: 16px;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
.bd-settings {
|
||||
position: absolute;
|
||||
backface-visibility: hidden;
|
||||
-webkit-backface-visibility: hidden;
|
||||
top: 22px;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 1001;
|
||||
width: 310px;
|
||||
transform: translateX(-100%) translateY(-100%);
|
||||
transform: translateX(-310px);
|
||||
opacity: 0;
|
||||
transition: all .4s ease-in-out;
|
||||
transition: transform .3s cubic-bezier(.4, 0, 0, 1), opacity .25s ease;
|
||||
pointer-events: none;
|
||||
|
||||
&.bd-active {
|
||||
|
@ -115,7 +117,7 @@
|
|||
|
||||
&.bd-active {
|
||||
.bd-contentRegion {
|
||||
transition: transform .4s ease-in-out, opacity .2s ease;
|
||||
transition: all .3s cubic-bezier(.4, 0, 0, 1);
|
||||
transform: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
|
|
@ -3,4 +3,22 @@
|
|||
margin: 0 0 10px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.bd-settingSwitch {
|
||||
.bd-spinner7 {
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.bd-updaterStatus {
|
||||
text-align: right;
|
||||
|
||||
&.bd-err {
|
||||
color: $colerr;
|
||||
}
|
||||
|
||||
&.bd-ok {
|
||||
color: $colok;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
// sass-lint:disable-all
|
||||
body:not(.bd-hideButton) {
|
||||
[class*='guildsWrapper-'] {
|
||||
[class*='layer-'] > * > [class*='wrapper-'] {
|
||||
padding-top: 49px !important;
|
||||
}
|
||||
.platform-osx [class*='guildsWrapper-'] {
|
||||
.platform-osx [class*='layer-'] > * > [class*='wrapper-'] {
|
||||
margin-top: 26px;
|
||||
}
|
||||
|
||||
[class*='guildsWrapper-'] + [class*='flex'] {
|
||||
[class*='layer-'] > * > [class*='wrapper-'] + [class*='flex'] {
|
||||
border-radius: 0 0 0 5px;
|
||||
}
|
||||
|
||||
|
@ -27,3 +27,9 @@ body:not(.bd-hideButton) {
|
|||
.bd-settingsWrapper.platform-linux {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
// Remove the margin on message attachments with an emote
|
||||
.da-containerCozy + .da-containerCozy > * > .bd-emote {
|
||||
margin-top: -8px;
|
||||
margin-bottom: -8px;
|
||||
}
|
||||
|
|
|
@ -26,14 +26,25 @@
|
|||
position: relative;
|
||||
margin-right: 15px;
|
||||
|
||||
.bd-chevron1,
|
||||
.bd-chevron1 {
|
||||
position: absolute;
|
||||
|
||||
svg {
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.bd-chevron2 {
|
||||
position: absolute;
|
||||
|
||||
svg {
|
||||
visibility: hidden;
|
||||
transform: scale(1.4, .5) translateY(6px) rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
transition: transform .5s ease;
|
||||
transform: none;
|
||||
transition: transform .2s cubic-bezier(.2, 0, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,16 +81,16 @@
|
|||
.bd-drawerOpenButton {
|
||||
.bd-chevron1 {
|
||||
svg {
|
||||
transform: rotate(90deg);
|
||||
transform: scale(1.4, .5) translateY(-6px);
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bd-chevron2 {
|
||||
margin-left: -4px;
|
||||
|
||||
svg {
|
||||
transform: rotate(270deg);
|
||||
visibility: visible;
|
||||
transform: scale(1) translateY(0) rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
bottom: 3px;
|
||||
background: #f6f6f7;
|
||||
border-radius: 10px;
|
||||
transition: all .15s ease;
|
||||
transition: all .15s cubic-bezier(.2, 0, 0, 1);
|
||||
box-shadow: 0 3px 1px 0 rgba(0, 0, 0, .05), 0 2px 2px 0 rgba(0, 0, 0, .1), 0 3px 3px 0 rgba(0, 0, 0, .05);
|
||||
}
|
||||
|
||||
|
|
|
@ -164,11 +164,11 @@
|
|||
.bd-fancySearch {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
transform: translateY(80px) translateX(-140px);
|
||||
transition: all .5s ease-in-out;
|
||||
// transform: translateY(80px) translateX(-140px);
|
||||
// transition: all .5s ease-in-out;
|
||||
|
||||
&::before {
|
||||
content: 'Search by name, description or tag...';
|
||||
// content: 'Search by name, description or tag...';
|
||||
color: #f6f6f7;
|
||||
position: relative;
|
||||
top: -20px;
|
||||
|
@ -184,6 +184,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.bd-disabled {
|
||||
.bd-textInput {
|
||||
opacity: .8;
|
||||
}
|
||||
}
|
||||
|
||||
.bd-textInput {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
|
|
|
@ -8,3 +8,4 @@
|
|||
@import './settings-modal';
|
||||
@import './permission-modal';
|
||||
@import './input-modal';
|
||||
@import './install-modal';
|
||||
|
|
|
@ -0,0 +1,231 @@
|
|||
.bd-installModal {
|
||||
|
||||
.bd-modalInner {
|
||||
min-width: 520px;
|
||||
min-height: 200px;
|
||||
|
||||
@at-root .bd-err#{&} {
|
||||
border: 2px solid $colerr;
|
||||
}
|
||||
|
||||
.bd-modalBody {
|
||||
flex-grow: 1;
|
||||
padding: 0;
|
||||
|
||||
.bd-installModalBody {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
|
||||
.bd-installModalTop {
|
||||
padding: 0 15px;
|
||||
display: flex;
|
||||
height: 100px;
|
||||
|
||||
span {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.bd-installModalIcon {
|
||||
background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMTAwIDEzMCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMTAwIDEzMDsiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+LnN0MHtmaWxsOiNGNEY2RkM7fS5zdDF7ZmlsbDpub25lO3N0cm9rZTojMzYzOTNFO3N0cm9rZS13aWR0aDoyO30uc3Qye29wYWNpdHk6MC42O2ZpbGw6bm9uZTtzdHJva2U6IzM2MzkzRTtzdHJva2Utd2lkdGg6MjtlbmFibGUtYmFja2dyb3VuZDpuZXcgICAgO308L3N0eWxlPjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0xNSwxOWMwLTMuMywyLjctNiw2LTZoNDRjNC43LDAsMjAsMTUuMywyMCwyMHY3NGMwLDMuMy0yLjcsNi02LDZIMjFjLTMuMywwLTYtMi43LTYtNlYxOXoiLz48cGF0aCBjbGFzcz0ic3QxIiBkPSJNMTUsMTljMC0zLjMsMi43LTYsNi02aDQ0YzQuNywwLDIwLDE1LjMsMjAsMjB2NzRjMCwzLjMtMi43LDYtNiw2SDIxYy0zLjMsMC02LTIuNy02LTZWMTl6Ii8+PHBhdGggY2xhc3M9InN0MCIgZD0iTTY2LDE2YzAtMy4zLDEuOS00LjEsNC4yLTEuOGwxMy41LDEzLjVjMi4zLDIuMywxLjYsNC4yLTEuOCw0LjJINjljLTEuNywwLTMtMS4zLTMtM1YxNkw2NiwxNnoiLz48cGF0aCBjbGFzcz0ic3QxIiBkPSJNNjYsMTZjMC0zLjMsMS45LTQuMSw0LjItMS44bDEzLjUsMTMuNWMyLjMsMi4zLDEuNiw0LjItMS44LDQuMkg2OWMtMS43LDAtMy0xLjMtMy0zVjE2TDY2LDE2eiIvPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik02MSwxN0gyM2MtMi44LDAtNSwyLjItNSw1djciLz48cGF0aCBjbGFzcz0ic3QyIiBkPSJNNjksMTl2Ny41YzAsMS40LDEuMSwyLjUsMi41LDIuNUg3NCIvPjwvc3ZnPg==');
|
||||
background-size: 100% 100%;
|
||||
flex-shrink: 0;
|
||||
width: 100px;
|
||||
height: 130px;
|
||||
position: fixed;
|
||||
border-radius: 3px;
|
||||
margin-top: -30px;
|
||||
display: flex;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
|
||||
@at-root .bd-err#{&} {
|
||||
background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgMTAwIDEzMCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMTAwIDEzMDsiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+LnN0MHtmaWxsOiNGNEY2RkM7fS5zdDF7ZmlsbDpub25lO3N0cm9rZTojRDg0MDQwO3N0cm9rZS13aWR0aDoyO30uc3Qye29wYWNpdHk6MC42O2ZpbGw6bm9uZTtzdHJva2U6I0Q4NDA0MDtzdHJva2Utd2lkdGg6MjtlbmFibGUtYmFja2dyb3VuZDpuZXcgICAgO308L3N0eWxlPjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik0xNSwxOWMwLTMuMywyLjctNiw2LTZoNDRjNC43LDAsMjAsMTUuMywyMCwyMHY3NGMwLDMuMy0yLjcsNi02LDZIMjFjLTMuMywwLTYtMi43LTYtNlYxOXoiLz48cGF0aCBjbGFzcz0ic3QxIiBkPSJNMTUsMTljMC0zLjMsMi43LTYsNi02aDQ0YzQuNywwLDIwLDE1LjMsMjAsMjB2NzRjMCwzLjMtMi43LDYtNiw2SDIxYy0zLjMsMC02LTIuNy02LTZWMTl6Ii8+PHBhdGggY2xhc3M9InN0MCIgZD0iTTY2LDE2YzAtMy4zLDEuOS00LjEsNC4yLTEuOGwxMy41LDEzLjVjMi4zLDIuMywxLjYsNC4yLTEuOCw0LjJINjljLTEuNywwLTMtMS4zLTMtM1YxNkw2NiwxNnoiLz48cGF0aCBjbGFzcz0ic3QxIiBkPSJNNjYsMTZjMC0zLjMsMS45LTQuMSw0LjItMS44bDEzLjUsMTMuNWMyLjMsMi4zLDEuNiw0LjItMS44LDQuMkg2OWMtMS43LDAtMy0xLjMtMy0zVjE2TDY2LDE2eiIvPjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik02MSwxN0gyM2MtMi44LDAtNSwyLjItNSw1djciLz48cGF0aCBjbGFzcz0ic3QyIiBkPSJNNjksMTl2Ny41YzAsMS40LDEuMSwyLjUsMi41LDIuNUg3NCIvPjwvc3ZnPg==');
|
||||
}
|
||||
|
||||
.bd-installModalCi {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: #36393e;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.bd-installModalInfo {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
padding: 15px 10px;
|
||||
margin-left: 100px;
|
||||
}
|
||||
|
||||
.bd-installModalDesc {
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.bd-installModalBottom {
|
||||
padding: 0 15px;
|
||||
flex-grow: 1;
|
||||
|
||||
.bd-permScope {
|
||||
&:last-child {
|
||||
.bd-permInner {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bd-installModalFooter {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 10px;
|
||||
background: rgba(0, 0, 0, .1);
|
||||
|
||||
.bd-installModalStatus {
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
flex-grow: 1;
|
||||
padding: 0 25px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
|
||||
&.bd-ok {
|
||||
color: $colok;
|
||||
}
|
||||
|
||||
&.bd-err {
|
||||
color: $colerr;
|
||||
}
|
||||
}
|
||||
|
||||
.bd-button {
|
||||
font-size: 16px;
|
||||
padding: 10px;
|
||||
border-radius: 3px;
|
||||
min-width: 100px;
|
||||
transition: opacity .2s ease-in-out;
|
||||
|
||||
&.bd-ok {
|
||||
background: #3ecc9c;
|
||||
}
|
||||
|
||||
&.bd-err {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
&.bd-installModalUpload {
|
||||
background: transparent;
|
||||
transition: none;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.bd-installModalDone {
|
||||
.bd-modalInner {
|
||||
min-width: 400px;
|
||||
max-width: 400px;
|
||||
height: 250px;
|
||||
border: 2px solid $colok;
|
||||
|
||||
.bd-installModalBody {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
h3 {
|
||||
color: $colok;
|
||||
font-size: 1.2em;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.bd-materialDesignIcon {
|
||||
svg {
|
||||
fill: $colok;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.bd-installModalFooter {
|
||||
.bd-button {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.bd-installModalFail {
|
||||
.bd-modalInner {
|
||||
min-width: 400px;
|
||||
max-width: 400px;
|
||||
height: 250px;
|
||||
border: 2px solid $colerr;
|
||||
|
||||
.bd-installModalBody {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
h3 {
|
||||
color: $colerr;
|
||||
font-size: 1.2em;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.bd-materialDesignIcon {
|
||||
svg {
|
||||
fill: $colerr;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.bd-installModalFooter {
|
||||
|
||||
&.bd-installModalErrMsg {
|
||||
justify-content: flex-start;
|
||||
padding: 10px;
|
||||
background: rgba(0, 0, 0, .2);
|
||||
font-weight: 700;
|
||||
|
||||
span {
|
||||
color: #fff;
|
||||
flex-grow: 1;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,12 +22,12 @@
|
|||
|
||||
> .bd-scroller {
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
|
||||
.bd-settings & {
|
||||
.platform-darwin & { // sass-lint:disable-line class-name-format
|
||||
padding-top: 22px;
|
||||
}
|
||||
}
|
||||
.bd-settings & {
|
||||
.platform-darwin & { // sass-lint:disable-line class-name-format
|
||||
padding-top: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,8 +120,14 @@
|
|||
}
|
||||
|
||||
.bd-settingswrapContents {
|
||||
padding: 0 20px;
|
||||
margin-bottom: 84px;
|
||||
padding: 0 0 0 20px;
|
||||
}
|
||||
|
||||
.bd-scroller {
|
||||
.bd-settingswrapContents {
|
||||
margin-bottom: 84px;
|
||||
padding: 0 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,13 +37,14 @@
|
|||
white-space: nowrap;
|
||||
font-family: Whitney, Helvetica Neue, Helvetica, Arial, sans-serif;
|
||||
|
||||
&:hover,
|
||||
&.bd-active {
|
||||
background: $colbdgreen;
|
||||
color: #fff;
|
||||
&:hover {
|
||||
background-color: rgba(185, 185, 185, .1);
|
||||
color: #f6f6f6;
|
||||
}
|
||||
|
||||
&.bd-active {
|
||||
background: $colbdgreen;
|
||||
color: #fff;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ export default new class Autocomplete {
|
|||
}
|
||||
|
||||
async init() {
|
||||
this.cta = await ReactComponents.getComponent('ChannelTextArea', { selector: Reflection.resolve('channelTextArea', 'emojiButton').selector });
|
||||
this.cta = await ReactComponents.getComponent('ChannelTextArea');
|
||||
MonkeyPatch('BD:Autocomplete', this.cta.component.prototype).after('render', this.channelTextAreaAfterRender.bind(this));
|
||||
this.initialized = true;
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
</Sidebar>
|
||||
|
||||
<div slot="sidebarfooter" class="bd-info">
|
||||
<span class="bd-vtext">v2.0.0a by Jiiks/JsSucks</span>
|
||||
<span class="bd-vtext">{{versionString}}</span>
|
||||
<div @click="openGithub" v-tooltip="'GitHub'" class="bd-materialButton">
|
||||
<MiGithubCircle size="16" />
|
||||
</div>
|
||||
|
@ -63,11 +63,11 @@
|
|||
|
||||
<script>
|
||||
// Imports
|
||||
import { Events, Settings } from 'modules';
|
||||
import { Events, Settings, Globals, Reflection } from 'modules';
|
||||
import { BdMenuItems } from 'ui';
|
||||
import { shell } from 'electron';
|
||||
import { SidebarView, Sidebar, SidebarItem, ContentColumn } from './sidebar';
|
||||
import { SettingsWrapper, SettingsPanel, CssEditorView, PluginsView, ThemesView, UpdaterView, ConnectivityView } from './bd';
|
||||
import { SettingsWrapper, SettingsPanel, CssEditorView, PluginsView, ThemesView, UpdaterView, ConnectivityView, SuperSecretView } from './bd';
|
||||
import { SvgX, MiGithubCircle, MiWeb, MiClose, MiTwitterCircle } from './common';
|
||||
|
||||
export default {
|
||||
|
@ -96,6 +96,9 @@
|
|||
category.push(item);
|
||||
}
|
||||
return categories;
|
||||
},
|
||||
versionString() {
|
||||
return Globals.version;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -123,6 +126,13 @@
|
|||
},
|
||||
created() {
|
||||
Events.on('bd-open-menu', this.openMenuHandler = item => item && this.itemOnClick(this.items.find(i => i === item || i.id === item || i.contentid === item || i.set === item).id));
|
||||
|
||||
try {
|
||||
const currentUser = Reflection.module.byName('UserStore').getCurrentUser();
|
||||
if (['81388395867156480', '98003542823944192', '249746236008169473', '284056145272766465', '478559353516064769'].includes(currentUser.id)) {
|
||||
BdMenuItems.addVueComponent('BD Devs', 'Super Secret', SuperSecretView);
|
||||
}
|
||||
} catch (err) {}
|
||||
},
|
||||
destroyed() {
|
||||
if (this.openMenuHandler) Events.off('bd-open-menu', this.openMenuHandler);
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
// TODO this should be remade as editor instead of css editor
|
||||
|
||||
<template>
|
||||
<SettingsWrapper headertext="CSS Editor">
|
||||
<div class="bd-cssEditor">
|
||||
|
@ -49,7 +51,7 @@
|
|||
|
||||
<script>
|
||||
// Imports
|
||||
import { Settings, CssEditor } from 'modules';
|
||||
import { Settings, Editor } from 'modules';
|
||||
import { SettingsWrapper } from './';
|
||||
import { FormButton } from '../common';
|
||||
import SettingsPanel from './SettingsPanel.vue';
|
||||
|
@ -64,18 +66,18 @@
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
CssEditor
|
||||
Editor
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
error() {
|
||||
return this.CssEditor.error;
|
||||
return this.Editor.error;
|
||||
},
|
||||
compiling() {
|
||||
return this.CssEditor.compiling;
|
||||
return this.Editor.compiling;
|
||||
},
|
||||
systemEditorPath() {
|
||||
return this.CssEditor.filePath;
|
||||
return this.Editor.filePath;
|
||||
},
|
||||
liveUpdateSetting() {
|
||||
return Settings.getSetting('css', 'default', 'live-update');
|
||||
|
@ -92,13 +94,13 @@
|
|||
},
|
||||
methods: {
|
||||
openInternalEditor() {
|
||||
this.CssEditor.show();
|
||||
this.Editor.show();
|
||||
},
|
||||
openSystemEditor() {
|
||||
this.CssEditor.openSystemEditor();
|
||||
// this.Editor.openSystemEditor();
|
||||
},
|
||||
recompile() {
|
||||
this.CssEditor.recompile();
|
||||
// this.Editor.recompile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
<Card :item="plugin">
|
||||
<SettingSwitch v-if="plugin.type === 'plugin'" slot="toggle" :value="plugin.enabled" @input="$emit('toggle-plugin')" />
|
||||
<ButtonGroup slot="controls">
|
||||
<Button v-if="devmode && !plugin.packed" v-tooltip="'Package Plugin'" @click="package"><MiBoxDownload size="18"/></Button>
|
||||
<Button v-tooltip="'Settings (shift + click to open settings without cloning the set)'" v-if="plugin.hasSettings" @click="$emit('show-settings', $event.shiftKey)"><MiSettings size="18" /></Button>
|
||||
<Button v-tooltip="'Reload'" @click="$emit('reload-plugin')"><MiRefresh size="18" /></Button>
|
||||
<Button v-tooltip="'Edit'" @click="editPlugin"><MiPencil size="18" /></Button>
|
||||
|
@ -22,18 +23,33 @@
|
|||
|
||||
<script>
|
||||
// Imports
|
||||
import { Toasts } from 'ui';
|
||||
import { Settings, PluginManager } from 'modules';
|
||||
import { ClientLogger as Logger } from 'common';
|
||||
import { shell } from 'electron';
|
||||
import Card from './Card.vue';
|
||||
import { Button, ButtonGroup, SettingSwitch, MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension } from '../common';
|
||||
import { Button, ButtonGroup, SettingSwitch, MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension, MiBoxDownload } from '../common';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
devmode: Settings.getSetting('core', 'advanced', 'developer-mode').value
|
||||
}
|
||||
},
|
||||
props: ['plugin'],
|
||||
components: {
|
||||
Card, Button, ButtonGroup, SettingSwitch,
|
||||
MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension
|
||||
MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension, MiBoxDownload
|
||||
},
|
||||
methods: {
|
||||
async package() {
|
||||
try {
|
||||
const packagePath = await PluginManager.packContent(this.plugin.name, this.plugin.contentPath);
|
||||
Toasts.success(`Plugin Packaged: ${packagePath}`);
|
||||
} catch (err) {
|
||||
Logger.log('PluginCard', err);
|
||||
}
|
||||
},
|
||||
editPlugin() {
|
||||
try {
|
||||
shell.openItem(this.plugin.contentPath);
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
*/
|
||||
|
||||
<template>
|
||||
<SettingsWrapper headertext="Plugins">
|
||||
<SettingsWrapper headertext="Plugins" :noscroller="true">
|
||||
<div class="bd-tabbar" slot="header">
|
||||
<div class="bd-button" :class="{'bd-active': local}" @click="showLocal">
|
||||
<h3>Installed</h3>
|
||||
|
@ -22,12 +22,39 @@
|
|||
</div>
|
||||
|
||||
<div class="bd-flex bd-flexCol bd-pluginsview">
|
||||
<div v-if="local" class="bd-flex bd-flexGrow bd-flexCol bd-pluginsContainer bd-localPlugins">
|
||||
<PluginCard v-for="plugin in localPlugins" :plugin="plugin" :key="plugin.id" :data-plugin-id="plugin.id" @toggle-plugin="togglePlugin(plugin)" @reload-plugin="reloadPlugin(plugin)" @delete-plugin="unload => deletePlugin(plugin, unload)" @show-settings="dont_clone => showSettings(plugin, dont_clone)" />
|
||||
<div v-if="local" class="bd-flex bd-flexGrow bd-flexCol bd-pluginsContainer bd-localPlugins bd-localPh">
|
||||
<ScrollerWrap>
|
||||
<PluginCard v-for="plugin in localPlugins" :plugin="plugin" :key="plugin.id" :data-plugin-id="plugin.id" @toggle-plugin="togglePlugin(plugin)" @reload-plugin="reloadPlugin(plugin)" @delete-plugin="unload => deletePlugin(plugin, unload)" @show-settings="dont_clone => showSettings(plugin, dont_clone)" />
|
||||
</ScrollerWrap>
|
||||
</div>
|
||||
<div v-if="!local" class="bd-onlinePh">
|
||||
<h3>Coming Soon</h3>
|
||||
<a href="https://v2.betterdiscord.net/plugins" target="_new">Website Browser</a>
|
||||
<div v-else class="bd-onlinePh">
|
||||
<div class="bd-onlinePhHeader bd-flexCol">
|
||||
<div class="bd-flex bd-flexRow">
|
||||
<div v-if="loadingOnline" class="bd-spinnerContainer">
|
||||
<div class="bd-spinner7" />
|
||||
</div>
|
||||
<div class="bd-searchHint">{{searchHint}}</div>
|
||||
<div class="bd-fancySearch" :class="{'bd-disabled': loadingOnline, 'bd-active': loadingOnline || (onlinePlugins && onlinePlugins.docs)}">
|
||||
<input type="text" class="bd-textInput" placeholder="Search" @keydown.enter="searchInput" @keyup.stop :value="onlinePlugins.filters.sterm" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="bd-flex bd-flexRow" v-if="onlinePlugins && onlinePlugins.docs && onlinePlugins.docs.length">
|
||||
<div class="bd-searchSort bd-flex bd-flexGrow">
|
||||
<div v-for="btn in sortBtns"
|
||||
class="bd-sort"
|
||||
:class="{'bd-active': onlinePlugins.filters.sort === btn.toLowerCase(), 'bd-flipY': onlinePlugins.filters.ascending}"
|
||||
@click="sortBy(btn.toLowerCase())">
|
||||
{{btn}}<MiChevronDown v-if="onlinePlugins.filters.sort === btn.toLowerCase()" size="18" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ScrollerWrap class="bd-onlinePhBody" v-if="!loadingOnline && onlinePlugins" :scrollend="scrollend">
|
||||
<RemoteCard v-if="onlinePlugins && onlinePlugins.docs" v-for="plugin in onlinePlugins.docs" :key="onlinePlugins.id" :item="plugin" :tagClicked="searchByTag" />
|
||||
<div class="bd-spinnerContainer">
|
||||
<div v-if="loadingMore" class="bd-spinner7" />
|
||||
</div>
|
||||
</ScrollerWrap>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsWrapper>
|
||||
|
@ -35,25 +62,45 @@
|
|||
|
||||
<script>
|
||||
// Imports
|
||||
import { PluginManager } from 'modules';
|
||||
import { PluginManager, BdWebApi } from 'modules';
|
||||
import { Modals } from 'ui';
|
||||
import { ClientLogger as Logger } from 'common';
|
||||
import { MiRefresh } from '../common';
|
||||
import { MiRefresh, ScrollerWrap, MiChevronDown } from '../common';
|
||||
import SettingsWrapper from './SettingsWrapper.vue';
|
||||
import PluginCard from './PluginCard.vue';
|
||||
import RemoteCard from './RemoteCard.vue';
|
||||
import RefreshBtn from '../common/RefreshBtn.vue';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
PluginManager,
|
||||
sortBtns: ['Updated', 'Installs', 'Users', 'Rating'],
|
||||
local: true,
|
||||
localPlugins: PluginManager.localPlugins
|
||||
localPlugins: PluginManager.localPlugins,
|
||||
onlinePlugins: {
|
||||
docs: [],
|
||||
filters: {
|
||||
sterm: '',
|
||||
sort: 'installs',
|
||||
ascending: false
|
||||
},
|
||||
pagination: {
|
||||
total: 0,
|
||||
pages: 0,
|
||||
limit: 9,
|
||||
page: 1
|
||||
}
|
||||
},
|
||||
loadingOnline: false,
|
||||
loadingMore: false,
|
||||
searchHint: ''
|
||||
};
|
||||
},
|
||||
components: {
|
||||
SettingsWrapper, PluginCard,
|
||||
MiRefresh,
|
||||
SettingsWrapper, PluginCard, RemoteCard,
|
||||
MiRefresh, MiChevronDown,
|
||||
ScrollerWrap,
|
||||
RefreshBtn
|
||||
},
|
||||
methods: {
|
||||
|
@ -67,7 +114,19 @@
|
|||
await this.PluginManager.refreshPlugins();
|
||||
},
|
||||
async refreshOnline() {
|
||||
// TODO
|
||||
this.searchHint = '';
|
||||
if (this.loadingOnline || this.loadingMore) return;
|
||||
this.loadingOnline = true;
|
||||
try {
|
||||
const getPlugins = await BdWebApi.plugins.get(this.onlinePlugins.filters);
|
||||
this.onlinePlugins = getPlugins;
|
||||
if (!this.onlinePlugins.docs) return;
|
||||
this.searchHint = `${this.onlinePlugins.pagination.total} Results`;
|
||||
} catch (err) {
|
||||
Logger.err('PluginsView', err);
|
||||
} finally {
|
||||
this.loadingOnline = false;
|
||||
}
|
||||
},
|
||||
async togglePlugin(plugin) {
|
||||
// TODO: display error if plugin fails to start/stop
|
||||
|
@ -96,6 +155,48 @@
|
|||
return Modals.contentSettings(plugin, null, {
|
||||
dont_clone
|
||||
});
|
||||
},
|
||||
searchInput(e) {
|
||||
if (this.loadingOnline || this.loadingMore) return;
|
||||
this.onlinePlugins.filters.sterm = e.target.value;
|
||||
this.refreshOnline();
|
||||
},
|
||||
async scrollend(e) {
|
||||
if (this.onlinePlugins.pagination.page >= this.onlinePlugins.pagination.pages) return;
|
||||
if (this.loadingOnline || this.loadingMore) return;
|
||||
this.loadingMore = true;
|
||||
|
||||
try {
|
||||
const getPlugins = await BdWebApi.plugins.get({
|
||||
sterm: this.onlinePlugins.filters.sterm,
|
||||
page: this.onlinePlugins.pagination.page + 1,
|
||||
sort: this.onlinePlugins.filters.sort,
|
||||
ascending: this.onlinePlugins.filters.ascending
|
||||
});
|
||||
|
||||
this.onlinePlugins.docs = [...this.onlinePlugins.docs, ...getPlugins.docs];
|
||||
this.onlinePlugins.filters = getPlugins.filters;
|
||||
this.onlinePlugins.pagination = getPlugins.pagination;
|
||||
} catch (err) {
|
||||
Logger.err('PluginsView', err);
|
||||
} finally {
|
||||
this.loadingMore = false;
|
||||
}
|
||||
},
|
||||
async sortBy(by) {
|
||||
if (this.loadingOnline || this.loadingMore) return;
|
||||
if (this.onlinePlugins.filters.sort === by) {
|
||||
this.onlinePlugins.filters.ascending = !this.onlinePlugins.filters.ascending;
|
||||
} else {
|
||||
this.onlinePlugins.filters.sort = by;
|
||||
this.onlinePlugins.filters.ascending = false;
|
||||
}
|
||||
this.refreshOnline();
|
||||
},
|
||||
async searchByTag(tag) {
|
||||
if (this.loadingOnline || this.loadingMore) return;
|
||||
this.onlinePlugins.filters.sterm = tag;
|
||||
this.refreshOnline();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,30 +20,51 @@
|
|||
<div class="bd-remoteCardInfoBox bd-flex bd-flexGrow bd-flexCol">
|
||||
<div class="bd-remoteCardInfo">{{item.installs}} Installs</div>
|
||||
<div class="bd-remoteCardInfo">{{item.activeUsers}} Active Users</div>
|
||||
<div class="bd-remoteCardInfo">Updated: Some time ago</div>
|
||||
<div class="bd-remoteCardInfo">Updated {{fromNow()}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bd-flexRow bd-flex bd-flexGrow">
|
||||
<div class="bd-flexGrow bd-remoteCardTags">{{item.tags.join(', ')}}</div>
|
||||
<div class="bd-flexGrow bd-remoteCardTags">
|
||||
<div v-for="(tag, index) in item.tags" class="bd-remoteCardTag">
|
||||
<div @click="tagClicked(tag)">{{tag}}</div><span v-if="index + 1 < item.tags.length">, </span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bd-buttonGroup">
|
||||
<div class="bd-button">Install</div>
|
||||
<div class="bd-button" @click="install">Install</div>
|
||||
<div class="bd-button">Preview</div>
|
||||
<div class="bd-button">Source</div>
|
||||
<div class="bd-button" @click="openSourceUrl">Source</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Reflection, PackageInstaller } from 'modules';
|
||||
import { shell } from 'electron';
|
||||
|
||||
export default {
|
||||
props: ['item'],
|
||||
props: ['item', 'tagClicked'],
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
resolveThumb() {
|
||||
return `${this.item.repository.rawUri}/${this.item.files.previews[0].thumb}`;
|
||||
// TODO
|
||||
return '';
|
||||
// return `${this.item.repository.rawUri}/${this.item.files.previews[0].thumb}`;
|
||||
},
|
||||
fromNow() {
|
||||
const { Moment } = Reflection.modules;
|
||||
return Moment(this.item.updated).fromNow();
|
||||
},
|
||||
openSourceUrl() {
|
||||
if (!this.item.repository || !this.item.repository.baseUri) return;
|
||||
if (Object.assign(document.createElement('a'), { href: this.item.repository.baseUri }).hostname !== 'github.com') return;
|
||||
shell.openExternal(this.item.repository.baseUri);
|
||||
},
|
||||
async install() {
|
||||
await PackageInstaller.installRemotePackage(this.item.repository.assetUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,16 @@
|
|||
|
||||
<template>
|
||||
<div class="bd-settingswrap">
|
||||
<ScrollerWrap>
|
||||
<div v-if="noscroller" class="bd-flex bd-flexCol">
|
||||
<div class="bd-settingswrapHeader">
|
||||
<span class="bd-settingswrapHeaderText">{{ headertext }}</span>
|
||||
<slot name="header" />
|
||||
</div>
|
||||
<div class="bd-settingswrapContents bd-flex bd-flexGrow bd-flexCol">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
<ScrollerWrap v-else :scrollend="scrollend">
|
||||
<div class="bd-settingswrapHeader">
|
||||
<span class="bd-settingswrapHeaderText">{{ headertext }}</span>
|
||||
<slot name="header" />
|
||||
|
@ -27,7 +36,7 @@
|
|||
import { ScrollerWrap } from '../common';
|
||||
|
||||
export default {
|
||||
props: ['headertext'],
|
||||
props: ['headertext', 'scrollend', 'noscroller'],
|
||||
components: {
|
||||
ScrollerWrap
|
||||
}
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
/**
|
||||
* BetterDiscord Developer View Component
|
||||
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
|
||||
* All rights reserved.
|
||||
* https://betterdiscord.net
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
<template>
|
||||
<SettingsWrapper headertext="Super Secret">
|
||||
<div class="bd-flex bd-flexCol bd-devview">
|
||||
<FormButton @click="forceUpdate">Force Update</FormButton>
|
||||
<FormButton @click="debugConfig">Config Debug</FormButton>
|
||||
<FormButton @click="testUpdateUi">Update UI Test</FormButton>
|
||||
</div>
|
||||
</SettingsWrapper>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import SettingsWrapper from './SettingsWrapper.vue';
|
||||
import { FormButton } from '../common';
|
||||
import { Globals, Events, Updater } from 'modules';
|
||||
import { ClientIPC } from 'common';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
};
|
||||
},
|
||||
components: {
|
||||
SettingsWrapper,
|
||||
FormButton
|
||||
},
|
||||
methods: {
|
||||
forceUpdate() {
|
||||
ClientIPC.send('debug-updater-forceUpdate');
|
||||
},
|
||||
debugConfig() {
|
||||
console.log(Globals);
|
||||
},
|
||||
testUpdateUi() {
|
||||
Updater.testUi({
|
||||
'bd': [
|
||||
{
|
||||
'id': 'update',
|
||||
'version': '3.0.0',
|
||||
'currentVersion': '2.0.0',
|
||||
'text': 'Update test',
|
||||
'hint': 'Current: 2.0.0 | Latest: 3.0.0',
|
||||
'status': {
|
||||
'update': true,
|
||||
'updating': false,
|
||||
'updated': false,
|
||||
'error': null
|
||||
}
|
||||
},
|
||||
{
|
||||
'id': 'updating',
|
||||
'version': '3.0.0',
|
||||
'currentVersion': '2.0.0',
|
||||
'text': 'Updating test',
|
||||
'hint': 'Current: 2.0.0 | Latest: 3.0.0',
|
||||
'status': {
|
||||
'update': true,
|
||||
'updating': true,
|
||||
'updated': false,
|
||||
'error': null
|
||||
}
|
||||
},
|
||||
{
|
||||
'id': 'updated',
|
||||
'version': '3.0.0',
|
||||
'currentVersion': '2.0.0',
|
||||
'text': 'Updated test',
|
||||
'hint': 'Current: 2.0.0 | Latest: 3.0.0',
|
||||
'status': {
|
||||
'update': true,
|
||||
'updating': true,
|
||||
'updated': true,
|
||||
'error': null
|
||||
}
|
||||
},
|
||||
{
|
||||
'id': 'error',
|
||||
'version': '3.0.0',
|
||||
'currentVersion': '2.0.0',
|
||||
'text': 'Error test',
|
||||
'hint': 'Current: 2.0.0 | Latest: 3.0.0',
|
||||
'status': {
|
||||
'update': true,
|
||||
'updating': true,
|
||||
'updated': false,
|
||||
'error': 'Failed to update.'
|
||||
}
|
||||
}
|
||||
],
|
||||
'haveUpdates': true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -12,6 +12,7 @@
|
|||
<Card :item="theme">
|
||||
<SettingSwitch slot="toggle" :value="theme.enabled" @input="$emit('toggle-theme')" />
|
||||
<ButtonGroup slot="controls" v-if="!online">
|
||||
<Button v-if="devmode && !theme.packed" v-tooltip="'Package Theme'" @click="package"><MiBoxDownload size="18" /></Button>
|
||||
<Button v-tooltip="'Settings (shift + click to open settings without cloning the set)'" v-if="theme.hasSettings" @click="$emit('show-settings', $event.shiftKey)"><MiSettings size="18" /></Button>
|
||||
<Button v-tooltip="'Recompile (shift + click to reload)'" @click="$emit('reload-theme', $event.shiftKey)"><MiRefresh size="18" /></Button>
|
||||
<Button v-tooltip="'Edit'" @click="editTheme"><MiPencil size="18" /></Button>
|
||||
|
@ -22,17 +23,33 @@
|
|||
|
||||
<script>
|
||||
// Imports
|
||||
import { Toasts } from 'ui';
|
||||
import { Settings, ThemeManager } from 'modules';
|
||||
import { ClientLogger as Logger } from 'common';
|
||||
import { shell } from 'electron';
|
||||
import Card from './Card.vue';
|
||||
import { Button, ButtonGroup, SettingSwitch, MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension } from '../common';
|
||||
import { Button, ButtonGroup, SettingSwitch, MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension, MiBoxDownload } from '../common';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
devmode: Settings.getSetting('core', 'advanced', 'developer-mode').value
|
||||
}
|
||||
},
|
||||
props: ['theme', 'online'],
|
||||
components: {
|
||||
Card, Button, ButtonGroup, SettingSwitch,
|
||||
MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension
|
||||
MiSettings, MiRefresh, MiPencil, MiDelete, MiExtension, MiBoxDownload
|
||||
},
|
||||
methods: {
|
||||
async package() {
|
||||
try {
|
||||
const packagePath = await ThemeManager.packContent(this.theme.name, this.theme.contentPath);
|
||||
Toasts.success(`Theme Packaged: ${packagePath}`);
|
||||
} catch (err) {
|
||||
Logger.log('ThemeCard', err);
|
||||
}
|
||||
},
|
||||
editTheme() {
|
||||
try {
|
||||
shell.openItem(this.theme.contentPath);
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
*/
|
||||
|
||||
<template>
|
||||
<SettingsWrapper headertext="Themes">
|
||||
<SettingsWrapper headertext="Themes" :noscroller="true">
|
||||
<div class="bd-tabbar" slot="header">
|
||||
<div class="bd-button" :class="{'bd-active': local}" @click="showLocal">
|
||||
<h3>Installed</h3>
|
||||
|
@ -22,15 +22,38 @@
|
|||
</div>
|
||||
|
||||
<div class="bd-flex bd-flexCol bd-themesview">
|
||||
<div v-if="local" class="bd-flex bd-flexGrow bd-flexCol bd-themesContainer bd-localThemes">
|
||||
<ThemeCard v-for="theme in localThemes" :theme="theme" :key="theme.id" :data-theme-id="theme.id" @toggle-theme="toggleTheme(theme)" @reload-theme="reload => reloadTheme(theme, reload)" @show-settings="dont_clone => showSettings(theme, dont_clone)" @delete-theme="unload => deleteTheme(theme, unload)" />
|
||||
<div v-if="local" class="bd-flex bd-flexGrow bd-flexCol bd-themesContainer bd-localPh">
|
||||
<ScrollerWrap>
|
||||
<ThemeCard v-for="theme in localThemes" :theme="theme" :key="theme.id" :data-theme-id="theme.id" @toggle-theme="toggleTheme(theme)" @reload-theme="reload => reloadTheme(theme, reload)" @show-settings="dont_clone => showSettings(theme, dont_clone)" @delete-theme="unload => deleteTheme(theme, unload)" />
|
||||
</ScrollerWrap>
|
||||
</div>
|
||||
<div v-if="!local" class="bd-onlinePh">
|
||||
<div class="bd-fancySearch" :class="{'bd-active': loadingOnline || (onlineThemes && onlineThemes.docs)}">
|
||||
<input type="text" class="bd-textInput" @keydown.enter="searchInput" @keyup.stop/>
|
||||
<div v-else class="bd-onlinePh">
|
||||
<div class="bd-onlinePhHeader bd-flexCol">
|
||||
<div class="bd-flex bd-flexRow">
|
||||
<div v-if="loadingOnline" class="bd-spinnerContainer">
|
||||
<div class="bd-spinner7" />
|
||||
</div>
|
||||
<div class="bd-searchHint">{{searchHint}}</div>
|
||||
<div class="bd-fancySearch" :class="{'bd-disabled': loadingOnline, 'bd-active': loadingOnline || (onlineThemes && onlineThemes.docs)}">
|
||||
<input type="text" class="bd-textInput" placeholder="Search" @keydown.enter="searchInput" @keyup.stop :value="onlineThemes.filters.sterm"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bd-flex bd-flexRow" v-if="onlineThemes && onlineThemes.docs && onlineThemes.docs.length">
|
||||
<div class="bd-searchSort bd-flex bd-flexGrow">
|
||||
<div v-for="btn in sortBtns"
|
||||
class="bd-sort"
|
||||
:class="{'bd-active': onlineThemes.filters.sort === btn.toLowerCase(), 'bd-flipY': onlineThemes.filters.ascending}"
|
||||
@click="sortBy(btn.toLowerCase())">{{btn}}<MiChevronDown v-if="onlineThemes.filters.sort === btn.toLowerCase()" size="18" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2 v-if="loadingOnline">Loading</h2>
|
||||
<RemoteCard v-else-if="onlineThemes && onlineThemes.docs" v-for="theme in onlineThemes.docs" :key="theme.id" :item="theme"/>
|
||||
<ScrollerWrap class="bd-onlinePhBody" v-if="!loadingOnline && onlineThemes" :scrollend="scrollend">
|
||||
<RemoteCard v-if="onlineThemes && onlineThemes.docs" v-for="theme in onlineThemes.docs" :key="theme.id" :item="theme" :tagClicked="searchByTag"/>
|
||||
<div class="bd-spinnerContainer">
|
||||
<div v-if="loadingMore" class="bd-spinner7"/>
|
||||
</div>
|
||||
</ScrollerWrap>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsWrapper>
|
||||
|
@ -41,7 +64,7 @@
|
|||
import { ThemeManager, BdWebApi } from 'modules';
|
||||
import { Modals } from 'ui';
|
||||
import { ClientLogger as Logger } from 'common';
|
||||
import { MiRefresh } from '../common';
|
||||
import { MiRefresh, ScrollerWrap, MiChevronDown } from '../common';
|
||||
import SettingsWrapper from './SettingsWrapper.vue';
|
||||
import ThemeCard from './ThemeCard.vue';
|
||||
import RemoteCard from './RemoteCard.vue';
|
||||
|
@ -51,15 +74,32 @@
|
|||
data() {
|
||||
return {
|
||||
ThemeManager,
|
||||
sortBtns: ['Updated', 'Installs', 'Users', 'Rating'],
|
||||
local: true,
|
||||
localThemes: ThemeManager.localThemes,
|
||||
onlineThemes: null,
|
||||
loadingOnline: false
|
||||
onlineThemes: {
|
||||
docs: [],
|
||||
filters: {
|
||||
sterm: '',
|
||||
sort: 'installs',
|
||||
ascending: false
|
||||
},
|
||||
pagination: {
|
||||
total: 0,
|
||||
pages: 0,
|
||||
limit: 9,
|
||||
page: 1
|
||||
}
|
||||
},
|
||||
loadingOnline: false,
|
||||
loadingMore: false,
|
||||
searchHint: ''
|
||||
};
|
||||
},
|
||||
components: {
|
||||
SettingsWrapper, ThemeCard, RemoteCard,
|
||||
MiRefresh,
|
||||
MiRefresh, MiChevronDown,
|
||||
ScrollerWrap,
|
||||
RefreshBtn
|
||||
},
|
||||
methods: {
|
||||
|
@ -68,44 +108,19 @@
|
|||
},
|
||||
async showOnline() {
|
||||
this.local = false;
|
||||
if (this.loadingOnline || this.onlineThemes) return;
|
||||
},
|
||||
async refreshLocal() {
|
||||
await this.ThemeManager.refreshThemes();
|
||||
},
|
||||
async refreshOnline() {
|
||||
this.searchHint = '';
|
||||
if (this.loadingOnline || this.loadingMore) return;
|
||||
this.loadingOnline = true;
|
||||
try {
|
||||
// const getThemes = await BdWebApi.themes.get();
|
||||
// this.onlineThemes = JSON.parse(getThemes);
|
||||
const dummies = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
dummies.push({
|
||||
id: `theme${i}`,
|
||||
name: `Dummy ${i}`,
|
||||
tags: ['tag1', 'tag2', 'tag3', 'tag4', 'tag5'],
|
||||
installs: Math.floor(Math.random() * 10000),
|
||||
updated: '2018-07-21T14:51:32.057Z',
|
||||
rating: Math.floor(Math.random() * 1000),
|
||||
activeUsers: Math.floor(Math.random() * 1000),
|
||||
rated: Math.random() > .5,
|
||||
version: '1.0.0',
|
||||
repository: {
|
||||
name: 'ExampleRepository',
|
||||
baseUri: 'https://github.com/Jiiks/ExampleRepository',
|
||||
rawUri: 'https://github.com/Jiiks/ExampleRepository/raw/master'
|
||||
},
|
||||
files: {
|
||||
readme: 'Example/readme.md',
|
||||
previews: [{
|
||||
large: 'Example/preview1-big.png',
|
||||
thumb: 'Example/preview1-small.png'
|
||||
}]
|
||||
},
|
||||
author: 'Jiiks'
|
||||
});
|
||||
}
|
||||
this.onlineThemes = { docs: dummies };
|
||||
const getThemes = await BdWebApi.themes.get(this.onlineThemes.filters);
|
||||
this.onlineThemes = getThemes;
|
||||
if (!this.onlineThemes.docs) return;
|
||||
this.searchHint = `${this.onlineThemes.pagination.total} Results`;
|
||||
} catch (err) {
|
||||
Logger.err('ThemesView', err);
|
||||
} finally {
|
||||
|
@ -140,8 +155,46 @@
|
|||
});
|
||||
},
|
||||
searchInput(e) {
|
||||
this.loadingOnline = true;
|
||||
setTimeout(this.refreshOnline, 1000);
|
||||
if (this.loadingOnline || this.loadingMore) return;
|
||||
this.onlineThemes.filters.sterm = e.target.value;
|
||||
this.refreshOnline();
|
||||
},
|
||||
async scrollend(e) {
|
||||
if (this.onlineThemes.pagination.page >= this.onlineThemes.pagination.pages) return;
|
||||
if (this.loadingOnline || this.loadingMore) return;
|
||||
this.loadingMore = true;
|
||||
|
||||
try {
|
||||
const getThemes = await BdWebApi.themes.get({
|
||||
sterm: this.onlineThemes.filters.sterm,
|
||||
page: this.onlineThemes.pagination.page + 1,
|
||||
sort: this.onlineThemes.filters.sort,
|
||||
ascending: this.onlineThemes.filters.ascending
|
||||
});
|
||||
|
||||
this.onlineThemes.docs = [...this.onlineThemes.docs, ...getThemes.docs];
|
||||
this.onlineThemes.filters = getThemes.filters;
|
||||
this.onlineThemes.pagination = getThemes.pagination;
|
||||
} catch (err) {
|
||||
Logger.err('ThemesView', err);
|
||||
} finally {
|
||||
this.loadingMore = false;
|
||||
}
|
||||
},
|
||||
async sortBy(by) {
|
||||
if (this.loadingOnline || this.loadingMore) return;
|
||||
if (this.onlineThemes.filters.sort === by) {
|
||||
this.onlineThemes.filters.ascending = !this.onlineThemes.filters.ascending;
|
||||
} else {
|
||||
this.onlineThemes.filters.sort = by;
|
||||
this.onlineThemes.filters.ascending = false;
|
||||
}
|
||||
this.refreshOnline();
|
||||
},
|
||||
async searchByTag(tag) {
|
||||
if (this.loadingOnline || this.loadingMore) return;
|
||||
this.onlineThemes.filters.sterm = tag;
|
||||
this.refreshOnline();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* BetterDiscord Updater Status 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-settingSwitch">
|
||||
<div class="bd-title">
|
||||
<h3>{{item.text}}</h3>
|
||||
<h3 class="bd-updaterStatus bd-err" v-if="item.status.error">Update Failed!</h3>
|
||||
<h3 class="bd-updaterStatus bd-ok" v-else-if="item.status.updated">Done</h3>
|
||||
<div class="bd-spinner7" v-else-if="item.status.updating" />
|
||||
<h3 class="bd-updaterStatus" v-else>Unknown</h3>
|
||||
</div>
|
||||
<div class="bd-hint">{{item.hint}}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['item']
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* BetterDiscord Updater Switch 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-settingSwitch">
|
||||
<div class="bd-title">
|
||||
<h3>{{item.text}}</h3>
|
||||
<div class="bd-switchWrapper" @click="() => toggle(item)">
|
||||
<input type="checkbox" class="bd-switchCheckbox" />
|
||||
<div class="bd-switch" :class="{'bd-checked': item.status.update}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="bd-hint">{{item.hint}}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['item', 'toggle']
|
||||
}
|
||||
</script>
|
|
@ -11,19 +11,22 @@
|
|||
<template>
|
||||
<SettingsWrapper headertext="Updates">
|
||||
<div class="bd-flex bd-flexCol bd-updaterview">
|
||||
<div v-if="error" class="bd-formItem">
|
||||
<h5 style="margin-bottom: 10px;">Error installing updates</h5>
|
||||
<div class="bd-err bd-preWrap"><div class="bd-pre">{{ error.formatted }}</div></div>
|
||||
<div class="bd-formDivider"></div>
|
||||
<div class="bd-settingsCategories">
|
||||
<div class="bd-settingsCategory" v-if="bdUpdates && bdUpdates.length">
|
||||
<div class="bd-formItem">
|
||||
<h5>BetterDiscord</h5>
|
||||
</div>
|
||||
<div class="bd-formDivider"></div>
|
||||
<div v-for="update in bdUpdates">
|
||||
<UpdaterStatus :item="update" v-if="update.status.updating" />
|
||||
<UpdaterToggle :item="update" :toggle="() => updater.toggleUpdate(update)" v-else />
|
||||
<div class="bd-formDivider"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bd-formButton bd-button" @click="update">
|
||||
Update
|
||||
</div>
|
||||
|
||||
<template v-if="updatesAvailable">
|
||||
<p>Version {{ newVersion }} is available. You are currently running version {{ currentVersion }}.</p>
|
||||
<FormButton :onClick="install" :loading="updating">Install</FormButton>
|
||||
</template>
|
||||
<template v-else>
|
||||
<p>You're all up to date!</p>
|
||||
</template>
|
||||
</div>
|
||||
</SettingsWrapper>
|
||||
</template>
|
||||
|
@ -32,7 +35,8 @@
|
|||
import { Globals, Updater } from 'modules';
|
||||
import { ClientLogger as Logger } from 'common';
|
||||
import SettingsWrapper from './SettingsWrapper.vue';
|
||||
import { FormButton } from '../common';
|
||||
import UpdaterToggle from './UpdaterToggle.vue';
|
||||
import UpdaterStatus from './UpdaterStatus.vue';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
|
@ -44,26 +48,29 @@
|
|||
},
|
||||
components: {
|
||||
SettingsWrapper,
|
||||
FormButton
|
||||
UpdaterToggle,
|
||||
UpdaterStatus
|
||||
},
|
||||
computed: {
|
||||
updatesAvailable() {
|
||||
return this.updater.updatesAvailable;
|
||||
},
|
||||
newVersion() {
|
||||
return this.updater.latestVersion;
|
||||
return '2.0.0-beta.4';
|
||||
},
|
||||
error() {
|
||||
return this.updater.error;
|
||||
},
|
||||
updates() {
|
||||
return this.updater.updates;
|
||||
},
|
||||
bdUpdates() {
|
||||
return this.updater.bdUpdates;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async install() {
|
||||
this.updating = true;
|
||||
try {
|
||||
await this.updater.update();
|
||||
} catch (err) {}
|
||||
this.updating = false;
|
||||
update() {
|
||||
this.updater.startUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,5 +4,8 @@ export { default as CssEditorView } from './CssEditor.vue';
|
|||
export { default as PluginsView } from './PluginsView.vue';
|
||||
export { default as ThemesView } from './ThemesView.vue';
|
||||
export { default as UpdaterView } from './UpdaterView.vue';
|
||||
export { default as UpdaterStatus } from './UpdaterStatus.vue';
|
||||
export { default as UpdaterToggle } from './UpdaterToggle.vue';
|
||||
export { default as BdBadge } from './BdBadge.vue';
|
||||
export { default as ConnectivityView } from './ConnectivityView.vue';
|
||||
export { default as SuperSecretView } from './SuperSecretView.vue';
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
<template>
|
||||
<Modal class="bd-installModal" :headertext="modal.title" :closing="modal.closing" @close="modal.close" :noheader="true" :class="{'bd-err': !verifying && !verified, 'bd-installModalDone': installed, 'bd-installModalFail': err}">
|
||||
<template v-if="!installed && !err">
|
||||
<div slot="body" class="bd-installModalBody">
|
||||
<div class="bd-installModalTop">
|
||||
<div class="bd-installModalIcon">
|
||||
<div v-if="modal.icon" class="bd-installModalCi" :style="{backgroundImage: `url(${modal.icon})`}" />
|
||||
<MiExtension v-else />
|
||||
</div>
|
||||
<div class="bd-installModalInfo">
|
||||
<span>{{modal.config.info.name}} v{{modal.config.info.version}} by {{modal.config.info.authors.map(a => a.name).join(', ')}}</span>
|
||||
<div class="bd-installModalDesc">
|
||||
{{modal.config.info.description}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bd-installModalBottom">
|
||||
<div v-for="(perm, i) in modal.config.permissions" :key="`perm-${i}`" class="bd-permScope">
|
||||
<div class="bd-permAllow">
|
||||
<div class="bd-permCheck">
|
||||
<div class="bd-permCheckInner"></div>
|
||||
</div>
|
||||
<div class="bd-permInner">
|
||||
<div class="bd-permName">{{perm.HEADER}}</div>
|
||||
<div class="bd-permDesc">{{perm.BODY}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="verifying" slot="footer" class="bd-installModalFooter">
|
||||
<span class="bd-installModalStatus">Verifying {{modal.contentType}}</span>
|
||||
</div>
|
||||
<div v-else-if="!verified" slot="footer" class="bd-installModalFooter">
|
||||
<span class="bd-installModalStatus bd-err">Not verified!</span>
|
||||
<div class="bd-button bd-installModalUpload" @click="modal.confirm(0); modal.close();" v-if="modal.canUpload">Upload</div>
|
||||
<div class="bd-button bd-err" @click="install" v-if="allowUnsafe">{{ !alreadyInstalled ? 'Install' : 'Update' }}</div>
|
||||
</div>
|
||||
<div v-else-if="alreadyInstalled && upToDate" slot="footer" class="bd-installModalFooter">
|
||||
<span class="bd-installModalStatus">Up to date version already installed!</span>
|
||||
<div class="bd-button bd-installModalUpload" @click="modal.confirm(0); modal.close();" v-if="modal.canUpload">Upload</div>
|
||||
</div>
|
||||
<div v-else slot="footer" class="bd-installModalFooter">
|
||||
<span class="bd-installModalStatus bd-ok">Verified!</span>
|
||||
<div class="bd-button bd-installModalUpload" @click="modal.confirm(0); modal.close();" v-if="modal.canUpload">Upload</div>
|
||||
<div class="bd-button bd-ok" @click="install">{{ !alreadyInstalled ? 'Install' : 'Update' }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="err">
|
||||
<div slot="body" class="bd-installModalBody">
|
||||
<h3>Something went wrong :(</h3>
|
||||
<MiError />
|
||||
</div>
|
||||
<div slot="footer" class="bd-installModalFooter bd-installModalErrMsg">
|
||||
{{err.message}}
|
||||
<span>Ctrl+Shift+I</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div slot="body" class="bd-installModalBody">
|
||||
<h3>{{alreadyInstalled ? 'Succesfully Updated!' : 'Successfully Installed!'}}</h3>
|
||||
<MiSuccessCircle/>
|
||||
</div>
|
||||
<div slot="footer" class="bd-installModalFooter">
|
||||
<div class="bd-button bd-ok" v-if="installed.hasSettings" @click="showSettingsModal">Settings</div>
|
||||
<div class="bd-button bd-ok" @click="modal.confirm(); modal.close();">OK</div>
|
||||
</div>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
<script>
|
||||
// Imports
|
||||
import { Modal, MiExtension, MiSuccessCircle, MiError } from '../../common';
|
||||
import { PluginManager, ThemeManager, PackageInstaller, Settings } from 'modules';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
installing: false,
|
||||
verifying: true,
|
||||
alreadyInstalled: false,
|
||||
upToDate: true,
|
||||
allowUnsafe: Settings.getSetting('security', 'default', 'unsafe-content').value,
|
||||
installed: false,
|
||||
err: null
|
||||
}
|
||||
},
|
||||
props: ['modal'],
|
||||
components: {
|
||||
Modal, MiExtension, MiSuccessCircle, MiError
|
||||
},
|
||||
mounted() {
|
||||
const { contentType, config } = this.modal;
|
||||
const alreadyInstalled = contentType === 'plugin' ? PluginManager.getPluginById(config.info.id) : ThemeManager.getContentById(config.info.id);
|
||||
|
||||
if (alreadyInstalled) {
|
||||
this.alreadyInstalled = true;
|
||||
if (config.info.version > alreadyInstalled.version) {
|
||||
this.upToDate = false;
|
||||
}
|
||||
}
|
||||
this.verify();
|
||||
},
|
||||
methods: {
|
||||
async verify() {
|
||||
const verified = await PackageInstaller.verifyPackage(this.modal.filePath);
|
||||
this.verified = verified;
|
||||
this.verifying = false;
|
||||
},
|
||||
async install() {
|
||||
try {
|
||||
const installed = await PackageInstaller.installPackage(this.modal.filePath, this.modal.config.info.id || this.modal.config.info.name, this.modal.contentType, this.alreadyInstalled);
|
||||
this.installed = installed;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
this.err = err;
|
||||
}
|
||||
},
|
||||
showSettingsModal() {
|
||||
this.installed.showSettingsModal();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -22,3 +22,5 @@ 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';
|
||||
export { default as MiBoxDownload } from './materialicons/BoxDownload.vue';
|
||||
export { default as MiSuccessCircle } from './materialicons/SuccessCircle.vue';
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<template>
|
||||
<div :class="['bd-modal', {'bd-modalOut': closing, 'bd-modalScrolled': scrolled}]">
|
||||
<div class="bd-modalInner">
|
||||
<div class="bd-modalHeader">
|
||||
<div class="bd-modalHeader" v-if="!noheader">
|
||||
<slot name="header">
|
||||
<div v-if="$slots.icon" class="bd-modalIcon">
|
||||
<slot name="icon" />
|
||||
|
@ -41,7 +41,7 @@
|
|||
import { MiClose } from './MaterialIcon';
|
||||
|
||||
export default {
|
||||
props: ['headertext', 'closing'],
|
||||
props: ['headertext', 'closing', 'noheader'],
|
||||
components: {
|
||||
MiClose
|
||||
},
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
<template>
|
||||
<div class="bd-scrollerWrap" :class="{'bd-dark': dark}">
|
||||
<div class="bd-scroller">
|
||||
<div class="bd-scroller" @scroll="onscroll">
|
||||
<slot/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -18,6 +18,13 @@
|
|||
|
||||
<script>
|
||||
export default {
|
||||
props: ['dark']
|
||||
props: ['dark', 'scrollend'],
|
||||
methods: {
|
||||
onscroll(e) {
|
||||
if (!this.scrollend) return;
|
||||
const { offsetHeight, scrollTop, scrollHeight } = e.target;
|
||||
if (offsetHeight + scrollTop >= scrollHeight) this.scrollend(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* 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="M 4.9956,3L 19.0076,3L 20.7359,5.99339L 20.7304,5.99653C 20.9018,6.29149 21,6.63428 21,7L 21,19C 21,20.1046 20.1046,21 19,21L 5,21C 3.89543,21 3,20.1046 3,19L 3,7C 3,6.638 3.09618,6.29846 3.26437,6.00554L 3.26135,6.0038L 4.9956,3 Z M 5.57294,4.00001L 4.99559,5.00001L 5,5.00001L 19.0076,5.00002L 18.4303,4.00001L 5.57294,4.00001 Z M 7,12L 12,17L 17,12L 14,12L 14,10L 10,10L 10,12L 7,12 Z "/>
|
||||
</svg>
|
||||
</span>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['size']
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* 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="M 19.9994,11.9981C 19.9994,16.4161 16.4174,19.9981 11.9994,19.9981C 7.58139,19.9981 3.99939,16.4161 3.99939,11.9981C 3.99939,7.58007 7.58139,3.99807 11.9994,3.99807C 12.7634,3.99807 13.5004,4.11207 14.2004,4.31207L 15.7724,2.74007C 14.6074,2.26467 13.3354,1.99807 11.9994,1.99807C 6.47638,1.99807 1.99939,6.47507 1.99939,11.9981C 1.99939,17.5211 6.47638,21.9981 11.9994,21.9981C 17.5224,21.9981 21.9994,17.5211 21.9994,11.9981M 7.91339,10.0841L 6.49939,11.4981L 10.9994,15.9981L 20.9994,5.99807L 19.5854,4.58407L 10.9994,13.1701L 7.91339,10.0841 Z " />
|
||||
</svg>
|
||||
</span>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: ['size']
|
||||
}
|
||||
</script>
|
|
@ -9,7 +9,7 @@
|
|||
*/
|
||||
|
||||
import { Utils, ClientLogger as Logger } from 'common';
|
||||
import { ReactComponents, Reflection, MonkeyPatch } from 'modules';
|
||||
import { Reflection, MonkeyPatch } from 'modules';
|
||||
import { VueInjector, Toasts } from 'ui';
|
||||
import CMGroup from './components/contextmenu/Group.vue';
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import ErrorModal from './components/bd/modals/ErrorModal.vue';
|
|||
import SettingsModal from './components/bd/modals/SettingsModal.vue';
|
||||
import PermissionModal from './components/bd/modals/PermissionModal.vue';
|
||||
import InputModal from './components/bd/modals/InputModal.vue';
|
||||
import InstallModal from './components/bd/modals/InstallModal.vue';
|
||||
|
||||
let modals = 0;
|
||||
|
||||
|
@ -190,6 +191,19 @@ export default class Modals {
|
|||
return new Modal(modal, InputModal);
|
||||
}
|
||||
|
||||
static installModal(contentType, config, filePath, icon, canUpload = false) {
|
||||
return this.add(this.createInstallModal(contentType, config, filePath, icon, canUpload));
|
||||
}
|
||||
|
||||
static createInstallModal(contentType, config, filePath, icon, canUpload = false) {
|
||||
const modal = { contentType, config, filePath, icon, canUpload };
|
||||
modal.promise = new Promise((resolve, reject) => {
|
||||
modal.confirm = value => resolve(value);
|
||||
modal.beforeClose = () => reject();
|
||||
});
|
||||
return new Modal(modal, InstallModal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new permissions modal and adds it to the open stack.
|
||||
* The modal will have a promise property that will be set to a Promise object that is resolved or rejected if the user accepts the permissions or closes the modal.
|
||||
|
|
|
@ -87,8 +87,7 @@ export default class extends Module {
|
|||
async patchNameTag() {
|
||||
if (this.PatchedNameTag) return this.PatchedNameTag;
|
||||
|
||||
const selector = Reflection.resolve('nameTag', 'username', 'discriminator', 'ownerIcon').selector;
|
||||
const NameTag = await ReactComponents.getComponent('NameTag', {selector});
|
||||
const NameTag = await ReactComponents.getComponent('NameTag');
|
||||
|
||||
this.PatchedNameTag = class extends NameTag.component {
|
||||
render() {
|
||||
|
|
|
@ -9,6 +9,7 @@ export * from './contextmenus';
|
|||
export { default as VueInjector } from './vueinjector';
|
||||
export { default as Reflection } from './reflection';
|
||||
|
||||
export { default as Autocomplete } from './autocomplete';
|
||||
export { default as ProfileBadges } from './profilebadges';
|
||||
export { default as ClassNormaliser } from './classnormaliser';
|
||||
|
||||
|
|
|
@ -71,7 +71,7 @@ export default class {
|
|||
|
||||
get vueMount() {
|
||||
const element = ReactDOM.findDOMNode(this);
|
||||
if (!element) return;
|
||||
if (!element) return null;
|
||||
if (this.props.mountAtTop) return element;
|
||||
if (element.firstChild) return element.firstChild;
|
||||
const newElement = document.createElement('span');
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const VueLoaderPlugin = require('vue-loader/lib/plugin');
|
||||
|
||||
const jsLoader = {
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'babel-loader'
|
||||
}
|
||||
};
|
||||
|
||||
const vueLoader = {
|
||||
test: /\.(vue)$/,
|
||||
use: 'vue-loader'
|
||||
};
|
||||
|
||||
const scssLoader = {
|
||||
test: /\.scss$/,
|
||||
exclude: /node_modules/,
|
||||
use: ['css-loader', 'sass-loader']
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
entry: './src/index.js',
|
||||
module: {
|
||||
rules: [jsLoader, vueLoader, scssLoader]
|
||||
},
|
||||
externals: {
|
||||
electron: 'require("electron")',
|
||||
fs: 'require("fs")',
|
||||
path: 'require("path")',
|
||||
util: 'require("util")',
|
||||
process: 'require("process")',
|
||||
net: 'require("net")',
|
||||
request: 'require(require("path").join(require("electron").remote.app.getAppPath(), "node_modules", "request"))',
|
||||
sparkplug: 'require("../../core/dist/sparkplug")',
|
||||
'node-crypto': 'require("crypto")',
|
||||
'child_process': 'require("child_process")'
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
vue$: path.resolve('..', 'node_modules', 'vue', 'dist', 'vue.esm.js')
|
||||
},
|
||||
modules: [
|
||||
path.resolve('..', 'node_modules'),
|
||||
path.resolve('..', 'common', 'modules'),
|
||||
path.resolve('src', 'modules'),
|
||||
path.resolve('src', 'ui'),
|
||||
path.resolve('src', 'plugins'),
|
||||
path.resolve('src', 'structs'),
|
||||
path.resolve('src', 'builtin')
|
||||
]
|
||||
},
|
||||
node: {
|
||||
process: false,
|
||||
__filename: false,
|
||||
__dirname: false
|
||||
},
|
||||
plugins: [
|
||||
new VueLoaderPlugin()
|
||||
]
|
||||
};
|
|
@ -1,67 +1,22 @@
|
|||
const path = require('path');
|
||||
|
||||
const baseconfig = require('./webpack.base.config');
|
||||
|
||||
const merge = require('webpack-merge');
|
||||
const webpack = require('webpack');
|
||||
|
||||
const jsLoader = {
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
query: {
|
||||
presets: ['react']
|
||||
}
|
||||
};
|
||||
|
||||
const vueLoader = {
|
||||
test: /\.(vue)$/,
|
||||
loader: 'vue-loader'
|
||||
};
|
||||
|
||||
const scssLoader = {
|
||||
test: /\.scss$/,
|
||||
exclude: /node_modules/,
|
||||
loader: ['css-loader', 'sass-loader']
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
entry: './src/index.js',
|
||||
const config = {
|
||||
mode: 'development',
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'betterdiscord.client.js'
|
||||
},
|
||||
module: {
|
||||
loaders: [jsLoader, vueLoader, scssLoader]
|
||||
},
|
||||
externals: {
|
||||
electron: 'require("electron")',
|
||||
fs: 'require("fs")',
|
||||
path: 'require("path")',
|
||||
util: 'require("util")',
|
||||
process: 'require("process")',
|
||||
net: 'require("net")',
|
||||
request: 'require(require("path").join(require("electron").remote.app.getAppPath(), "node_modules", "request"))',
|
||||
sparkplug: 'require("../../core/dist/sparkplug")',
|
||||
'node-crypto': 'require("crypto")'
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
vue$: path.resolve('..', 'node_modules', 'vue', 'dist', 'vue.esm.js')
|
||||
},
|
||||
modules: [
|
||||
path.resolve('..', 'node_modules'),
|
||||
path.resolve('..', 'common', 'modules'),
|
||||
path.resolve('src', 'modules'),
|
||||
path.resolve('src', 'ui'),
|
||||
path.resolve('src', 'plugins'),
|
||||
path.resolve('src', 'structs'),
|
||||
path.resolve('src', 'builtin')
|
||||
]
|
||||
},
|
||||
node: {
|
||||
process: false,
|
||||
__filename: false,
|
||||
__dirname: false
|
||||
},
|
||||
plugins: [
|
||||
new webpack.NamedModulesPlugin(),
|
||||
new webpack.EvalSourceMapDevToolPlugin()
|
||||
]
|
||||
new webpack.NamedModulesPlugin()
|
||||
],
|
||||
externals: {
|
||||
asar: 'require("asar")'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = merge(baseconfig, config);
|
||||
|
|
|
@ -1,70 +1,24 @@
|
|||
const path = require('path');
|
||||
|
||||
const baseconfig = require('./webpack.base.config');
|
||||
|
||||
const merge = require('webpack-merge');
|
||||
const webpack = require('webpack');
|
||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
||||
|
||||
const jsLoader = {
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
query: {
|
||||
presets: ['react']
|
||||
}
|
||||
};
|
||||
|
||||
const vueLoader = {
|
||||
test: /\.(vue)$/,
|
||||
loader: 'vue-loader'
|
||||
};
|
||||
|
||||
const scssLoader = {
|
||||
test: /\.scss$/,
|
||||
exclude: /node_modules/,
|
||||
loader: ['css-loader', 'sass-loader']
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
entry: './src/index.js',
|
||||
const config = {
|
||||
mode: 'production',
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'betterdiscord.client-release.js'
|
||||
},
|
||||
module: {
|
||||
loaders: [jsLoader, vueLoader, scssLoader]
|
||||
},
|
||||
externals: {
|
||||
electron: 'require("electron")',
|
||||
fs: 'require("fs")',
|
||||
path: 'require("path")',
|
||||
util: 'require("util")',
|
||||
process: 'require("process")',
|
||||
net: 'require("net")',
|
||||
request: 'require(require("path").join(require("electron").remote.app.getAppPath(), "node_modules", "request"))',
|
||||
sparkplug: 'require("./sparkplug")',
|
||||
'node-crypto': 'require("crypto")'
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
vue$: path.resolve('..', 'node_modules', 'vue', 'dist', 'vue.esm.js')
|
||||
},
|
||||
modules: [
|
||||
path.resolve('..', 'node_modules'),
|
||||
path.resolve('..', 'common', 'modules'),
|
||||
path.resolve('src', 'modules'),
|
||||
path.resolve('src', 'ui'),
|
||||
path.resolve('src', 'plugins'),
|
||||
path.resolve('src', 'structs'),
|
||||
path.resolve('src', 'builtin')
|
||||
]
|
||||
},
|
||||
node: {
|
||||
process: false,
|
||||
__filename: false,
|
||||
__dirname: false
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
PRODUCTION: JSON.stringify(true)
|
||||
}),
|
||||
new UglifyJsPlugin()
|
||||
]
|
||||
})
|
||||
],
|
||||
externals: {
|
||||
sparkplug: 'require("./sparkplug")'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = merge(baseconfig, config);
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* BetterDiscord axios wrapper
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import axios from 'axios';
|
||||
|
||||
export default class AxiosWrapper {
|
||||
|
||||
static get axios() { return axios; }
|
||||
|
||||
static get github() {
|
||||
return this._github ? this._github : (
|
||||
this._github = {
|
||||
main: this.create('https://github.com'),
|
||||
api: this.create('https://api.github.com')
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
static get zl() {
|
||||
return this._zl ? this._zl : (this._zl = {
|
||||
api: this.create('https://zl', 1000, this.zlHeaders),
|
||||
cdn: this.create('https://zl', 1000, this.zlHeaders)
|
||||
});
|
||||
}
|
||||
|
||||
static create(baseUrl, timeout = 1000, headers = null) {
|
||||
return axios.create({ baseURL: baseUrl, timeout, headers: headers ? headers : this.defaultHeaders });
|
||||
}
|
||||
|
||||
static get defaultHeaders() {
|
||||
return {
|
||||
'User-Agent': 'BetterDiscordApp User'
|
||||
};
|
||||
}
|
||||
|
||||
static get zlHeaders() {
|
||||
return {
|
||||
'User-Agent': 'BetterDiscordApp User',
|
||||
'X-ZL-Apikey': '1a20cce89a2dbd163fc9570f3246c20891e62b2818ada55f82fa3d1d96fa7ef4',
|
||||
'X-ZL-User': 'anonymous'
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -3,3 +3,4 @@ export { default as Filters } from './filters';
|
|||
export { default as Logger, ClientLogger } from './logger';
|
||||
export { default as ClientIPC } from './bdipc';
|
||||
export { default as AsyncEventEmitter } from './async-eventemitter';
|
||||
export { default as Axi } from './axi';
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
module.exports = function(api) {
|
||||
|
||||
api.cache(true);
|
||||
|
||||
const presets = [['@babel/env', {
|
||||
targets: {
|
||||
'node': '8.6.0'
|
||||
}
|
||||
}]];
|
||||
|
||||
const plugins = [];
|
||||
|
||||
return {
|
||||
presets,
|
||||
plugins
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
"name": "bdcore",
|
||||
"description": "BetterDiscord core package",
|
||||
"author": "Jiiks",
|
||||
"version": "2.0.0b",
|
||||
"version": "2.0.0-beta.6",
|
||||
"homepage": "https://betterdiscord.net",
|
||||
"license": "MIT",
|
||||
"main": "dist/main.js",
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"img-src": [
|
||||
"https://cdn.betterttv.net",
|
||||
"https://cdn.frankerfacez.com",
|
||||
"https://i.imgur.com"
|
||||
],
|
||||
"style-src": [
|
||||
"https://fonts.googleapis.com"
|
||||
],
|
||||
"script-src": [
|
||||
"'sha256-fSHKdpQGCHaIqWP3SpJOuUHrLp49jy4dWHzZ/RBJ/p4='",
|
||||
"'sha256-VFJcfKY5B3EBkFDgQnv3CozPwBlZcxwssfLVWlPFfZU='",
|
||||
"'sha256-VzDmLZ4PxPkOS/KY7ITzLQsSWhfCnvUrNculcj8UNgE='",
|
||||
"'sha256-l6K+77Z1cmldR9gIvaVWlboF/zr5MXCQHcsEHfnr5TU='"
|
||||
],
|
||||
"connect-src": [
|
||||
"https://github.com",
|
||||
"https://api.github.com",
|
||||
"https://betterdiscord.net",
|
||||
"https://api.betterdiscord.net",
|
||||
"https://cdn.betterdiscord.net",
|
||||
"https://api.supersecretbdapiandcdn.net",
|
||||
"https://cdn.supersecretbdapiandcdn.net"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
React Devtools: sha256-fSHKdpQGCHaIqWP3SpJOuUHrLp49jy4dWHzZ/RBJ/p4=
|
||||
Vue Devtools: sha256-VFJcfKY5B3EBkFDgQnv3CozPwBlZcxwssfLVWlPFfZU=
|
||||
Vue Detector: sha256-l6K+77Z1cmldR9gIvaVWlboF/zr5MXCQHcsEHfnr5TU=
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue