diff --git a/client/src/builtin/E2EE.js b/client/src/builtin/E2EE.js index c22f29a8..a56d58e0 100644 --- a/client/src/builtin/E2EE.js +++ b/client/src/builtin/E2EE.js @@ -9,6 +9,10 @@ */ import BuiltinModule from './BuiltinModule'; +import { WebpackModules, ReactComponents, MonkeyPatch, Patcher } from 'modules'; +import { VueInjector, Reflection } from 'ui'; +import E2EEComponent from './E2EEComponent.vue'; +import aes256 from 'aes256'; export default new class E2EE extends BuiltinModule { @@ -16,12 +20,25 @@ export default new class E2EE extends BuiltinModule { return ['security', 'default', 'e2ee']; } - enabled(e) { - + async enabled(e) { + const ctaComponent = await ReactComponents.getComponent('ChannelTextArea'); + MonkeyPatch('BD:E2EE', ctaComponent.component.prototype).after('render', this.render); + MonkeyPatch('BD:E2EE', ctaComponent.component.prototype).before('handleSubmit', this.handleSubmit); + } + + render(component, args, retVal) { + if (!(retVal.props.children instanceof Array)) retVal.props.children = [retVal.props.children]; + const inner = retVal.props.children.find(child => child.props.className && child.props.className.includes('inner')); + + inner.props.children.splice(0, 0, VueInjector.createReactElement(E2EEComponent, {}, true)); + } + + handleSubmit(component, args, retVal) { + component.props.value = aes256.encrypt('randomkey', component.props.value); } disabled(e) { - + for (const patch of Patcher.getPatchesByCaller('BD:E2EE')) patch.unpatch(); } } diff --git a/client/src/builtin/E2EEComponent.vue b/client/src/builtin/E2EEComponent.vue new file mode 100644 index 00000000..21660241 --- /dev/null +++ b/client/src/builtin/E2EEComponent.vue @@ -0,0 +1,29 @@ +/** + * BetterDiscord E2EE 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. +*/ + + + + diff --git a/client/src/builtin/Manager.js b/client/src/builtin/Manager.js index 4d941a03..4d769966 100644 --- a/client/src/builtin/Manager.js +++ b/client/src/builtin/Manager.js @@ -2,6 +2,7 @@ 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'; export default class { static initAll() { @@ -9,5 +10,6 @@ export default class { ReactDevtoolsModule.init(); VueDevtoolsModule.init(); TrackingProtection.init(); + E2EE.init(); } } diff --git a/client/src/data/user.settings.default.json b/client/src/data/user.settings.default.json index 3a57d7c8..2ca31ad6 100644 --- a/client/src/data/user.settings.default.json +++ b/client/src/data/user.settings.default.json @@ -171,10 +171,9 @@ "type": "drawer", "settings": [ { - "id": "kvp0", - "type": "kvp", - "text": "", - "value": { "key": "kvpKey", "value": "kvpValue" } + "id": "e2ekvps", + "type": [ "securekvp" ], + "value": [] } ] } diff --git a/client/src/modules/reactcomponents.js b/client/src/modules/reactcomponents.js index 0b9e5bbe..e8d97cef 100644 --- a/client/src/modules/reactcomponents.js +++ b/client/src/modules/reactcomponents.js @@ -10,7 +10,7 @@ * LICENSE file in the root directory of this source tree. */ -import { DOM, Reflection } from 'ui'; +import { DOM, Reflection, Modals } from 'ui'; import { Utils, Filters, ClientLogger as Logger } from 'common'; import { MonkeyPatch } from './patcher'; import { WebpackModules } from './webpackmodules'; @@ -501,4 +501,30 @@ export class ReactAutoPatcher { this.UserPopout.forceUpdateAll(); } + + static async patchUploadArea() { + const selector = '.' + WebpackModules.getClassName('uploadArea'); + this.UploadArea = await ReactComponents.getComponent('UploadArea', {selector}); + + const reflect = Reflection(selector); + const stateNode = reflect.getComponentStateNode(this.UploadArea); + const callback = function(e) { + if (!e.dataTransfer.files.length || !e.dataTransfer.files[0].name.endsWith('.bd')) return; + e.preventDefault(); + e.stopPropagation(); + e.stopImmediatePropagation(); + stateNode.clearDragging(); + Modals.confirm("Function not ready", `You tried to install "${e.dataTransfer.files[0].path}", but installing .bd files isn't ready yet.`) + // Possibly something like Events.emit('install-file', e.dataTransfer.files[0]); + }; + + // Remove their handler, add ours, then readd theirs to give ours priority to stop theirs when we get a .bd file. + reflect.element.removeEventListener('drop', stateNode.handleDrop); + reflect.element.addEventListener('drop', callback); + reflect.element.addEventListener('drop', stateNode.handleDrop); + + this.unpatchUploadArea = function() { + reflect.element.removeEventListener('drop', callback); + }; + } } diff --git a/client/src/structs/settings/setting.js b/client/src/structs/settings/setting.js index 82ba7ba1..fe2b4e5a 100644 --- a/client/src/structs/settings/setting.js +++ b/client/src/structs/settings/setting.js @@ -19,7 +19,9 @@ import KeybindSetting from './types/keybind'; import FileSetting from './types/file'; import GuildSetting from './types/guild'; import ArraySetting from './types/array'; +import CollectionSetting from './types/collection'; import KvpSetting from './types/kvp'; +import SecureKvpSetting from './types/securekvp'; import CustomSetting from './types/custom'; export default class Setting { @@ -27,6 +29,7 @@ export default class Setting { constructor(args, ...merge) { args = args.args || args; + if (args.type instanceof Array) args.subtype = args.type[0], args.type = 'collection'; if (args.type === 'color') args.type = 'colour'; if (args.type === 'bool') return new BoolSetting(args, ...merge); @@ -40,8 +43,10 @@ export default class Setting { else if (args.type === 'file') return new FileSetting(args, ...merge); else if (args.type === 'guild') return new GuildSetting(args, ...merge); else if (args.type === 'array') return new ArraySetting(args, ...merge); - else if (args.type === 'custom') return new CustomSetting(args, ...merge); + else if (args.type === 'collection') return new CollectionSetting(args, ...merge); else if (args.type === 'kvp') return new KvpSetting(args, ...merge); + else if (args.type === 'securekvp') return new SecureKvpSetting(args, ...merge); + else if (args.type === 'custom') return new CustomSetting(args, ...merge); else throw {message: `Setting type ${args.type} unknown`}; } diff --git a/client/src/structs/settings/types/collection.js b/client/src/structs/settings/types/collection.js new file mode 100644 index 00000000..2390f489 --- /dev/null +++ b/client/src/structs/settings/types/collection.js @@ -0,0 +1,49 @@ +/** + * BetterDiscord Collection Setting Struct + * 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 { Utils } from 'common'; +import ArraySetting from './array'; +import Setting from '../setting'; + +export default class CollectionSetting extends ArraySetting { + + constructor(args, ...merge) { + // The ArraySetting constructor will call createItem which requires this to be set + if (!(args.setting instanceof Setting)) args.setting = new Setting(args.setting || {type: args.subtype}); + + super(args, ...merge); + } + + get setting() { + return this.args.setting; + } + + /** + * Creates a new setting for this collection setting. + * @param {Setting} item Values to merge into the new setting (optional) + * @return {Setting} The new set + */ + createItem(item) { + if (item instanceof Setting) + return item; + + const merge = [...arguments].filter(a => a); + const setting = this.setting.clone(...merge); + setting.args.id = item ? item.args ? item.args.id : item.id : Math.random(); + + setting.setSaved(); + setting.on('settings-updated', async event => { + await this.emit('item-updated', { item: setting, event, updatedSettings: event.updatedSettings }); + if (event.args.updating_array !== this) await this.updateValue(); + }); + return setting; + } + +} diff --git a/client/src/structs/settings/types/index.js b/client/src/structs/settings/types/index.js index 16f60e1e..3ad64c51 100644 --- a/client/src/structs/settings/types/index.js +++ b/client/src/structs/settings/types/index.js @@ -10,4 +10,5 @@ export { default as FileSetting } from './file'; export { default as GuildSetting } from './guild'; export { default as ArraySetting } from './array'; export { default as KvpSetting } from './kvp'; +export { default as CollectionSetting } from './kvp'; export { default as CustomSetting } from './custom'; diff --git a/client/src/structs/settings/types/kvp.js b/client/src/structs/settings/types/kvp.js index 52c4a04a..466b0094 100644 --- a/client/src/structs/settings/types/kvp.js +++ b/client/src/structs/settings/types/kvp.js @@ -16,6 +16,6 @@ export default class KvpSetting extends Setting { * The value to use when the setting doesn't have a value. */ get defaultValue() { - return { key: 'Channel ID', value: 'Encryption Key' }; + return { key: 'Key', value: 'Value' }; } } diff --git a/client/src/structs/settings/types/securekvp.js b/client/src/structs/settings/types/securekvp.js new file mode 100644 index 00000000..d11a90d3 --- /dev/null +++ b/client/src/structs/settings/types/securekvp.js @@ -0,0 +1,20 @@ +/** + * BetterDiscord Secure Key Value Pair Setting Struct + * 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 Kvp from './kvp'; + +export default class SecureKvpSetting extends Kvp { + /** + * The value to use when the setting doesn't have a value. + */ + get defaultValue() { + return { key: 'Key', value: '**********' }; + } +} diff --git a/client/src/styles/partials/bdsettings/button.scss b/client/src/styles/partials/bdsettings/button.scss index 9ab8ae99..e2fc7fb3 100644 --- a/client/src/styles/partials/bdsettings/button.scss +++ b/client/src/styles/partials/bdsettings/button.scss @@ -43,9 +43,22 @@ } } + &.bd-hide-button { + transition: opacity 0.4s ease-out; + opacity: 0; + + &.bd-active { + transition-timing-function: ease-in; + } + } + &.bd-active { - background: transparent; opacity: 1; + } + + &.bd-active, + &.bd-hide-button { + background: transparent; box-shadow: none; .bd-settings-button-btn { diff --git a/client/src/styles/partials/bdsettings/collection.scss b/client/src/styles/partials/bdsettings/collection.scss new file mode 100644 index 00000000..3f086070 --- /dev/null +++ b/client/src/styles/partials/bdsettings/collection.scss @@ -0,0 +1,57 @@ +.bd-formCollection { + display: flex; + flex-direction: column; + + div:first-child { + flex: 1 1 auto; + } + + .bd-collectionItem { + display: flex; + flex-grow: 1; + margin-top: 5px; + + .bd-removeCollectionItem { + width: 20px; + flex: 0 1 auto; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + margin-bottom: 30px; + + &:hover { + svg { + fill: #FFF; + } + } + + svg { + width: 16px; + height: 16px; + fill: #ccc; + } + } + } + + .bd-newCollectionItem { + display: flex; + cursor: pointer; + align-self: flex-end; + justify-content: center; + align-items: center; + margin-right: 2px; + + svg { + width: 16px; + height: 16px; + fill: #ccc; + } + + &:hover { + svg { + fill: #FFF; + } + } + } +} diff --git a/client/src/styles/partials/bdsettings/e2ee.scss b/client/src/styles/partials/bdsettings/e2ee.scss new file mode 100644 index 00000000..3fd121fe --- /dev/null +++ b/client/src/styles/partials/bdsettings/e2ee.scss @@ -0,0 +1,24 @@ +.bd-e2eeTaContainer { + display: flex; + + .bd-e2eeTaBtn { + padding: 10px; + display: flex; + flex: 1 1 auto; + flex-direction: row; + cursor: pointer; + + &.bd-e2eeLock { + fill: #cc3e3e; + } + } + + .bd-taDivider { + background-color: hsla(0,0%,100%,.1); + box-sizing: border-box; + height: 80%; + position: relative; + top: 10%; + width: 1px; + } +} diff --git a/client/src/styles/partials/bdsettings/index.scss b/client/src/styles/partials/bdsettings/index.scss index 56081af4..69a04481 100644 --- a/client/src/styles/partials/bdsettings/index.scss +++ b/client/src/styles/partials/bdsettings/index.scss @@ -8,3 +8,5 @@ @import './updater.scss'; @import './window-preferences'; @import './kvp'; +@import './collection'; +@import './e2ee'; diff --git a/client/src/styles/partials/bdsettings/sidebarview.scss b/client/src/styles/partials/bdsettings/sidebarview.scss index bdb1ae84..50a5e949 100644 --- a/client/src/styles/partials/bdsettings/sidebarview.scss +++ b/client/src/styles/partials/bdsettings/sidebarview.scss @@ -122,6 +122,9 @@ } } + &.bd-stop .bd-sidebar-region { + z-index: 3004; + } &.bd-stop .bd-content-region { z-index: 3003; } diff --git a/client/src/styles/partials/discordoverrides.scss b/client/src/styles/partials/discordoverrides.scss index 6d051e20..84a697a3 100644 --- a/client/src/styles/partials/discordoverrides.scss +++ b/client/src/styles/partials/discordoverrides.scss @@ -1,19 +1,19 @@ -[class*="guildsWrapper-"] { - padding-top: 49px !important; - - .platform-osx & { +body:not(.bd-hide-button) { + [class*="guildsWrapper-"] { + padding-top: 49px !important; + } + .platform-osx [class*="guildsWrapper-"] { margin-top: 26px; } -} -[class*="guildsWrapper-"] + [class*="flex"] { - border-radius: 0 0 0 5px; -} + [class*="guildsWrapper-"] + [class*="flex"] { + border-radius: 0 0 0 5px; + } -[class*="unreadMentionsIndicatorTop-"] { - top: 49px; - - .platform-osx & { + [class*="unreadMentionsIndicatorTop-"] { + top: 49px; + } + .platform-osx [class*="unreadMentionsIndicatorTop-"] { top: 50px; } } diff --git a/client/src/styles/partials/sidebarview/content.scss b/client/src/styles/partials/sidebarview/content.scss index 626230dc..949742fc 100644 --- a/client/src/styles/partials/sidebarview/content.scss +++ b/client/src/styles/partials/sidebarview/content.scss @@ -5,27 +5,28 @@ flex-grow: 1; backface-visibility: hidden; - > div { + > * { display: flex; flex-direction: column; flex-grow: 1; - } - > div:not(.active) { - opacity: 0; - position: absolute; - left: 310px; - right: 0; - // width: 100%; - height: 100%; - pointer-events: none; - } -} - -.bd-content { - animation: bd-fade-in .4s forwards; - - .animating { - animation: bd-fade-out .4s forwards; + &.bd-contentcolumn-enter-active, + &.bd-contentcolumn-leave-active { + transition: opacity 0.4s ease; + } + + &.bd-contentcolumn-enter-to { + opacity: 1; + } + + &.bd-contentcolumn-leave-to { + opacity: 0; + } + + &.bd-contentcolumn-leave-active { + position: absolute; + width: 590px; + pointer-events: none; + } } } diff --git a/client/src/ui/bdui.js b/client/src/ui/bdui.js index 4cf25833..f1d22464 100644 --- a/client/src/ui/bdui.js +++ b/client/src/ui/bdui.js @@ -8,7 +8,7 @@ * LICENSE file in the root directory of this source tree. */ -import { Events, DiscordApi } from 'modules'; +import { Events, DiscordApi, Settings } from 'modules'; import { remote } from 'electron'; import DOM from './dom'; import Vue from './vue'; @@ -17,6 +17,13 @@ import { BdSettingsWrapper, BdModals, BdToasts } from './components'; export default class { static initUiEvents() { + const hideButtonSetting = Settings.getSetting('ui', 'default', 'hide-button'); + hideButtonSetting.on('setting-updated', event => { + if (event.value) document.body.classList.add('bd-hide-button'); + else document.body.classList.remove('bd-hide-button'); + }); + if (hideButtonSetting.value) document.body.classList.add('bd-hide-button'); + this.pathCache = { isDm: null, server: DiscordApi.currentGuild, @@ -46,22 +53,16 @@ export default class { DOM.createElement('div', null, 'bd-toasts').appendTo(DOM.bdToasts); DOM.createElement('bd-tooltips').appendTo(DOM.bdBody); - this.toasts = new Vue({ - el: '#bd-toasts', - components: { BdToasts }, - template: '' + this.toasts = new (Vue.extend(BdToasts))({ + el: '#bd-toasts' }); - this.modals = new Vue({ - el: '#bd-modals', - components: { BdModals }, - template: '' + this.modals = new (Vue.extend(BdModals))({ + el: '#bd-modals' }); - this.vueInstance = new Vue({ - el: '#bd-settings', - components: { BdSettingsWrapper }, - template: '' + this.vueInstance = new (Vue.extend(BdSettingsWrapper))({ + el: '#bd-settings' }); return this.vueInstance; diff --git a/client/src/ui/classnormaliser.js b/client/src/ui/classnormaliser.js index f8509bf4..52cf0bf7 100644 --- a/client/src/ui/classnormaliser.js +++ b/client/src/ui/classnormaliser.js @@ -29,8 +29,8 @@ export default class ClassNormaliser extends Module { shouldIgnore(value) { if (!isNaN(value)) return true; if (value.endsWith('px') || value.endsWith('ch') || value.endsWith('em') || value.endsWith('ms')) return true; - if (value.startsWith('rgba')) return true; - if (value.includes('calc(')) return true; + if (value.startsWith('#') && (value.length == 7 || value.length == 4)) return true; + if (value.includes('calc(') || value.includes('rgba')) return true; return false; } diff --git a/client/src/ui/components/BdSettings.vue b/client/src/ui/components/BdSettings.vue index ab77f5b3..6ef44176 100644 --- a/client/src/ui/components/BdSettings.vue +++ b/client/src/ui/components/BdSettings.vue @@ -9,8 +9,8 @@ */