Merge pull request #1 from JsSucks/security

Security
This commit is contained in:
Mega-Mewthree 2018-08-10 09:39:53 -07:00 committed by GitHub
commit 7899312e73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 519 additions and 147 deletions

View File

@ -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();
}
}

View File

@ -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.
*/
<template>
<div class="bd-e2eeTaContainer">
<div class="bd-e2eeTaBtn bd-e2eeLock">
<MiLock v-tooltip="'E2EE'" />
</div>
<div class="bd-taDivider"></div>
</div>
</template>
<script>
import { MiLock } from '../ui/components/common/MaterialIcon';
export default {
components: { MiLock },
data() {
return {};
},
methods: {}
}
</script>

View File

@ -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();
}
}

View File

@ -171,10 +171,9 @@
"type": "drawer",
"settings": [
{
"id": "kvp0",
"type": "kvp",
"text": "",
"value": { "key": "kvpKey", "value": "kvpValue" }
"id": "e2ekvps",
"type": [ "securekvp" ],
"value": []
}
]
}

View File

@ -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);
};
}
}

View File

@ -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`};
}

View File

@ -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;
}
}

View File

@ -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';

View File

@ -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' };
}
}

View File

@ -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: '**********' };
}
}

View File

@ -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 {

View File

@ -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;
}
}
}
}

View File

@ -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;
}
}

View File

@ -8,3 +8,5 @@
@import './updater.scss';
@import './window-preferences';
@import './kvp';
@import './collection';
@import './e2ee';

View File

@ -122,6 +122,9 @@
}
}
&.bd-stop .bd-sidebar-region {
z-index: 3004;
}
&.bd-stop .bd-content-region {
z-index: 3003;
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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: '<BdToasts />'
this.toasts = new (Vue.extend(BdToasts))({
el: '#bd-toasts'
});
this.modals = new Vue({
el: '#bd-modals',
components: { BdModals },
template: '<BdModals />'
this.modals = new (Vue.extend(BdModals))({
el: '#bd-modals'
});
this.vueInstance = new Vue({
el: '#bd-settings',
components: { BdSettingsWrapper },
template: '<BdSettingsWrapper />'
this.vueInstance = new (Vue.extend(BdSettingsWrapper))({
el: '#bd-settings'
});
return this.vueInstance;

View File

@ -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;
}

View File

@ -9,8 +9,8 @@
*/
<template>
<div class="bd-settings" :class="{active: active, 'bd-settings-out': activeIndex === -1 && lastActiveIndex >= 0}" @keyup="$emit('close')">
<SidebarView :contentVisible="this.activeIndex >= 0 || this.lastActiveIndex >= 0" :animating="this.animating" :class="{'bd-stop': !first}">
<div class="bd-settings" :class="{active, 'bd-settings-out': !item && animating}" @keyup="$emit('close')">
<SidebarView :contentVisible="item" :animating="animating" :class="{'bd-stop': item}">
<Sidebar slot="sidebar">
<div class="bd-settings-x" @click="$emit('close')">
<MiClose size="17"/>
@ -18,9 +18,10 @@
</div>
<template v-for="(category, text) in sidebar">
<SidebarItem :item="{text, type: 'header'}" />
<SidebarItem v-for="item in category" :item="item" :key="item.id" @click="itemOnClick(item.id)" />
<SidebarItem v-for="i in category" :key="i.id" :item="i" :active="item && i.id === item.id" @click="itemOnClick(i.id)" />
</template>
</Sidebar>
<div slot="sidebarfooter" class="bd-info">
<span class="bd-vtext">v2.0.0a by Jiiks/JsSucks</span>
<div @click="openGithub" v-tooltip="'GitHub'" class="bd-material-button">
@ -33,25 +34,28 @@
<MiWeb size="16" />
</div>
</div>
<ContentColumn slot="content">
<div v-for="item in sidebarItems" v-if="activeContent(item.contentid) || animatingContent(item.contentid)" :class="{active: activeContent(item.contentid), animating: animatingContent(item.contentid)}">
<template v-if="item.component">
<component :is="item.component" :SettingsWrapper="SettingsWrapper" />
</template>
<transition name="bd-contentcolumn" @before-enter="animating++" @after-enter="animating--" @enter-cancelled="animating--" @before-leave="animating++" @after-leave="animating--" @leave-cancelled="animating--">
<div v-if="item" :key="item.id">
<template v-if="item.component">
<component :is="item.component" :SettingsWrapper="SettingsWrapper" />
</template>
<SettingsWrapper v-if="typeof item.set === 'string'" :headertext="Settings.getSet(item.set).headertext">
<SettingsPanel :settings="Settings.getSet(item.set)" :schemes="Settings.getSet(item.set).schemes" />
</SettingsWrapper>
<SettingsWrapper v-else-if="item.set" :headertext="item.set.headertext">
<SettingsPanel :settings="item.set" :schemes="item.set.schemes" />
</SettingsWrapper>
<SettingsWrapper v-else-if="typeof item.set === 'string'" :headertext="Settings.getSet(item.set).headertext">
<SettingsPanel :settings="Settings.getSet(item.set)" :schemes="Settings.getSet(item.set).schemes" />
</SettingsWrapper>
<SettingsWrapper v-else-if="item.set" :headertext="item.set.headertext">
<SettingsPanel :settings="item.set" :schemes="item.set.schemes" />
</SettingsWrapper>
<ConnectivityView v-if="item.contentid === 'connectivity'"/>
<CssEditorView v-if="item.contentid === 'css'" />
<PluginsView v-if="item.contentid === 'plugins'" />
<ThemesView v-if="item.contentid === 'themes'" />
<UpdaterView v-if="item.contentid === 'updater'" />
</div>
<ConnectivityView v-else-if="item.contentid === 'connectivity'" />
<CssEditorView v-else-if="item.contentid === 'css'" />
<PluginsView v-else-if="item.contentid === 'plugins'" />
<ThemesView v-else-if="item.contentid === 'themes'" />
<UpdaterView v-else-if="item.contentid === 'updater'" />
</div>
</transition>
</ContentColumn>
</SidebarView>
</div>
@ -69,13 +73,10 @@
export default {
data() {
return {
BdMenuItems,
activeIndex: -1,
lastActiveIndex: -1,
animating: false,
first: true,
animating: 0,
item: null,
items: BdMenuItems.items,
Settings,
timeout: null,
SettingsWrapper,
openMenuHandler: null
};
@ -87,12 +88,9 @@
MiGithubCircle, MiWeb, MiClose, MiTwitterCircle
},
computed: {
sidebarItems() {
return this.BdMenuItems.items;
},
sidebar() {
const categories = {};
for (let item of this.sidebarItems) {
for (let item of this.items) {
if (item.hidden) continue;
const category = categories[item.category] || (categories[item.category] = []);
category.push(item);
@ -102,42 +100,10 @@
},
methods: {
itemOnClick(id) {
if (this.animating || id === this.activeIndex) return;
const activeItem = this.sidebarItems.find(item => item.id === this.activeIndex);
if (activeItem) activeItem.active = false;
this.sidebarItems.find(item => item.id === id).active = true;
this.animating = true;
this.lastActiveIndex = this.activeIndex;
this.activeIndex = id;
if (this.timeout) clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
this.first = false;
this.animating = false;
this.lastActiveIndex = -1;
this.timeout = null;
}, 400);
},
activeContent(s) {
const item = this.sidebarItems.find(item => item.contentid === s);
return item && item.id === this.activeIndex;
},
animatingContent(s) {
const item = this.sidebarItems.find(item => item.contentid === s);
return item && item.id === this.lastActiveIndex;
this.item = this.items.find(item => item.id === id);
},
closeContent() {
if (this.activeIndex >= 0) this.sidebarItems.find(item => item.id === this.activeIndex).active = false;
this.first = true;
this.lastActiveIndex = this.activeIndex;
this.activeIndex = -1;
if (this.timeout) clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
this.animating = false;
this.lastActiveIndex = -1;
this.timeout = null;
}, 400);
this.item = null;
},
openGithub() {
shell.openExternal('https://github.com/JsSucks/BetterDiscordApp');
@ -156,7 +122,7 @@
}
},
created() {
Events.on('bd-open-menu', this.openMenuHandler = item => item && this.itemOnClick(this.sidebarItems.find(i => i === item || i.id === item || i.contentid === item || i.set === item).id));
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));
},
destroyed() {
if (this.openMenuHandler) Events.off('bd-open-menu', this.openMenuHandler);

View File

@ -9,8 +9,8 @@
*/
<template>
<div class="bd-settings-wrapper" :class="[{active: active}, 'platform-' + this.platform]">
<div class="bd-settings-button" :class="{'bd-active': active, 'bd-animating': animating}" @click="active = true">
<div class="bd-settings-wrapper" :class="[{active}, 'platform-' + this.platform]">
<div class="bd-settings-button" :class="{'bd-active': active, 'bd-animating': animating, 'bd-hide-button': hideButton}" @click="active = true">
<div v-if="updating === 0" v-tooltip.right="'Checking for updates'" class="bd-settings-button-btn bd-loading"></div>
<div v-else-if="updating === 2" v-tooltip.right="'Updates available!'" class="bd-settings-button-btn bd-updates"></div>
<div v-else class="bd-settings-button-btn" :class="[{'bd-loading': !loaded}]"></div>
@ -36,7 +36,9 @@
timeout: null,
platform: process.platform,
eventHandlers: {},
keybindHandler: null
keybindHandler: null,
hideButton: false,
hideButtonToggleHandler: null
};
},
components: {
@ -80,6 +82,10 @@
const menuKeybind = Settings.getSetting('core', 'default', 'menu-keybind');
menuKeybind.on('keybind-activated', this.keybindHandler = () => this.active = !this.active);
const hideButtonSetting = Settings.getSetting('ui', 'default', 'hide-button');
hideButtonSetting.on('setting-updated', this.hideButtonToggleHandler = event => this.hideButton = event.value);
this.hideButton = hideButtonSetting.value;
},
destroyed() {
for (let event in this.eventHandlers) Events.off(event, this.eventHandlers[event]);
@ -89,7 +95,12 @@
if (this.keybindHandler) {
const menuKeybind = Settings.getSetting('core', 'default', 'menu-keybind');
menuKeybind.removeListener('keybind-activated', this.keybindHandler = () => this.active = !this.active);
menuKeybind.removeListener('keybind-activated', this.keybindHandler);
}
if (this.hideButtonToggleHandler) {
const hideButtonSetting = Settings.getSetting('ui', 'default', 'hide-button');
hideButtonSetting.removeListener('setting-updated', this.hideButtonToggleHandler);
}
}
}

View File

@ -0,0 +1,43 @@
/**
* BetterDiscord Collection Setting 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-formCollection">
<div v-for="s in setting.items" class="bd-collectionItem">
<Setting :setting="s" :key="s.id" />
<div class="bd-removeCollectionItem" @click="removeItem(s)"><MiMinus/></div>
</div>
<div class="bd-newCollectionItem" @click="addItem"><MiPlus/></div>
</div>
</template>
<script>
import Setting from './Setting.vue';
import { MiMinus, MiPlus } from '../../common';
export default {
props: ['setting'],
components: {
MiMinus, MiPlus
},
methods: {
removeItem(item) {
this.setting.removeItem(item);
},
addItem() {
this.setting.addItem();
}
},
beforeCreate() {
// https://vuejs.org/v2/guide/components.html#Circular-References-Between-Components
this.$options.components.Setting = Setting;
}
}
</script>

View File

@ -12,12 +12,11 @@
<div class="bd-formKvp">
<div class="bd-formKvpDetails">
<div class="bd-inputWrapper">
<input type="text" class="bd-textInput" :value="setting.value.key" />
<input type="text" class="bd-textInput" :value="setting.value.key" @keyup.stop @input="keyChange"/>
</div>
<div class="bd-inputWrapper">
<input type="text" class="bd-textInput" :value="setting.value.value" />
<input type="text" class="bd-textInput" :value="setting.value.value" @keyup.stop @input="valueChange"/>
</div>
<!-- using a text field is temporary -->
</div>
</div>
</template>
@ -25,7 +24,13 @@
<script>
export default {
props: ['setting'],
methods: {},
mounted() {console.log('setting', this.setting)}
methods: {
keyChange(e) {
this.setting.value = { key: e.target.value, value: this.setting.value.value }
},
valueChange(e) {
this.setting.value = { key: this.setting.value.key, value: e.target.value }
}
}
}
</script>

View File

@ -0,0 +1,57 @@
/**
* BetterDiscord Setting Secure Key Value Pair 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-formKvp">
<div class="bd-formKvpDetails">
<div class="bd-inputWrapper">
<input type="text" class="bd-textInput" :value="setting.value.key" @keyup.stop="keyUpKey" @input="keyChange" />
</div>
<div class="bd-inputWrapper">
<input type="password" class="bd-textInput" :value="setting.value.value" @keyup.stop="keyUpValue" @blur="valueBlur" @input="valueChange" ref="valueInput" />
</div>
</div>
</div>
</template>
<script>
import aes256 from 'aes256';
export default {
data() {
return {
masterKey: 'temporarymasterkey',
valueChanged: false
}
},
props: ['setting'],
methods: {
keyChange(e) {
this.setting.value = { key: e.target.value, value: this.setting.value.value }
},
valueChange(e) {
this.valueChanged = true;
},
valueBlur(e) {
if (!this.valueChanged) return;
const value = aes256.encrypt(this.masterKey, e.target.value);
this.setting.value = { key: this.setting.value.key, value }
this.valueChanged = false;
},
keyUpKey(e) {
if (e.key !== 'Enter') return;
this.$refs.valueInput.focus();
},
keyUpValue(e) {
if (e.key !== 'Enter') return;
e.target.blur();
}
}
}
</script>

View File

@ -11,19 +11,21 @@
<template>
<div class="bd-form-item" :class="{'bd-form-item-changed': setting.changed, 'bd-disabled': disabled, 'bd-form-item-noheader': !setting.text, 'bd-form-item-fullwidth': setting.fullwidth}">
<BoolSetting v-if="setting.type === 'bool'" :setting="setting" />
<StringSetting v-if="setting.type === 'text' && !setting.multiline" :setting="setting" />
<MultilineTextSetting v-if="setting.type === 'text' && setting.multiline" :setting="setting" />
<NumberSetting v-if="setting.type === 'number'" :setting="setting" />
<DropdownSetting v-if="setting.type === 'dropdown'" :setting="setting" />
<RadioSetting v-if="setting.type === 'radio'" :setting="setting" />
<SliderSetting v-if="setting.type === 'slider'" :setting="setting" />
<ColourSetting v-if="setting.type === 'colour'" :setting="setting" />
<KeybindSetting v-if="setting.type === 'keybind'" :setting="setting" />
<FileSetting v-if="setting.type === 'file'" :setting="setting" />
<GuildSetting v-if="setting.type === 'guild'" :setting="setting" />
<ArraySetting v-if="setting.type === 'array'" :setting="setting" />
<KeyValuePair v-if="setting.type === 'kvp'" :setting="setting"/>
<CustomSetting v-if="setting.type === 'custom'" :setting="setting" />
<StringSetting v-else-if="setting.type === 'text' && !setting.multiline" :setting="setting" />
<MultilineTextSetting v-else-if="setting.type === 'text' && setting.multiline" :setting="setting" />
<NumberSetting v-else-if="setting.type === 'number'" :setting="setting" />
<DropdownSetting v-else-if="setting.type === 'dropdown'" :setting="setting" />
<RadioSetting v-else-if="setting.type === 'radio'" :setting="setting" />
<SliderSetting v-else-if="setting.type === 'slider'" :setting="setting" />
<ColourSetting v-else-if="setting.type === 'colour'" :setting="setting" />
<KeybindSetting v-else-if="setting.type === 'keybind'" :setting="setting" />
<FileSetting v-else-if="setting.type === 'file'" :setting="setting" />
<GuildSetting v-else-if="setting.type === 'guild'" :setting="setting" />
<ArraySetting v-else-if="setting.type === 'array'" :setting="setting" />
<Collection v-else-if="setting.type === 'collection'" :setting="setting" />
<KeyValuePair v-else-if="setting.type === 'kvp'" :setting="setting" />
<SecureKeyValuePair v-else-if="setting.type === 'securekvp'" :setting="setting" />
<CustomSetting v-else-if="setting.type === 'custom'" :setting="setting" />
<div class="bd-form-divider"></div>
</div>
</template>
@ -42,7 +44,9 @@
import FileSetting from './File.vue';
import GuildSetting from './Guild.vue';
import ArraySetting from './Array.vue';
import Collection from './Collection.vue';
import KeyValuePair from './KeyValuePair.vue';
import SecureKeyValuePair from './SecureKeyValuePair.vue';
import CustomSetting from './Custom.vue';
export default {
@ -62,7 +66,9 @@
FileSetting,
GuildSetting,
ArraySetting,
Collection,
KeyValuePair,
SecureKeyValuePair,
CustomSetting
},
computed: {

View File

@ -18,3 +18,4 @@ export { default as MiInfo } from './materialicons/Info.vue';
export { default as MiWarning } from './materialicons/Warning.vue';
export { default as MiSuccess } from './materialicons/Success.vue';
export { default as AccountCircle } from './materialicons/AccountCircle.vue';
export { default as MiLock } from './materialicons/Lock.vue';

View File

@ -0,0 +1,27 @@
/**
* BetterDiscord Material Design Icon
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
* All rights reserved.
* https://betterdiscord.net
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* Material Design Icons
* Copyright (c) 2014 Google
* Apache 2.0 LICENSE
* https://www.apache.org/licenses/LICENSE-2.0.txt
*/
<template>
<span class="bd-material-design-icon">
<svg :width="size || 24" :height="size || 24" viewBox="0 0 24 24">
<path fill-opacity="1" stroke-width="0.2" stroke-linejoin="round" d="M 11.9994,16.998C 13.1044,16.998 13.9994,16.1021 13.9994,14.998C 13.9994,13.894 13.1044,12.998 11.9994,12.998C 10.8954,12.998 9.9994,13.894 9.9994,14.998C 9.9994,16.1021 10.8954,16.998 11.9994,16.998 Z M 17.9994,7.99813C 19.1034,7.99813 19.9994,8.89413 19.9994,9.99813L 19.9994,19.9981C 19.9994,21.1021 19.1034,21.9981 17.9994,21.9981L 5.99938,21.9981C 4.89539,21.9981 3.99938,21.1021 3.99938,19.9981L 3.99938,9.99813C 3.99938,8.89413 4.89539,7.99813 5.99938,7.99813L 6.99938,7.99813L 6.99938,5.99813C 6.99938,3.23714 9.23838,0.998133 11.9994,0.998133C 14.7604,0.998133 16.9994,3.23714 16.9994,5.99813L 16.9994,7.99813L 17.9994,7.99813 Z M 12,3C 10.3431,3 9,4.34315 9,6L 9,8L 15,8L 15,6C 15,4.34315 13.6569,3 12,3 Z " />
</svg>
</span>
</template>
<script>
export default {
props: ['size']
}
</script>

View File

@ -9,13 +9,13 @@
*/
<template>
<div class="bd-item" :class="{active: item.active}" @click="$emit('click', item.id)">
<div class="bd-item" :class="{active}" @click="$emit('click', item.id)">
{{item.text}}
</div>
</template>
<script>
export default {
props: ['item']
props: ['item', 'active']
}
</script>

View File

@ -9,7 +9,7 @@
*/
<template>
<SidebarButton v-if="item._type === 'button'" :item="item" @click="$emit('click', $event)" />
<SidebarButton v-if="item._type === 'button'" :item="item" :active="active" @click="$emit('click', $event)" />
<SidebarHeader v-else :item="item" />
</template>
@ -18,7 +18,7 @@
import { SidebarHeader, SidebarButton } from './';
export default {
props: ['item'],
props: ['item', 'active'],
components: {
SidebarHeader,
SidebarButton

View File

@ -3,4 +3,4 @@ export { default as Sidebar } from './Sidebar.vue';
export { default as SidebarHeader } from './Header.vue';
export { default as SidebarButton } from './Button.vue';
export { default as SidebarItem } from './Item.vue';
export { default as ContentColumn } from './ContentColumn.vue';
export { default as ContentColumn } from './ContentColumn.vue';

6
package-lock.json generated
View File

@ -55,6 +55,12 @@
}
}
},
"aes256": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/aes256/-/aes256-1.0.4.tgz",
"integrity": "sha512-yuaKOdoKebChkP+uRsQovWsJYm6qf58cQTvXBu6MM0BMnrXZ9SXEiXREBuU513ZRr+Uo2qX/Ci6EODLUu/qiHA==",
"dev": true
},
"ajv": {
"version": "5.5.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",

View File

@ -22,6 +22,7 @@
"node-sass": "^4.9.2"
},
"devDependencies": {
"aes256": "^1.0.4",
"archiver": "^2.1.1",
"babel-core": "^6.26.3",
"babel-loader": "^7.1.5",