Compare commits
180 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 | |
Jiiks | 6ca36ebbb5 | |
Jiiks | f511e4556e | |
Jiiks | 3a370f2827 | |
Jiiks | 90f5596cb2 | |
Jiiks | 75439a600e | |
Jiiks | 6c8631f1cd | |
Jiiks | 0311602539 | |
Jiiks | b5e8098d20 | |
Jiiks | 111be57b59 | |
Jiiks | 76e19c8469 | |
Jiiks | ccff016820 | |
Jiiks | 021e282b1c | |
Jiiks | 6699fcc1e7 | |
Jiiks | f3ea192974 | |
Jiiks | 9c1a93f4c1 | |
Jiiks | 2c23f18e89 | |
Jiiks | c88d2cdae9 |
|
@ -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",
|
||||
|
|
|
@ -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,12 +74,14 @@ 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) => {
|
||||
this.patch(retVal[child[0]], child[1], cb, when);
|
||||
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,16 +54,16 @@ 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 }, m => m.defaultProps && m.defaultProps.hasOwnProperty('disableButtons'));
|
||||
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, originalReturn) {
|
||||
this.patch(originalReturn.props, 'children', function(obj, args, returnValue) {
|
||||
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;
|
||||
|
|
|
@ -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 }, m => m.defaultProps && m.defaultProps.hasOwnProperty('disableButtons'));
|
||||
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,15 +217,18 @@ 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 }, m => m.defaultProps && m.defaultProps.hasOwnProperty('disableButtons'));
|
||||
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';
|
||||
|
|
|
@ -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, PackageInstaller } 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,
|
||||
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,
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -220,6 +220,7 @@ export default class {
|
|||
const unpackedPath = path.join(Globals.getPath('tmp'), packageName);
|
||||
|
||||
asar.extractAll(packagePath, unpackedPath);
|
||||
|
||||
return this.preloadContent({
|
||||
config,
|
||||
contentPath: unpackedPath,
|
||||
|
@ -228,8 +229,8 @@ export default class {
|
|||
packageName,
|
||||
packed: true
|
||||
}, reload, index);
|
||||
|
||||
} catch (err) {
|
||||
Logger.log('ContentManager', ['Error extracting packed content', err]);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
@ -322,12 +323,6 @@ export default class {
|
|||
return content;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
} finally {
|
||||
if (typeof dirName === 'object' && dirName.packed) {
|
||||
rimraf(dirName.contentPath, err => {
|
||||
if (err) Logger.err(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -353,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);
|
||||
|
@ -384,7 +380,7 @@ export default class {
|
|||
|
||||
if (this.unloadContentHook) this.unloadContentHook(content);
|
||||
|
||||
if (reload) return content.packed ? this.preloadPackedContent(content.packed.pkg, true, index) : this.preloadContent(content.dirName, true, index);
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
|
@ -36,10 +36,6 @@ export default new class extends Module {
|
|||
|
||||
async first() {
|
||||
const config = await ClientIPC.send('getConfig');
|
||||
config.paths.push({
|
||||
id: 'tmp',
|
||||
path: path.join(config.paths.find(p => p.id === 'base').path, 'tmp')
|
||||
});
|
||||
this.setState({ config });
|
||||
|
||||
// This is for Discord to stop error reporting :3
|
||||
|
@ -102,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';
|
||||
|
|
|
@ -6,23 +6,25 @@ import rimraf from 'rimraf';
|
|||
|
||||
import { request } from 'vendor';
|
||||
import { Modals } from 'ui';
|
||||
import { Utils } from 'common';
|
||||
import { Utils, FileUtils } from 'common';
|
||||
import PluginManager from './pluginmanager';
|
||||
import Globals from './globals';
|
||||
import Security from './security';
|
||||
import { ReactComponents } from './reactcomponents';
|
||||
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 {String} channelId Current channel id
|
||||
* @param {Boolean} canUpload If the user can upload files in current window
|
||||
* @returns {Number} returns action code from modal
|
||||
*/
|
||||
static async dragAndDropHandler(filePath, channelId) {
|
||||
static async dragAndDropHandler(filePath, canUpload) {
|
||||
try {
|
||||
const config = JSON.parse(asar.extractFile(filePath, 'config.json').toString());
|
||||
const { info, main } = config;
|
||||
|
@ -36,12 +38,8 @@ export default class PackageInstaller {
|
|||
const isPlugin = info.type && info.type === 'plugin' || main.endsWith('.js');
|
||||
|
||||
// Show install modal
|
||||
const modalResult = await Modals.installModal(isPlugin ? 'plugin' : 'theme', config, filePath, icon).promise;
|
||||
|
||||
if (modalResult === 0) {
|
||||
// Upload it instead
|
||||
}
|
||||
|
||||
const modalResult = await Modals.installModal(isPlugin ? 'plugin' : 'theme', config, filePath, icon, canUpload).promise;
|
||||
return modalResult;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
@ -86,15 +84,10 @@ export default class PackageInstaller {
|
|||
|
||||
await oldContent.unload(true);
|
||||
|
||||
if (oldContent.packed && oldContent.packed.packageName !== nameOrId) {
|
||||
rimraf(oldContent.packed.packagePath, err => {
|
||||
if (err) throw err;
|
||||
});
|
||||
} else {
|
||||
rimraf(oldContent.contentPath, err => {
|
||||
if (err) throw err;
|
||||
});
|
||||
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) {
|
||||
|
@ -135,33 +128,51 @@ export default class PackageInstaller {
|
|||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
const { selector } = Reflection.resolve('uploadArea');
|
||||
this.UploadArea = await ReactComponents.getComponent('UploadArea', { selector });
|
||||
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 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();
|
||||
const unpatchUploadAreaHandleDrop = MonkeyPatch('BD:ReactComponents', UploadArea.component.prototype).instead('handleDrop', (component, [e], original) => this.handleDrop(component, e, original));
|
||||
|
||||
PackageInstaller.dragAndDropHandler(e.dataTransfer.files[0].path, DiscordApi.currentChannel.id);
|
||||
this.unpatchUploadArea = () => {
|
||||
unpatchUploadAreaHandleDrop();
|
||||
root.removeEventListener('drop', rootHandleDrop);
|
||||
this.unpatchUploadArea = undefined;
|
||||
};
|
||||
|
||||
// Remove their handler, add ours, then read 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);
|
||||
for (const element of document.querySelectorAll(UploadArea.important.selector)) {
|
||||
const stateNode = Reflection.DOM(element).getComponentStateNode(UploadArea);
|
||||
|
||||
this.unpatchUploadArea = function () {
|
||||
reflect.element.removeEventListener('drop', callback);
|
||||
};
|
||||
element.removeEventListener('drop', stateNode.handleDrop);
|
||||
stateNode.handleDrop = UploadArea.component.prototype.handleDrop.bind(stateNode);
|
||||
element.addEventListener('drop', stateNode.handleDrop);
|
||||
|
||||
stateNode.forceUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -109,21 +109,9 @@ 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: packed ? packed.packageName : paths.dirName,
|
||||
mainPath: paths.mainPath
|
||||
}
|
||||
configs, info, main, paths
|
||||
});
|
||||
|
||||
if (packed) instance.packed = {
|
||||
pkg: packed.pkg,
|
||||
packageName: packed.packageName,
|
||||
packagePath: packed.packagePath,
|
||||
packed: true
|
||||
}; else instance.packed = false;
|
||||
|
||||
if (instance.enabled && this.loaded) {
|
||||
instance.userConfig.enabled = false;
|
||||
instance.start(false);
|
||||
|
|
|
@ -173,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);
|
||||
}
|
||||
}
|
||||
|
@ -186,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 }
|
||||
|
||||
|
@ -222,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;
|
||||
|
||||
|
@ -239,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;
|
||||
}
|
||||
|
||||
|
@ -276,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;
|
||||
|
@ -351,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});
|
||||
|
@ -369,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);
|
||||
|
||||
|
@ -385,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) => {
|
||||
|
@ -403,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) => {
|
||||
|
@ -419,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);
|
||||
|
||||
|
@ -435,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);
|
||||
|
||||
|
@ -447,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);
|
||||
|
@ -469,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();
|
||||
|
@ -485,24 +559,28 @@ 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();
|
||||
}
|
||||
|
||||
static async patchUploadArea() {
|
||||
PackageInstaller.uploadAreaPatch();
|
||||
const { selector } = Reflection.resolve('uploadArea');
|
||||
this.UploadArea = await ReactComponents.getComponent('UploadArea', {selector});
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,122 +1,125 @@
|
|||
.bd-settingsButton {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 22px;
|
||||
width: 70px;
|
||||
height: 48px;
|
||||
left: 0;
|
||||
box-shadow: 0 1px 0 rgba(0, 0, 0, .2), 0 2px 0 rgba(0, 0, 0, .06);
|
||||
opacity: 1;
|
||||
|
||||
.platform-darwin & { // sass-lint:disable-line class-name-format
|
||||
top: 27px;
|
||||
}
|
||||
|
||||
.platform-linux & { // sass-lint:disable-line class-name-format
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.bd-settingsButtonBtn {
|
||||
backface-visibility: hidden;
|
||||
-webkit-backface-visibility: hidden;
|
||||
width: 70px;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 22px;
|
||||
width: 70px;
|
||||
height: 48px;
|
||||
cursor: pointer;
|
||||
filter: grayscale(100%);
|
||||
opacity: .5;
|
||||
position: relative;
|
||||
transition: all .3s cubic-bezier(.4,0,0,1);
|
||||
left: 0;
|
||||
box-shadow: 0 1px 0 rgba(0, 0, 0, .2), 0 2px 0 rgba(0, 0, 0, .06);
|
||||
opacity: 1;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content:"";
|
||||
display: block;
|
||||
position: absolute;
|
||||
top:0;
|
||||
left:0;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
.platform-darwin & { // sass-lint:disable-line class-name-format
|
||||
top: 27px;
|
||||
}
|
||||
|
||||
&::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(0.5);
|
||||
opacity: 0;
|
||||
transition:all .3s cubic-bezier(.4,0,0,1);
|
||||
}
|
||||
.platform-linux & { // sass-lint:disable-line class-name-format
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&:not(.bd-loading) {
|
||||
&:hover {
|
||||
filter: none;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.bd-settingsButtonBtn {
|
||||
backface-visibility: hidden;
|
||||
-webkit-backface-visibility: hidden;
|
||||
width: 70px;
|
||||
height: 48px;
|
||||
cursor: pointer;
|
||||
filter: grayscale(100%);
|
||||
opacity: .5;
|
||||
position: relative;
|
||||
transition: all .3s cubic-bezier(.4, 0, 0, 1);
|
||||
|
||||
&.bd-loading {
|
||||
animation: bd-settingsButtonPulse 1.5s infinite;
|
||||
}
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
&.bd-updates {
|
||||
filter: hue-rotate(250deg) !important; // sass-lint:disable-line no-important
|
||||
opacity: 1 !important; // sass-lint:disable-line no-important
|
||||
}
|
||||
}
|
||||
&::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;
|
||||
}
|
||||
|
||||
&.bd-hideButton {
|
||||
animation: bd-fadeOut .4s ease-out;
|
||||
&::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);
|
||||
}
|
||||
|
||||
&.bd-active {
|
||||
animation: bd-fadeIn .4s ease-in;
|
||||
}
|
||||
&:not(.bd-loading) {
|
||||
&:hover {
|
||||
filter: none;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.bd-active) {
|
||||
&:not(.bd-animating) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
&.bd-loading {
|
||||
animation: bd-settingsButtonPulse 1.5s infinite;
|
||||
}
|
||||
|
||||
&.bd-active {
|
||||
opacity: 1;
|
||||
}
|
||||
&.bd-updates {
|
||||
filter: hue-rotate(250deg) !important; // sass-lint:disable-line no-important
|
||||
opacity: 1 !important; // sass-lint:disable-line no-important
|
||||
}
|
||||
}
|
||||
|
||||
&.bd-active,
|
||||
&.bd-hideButton {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
&.bd-hideButton {
|
||||
animation: bd-fadeOut .4s ease-out;
|
||||
|
||||
.bd-settingsButtonBtn {
|
||||
filter: none;
|
||||
opacity: 1;
|
||||
width: 130px;
|
||||
height: 43px;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
&.bd-active {
|
||||
animation: bd-fadeIn .4s ease-in;
|
||||
}
|
||||
|
||||
&.bd-active,
|
||||
&.bd-animating {
|
||||
z-index: 3001;
|
||||
}
|
||||
&:not(.bd-active) {
|
||||
&:not(.bd-animating) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.bd-active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.bd-active,
|
||||
&.bd-hideButton {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
|
||||
.bd-settingsButtonBtn {
|
||||
filter: none;
|
||||
opacity: 1;
|
||||
width: 130px;
|
||||
height: 43px;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.bd-active,
|
||||
&.bd-animating {
|
||||
z-index: 3001;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,180 +1,180 @@
|
|||
.bd-settings {
|
||||
position: absolute;
|
||||
backface-visibility: hidden;
|
||||
-webkit-backface-visibility: hidden;
|
||||
top: 22px;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 1001;
|
||||
width: 310px;
|
||||
transform: translateX(-310px);
|
||||
opacity: 0;
|
||||
transition: transform .3s cubic-bezier(.4,0,0,1), opacity .25s ease;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
backface-visibility: hidden;
|
||||
-webkit-backface-visibility: hidden;
|
||||
top: 22px;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 1001;
|
||||
width: 310px;
|
||||
transform: translateX(-310px);
|
||||
opacity: 0;
|
||||
transition: transform .3s cubic-bezier(.4, 0, 0, 1), opacity .25s ease;
|
||||
pointer-events: none;
|
||||
|
||||
&.bd-active {
|
||||
width: 900px;
|
||||
transform: none;
|
||||
opacity: 1;
|
||||
}
|
||||
&.bd-active {
|
||||
width: 900px;
|
||||
transform: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.bd-settingsX {
|
||||
position: absolute;
|
||||
top: 18px;
|
||||
left: 255px;
|
||||
border: 2px solid #6e6e6e;
|
||||
border-radius: 50%;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
.bd-settingsX {
|
||||
position: absolute;
|
||||
top: 18px;
|
||||
left: 255px;
|
||||
border: 2px solid #6e6e6e;
|
||||
border-radius: 50%;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
.platform-darwin & { // sass-lint:disable-line class-name-format
|
||||
top: 43px;
|
||||
}
|
||||
.platform-darwin & { // sass-lint:disable-line class-name-format
|
||||
top: 43px;
|
||||
}
|
||||
|
||||
.bd-xText {
|
||||
color: #72767d;
|
||||
position: absolute;
|
||||
top: 32px;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
.bd-xText {
|
||||
color: #72767d;
|
||||
position: absolute;
|
||||
top: 32px;
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.bd-materialDesignIcon {
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
fill: #72767d;
|
||||
}
|
||||
.bd-materialDesignIcon {
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
fill: #72767d;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: hsla(218, 5%, 47%, .3);
|
||||
&:hover {
|
||||
background-color: hsla(218, 5%, 47%, .3);
|
||||
|
||||
.bd-materialDesignIcon {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
.bd-materialDesignIcon {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bd-info {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
overflow: hidden;
|
||||
padding: 0 25px;
|
||||
margin: 10px 0;
|
||||
.bd-info {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
overflow: hidden;
|
||||
padding: 0 25px;
|
||||
margin: 10px 0;
|
||||
|
||||
.bd-vtext {
|
||||
color: #414245;
|
||||
font-weight: 700;
|
||||
font-size: 12px;
|
||||
flex-grow: 1;
|
||||
height: 20px;
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
}
|
||||
.bd-vtext {
|
||||
color: #414245;
|
||||
font-weight: 700;
|
||||
font-size: 12px;
|
||||
flex-grow: 1;
|
||||
height: 20px;
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.bd-materialButton {
|
||||
cursor: pointer;
|
||||
.bd-materialButton {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
.bd-materialDesignIcon {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.bd-materialDesignIcon {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bd-materialDesignIcon {
|
||||
fill: #414245;
|
||||
.bd-materialDesignIcon {
|
||||
fill: #414245;
|
||||
|
||||
&:hover {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bd-sidebarView {
|
||||
&::after {
|
||||
content: '';
|
||||
height: 100%;
|
||||
width: 310px;
|
||||
background-color: #202225;
|
||||
top: 100%;
|
||||
display: block;
|
||||
position: absolute;
|
||||
}
|
||||
.bd-sidebarView {
|
||||
&::after {
|
||||
content: '';
|
||||
height: 100%;
|
||||
width: 310px;
|
||||
background-color: #202225;
|
||||
top: 100%;
|
||||
display: block;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.bd-sidebarRegion {
|
||||
.bd-scroller {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
.bd-sidebarRegion {
|
||||
.bd-scroller {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.bd-contentRegion {
|
||||
width: 590px;
|
||||
}
|
||||
.bd-contentRegion {
|
||||
width: 590px;
|
||||
}
|
||||
|
||||
&.bd-active {
|
||||
.bd-contentRegion {
|
||||
transition: all .3s cubic-bezier(.4,0,0,1);
|
||||
transform: none;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
&.bd-active {
|
||||
.bd-contentRegion {
|
||||
transition: all .3s cubic-bezier(.4, 0, 0, 1);
|
||||
transform: none;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.bd-stop {
|
||||
.bd-sidebarRegion {
|
||||
z-index: 1003;
|
||||
}
|
||||
&.bd-stop {
|
||||
.bd-sidebarRegion {
|
||||
z-index: 1003;
|
||||
}
|
||||
|
||||
.bd-contentRegion {
|
||||
z-index: 1002;
|
||||
}
|
||||
}
|
||||
}
|
||||
.bd-contentRegion {
|
||||
z-index: 1002;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.platform-darwin & { // sass-lint:disable-line class-name-format
|
||||
top: 0;
|
||||
.platform-darwin & { // sass-lint:disable-line class-name-format
|
||||
top: 0;
|
||||
|
||||
.bd-sidebarView {
|
||||
.bd-sidebarRegion {
|
||||
padding-top: 22px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.bd-sidebarView {
|
||||
.bd-sidebarRegion {
|
||||
padding-top: 22px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.platform-linux & { // sass-lint:disable-line class-name-format
|
||||
top: 0;
|
||||
}
|
||||
.platform-linux & { // sass-lint:disable-line class-name-format
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&:not(.bd-active) > .bd-sidebarView.bd-active, // sass-lint:disable-line force-element-nesting
|
||||
&.bd-settingsOut .bd-sidebarView.bd-active { // sass-lint:disable-line force-element-nesting
|
||||
.bd-contentRegion {
|
||||
transform: translate(-600px, 0%);
|
||||
opacity: 0;
|
||||
width: 590px;
|
||||
}
|
||||
}
|
||||
&:not(.bd-active) > .bd-sidebarView.bd-active, // sass-lint:disable-line force-element-nesting
|
||||
&.bd-settingsOut .bd-sidebarView.bd-active { // sass-lint:disable-line force-element-nesting
|
||||
.bd-contentRegion {
|
||||
transform: translate(-600px, 0%);
|
||||
opacity: 0;
|
||||
width: 590px;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.bd-active) {
|
||||
.bd-sidebarView {
|
||||
&.bd-active {
|
||||
.bd-contentRegion {
|
||||
transform: translate(-600px, 100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&:not(.bd-active) {
|
||||
.bd-sidebarView {
|
||||
&.bd-active {
|
||||
.bd-contentRegion {
|
||||
transform: translate(-600px, 100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bd-sidebar {
|
||||
.bd-settingsButton {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
.bd-settingsButton {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
||||
.platform-darwin & { // sass-lint:disable-line class-name-format
|
||||
top: 22px;
|
||||
}
|
||||
}
|
||||
.platform-darwin & { // sass-lint:disable-line class-name-format
|
||||
top: 22px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -28,21 +28,23 @@
|
|||
|
||||
.bd-chevron1 {
|
||||
position: absolute;
|
||||
svg{
|
||||
|
||||
svg {
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.bd-chevron2{
|
||||
.bd-chevron2 {
|
||||
position: absolute;
|
||||
svg{
|
||||
|
||||
svg {
|
||||
visibility: hidden;
|
||||
transform: scale(1.4,.5) translateY(6px) rotate(180deg);
|
||||
transform: scale(1.4, .5) translateY(6px) rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
transition: transform .2s cubic-bezier(.2,0,0,1);
|
||||
transition: transform .2s cubic-bezier(.2, 0, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,7 +81,7 @@
|
|||
.bd-drawerOpenButton {
|
||||
.bd-chevron1 {
|
||||
svg {
|
||||
transform: scale(1.4,.5) translateY(-6px);
|
||||
transform: scale(1.4, .5) translateY(-6px);
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
bottom: 3px;
|
||||
background: #f6f6f7;
|
||||
border-radius: 10px;
|
||||
transition: all .15s cubic-bezier(.2,0,0,1);
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,50 +1,51 @@
|
|||
.bd-sidebar {
|
||||
width: 100%;
|
||||
padding-right: 20px;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
padding-right: 20px;
|
||||
padding: 0;
|
||||
|
||||
.bd-header {
|
||||
padding: 6px 0;
|
||||
margin-left: 10px;
|
||||
margin-top: 15px;
|
||||
color: rgba(255, 255, 255, .15);
|
||||
font-size: 14px;
|
||||
line-height: 16px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-family: Whitney, Helvetica Neue, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
.bd-header {
|
||||
padding: 6px 0;
|
||||
margin-left: 10px;
|
||||
margin-top: 15px;
|
||||
color: rgba(255, 255, 255, .15);
|
||||
font-size: 14px;
|
||||
line-height: 16px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-family: Whitney, Helvetica Neue, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.bd-item {
|
||||
border-radius: 3px;
|
||||
margin-bottom: 2px;
|
||||
padding-bottom: 6px;
|
||||
padding-top: 6px;
|
||||
padding: 6px 10px;
|
||||
color: $coldimwhite;
|
||||
cursor: pointer;
|
||||
font-size: 17px;
|
||||
line-height: 20px;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-family: Whitney, Helvetica Neue, Helvetica, Arial, sans-serif;
|
||||
.bd-item {
|
||||
border-radius: 3px;
|
||||
margin-bottom: 2px;
|
||||
padding-bottom: 6px;
|
||||
padding-top: 6px;
|
||||
padding: 6px 10px;
|
||||
color: $coldimwhite;
|
||||
cursor: pointer;
|
||||
font-size: 17px;
|
||||
line-height: 20px;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-family: Whitney, Helvetica Neue, Helvetica, Arial, sans-serif;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(185,185,185,.1);
|
||||
color: #f6f6f6;
|
||||
}
|
||||
&.bd-active {
|
||||
background: $colbdgreen;
|
||||
color: #fff;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
&: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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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';
|
||||
|
|
|
@ -33,16 +33,16 @@
|
|||
</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();">Upload</div>
|
||||
<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();">Upload</div>
|
||||
<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();">Upload</div>
|
||||
<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>
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -191,12 +191,12 @@ export default class Modals {
|
|||
return new Modal(modal, InputModal);
|
||||
}
|
||||
|
||||
static installModal(contentType, config, filePath, icon) {
|
||||
return this.add(this.createInstallModal(contentType, config, filePath, icon));
|
||||
static installModal(contentType, config, filePath, icon, canUpload = false) {
|
||||
return this.add(this.createInstallModal(contentType, config, filePath, icon, canUpload));
|
||||
}
|
||||
|
||||
static createInstallModal(contentType, config, filePath, icon) {
|
||||
const modal = { contentType, config, filePath, icon };
|
||||
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();
|
||||
|
|
|
@ -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,68 +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")',
|
||||
asar: 'require("asar")',
|
||||
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=
|
353
core/src/main.js
353
core/src/main.js
|
@ -8,100 +8,57 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/*PRODUCTION*/
|
||||
const TESTS = typeof PRODUCTION === 'undefined';
|
||||
const TEST_ARGS = () => {
|
||||
const _basePath = path.resolve(__dirname, '..', '..');
|
||||
const _baseDataPath = path.resolve(_basePath, 'tests');
|
||||
|
||||
const _corePkg = require(path.resolve(_basePath, 'core', 'package.json'));
|
||||
const _clientPkg = require(path.resolve(_basePath, 'client', 'package.json'));
|
||||
const _editorPkg = require(path.resolve(_basePath, 'editor', 'package.json'));
|
||||
|
||||
const coreVersion = _corePkg.version;
|
||||
const clientVersion = _clientPkg.version;
|
||||
const editorVersion = _editorPkg.version;
|
||||
|
||||
return {
|
||||
coreVersion,
|
||||
clientVersion,
|
||||
editorVersion,
|
||||
'options': {
|
||||
'autoInject': true,
|
||||
'commonCore': true,
|
||||
'commonData': true
|
||||
},
|
||||
'paths': {
|
||||
'client': path.resolve(_basePath, 'client', 'dist'),
|
||||
'core': path.resolve(_basePath, 'core', 'dist'),
|
||||
'data': path.resolve(_baseDataPath, 'data'),
|
||||
'editor': path.resolve(_basePath, 'editor', 'dist'),
|
||||
// tmp: path.join(_basePath, 'tmp')
|
||||
tmp: path.join(os.tmpdir(), 'betterdiscord', `${process.getuid()}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
const TEST_EDITOR = TESTS && true;
|
||||
|
||||
import process from 'process';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import sass from 'node-sass';
|
||||
import { BrowserWindow as OriginalBrowserWindow, dialog, session } from 'electron';
|
||||
import { BrowserWindow as OriginalBrowserWindow, dialog, session, shell } from 'electron';
|
||||
import deepmerge from 'deepmerge';
|
||||
import ContentSecurityPolicy from 'csp-parse';
|
||||
import keytar from 'keytar';
|
||||
|
||||
import { FileUtils, BDIpc, Config, WindowUtils, CSSEditor, Database } from './modules';
|
||||
|
||||
const tests = typeof PRODUCTION === 'undefined';
|
||||
|
||||
const _basePath = tests ? path.resolve(__dirname, '..', '..') : __dirname;
|
||||
const _baseDataPath = tests ? path.resolve(_basePath, 'tests') : _basePath;
|
||||
import { FileUtils, BDIpc, Config, WindowUtils, Updater, Editor, Database } from './modules';
|
||||
|
||||
const sparkplug = path.resolve(__dirname, 'sparkplug.js');
|
||||
|
||||
const _clientScript = tests
|
||||
? path.resolve(_basePath, 'client', 'dist', 'betterdiscord.client.js')
|
||||
: path.resolve(_basePath, 'betterdiscord.client.js');
|
||||
const _cssEditorPath = tests
|
||||
? path.resolve(__dirname, '..', '..', 'csseditor', 'dist')
|
||||
: path.resolve(__dirname, 'csseditor');
|
||||
let configProxy;
|
||||
|
||||
const _dataPath = path.resolve(_baseDataPath, 'data');
|
||||
const _extPath = path.resolve(_baseDataPath, 'ext');
|
||||
const _pluginPath = path.resolve(_extPath, 'plugins');
|
||||
const _themePath = path.resolve(_extPath, 'themes');
|
||||
const _modulePath = path.resolve(_extPath, 'modules');
|
||||
|
||||
const version = require(path.resolve(_basePath, 'package.json')).version;
|
||||
|
||||
const paths = [
|
||||
{ id: 'base', path: _basePath },
|
||||
{ id: 'cs', path: _clientScript },
|
||||
{ id: 'data', path: _dataPath },
|
||||
{ id: 'ext', path: _extPath },
|
||||
{ id: 'plugins', path: _pluginPath },
|
||||
{ id: 'themes', path: _themePath },
|
||||
{ id: 'modules', path: _modulePath },
|
||||
{ id: 'csseditor', path: _cssEditorPath }
|
||||
];
|
||||
|
||||
const globals = {
|
||||
version,
|
||||
paths
|
||||
};
|
||||
|
||||
const CSP = {
|
||||
'img-src': ['https://cdn.betterttv.net', 'https://cdn.frankerfacez.com'],
|
||||
'script-src': [
|
||||
`'sha256-fSHKdpQGCHaIqWP3SpJOuUHrLp49jy4dWHzZ/RBJ/p4='`, // React Devtools
|
||||
`'sha256-VFJcfKY5B3EBkFDgQnv3CozPwBlZcxwssfLVWlPFfZU='`, // Vue Devtools
|
||||
`'sha256-VzDmLZ4PxPkOS/KY7ITzLQsSWhfCnvUrNculcj8UNgE=' 'sha256-l6K+77Z1cmldR9gIvaVWlboF/zr5MXCQHcsEHfnr5TU='` // Vue Detector
|
||||
]
|
||||
};
|
||||
|
||||
class BrowserWindow extends OriginalBrowserWindow {
|
||||
constructor(originalOptions) {
|
||||
const userOptions = BrowserWindow.userWindowPreferences;
|
||||
|
||||
const options = deepmerge(originalOptions, userOptions);
|
||||
options.webPreferences = Object.assign({}, options.webPreferences);
|
||||
|
||||
// Make sure Node integration is enabled
|
||||
options.webPreferences.preload = sparkplug;
|
||||
|
||||
super(options);
|
||||
|
||||
Object.defineProperty(this, '__bd_preload', {value: []});
|
||||
|
||||
if (originalOptions.webPreferences && originalOptions.webPreferences.preload) {
|
||||
this.__bd_preload.push(originalOptions.webPreferences.preload);
|
||||
}
|
||||
if (userOptions.webPreferences && userOptions.webPreferences.preload) {
|
||||
this.__bd_preload.push(path.resolve(_dataPath, userOptions.webPreferences.preload));
|
||||
}
|
||||
|
||||
Object.defineProperty(this, '__bd_options', {value: options});
|
||||
Object.freeze(options);
|
||||
Object.freeze(options.webPreferences);
|
||||
Object.freeze(this.__bd_preload);
|
||||
}
|
||||
|
||||
static get userWindowPreferences() {
|
||||
try {
|
||||
const userWindowPreferences = require(path.join(_dataPath, 'window'));
|
||||
if (typeof userWindowPreferences === 'object') return userWindowPreferences;
|
||||
} catch (err) {
|
||||
console.log('[BetterDiscord] Error getting window preferences:', err);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
const CSP = TESTS ? require('../src/csp.json') : require('./csp.json');
|
||||
|
||||
class Comms {
|
||||
constructor(bd) {
|
||||
|
@ -116,8 +73,9 @@ class Comms {
|
|||
|
||||
BDIpc.on('bd-sendToDiscord', (event, m) => this.sendToDiscord(m.channel, m.message), true);
|
||||
|
||||
BDIpc.on('bd-openCssEditor', (event, options) => this.bd.csseditor.openEditor(options), true);
|
||||
BDIpc.on('bd-sendToCssEditor', (event, m) => this.sendToCssEditor(m.channel, m.message), true);
|
||||
// BDIpc.on('bd-openCssEditor', (event, options) => this.bd.csseditor.openEditor(options), true);
|
||||
// BDIpc.on('bd-sendToCssEditor', (event, m) => this.sendToCssEditor(m.channel, m.message), true);
|
||||
// BDIpc.on('bd-openCssEditor', (event, options) => this.bd.editor.openEditor(options), true);
|
||||
|
||||
BDIpc.on('bd-native-open', (event, options) => {
|
||||
dialog.showOpenDialog(OriginalBrowserWindow.fromWebContents(event.ipcEvent.sender), options, filenames => {
|
||||
|
@ -137,12 +95,54 @@ class Comms {
|
|||
});
|
||||
});
|
||||
|
||||
BDIpc.on('bd-dba', (event, options) => this.bd.dbInstance.exec(options), true);
|
||||
BDIpc.on('bd-dba', (event, options) => this.bd.database.exec(options), true);
|
||||
|
||||
BDIpc.on('bd-keytar-get', (event, {service, account}) => keytar.getPassword(service, account), true);
|
||||
BDIpc.on('bd-keytar-set', (event, {service, account, password}) => keytar.setPassword(service, account, password), true);
|
||||
BDIpc.on('bd-keytar-delete', (event, {service, account}) => keytar.deletePassword(service, account), true);
|
||||
BDIpc.on('bd-keytar-find-credentials', (event, {service}) => keytar.findCredentials(service), true);
|
||||
BDIpc.on('bd-keytar-get', (event, { service, account }) => keytar.getPassword(service, account), true);
|
||||
BDIpc.on('bd-keytar-set', (event, { service, account, password }) => keytar.setPassword(service, account, password), true);
|
||||
BDIpc.on('bd-keytar-delete', (event, { service, account }) => keytar.deletePassword(service, account), true);
|
||||
BDIpc.on('bd-keytar-find-credentials', (event, { service }) => keytar.findCredentials(service), true);
|
||||
|
||||
BDIpc.on('bd-readDataFile', async (event, fileName) => {
|
||||
const rf = await FileUtils.readFile(path.resolve(configProxy().getPath('data'), fileName));
|
||||
event.reply(rf);
|
||||
});
|
||||
|
||||
BDIpc.on('bd-explorer', (_, _path) => {
|
||||
if (_path.static) _path = this.bd.config.getPath(_path.static);
|
||||
else if (_path.full) _path = _path.full;
|
||||
else if (_path.sub) _path = path.resolve(this.bd.config.getPath(_path.sub.base), [..._path.sub.subs]);
|
||||
try {
|
||||
shell.openItem(_path);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
});
|
||||
|
||||
BDIpc.on('bd-getPath', (event, paths) => {
|
||||
event.reply(path.resolve(this.bd.config.getPath(paths[0]), ...paths.splice(1)));
|
||||
});
|
||||
|
||||
BDIpc.on('bd-rmFile', async (event, paths) => {
|
||||
const fullPath = path.resolve(this.bd.config.getPath(paths[0]), ...paths.splice(1));
|
||||
try {
|
||||
await FileUtils.rm(fullPath);
|
||||
event.reply('ok');
|
||||
} catch (err) {
|
||||
event.reject(err);
|
||||
}
|
||||
});
|
||||
|
||||
BDIpc.on('bd-rnFile', async (event, paths) => {
|
||||
const oldPath = path.resolve(this.bd.config.getPath(paths.oldName[0]), ...paths.oldName.splice(1));
|
||||
const newPath = path.resolve(this.bd.config.getPath(paths.newName[0]), ...paths.newName.splice(1));
|
||||
|
||||
try {
|
||||
await FileUtils.rn(oldPath, newPath);
|
||||
event.reply('ok');
|
||||
} catch (err) {
|
||||
event.reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async send(channel, message) {
|
||||
|
@ -158,38 +158,90 @@ class Comms {
|
|||
}
|
||||
}
|
||||
|
||||
class BrowserWindow extends OriginalBrowserWindow {
|
||||
constructor(originalOptions) {
|
||||
const userOptions = BrowserWindow.userWindowPreferences;
|
||||
|
||||
const options = deepmerge(originalOptions, userOptions);
|
||||
options.webPreferences = Object.assign({}, options.webPreferences);
|
||||
|
||||
// Make sure Node integration is enabled
|
||||
options.webPreferences.preload = sparkplug;
|
||||
|
||||
super(options);
|
||||
|
||||
Object.defineProperty(this, '__bd_preload', { value: [] });
|
||||
|
||||
if (originalOptions.webPreferences && originalOptions.webPreferences.preload) {
|
||||
this.__bd_preload.push(originalOptions.webPreferences.preload);
|
||||
}
|
||||
if (userOptions.webPreferences && userOptions.webPreferences.preload) {
|
||||
this.__bd_preload.push(path.resolve(configProxy().getPath('data'), userOptions.webPreferences.preload));
|
||||
}
|
||||
|
||||
Object.defineProperty(this, '__bd_options', { value: options });
|
||||
Object.freeze(options);
|
||||
Object.freeze(options.webPreferences);
|
||||
Object.freeze(this.__bd_preload);
|
||||
}
|
||||
|
||||
static get userWindowPreferences() {
|
||||
try {
|
||||
const userWindowPreferences = require(path.join(configProxy().getPath('data'), 'window'));
|
||||
if (typeof userWindowPreferences === 'object') return userWindowPreferences;
|
||||
} catch (err) {
|
||||
console.log('[BetterDiscord] Error getting window preferences:', err);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
export class BetterDiscord {
|
||||
|
||||
get comms() { return this._comms ? this._comms : (this._commas = new Comms(this)); }
|
||||
get database() { return this._db ? this._db : (this._db = new Database(this.config.getPath('data'))); }
|
||||
get config() { return this._config ? this._config : (this._config = new Config(this._args)); }
|
||||
get window() { return this.windowUtils ? this.windowUtils.window : undefined; }
|
||||
get editor() { return this._editor ? this._editor : (this._editor = new Editor(this, this.config.getPath('editor'))); }
|
||||
get updater() { return this._updater ? this._updater : (this._updater = new Updater(this)); }
|
||||
get sendToDiscord() { return this.windowUtils.send; }
|
||||
|
||||
constructor(args) {
|
||||
if (TESTS) args = TEST_ARGS();
|
||||
console.log('[BetterDiscord|args] ', JSON.stringify(args, null, 4));
|
||||
if (BetterDiscord.loaded) {
|
||||
console.log('Creating two BetterDiscord objects???');
|
||||
console.log('[BetterDiscord] Creating two BetterDiscord objects???');
|
||||
return null;
|
||||
}
|
||||
|
||||
BetterDiscord.loaded = true;
|
||||
this._args = args;
|
||||
this.config.compatibility();
|
||||
|
||||
this.injectScripts = this.injectScripts.bind(this);
|
||||
this.ignite = this.ignite.bind(this);
|
||||
|
||||
this.config = new Config(args || globals);
|
||||
this.dbInstance = new Database(this.config.getPath('data'));
|
||||
this.comms = new Comms(this);
|
||||
this.bindings();
|
||||
this.extraPaths();
|
||||
this.parseClientPackage();
|
||||
this.parseEditorPackage();
|
||||
this.parseCorePackage();
|
||||
|
||||
configProxy = () => this.config;
|
||||
const autoInitComms = this.comms;
|
||||
const autoInitEditor = this.editor;
|
||||
this.updater.start();
|
||||
this.init();
|
||||
}
|
||||
|
||||
bindings() {
|
||||
this.injectScripts = this.injectScripts.bind(this);
|
||||
this.ignite = this.ignite.bind(this);
|
||||
this.ensureDirectories = this.ensureDirectories.bind(this);
|
||||
}
|
||||
|
||||
async init() {
|
||||
console.log('[BetterDiscord] init');
|
||||
await this.waitForWindowUtils();
|
||||
|
||||
if (!tests) {
|
||||
const basePath = this.config.getPath('base');
|
||||
const files = await FileUtils.listDirectory(basePath);
|
||||
const latestCs = FileUtils.resolveLatest(files, file => file.endsWith('.js') && file.startsWith('client.'), file => file.replace('client.', '').replace('.js', ''), 'client.', '.js');
|
||||
this.config.getPath('cs', true).path = path.resolve(basePath, latestCs);
|
||||
}
|
||||
|
||||
await FileUtils.ensureDirectory(this.config.getPath('ext'));
|
||||
|
||||
this.csseditor = new CSSEditor(this, this.config.getPath('csseditor'));
|
||||
await this.ensureDirectories();
|
||||
|
||||
this.windowUtils.on('did-finish-load', () => this.injectScripts(true));
|
||||
|
||||
|
@ -199,9 +251,30 @@ export class BetterDiscord {
|
|||
|
||||
setTimeout(() => {
|
||||
this.injectScripts();
|
||||
if (TEST_EDITOR) this.editor.openEditor({});
|
||||
}, 500);
|
||||
}
|
||||
|
||||
async ensureDirectories() {
|
||||
await FileUtils.ensureDirectory(this.config.getPath('ext'));
|
||||
await FileUtils.ensureDirectory(this.config.getPath('userdata'));
|
||||
await Promise.all([
|
||||
FileUtils.ensureDirectory(this.config.getPath('plugins')),
|
||||
FileUtils.ensureDirectory(this.config.getPath('themes')),
|
||||
FileUtils.ensureDirectory(this.config.getPath('modules')),
|
||||
FileUtils.ensureDirectory(this.config.getPath('userfiles'))
|
||||
]);
|
||||
}
|
||||
|
||||
async waitForWindowUtils() {
|
||||
if (this.windowUtils) return this.windowUtils;
|
||||
const window = await this.waitForWindow();
|
||||
return this.windowUtils = new WindowUtils({ window });
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for Discord to load before doing any injection
|
||||
*/
|
||||
async waitForWindow() {
|
||||
return new Promise(resolve => {
|
||||
const defer = setInterval(() => {
|
||||
|
@ -215,14 +288,55 @@ export class BetterDiscord {
|
|||
});
|
||||
}
|
||||
|
||||
async waitForWindowUtils() {
|
||||
if (this.windowUtils) return this.windowUtils;
|
||||
const window = await this.waitForWindow();
|
||||
return this.windowUtils = new WindowUtils({ window });
|
||||
/**
|
||||
* Parses the package.json of client script into config
|
||||
*/
|
||||
parseClientPackage() {
|
||||
const clientPath = this.config.getPath('client');
|
||||
const clientPkg = TESTS ? require(`${path.resolve(clientPath, '..')}/package.json`) : require(`${clientPath}/package.json`);
|
||||
const { version } = clientPkg;
|
||||
const main = TESTS ? 'betterdiscord.client.js' : clientPkg.main;
|
||||
this.config.addPath('client_script', `${clientPath}/${main}`);
|
||||
this.config.setClientVersion(version);
|
||||
console.log(`[BetterDiscord] Client v${this.config.clientVersion} - ${this.config.getPath('client_script')}`);
|
||||
}
|
||||
|
||||
get window() {
|
||||
return this.windowUtils ? this.windowUtils.window : undefined;
|
||||
parseCorePackage() {
|
||||
const corePath = this.config.getPath('core');
|
||||
const corePkg = TESTS ? require(`${path.resolve(corePath, '..')}/package.json`) : require(`${corePath}/package.json`);
|
||||
const { version } = corePkg;
|
||||
this.config.setCoreVersion(version);
|
||||
}
|
||||
|
||||
parseEditorPackage() {
|
||||
const editorPath = this.config.getPath('editor');
|
||||
const editorPkg = TESTS ? require(`${path.resolve(editorPath, '..')}/package.json`) : require(`${editorPath}/package.json`);
|
||||
const { version } = editorPkg;
|
||||
this.config.setEditorVersion(version);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add extra paths to config
|
||||
*/
|
||||
extraPaths() {
|
||||
const base = path.resolve(this.config.getPath('data'), '..');
|
||||
const userdata = path.resolve(base, 'userdata');
|
||||
const ext = path.resolve(base, 'ext');
|
||||
const plugins = path.resolve(ext, 'plugins');
|
||||
const themes = path.resolve(ext, 'themes');
|
||||
const modules = path.resolve(ext, 'modules');
|
||||
const userfiles = path.resolve(userdata, 'files');
|
||||
const snippets = path.resolve(userdata, 'snippets.json');
|
||||
|
||||
this.config.addPath('base', base);
|
||||
this.config.addPath('ext', ext);
|
||||
this.config.addPath('plugins', plugins);
|
||||
this.config.addPath('themes', themes);
|
||||
this.config.addPath('modules', modules);
|
||||
this.config.addPath('userdata', userdata);
|
||||
this.config.addPath('userfiles', userfiles);
|
||||
this.config.addPath('snippets', snippets);
|
||||
if (!this.config.getPath('editor')) this.config.addPath('editor', path.resolve(base, 'editor'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -245,8 +359,8 @@ export class BetterDiscord {
|
|||
* @param {Boolean} reload Whether the main window was reloaded
|
||||
*/
|
||||
async injectScripts(reload = false) {
|
||||
console.log(`RELOAD? ${reload}`);
|
||||
return this.windowUtils.injectScript(this.config.getPath('cs'));
|
||||
console.log(`[BetterDiscord] injecting ${this.config.getPath('client_script')}. Reload: ${reload}`);
|
||||
return this.windowUtils.injectScript(this.config.getPath('client_script'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -255,10 +369,11 @@ export class BetterDiscord {
|
|||
* Basically BetterDiscord needs to load before discord_desktop_core.
|
||||
*/
|
||||
static patchBrowserWindow() {
|
||||
console.log('[BetterDiscord] patching BrowserWindow');
|
||||
const electron = require('electron');
|
||||
const electron_path = require.resolve('electron');
|
||||
Object.assign(BrowserWindow, electron.BrowserWindow); // Assigns the new chrome-specific ones
|
||||
const newElectron = Object.assign({}, electron, {BrowserWindow});
|
||||
const newElectron = Object.assign({}, electron, { BrowserWindow });
|
||||
require.cache[electron_path].exports = newElectron;
|
||||
}
|
||||
|
||||
|
@ -266,6 +381,7 @@ export class BetterDiscord {
|
|||
* Attaches an event handler for HTTP requests to update the Content Security Policy.
|
||||
*/
|
||||
static hookSessionRequest() {
|
||||
console.log('[BetterDiscord] hook session request');
|
||||
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
|
||||
for (const [header, values] of Object.entries(details.responseHeaders)) {
|
||||
if (!header.match(/^Content-Security-Policy(-Report-Only)?$/i)) continue;
|
||||
|
@ -283,7 +399,6 @@ export class BetterDiscord {
|
|||
callback({ responseHeaders: details.responseHeaders });
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
BetterDiscord.patchBrowserWindow();
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* 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(url) { return axios.get(url) }
|
||||
|
||||
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'
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -16,20 +16,62 @@ export default class Config extends Module {
|
|||
return this.args.version;
|
||||
}
|
||||
|
||||
get versions() {
|
||||
return {
|
||||
core: this.coreVersion,
|
||||
client: this.clientVersion,
|
||||
editor: this.editorVersion
|
||||
};
|
||||
}
|
||||
|
||||
get coreVersion() {
|
||||
return this.state.coreVersion;
|
||||
}
|
||||
|
||||
get clientVersion() {
|
||||
return this.state.clientVersion;
|
||||
}
|
||||
|
||||
get editorVersion() {
|
||||
return this.state.editorVersion;
|
||||
}
|
||||
|
||||
setClientVersion(clientVersion) {
|
||||
this.state.clientVersion = clientVersion;
|
||||
}
|
||||
|
||||
setCoreVersion(coreVersion) {
|
||||
this.state.coreVersion = coreVersion;
|
||||
}
|
||||
|
||||
setEditorVersion(editorVersion) {
|
||||
this.state.editorVersion = editorVersion;
|
||||
}
|
||||
|
||||
get paths() {
|
||||
return this.args.paths;
|
||||
}
|
||||
|
||||
getPath(id, full) {
|
||||
const path = this.paths.find(path => path.id === id);
|
||||
const path = this.paths.find(p => p.id === id);
|
||||
if (!path) return null;
|
||||
return full ? path : path.path;
|
||||
}
|
||||
|
||||
addPath(id, path) {
|
||||
this.paths.push({ id, path });
|
||||
}
|
||||
|
||||
get config() {
|
||||
return {
|
||||
version: this.version,
|
||||
versions: this.versions,
|
||||
paths: this.paths
|
||||
};
|
||||
}
|
||||
|
||||
// Compatibility with old client code and new installer args
|
||||
compatibility() {
|
||||
this.args.paths = Object.entries(this.args.paths).map(([id, path]) => ({ id, path }));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,285 @@
|
|||
/**
|
||||
* BetterDiscord Editor Module
|
||||
* Copyright (c) 2015-present JsSucks - https://github.com/JsSucks
|
||||
* All rights reserved.
|
||||
* https://github.com/JsSucks - https://betterdiscord.net
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import { BrowserWindow } from 'electron';
|
||||
|
||||
import Module from './modulebase';
|
||||
import { WindowUtils, FileUtils } from './utils';
|
||||
import BDIpc from './bdipc';
|
||||
import sass from 'node-sass';
|
||||
import chokidar from 'chokidar';
|
||||
|
||||
export default class Editor extends Module {
|
||||
|
||||
constructor(bd, path) {
|
||||
super();
|
||||
this.editorPath = path;
|
||||
this.bd = bd;
|
||||
this.initListeners();
|
||||
this.initWatchers();
|
||||
}
|
||||
|
||||
initListeners() {
|
||||
BDIpc.on('openCssEditor', (event, options) => this.openEditor(options), true);
|
||||
BDIpc.on('editor-open', (event, options) => this.openEditor(options), true);
|
||||
|
||||
BDIpc.on('editor-runScript', async (event, script) => {
|
||||
const result = await this.sendToDiscord('editor-runScript', script);
|
||||
event.reply(result);
|
||||
});
|
||||
|
||||
BDIpc.on('editor-getFiles', async (event) => {
|
||||
try {
|
||||
const files = await FileUtils.listDirectory(this.bd.config.getPath('userfiles'));
|
||||
|
||||
const constructFiles = await Promise.all(files.map(async file => {
|
||||
// const content = await FileUtils.readFile(path.resolve(this.bd.config.getPath('userfiles'), file));
|
||||
return { type: 'file', name: file, saved: true, mode: this.resolveMode(file), content: '', savedContent: '', read: false, changed: false };
|
||||
}));
|
||||
|
||||
const userscssPath = path.resolve(this.bd.config.getPath('data'), 'user.scss');
|
||||
|
||||
await FileUtils.ensureFile(userscssPath);
|
||||
|
||||
const userscss = await FileUtils.readFile(userscssPath);
|
||||
constructFiles.push({
|
||||
caption: 'userstyle',
|
||||
type: 'file',
|
||||
name: 'user.scss',
|
||||
saved: true,
|
||||
mode: 'scss',
|
||||
content: userscss,
|
||||
savedContent: userscss,
|
||||
hoisted: true,
|
||||
liveUpdate: true,
|
||||
read: true,
|
||||
changed: false
|
||||
});
|
||||
|
||||
event.reply(constructFiles);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
event.reject({ err });
|
||||
}
|
||||
});
|
||||
|
||||
BDIpc.on('editor-getSnippets', async (event) => {
|
||||
try {
|
||||
const snippets = await FileUtils.readJsonFromFile(this.bd.config.getPath('snippets'));
|
||||
event.reply(snippets.map(snippet => {
|
||||
return {
|
||||
type: 'snippet',
|
||||
name: snippet.name,
|
||||
mode: this.resolveMode(snippet.name),
|
||||
content: snippet.content,
|
||||
savedContent: snippet.content,
|
||||
read: true,
|
||||
saved: true
|
||||
}
|
||||
}));
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
event.reply([]);
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
BDIpc.on('editor-saveFile', async (event, file) => {
|
||||
const filePath = (file.hoisted && file.name === 'user.scss') ?
|
||||
path.resolve(this.bd.config.getPath('data'), 'user.scss') :
|
||||
path.resolve(this.bd.config.getPath('userfiles'), file.name);
|
||||
|
||||
try {
|
||||
await FileUtils.writeFile(filePath, file.content);
|
||||
event.reply('ok');
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
event.reject({ err });
|
||||
}
|
||||
});
|
||||
|
||||
BDIpc.on('editor-saveSnippet', async (event, snippet) => {
|
||||
try {
|
||||
await FileUtils.writeFile(this.bd.config.getPath('snippets'), JSON.stringify(snippet));
|
||||
event.reply('ok');
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
event.reject({ err });
|
||||
}
|
||||
});
|
||||
|
||||
BDIpc.on('editor-injectStyle', async (event, { id, style, mode }) => {
|
||||
if (mode !== 'scss') {
|
||||
await this.sendToDiscord('editor-injectStyle', { id, style });
|
||||
event.reply('ok');
|
||||
return;
|
||||
}
|
||||
|
||||
style = await Promise.all(style.split('\n').map(async(line) => {
|
||||
if (!line.startsWith('@import')) return line;
|
||||
const filename = line.split(' ')[1].replace(/'|"|;/g, '');
|
||||
const filePath = path.resolve(this.bd.config.getPath('userfiles'), filename).replace(/\\/g, '/');
|
||||
|
||||
try {
|
||||
await FileUtils.fileExists(filePath);
|
||||
} catch (err) {
|
||||
`/*${filename}*/`;
|
||||
}
|
||||
|
||||
return `@import '${filePath}';`;
|
||||
}));
|
||||
|
||||
style = style.join('\n');
|
||||
|
||||
sass.render({ data: style }, (err, result) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
event.reply({ err });
|
||||
return;
|
||||
}
|
||||
const { css } = result;
|
||||
(async () => {
|
||||
await this.sendToDiscord('editor-injectStyle', { id, style: css.toString() });
|
||||
event.reply('ok');
|
||||
})();
|
||||
});
|
||||
});
|
||||
|
||||
BDIpc.on('editor-readFile', async (event, file) => {
|
||||
const content = await FileUtils.readFile(path.resolve(this.bd.config.getPath('userfiles'), file.name));
|
||||
event.reply(content);
|
||||
});
|
||||
}
|
||||
|
||||
initWatchers() {
|
||||
this.fileWatcher = chokidar.watch(this.bd.config.getPath('userfiles'));
|
||||
|
||||
this.fileWatcher.on('add', file => {
|
||||
const fileName = path.basename(file);
|
||||
try {
|
||||
this.send('editor-addFile', {
|
||||
type: 'file',
|
||||
name: fileName,
|
||||
saved: true,
|
||||
mode: this.resolveMode(fileName),
|
||||
content: '',
|
||||
savedContent: ''
|
||||
});
|
||||
} catch (err) {}
|
||||
});
|
||||
|
||||
this.fileWatcher.on('unlink', file => {
|
||||
const fileName = path.basename(file);
|
||||
try {
|
||||
this.send('editor-remFile', { name: fileName });
|
||||
} catch (err) {}
|
||||
});
|
||||
|
||||
this.fileWatcher.on('change', file => {
|
||||
this.send('editor-fileChange', { name: path.basename(file) });
|
||||
});
|
||||
}
|
||||
|
||||
resolveMode(fileName) {
|
||||
if (!fileName.includes('.')) return 'text';
|
||||
const ext = fileName.substr(fileName.lastIndexOf('.') + 1);
|
||||
if (this.modes.hasOwnProperty(ext)) return this.modes[ext];
|
||||
return 'text';
|
||||
}
|
||||
|
||||
get modes() {
|
||||
return {
|
||||
'css': 'css',
|
||||
'scss': 'scss',
|
||||
'js': 'javascript',
|
||||
'txt': 'text',
|
||||
'json': 'json'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens an editor.
|
||||
* @return {Promise}
|
||||
*/
|
||||
async openEditor(options) {
|
||||
if (!this.editorPkg) {
|
||||
this.editorPkg = await FileUtils.readJsonFromFile(path.join(this.editorPath, 'package.json'));
|
||||
}
|
||||
|
||||
console.log(this.editorPkg);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.editor) {
|
||||
if (this.editor.isFocused()) return;
|
||||
|
||||
this.editor.focus();
|
||||
this.editor.flashFrame(true);
|
||||
return resolve(true);
|
||||
}
|
||||
|
||||
options = Object.assign({}, this.options, options);
|
||||
|
||||
this.editor = new BrowserWindow(options);
|
||||
this.editor.loadURL('about:blank');
|
||||
this.editor.setSheetOffset(33);
|
||||
this.editorUtils = new WindowUtils({ window: this.editor });
|
||||
|
||||
this.editor.on('close', () => {
|
||||
this.bd.windowUtils.send('bd-save-csseditor-bounds', this.editor.getBounds());
|
||||
this.editor = null;
|
||||
});
|
||||
|
||||
this.editor.once('ready-to-show', () => {
|
||||
this.editor.show();
|
||||
});
|
||||
|
||||
this.editor.webContents.on('did-finish-load', () => {
|
||||
this.editorUtils.injectScript(path.join(this.editorPath, this.editorPkg.main));
|
||||
resolve(true);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends data to the editor.
|
||||
* @param {String} channel
|
||||
* @param {Any} data
|
||||
*/
|
||||
send(channel, data) {
|
||||
if (!this.editor) throw { message: 'The CSS editor is not open.' };
|
||||
return BDIpc.send(this.editor, channel, data);
|
||||
}
|
||||
|
||||
async sendToDiscord(channel, message) {
|
||||
return this.bd.windowUtils.send(channel, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the CSS editor's always on top flag.
|
||||
*/
|
||||
set alwaysOnTop(state) {
|
||||
if (!this.editor) return;
|
||||
this.editor.setAlwaysOnTop(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default options to pass to BrowserWindow.
|
||||
*/
|
||||
get options() {
|
||||
return {
|
||||
width: 800,
|
||||
height: 600,
|
||||
show: false,
|
||||
frame: false
|
||||
};
|
||||
}
|
||||
|
||||
}
|
|
@ -2,4 +2,6 @@ export { default as BDIpc } from './bdipc';
|
|||
export { Utils, FileUtils, WindowUtils } from './utils';
|
||||
export { default as Config } from './config';
|
||||
export { default as CSSEditor } from './csseditor';
|
||||
export { default as Editor } from './editor';
|
||||
export { default as Database } from './database';
|
||||
export { default as Updater } from './updater';
|
||||
|
|
|
@ -11,6 +11,9 @@
|
|||
/**
|
||||
* Base Module that every non-static module should extend.
|
||||
*/
|
||||
|
||||
import { default as BDIpc } from './bdipc';
|
||||
|
||||
export default class Module {
|
||||
|
||||
constructor(args) {
|
||||
|
@ -24,6 +27,7 @@ export default class Module {
|
|||
init() {
|
||||
if (this.bindings) this.bindings();
|
||||
if (this.setInitialState) this.setInitialState(this.state);
|
||||
if (this.events) this.events(BDIpc);
|
||||
}
|
||||
|
||||
set args(t) {}
|
||||
|
|
|
@ -0,0 +1,286 @@
|
|||
/**
|
||||
* BetterDiscord Updater Module
|
||||
* Copyright (c) 2015-present JsSucks - https://github.com/JsSucks
|
||||
* All rights reserved.
|
||||
* https://github.com/JsSucks - https://betterdiscord.net
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import Module from './modulebase';
|
||||
import { FileUtils } from './utils';
|
||||
import semver from 'semver';
|
||||
import Axi from './axi';
|
||||
import zlib from 'zlib';
|
||||
import tarfs from 'tar-fs';
|
||||
|
||||
const TEST_UPDATE = [
|
||||
{
|
||||
'id': 'core',
|
||||
'version': '2.0.0-beta.5'
|
||||
},
|
||||
{
|
||||
'id': 'client',
|
||||
'version': '2.0.0-beta.5'
|
||||
},
|
||||
{
|
||||
'id': 'editor',
|
||||
'version': '0.4.1'
|
||||
}
|
||||
];
|
||||
|
||||
class ReleaseInfo {
|
||||
|
||||
constructor(versions) {
|
||||
this.versions = versions;
|
||||
}
|
||||
|
||||
get core() {
|
||||
const f = this.files.find(f => f.id === 'core');
|
||||
f.upToDate = semver.satisfies(this.versions.core, `>=${f.version}`, { includePrerelease: true });
|
||||
f.currentVersion = this.versions.core;
|
||||
return f;
|
||||
}
|
||||
|
||||
get client() {
|
||||
const f = this.files.find(f => f.id === 'client');
|
||||
f.upToDate = semver.satisfies(this.versions.client, `>=${f.version}`, { includePrerelease: true });
|
||||
f.currentVersion = this.versions.client;
|
||||
return f;
|
||||
}
|
||||
|
||||
get editor() {
|
||||
const f = this.files.find(f => f.id === 'editor');
|
||||
f.upToDate = semver.satisfies(this.versions.editor, `>=${f.version}`, { includePrerelease: true });
|
||||
f.currentVersion = this.versions.editor;
|
||||
return f;
|
||||
}
|
||||
|
||||
test() {
|
||||
this.files = TEST_UPDATE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default class Updater extends Module {
|
||||
|
||||
constructor(bd) {
|
||||
super();
|
||||
this.bd = bd;
|
||||
}
|
||||
|
||||
bindings() {
|
||||
this.checkForUpdates = this.checkForUpdates.bind(this);
|
||||
this.checkForBdUpdates = this.checkForBdUpdates.bind(this);
|
||||
this.updateAll = this.updateAll.bind(this);
|
||||
this.updateFinished = this.updateFinished.bind(this);
|
||||
this.start = this.start.bind(this);
|
||||
}
|
||||
|
||||
events(ipc) {
|
||||
ipc.on('updater-startUpdate', (_, updates) => {
|
||||
clearInterval(this.updaterThread);
|
||||
this.updateAll(updates);
|
||||
});
|
||||
ipc.on('debug-updater-forceUpdate', () => {
|
||||
this.checkForUpdates(true);
|
||||
});
|
||||
}
|
||||
|
||||
async updateBd(update) {
|
||||
try {
|
||||
console.log('[BetterDiscord:Updater] Updating', update.id);
|
||||
await this.downloadTarGz(`https://github.com/JsSucks/BetterDiscordApp${update.remote}`, this.bd.config.getPath('base'));
|
||||
this.updateFinished(update);
|
||||
// Cleanup
|
||||
await FileUtils.rm(`${this.bd.config.getPath(update.id)}_old`);
|
||||
} catch (err) {
|
||||
console.log('[BetterDiscord:Updater] Failed to update', update.id);
|
||||
console.log(err);
|
||||
update.error = err;
|
||||
this.bd.sendToDiscord('updater-updateError', update);
|
||||
}
|
||||
}
|
||||
|
||||
async updateAll(updates) {
|
||||
const bd = updates.bd || [];
|
||||
const plugins = updates.plugins || [];
|
||||
const themes = updates.themes || [];
|
||||
const modules = updates.modules || [];
|
||||
|
||||
this.restartRequired = this.reloadRequired = false;
|
||||
this.finishedUpdates = 0;
|
||||
this.totalUpdates = bd.length + plugins.length + themes.length + modules.length;
|
||||
|
||||
const renamed = [];
|
||||
// TODO cleaner
|
||||
if (bd.length) {
|
||||
for (const update of bd) {
|
||||
try {
|
||||
await FileUtils.rm(`${this.bd.config.getPath(update.id)}_old`);
|
||||
// Try to rename dirs first
|
||||
await FileUtils.rn(this.bd.config.getPath(update.id), `${this.bd.config.getPath(update.id)}_old`);
|
||||
renamed.push({ 'old': this.bd.config.getPath(update.id), 'new': `${this.bd.config.getPath(update.id)}_old`});
|
||||
} catch (err) {
|
||||
if (renamed.length) {
|
||||
// Restore dirs
|
||||
for (const r of renamed) {
|
||||
await FileUtils.rn(r.new, r.old);
|
||||
}
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
for (const update of bd) {
|
||||
this.updateBd(update);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
updateFinished(update) {
|
||||
if (update.id === 'core') this.restartRequired = true;
|
||||
if (update.id === 'client') this.reloadRequired = true;
|
||||
|
||||
console.log('[BetterDiscord:Updater] Finished updating', update.id);
|
||||
this.bd.sendToDiscord('updater-updateFinished', update);
|
||||
|
||||
this.finishedUpdates++;
|
||||
if (this.finishedUpdates >= this.totalUpdates) {
|
||||
this.bd.sendToDiscord('updater-updated', { restartRequired: this.restartRequired, reloadRequired: this.reloadRequired });
|
||||
}
|
||||
}
|
||||
|
||||
start(interval = 30) {
|
||||
this.updaterThread = setInterval(this.checkForUpdates, interval * 60 * 1000);
|
||||
}
|
||||
|
||||
validate(releaseInfo) {
|
||||
return releaseInfo &&
|
||||
typeof releaseInfo === 'object' &&
|
||||
releaseInfo.files &&
|
||||
Array.isArray(releaseInfo.files) &&
|
||||
releaseInfo.files.length >= 4;
|
||||
}
|
||||
|
||||
async latestRelease() {
|
||||
try {
|
||||
const release = await Axi.github.api.get('repos/JsSucks/BetterDiscordApp/releases/latest'); // TODO replace with config
|
||||
const releaseInfoAsset = release.data.assets.find(asset => asset.name === 'releaseinfo.json');
|
||||
const releaseInfo = await Axi.get(releaseInfoAsset['browser_download_url']);
|
||||
|
||||
if (this.validate(releaseInfo.data)) return releaseInfo.data;
|
||||
return this.latestReleaseFallback();
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return this.latestReleaseFallback();
|
||||
}
|
||||
}
|
||||
|
||||
async latestReleaseFallback() {
|
||||
console.log('fallback');
|
||||
}
|
||||
|
||||
async checkForBdUpdates(forced = false) {
|
||||
try {
|
||||
const { coreVersion, clientVersion, editorVersion } = this.bd.config;
|
||||
const releaseInfo = new ReleaseInfo({ core: coreVersion, client: clientVersion, editor: editorVersion });
|
||||
|
||||
const latestRelease = await this.latestRelease();
|
||||
|
||||
if (forced) {
|
||||
latestRelease.files = latestRelease.files.map(file => {
|
||||
file.version = '10.0.0';
|
||||
return file;
|
||||
});
|
||||
}
|
||||
|
||||
releaseInfo.files = latestRelease.files;
|
||||
|
||||
const updates = [];
|
||||
|
||||
const { core, client, editor } = releaseInfo;
|
||||
if (!core.upToDate) updates.push(core);
|
||||
if (!client.upToDate) updates.push(client);
|
||||
if (!editor.upToDate) updates.push(editor);
|
||||
|
||||
return updates;
|
||||
|
||||
} catch (err) {
|
||||
console.log('[BetterDiscord:Updater]', err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async checkForUpdates(forced = false) {
|
||||
console.log('[BetterDiscord:Updater] Checking for updates');
|
||||
this.bd.sendToDiscord('updater-checkForUpdates', '');
|
||||
|
||||
try {
|
||||
const bd = await this.checkForBdUpdates(forced);
|
||||
const updates = { bd, haveUpdates: false };
|
||||
|
||||
if (bd.length) updates.haveUpdates = true;
|
||||
|
||||
if (!updates.haveUpdates) {
|
||||
this.bd.sendToDiscord('updater-noUpdates', '');
|
||||
return true;
|
||||
}
|
||||
|
||||
this.bd.sendToDiscord('updater-updatesAvailable', updates);
|
||||
|
||||
return true;
|
||||
|
||||
} catch (err) {
|
||||
console.log('[BetterDiscord:Updater]', err);
|
||||
this.bd.sendToDiscord('updater-error', err);
|
||||
return 'err';
|
||||
}
|
||||
}
|
||||
|
||||
async downloadTarGz(url, dest, responseType = 'stream', headers = null) {
|
||||
try {
|
||||
const stream = await Axi.axios({
|
||||
url,
|
||||
type: 'GET',
|
||||
responseType,
|
||||
headers: headers ||
|
||||
{
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'Accept': 'application/octet-stream'
|
||||
}
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
stream.data.pipe(zlib.createGunzip()).pipe(tarfs.extract(dest)).on('finish', resolve).on('error', reject);
|
||||
});
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
debug(releaseInfo) {
|
||||
const { core, client, editor } = releaseInfo;
|
||||
if (!core.upToDate) {
|
||||
console.log(`[BetterDiscord:Updater] Core update available: ${core.currentVersion} > ${core.version}`);
|
||||
} else {
|
||||
console.log(`[BetterDiscord:Updater] Core up to date: ${core.currentVersion} = ${core.version}`);
|
||||
}
|
||||
|
||||
if (!client.upToDate) {
|
||||
console.log(`[BetterDiscord:Updater] Client update available: ${client.currentVersion} > ${client.version}`);
|
||||
} else {
|
||||
console.log(`[BetterDiscord:Updater] Client up to date: ${client.currentVersion} = ${client.version}`);
|
||||
}
|
||||
|
||||
if (!editor.upToDate) {
|
||||
console.log(`[BetterDiscord:Updater] Editor update available: ${editor.currentVersion} > ${editor.version}`);
|
||||
} else {
|
||||
console.log(`[BetterDiscord:Updater] Editor up to date: ${editor.currentVersion} = ${editor.version}`);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -11,6 +11,7 @@
|
|||
// TODO Use common
|
||||
|
||||
import fs from 'fs';
|
||||
import rimraf from 'rimraf';
|
||||
|
||||
import Module from './modulebase';
|
||||
import BDIpc from './bdipc';
|
||||
|
@ -84,6 +85,20 @@ export class FileUtils {
|
|||
});
|
||||
}
|
||||
|
||||
static async ensureFile(path) {
|
||||
try {
|
||||
await this.fileExists(path);
|
||||
return true;
|
||||
} catch (err) {
|
||||
try {
|
||||
await this.writeFile(path, '');
|
||||
return true;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static async readJsonFromFile(path) {
|
||||
let readFile;
|
||||
try {
|
||||
|
@ -99,6 +114,15 @@ export class FileUtils {
|
|||
}
|
||||
}
|
||||
|
||||
static async writeFile(path, data, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.writeFile(path, data, options, err => {
|
||||
if (err) return reject(err);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static async listDirectory(path) {
|
||||
try {
|
||||
await this.directoryExists(path);
|
||||
|
@ -155,6 +179,27 @@ export class FileUtils {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
static async rm(path) {
|
||||
return new Promise((resolve, reject) => {
|
||||
rimraf(path, err => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
reject(err);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static async rn(oldPath, newPath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.rename(oldPath, newPath, err => {
|
||||
if (err) return reject(err);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class WindowUtils extends Module {
|
||||
|
|
|
@ -1,191 +0,0 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<div class="titlebar">
|
||||
<div class="draggable"></div>
|
||||
<div class="icon">
|
||||
<div class="inner"></div>
|
||||
</div>
|
||||
<div class="title">CSS Editor</div>
|
||||
<div class="flex-spacer"></div>
|
||||
<div class="controls">
|
||||
<button :class="{active: alwaysOnTop}" ref="aot" title="Toggle always on top" @click="toggleaot">P</button>
|
||||
<button title="Close CSS Editor" @click="close">X</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="spinner" v-if="loading">
|
||||
<div class="valign">Loading Please Wait...</div>
|
||||
</div>
|
||||
<div id="editor" class="editor">
|
||||
<codemirror ref="mycm" :options="cmOptions" @input="cmOnChange" />
|
||||
</div>
|
||||
<div class="parser-error" v-if="error">{{ error.formatted }}</div>
|
||||
<div class="tools">
|
||||
<div class="flex-row">
|
||||
<button @click="save">Save</button>
|
||||
<button @click="update">Update</button>
|
||||
<div class="flex-spacer"></div>
|
||||
<div id="chkboxLiveUpdate">
|
||||
<label><input type="checkbox" @click="toggleLiveUpdate" v-model="liveUpdate" /><span>Live Update</span></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ClientIPC } from 'common';
|
||||
|
||||
import { remote } from 'electron';
|
||||
|
||||
import 'codemirror/addon/scroll/simplescrollbars.js';
|
||||
import 'codemirror/mode/css/css.js';
|
||||
import 'codemirror/addon/hint/css-hint.js';
|
||||
import 'codemirror/addon/search/search.js';
|
||||
import 'codemirror/addon/search/searchcursor.js';
|
||||
import 'codemirror/addon/search/jump-to-line.js';
|
||||
import 'codemirror/addon/dialog/dialog.js';
|
||||
import 'codemirror/addon/hint/show-hint.js';
|
||||
|
||||
const ExcludedIntelliSenseTriggerKeys = {
|
||||
'8': 'backspace',
|
||||
'9': 'tab',
|
||||
'13': 'enter',
|
||||
'16': 'shift',
|
||||
'17': 'ctrl',
|
||||
'18': 'alt',
|
||||
'19': 'pause',
|
||||
'20': 'capslock',
|
||||
'27': 'escape',
|
||||
'33': 'pageup',
|
||||
'34': 'pagedown',
|
||||
'35': 'end',
|
||||
'36': 'home',
|
||||
'37': 'left',
|
||||
'38': 'up',
|
||||
'39': 'right',
|
||||
'40': 'down',
|
||||
'45': 'insert',
|
||||
'46': 'delete',
|
||||
'91': 'left window key',
|
||||
'92': 'right window key',
|
||||
'93': 'select',
|
||||
'107': 'add',
|
||||
'109': 'subtract',
|
||||
'110': 'decimal point',
|
||||
'111': 'divide',
|
||||
'112': 'f1',
|
||||
'113': 'f2',
|
||||
'114': 'f3',
|
||||
'115': 'f4',
|
||||
'116': 'f5',
|
||||
'117': 'f6',
|
||||
'118': 'f7',
|
||||
'119': 'f8',
|
||||
'120': 'f9',
|
||||
'121': 'f10',
|
||||
'122': 'f11',
|
||||
'123': 'f12',
|
||||
'144': 'numlock',
|
||||
'145': 'scrolllock',
|
||||
'186': 'semicolon',
|
||||
'187': 'equalsign',
|
||||
'188': 'comma',
|
||||
'189': 'dash',
|
||||
'190': 'period',
|
||||
'191': 'slash',
|
||||
'192': 'graveaccent',
|
||||
'220': 'backslash',
|
||||
'222': 'quote'
|
||||
};
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
codeMirror: null,
|
||||
alwaysOnTop: false,
|
||||
liveUpdate: false,
|
||||
cmOptions: {
|
||||
indentUnit: 4,
|
||||
tabSize: 4,
|
||||
mode: 'text/x-scss',
|
||||
lineNumbers: true,
|
||||
theme: 'material',
|
||||
scrollbarStyle: 'overlay',
|
||||
extraKeys: {
|
||||
'Ctrl-Space': 'autocomplete'
|
||||
},
|
||||
dialog: {
|
||||
'position': 'bottom'
|
||||
}
|
||||
},
|
||||
error: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
codemirror() {
|
||||
return this.$refs.mycm.codemirror;
|
||||
},
|
||||
CodeMirror() {
|
||||
return this.$refs.mycm;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
ClientIPC.on('set-scss', (_, scss) => this.setScss(scss));
|
||||
|
||||
ClientIPC.on('scss-error', (_, err) => {
|
||||
this.error = err;
|
||||
this.$forceUpdate();
|
||||
if (err)
|
||||
console.error('SCSS parse error:', err);
|
||||
});
|
||||
|
||||
ClientIPC.on('set-liveupdate', (e, liveUpdate) => this.liveUpdate = liveUpdate);
|
||||
},
|
||||
mounted() {
|
||||
this.codemirror.on('keyup', this.cmOnKeyUp);
|
||||
|
||||
(async () => {
|
||||
this.setScss(await ClientIPC.sendToDiscord('get-scss'));
|
||||
this.liveUpdate = await ClientIPC.sendToDiscord('get-liveupdate');
|
||||
})();
|
||||
},
|
||||
watch: {
|
||||
liveUpdate(liveUpdate) {
|
||||
ClientIPC.sendToDiscord('set-liveupdate', liveUpdate);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
save() {
|
||||
const scss = this.codemirror.getValue();
|
||||
ClientIPC.sendToDiscord('save-scss', scss);
|
||||
},
|
||||
update() {
|
||||
const scss = this.codemirror.getValue();
|
||||
ClientIPC.sendToDiscord('update-scss', scss);
|
||||
},
|
||||
toggleaot() {
|
||||
this.alwaysOnTop = !this.alwaysOnTop;
|
||||
remote.getCurrentWindow().setAlwaysOnTop(this.alwaysOnTop);
|
||||
},
|
||||
close() {
|
||||
window.close();
|
||||
},
|
||||
setScss(scss) {
|
||||
this.loading = false;
|
||||
this.codemirror.setValue(scss || '');
|
||||
},
|
||||
cmOnChange(value) {
|
||||
if(this.liveUpdate) ClientIPC.sendToDiscord('update-scss', value);
|
||||
},
|
||||
cmOnKeyUp(editor, event) {
|
||||
if (event.ctrlKey) return;
|
||||
if (ExcludedIntelliSenseTriggerKeys[event.keyCode]) return;
|
||||
cmCommands.autocomplete(editor, null, { completeSingle: false });
|
||||
},
|
||||
toggleLiveUpdate(e) {
|
||||
this.liveUpdate = !this.liveUpdate;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,105 +0,0 @@
|
|||
.CodeMirror-scroll {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.CodeMirror-overlayscroll .CodeMirror-scrollbar-filler {
|
||||
background: #38444a;
|
||||
}
|
||||
|
||||
.CodeMirror-overlayscroll-horizontal div,
|
||||
.CodeMirror-overlayscroll-vertical div {
|
||||
background: rgb(41, 43, 47);
|
||||
}
|
||||
|
||||
.CodeMirror-overlayscroll-horizontal,
|
||||
.CodeMirror-overlayscroll-horizontal div {
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.CodeMirror-overlayscroll-vertical,
|
||||
.CodeMirror-overlayscroll-vertical div {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.CodeMirror-scrollbar-filler {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: rgb(41, 43, 47);
|
||||
}
|
||||
|
||||
.cm-s-material.CodeMirror {
|
||||
background: #36393f;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.cm-s-material .CodeMirror-gutters {
|
||||
background: #292b2f;
|
||||
}
|
||||
|
||||
.CodeMirror-gutter {
|
||||
min-width: 34px;
|
||||
border-right: 1px solid hsla(218,5%,47%,.3);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.CodeMirror-hints {
|
||||
/*background: #1e262a;*/
|
||||
background: #292b2f;
|
||||
box-shadow: 2px 3px 5px rgba(4, 4, 4, 0.22);
|
||||
border: 1px solid #262f33;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(0,0,0,.4);
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb,
|
||||
&::-webkit-scrollbar-track {
|
||||
background-clip: padding-box;
|
||||
border-width: 3px;
|
||||
border-style: solid;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.CodeMirror-linenumber,
|
||||
.CodeMirror-line {
|
||||
padding: 0 5px !important;
|
||||
}
|
||||
|
||||
.CodeMirror-linenumber {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.cm-s-material .CodeMirror-linenumber {
|
||||
color: #f6f6f7;
|
||||
}
|
||||
|
||||
.CodeMirror-hint {
|
||||
color: #bac9d2;
|
||||
}
|
||||
|
||||
li.CodeMirror-hint-active {
|
||||
color: #bac9d2;
|
||||
/*background: #3b4950;*/
|
||||
background: #36393f;
|
||||
}
|
||||
|
||||
.CodeMirror-dialog-top {
|
||||
bottom: 0;
|
||||
top: auto;
|
||||
border: none;
|
||||
background: #1e262a;
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
.editor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
|
||||
.vue-codemirror {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
|
||||
&, & .CodeMirror {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
@import '../../../node_modules/codemirror/lib/codemirror.css';
|
||||
@import '../../../node_modules/codemirror/theme/material.css';
|
||||
@import '../../../node_modules/codemirror/addon/scroll/simplescrollbars.css';
|
||||
@import '../../../node_modules/codemirror/addon/dialog/dialog.css';
|
||||
@import '../../../node_modules/codemirror/addon/hint/show-hint.css';
|
||||
|
||||
@import './images.scss';
|
||||
@import './main.scss';
|
||||
@import './titlebar.scss';
|
||||
@import './spinner.scss';
|
||||
@import './editor.scss';
|
||||
@import './tools.scss';
|
||||
@import './codemirror.scss';
|
|
@ -1,14 +0,0 @@
|
|||
#spinner {
|
||||
background: rgba(51, 48, 48, 0.41);
|
||||
position: absolute;
|
||||
top: 34px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
color: #bac9d2;
|
||||
font-family: Whitney,Helvetica Neue,Helvetica,Arial,sans-serif;
|
||||
font-weight: 600;
|
||||
font-size: 2em;
|
||||
z-index: 90000;
|
||||
user-select: none;
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
.parser-error {
|
||||
padding: 4px 6px;
|
||||
background: #292b2f;
|
||||
border-top: 1px solid hsla(218,5%,47%,.3);
|
||||
color: #d84040;
|
||||
font-family: monospace;
|
||||
white-space: pre-wrap;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.tools {
|
||||
height: 36px;
|
||||
background: #292b2f;
|
||||
border-top: 1px solid hsla(218,5%,47%,.3);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
user-select: none;
|
||||
|
||||
.flex-row {
|
||||
flex-grow: 1;
|
||||
padding: 4px 5px;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 3px;
|
||||
width: 100px;
|
||||
padding: 3px 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
background: #36393f;
|
||||
color: #bac9d2;
|
||||
font-family: Whitney,Helvetica Neue,Helvetica,Arial,sans-serif;
|
||||
transition: background-color .2s ease, color .2s ease;
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
margin-right: 4px;
|
||||
flex: 0 0 auto;
|
||||
|
||||
&:hover {
|
||||
background: #44474e;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
#chkboxLiveUpdate {
|
||||
padding: 3px 10px;
|
||||
line-height: 22px;
|
||||
flex: 0 0 auto;
|
||||
|
||||
label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
margin: 0 6px 0 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: #bac9d2;
|
||||
font-family: Whitney,Helvetica Neue,Helvetica,Arial,sans-serif;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
|
||||
|
||||
const vueLoader = {
|
||||
test: /\.(vue)$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'vue-loader'
|
||||
};
|
||||
|
||||
const scssLoader = {
|
||||
test: /\.(css|scss)$/,
|
||||
loader: ['css-loader', 'sass-loader']
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
entry: './src/index.js',
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'csseditor-release.js'
|
||||
},
|
||||
module: {
|
||||
loaders: [vueLoader, scssLoader]
|
||||
},
|
||||
externals: {
|
||||
electron: 'window.require("electron")',
|
||||
fs: 'window.require("fs")',
|
||||
util: 'window.require("util")',
|
||||
process: 'require("process")'
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
vue$: path.resolve('..', 'node_modules', 'vue', 'dist', 'vue.esm.js')
|
||||
},
|
||||
modules: [
|
||||
path.resolve('..', 'node_modules'),
|
||||
path.resolve('..', 'common', 'modules')
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
PRODUCTION: JSON.stringify(true)
|
||||
}),
|
||||
new UglifyJsPlugin()
|
||||
]
|
||||
};
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"name": "bdcsseditor",
|
||||
"description": "BetterDiscord css editor package",
|
||||
"author": "Jiiks",
|
||||
"name": "bdeditorwindow",
|
||||
"description": "BetterDiscord editor window",
|
||||
"author": "JsSucks",
|
||||
"version": "0.4.0",
|
||||
"homepage": "https://betterdiscord.net",
|
||||
"license": "MIT",
|
||||
"main": "dist/csseditor.js",
|
||||
"main": "dist/editor.js",
|
||||
"contributors": [
|
||||
"Jiiks",
|
||||
"Pohky"
|
|
@ -0,0 +1,275 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<div class="titlebar">
|
||||
<div class="draggable"></div>
|
||||
<div class="icon">
|
||||
<div class="inner"></div>
|
||||
</div>
|
||||
<div class="title">Content Editor</div>
|
||||
<div class="flex-spacer"></div>
|
||||
<div class="controls">
|
||||
<button :class="{active: alwaysOnTop}" ref="aot" title="Toggle always on top" @click="toggleaot">P</button>
|
||||
<button title="Close CSS Editor" @click="close">X</button>
|
||||
</div>
|
||||
</div>
|
||||
<BDEdit :files="files"
|
||||
:parentLoading="loading"
|
||||
:snippets="snippets"
|
||||
:updateContent="updateContent"
|
||||
:runScript="runScript"
|
||||
:newFile="newFile"
|
||||
:saveFile="saveFile"
|
||||
:newSnippet="newSnippet"
|
||||
:saveSnippet="saveSnippet"
|
||||
:readFile="readFile"
|
||||
:readSnippet="readSnippet"
|
||||
:injectStyle="injectStyle"
|
||||
:toggleLiveUpdate="toggleLiveUpdate"
|
||||
:ctxAction="ctxAction"
|
||||
:toast="toast"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ClientIPC } from 'common';
|
||||
import { remote } from 'electron';
|
||||
|
||||
import { BDEdit } from 'bdedit';
|
||||
|
||||
ace.acequire = ace.require;
|
||||
|
||||
const modes = {
|
||||
'css': 'css',
|
||||
'scss': 'scss',
|
||||
'js': 'javascript',
|
||||
'txt': 'text',
|
||||
'json': 'json'
|
||||
};
|
||||
|
||||
function resolveMode(fileName) {
|
||||
if (!fileName.includes('.')) return 'text';
|
||||
const ext = fileName.substr(fileName.lastIndexOf('.') + 1);
|
||||
if (modes.hasOwnProperty(ext)) return modes[ext];
|
||||
return 'text';
|
||||
}
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
files: [],
|
||||
snippets: [],
|
||||
loading: true,
|
||||
alwaysOnTop: false,
|
||||
error: undefined,
|
||||
lastSaved: undefined,
|
||||
toast: { active: false, msg: '' }
|
||||
}
|
||||
},
|
||||
components: { BDEdit },
|
||||
created() {
|
||||
ClientIPC.on('bd-editor-addFile', (_, file) => {
|
||||
if (this.files.find(f => f.name === file.name)) return;
|
||||
this.addFile(file);
|
||||
});
|
||||
|
||||
ClientIPC.on('bd-editor-remFile', (_, file) => {
|
||||
this.files = this.files.filter(f => f.name !== file.name);
|
||||
});
|
||||
|
||||
ClientIPC.on('bd-editor-addSnippet', (_, snippet) => {
|
||||
if (this.snippets.find(s => s.name === snippet.name)) return;
|
||||
this.addSnippet(snippet);
|
||||
});
|
||||
|
||||
ClientIPC.on('bd-editor-remSnippet', (_, snippet) => {
|
||||
this.snippets = this.snippets.filter(s => s.name !== snippet.name);
|
||||
});
|
||||
|
||||
ClientIPC.on('bd-editor-fileChange', (_, file) => {
|
||||
if (this.lastSaved && this.lastSaved.name === file.name) { // If we saved in our editor then don't trigger
|
||||
this.lastSaved = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
const f = this.files.find(f => f.name === file.name);
|
||||
if (f) f.changed = true;
|
||||
});
|
||||
},
|
||||
async mounted() {
|
||||
this.files = await ClientIPC.send('bd-editor-getFiles');
|
||||
this.snippets = await ClientIPC.send('bd-editor-getSnippets');
|
||||
this.loading = false;
|
||||
},
|
||||
methods: {
|
||||
addFile(file) {
|
||||
this.files.push(file);
|
||||
},
|
||||
|
||||
addSnippet(snippet) { this.snippets.push(file) },
|
||||
|
||||
updateContent(item, content) {
|
||||
item.content = content;
|
||||
item.saved = item.content === item.savedContent;
|
||||
|
||||
if (this.liveUpdateTimeout) clearTimeout(this.liveUpdateTimeout);
|
||||
if (item.liveUpdateEnabled) {
|
||||
this.liveUpdateTimeout = setTimeout(() => {
|
||||
this.injectStyle(item);
|
||||
}, 5000);
|
||||
}
|
||||
},
|
||||
|
||||
async runScript(script) {
|
||||
return ClientIPC.send('editor-runScript', script);
|
||||
},
|
||||
|
||||
newFile(fileName) {
|
||||
const prefix = fileName;
|
||||
const mode = resolveMode(fileName);
|
||||
|
||||
let newName = prefix;
|
||||
let iter = 0;
|
||||
|
||||
while (this.files.find(file => file.name === newName)) {
|
||||
newName = `${prefix.split('.')[0]}_${iter}.${prefix.split('.')[1]}`;
|
||||
iter++;
|
||||
}
|
||||
|
||||
const newItem = { type: 'file', name: newName, content: '', mode, saved: false, read: true };
|
||||
this.files.push(newItem);
|
||||
return newItem;
|
||||
},
|
||||
|
||||
newSnippet(snippetName) {
|
||||
const prefix = snippetName;
|
||||
const mode = resolveMode(snippetName);
|
||||
|
||||
let newName = prefix;
|
||||
let iter = 0;
|
||||
|
||||
while (this.snippets.find(snippet => snippet.name === newName)) {
|
||||
newName = `${prefix}_${iter}`;
|
||||
iter++;
|
||||
}
|
||||
|
||||
const newItem = { type: 'snippet', name: newName, content: '', savedContent: '', mode, saved: false, read: true };
|
||||
this.snippets.push(newItem);
|
||||
return newItem;
|
||||
},
|
||||
|
||||
async saveFile(file) {
|
||||
try {
|
||||
this.lastSaved = file;
|
||||
const result = await ClientIPC.send('bd-editor-saveFile', file);
|
||||
file.savedContent = file.content;
|
||||
file.saved = true;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
},
|
||||
|
||||
async saveSnippet(snippet) {
|
||||
snippet.saved = true;
|
||||
snippet.savedContent = snippet.content;
|
||||
const result = await ClientIPC.send('bd-editor-saveSnippet', this.snippets.map(snippet => {
|
||||
return {
|
||||
name: snippet.name,
|
||||
content: snippet.savedContent
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
async readFile(file) {
|
||||
const content = await ClientIPC.send('editor-readFile', file);
|
||||
file.read = true;
|
||||
file.changed = false;
|
||||
file.content = file.savedContent = content;
|
||||
return content;
|
||||
},
|
||||
|
||||
async readSnippet(snippet) {
|
||||
const content = await ClientIPC.send('editor-readSnippet', snippet);
|
||||
snippet.read = true;
|
||||
return content;
|
||||
},
|
||||
|
||||
async injectStyle(item) {
|
||||
const style = item.content.length <= 0 ? ' ' : item.content;
|
||||
const result = await ClientIPC.send('bd-editor-injectStyle', { id: item.name.split('.')[0], style, mode: item.mode });
|
||||
return result;
|
||||
},
|
||||
|
||||
async ctxAction(action, item) {
|
||||
if (action === 'reveal') {
|
||||
ClientIPC.send('explorer', { 'static': 'userfiles' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === 'copy') {
|
||||
remote.clipboard.writeText(item.content);
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === 'copyPath') {
|
||||
const fullPath = await ClientIPC.send('getPath', ['userfiles', item.name]);
|
||||
remote.clipboard.writeText(fullPath);
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === 'rename') { // TODO select correct file after
|
||||
this.loading = true;
|
||||
const { oldName, newName } = item;
|
||||
|
||||
if (this.files.find(f => f.name === newName)) {
|
||||
this.loading = false;
|
||||
this.showToast('err', `File ${newName} already exists`);
|
||||
return {
|
||||
err: `File ${newName} already exists`
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
await ClientIPC.send('rnFile', { oldName: ['userfiles', oldName], newName: ['userfiles', newName] });
|
||||
return item;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
return { err };
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (action === 'delete') {
|
||||
this.loading = true;
|
||||
try {
|
||||
await ClientIPC.send('rmFile', ['userfiles', item.name]);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
showToast(type, msg, timeout = 3000) {
|
||||
this.toast = { active: true, type, msg };
|
||||
setTimeout(() => { this.toast.active = false }, timeout);
|
||||
},
|
||||
|
||||
toggleLiveUpdate(item) {
|
||||
item.liveUpdateEnabled = !item.liveUpdateEnabled;
|
||||
return item;
|
||||
},
|
||||
|
||||
toggleaot() {
|
||||
this.alwaysOnTop = !this.alwaysOnTop;
|
||||
remote.getCurrentWindow().setAlwaysOnTop(this.alwaysOnTop);
|
||||
},
|
||||
|
||||
close() {
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,15 +1,10 @@
|
|||
// const styles = require('./styles/index.scss');
|
||||
|
||||
import Vue from 'vue';
|
||||
import VueCodemirror from 'vue-codemirror';
|
||||
|
||||
import Editor from './Editor.vue';
|
||||
import styles from './styles/index.scss';
|
||||
|
||||
Vue.use(VueCodemirror, {});
|
||||
|
||||
window.cmCommands = VueCodemirror.CodeMirror.commands;
|
||||
|
||||
const mount = document.createElement('div');
|
||||
mount.classList.add('container');
|
||||
document.body.appendChild(mount);
|
|
@ -1,36 +1,20 @@
|
|||
@import './vars.scss';
|
||||
@import './titlebar.scss';
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
max-height: 100%;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: #2c383e;
|
||||
min-width: 700px;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
* {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.flex-spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.valign {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
|
@ -28,12 +28,17 @@
|
|||
font-size: 15px;
|
||||
}
|
||||
|
||||
.flex-spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin: 0 0 0 2px;
|
||||
font-size: 0;
|
||||
|
||||
button {
|
||||
-webkit-app-region: no-drag;
|
||||
outline: none !important;
|
||||
border-radius: 3px;
|
||||
width: 25px;
|
||||
font-size: 12px;
|
||||
|
@ -50,12 +55,12 @@
|
|||
margin: 0 0 0 4px;
|
||||
|
||||
&:hover {
|
||||
background: #44474e;
|
||||
background: $bdGreen;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: #3a71c1;
|
||||
background: $bdGreen;
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue